注释
本文档适用于 .NET Framework 开发人员,他们想要使用 System.Windows.Automation 命名空间中定义的管理的 UI 自动化类。 有关 UI 自动化的最新信息,请参阅 Windows 自动化 API:UI 自动化。
本主题包含一些场景和示例代码,展示了如何以及何时可以使用AutomationIdProperty来在 UI 自动化树中定位元素。
AutomationIdProperty 唯一标识其同级中的 UI 自动化元素。 有关与控件标识相关的属性标识符的详细信息,请参阅 UI 自动化属性概述。
注释
AutomationIdProperty 不保证整个树的唯一标识,它通常需要容器和范围信息才能有用。 例如,应用程序可能包含具有多个顶级菜单项的菜单控件,而顶级菜单项又包含多个子菜单项。 这些辅助菜单项可以通过泛型方案(如“Item1”、“Item 2”等)进行标识,从而允许顶级菜单项的子项使用重复的标识符。
Scenarios
已确定了三个主要的 UI 自动化客户端应用程序场景在搜索元素时需要使用 \
注释
AutomationIdProperty 控件视图中除顶级应用程序窗口、从 Windows Presentation Foundation(WPF) 控件派生的 UI 自动化元素、没有 ID 或 x:Uid 的 UI 自动化元素以及从没有控件 ID 的 Win32 控件派生的 UI 自动化元素之外,都支持该元素。
使用唯一且可发现的 AutomationID 在 UI 自动化树中查找特定元素
- 使用 UI Spy 等工具报告感兴趣的 UI 元素的AutomationIdProperty。 然后,可以将此值复制并粘贴到客户端应用程序中,例如测试脚本,以便进行后续的自动测试。 此方法可减少和简化在运行时识别和查找元素所需的代码。
注意
一般情况下,应尝试只获取到RootElement的直接子级。 搜索后代可能会循环访问数百甚至数千个元素,这可能会导致堆栈溢出。 如果尝试在较低级别获取特定元素,则应从应用程序窗口或较低级别的容器启动搜索。
///--------------------------------------------------------------------
/// <summary>
/// Finds all elements in the UI Automation tree that have a specified
/// AutomationID.
/// </summary>
/// <param name="targetApp">
/// The root element from which to start searching.
/// </param>
/// <param name="automationID">
/// The AutomationID value of interest.
/// </param>
/// <returns>
/// The collection of UI Automation elements that have the specified
/// AutomationID value.
/// </returns>
///--------------------------------------------------------------------
private AutomationElementCollection FindElementFromAutomationID(AutomationElement targetApp,
string automationID)
{
return targetApp.FindAll(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, automationID));
}
'''--------------------------------------------------------------------
''' <summary>
''' Finds all elements in the UI Automation tree that have a specified
''' AutomationID.
''' </summary>
''' <param name="targetApp">
''' The root element from which to start searching.
''' </param>
''' <param name="automationID">
''' The AutomationID value of interest.
''' </param>
''' <returns>
''' The collection of automation elements that have the specified
''' AutomationID value.
''' </returns>
'''--------------------------------------------------------------------
Private Function FindElementFromAutomationID( _
ByVal targetApp As AutomationElement, _
ByVal automationID As String) As AutomationElementCollection
Return targetApp.FindAll( _
TreeScope.Descendants, _
New PropertyCondition( _
AutomationElement.AutomationIdProperty, automationID))
End Function 'FindElementFromAutomationID
使用持久路径返回到先前识别的 AutomationElement
- 客户端应用程序(从简单的测试脚本到可靠的记录和播放实用工具)可能需要访问当前未实例化的元素,例如文件打开对话框或菜单项,因此在 UI 自动化树中不存在。 这些元素只能通过使用 UI 自动化属性(如 AutomationID、控件模式和事件侦听器),通过重现或“回放”特定的 UI 操作序列来实例化。
///--------------------------------------------------------------------
/// <summary>
/// Creates a UI Automation thread.
/// </summary>
/// <param name="sender">Object that raised the event.</param>
/// <param name="e">Event arguments.</param>
/// <remarks>
/// UI Automation must be called on a separate thread if the client
/// application itself could become a target for event handling.
/// For example, focus tracking is a desktop event that could involve
/// the client application.
/// </remarks>
///--------------------------------------------------------------------
private void CreateUIAThread(object sender, EventArgs e)
{
// Start another thread to do the UI Automation work.
ThreadStart threadDelegate = new ThreadStart(CreateUIAWorker);
Thread workerThread = new Thread(threadDelegate);
workerThread.Start();
}
///--------------------------------------------------------------------
/// <summary>
/// Delegated method for ThreadStart. Creates a UI Automation worker
/// class that does all UI Automation related work.
/// </summary>
///--------------------------------------------------------------------
public void CreateUIAWorker()
{
uiautoWorker = new FindByAutomationID(targetApp);
}
private FindByAutomationID uiautoWorker;
'''--------------------------------------------------------------------
''' <summary>
''' Creates a UI Automation thread.
''' </summary>
''' <param name="sender">Object that raised the event.</param>
''' <param name="e">Event arguments.</param>
''' <remarks>
''' UI Automation must be called on a separate thread if the client
''' application itself could become a target for event handling.
''' For example, focus tracking is a desktop event that could involve
''' the client application.
''' </remarks>
'''--------------------------------------------------------------------
Private Sub CreateUIAThread(ByVal sender As Object, ByVal e As EventArgs)
' Start another thread to do the UI Automation work.
Dim threadDelegate As New ThreadStart(AddressOf CreateUIAWorker)
Dim workerThread As New Thread(threadDelegate)
workerThread.Start()
End Sub
'''--------------------------------------------------------------------
''' <summary>
''' Delegated method for ThreadStart. Creates a UI Automation worker
''' class that does all UI Automation related work.
''' </summary>
'''--------------------------------------------------------------------
Public Sub CreateUIAWorker()
uiautoWorker = New UIAWorker(targetApp)
End Sub
Private uiautoWorker As UIAWorker
///--------------------------------------------------------------------
/// <summary>
/// Function to playback through a series of recorded events calling
/// a WriteToScript function for each event of interest.
/// </summary>
/// <remarks>
/// A major drawback to using AutomationID for recording user
/// interactions in a volatile UI is the probability of catastrophic
/// change in the UI. For example, the //Processes// dialog where items
/// in the listbox container can change with no input from the user.
/// This mandates that a record and playback application must be
/// reliant on the tester owning the UI being tested. In other words,
/// there has to be a contract between the provider and client that
/// excludes uncontrolled, external applications. The added benefit
/// is the guarantee that each control in the UI should have an
/// AutomationID assigned to it.
///
/// This function relies on a UI Automation worker class to create
/// the System.Collections.Generic.Queue object that stores the
/// information for the recorded user interactions. This
/// allows post-processing of the recorded items prior to actually
/// writing them to a script. If this is not necessary the interaction
/// could be written to the script immediately.
/// </remarks>
///--------------------------------------------------------------------
private void Playback(AutomationElement targetApp)
{
AutomationElement element;
foreach(ElementStore storedItem in uiautoWorker.elementQueue)
{
PropertyCondition propertyCondition =
new PropertyCondition(
AutomationElement.AutomationIdProperty, storedItem.AutomationID);
// Confirm the existence of a control.
// Depending on the controls and complexity of interaction
// this step may not be necessary or may require additional
// functionality. For example, to confirm the existence of a
// child menu item that had been invoked the parent menu item
// would have to be expanded.
element = targetApp.FindFirst(TreeScope.Descendants, propertyCondition);
if(element == null)
{
// Control not available, unable to continue.
// TODO: Handle error condition.
return;
}
WriteToScript(storedItem.AutomationID, storedItem.EventID);
}
}
///--------------------------------------------------------------------
/// <summary>
/// Generates script code and outputs the code to a text control in
/// the client.
/// </summary>
/// <param name="automationID">
/// The AutomationID of the current control.
/// </param>
/// <param name="eventID">
/// The event recorded on that control.
/// </param>
///--------------------------------------------------------------------
private void WriteToScript(string automationID, string eventID)
{
// Script code would be generated and written to an output file
// as plain text at this point, but for the
// purposes of this example we just write to the console.
Console.WriteLine(automationID + " - " + eventID);
}
'''--------------------------------------------------------------------
''' <summary>
''' Function to playback through a series of recorded events calling
''' a WriteToScript function for each event of interest.
''' </summary>
''' <remarks>
''' A major drawback to using AutomationID for recording user
''' interactions in a volatile UI is the probability of catastrophic
''' change in the UI. For example, the 'Processes' dialog where items
''' in the listbox container can change with no input from the user.
''' This mandates that a record and playback application must be
''' reliant on the tester owning the UI being tested. In other words,
''' there has to be a contract between the provider and client that
''' excludes uncontrolled, external applications. The added benefit
''' is the guarantee that each control in the UI should have an
''' AutomationID assigned to it.
'''
''' This function relies on a UI Automation worker class to create
''' the System.Collections.Generic.Queue object that stores the
''' information for the recorded user interactions. This
''' allows post-processing of the recorded items prior to actually
''' writing them to a script. If this is not necessary the interaction
''' could be written to the script immediately.
''' </remarks>
'''--------------------------------------------------------------------
Private Sub Playback(ByVal targetApp As AutomationElement)
Dim element As AutomationElement
Dim storedItem As ElementStore
For Each storedItem In uiautoWorker.elementQueue
Dim propertyCondition As New PropertyCondition( _
AutomationElement.AutomationIdProperty, storedItem.AutomationID)
' Confirm the existence of a control.
' Depending on the controls and complexity of interaction
' this step may not be necessary or may require additional
' functionality. For example, to confirm the existence of a
' child menu item that had been invoked the parent menu item
' would have to be expanded.
element = targetApp.FindFirst( _
TreeScope.Descendants, propertyCondition)
If element Is Nothing Then
' Control not available, unable to continue.
' TODO: Handle error condition.
Return
End If
WriteToScript(storedItem.AutomationID, storedItem.EventID)
Next storedItem
End Sub
'''--------------------------------------------------------------------
''' <summary>
''' Generates script code and outputs the code to a text control in
''' the client.
''' </summary>
''' <param name="automationID">
''' The AutomationID of the current control.
''' </param>
''' <param name="eventID">
''' The event recorded on that control.
''' </param>
'''--------------------------------------------------------------------
Private Sub WriteToScript( _
ByVal automationID As String, ByVal eventID As String)
' Script code would be generated and written to an output file
' as plain text at this point, but for the
' purposes of this example we just write to the console.
Console.WriteLine(automationID + " - " + eventID)
End Sub
使用相对路径返回到先前标识的 AutomationElement
- 在某些情况下,由于 AutomationID 只在同级元素之间保证唯一性,因此在 UI 自动化树中,多个元素可能会有相同的 AutomationID 属性值。 在这些情况下,可以根据父元素和(如有必要)祖父元素来唯一标识元素。 例如,开发人员可以提供一个菜单栏,其中包含多个菜单项,每个菜单项包含多个子菜单项,其中子项使用顺序 AutomationID 进行标识,例如“Item1”、“Item2”等。 然后,每个菜单项都可以由其 AutomationID 及其父级的 AutomationID 唯一标识,如有必要,也可以由其祖父标识。