Использование свойства AutomationID
Примечание.
Эта документация предназначена для разработчиков .NET Framework, желающих использовать управляемые классы автоматизации пользовательского интерфейса, определенные в пространстве имен System.Windows.Automation. Последние сведения об автоматизации пользовательского интерфейса см. в статье API автоматизации Windows. Автоматизация пользовательского интерфейса.
В этом разделе содержатся сценарии и пример кода, показывающие, как и когда AutomationIdProperty его можно использовать для поиска элемента в дереве модель автоматизации пользовательского интерфейса.
AutomationIdProperty уникально идентифицирует элемент модели автоматизации пользовательского интерфейса среди элементов того же уровня. Дополнительные сведения об идентификаторах свойств, связанных с идентификацией элементов управления, см. в разделе UI Automation Properties Overview.
Примечание.
AutomationIdProperty не гарантирует уникальную идентификацию по всему дереву; обычно для его использования требуются сведения о контейнере и области. Например, приложение может содержать элемент управления "Меню" с несколькими пунктами меню верхнего уровня, которые в свою очередь имеют несколько дочерних пунктов меню. Эти пункты меню второго уровня могут идентифицироваться по универсальной схеме, такой как "элемент1", "элемент2" и т. д., что допускает повторяющиеся идентификаторы дочерних элементов в разных пунктах меню верхнего уровня.
Сценарии
Были определены три основных сценария клиентского приложения модели автоматизации пользовательского интерфейса, в которых необходимо использовать AutomationIdProperty для получения точных и согласованных результатов при поиске элементов.
Примечание.
AutomationIdPropertyподдерживается всеми элементами модель автоматизации пользовательского интерфейса в представлении элементов управления, кроме окон приложений верхнего уровня, модель автоматизации пользовательского интерфейса элементы, производные от элементов управления Windows Presentation Foundation (WPF), которые не имеют идентификатора или x:Uid, и модель автоматизации пользовательского интерфейса элементы, производные от элементов управления Win32, которые не имеют идентификатора элемента управления.
Для поиска определенного элемента в дереве модели автоматизации пользовательского интерфейса используйте уникальный и обнаруживаемый AutomationID.
- Используйте средство, например UI Spy, чтобы сообщить 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
- Клиентским приложениям, от простых тестовых скриптов до до надежных программ записи и воспроизведения, может потребоваться доступ к элементам, в текущий момент не реализованным, таким как диалоговое окно открытия файла или пункт меню, и поэтому не существующим в дереве модели автоматизации пользовательского интерфейса. Эти элементы можно создать только путем воспроизведения или воспроизведения, определенной последовательности действий пользовательского интерфейса с помощью свойств модель автоматизации пользовательского интерфейса, таких как AutomationID, шаблоны элементов управления и прослушиватели событий.
///--------------------------------------------------------------------
/// <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, поскольку AutomationID гарантированно уникален только среди элементов того же уровня. В этих случаях элементы можно однозначно идентифицировать на основе родителя и при необходимости прародителя. Например, разработчик может создать строку меню с несколькими пунктами меню, каждый из которых имеет несколько дочерних пунктов меню, где дочерние пункты идентифицируются с помощью последовательных идентификаторов AutomationID, например Item1, Item2 и т. д. Каждый пункт меню может затем быть однозначно идентифицирован по его идентификатору AutomationID вместе с AutomationID его родителя и при необходимости его прародителя.