ロケーター
はじめに
ロケーターは、Playwright の自動ウェイトおよびリトライ機能の中核となる要素です。一言で言えば、ロケーターは任意の瞬間にページ上の要素を見つける方法を表します。
クイックガイド
これらは推奨される組み込みロケーターです。
- Page.GetByRole(): 明示的および暗黙的なアクセシビリティ属性で位置特定します。
- Page.GetByText(): テキストコンテンツで位置特定します。
- Page.GetByLabel(): 関連付けられたラベルのテキストでフォームコントロールを位置特定します。
- Page.GetByPlaceholder(): プレースホルダーで入力を位置特定します。
- Page.GetByAltText(): 要素 (通常は画像) をテキスト代替で位置特定します。
- Page.GetByTitle(): 要素を title 属性で位置特定します。
- Page.GetByTestId(): 要素を
data-testid
属性に基づいて位置特定します (他の属性も設定可能)。
await Page.GetByLabel("User Name").FillAsync("John");
await Page.GetByLabel("Password").FillAsync("secret-password");
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
await Expect(Page.GetByText("Welcome, John!")).ToBeVisibleAsync();
要素の特定
Playwright には、複数の組み込みロケーターが付属しています。テストの耐性を高めるために、Page.GetByRole() のようなユーザーに接する属性と明示的なコントラクトを優先することをお勧めします。
たとえば、次の DOM 構造を考えてみましょう。
<button>Sign in</button>
名前が "サインイン" の button
のロールで要素を特定します。
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
コードジェネレーターを使用してロケーターを生成し、必要に応じて編集します。
ロケーターがアクションに使用されるたびに、最新の DOM 要素がページ内で特定されます。以下のスニペットでは、基になる DOM 要素は、すべてのアクションの前に 1 回ずつ、合計 2 回特定されます。これは、再レンダリングによって呼び出しの合間に DOM が変更された場合、ロケーターに対応する新しい要素が使用されることを意味します。
var locator = Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
await locator.HoverAsync();
await locator.ClickAsync();
Page.GetByLabel() など、ロケーターを作成するすべてのメソッドは、Locator および FrameLocator クラスでも利用できることに注意してください。したがって、それらをチェーンして、ロケーターを段階的に絞り込むことができます。
var locator = Page
.FrameLocator("#my-frame")
.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
await locator.ClickAsync();
ロールで位置特定
Page.GetByRole() ロケーターは、ユーザーと支援技術がページをどのように認識するかを反映します。たとえば、要素がボタンかチェックボックスかなどです。ロールで位置特定する場合は、通常、アクセシブルな名前も渡して、ロケーターが正確な要素を特定するようにする必要があります。
たとえば、次の DOM 構造を考えてみましょう。
サインアップ
<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
各要素を暗黙的なロールで特定できます
await Expect(Page
.GetByRole(AriaRole.Heading, new() { Name = "Sign up" }))
.ToBeVisibleAsync();
await Page
.GetByRole(AriaRole.Checkbox, new() { Name = "Subscribe" })
.CheckAsync();
await Page
.GetByRole(AriaRole.Button, new() {
NameRegex = new Regex("submit", RegexOptions.IgnoreCase)
})
.ClickAsync();
ロールロケーターには、ボタン、チェックボックス、見出し、リンク、リスト、テーブルなどが含まれており、ARIA role、ARIA attributes、アクセシブルな名前に関する W3C 仕様に従います。<button>
のような多くの html 要素には、ロールロケーターによって認識される 暗黙的に定義されたロールがあることに注意してください。
ロールロケーターは、アクセシビリティ監査および適合性テストの代わりにはならないことに注意してください。むしろ、ARIA ガイドラインに関する早期フィードバックを提供します。
要素を特定するには、ロールロケーターを優先することをお勧めします。これは、ユーザーと支援技術がページを認識する方法に最も近い方法だからです。
ラベルで位置特定
ほとんどのフォームコントロールには、通常、フォームとの対話に便利に使用できる専用のラベルがあります。この場合、Page.GetByLabel() を使用して、関連付けられたラベルでコントロールを特定できます。
たとえば、次の DOM 構造を考えてみましょう。
<label>Password <input type="password" /></label>
ラベルテキストで特定した後、入力を入力できます
await Page.GetByLabel("Password").FillAsync("secret");
フォームフィールドを特定する場合は、このロケーターを使用します。
プレースホルダーで位置特定
入力には、ユーザーに入力すべき値をヒントするためのプレースホルダー属性がある場合があります。Page.GetByPlaceholder() を使用して、そのような入力を特定できます。
たとえば、次の DOM 構造を考えてみましょう。
<input type="email" placeholder="name@example.com" />
プレースホルダーテキストで特定した後、入力を入力できます
await Page
.GetByPlaceholder("name@example.com")
.FillAsync("playwright@microsoft.com");
ラベルはないがプレースホルダーテキストはあるフォーム要素を特定する場合は、このロケーターを使用します。
テキストで位置特定
要素に含まれるテキストで要素を見つけます。Page.GetByText() を使用すると、部分文字列、完全一致文字列、または正規表現で一致させることができます。
たとえば、次の DOM 構造を考えてみましょう。
<span>Welcome, John</span>
要素に含まれるテキストで要素を特定できます
await Expect(Page.GetByText("Welcome, John")).ToBeVisibleAsync();
完全一致を設定
await Expect(Page
.GetByText("Welcome, John", new() { Exact = true }))
.ToBeVisibleAsync();
正規表現で一致
await Expect(Page
.GetByText(new Regex("welcome, john", RegexOptions.IgnoreCase)))
.ToBeVisibleAsync();
テキストによる一致は、完全一致の場合でも常に空白を正規化します。たとえば、複数のスペースを 1 つに変換したり、改行をスペースに変換したり、先頭と末尾の空白を無視したりします。
div
、span
、p
などのインタラクティブでない要素を見つけるには、テキストロケーターを使用することをお勧めします。button
、a
、input
などのインタラクティブな要素の場合は、ロールロケーターを使用してください。
リスト内の特定の項目を見つけようとするときに役立つ テキストでフィルター処理することもできます。
代替テキストで位置特定
すべての画像には、画像を説明する alt
属性が必要です。Page.GetByAltText() を使用して、テキスト代替に基づいて画像を特定できます。
たとえば、次の DOM 構造を考えてみましょう。
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
テキスト代替で特定した後、画像をクリックできます
await Page.GetByAltText("playwright logo").ClickAsync();
img
や area
要素など、要素が代替テキストをサポートしている場合は、このロケーターを使用します。
タイトルで位置特定
Page.GetByTitle() を使用して、一致する title 属性を持つ要素を特定します。
たとえば、次の DOM 構造を考えてみましょう。
<span title='Issues count'>25 issues</span>
タイトルテキストで特定した後、課題数を確認できます
await Expect(Page.GetByTitle("Issues count")).toHaveText("25 issues");
要素に title
属性がある場合は、このロケーターを使用します。
テスト ID で位置特定
テスト ID によるテストは、属性のテキストまたはロールが変更されてもテストが合格するため、最も耐性のあるテスト方法です。QA および開発者は、明示的なテスト ID を定義し、Page.GetByTestId() でクエリする必要があります。ただし、テスト ID によるテストはユーザーに接するものではありません。ロールまたはテキスト値が重要な場合は、ロールや テキストロケーターなどのユーザーに接するロケーターを使用することを検討してください。
たとえば、次の DOM 構造を考えてみましょう。
<button data-testid="directions">Itinéraire</button>
テスト ID で要素を特定できます
await Page.GetByTestId("directions").ClickAsync();
カスタムテスト ID 属性を設定する
デフォルトでは、Page.GetByTestId() は data-testid
属性に基づいて要素を特定しますが、テスト構成で設定するか、Selectors.SetTestIdAttribute() を呼び出すことで設定できます。
カスタムデータ属性をテストに使用するようにテスト ID を設定します。
playwright.Selectors.SetTestIdAttribute("data-pw");
html で、デフォルトの data-testid
の代わりに data-pw
をテスト ID として使用できるようになりました。
<button data-pw="directions">Itinéraire</button>
そして、通常どおりに要素を特定します
await Page.GetByTestId("directions").ClickAsync();
CSS または XPath で位置特定
どうしても CSS または XPath ロケーターを使用する必要がある場合は、Page.Locator() を使用して、ページ内の要素を見つける方法を記述するセレクターを受け取るロケーターを作成できます。Playwright は CSS および XPath セレクターをサポートしており、css=
または xpath=
プレフィックスを省略すると自動的に検出します。
await Page.Locator("css=button").ClickAsync();
await Page.Locator("xpath=//button").ClickAsync();
await Page.Locator("button").ClickAsync();
await Page.Locator("//button").ClickAsync();
XPath および CSS セレクターは、DOM 構造または実装に結び付けられる可能性があります。これらのセレクターは、DOM 構造が変更されると壊れる可能性があります。以下の長い CSS または XPath チェーンは、不安定なテストにつながる悪い例です。
await Page.Locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").ClickAsync();
await Page.Locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").ClickAsync();
DOM は頻繁に変更される可能性があり、耐性のないテストにつながるため、CSS および XPath は推奨されません。代わりに、ロールロケーターなど、ユーザーがページを認識する方法に近いロケーターを考案するか、テスト ID を使用して 明示的なテストコントラクトを定義してみてください。
Shadow DOM で位置特定
Playwright のすべてのロケーターは、デフォルトで Shadow DOM 内の要素で動作します。例外は次のとおりです。
- XPath による位置特定は、シャドウルートを貫通しません。
- クローズドモードのシャドウルートはサポートされていません。
カスタム Web コンポーネントの次の例を考えてみましょう
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
シャドウルートが存在しない場合と同じ方法で位置特定できます。
<div>Details</div>
をクリックするには
await page.GetByText("Details").ClickAsync();
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
<x-details>
をクリックするには
await page
.Locator("x-details", new() { HasText = "Details" })
.ClickAsync();
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
<x-details>
にテキスト "Details" が含まれていることを確認するには
await Expect(Page.Locator("x-details")).ToContainTextAsync("Details");
ロケーターのフィルタリング
2 番目の製品カードの [購入] ボタンをクリックしたい次の DOM 構造を考えてみましょう。適切なものを取得するためにロケーターをフィルタリングするためのいくつかのオプションがあります。
製品 1
製品 2
<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
テキストでフィルター処理
ロケーターは、Locator.Filter() メソッドを使用してテキストでフィルタリングできます。要素内のどこか、おそらく子孫要素内で特定に文字列を検索します (大文字と小文字は区別されません)。正規表現を渡すこともできます。
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "Product 2" })
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();
正規表現を使用する
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasTextRegex = new Regex("Product 2") })
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();
テキストがないことでフィルター処理
または、テキストを持たないことでフィルター処理します
// 5 in-stock items
await Expect(Page.getByRole(AriaRole.Listitem).Filter(new() { HasNotText = "Out of stock" }))
.ToHaveCountAsync(5);
子/子孫でフィルター処理
ロケーターは、別のロケーターに一致する子孫を持つ要素のみを選択するオプションをサポートしています。したがって、Locator.GetByRole()、Locator.GetByTestId()、Locator.GetByText() など、他のロケーターでフィルター処理できます。
製品 1
製品 2
<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
Has = page.GetByRole(AriaRole.Heading, new() {
Name = "Product 2"
})
})
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();
製品カードをアサートして、1 つしかないことを確認することもできます
await Expect(Page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
Has = page.GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
}))
.ToHaveCountAsync(1);
フィルター処理ロケーターは、元のロケーターに相対的である必要があり、ドキュメントルートではなく、元のロケーターの一致からクエリが開始されます。したがって、次のコードは機能しません。これは、フィルター処理ロケーターが元のロケーターによって一致された <li>
リスト項目の外側にある <ul>
リスト要素から一致を開始するためです。
// ✖ WRONG
await Expect(Page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
Has = page.GetByRole(AriaRole.List).GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
}))
.ToHaveCountAsync(1);
子/子孫を持たないことでフィルター処理
一致する要素を内部に持たないことでフィルター処理することもできます。
await Expect(Page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
HasNot = page.GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
}))
.ToHaveCountAsync(1);
内部ロケーターは、ドキュメントルートからではなく、外部ロケーターから開始して一致することに注意してください。
ロケーター演算子
ロケーター内での一致
Page.GetByText() や Locator.GetByRole() のようなロケーターを作成するメソッドをチェーンして、ページの特定の部分への検索を絞り込むことができます。
この例では、最初に listitem
のロールを特定して product というロケーターを作成します。次に、テキストでフィルター処理します。product ロケーターを再度使用してボタンのロールで取得してクリックし、アサーションを使用してテキスト "製品 2" を持つ製品が 1 つしかないことを確認できます。
var product = page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "Product 2" });
await product
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();
2 つのロケーターをチェーンすることもできます。たとえば、特定のダイアログ内の [保存] ボタンを見つける場合などです
var saveButton = page.GetByRole(AriaRole.Button, new() { Name = "Save" });
// ...
var dialog = page.GetByTestId("settings-dialog");
await dialog.Locator(saveButton).ClickAsync();
2 つのロケーターを同時に一致させる
Locator.And() メソッドは、追加のロケーターを一致させることによって、既存のロケーターを絞り込みます。たとえば、Page.GetByRole() と Page.GetByTitle() を組み合わせて、ロールとタイトルの両方で一致させることができます。
var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
2 つの代替ロケーターのいずれかに一致させる
2 つ以上の要素のいずれかをターゲットにしたいが、どちらになるかわからない場合は、Locator.Or() を使用して、代替案のいずれかまたは両方に一致するロケーターを作成します。
たとえば、[新しいメール] ボタンをクリックしたいが、代わりにセキュリティ設定ダイアログが表示される場合があるシナリオを考えてみましょう。この場合、[新しいメール] ボタンまたはダイアログのいずれかを待って、それに応じて動作できます。
[新しいメール] ボタンとセキュリティダイアログの両方が画面に表示される場合、「or」ロケーターは両方と一致し、「厳密モード違反」エラーが発生する可能性があります。この場合、Locator.First を使用して、それらのうちの 1 つだけを一致させることができます。
var newEmail = page.GetByRole(AriaRole.Button, new() { Name = "New" });
var dialog = page.GetByText("Confirm security settings");
await Expect(newEmail.Or(dialog).First).ToBeVisibleAsync();
if (await dialog.IsVisibleAsync())
await page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync();
await newEmail.ClickAsync();
表示されている要素のみを一致させる
通常、可視性を確認する代わりに、要素を一意に識別する より信頼性の高い方法を見つける方が良いでしょう。
最初のボタンが非表示で、2 番目のボタンが 表示されている 2 つのボタンがあるページを考えてみましょう。
<button style='display: none'>Invisible</button>
<button>Visible</button>
-
これは両方のボタンを見つけ、厳密違反エラーをスローします
await page.Locator("button").ClickAsync();
-
これは、2 番目のボタンが表示されているため、2 番目のボタンのみを見つけてクリックします。
await page.Locator("button").Filter(new() { Visible = true }).ClickAsync();
リスト
リスト内の項目を数える
リスト内の項目の数を数えるためにロケーターをアサートできます。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
リストに 3 つの項目があることを確認するには、カウントアサーションを使用します。
await Expect(Page.GetByRole(AriaRole.Listitem)).ToHaveCountAsync(3);
リスト内のすべてのテキストをアサートする
リスト内のすべてのテキストを見つけるためにロケーターをアサートできます。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
Expect(Locator).ToHaveTextAsync() を使用して、リストにテキスト "りんご"、"バナナ"、および "オレンジ" が含まれていることを確認します。
await Expect(Page
.GetByRole(AriaRole.Listitem))
.ToHaveTextAsync(new string[] {"apple", "banana", "orange"});
特定の項目を取得する
リスト内の特定の項目を取得する方法はたくさんあります。
テキストで取得
Page.GetByText() メソッドを使用して、リスト内の要素をテキストコンテンツで特定し、クリックします。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
項目をテキストコンテンツで特定してクリックします。
await page.GetByText("orange").ClickAsync();
テキストでフィルター処理
Locator.Filter() を使用して、リスト内の特定の項目を特定します。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
項目のロールを "listitem" で特定し、テキスト "オレンジ" でフィルター処理してからクリックします。
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "orange" })
.ClickAsync();
テスト ID で取得
Page.GetByTestId() メソッドを使用して、リスト内の要素を特定します。テスト ID がまだない場合は、html を変更してテスト ID を追加する必要がある場合があります。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li data-testid='apple'>apple</li>
<li data-testid='banana'>banana</li>
<li data-testid='orange'>orange</li>
</ul>
項目をテスト ID "オレンジ" で特定してクリックします。
await page.GetByTestId("orange").ClickAsync();
n 番目の項目で取得
同一の要素のリストがあり、それらを区別する唯一の方法が順序である場合は、Locator.First、Locator.Last、または Locator.Nth() を使用して、リストから特定の要素を選択できます。
var banana = await page.GetByRole(AriaRole.Listitem).Nth(1);
ただし、このメソッドは注意して使用してください。多くの場合、ページが変更され、ロケーターが予期していた要素とはまったく異なる要素を指す可能性があります。代わりに、厳密性基準に合格する一意のロケーターを考案してみてください。
フィルターのチェーン
さまざまな類似性を持つ要素がある場合は、Locator.Filter() メソッドを使用して適切な要素を選択できます。複数のフィルターをチェーンして選択範囲を絞り込むこともできます。
たとえば、次の DOM 構造を考えてみましょう
- ジョン
- メアリー
- ジョン
- メアリー
<ul>
<li>
<div>John</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>John</div>
<div><button>Say goodbye</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say goodbye</button></div>
</li>
</ul>
"メアリー" と "さようならと言う" が含まれる行のスクリーンショットを撮るには
var rowLocator = page.GetByRole(AriaRole.Listitem);
await rowLocator
.Filter(new() { HasText = "Mary" })
.Filter(new() {
Has = page.GetByRole(AriaRole.Button, new() { Name = "Say goodbye" })
})
.ScreenshotAsync(new() { Path = "screenshot.png" });
これで、プロジェクトのルートディレクトリに "screenshot.png" ファイルが作成されます。
まれなユースケース
リスト内の各要素で何かを行う
要素を反復処理する
foreach (var row in await page.GetByRole(AriaRole.Listitem).AllAsync())
Console.WriteLine(await row.TextContentAsync());
通常の for ループを使用して反復処理する
var rows = page.GetByRole(AriaRole.Listitem);
var count = await rows.CountAsync();
for (int i = 0; i < count; ++i)
Console.WriteLine(await rows.Nth(i).TextContentAsync());
ページ内で評価する
Locator.EvaluateAllAsync() 内のコードはページ内で実行され、そこで任意の DOM API を呼び出すことができます。
var rows = page.GetByRole(AriaRole.Listitem);
var texts = await rows.EvaluateAllAsync(
"list => list.map(element => element.textContent)");
厳密性
ロケーターは厳密です。これは、何らかのターゲット DOM 要素を意味するロケーターに対するすべての操作は、複数の要素が一致する場合に例外をスローすることを意味します。たとえば、次の呼び出しは、DOM に複数のボタンがある場合にスローされます。
複数ある場合はエラーをスローする
await page.GetByRole(AriaRole.Button).ClickAsync();
一方、Playwright は、複数要素の操作を実行するときを理解しているため、ロケーターが複数の要素に解決される場合、次の呼び出しは完全に正常に機能します。
複数の要素で正常に動作する
await page.GetByRole(AriaRole.Button).CountAsync();
Locator.First、Locator.Last、および Locator.Nth() を使用して、複数の要素が一致する場合に Playwright に使用する要素を指示することで、厳密性チェックを明示的にオプトアウトできます。これらのメソッドは、ページが変更されたときに、Playwright が意図しない要素をクリックする可能性があるため、推奨されません。代わりに、上記のベストプラクティスに従って、ターゲット要素を一意に識別するロケーターを作成してください。
その他のロケーター
あまり使用されないロケーターについては、その他のロケーターガイドを参照してください。