MS UI Automation Introduction
2014-09-17
MS UI Automation是什么
UIA架构
UI自动化模型
UI自动化树概述
UI自动化控件模式概述
UI 自动化属性概述
UI 自动化事件概述
示例
使用UISpy工具
UI自动化提供者
常见问题分析解决
控件无法识别
Timing issue
本地化问题
自动化技术和自动化框架
参考
MS UI Automation是什么[1]
返回
UI Automation 就是用另一个程序来控制UI 程序,模拟用户操作。他包含三步骤:
- 找到UI 元素
- 模拟用户操作
- 检查UI属性和行为
微软UI Automation技术提供了很好的实现模型。简单来讲,它就是几个dll,提供了一套API及其相应的模式,让软件的开发者遵循该模式去实现相应的interface,从而使测试人员更方便的编写UI Automation。
UIA架构[1]
返回
MS UIA明确定义了两个role:UIA Provider即软件本身,也可成为服务器端,UIA Client即自动化脚本和相关的assistive technology applications,见图1。
- UIA Provider: 开发人员确定控件行为并实现对应的UIA control pattern(注意:对于标准控件而言,默认是支持UIA的,而对于自定义的控件,需要实现该控件的行为对应于UIA所定义的interface。)
- UIA Client:相对而言,UIA Client则简单了很多,只需调用相关的UIA API去完成自动化测试脚本。
图1 架构简易图
图2 官方架构图
在图2中,Applications是软件本身,也可成为服务器端,Accessibility Tool是客户端,即测试软件或测试工具(如UISpy.exe)。
从图2可得知UIAutomation Core是通信基础代码,而且Applications和Accessibility Tool是通过管道通信的。
UIA主要有4个组件:
组件 |
描述 |
---|---|
提供程序 API(UIAutomationProvider.dll 和 UIAutomationTypes.dll) |
定义了各种行为的interface,例如,假设有个自定义的控件,开发人员觉得它需要支持Dock行为,就需要实现IDockProvider接口。 |
客户端 API(UIAutomationClient.dll 和 UIAutomationTypes.dll) |
定义了各种控件模式,以及一些用来支持更好的定位控件的辅助条件搜索类 |
UiAutomationCore.dll |
处理提供程序与客户端之间的通信的基础代码(有时也称为 UI 自动化核心)。 |
UIAutomationClientsideProviders.dll |
一组用于标准旧版本控件的 UI 自动化提供程序。(WPF 控件为 UI 自动化提供本机支持。)此支持自动提供给客户端应用程序。 |
图3 UIA dll 使用
常用命名空间:
namespace |
引用的 DLL |
读者 |
---|---|---|
System.Windows.Automation |
UIAutomationClient;UIAutomationTypes |
UI 自动化客户端开发人员;用于查找 AutomationElement 对象、注册 UI 自动化事件以及与 UI 自动化控件模式一起使用。 |
System.Windows.Automation.Provider |
UIAutomationProvider;UIAutomationTypes |
除 WPF 之外的框架的 UI 自动化提供程序开发人员。 |
System.Windows.Automation.Text |
UIAutomationClient;UIAutomationTypes |
除 WPF 之外的框架的 UI 自动化提供程序开发人员;用于实现 TextPattern 控件模式。 |
System.Windows.Automation.Peers |
PresentationFramework |
WPF 的 UI 自动化提供程序开发人员。 |
UI自动化模型[2]
返回
UI 自动化将 UI 的每一部分作为一个 AutomationElement 向客户端应用程序公开。
元素包含在树结构中,以桌面作为根元素。
AutomationElement 对象公开它们所表示的 UI 元素的通用属性。 其中一个属性是控件类型,它将其基本外观和功能定义为一个可识别的实体:例如按钮或复选框。
此外,元素还公开控件模式,以提供特定于这些元素的控件类型的属性。 控件模式还公开方法,使客户端能够获取有关元素的进一步信息并提供输入。
注意:控件类型和控件模式之间并不是一一对应的关系。 多个控件类型可以支持同一个控件模式,一个控件可以支持多个控件模式,每个控件模式公开其行为的不同方面。 例如,一个组合框至少具有两个控件模式:一个表示其展开和折叠功能,另一个表示选择机制
UI 自动化还通过事件向客户端应用程序提供信息。 与 WinEvent 不同的是,UI 自动化事件并不基于广播机制。 UI 自动化客户端注册特定的事件通知,并且可以请求将特定的 UI 自动化属性和控件模式信息传入其事件处理程序中。 此外,UI 自动化 事件包含到引发该事件的元素的引用。 提供程序可以通过有选择地引发事件来改善性能,具体取决于所有客户端是否在侦听。
UI自动化树概述[3]
在UIA中,程序UI的每一个部分都被认为是一个AutomationElement类,他们是一个树状的结构,Desktop被认为是每个windows based app的UIA树状图的根,从类的定义中,我们也可以看到一个AutomationElement类中有一个static的RootElement属性。
该树的结构中,一共有3中View Model,分别为Raw View, Control View和Content View:
Raw View提供的信息最多,也是其他view的基础,最贴近于程序本身的编程结构;
Control View是Raw View的子集,它最贴近于最终用户所能感知的UI结构,但是它不包含不能和用户相互交互的一些UI,例如listview的header,toolbar等等;
Content View则是Control View的一个子集,它只包含能和用户直接交互真实信息的控件,比如接受键盘输入的Textbox,选择不同值的Combobox;而诸如lable等控件则不会包含在其中。
UI自动化控件模式概述[4]
控件模式需实现定义控件中可用的一项独立功能所需的方法、属性、事件和关系:
- UI 自动化元素与其父元素、子元素以及同级元素之间的关系描述了 UI 自动化树内元素的结构。
- UI 自动化客户端使用方法可以操作控件。
- 属性和事件提供了有关控件模式功能的信息以及有关控件状态的信息。
UIA大概一共定义了38种pattern,代表了常用的控件行为,他们也会提供一些具体的功能性的属性。
- 对于UIA Provider来说,所做的事情就是定义控件相关的行为,找到该行为对应的模式,并实现该模式;
- 对于client而言,即访问相关的方法和属性,来实现自动化。
如某个控件需要有InvokePattern,则provider和client相对应的则为:
控件模式类 (Client) |
提供程序接口(Provider) |
说明 |
---|---|---|
InvokePattern |
IInvokeProvider |
用于可被调用的控件,如按钮。 |
UI 自动化属性概述[5]
每个property都由一个数字和名字来标识,provider用数字ID来确定属性请求(出于安全原因,UI 自动化提供程序将从 Uiautomationtypes.dll 中包含的一组单独的类中获取这些对象。);而client则用AutomationProperty类获取具体的某一属性的内容。
UI 自动化事件概述[6]
UIA是采用订阅模型,而不是以前的广播事件模型。定义了四种事件类型:Property change,Element action,Structure change和Global desktop change。
示例[8]
返回
下面这个示例用测试程序完成以下操作:
- 打开Calculater.exe;
- 找到button ‘3‘, ‘+‘, ‘5‘, ‘-‘, ‘2‘, ‘=‘, 并模拟点击它们。这样,计算器屏幕上会显示‘3+5-2’,并计算结果;
- 找到计算器屏幕,得到计算结果;
- 比较计算结果和预期结果是否一致。一致,则pass;反之,则fail。
1 //Reference UIAutomationClient and UIAutomationTypes 2 3 using System; 4 using System.Windows.Automation; 5 using System.Windows; 6 7 namespace CalcClient 8 { 9 class CalcAutomationClient 10 { 11 AutomationElement calcWindow = null; //Main UI Window element 12 //The following ID can be obtained from tool: UI Spy 13 string resultTextAutoID = "150"; //ID for Text element of output window 14 string btn5AutoID = "135"; //ID for button 5 15 string btn3AutoID = "133"; // ID for button 3 16 string btn2AutoID = "132"; // ID for button 2 17 string btnPlusAutoID = "93"; // ID for button + 18 string btnSubAutoID = "94"; // ID for button - 19 string btnEqualAutoID = "121"; // ID for button = 20 21 static void Main(string[] args) 22 { 23 CalcAutomationClient autoClient = new CalcAutomationClient(); 24 25 //Create callback for new Window open event. Test should run only when the main Window shows. 26 AutomationEventHandler eventHandler = new AutomationEventHandler(autoClient.OnWindowOpenOrClose); 27 //Attach the event with desktop element and start listening. 28 Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, eventHandler); 29 30 //Start caculator. When new window opens, the new window open event should fire. 31 System.Diagnostics.Process.Start("calc.exe"); 32 33 //Wait execution 34 Console.ReadLine(); 35 } 36 37 void OnWindowOpenOrClose(object src, AutomationEventArgs e) 38 { 39 if (e.EventId != WindowPattern.WindowOpenedEvent) 40 { 41 return; 42 } 43 44 AutomationElement sourceElement; 45 46 try 47 { 48 sourceElement = src as AutomationElement; 49 50 //Check the event source is caculator or not. 51 //In production code, string should be read from resource to support localization testing. 52 if (sourceElement.Current.Name == "Calculator") 53 { 54 calcWindow = sourceElement; 55 } 56 } 57 catch (ElementNotAvailableException) 58 { 59 return; 60 } 61 62 //Start testing 63 ExecuteTest(); 64 } 65 66 void ExecuteTest() 67 { 68 //Execute 3+5-2 69 //Invoke ExecuteButtonInvoke function to click buttons 70 ExecuteButtonInvoke(btn3AutoID); 71 ExecuteButtonInvoke(btnPlusAutoID); 72 ExecuteButtonInvoke(btn5AutoID); 73 ExecuteButtonInvoke(btnSubAutoID); 74 ExecuteButtonInvoke(btn2AutoID); 75 System.Threading.Thread.Sleep(1000); 76 ExecuteButtonInvoke(btnEqualAutoID); 77 78 //Invoke GetCurrentResult function to read caculator output 79 if (GetCurrentResult() == "6") 80 { 81 Console.WriteLine("Execute Pass!"); 82 return; 83 } 84 85 Console.WriteLine("Execute Fail!"); 86 } 87 88 void ExecuteButtonInvoke(string automationID) 89 { 90 91 //Create query condition object, there are two conditions. 92 //1. Check AutomationID 93 //2. Check Control Type 94 Condition conditions = new AndCondition( 95 new PropertyCondition(AutomationElement.AutomationIdProperty, automationID), 96 new PropertyCondition(AutomationElement.ControlTypeProperty, 97 ControlType.Button)); 98 99 AutomationElement btn = calcWindow.FindAll(TreeScope.Descendants, conditions)[0]; 100 101 //Obtain the InvokePattern interface 102 InvokePattern invokeptn = (InvokePattern)btn.GetCurrentPattern(InvokePattern.Pattern); 103 104 //Click button by Invoke interface 105 invokeptn.Invoke(); 106 } 107 108 string GetCurrentResult() 109 { 110 111 Condition conditions = new AndCondition( 112 new PropertyCondition(AutomationElement.AutomationIdProperty, resultTextAutoID), 113 new PropertyCondition(AutomationElement.ControlTypeProperty, 114 ControlType.Text)); 115 116 AutomationElement btn = calcWindow.FindAll(TreeScope.Descendants, conditions)[0]; 117 118 //Read name property of Text control. The name property is the output. 119 return btn.Current.Name; 120 } 121 } 122 }
使用UISpy工具
UISpy可以当作Client,找到Server所提供的属性、控件模式,也可对Server进行模拟操作。
图4 UISpy attached to Calculator
UI自动化提供者
在UIA架构中提到:对于标准控件而言,默认是支持UIA的,而对于自定义的控件,需要实现该控件的行为对应于UIA所定义的interface。
这里提到的实现UIA所定义的interface,就是UIA provider。UIA provider可在根据实际情况在服务器端和客户端实现,示例如下:
客户端:
Client-Side UI Automation Provider - WinForm Sample
服务器端:
Server-Side UI Automation Provider - WinForm Sample
Server-Side UI Automation Provider - WPF Sample
常见问题分析解决[8]
返回
控件无法识别
- 如果是因为自动化测试工具的限制,比如对于WinForm的控件,有些自动化工具就不能识别,碰到这种情况,最好是看这个工具有没有扩展可以用,比如Silktest的.Net Framework扩展。如果不行,那只能换自动化测试工具了。所以这个凸显出在做自动化测试以前,选择自动化测试工具的重要性。
- 如果是因为控件比较复杂,自动化工具可以识别,但是无法操作。这时我们可以通过Window API以及消息的方式来做,比如自己去调Window API来操作窗口,或者请开发实现一下消息的接口来给自动化工具调用等
- 跟开发沟通,让他们的控件支持IAccessible接口,然后我们通过MSAA来操作(如果是WPF控件,则需要实现UIAutomation定义的一些接口)。不过一般情况下,除了微软这样对软件的Accessible要求很高的公司,其它公司很少会花费时间来实现这个接口……。 另外扯一句,产品的Accessible的程度,实质上决定了一个公司能对产品做自动化测试的程度。
- 如果以上方法都不行,那只有最后一个双刃剑可以用了,就是鼠标键盘模拟。理论上来说,只要用户可以操作的东西,只要有界面,就可以通过鼠标键盘模拟来实现(君不见N多游戏外挂的键盘鼠标模拟大法)。就如双刃剑一样,这种做法是杀敌一千,自损八百。因为鼠标键盘模拟非常依赖于当前激活的窗口以及光标位置和焦点位置,而且同步起来很困难。这也造成了后期维护成本很高。
Timing issue
提倡第一种方法。
- Waiter/EventDriven
- Retry sleep a small interval value
- Thread.Sleep a long time
本地化问题
Avoid localize issue, read resource string
自动化技术和自动化框架[8]
返回
前面提到了UIA作为全新UI自动化测试技术的优势,但这并不能解决所有的UI 自动化问题。 自动化框架正是为了自动化技术没有完全解决的问题。比如:
- 自动化中的同步和等待。 对于稍复杂的UI 程序,测试程序往往需要根据测试目标的状态决定 下一步的操作。 比如测试文件另存为功能的时候,若保存路径是网络路径,可能会因为网络延迟导致整个UI停顿比较长的时间。这个时候测试,程序如果不顾当前状态而简单地执行下一步操作,比如新建文件, 很可能会因为UI延迟而失败。 正确的做法是,测试程序应该等待文件保存成功返回后,再进行下一步操作。 这就是自动化中同步和等待的一个例子。实现同步和等待有多种方法,最简单粗暴的做法是硬编码一个长时间的 Sleep在测试代码中。 稍微好一点的做法可以采取小时间片的轮询状态检查, 或者反复重试。 借助 UIA的Event Pattern,可以尝试捕获另存为窗口的关闭WindowClosedEvent。 如果要做得完善一点, 可以把多种方法结合, 另外再额外检查目标程序的CPU使用情况,消息循环是否有回应,设定超时时间等等。
- 冗繁的编码过程。 对于一个UI窗口,里面可能有几十个子控件或者子窗口。 在编写测试代码的时候, 如果对这些子元素的获取,操作不能简化, 势必导致代码冗繁,难以维护。 借助自动代码生成和ORM (Object Role Modeling)等技术, 可以解决这个问题。 比如可以用工具把窗口及其子元素的关系和搜索条件都序列化到XML文件中, 然后采用ORM技术即可在代码中轻松获取子元素。
- 多语言和本地化测试。多语言和本地化的测试对UI来说显得尤为重要。 UI程序往往通过资源文件来定义所显示的内容, 这就要求自动化测试要可以方便读取和定位程序的资源文件, 来支持多语言和本地化测试。
- 支持工具和辅助函数的匮乏。 对于大的项目研发, 通过好的工具来减小开发成本是非常必要的。 就UI自动化来说, 如果自动化测试用例可以通过一次录制,多次播放来做的话,成本会减少很多。 在VS2010中就提供了这样的录制-播放功能。 详细视频可以参考How to create record and playback Test Cases in Visual Studio Beta2。
- 区分功能性测试和用户真实行为模拟。 前面提到, 就点击按钮功能来说, 可以通过SendKey来模拟鼠标操作, 或者通过Windows Message来直接触发点击事件。 这两种不同方法各有优劣。 比如当按钮被其它元素遮挡, 通过SendKey进行模拟就会导致失败,而直接发送Windows Message还是会成功。 孰优孰劣取决于要达到的目的。 如果单纯为了测试按钮点击后导致的结果,通过Windows Message来模拟就省去了很多麻烦。 相反, 如果是界面测试, 通过SendKey来模拟就可以让按钮被遮挡的bug暴露出来, 而Windows Message则不能发现这样的问题。
所以,单纯的某个自动化技术或者方法也无法满足需求。为了解决上述问题,各种自动化测试框架逐渐涌现和发展。微软内部有多个不同的自动化框架,设计理念和侧重点各有不同。 Visual Studio 2010将加入对自动化测试的支持。 在CodePlex上面, 也可以找到多种框架,比如White和UI Automation Verify。
参考
[2] UI 自动化概述
[3] UI 自动化树概述
[4] UI 自动化控件模式概述
[5] UI 自动化属性概述
[6] UI 自动化事件概述
[7] 使用 UI 自动化进行自动化测试
[8] UI Automation - under the hood