当前位置:   article > 正文

Flaui使用说明

flaui

目录

简介

一、获取窗体

1.ByMain

2.ByMainChild

3.ByAllTopLevel

4、ByAllDesktop

二、获取元素

1.通过Xpath获取 推荐使用

三、元素操作


简介

FlaUI 是一个 .NET 库,可帮助对 Windows 应用程序(Win32、WinForms、WPF、Store Apps 等)进行自动化 UI 测试。
它基于 Microsoft 的本机 UI 自动化库,因此是它们的包装器。
FlaUI 包装了 UI Automation 库中的几乎所有内容,但也提供了本机对象,以防有人有 FlaUI 尚未涵盖的特殊需求。
源代码网址:http://www.github.com/Roemer/FlaUI
FlaUI源代码文档:https://github.com/FlaUI/FlaUI/wiki

FlaUI官方介绍讲分两个版本

  • UIA2:原生 UI 自动化 API 的托管库
    • UIA2 是只管理的,这对 C# 来说很好,但它不支持更新的功能(如触摸),而且它也不能很好地与 WPF 一起工作,甚至更糟糕的是与 Windows 应用商店应用程序一起工作。
  • UIA3:原生 UI 自动化 API 的 Com 库
    • UIA3 是最新的,非常适合 WPF/Windows 商店应用程序,但不幸的是,它可能有一些 WinForms 应用程序的错误(请参阅常见问题解答),这些错误在 UIA2 中不存在。

尝试下来:winform界面 尽量用UIA2,其他用UIA3,并且同一APPDomain只能存在一个对象,也切换时需要重启。

一、获取窗体

获取窗体有时候最困难,你可能遇到且不限于:类名重复、类名变化、窗体名重复、窗体父子关系但是父窗体设置成MDI,甚至有的窗体实际上是个Panle等等诸如此类。

获取窗体分为四种模式:

       为什么要分为四种模式呢?写RPA脚本的时候要根据窗体的层级关系选择合适的代码,查找的执行效率和查询的范围有关,预设的范围越小,查询的效率越快。这几种模式下边会一一介绍。

  1. [Description("当前进程主窗体")]
  2.         ByMain,
  3.         [Description("当前进程主窗体的子窗体")]
  4.         ByMainChild,
  5.         [Description("当前进程所有弹出窗体")]
  6.         ByAllTopLevel,
  7.         [Description("当前桌面所有弹出窗体")]
  8. ByAllDesktop,

获取的条件:

 获取的条件也要根据实际情况进行选择,有些窗体类名一致、窗体名不同,有些类名窗体名都不同。值的注意的是XPath组件内部自己生成的类似xml结构的固定格式,执行效率很高,建议获取元素的时候使用。

  1. [Description("类名")]
  2. ClassName,
  3. [Description("窗体名")]
  4. Title,
  5. [Description("AutomationId")]
  6. AutomationId,
  7. [Description("XPath(窗体不可用)")]
  8. XPath,
  9. [Description("类名和窗体名")]
  10. ClassNameAndTitle,
  11. [Description("类名或窗体名")]
  12. ClassNameOrTitle,

1.ByMain

要捕获的窗体只有一个、或者有父子关系时的父窗体(模态框)。

注意:非模态框不能用此模式

  1. System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(processName);//获取进程
  2. var id = processes.First().Id;
  3. var app = FlaUI.Core.Application.Attach(id);
  4. //new TimeSpan(1) 不能缺省 否则会假死
  5. var mainWindow = appl?.GetMainWindow(AutomationBase, new TimeSpan(1));

2.ByMainChild

