关于UI Automation框架

微软提供的UI Automation框架给开发windows平台的自动化测试带来了很大的便利,这里就总结一下相关的代码。

首先,直接使用UI Automation框架,完成一个NotePad的about窗口中的 “OK” button的点击:

复制代码
 1 AutomationElement root = AutomationElement.RootElement;
 2 AutomationElement about_notepad_windows = root.FindFirst(
 3     TreeScope.Descendants,
 4     new AndCondition(
 5         new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window),
 6         new PropertyCondition(AutomationElement.NameProperty, "About Notepad")));
 7 if (about_notepad_windows == null)
 8 {
 9     Console.WriteLine("About Notepad window doesn't exist!!");
10     return;
11 }
12 
13 AutomationElement ok_button = about_notepad_windows.FindFirst(
14     TreeScope.Children,
15     new AndCondition(
16         new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button),
17         new PropertyCondition(AutomationElement.NameProperty, "OK")));
18 Object invokePatternObject;
19 if (ok_button.TryGetCurrentPattern(InvokePattern.Pattern, out invokePatternObject))
20 {
21     (invokePatternObject as InvokePattern).Invoke();
22 }
复制代码

好吧,上面是面向过程的代码,不利于复用,那么让我们来将其抽象成类,

首先定义一个基类UIAControl,它包含有搜索的根节点searchRoot,搜索范围searchScope和条件searchConditions,使用这三个对象来搜索一个AutomationElement对象并将其赋给innerElement,由于默认使用的是AndCondition来关联所有传入的condition对象,所以将CombineCondition方法设为虚方法,以便如果有子类想要使用其他关联条件处理condition的时候可以覆盖:

复制代码
  1 public abstract class UIAControl
  2 {
  3     private UIAControl searchRoot;
  4     private TreeScope searchScope;
  5     private List<Condition> searchConditions;
  6 
  7     protected void AddSearchCondition(Condition condition)
  8     {
  9         this.searchConditions.Add(condition);
 10     }
 11 
 12     public UIAControl()
 13     {
 14         searchConditions = new List<Condition>();
 15         searchScope = TreeScope.Descendants;
 16     }
 17 
 18     public UIAControl(UIAControl searchRoot)
 19         : this()
 20     {
 21         this.searchRoot = searchRoot;
 22     }
 23 
 24     public UIAControl(IntPtr hwnd)
 25         : this()
 26     {
 27         searchConditions.Add(PropertyConditionFactory.GetHandleCondition(hwnd));
 28     }
 29 
 30     public AutomationElement.AutomationElementInformation? ControlInformation
 31     {
 32         get
 33         {
 34             if (Exists())
 35             {
 36                 return InnerElement.Current;
 37             }
 38             return null;
 39         }
 40     }
 41 
 42     private AutomationElement innerElement;
 43     public AutomationElement InnerElement
 44     {
 45         get
 46         {
 47             if (innerElement == null)
 48             {
 49                 innerElement = SearchElement();
 50             }
 51             return innerElement;
 52         }
 53     }
 54 
 55     protected virtual AutomationElement SearchElement()
 56     {
 57         AutomationElement ele = null;
 58         if (searchRoot == null)
 59         {
 60             ele = AutomationElement.RootElement;
 61         }
 62         else
 63         {
 64             ele = searchRoot.InnerElement;
 65         }
 66 
 67         if (ele == null || 0 == searchConditions.Count)
 68         {
 69             return ele;
 70         }
 71         else
 72         {
 73             Condition conditions = CombineAllConditions();
 74             try
 75             {
 76                 return ele.FindFirst(searchScope, conditions);
 77             }
 78             catch(Exception ex)
 79             {
 80                 Console.WriteLine("Getting exception when searching element: " + ex.Message);
 81                 return null;
 82             }
 83         }
 84     }
 85 
 86     //Can override this method to return other type conditions, default will return AndCondition
 87     protected virtual Condition CombineAllConditions()
 88     {
 89         if (searchConditions.Count > 1)
 90         {
 91             return new AndCondition(searchConditions.ToArray());
 92         }
 93         else if (searchConditions.Count == 1)
 94         {
 95             return searchConditions.First();
 96         }
 97         else
 98         {
 99             return null;
100         }
101 
102     }
103 
104     public virtual bool Exists()
105     {
106         //Before checking existence, set innerElement to null to trigger fresh search.
107         return null != SearchElement();
108     }
109 
110     public bool WaitTillExist(int timeout, int interval = 2000)
111     {
112         Stopwatch stopwatch = new Stopwatch();
113         stopwatch.Start();
114         while (stopwatch.ElapsedMilliseconds < timeout)
115         {
116             if (this.Exists())
117             {
118                 return true;
119             }
120 
121             Thread.Sleep(interval);
122         }
123 
124         return false;
125     }
126 
127     protected bool CallPattern<T>(T pattern, Action<T> action) where T : BasePattern
128     {
129         if (pattern != null)
130         {
131             try
132             {
133                 action(pattern);
134                 return true;
135             }
136             catch (Exception ex)
137             {
138                 Console.WriteLine(ex.Message);
139             }
140         }
141         return false;
142     }
143 
144     protected T GetPattern<T>() where T : BasePattern
145     {
146         var ele = InnerElement;
147         if (ele != null)
148         {
149             try
150             {
151                 var patternIdentifier = (AutomationPattern)(typeof(T).GetField("Pattern").GetValue(null));
152                 return ele.GetCurrentPattern(patternIdentifier) as T;
153             }
154             catch (Exception ex)
155             {
156                 Console.WriteLine(ex.Message);
157             }
158         }
159         return null;
160     }
161 
162     public bool Invoke()
163     {
164         return CallPattern(GetPattern<InvokePattern>(), (pattern) => pattern.Invoke());
165     }
166 }
复制代码

 

