ロケーター
はじめに
Locatorは、Playwright の自動ウェイトおよびリトライ機能の中核となるものです。一言で言えば、ロケーターとは、任意の瞬間にページ上の要素を見つける方法を表します。
クイックガイド
これらは推奨される組み込みロケーターです。
- Page.getByRole() 明示的および暗黙的なアクセシビリティ属性で特定します。
- Page.getByText() テキストコンテンツで特定します。
- Page.getByLabel() 関連付けられたラベルのテキストでフォームコントロールを特定します。
- Page.getByPlaceholder() プレースホルダーで入力を特定します。
- Page.getByAltText() 要素(通常は画像)をテキストの代替で特定します。
- Page.getByTitle() 要素を title 属性で特定します。
- Page.getByTestId()
data-testid
属性に基づいて要素を特定します(他の属性も設定可能です)。
page.getByLabel("User Name").fill("John");
page.getByLabel("Password").fill("secret-password");
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
.click();
assertThat(page.getByText("Welcome, John!")).isVisible();
要素の特定
Playwright には複数の組み込みロケーターが付属しています。テストの耐性を高めるために、Page.getByRole() のようなユーザー向けの属性と明示的なコントラクトを優先することをお勧めします。
たとえば、次の DOM 構造を考えてみましょう。
<button>Sign in</button>
名前が「サインイン」の button
ロールで要素を特定します。
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
.click();
コードジェネレーターを使用してロケーターを生成し、必要に応じて編集してください。
アクションにロケーターが使用されるたびに、最新の DOM 要素がページ内で特定されます。以下のスニペットでは、基になる DOM 要素は、各アクションの前に 1 回ずつ、2 回特定されます。これは、再レンダリングによって呼び出しの間に DOM が変更された場合、ロケーターに対応する新しい要素が使用されることを意味します。
Locator locator = page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in"));
locator.hover();
locator.click();
Page.getByLabel() のようなロケーターを作成するすべてのメソッドは、Locator クラスと FrameLocator クラスでも利用できるため、それらをチェーンして、ロケーターを反復的に絞り込むことができます。
Locator locator = page
.frameLocator("#my-frame")
.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"));
locator.click();
ロールで特定
Page.getByRole() ロケーターは、ユーザーと支援技術がページをどのように認識するかを反映します。たとえば、要素がボタンなのかチェックボックスなのかなどです。ロールで特定する場合、通常はアクセシブルな名前も渡して、ロケーターが正確な要素を特定できるようにする必要があります。
たとえば、次の DOM 構造を考えてみましょう。
サインアップ
<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
各要素を暗黙的なロールで特定できます
assertThat(page
.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Sign up")))
.isVisible();
page.getByRole(AriaRole.CHECKBOX,
new Page.GetByRoleOptions().setName("Subscribe"))
.check();
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName(
Pattern.compile("submit", Pattern.CASE_INSENSITIVE)))
.click();
ロールロケーターには、ボタン、チェックボックス、見出し、リンク、リスト、テーブルなどが含まれており、ARIA ロール、ARIA 属性、アクセシブルな名前に関する W3C 仕様に従います。<button>
のような多くの html 要素には、ロールロケーターによって認識される暗黙的に定義されたロールがあることに注意してください。
ロールロケーターはアクセシビリティ監査と適合性テストに取って代わるものではありませんが、ARIA ガイドラインに関する早期フィードバックを提供します。
ユーザーと支援技術がページを認識する方法に最も近い方法であるため、要素を特定するためにロールロケーターを優先することをお勧めします。
ラベルで特定
ほとんどのフォームコントロールには、通常、フォームとの対話に便利に使用できる専用のラベルがあります。この場合、Page.getByLabel() を使用して、関連付けられたラベルでコントロールを特定できます。
たとえば、次の DOM 構造を考えてみましょう。
<label>Password <input type="password" /></label>
ラベルテキストで特定した後に入力できます
page.getByLabel("Password").fill("secret");
フォームフィールドを特定する場合にこのロケーターを使用します。
プレースホルダーで特定
入力には、ユーザーに入力する必要がある値をヒントするためのプレースホルダー属性がある場合があります。Page.getByPlaceholder() を使用して、そのような入力を特定できます。
たとえば、次の DOM 構造を考えてみましょう。
<input type="email" placeholder="name@example.com" />
プレースホルダーテキストで特定した後に入力できます
page.getByPlaceholder("name@example.com").fill("playwright@microsoft.com");
ラベルはないがプレースホルダーテキストはあるフォーム要素を特定する場合に、このロケーターを使用します。
テキストで特定
要素に含まれるテキストで要素を検索します。Page.getByText() を使用する場合、部分文字列、完全一致文字列、または正規表現で一致させることができます。
たとえば、次の DOM 構造を考えてみましょう。
<span>Welcome, John</span>
要素に含まれるテキストで要素を特定できます
assertThat(page.getByText("Welcome, John")).isVisible();
完全一致を設定
assertThat(page
.getByText("Welcome, John", new Page.GetByTextOptions().setExact(true)))
.isVisible();
正規表現で一致
assertThat(page
.getByText(Pattern.compile("welcome, john$", Pattern.CASE_INSENSITIVE)))
.isVisible();
テキストによるマッチングは、完全一致であっても常に空白を正規化します。たとえば、複数のスペースを 1 つのスペースに変換し、改行をスペースに変換し、先頭と末尾の空白を無視します。
div
、span
、p
などの非インタラクティブ要素を見つけるには、テキストロケーターを使用することをお勧めします。button
、a
、input
などのインタラクティブ要素には、ロールロケーターを使用してください。
リスト内の特定のアイテムを見つけようとするときに役立つテキストでフィルター処理することもできます。
alt テキストで特定
すべての画像には、画像を説明する alt
属性が必要です。Page.getByAltText() を使用して、テキストの代替に基づいて画像を特定できます。
たとえば、次の DOM 構造を考えてみましょう。
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
テキストの代替で特定した後、画像をクリックできます
page.getByAltText("playwright logo").click();
img
や area
要素など、要素が alt テキストをサポートしている場合に、このロケーターを使用します。
title で特定
Page.getByTitle() を使用して、一致する title 属性を持つ要素を特定します。
たとえば、次の DOM 構造を考えてみましょう。
<span title='Issues count'>25 issues</span>
title テキストで特定した後、問題の数を確認できます
assertThat(page.getByTitle("Issues count")).hasText("25 issues");
要素に title
属性がある場合に、このロケーターを使用します。
テスト ID で特定
テスト ID によるテストは、属性のテキストまたはロールが変更された場合でもテストが合格するため、最も回復力のあるテスト方法です。QA と開発者は、明示的なテスト ID を定義し、Page.getByTestId() でクエリする必要があります。ただし、テスト ID によるテストはユーザー向けではありません。ロールまたはテキストの値が重要な場合は、ロールロケーターやテキストロケーターなどのユーザー向けのロケーターの使用を検討してください。
たとえば、次の DOM 構造を考えてみましょう。
<button data-testid="directions">Itinéraire</button>
要素をテスト ID で特定できます
page.getByTestId("directions").click();
カスタムテスト 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>
そして、通常行うように要素を特定します
page.getByTestId("directions").click();
CSS または XPath で特定
どうしても CSS または XPath ロケーターを使用する必要がある場合は、Page.locator() を使用して、ページ内の要素を検索する方法を記述するセレクターを受け取るロケーターを作成できます。Playwright は CSS および XPath セレクターをサポートしており、css=
または xpath=
プレフィックスを省略すると自動検出します。
page.locator("css=button").click();
page.locator("xpath=//button").click();
page.locator("button").click();
page.locator("//button").click();
XPath および CSS セレクターは、DOM 構造または実装に結び付けることができます。これらのセレクターは、DOM 構造が変更されると壊れる可能性があります。以下の長い CSS または XPath チェーンは、不安定なテストにつながる悪い例です
page.locator(
"#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input"
).click();
page.locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").click();
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>
をクリックするには
page.getByText("Details").click();
<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>
をクリックするには
page.locator("x-details", new Page.LocatorOptions().setHasText("Details"))
.click();
<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」が含まれていることを確認するには
assertThat(page.locator("x-details")).containsText("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() メソッドを使用してテキストでフィルタリングできます。要素内のどこか、おそらく子孫要素内の特定の文字列を、大文字と小文字を区別せずに検索します。正規表現を渡すこともできます。
page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasText("Product 2"))
.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Add to cart"))
.click();
正規表現を使用する
page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHasText(Pattern.compile("Product 2")))
.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Add to cart"))
.click();
テキストがないことでフィルタリング
または、テキストがないことでフィルタリング
// 5 in-stock items
assertThat(page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasNotText("Out of stock")))
.hasCount(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>
page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHas(page.GetByRole(AriaRole.HEADING, new Page.GetByRoleOptions()
.setName("Product 2"))))
.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Add to cart"))
.click();
製品カードが 1 つしかないことを確認するためにアサートすることもできます
assertThat(page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHas(page.GetByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Product 2")))))
.hasCount(1);
フィルタリングロケーターは、元のロケーターに対して相対的である必要があり、ドキュメントルートではなく、元のロケーターの一致からクエリが開始されます。したがって、フィルタリングロケーターが、元のロケーターによって一致した <li>
リストアイテムの外部にある <ul>
リスト要素から一致を開始するため、以下は機能しません。
// ✖ WRONG
assertThat(page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHas(page.GetByRole(AriaRole.LIST)
.GetByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Product 2")))))
.hasCount(1);
子/子孫がないことでフィルタリング
内部に一致する要素がないことでフィルタリングすることもできます。
assertThat(page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasNot(page.getByText("Product 2"))))
.hasCount(1);
内部ロケーターは、ドキュメントルートからではなく、外部ロケーターから一致を開始することに注意してください。
ロケーター演算子
ロケーター内でのマッチング
Page.getByText() や Locator.getByRole() のようなロケーターを作成するメソッドをチェーンして、ページの特定の部分に検索を絞り込むことができます。
この例では、最初に listitem
のロールを特定することにより、product というロケーターを作成します。次に、テキストでフィルタリングします。product ロケーターを再度使用して、ボタンのロールで取得してクリックし、テキスト「製品 2」の製品が 1 つしかないことを確認するためにアサーションを使用できます。
Locator product = page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasText("Product 2"));
product
.getByRole(AriaRole.BUTTON,
new Locator.GetByRoleOptions().setName("Add to cart"))
.click();
2 つのロケーターをチェーンすることもできます。たとえば、特定のダイアログ内にある [保存] ボタンを見つける場合などです
Locator saveButton = page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Save"));
// ...
Locator dialog = page.getByTestId("settings-dialog");
dialog.locator(saveButton).click();
2 つのロケーターを同時にマッチング
Locator.and() メソッドは、追加のロケーターを一致させることにより、既存のロケーターを絞り込みます。たとえば、Page.getByRole() と Page.getByTitle() を組み合わせて、ロールとタイトルの両方で一致させることができます。
Locator button = page.getByRole(AriaRole.BUTTON).and(page.getByTitle("Subscribe"));
2 つの代替ロケーターのいずれかをマッチング
2 つ以上の要素のいずれかをターゲットにしたい場合、どれになるかわからない場合は、Locator.or() を使用して、いずれかまたは両方の代替に一致するロケーターを作成します。
たとえば、[新しいメール] ボタンをクリックしたいが、代わりにセキュリティ設定ダイアログが表示されるシナリオを考えてみましょう。この場合、[新しいメール] ボタンまたはダイアログのいずれかを待機し、それに応じてアクションを実行できます。
[新しいメール] ボタンとセキュリティダイアログの両方が画面に表示された場合、「or」ロケーターは両方に一致し、場合によっては「厳密モード違反」エラーがスローされます。この場合、Locator.first() を使用して、それらのいずれか 1 つのみを一致させることができます。
Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New"));
Locator dialog = page.getByText("Confirm security settings");
assertThat(newEmail.or(dialog).first()).isVisible();
if (dialog.isVisible())
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click();
newEmail.click();
可視要素のみをマッチング
通常、可視性を確認するのではなく、要素を一意に識別するためのより信頼性の高い方法を見つける方が良いでしょう。
2 つのボタンがあるページを考えてみましょう。最初のボタンは非表示で、2 番目のボタンは表示されています。
<button style='display: none'>Invisible</button>
<button>Visible</button>
-
これにより、両方のボタンが見つかり、厳密性違反エラーがスローされます
page.locator("button").click();
-
これにより、2 番目のボタンのみが見つかり、可視であるため、クリックされます。
page.locator("button").filter(new Locator.FilterOptions.setVisible(true)).click();
リスト
リスト内のアイテム数を数える
リスト内のアイテム数を数えるためにロケーターをアサートできます。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
リストに 3 つのアイテムがあることを確認するために、カウントアサーションを使用します。
assertThat(page.getByRole(AriaRole.LISTITEM)).hasCount(3);
リスト内のすべてのテキストをアサート
リスト内のすべてのテキストを見つけるためにロケーターをアサートできます。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
リストにテキスト「りんご」、「バナナ」、「オレンジ」があることを確認するには、assertThat(locator).hasText() を使用します。
assertThat(page
.getByRole(AriaRole.LISTITEM))
.hasText(new String[] { "apple", "banana", "orange" });
特定のアイテムを取得
リスト内の特定のアイテムを取得する方法はたくさんあります。
テキストで取得
Page.getByText() メソッドを使用して、リスト内の要素をテキストコンテンツで特定し、クリックします。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
アイテムをテキストコンテンツで特定してクリックします。
page.getByText("orange").click();
テキストでフィルタリング
Locator.filter() を使用して、リスト内の特定のアイテムを特定します。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
アイテムを「listitem」のロールで特定し、次に「オレンジ」のテキストでフィルタリングしてからクリックします。
page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasText("orange"))
.click();
テスト 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 で特定してクリックします。
page.getByTestId("orange").click();
n 番目のアイテムで取得
同一の要素のリストがあり、それらを区別する唯一の方法が順序である場合、Locator.first()、Locator.last()、または Locator.nth() を使用してリストから特定の要素を選択できます。
Locator banana = 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>
「メアリー」と「さようなら」の行のスクリーンショットを撮るには
Locator rowLocator = page.getByRole(AriaRole.LISTITEM);
rowLocator
.filter(new Locator.FilterOptions().setHasText("Mary"))
.filter(new Locator.FilterOptions()
.setHas(page.getByRole(
AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Say goodbye"))))
.screenshot(new Page.ScreenshotOptions().setPath("screenshot.png"));
プロジェクトのルートディレクトリに「screenshot.png」ファイルが作成されているはずです。
まれなユースケース
リスト内の各要素に対して何かを行う
要素を反復処理
for (Locator row : page.getByRole(AriaRole.LISTITEM).all())
System.out.println(row.textContent());
通常の for ループを使用して反復処理
Locator rows = page.getByRole(AriaRole.LISTITEM);
int count = rows.count();
for (int i = 0; i < count; ++i)
System.out.println(rows.nth(i).textContent());
ページで評価
Locator.evaluateAll() 内のコードはページ内で実行され、そこで任意の DOM API を呼び出すことができます。
Locator rows = page.getByRole(AriaRole.LISTITEM);
Object texts = rows.evaluateAll(
"list => list.map(element => element.textContent)");
厳密性
ロケーターは厳密です。これは、ターゲット DOM 要素を暗示するロケーターに対するすべての操作は、複数の要素が一致した場合に例外をスローすることを意味します。たとえば、次の呼び出しは、DOM に複数のボタンがある場合にスローされます
複数ある場合はエラーをスロー
page.getByRole(AriaRole.BUTTON).click();
一方、Playwright は複数要素の操作を実行するときを理解しているため、ロケーターが複数の要素に解決される場合、次の呼び出しは完全に正常に機能します。
複数の要素で正常に機能
page.getByRole(AriaRole.BUTTON).count();
Locator.first()、Locator.last()、および Locator.nth() を使用して、複数の要素が一致した場合に使用する要素を Playwright に指示することにより、厳密性チェックを明示的にオプトアウトできます。これらのメソッドは、ページが変更されると、Playwright が意図しない要素をクリックする可能性があるため、推奨されません。代わりに、上記のベストプラクティスに従って、ターゲット要素を一意に識別するロケーターを作成してください。
その他のロケーター
あまり一般的に使用されないロケーターについては、その他のロケーターガイドをご覧ください。