要捕获的窗体是父子关系时的子窗体(模态框)

  1. System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(processName);//获取进程
  2. var id = processes.First().Id;
  3. var app = FlaUI.Core.Application.Attach(id);
  4. //new TimeSpan(1) 不能缺省 否则会假死
  5. var mainWindow = appl?.GetMainWindow(AutomationBase, new TimeSpan(1));
  6. var property = model.WindowProperty;
  7. var controlType = model.ControlType;
  8. var frameworkType = model.FrameworkType;
  9. var className = model.ClassName;
  10. var name = model.Title;
  11. var automationId = model.AutomationId;
  12. mainWindows = FindWindowByAllChildren(mainWindow, property, controlType, frameworkType, className, name, automationId);
  13. private static AutomationElement[] FindWindowByAllChildren(AutomationElement windows,
  14. PropertyType property, ControlType controlType, FrameworkType frameworkType,
  15. string className, string name, string automationId)
  16. {
  17. AutomationElement[] mainWindows = null;
  18. switch (property)
  19. {
  20. case PropertyType.ClassName:
  21. {
  22. //桌面的直接子窗口
  23. mainWindows = windows?.FindAllChildren(cf => cf.ByClassName(className).
  24. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  25. break;
  26. }
  27. case PropertyType.Title:
  28. {
  29. //桌面的直接子窗口
  30. mainWindows = windows?.FindAllChildren(cf => cf.ByName(name).
  31. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  32. break;
  33. }
  34. case PropertyType.AutomationId:
  35. {
  36. //桌面的直接子窗口
  37. mainWindows = windows?.FindAllChildren(cf => cf.ByAutomationId(automationId).
  38. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  39. break;
  40. }
  41. case PropertyType.ClassNameAndTitle:
  42. {
  43. //桌面的直接子窗口
  44. mainWindows = windows?.FindAllChildren(cf => cf.ByClassName(className).And(cf.ByName(name)).
  45. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  46. break;
  47. }
  48. case PropertyType.ClassNameOrTitle:
  49. {
  50. //桌面的直接子窗口
  51. mainWindows = windows?.FindAllChildren(cf => cf.ByClassName(className).Or(cf.ByName(name)).
  52. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  53. break;
  54. }
  55. default:
  56. break;
  57. }
  58. return mainWindows;
  59. }

3.ByAllTopLevel

当一个进程会弹出多个窗体并且要捕获的窗体是非模态框(和主窗体同级)

  1. System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(processName);//获取进程
  2. var id = processes.First().Id;
  3. var app = FlaUI.Core.Application.Attach(id);
  4. //new TimeSpan(1) 不能缺省 否则会假死
  5. var windows = appl?.GetAllTopLevelWindows(AutomationBase).ToList();
  6. //下边又分两种模式一种是
  7. //主窗体A 窗体B和主窗体A同级 需要的是窗体B此时走isheet =0
  8. //主窗体A 窗体B和主窗体A同级 窗体C是窗体B的子窗体(模态)isheet>0
  9. //此处应该循环
  10. var property = model.WindowProperty;
  11. var controlType = model.ControlType;
  12. var frameworkType = model.FrameworkType;
  13. var className = model.ClassName;
  14. var name = model.Title;
  15. var automationId = model.AutomationId;
  16. if (isheet == 0)
  17. {
  18. mainWindows = FindWindowByAllTopLevelWindows(windows, property, className, name, automationId)?.ToList();
  19. }
  20. else
  21. {
  22. mainWindows = FindWindowByAllChildren(mainWindow, property, controlType, frameworkType, className, name, automationId).ToList();
  23. }
  24. if (mainWindows?.Count() != 1)
  25. {
  26. var txt = $"{className},{name},{automationId}";
  27. msg = $"第{isheet}层\r\n{GetDescription(windowtype)}|通过条件:{GetDescription(property)}值:{txt}\r\n查询到窗体个数{mainWindows.Count()}不唯一," +
  28. $"请尝试切换其他模式重试," +
  29. $"重试全部失败时,需定制插件";
  30. break;
  31. }
  32. mainWindow = mainWindows?.FirstOrDefault();
  33. private static AutomationElement[] FindWindowByAllTopLevelWindows(List<Window> windows,
  34. PropertyType property,
  35. string className, string name, string automationId)
  36. {
  37. List<Window> mainWindows = null;
  38. //主窗体和子窗体同级时
  39. //简单方式(常用)
  40. switch (property)
  41. {
  42. case PropertyType.ClassName:
  43. {
  44. //桌面的直接子窗口
  45. mainWindows = windows?.FindAll(cf => cf.ClassName.Equals(className));
  46. break;
  47. }
  48. case PropertyType.Title:
  49. {
  50. //桌面的直接子窗口
  51. mainWindows = windows?.FindAll(cf => cf.Name.Equals(name));
  52. break;
  53. }
  54. case PropertyType.AutomationId:
  55. {
  56. //桌面的直接子窗口
  57. mainWindows = windows?.FindAll(cf => cf.AutomationId.Equals(automationId));
  58. break;
  59. }
  60. case PropertyType.ClassNameAndTitle:
  61. {
  62. //桌面的直接子窗口
  63. mainWindows = windows?.FindAll(cf => cf.ClassName.Equals(className) && cf.Name.Equals(name));
  64. break;
  65. }
  66. case PropertyType.ClassNameOrTitle:
  67. {
  68. //桌面的直接子窗口
  69. mainWindows = windows?.FindAll(cf => cf.ClassName.Equals(className) || cf.Name.Equals(name));
  70. break;
  71. }
  72. default:
  73. break;
  74. }
  75. return mainWindows?.ToArray();
  76. }
  77. private static AutomationElement[] FindWindowByAllChildren(AutomationElement windows,
  78. PropertyType property, ControlType controlType, FrameworkType frameworkType,
  79. string className, string name, string automationId)
  80. {
  81. AutomationElement[] mainWindows = null;
  82. switch (property)
  83. {
  84. case PropertyType.ClassName:
  85. {
  86. //桌面的直接子窗口
  87. mainWindows = windows?.FindAllChildren(cf => cf.ByClassName(className).
  88. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  89. break;
  90. }
  91. case PropertyType.Title:
  92. {
  93. //桌面的直接子窗口
  94. mainWindows = windows?.FindAllChildren(cf => cf.ByName(name).
  95. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  96. break;
  97. }
  98. case PropertyType.AutomationId:
  99. {
  100. //桌面的直接子窗口
  101. mainWindows = windows?.FindAllChildren(cf => cf.ByAutomationId(automationId).
  102. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  103. break;
  104. }
  105. case PropertyType.ClassNameAndTitle:
  106. {
  107. //桌面的直接子窗口
  108. mainWindows = windows?.FindAllChildren(cf => cf.ByClassName(className).And(cf.ByName(name)).
  109. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  110. break;
  111. }
  112. case PropertyType.ClassNameOrTitle:
  113. {
  114. //桌面的直接子窗口
  115. mainWindows = windows?.FindAllChildren(cf => cf.ByClassName(className).Or(cf.ByName(name)).
  116. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  117. break;
  118. }
  119. default:
  120. break;
  121. }
  122. return mainWindows;
  123. }

4、ByAllDesktop

效率最慢,当进程窗体很多时巨慢我本机试的1-3s

  1. var property = model.WindowProperty;
  2. var controlType = model.ControlType;
  3. var frameworkType = model.FrameworkType;
  4. var className = model.ClassName;
  5. var name = model.Title;
  6. var automationId = model.AutomationId;
  7. mainWindows = FindWindowByAllChildren(windows, property, controlType, frameworkType, className, name, automationId);
  8. windows = mainWindows?.FirstOrDefault()?.AsWindow();
  9. private static AutomationElement[] FindWindowByAllChildren(AutomationElement windows,
  10. PropertyType property, ControlType controlType, FrameworkType frameworkType,
  11. string className, string name, string automationId)
  12. {
  13. AutomationElement[] mainWindows = null;
  14. switch (property)
  15. {
  16. case PropertyType.ClassName:
  17. {
  18. //桌面的直接子窗口
  19. mainWindows = windows?.FindAllChildren(cf => cf.ByClassName(className).
  20. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  21. break;
  22. }
  23. case PropertyType.Title:
  24. {
  25. //桌面的直接子窗口
  26. mainWindows = windows?.FindAllChildren(cf => cf.ByName(name).
  27. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  28. break;
  29. }
  30. case PropertyType.AutomationId:
  31. {
  32. //桌面的直接子窗口
  33. mainWindows = windows?.FindAllChildren(cf => cf.ByAutomationId(automationId).
  34. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  35. break;
  36. }
  37. case PropertyType.ClassNameAndTitle:
  38. {
  39. //桌面的直接子窗口
  40. mainWindows = windows?.FindAllChildren(cf => cf.ByClassName(className).And(cf.ByName(name)).
  41. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  42. break;
  43. }
  44. case PropertyType.ClassNameOrTitle:
  45. {
  46. //桌面的直接子窗口
  47. mainWindows = windows?.FindAllChildren(cf => cf.ByClassName(className).Or(cf.ByName(name)).
  48. And(cf.ByControlType(controlType).And(cf.ByFrameworkType(frameworkType))));
  49. break;
  50. }
  51. default:
  52. break;
  53. }
  54. return mainWindows;
  55. }

二、获取元素

1.通过Xpath获取 推荐使用

类似于文件路径,这是个相对路径,可以稍微理解一下,下付转换代码

  1. //通过XPath获取
  2. var childrens = window?.FindAllByXPath(xPath);
  3. //转化Xpath
  4. public static string TranslationXpath(string xpath)
  5. {
  6. if (!xpath.ToLower().Contains("window"))
  7. {
  8. return xpath;
  9. }
  10. var eles = xpath.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
  11. var split = "/";
  12. var res = "";
  13. for (int i = 1; i < eles.Length; i++)
  14. {
  15. res += split + eles[i];
  16. }
  17. return res;
  18. }
  19. //通过类名窗体名查找
  20. switch (property)
  21. {
  22. case PropertyType.ClassName:
  23. {
  24. if (controlType == ControlType.Unknown)
  25. {
  26. elements = window?.FindAllDescendants(cf => cf.ByClassName(value[0]));
  27. }
  28. else
  29. {
  30. elements = window?.FindAllDescendants(cf => cf.ByClassName(value[0]).And(cf.ByControlType(controlType)));
  31. }
  32. break;
  33. }
  34. case PropertyType.Title:
  35. {
  36. if (controlType == ControlType.Unknown)
  37. {
  38. elements = window?.FindAllDescendants(cf => cf.ByName(value[1]));
  39. }
  40. else
  41. {
  42. elements = window?.FindAllDescendants(cf => cf.ByName(value[1]).And(cf.ByControlType(controlType)));
  43. }
  44. break;
  45. }
  46. case PropertyType.AutomationId:
  47. {
  48. if (controlType == ControlType.Unknown)
  49. {
  50. elements = window?.FindAllDescendants(cf => cf.ByAutomationId(value[2]));
  51. }
  52. else
  53. {
  54. elements = window?.FindAllDescendants(cf => cf.ByAutomationId(value[2]).And(cf.ByControlType(controlType)));
  55. }
  56. break;
  57. }
  58. case PropertyType.ClassNameAndTitle:
  59. {
  60. if (controlType == ControlType.Unknown)
  61. {
  62. elements = window?.FindAllDescendants(cf => cf.ByClassName(value[0]).And(cf.ByName(value[1])));
  63. }
  64. else
  65. {
  66. elements = window?.FindAllDescendants(cf => cf.ByClassName(value[0]).And(cf.ByName(value[1])).And(cf.ByControlType(controlType)));
  67. }
  68. break;
  69. }
  70. case PropertyType.ClassNameOrTitle:
  71. {
  72. if (controlType == ControlType.Unknown)
  73. {
  74. elements = window?.FindAllDescendants(cf => cf.ByClassName(value[0]).Or(cf.ByName(value[1])));
  75. }
  76. else
  77. {
  78. elements = window?.FindAllDescendants(cf => cf.ByClassName(value[0]).Or(cf.ByName(value[1])).And(cf.ByControlType(controlType)));
  79. }
  80. break;
  81. }
  82. default:
  83. break;
  84. }

三、元素操作

我现在只用到了textbox、combox、checkbox、RadioButton之类的简单的,复杂的可以自行参照文档,注意 有时候winform和wpf或者其他win32的界面同样的控件可能发送的写法不同,需要定制,我这里定义了个插件来定制化不能使用的界面,有需要的可以自行去除

  1. public static void SendText(string key, AutomationElement element, string txt)
  2. {
  3. try
  4. {
  5. var appl = GetApplication(key);
  6. var pElement = appl?.AutomationComponent?.TextBox(element, txt);
  7. if (!(pElement.HasValue && pElement.Value))
  8. {
  9. var textbox = element?.AsTextBox();
  10. if (textbox != null)
  11. {
  12. textbox.Text = txt;
  13. //发送失败时尝试用粘贴板粘贴
  14. if (string.IsNullOrEmpty(textbox.Text))
  15. textbox.Enter(txt);
  16. }
  17. }
  18. }
  19. catch (Exception ex) { throw ex; }
  20. }
  21. public static void ComboBoxSelectText(string key, AutomationElement element, string txt)
  22. {
  23. try
  24. {
  25. var appl = GetApplication(key);
  26. var pElement = appl?.AutomationComponent?.ComboBox(element, txt);
  27. if (!(pElement.HasValue && pElement.Value))
  28. {
  29. var combox = element?.AsComboBox();
  30. if (combox != null)
  31. {
  32. combox.Select(txt);
  33. combox.Collapse();
  34. }
  35. }
  36. }
  37. catch (Exception ex) { throw ex; }
  38. }
  39. public static void ComboBoxSelectIndex(string key, AutomationElement element, int index)
  40. {
  41. try
  42. {
  43. var appl = GetApplication(key);
  44. var pElement = appl?.AutomationComponent?.ComboBox(element, index);
  45. if (!(pElement.HasValue && pElement.Value))
  46. {
  47. var combox = element?.AsComboBox();
  48. if (combox != null)
  49. {
  50. try
  51. {
  52. combox.Select(index);
  53. combox.Collapse();
  54. }
  55. catch (Exception ex)
  56. {
  57. throw ex;
  58. }
  59. }
  60. }
  61. }
  62. catch (Exception ex) { throw ex; }
  63. }
  64. public static void CheckBoxChecked(string key, AutomationElement element, bool isCheck)
  65. {
  66. try
  67. {
  68. var appl = GetApplication(key);
  69. var pElement = appl?.AutomationComponent?.CheckBox(element, isCheck);
  70. if (!(pElement.HasValue && pElement.Value))
  71. {
  72. var check = element?.AsCheckBox();
  73. if (check != null)
  74. check.IsChecked = isCheck;
  75. }
  76. }
  77. catch (Exception ex) { throw ex; }
  78. }
  79. public static void RadioButtonCheck(string key, AutomationElement element, bool isCheck)
  80. {
  81. try
  82. {
  83. var appl = GetApplication(key);
  84. var pElement = appl?.AutomationComponent?.RadioButton(element, isCheck);
  85. if (!(pElement.HasValue && pElement.Value))
  86. {
  87. var radioButton = element?.AsRadioButton();
  88. if (radioButton != null)
  89. radioButton.IsChecked = isCheck;
  90. }
  91. }
  92. catch (Exception ex) { throw ex; }
  93. }
  94. public static void RadioButtonClick(string key, AutomationElement element)
  95. {
  96. try
  97. {
  98. var appl = GetApplication(key);
  99. var pElement = appl?.AutomationComponent?.RadioButton(element);
  100. if (!(pElement.HasValue && pElement.Value))
  101. {
  102. var radioButton = element?.AsRadioButton();
  103. if (radioButton != null)
  104. radioButton.Click();
  105. }
  106. }
  107. catch (Exception ex) { throw ex; }
  108. }
  109. public static void DateTimePicker(string key, AutomationElement element, DateTime dateTime)
  110. {
  111. try
  112. {
  113. var appl = GetApplication(key);
  114. var pElement = appl?.AutomationComponent?.DateTimePicker(element, dateTime);
  115. if (!(pElement.HasValue && pElement.Value))
  116. {
  117. var picker = element?.AsDateTimePicker();
  118. if (picker != null)
  119. picker.SelectedDate = dateTime;
  120. }
  121. }
  122. catch (Exception ex) { throw ex; }
  123. }
  124. public static void Calendar(string key, AutomationElement element, DateTime dateTime)
  125. {
  126. try
  127. {
  128. var appl = GetApplication(key);
  129. var pElement = appl?.AutomationComponent?.Calendar(element, dateTime);
  130. if (!(pElement.HasValue && pElement.Value))
  131. {
  132. var calendar = element?.AsCalendar();
  133. if (calendar != null)
  134. calendar.SelectDate(dateTime);
  135. }
  136. }
  137. catch (Exception ex) { throw ex; }
  138. }
  139. public static void Spinner(string key, AutomationElement element, double value)
  140. {
  141. try
  142. {
  143. var appl = GetApplication(key);
  144. var pElement = appl?.AutomationComponent?.Spinner(element, value);
  145. if (!(pElement.HasValue && pElement.Value))
  146. {
  147. var spinner = element?.AsSpinner();
  148. if (spinner != null)
  149. spinner.Value = value;
  150. }
  151. }
  152. catch (Exception ex) { throw ex; }
  153. }
  154. public static void Slider(string key, AutomationElement element, int value)
  155. {
  156. try
  157. {
  158. var appl = GetApplication(key);
  159. var pElement = appl?.AutomationComponent?.Slider(element, value);
  160. if (!(pElement.HasValue && pElement.Value))
  161. {
  162. var slider = element?.AsSlider();
  163. if (slider != null)
  164. slider.Value = AdjustNumberIfOnlyValue(slider, value);
  165. }
  166. }
  167. catch (Exception ex) { throw ex; }
  168. }
  169. public static void ButtonClick(string key, AutomationElement element)
  170. {
  171. try
  172. {
  173. var appl = GetApplication(key);
  174. var pElement = appl?.AutomationComponent?.Button(element);
  175. if (!(pElement.HasValue && pElement.Value))
  176. {
  177. var button = element?.AsButton();
  178. if (button != null)
  179. button.Invoke();
  180. }
  181. }
  182. catch (Exception ex) { throw ex; }
  183. }
  184. public static void ListSelete()
  185. {
  186. }
  187. private static double AdjustNumberIfOnlyValue(Slider slider, double number)
  188. {
  189. if (slider.IsOnlyValue)
  190. {
  191. return number * 10;
  192. }
  193. return number;
  194. }

先写这么多吧,后边有空写下类似Spy++的工具的坑。。。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/376463
推荐阅读
相关标签
  

闽ICP备14008679号