之后,就可以定义其他控件类型了,下面定义了一个button的对象,只有一个click方法:

复制代码
 1     public class UIAButton : UIAControl
 2     {
 3         public UIAButton(UIAControl root, string name)
 4             : base(root)
 5         {
 6             AddSearchCondition(PropertyConditionFactory.GetNameCondition(name));
 7             AddSearchCondition(PropertyConditionFactory.GetControlTypeCondition(ControlType.Button));
 8         }
 9 
10         public void Click()
11         {
12             this.Invoke();
13         }
14     }
复制代码

 

再定义一个window对象:

复制代码
 1     public class UIAWindow : UIAControl
 2     {
 3         public UIAWindow(string name)
 4         {
 5             AddSearchCondition(PropertyConditionFactory.GetNameCondition(name));
 6             AddSearchCondition(PropertyConditionFactory.GetControlTypeCondition(ControlType.Window));
 7         }
 8 
 9         public bool Close()
10         {
11             return CallPattern(GetPattern<WindowPattern>(), (pattern) => pattern.Close());
12         }
13 
14         public bool Maximize()
15         {
16             return CallPattern(GetPattern<WindowPattern>(), (pattern) => pattern.SetWindowVisualState(WindowVisualState.Maximized));
17         }
18 
19         public bool Minimize()
20         {
21             return CallPattern(GetPattern<WindowPattern>(), (pattern) => pattern.SetWindowVisualState(WindowVisualState.Minimized));
22         }
23 
24         public bool Resize(int width, int height)
25         {
26             return CallPattern(GetPattern<TransformPattern>(), (pattern) => pattern.Resize(width, height));
27         }
28     }
复制代码

最后,使用上面两个控件类型来完成Ok按钮的点击:

复制代码
1 UIAWindow windows = new UIAWindow("About Notepad");
2 UIAButton ok_button = new UIAButton(windows, "OK");
3 if (windows.WaitTillExist(3000))
4 {
5     ok_button.Click();
6 }
复制代码

 

当然,如果是稍微上点规模的项目,就需要利用这些控件抽象出一个对应产品功能的produc类了。

如果不想从头写起的话,可以看看开源工具White, 一个优秀的基于UI Automation的测试框架。

posted @ 2021-10-14 13:49  左正  阅读(14)  评论(0编辑  收藏  举报