微软提供的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的测试框架。
