[번역] 실버라이트에서의 UI Automation - 사용자 인터랙션 시뮬레이션
이 글은 다음 링크의 글을 번역한 글입니다.
http://blogs.msdn.com/gisenberg/archive/2008/07/12/ui-automation-in-silverlight-simulating-user-interactions.aspx
----------------------------------------------------------------------------------------------------
최근에 저는 한 임시 그룹에서 실버라이트 리치 인터넷 에플리케이션(RIAs)의 자동화에 대한 일을 맡았습니다. 공중에 발표된 몇가지 툴들은 이 점에서 제한된 도움만을 제공해주고 있습니다. 예를 들면 실버라이트는 컨트롤을 제작 중 Unit Test를 사용할 수 있습니다. 다음 링크에서 좀 더 자세한 것을 알 수 있습니다.
http://www.jeff.wilcox.name/2008/03/31/silverlight2-unit-testing/
불행하게도 저의 요구사항은 자동화 시나리오를 가능하게 하는 것입니다. 우리는 RIA의 안 밖에서 마우스를 움직이거나, 어떤 것을 클릭하거나, 제 3자의 인증공급자로 간다거나, 어떤 키들을 입력하는 등의 몇몇 사용자 흐름을 시뮬레이션 해야만 합니다.
실버라이트에서의 UI 자동화는 여기선 뜨겁게 떠오르는 주제가 되었습니다. 실버라이트 Beta2의 발표와 함께 우리는 갈만한 셋길을 발견하기 시작했습니다. 보다 정확히, 우리는 UI자동화를 하는 WPF의 방법을 조금씩 볼 수 있게 되었습니다. 여러분이 만약 이 글을 같이 따라오기 원한다면 UISpy를 설치하셔야 할 것입니다.(http://blogs.msdn.com/windowssdk/archive/2008/02/18/where-is-uispy-exe.aspx).
마이크로소프트 UI 자동화 어셈블리는 .Net 프레임워크 3.0 과 함께 릴리즈 되었습니다. 전통적으로 우리는 실버라이트 에플리케이션와 함께 아주 먼곳에 있지 않은 Microsoft Active Accessibility(MSAA)와 작동하는 다양한 COM 랩퍼(wrappers)를 가지고 있습니다. 만약 WPF에서 UI 자동화를 조금이라도 해보았다면 앞으로 편하게 이해하실 수 있을 겁니다. 그래서 더 깊게 고려하지 않고 바로 UIA 작업을 시작해보도록 하겠습니다.
첫번째로 Visual Studio 2008 에서 새로운 테스트 프로젝트를 시작합니다. 실버라이트에서 UI 자동화 에플리케이션을 시작하기위해 필요한 UIA 네임스페이스는 System.Windows.Automation과 System.Windows.Automation.Providers 입니다. 그리고 관련된 부분을 얻기 위해 닷넷 3.0 이상에 포함된 다음 어셈블리들을 참조로 추가 해야만합니다.
- UIAutomationProvider.dll
- UIAutomationClient.dll
- UIAutomationClientsideProviders.dll
- UIAutomationTypes.dll
실버라이트 커스텀 컨트롤의 자동화가 필요하다고 합시다. 컨트롤의 접근가능한 기능들을 조정하기 위한 Peer Type을 반환해주기 위해 (Control 클래스로 부터) OnCreateAutomationPeer 메소드를 오버라이드 해야만 합니다. 접근가능한 기능(?)들은 에플리케이션을 자동화할 수 있게 하는 Key가 될 것이기 때문에 이 작업은 매우 중요합니다.
텍스트 박스와 검색버튼으로 이루어진 가상의 검색 컨트롤을 가정합니다.
public partial class SearchBar : Control
{
...
public SearchBar()
{
this.GotFocus += (sender, args)
=>
{
this.SearchText.Focus();
};
InitializeComponent();
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new SearchBarAutomationPeer(this);
}
}
여기서 Override한 OnCreateAutomationPeer는 접근 가능 기능들을 위한 컨트롤 트리를 감시하는 것(결론적으로 automation 함수들)에 의해 불려지게 될 것입니다. Peer객체는 접근성이 필요한 것에 밀착하는 의미로(?) 컨트롤의 조합을 반환할 책임을 지게됩니다.( The Peer object will be responsible for returning your combination of controls in a manner that is coherent to anything that needs accessibility. )
또 여기서 이 Control의 GotFocus 핸들러를 컨트롤의 default .Focus() 동작에 설정하기 위해 엮어두었습니다.
다음으로 SearchBarAutomationPeer 클래스의 구현 부분을 봅시다.
public class SearchBarAutomationPeer : FrameworkElementAutomationPeer, IValueProvider
{
public SearchBarAutomationPeer(SearchBar searchBar) : base(searchBar)
{
}
Peer 클래스는 작업하는데 필요할 모든 메소드를 제공하는 FrameworkElementAutomationPeer 로 부터 상속 받아야 합니다. 그리고 이 컨트롤의 구성요소중 TextBox와의 인터랙션을 위해 IValueProvider 와 맵핑이 필요합니다. 다음 링크에서 Provider 인터페이스와 개개의 구성요소와의 맵핑에 관해 더 알 수 있습니다.
http://msdn.microsoft.com/en-us/library/system.windows.automation.provider.aspx
컨트롤 트리에서 우리 컨트롤을 찾기 위해선 컨트롤에 클래스 이름과 접근 가능한 구별자(accessibility identifier)를 주는 것이 필요합니다. 이것을 하기 위해서는 FrameworkElementAutomationPeer의 GetAutomationIdCore() 와 GetClassNameCore() 함수를 오버라이드 해야만 합니다.
protected override string GetAutomationIdCore()
{
return "SearchBar"; // You're going to want to make this unique. ;)
}
protected override string GetClassNameCore()
{
return "SearchBar";
}
protected override bool IsKeyboardFocusableCore()
{
return true;
}
IsKeyboardFocusableCore 는 꼭 override해야만 함수로 만약 이 함수가 없다면 컨트롤의 SetFocus() 함수의 호출이 실패하게 될 것입니다. 이제 Provider 인터페이스의 구현에 대해서도 생각해봐야 합니다. 생성자에서 건네 받은 SearchBar는 base.Owner 프로퍼티를 통해 얻어 올 수 있습니다. base.Owner로 부터 SearchBar를 매번 캐스팅 하는 단조로움을 피하기 위해 프로퍼티를 하나 추가할 것입니다.
public SearchBar SearchBar
{
get
{
return (SearchBar)base.Owner;
}
}
#region IValueProvider Members
public bool IsReadOnly
{
get
{
return this.SearchBar.SearchText.IsReadOnly;
}
}
public void SetValue(string value)
{
this.SearchBar.SearchText.Text = value;
}
public string Value
{
get
{
return this.SearchBar.SearchText.Text;
}
}
#endregion
이제 UISpy로 우리 컨트로을 보게 되면 다음과 같이 볼 수 있을 겁니다.
Identification
ClassName: "SearchBar"
ControlType: "ControlType.Custom"
Culture: "(null)"
AutomationId: "SearchBar"
LocalizedControlType: "custom"
Name: "SearchBar"
ProcessId: "2276 (iexplore)"
RuntimeId: "42 197110 6"
IsPassword: "False"
IsControlElement: "True"
IsContentElement: "True"
Visibility
BoundingRectangle: "(356, 286, 949, 36)"
ClickablePoint: "830,304"
IsOffscreen: "False"
ControlPatterns
Value
Value: ""
IsReadOnly: "False"
ControlPatterns 아래 "Vaule" 프로퍼티는 자동적으로 IValueProvider 인터페이스로 인해 우리 컨트롤의 TextBox 의 Vaule와 맵핑됩니다. 깔끔하죠?
이제 이 커스텀 컨트롤의 배관이 가능하게 되었습니다. 이제 TestMethod를 살펴봅시다.
[TestMethod]
public void TestMethod1()
{
Process process = System.Diagnostics.Process.GetProcessesByName("iexplore").First();
AutomationElement browserInstance = System.Windows.Automation.AutomationElement.FromHandle(process.MainWindowHandle);
TreeWalker tw = new TreeWalker(new PropertyCondition(AutomationElement.ClassNameProperty, "SearchBar"));
AutomationElement searchBar = tw.GetFirstChild(browserInstance);
myElement.SetFocus();
Thread.Sleep(1000);
searchBar.SetFocus();
Thread.Sleep(1000);
SendKeys.SendWait("Hello, world!");
}
이 부분에서 몇가지 질문이 생기는 분이 계실지 모르겠습니다. 예를 들면 "왜 내가 IValueProvider를 구현했을까?". 위의 코드 조각은 사용자 입력을 시뮬레이션 합니다. 먄약 저것이 우리 것이 아니라면 ValuePattern 으로 부터 와야합니다. 개인적으로, 저는 ValuePattern/TryGetCurrentPattern/etc 의 상호작용을 알아냈고 꽤 거추장스러운 전체 경험을 발견했습니다. 밑에 코드에서 제가 뜻하는 바를 알 수 있을 겁니다.
[TestMethod]
public void TestMethod1()
{
Process process = System.Diagnostics.Process.GetProcessesByName("iexplore").First();
AutomationElement myElement = System.Windows.Automation.AutomationElement.FromHandle(process.MainWindowHandle);
TreeWalker tw = new TreeWalker(new PropertyCondition(AutomationElement.ClassNameProperty, "SearchBar"));
AutomationElement searchBar = tw.GetFirstChild(myElement);
object valuePattern;
searchBar.TryGetCurrentPattern(ValuePattern.Pattern, out valuePattern);
((ValuePattern)valuePattern).SetValue("Hello, world!");
}
이글이 결코 이해하기 좋은 가이드라인은 아니지만 UI 자동화가 어떻게 진행되는지 관심이 있는 몇몇 분들에게는 충분할 것이라는 생각이 듭니다.
----------------------------------------------------------------------------------------------------------
흠냐... 번역이 좀 엉터리인 부분이 있고.. 글도 생각보다 어려워서 좀 걱정이 되는군요. 또 마지막 부분은 저도 이해하지 못한 부분이고 구현도 되지 않아서 여러분의 도움을 요청합니다.^^;;
일단 따라해본 샘플 프로젝트를 첨부합니다.
Part2 가 있어서 조금더 쉽게 구현하는 방법이 소개 되어있으니 그 부분만 따라하셔도 아마 자동화 부분은 해결하실 수 있으실 것이라 봅니다. 그럼..^^
- smile -