ロケーター
はじめに
ロケーターは、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>
「Sign in」という名前のbutton
の役割で要素を特定します。
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
.click();
コードジェネレーターを使用してロケーターを生成し、必要に応じて編集してください。
ロケーターがアクションに使用されるたびに、ページ内の最新のDOM要素が特定されます。以下のスニペットでは、基になるDOM要素はアクションごとに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
属性が必要です。Page.getByAltText()を使用して、代替テキストに基づいて画像を特定できます。
たとえば、次のDOM構造を考えます。
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
代替テキストで特定した後、画像をクリックできます
page.getByAltText("playwright logo").click();
img
やarea
要素のように、代替テキストをサポートする要素がある場合にこのロケーターを使用してください。
タイトルで特定
Page.getByTitle()を使用して、一致するtitle属性を持つ要素を特定します。
たとえば、次のDOM構造を考えます。
<span title='Issues count'>25 issues</span>
タイトルテキストで特定した後、問題数をチェックできます
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による特定はシャドウルートを透過しません。
- クローズドモードのシャドウルートはサポートされていません。
カスタムウェブコンポーネントの次の例を考えます
<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ロケーターを再度使用してボタンの役割で要素を取得し、それをクリックします。その後、アサーションを使用して「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つのロケーターを連結することもできます。たとえば、特定のダイアログ内の「Save」ボタンを見つける場合などです。
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()を使用して、リストに「apple」、「banana」、「orange」のテキストがあることを確認します。
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」の役割で項目を特定し、「orange」のテキストでフィルタリングしてクリックします。
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>
「orange」のテスト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>
「Mary」と「Say goodbye」を含む行のスクリーンショットを撮るには
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が意図しない要素をクリックする可能性があるためです。代わりに、上記のベストプラクティスに従って、ターゲット要素を一意に識別するロケーターを作成してください。
その他のロケーター
あまり一般的に使用されないロケーターについては、その他のロケーターガイドを参照してください。