AutomationID プロパティの使用
Note
このドキュメントは、System.Windows.Automation 名前空間で定義されているマネージド UI オートメーション クラスを使用する .NET Framework 開発者を対象としています。 UI オートメーションの最新情報については、Windows Automation API の「UI オートメーション」を参照してください。
このトピックには、AutomationIdProperty を使用して UI オートメーション ツリー内の要素を配置する方法とタイミングを示すシナリオおよびサンプル コードが記載されています。
AutomationIdProperty は、UI オートメーション要素をその兄弟から一意に識別します。 コントロール ID に関連するプロパティの識別子の詳細については、「 UI Automation Properties Overview」を参照してください。
Note
AutomationIdProperty では、ツリー全体で一意の ID は保証されません。これが役に立つには、通常、コンテナーとスコープ情報が必要です。 たとえば、アプリケーションには複数のトップレベルのメニュー項目を持つメニュー コントロールが含まれ、さらに、それらのメニュー項目に複数の子メニュー項目が含まれている場合があります。 これらの 2 次メニュー項目は、"Item1"、"Item 2" などの汎用スキームで識別され、トップレベルのメニュー項目間で子の識別子が重複することがあります。
シナリオ
要素を検索するときに、正確で一貫性のある結果を実現するために AutomationIdProperty の使用を必要とする 3 つの主な UI オートメーション クライアント アプリケーションのシナリオが識別されています。
Note
AutomationIdProperty をサポートしているのは、最上位のアプリケーション ウィンドウ以外のコントロール ビューにあるすべての UI オートメーション要素、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 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 オートメーション ツリーに存在しない要素にアクセスしなければならないことがあります。 これらの要素をインスタンス化するには、AutomationID、コントロール パターンとイベント リスナーなどの UI オートメーション プロパティを使用することで、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 プロパティの値を持っていることがあります。 このような場合、親 (または必要に応じて親の親) に基づいて、要素を一意に識別できます。 たとえば、開発者が複数のメニュー項目を持ち、それぞれに複数の子メニュー項目があるメニュー バーを提供するとします。ここで、子は "Item1"、"Item2" など、シーケンシャルの AutomationID で識別されます。 各メニュー項目は、それ自体の AutomationID と、その親の AutomationID (必要に応じて親の親の AutomationID も) によって一意に識別されます。