Introduction
UI Automation是Microsoft .NET 3.0框架下提供的一种用于自动化测试的技术,是在MSAA基础上建立的,MSAA就是Microsoft Active Accessibility。UI Automation在某些方面超过了MSAA,UI自动化提供了Windows Vista中,微软Windows XP的全部功能,和Windows Server 2003。
在UI Automation中,所有的窗体、控件都表现为一个AutomationElement, AutomationElement 中包含此控件或窗体的属性,在实现自动化的过程中,我们通过其相关属性进行对控件自动化操作。对于UI用户界面来说,所有显示在桌面上的UI,其实际是一个UI Tree,根节点是desktop。我们可以使用UI Spy或者是SPY++来获得Window和Control的相关信息。在UI Automation里,根节点表示为AutomationElemnet.RootElement. 通过根节点,我们可以通过窗体或控件的Process Id、Process Name或者Window Name找到相应的子AutomationElement,例如Dialog、Button、 TextBox、Checkbox等标准控件,通过控件所对应的Pattern进行相关的操作。
UI Automation structure
如下图所示:
附件: 您所在的用户组无法下载或查看附件
1. 在服务端由UIAutomationProvider.dll和UIAutomationTypes.dll提供。
2. 在客户端由UIAutomationClient.dll和UIAutomationTypes.dll提供。
3. UIAutomationCore.dll为UI自动化的核心部分,负责Server端和Client端的交互。
4. UIAUtomationClientSideProvides.dll为客户端程序提供自动化支持。
Summary
本文主要简单介绍了UI Automation相关结构以及核心库
本文通过一个实例来介绍怎样使用UI Automation实现软件的自动化测试。
1. 首先建立一个待测试的winform程序,即UI Automation的服务端。
附件: 您所在的用户组无法下载或查看附件
下面是button事件处理程序。
- private void button1_Click(object sender, EventArgs e)
- {
- int i = int.Parse(textBox1.Text);
- int j = int.Parse(textBox2.Text);
- textBox3.Text = (i + j).ToString();
- }
2. 建立一个测试程序,做UI Automaion的客户端。
添加引用:UIAutomationClient.dll 和 UIAutomationTypes.dll
- 1using System;
- 2using System.Diagnostics;
- 3using System.Threading;
- 4using System.Windows.Automation.Provider;
- 5using System.Windows.Automation.Text;
- 6using System.Windows.Automation;
- 7
- 8namespace UIAutomationTest
- 9{
- 10 class Program
- 11 {
- 12 static void Main(string[] args)
- 13 {
- 14 try
- 15 {
- 16 Console.WriteLine("/nBegin WinForm UIAutomation test run/n");
- 17 // launch Form1 application
- 18 // get refernce to main Form control
- 19 // get references to user controls
- 20 // manipulate application
- 21 // check resulting state and determine pass/fail
- 22
- 23 Console.WriteLine("/nBegin WinForm UIAutomation test run/n");
- 24 Console.WriteLine("Launching WinFormTest application");
- 25 //启动被测试的程序
- 26 Process p = Process.Start(@"E:/Project/WinFormTest/WinFormTest/bin/Debug/WinFormTest.exe");
- 27
- 28 //自动化根元素
- 29 AutomationElement aeDeskTop = AutomationElement.RootElement;
- 30
- 31 Thread.Sleep(2000);
- 32 AutomationElement aeForm = AutomationElement.FromHandle(p.MainWindowHandle);
- 33 //获得对主窗体对象的引用,该对象实际上就是 Form1 应用程序(方法一)
- 34 //if (null == aeForm)
- 35 //{
- 36 // Console.WriteLine("Can not find the WinFormTest from.");
- 37 //}
- 38
- 39 //获得对主窗体对象的引用,该对象实际上就是 Form1 应用程序(方法二)
- 40 int numWaits = 0;
- 41 do
- 42 {
- 43 Console.WriteLine("Looking for WinFormTest……");
- 44 //查找第一个自动化元素
- 45 aeForm = aeDeskTop.FindFirst(TreeScope.Children, new PropertyCondition(
- 46 AutomationElement.NameProperty, "Form1"));
- 47 ++numWaits;
- 48 Thread.Sleep(100);
- 49 } while (null == aeForm && numWaits < 50);
- 50 if (null == aeForm)
- 51 throw new NullReferenceException("Failed to find WinFormTest.");
- 52 else
- 53 Console.WriteLine("Found it!");
- 54
- 55 Console.WriteLine("Finding all user controls");
- 56 //找到第一次出现的Button控件
- 57 AutomationElement aeButton = aeForm.FindFirst(TreeScope.Children,
- 58 new PropertyCondition(AutomationElement.NameProperty, "button1"));
- 59
- 60 //找到所有的TextBox控件
- 61 AutomationElementCollection aeAllTextBoxes = aeForm.FindAll(TreeScope.Children,
- 62 new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
- 63
- 64 // 控件初始化的顺序是先初始化后添加到控件
- 65 // this.Controls.Add(this.textBox3);
- 66 // this.Controls.Add(this.textBox2);
- 67 // this.Controls.Add(this.textBox1);
- 68
- 69 AutomationElement aeTextBox1 = aeAllTextBoxes[2];
- 70 AutomationElement aeTextBox2 = aeAllTextBoxes[1];
- 71 AutomationElement aeTextBox3 = aeAllTextBoxes[0];
- 72
- 73 Console.WriteLine("Settiing input to '30'");
- 74 //通过ValuePattern设置TextBox1的值
- 75 ValuePattern vpTextBox1 = (ValuePattern)aeTextBox1.GetCurrentPattern(ValuePattern.Pattern);
- 76 vpTextBox1.SetValue("30");
- 77 Console.WriteLine("Settiing input to '50'");
- 78 //通过ValuePattern设置TextBox2的值
- 79 ValuePattern vpTextBox2 = (ValuePattern)aeTextBox2.GetCurrentPattern(ValuePattern.Pattern);
- 80 vpTextBox2.SetValue("50");
- 81 Thread.Sleep(1500);
- 82 Console.WriteLine("Clickinig on button1 Button.");
- 83 //通过InvokePattern模拟点击按钮
- 84 InvokePattern ipClickButton1 = (InvokePattern)aeButton.GetCurrentPattern(InvokePattern.Pattern);
- 85 ipClickButton1.Invoke();
- 86 Thread.Sleep(1500);
- 87
- 88 //验证计算的结果与预期的结果是否相符合
- 89 Console.WriteLine("Checking textBox3 for '80'");
- 90 TextPattern tpTextBox3 = (TextPattern)aeTextBox3.GetCurrentPattern(TextPattern.Pattern);
- 91 string result = tpTextBox3.DocumentRange.GetText(-1);//获取textbox3中的值
- 92 //获取textbox3中的值
- 93 //string result = (string)aeTextBox2.GetCurrentPropertyValue(ValuePattern.ValueProperty);
- 94 if ("80" == result)
- 95 {
- 96 Console.WriteLine("Found it.");
- 97 Console.WriteLine("TTest scenario: *PASS*");
- 98 }
- 99 else
- 100 {
- 101 Console.WriteLine("Did not find it.");
- 102 Console.WriteLine("Test scenario: *FAIL*");
- 103 }
- 104
- 105 Console.WriteLine("Close application in 5 seconds.");
- 106 Thread.Sleep(5000);
- 107 //实现关闭被测试程序
- 108 WindowPattern wpCloseForm = (WindowPattern)aeForm.GetCurrentPattern(WindowPattern.Pattern);
- 109 wpCloseForm.Close();
- 110
- 111 Console.WriteLine("/nEnd test run/n");
- 112 }
- 113 catch (Exception ex)
- 114 {
- 115 Console.WriteLine("Fatal error: " + ex.Message);
- 116 }
- 117 }
- 118 }
- 119}
- 120
(文/开着拖拉机)
MS提供的控件Pattern
DockPattern ExpandCollapsePattern
GridPattern GridItemPattern
InvokePattern MultipleViewPattern
RangeValuePattern ScrollPattern
ScrollItemPattern SelectionPattern
SelectionItemPattern TablePattern
TableItemPattern TextPattern
TogglePattern TransformPattern
ValuePattern WindowPattern
Chapter 3 UI Automation中的几个重要属性
Control Tree of the AutomationElement
在UI Automation控件树中,根节点为Desktop window, 其他运行在用户桌面的窗体都作为Desktop window的子节点。
如下图所示:
附件: 您所在的用户组无法下载或查看附件
Desktop window可通过AutomationElement.RootElement属性获取,子节点中的窗体或对话框可通过
AutomationElement.RootElement.FindAll(TreeScope.Descendants, condition)
或
AutomationElement.RootElement.FindFirt(TreeScope.Descendants, condition)来获取.
AutomationElement property
在UI Automation中有如下几个重要属性:
1. AutomationIdProperty: 通过AutomationId来查找AutomationElement。
2. NameProperty: 通过控件的Name属性来查找AutomationElement。
3. ControlType: 通过控件的类型来查找AutomationElement
4. AutomationId: 唯一地标识 自动化元素,将其与同级相区分。
5. Name: WPF 按钮的 Content 属性、Win32 按钮的 Caption 属性以及 HTML 图像的 ALT 属性都映射到 UI 自动化视图中的同一个属性 Name。
注:PropertyCondition类是用来对相关属性进行条件匹配,在控件树中查找控件时,可以通过最佳匹配来找到相应的控件。
如下代码列出了使用不同的属性来构建PropertyCondition,通过PropertyCondition来查找控件树中的控件.
- public class PropertyConditions
- {
- static PropertyCondition propertyCondition;
- /// <summary>
- /// Create PropertyCondition by AutomationId
- /// </summary>
- /// <param name="automationId">Control AutomationId</param>
- /// <returns>Return PropertyCondition instance</returns>
- public static PropertyCondition GetAutomationIdProperty(object automationId)
- {
- propertyCondition = new PropertyCondition(AutomationElement.AutomationIdProperty, automationId);
- return propertyCondition;
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="controlType"></param>
- /// <returns></returns>
- public static PropertyCondition GetControlTypeProperty(object controlType)
- {
- propertyCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, controlType);
- return propertyCondition;
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="controlName"></param>
- /// <returns></returns>
- public static PropertyCondition GetNameProperty(object controlName)
- {
- propertyCondition = new PropertyCondition(AutomationElement.NameProperty, controlName);
- return propertyCondition;
- }
- /// <summary>
- /// Find element by specific PropertyCondition
- /// </summary>
- /// <param name="condition">PropertyCondition instance</param>
- /// <returns>Target automation element</returns>
- public static AutomationElement FindElement(PropertyCondition condition)
- {
- return AutomationElement.RootElement.FindFirst(TreeScope.Descendants, condition);
- }
DockPattern
DockPattern用于操作可停靠容器控件,我们最熟悉的VS2005/2008中的ToolBox,Solution Explorer都可以设置不同的DockPosition, 但是目前并不支持DockPattern,所以无法做为实例来讲。使用DockPattern的前提为控件支持DockPattern。 DockPattern中的DockPosition有六个枚举变量,即Bottom、Left、Right、Top、Fill和None。如果控件支持DockPattern, 则可以获取相对应的DockPosition以及设置控件的DockPosition。
如下代码是获取控件的DockPattern、获取控件当前的DockPosition以及设置控件的DockPosition。
- #region DockPattern helper
- /// <summary>
- /// Get DockPattern
- /// </summary>
- /// <param name="element">AutomationElement instance</param>
- /// <returns>DockPattern instance</returns>
- public static DockPattern GetDockPattern(AutomationElement element)
- {
- object currentPattern;
- if (!element.TryGetCurrentPattern(DockPattern.Pattern, out currentPattern))
- {
- throw new Exception(string.Format("Element with AutomationId '{0}' and Name '{1}' does not support the DockPattern.",
- element.Current.AutomationId, element.Current.Name));
- }
- return currentPattern as DockPattern;
- }
- /// <summary>
- /// Get DockPosition
- /// </summary>
- /// <param name="element">AutomationElement instance</param>
- /// <returns>DockPosition instance</returns>
- public static DockPosition GetDockPosition(AutomationElement element)
- {
- return GetDockPattern(element).Current.DockPosition;
- }
- /// <summary>
- /// Set DockPosition
- /// </summary>
- /// <param name="element">AutomationElement instance</param>
- public static void SetDockPattern(AutomationElement element, DockPosition dockPosition)
- {
- GetDockPattern(element).SetDockPosition(dockPosition);
- }
- #endregion
表示以可视方式进行展开(以显示内容)和折叠(以隐藏内容)的控件。例如ComboBox控件支持ExpandCollapsePattern。
ExpandCollapsePattern有两个主要方法:
Expand()方法:隐藏 AutomationElement 的全部子代节点、控件或内容。
Collapse()方法:显示 AutomationElement 的全部子节点、控件或内容。
以下代码是用ExpandCollapsePattern来测试ComboBox控件的Expand和Collapse。
- using System;
- using System.Text;
- using System.Diagnostics;
- using System.Threading;
- using System.Windows.Automation;
- namespace UIATest
- {
- class Program
- {
- static void Main(string[] args)
- {
- Process process = Process.Start(@"F:/CSharpDotNet/AutomationTest/ATP/WpfApp/bin/Debug/WpfApp.exe");
- int processId = process.Id;
- AutomationElement element = FindElementById(processId, "comboBox1");
- ExpandCollapsePattern currentPattern = GetExpandCollapsePattern(element);
- currentPattern.Expand();
- Thread.Sleep(1000);
- currentPattern.Collapse();
- }
- /** <summary>
- /// Get the automation elemention of current form.
- /// </summary>
- /// <param name="processId">Process Id</param>
- /// <returns>Target element</returns>
- public static AutomationElement FindWindowByProcessId(int processId)
- {
- AutomationElement targetWindow = null;
- int count = 0;
- try
- {
- Process p = Process.GetProcessById(processId);
- targetWindow = AutomationElement.FromHandle(p.MainWindowHandle);
- return targetWindow;
- }
- catch (Exception ex)
- {
- count++;
- StringBuilder sb = new StringBuilder();
- string message = sb.AppendLine(string.Format("Target window is not existing.try #{0}", count)).ToString();
- if (count > 5)
- {
- throw new InvalidProgramException(message, ex);
- }
- else
- {
- return FindWindowByProcessId(processId);
- }
- }
- }
- /** <summary>
- /// Get the automation element by automation Id.
- /// </summary>
- /// <param name="windowName">Window name</param>
- /// <param name="automationId">Control automation Id</param>
- /// <returns>Automatin element searched by automation Id</returns>
- public static AutomationElement FindElementById(int processId, string automationId)
- {
- AutomationElement aeForm = FindWindowByProcessId(processId);
- AutomationElement tarFindElement = aeForm.FindFirst(TreeScope.Descendants,
- new PropertyCondition(AutomationElement.AutomationIdProperty, automationId));
- return tarFindElement;
- }
- ExpandCollapsePattern helper#region ExpandCollapsePattern helper
- /** <summary>
- /// Get ExpandCollapsePattern
- /// </summary>
- /// <param name="element">AutomationElement instance</param>
- /// <returns>ExpandCollapsePattern instance</returns>
- public static ExpandCollapsePattern GetExpandCollapsePattern(AutomationElement element)
- {
- object currentPattern;
- if (!element.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out currentPattern))
- {
- throw new Exception(string.Format("Element with AutomationId '{0}' and Name '{1}' does not support the ExpandCollapsePattern.",
- element.Current.AutomationId, element.Current.Name));
- }
- return currentPattern as ExpandCollapsePattern;
- }
- #endregion
- }
- }
以下代码为被测程序的xaml文件:
- 1<Window x:Class="WpfApp.Window1"
- 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- 4 Title="Window1" Height="219" Width="353">
- 5 <Grid>
- 6 <ComboBox Name="comboBox1" Height="23" VerticalAlignment="Top" Margin="94,58,0,0" HorizontalAlignment="Left" Width="119">
- 7 <ComboBoxItem>kaden</ComboBoxItem>
- 8 <ComboBoxItem>sam</ComboBoxItem>
- 9 </ComboBox>
- 10 </Grid>
- 11</Window>
- 12
Summary
本文主要是对ExpandCollapsePattern 做简单的介绍,并使用ExpandCollapsePattern来操作ComboBox控件,对ComboBox进行Expand和Collapse操作。
InvokePattern
InvokePattern是UIA中最常用的Pattern之一,WPF和Winform中的button控件都支持InvokePattern。
对InvokePattern的Invoke()方法的调用应立即返回,没有出现阻止情况。但是,此行为完全依赖于 Microsoft UI 自动化提供程序实现。在调用 Invoke() 会引起阻止问题(如Winform中的模式对话框,但是WPF中的对话框的处理方式和winform不同,所以可以使用Invoke()方法来操作WPF中的模式对话框,因为WPF中的模式对话框不会出现阻止的问题)的情况下,要调用此方法,则需要另起线程来操作。
- using System;
- using System.Text;
- using System.Diagnostics;
- using System.Threading;
- using System.Windows.Automation;
- namespace UIATest
- {
- class Program
- {
- static void Main(string[] args)
- {
- Process process = Process.Start(@"F:/CSharpDotNet/AutomationTest/ATP/WpfApp/bin/Debug/WpfApp.exe");
- int processId = process.Id;
- AutomationElement element = FindElementById(processId, "button1");
- InvokePattern currentPattern = GetInvokePattern(element);
- currentPattern.Invoke();
- }
- /// <summary>
- /// Get the automation elemention of current form.
- /// </summary>
- /// <param name="processId">Process Id</param>
- /// <returns>Target element</returns>
- public static AutomationElement FindWindowByProcessId(int processId)
- {
- AutomationElement targetWindow = null;
- int count = 0;
- try
- {
- Process p = Process.GetProcessById(processId);
- targetWindow = AutomationElement.FromHandle(p.MainWindowHandle);
- return targetWindow;
- }
- catch (Exception ex)
- {
- count++;
- StringBuilder sb = new StringBuilder();
- string message = sb.AppendLine(string.Format("Target window is not existing.try #{0}", count)).ToString();
- if (count > 5)
- {
- throw new InvalidProgramException(message, ex);
- }
- else
- {
- return FindWindowByProcessId(processId);
- }
- }
- }
- /// <summary>
- /// Get the automation element by automation Id.
- /// </summary>
- /// <param name="windowName">Window name</param>
- /// <param name="automationId">Control automation Id</param>
- /// <returns>Automatin element searched by automation Id</returns>
- public static AutomationElement FindElementById(int processId, string automationId)
- {
- AutomationElement aeForm = FindWindowByProcessId(processId);
- AutomationElement tarFindElement = aeForm.FindFirst(TreeScope.Descendants,
- new PropertyCondition(AutomationElement.AutomationIdProperty, automationId));
- return tarFindElement;
- }
- #region InvokePattern helper
- /// <summary>
- /// Get InvokePattern
- /// </summary>
- /// <param name="element">AutomationElement instance</param>
- /// <returns>InvokePattern instance</returns>
- public static InvokePattern GetInvokePattern(AutomationElement element)
- {
- object currentPattern;
- if (!element.TryGetCurrentPattern(InvokePattern.Pattern, out currentPattern))
- {
- throw new Exception(string.Format("Element with AutomationId '{0}' and Name '{1}' does not support the InvokePattern.",
- element.Current.AutomationId, element.Current.Name));
- }
- return currentPattern as InvokePattern;
- }
- #endregion
- }
- }