關於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的測試框架。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM