如何使用 UIAutomation 在 .NET 4.8 WinForms 应用程序中获取所有 ComboBox ListItem 值?
Posted
技术标签:
【中文标题】如何使用 UIAutomation 在 .NET 4.8 WinForms 应用程序中获取所有 ComboBox ListItem 值?【英文标题】:How to get all ComboBox ListItem values in a .NET 4.8 WinForms application with UIAutomation? 【发布时间】:2021-08-30 21:14:43 【问题描述】:我希望这不是一个愚蠢的问题,我只是没有看到一个非常明显的解决方案,但是通过一些 GUI 测试,我的功能测试团队负责一个软件项目的工作,我遇到了一个变化ComboBoxes 的行为,同时通过我们使用 UIAutomation 与 GUI 元素交互的方式更新我们的 GUI 自动化库的行为。我们的软件产品正在使用 .NET 4.8 的最新开发版本 WinForms 应用程序。通过这次更新,我注意到元素的行为发生了变化。
例如,对于菜单栏项,为了获取菜单选择的子元素,需要在任何子项可见之前展开该项。 Inspect 观察到相同的行为。通过对我们的 GUI 库进行轻微的代码修改,我们能够相当容易地克服这个障碍。我可以在一定程度上理解为什么微软在 .NET 4.8 上做出了这种改变。 搜索***窗口的所有后代可能会在测试时造成灾难性的后果。
但是,对于组合框,其行为似乎略有不同。使用组合框,未展开时有两个子项:
-Combobox
|-Text item
|-button item
ComboBox 有一个ExpandCollapsePattern
。直觉告诉我,这很像菜单栏项,如果我展开它然后尝试获取子项列表,甚至后代,我应该会在集合中看到超过 2 个元素。即使在对 ComboBox 的 ExpandCollapsePattern
执行 Expand() 并重新关注 ComboBox 之后有相当长的延迟(我已经读过某些子项有时需要这样做),我仍然只看到 2 个元素,即使我m 寻找后代。
当我查看使用 Inspect 展开的 ComboBox 时,我看到以下内容:
-Combobox
|-List item
| |-List item
| |-List item
| |-List item
| |-List item
| |-List item
| |-List item
|-Text item
|-button item
使用这种新行为处理组合框的更新方法目前如下所示:
public List<AutomationElement> GetComboBoxEntries(AutomationElement parentElement)
List<AutomationElement> items = new List<AutomationElement>();
try
var expandCollapsePattern = (ExpandCollapsePattern)parentElement.GetCurrentPattern(ExpandCollapsePatternIdentifiers.Pattern);
expandCollapsePattern.Expand();
//Slight delay
DelayFor(1000);
//Set focus to the combobox
parentElement.SetFocus();
//perform a FindAll() searching all descendants of the parentElement
var collection = FindAllListItems(parentElement);
//Add every element to the list
foreach (AutomationElement element in collection)
items.Add(element);
catch (Exception e)
Console.WriteLine("There was an error performing the operation.");
Console.WriteLine("Error: " + e.Message);
Console.WriteLine("Stack Trace: " + e.StackTrace);
return items;
我认为的一个解决方案,虽然在 ComboBox 中存在大量项目时不实用,但我预见解决我们的问题的方法是使用键盘/单击输入来迭代 ComboBox 的每个成员并捕获当前值元素的图案。我们以前不得不在引擎盖下做一些肮脏的巫术,但我真的不喜欢那种代码。我更喜欢在可能的情况下使用 UIA 保持清洁。此外,这似乎很乏味。
在此 .NET 4.8 更改之前,我们能够获取子列表项元素,检查每个元素的值以验证没有任何无效作为 GUI 冒烟测试的一部分。现在,您似乎需要跳过很多圈才能获得最终结果。
我是否遗漏了一些显而易见的东西?我更愿意将任何解决方案保留在 UIA 的范围内。就像意识到菜单栏项如何改变行为一样,我相信 ComboBoxes 有一个解决方案。
我只是没有看到它。希望你们当中有一点 UIA 专业知识的人可以为我指明正确的方向。谢谢。
【问题讨论】:
是的,托管 UI 自动化库在遍历树时存在一些问题。在面向 .Net Framework 4.8 的 WinForms 应用程序中,您可以找到 List 控件作为 RootElement 的子项(例如,var listElement = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, "ComboLBox"));
)。如果你在[ExpandCollapsePattern].Expand()
之前和之后调用它,你应该做对了。 -- 否则,您可以使用 Win32 API (GetComboBoxInfo()
),或者更改库或使用UIAComWrapper
。
顺便说一句,当您使用 Expand()
方法时,您不需要延迟(或者 DelayFor(1000)
是什么,我没有看到任何 async/await 实现,所以您是可能阻塞线程)。最后检查ExpandCollapseState.PartiallyExpanded
。
我没有代表将问题标记为已回答,但查看 RootElement 级别解决了我遇到的问题,即使 Inspect 似乎没有以这种方式显示树结构。我很好奇将 List 控件放在 .NET 4.8 中的 RootElement 级别的逻辑是什么。谢谢。
【参考方案1】:
这里有一些建议。
由于您正在处理 WinForms ComboBox,您不妨使用专用的 Win32 API 来获取列表控件的句柄(这里是 NativeWindow 派生对象,因此 UI 自动化看到它作为 Win32 控件,而不是 WinForm 控件)。
您可以使用GetComboBoxInfo() 函数,传递COMBOBOXINFO 结构和ComboBox 的句柄(由[AutomationElement].Current.NativeWindowHandle
返回)。
如果函数成功,则返回 NativeWindow 容器的句柄。
然后您可以使用 AutomationElement.FromHandle()
生成一个 AutomationElement。
这将允许在没有ExpandCollapsePattern 的情况下检索列表控件的列表项,因为您可以直接访问列表控件:
private AutomationElementCollection GetWinFormComboBoxListItems(AutomationElement comboBox)
if (comboBox is null) return null;
if (comboBox.Current.FrameworkId != "WinForm")
throw new ArgumentException("Not a WinForm Control");
var cboInfo = new COMBOBOXINFO();
cboInfo.Init();
if (GetComboBoxInfo((IntPtr)comboBox.Current.NativeWindowHandle, ref cboInfo))
var listElement = AutomationElement.FromHandle(cboInfo.hwndList);
if (listElement != null)
var items = listElement.FindAll(TreeScope.Children, Automation.RawViewCondition);
return items;
return null;
Win32 declarations
:
[StructLayout(LayoutKind.Sequential)]
internal struct COMBOBOXINFO
public int cbSize;
public RECT rcItem;
public RECT rcButton;
public ComboBoxButtonState buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
public void Init() => this.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
internal enum ComboBoxButtonState : int
STATE_SYSTEM_NONE = 0,
STATE_SYSTEM_INVISIBLE = 0x00008000,
STATE_SYSTEM_PRESSED = 0x00000008
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
如果你想一路走 UI 自动化,你可以尝试第一次从 ComboBox Element 中获取子 List Control。如果失败,请使用RootElement 作为父元素重试。这将在 NativeWindow 中检索列表控件 boxed 的 AutomationElement。
这将使用ExpandCollapsePatter
展开列表控件,使其对 UI 自动化可见。
您还可以使用Automation.AddAutomationPropertyChangedEventHandler() 创建一个自动化事件处理程序,传递一个用ExpandCollapsePattern.ExpandCollapseStateProperty
初始化的处理程序。
如果您想知道用户何时打开或关闭 ComboBox 的 DropDownList,或者选择了什么。
private AutomationElementCollection GetComboBoxListItems(AutomationElement comboBox)
if (comboBox is null) return null;
AutomationElementCollection items = null;
bool wasCollapsed = false;
if (comboBox.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out object exp))
var expPattern = exp as ExpandCollapsePattern;
var state = expPattern.Current.ExpandCollapseState;
if (state == ExpandCollapseState.PartiallyExpanded)
Thread.Sleep(50);
if (state == ExpandCollapseState.Collapsed)
expPattern.Expand();
wasCollapsed = true;
var condition = new AndCondition(
new PropertyCondition(AutomationElement.ClassNameProperty, "ComboLBox", PropertyConditionFlags.IgnoreCase),
new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ProcessIdProperty, comboBox.Current.ProcessId));
AutomationElement listElement = comboBox.FindFirst(TreeScope.Children, condition);
if (listElement is null && comboBox.Current.FrameworkId == "WinForm")
listElement = AutomationElement.RootElement.FindFirst(TreeScope.Children, condition);
if (listElement != null)
items = listElement.FindAll(TreeScope.Children, Automation.RawViewCondition);
if (wasCollapsed) expPattern.Collapse();
return items;
【讨论】:
以上是关于如何使用 UIAutomation 在 .NET 4.8 WinForms 应用程序中获取所有 ComboBox ListItem 值?的主要内容,如果未能解决你的问题,请参考以下文章
即使在应用退出后,如何使用 UIAutomation 工具继续测试 iOS 应用?
我如何在 XCODE 8、Swift 3 中使用 UIAutomation
如何在 UIAutomation 中从句子中取出特定的单词?