使用 AutomationID 屬性
注意事項 |
---|
這份文件適用於想要使用 System.Windows.Automation 命名空間中定義之 Managed UI Automation 類別的 .NET Framework 開發人員。如需 UI Automation 的最新資訊,請參閱 Windows Automation API:使用者介面自動化 (英文)。 |
本主題內含案例和範例程式碼,說明如何以及何時可使用 AutomationIdProperty 尋找 UI Automation樹狀目錄中的項目。
AutomationIdProperty 可在同層級 (Sibling) 之間唯一識別 UI 自動化項目。 如需與控制項識別相關的屬性識別項的詳細資訊,請參閱 UI 自動化屬性概觀。
注意事項 |
---|
AutomationIdProperty 並不能保證識別在整個樹狀目錄都是唯一的,通常需要搭配容器和範圍資訊一起使用。例如,應用程式可能會包含具有多個最上層功能表項目的功能表控制項,而這些項目也有多個子功能表項目。這些次要功能表項目可以由一般配置 (例如 "Item1"、"Item 2" 等) 所識別,因此在最上層功能表項目中子系可以有重複的識別項。 |
案例
目前已知有三個主要 UI 自動化用戶端應用程式案例需要使用 AutomationIdProperty,才能在搜尋項目時獲得正確且一致的結果。
注意事項 |
---|
控制項檢視中的所有 UI 自動化項目都支援 AutomationIdProperty,但最上層的應用程式視窗、衍生自沒有 ID 或 x:Uid 之 Windows Presentation Foundation (WPF) 控制項的 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 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
///--------------------------------------------------------------------
/// <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));
}
使用持續性路徑以回到先前識別的 AutomationElement
- 用戶端應用程式有簡單的測試指令碼,也有完整的錄製和播放公用程式,這些都可能需要存取目前未執行個體化的項目,例如檔案開啟對話方塊或功能表項目,而由於未執行個體化,這些項目因此並不存在於 UI 自動化樹狀目錄中。 這些項目只有透過使用 UI Automation屬性 (例如 AutomationID)、控制項模式和事件接聽程式 (Event Listener),重現或「播放」特定的user interface (UI) 動作序列,才能進行執行個體化。 請參閱Test Script Generator Sample中的範例,此範例會根據使用者與user interface (UI) 的互動,使用 Microsoft UI Automation產生測試指令碼。
'''--------------------------------------------------------------------
''' <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 'CreateUIAThread
'''--------------------------------------------------------------------
''' <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 'CreateUIAWorker
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 thtat 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 'Playback
'''--------------------------------------------------------------------
''' <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 'WriteToScript
///--------------------------------------------------------------------
/// <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>
/// 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 thtat 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);
}
使用相對路徑回到先前識別的 AutomationElement
- 在某些情況下,由於 AutomationID 只能保證在同層級之間是唯一的,因此 UI 自動化樹狀目錄可能會有多個項目的 AutomationID 屬性值完全相同。 在這些情況下,項目可以根據其上層以及其上二層 (必要時) 唯一識別。 例如,開發人員可能會提供有多個功能表項目的功能表列,每個功能表項目又有多個子功能表項目,這些子項是依循序的 AutomationID 識別,例如 "Item1"、"Item2" 等。 上述的每個功能表項目都可以利用其 AutomationID 配合其上層的 AutomationID,以及必要時再配合其上二層的 AutomationID,以此方式唯一識別。