ロケーター
はじめに
Locator は、Playwright の自動ウェイトとリトライ機能の中核となる要素です。簡単に言うと、ロケーターは、任意の瞬間にページ上の要素を見つけるための方法を表します。
クイックガイド
これらは、推奨される組み込みロケーターです。
- page.getByRole() を使用して、明示的および暗黙的なアクセシビリティ属性で位置を特定します。
- page.getByText() を使用して、テキストコンテンツで位置を特定します。
- page.getByLabel() を使用して、関連付けられたラベルのテキストでフォームコントロールの位置を特定します。
- page.getByPlaceholder() を使用して、プレースホルダーで入力を特定します。
- page.getByAltText() を使用して、要素 (通常は画像) をテキスト代替で特定します。
- page.getByTitle() を使用して、title 属性で要素を特定します。
- page.getByTestId() を使用して、
data-testid
属性に基づいて要素を特定します (他の属性も設定可能)。
await page.getByLabel('User Name').fill('John');
await page.getByLabel('Password').fill('secret-password');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByText('Welcome, John!')).toBeVisible();
要素の特定
Playwright には、複数の組み込みロケーターが付属しています。テストの耐性を高めるために、page.getByRole() などのユーザー向けの属性と明示的なコントラクトを優先することをお勧めします。
たとえば、次の DOM 構造を考えてみましょう。
<button>Sign in</button>
ロールが button
で名前が "サインイン" の要素を特定します。
await page.getByRole('button', { name: 'Sign in' }).click();
コードジェネレーター を使用してロケーターを生成し、必要に応じて編集します。
ロケーターがアクションに使用されるたびに、最新の DOM 要素がページ内で特定されます。以下のスニペットでは、基になる DOM 要素は、すべてのアクションの前に 1 回ずつ、合計 2 回特定されます。これは、再レンダリングによって呼び出しの合間に DOM が変更された場合、ロケーターに対応する新しい要素が使用されることを意味します。
const locator = page.getByRole('button', { name: 'Sign in' });
await locator.hover();
await locator.click();
page.getByLabel() など、ロケーターを作成するすべてのメソッドは、Locator クラスと FrameLocator クラスでも使用できるため、それらをチェーンしてロケーターを段階的に絞り込むことができます。
const locator = page
.frameLocator('#my-frame')
.getByRole('button', { name: 'Sign in' });
await locator.click();
ロールによる特定
page.getByRole() ロケーターは、ユーザーと支援技術がページをどのように認識するかを反映します。たとえば、一部の要素がボタンまたはチェックボックスであるかどうかなどです。ロールで特定する場合は、通常、アクセス可能な名前も渡して、ロケーターが正確な要素を特定するようにする必要があります。
たとえば、次の DOM 構造を考えてみましょう。
サインアップ
<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
各要素を暗黙的なロールで特定できます
await expect(page.getByRole('heading', { name: 'Sign up' })).toBeVisible();
await page.getByRole('checkbox', { name: 'Subscribe' }).check();
await page.getByRole('button', { name: /submit/i }).click();
ロールロケーターには、ボタン、チェックボックス、見出し、リンク、リスト、テーブルなど多数 が含まれており、ARIA ロール、ARIA 属性、および アクセス可能な名前 に関する W3C 仕様に従います。<button>
などの多くの HTML 要素には、ロールロケーターによって認識される 暗黙的に定義されたロール があることに注意してください。
ロールロケーターは、アクセシビリティ監査および適合性テストを置き換えるものではなく、ARIA ガイドラインに関する早期フィードバックを提供するものであることに注意してください。
要素を特定するには、ロールロケーターを優先することをお勧めします。これは、ユーザーと支援技術がページを認識する方法に最も近い方法であるためです。
ラベルによる特定
ほとんどのフォームコントロールには通常、フォームの操作に便利に使用できる専用のラベルがあります。この場合、page.getByLabel() を使用して、関連付けられたラベルでコントロールを特定できます。
たとえば、次の DOM 構造を考えてみましょう。
<label>Password <input type="password" /></label>
ラベルテキストで特定した後、入力を入力できます
await page.getByLabel('Password').fill('secret');
フォームフィールドを特定する場合は、このロケーターを使用してください。
プレースホルダーによる特定
入力には、ユーザーに入力する値をヒントするためのプレースホルダー属性がある場合があります。page.getByPlaceholder() を使用して、そのような入力を特定できます。
たとえば、次の DOM 構造を考えてみましょう。
<input type="email" placeholder="name@example.com" />
プレースホルダーテキストで特定した後、入力を入力できます
await page
.getByPlaceholder('name@example.com')
.fill('playwright@microsoft.com');
ラベルはないがプレースホルダーテキストがあるフォーム要素を特定する場合は、このロケーターを使用してください。
テキストによる特定
要素に含まれるテキストで要素を見つけます。page.getByText() を使用すると、部分文字列、完全一致文字列、または正規表現で一致させることができます。
たとえば、次の DOM 構造を考えてみましょう。
<span>Welcome, John</span>
要素に含まれるテキストで要素を特定できます
await expect(page.getByText('Welcome, John')).toBeVisible();
完全一致を設定
await expect(page.getByText('Welcome, John', { exact: true })).toBeVisible();
正規表現で一致
await expect(page.getByText(/welcome, [A-Za-z]+$/i)).toBeVisible();
テキストによるマッチングは、完全一致の場合でも常に空白を正規化します。たとえば、複数のスペースを 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').click();
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').click();
カスタムテスト ID 属性の設定
デフォルトでは、page.getByTestId() は data-testid
属性に基づいて要素を特定しますが、テスト構成で設定するか、selectors.setTestIdAttribute() を呼び出すことで設定できます。
テストに使用するカスタムデータ属性にテスト ID を設定します。
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
testIdAttribute: 'data-pw'
}
});
HTML で、デフォルトの data-testid
の代わりに data-pw
をテスト ID として使用できるようになりました。
<button data-pw="directions">Itinéraire</button>
そして、通常どおりに要素を特定します
await page.getByTestId('directions').click();
CSS または XPath による特定
CSS または XPath ロケーターをどうしても使用する必要がある場合は、page.locator() を使用して、ページ内の要素を見つける方法を記述するセレクターを受け取るロケーターを作成できます。Playwright は CSS および XPath セレクターをサポートしており、css=
または xpath=
プレフィックスを省略すると自動検出します。
await page.locator('css=button').click();
await page.locator('xpath=//button').click();
await page.locator('button').click();
await page.locator('//button').click();
XPath および CSS セレクターは、DOM 構造または実装に結び付けることができます。これらのセレクターは、DOM 構造が変更されると壊れる可能性があります。以下の長い CSS または XPath チェーンは、不安定なテストにつながる悪い例です
await page.locator(
'#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input'
).click();
await page
.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
.click();
DOM は頻繁に変更される可能性があり、耐性の低いテストにつながるため、CSS および XPath は推奨されません。代わりに、ロールロケーター など、ユーザーがページを認識する方法に近いロケーターを考案するか、テスト ID を使用して 明示的なテストコントラクトを定義 してみてください。
シャドウ DOM 内の特定
Playwright のすべてのロケーターは、デフォルトで シャドウ 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>詳細</div>
をクリックするには
await 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>
をクリックするには
await page.locator('x-details', { hasText: '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>
にテキスト "詳細" が含まれていることを確認するには
await expect(page.locator('x-details')).toContainText('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('listitem')
.filter({ hasText: 'Product 2' })
.getByRole('button', { name: 'Add to cart' })
.click();
正規表現を使用する
await page
.getByRole('listitem')
.filter({ hasText: /Product 2/ })
.getByRole('button', { name: 'Add to cart' })
.click();
テキストがないことによるフィルター処理
または、テキストがないことによってフィルター処理します
// 5 in-stock items
await expect(page.getByRole('listitem').filter({ hasNotText: 'Out of stock' })).toHaveCount(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('listitem')
.filter({ has: page.getByRole('heading', { name: 'Product 2' }) })
.getByRole('button', { name: 'Add to cart' })
.click();
製品カードが 1 つしかないことを確認するために、製品カードをアサートすることもできます
await expect(page
.getByRole('listitem')
.filter({ has: page.getByRole('heading', { name: 'Product 2' }) }))
.toHaveCount(1);
フィルター処理ロケーターは、元のロケーターに相対的である必要があり、ドキュメントルートではなく、元のロケーターの一致から始まるクエリが実行されます。したがって、次のコードは機能しません。これは、フィルター処理ロケーターが、元のロケーターによって一致された <li>
リストアイテムの外側にある <ul>
リスト要素から一致を開始するためです。
// ✖ WRONG
await expect(page
.getByRole('listitem')
.filter({ has: page.getByRole('list').getByText('Product 2') }))
.toHaveCount(1);
子/子孫を持たないことによるフィルター処理
内部に一致する要素がないことによってフィルター処理することもできます。
await expect(page
.getByRole('listitem')
.filter({ hasNot: page.getByText('Product 2') }))
.toHaveCount(1);
内側のロケーターは、ドキュメントルートからではなく、外側のロケーターから一致が開始されることに注意してください。
ロケーター演算子
ロケーター内での一致
page.getByText() や locator.getByRole() など、ロケーターを作成するメソッドをチェーンして、ページの特定の部分に検索を絞り込むことができます。
この例では、最初にロールが listitem
であることを特定して、product というロケーターを作成します。次に、テキストでフィルター処理します。product ロケーターを再度使用して、ロールが button であることを取得してクリックし、アサーションを使用して、テキスト "製品 2" を持つ製品が 1 つしかないことを確認できます。
const product = page.getByRole('listitem').filter({ hasText: 'Product 2' });
await product.getByRole('button', { name: 'Add to cart' }).click();
await expect(product).toHaveCount(1);
2 つのロケーターをチェーンすることもできます。たとえば、特定のダイアログ内の [保存] ボタンを見つける場合などです。
const saveButton = page.getByRole('button', { name: 'Save' });
// ...
const dialog = page.getByTestId('settings-dialog');
await dialog.locator(saveButton).click();
2 つのロケーターを同時に一致させる
locator.and() メソッドは、追加のロケーターを一致させることで既存のロケーターを絞り込みます。たとえば、page.getByRole() と page.getByTitle() を組み合わせて、ロールとタイトルの両方で一致させることができます。
const button = page.getByRole('button').and(page.getByTitle('Subscribe'));
2 つの代替ロケーターのいずれかに一致させる
2 つ以上の要素のいずれかをターゲットにしたいが、どちらになるかわからない場合は、locator.or() を使用して、いずれかの代替または両方に一致するロケーターを作成します。
たとえば、[新しいメール] ボタンをクリックしたいが、代わりにセキュリティ設定ダイアログが表示されるシナリオを考えてみましょう。この場合、[新しいメール] ボタンまたはダイアログのいずれかを待機し、それに応じて対応できます。
[新しいメール] ボタンとセキュリティダイアログの両方が画面に表示される場合、"or" ロケーターは両方に一致し、"厳密モード違反" エラー が発生する可能性があります。この場合、locator.first() を使用して、いずれか 1 つのみを一致させることができます。
const newEmail = page.getByRole('button', { name: 'New' });
const dialog = page.getByText('Confirm security settings');
await expect(newEmail.or(dialog).first()).toBeVisible();
if (await dialog.isVisible())
await page.getByRole('button', { name: 'Dismiss' }).click();
await newEmail.click();
表示されている要素のみを一致させる
通常、可視性を確認する代わりに、要素を一意に識別する より信頼性の高い方法 を見つける方が適切です。
2 つのボタン (最初は非表示、2 番目は 表示) があるページを考えてみましょう。
<button style='display: none'>Invisible</button>
<button>Visible</button>
-
これにより、両方のボタンが見つかり、厳密性 違反エラーがスローされます
await page.locator('button').click();
-
これは、2 番目のボタンのみが見つかり、表示されているため、それをクリックします。
await page.locator('button').filter({ visible: true }).click();
リスト
リスト内のアイテム数をカウントする
リスト内のアイテム数をカウントするためにロケーターをアサートできます。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
リストに 3 つのアイテムがあることを確認するには、count アサーションを使用します。
await expect(page.getByRole('listitem')).toHaveCount(3);
リスト内のすべてのテキストをアサートする
リスト内のすべてのテキストを見つけるためにロケーターをアサートできます。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
リストにテキスト "りんご"、"バナナ"、"オレンジ" が含まれていることを確認するには、expect(locator).toHaveText() を使用します。
await expect(page
.getByRole('listitem'))
.toHaveText(['apple', 'banana', 'orange']);
特定のアイテムを取得する
リスト内の特定のアイテムを取得するには、多くの方法があります。
テキストで取得
page.getByText() メソッドを使用して、リスト内の要素をテキストコンテンツで特定し、クリックします。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
テキストコンテンツでアイテムを特定してクリックします。
await page.getByText('orange').click();
テキストでフィルター処理
locator.filter() を使用して、リスト内の特定のアイテムを特定します。
たとえば、次の DOM 構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
ロールが "listitem" のアイテムを特定し、テキスト "オレンジ" でフィルター処理してクリックします。
await page
.getByRole('listitem')
.filter({ hasText: '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 が "オレンジ" のアイテムを特定してクリックします。
await page.getByTestId('orange').click();
n 番目のアイテムで取得
同一の要素のリストがあり、それらを区別する唯一の方法が順序である場合は、locator.first()、locator.last()、または locator.nth() を使用して、リストから特定の要素を選択できます。
const banana = await page.getByRole('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>
"メアリー" と "さようならを言う" を含む行のスクリーンショットを撮るには
const rowLocator = page.getByRole('listitem');
await rowLocator
.filter({ hasText: 'Mary' })
.filter({ has: page.getByRole('button', { name: 'Say goodbye' }) })
.screenshot({ path: 'screenshot.png' });
これで、プロジェクトのルートディレクトリに "screenshot.png" ファイルが作成されます。
まれなユースケース
リスト内の各要素に対して何かを行う
要素の反復処理
for (const row of await page.getByRole('listitem').all())
console.log(await row.textContent());
通常の for ループを使用した反復処理
const rows = page.getByRole('listitem');
const count = await rows.count();
for (let i = 0; i < count; ++i)
console.log(await rows.nth(i).textContent());
ページ内で評価する
locator.evaluateAll() 内のコードはページ内で実行され、そこで任意の DOM API を呼び出すことができます。
const rows = page.getByRole('listitem');
const texts = await rows.evaluateAll(
list => list.map(element => element.textContent));
厳密性
ロケーターは厳密です。これは、ターゲット DOM 要素を暗示するロケーターに対するすべての操作は、複数の要素が一致した場合に例外をスローすることを意味します。たとえば、DOM に複数のボタンがある場合、次の呼び出しは例外をスローします
複数ある場合はエラーをスローする
await page.getByRole('button').click();
一方、Playwright は複数要素操作を実行するときを理解しているため、ロケーターが複数の要素に解決される場合、次の呼び出しは完全に正常に機能します。
複数の要素で正常に動作する
await page.getByRole('button').count();
複数の要素が一致した場合に Playwright に使用する要素を指示することで、locator.first()、locator.last()、および locator.nth() を使用して厳密性チェックから明示的にオプトアウトできます。これらのメソッドは、ページが変更されたときに Playwright が意図しない要素をクリックする可能性があるため、推奨されません。代わりに、上記のベストプラクティスに従って、ターゲット要素を一意に識別するロケーターを作成してください。
その他のロケーター
あまり使用されないロケーターについては、その他のロケーター ガイドを参照してください。