ロケーター
はじめに
ロケーターは、Playwrightの自動待機とリトライ機能の中心的な要素です。簡単に言えば、ロケーターはページ上の要素をいつでも見つける方法を表します。
クイックガイド
これらが推奨される組み込みロケーターです。
- Page.GetByRole(): 明示的および暗黙的なアクセシビリティ属性で要素を特定します。
- Page.GetByText(): テキストコンテンツで要素を特定します。
- Page.GetByLabel(): 関連付けられたラベルのテキストでフォームコントロールを特定します。
- Page.GetByPlaceholder(): プレースホルダーで入力要素を特定します。
- Page.GetByAltText(): テキスト代替で要素(通常は画像)を特定します。
- Page.GetByTitle(): タイトル属性で要素を特定します。
- 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`で名前が「Sign in」の要素を特定します。
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
コードジェネレーターを使用してロケーターを生成し、必要に応じて編集してください。
ロケーターがアクションに使用されるたびに、ページ内の最新のDOM要素が特定されます。以下のスニペットでは、基になるDOM要素が各アクションの前に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ロール、ARIA属性、およびアクセシブルネームに関する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テキストで特定
すべての画像には、画像を説明する`alt`属性があるべきです。Page.GetByAltText()を使用して、テキスト代替に基づいて画像を特定できます。
例えば、次のDOM構造を考えてみましょう。
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
テキスト代替で画像を特定した後、その画像をクリックできます。
await Page.GetByAltText("playwright logo").ClickAsync();
`img`要素や`area`要素のようにaltテキストをサポートする要素を特定する際に、このロケーターを使用してください。
タイトルで特定
Page.GetByTitle()を使用して、一致するタイトル属性を持つ要素を特定します。
例えば、次の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ロケーターを再度使用して、ボタンのロールで要素を取得しクリックし、さらに「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」ロケーターは両方に一致し、「strict mode violation」エラーをスローする可能性があります。この場合、Locator.Firstを使用してどちらか一方のみに一致させることができます。
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番目のボタンのみを見つけ、クリックします。
await page.Locator("button").Filter(new() { Visible = true }).ClickAsync();
リスト
リスト内のアイテムを数える
リスト内のアイテム数を数えるためにロケーターをアサートできます。
例えば、次のDOM構造を考えてみましょう。
- apple
- banana
- orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
リストに3つのアイテムがあることを確認するために、カウントアサーションを使用します。
await Expect(Page.GetByRole(AriaRole.Listitem)).ToHaveCountAsync(3);
リスト内のすべてのテキストをアサート
リスト内のすべてのテキストを見つけるためにロケーターをアサートできます。
例えば、次のDOM構造を考えてみましょう。
- apple
- banana
- orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
リストに「apple」、「banana」、「orange」のテキストがあることを確認するために、Expect(Locator).ToHaveTextAsync()を使用します。
await Expect(Page
.GetByRole(AriaRole.Listitem))
.ToHaveTextAsync(new string[] {"apple", "banana", "orange"});
特定のアイテムを取得する
リスト内の特定のアイテムを取得するには多くの方法があります。
テキストで取得
Page.GetByText()メソッドを使用して、リスト内の要素をそのテキストコンテンツで特定し、クリックします。
例えば、次のDOM構造を考えてみましょう。
- apple
- banana
- orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
アイテムをそのテキストコンテンツで特定し、クリックします。
await page.GetByText("orange").ClickAsync();
テキストでフィルタリング
Locator.Filter()を使用して、リスト内の特定のアイテムを特定します。
例えば、次のDOM構造を考えてみましょう。
- apple
- banana
- orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
ロールが「listitem」のアイテムを特定し、次にテキスト「orange」でフィルタリングしてクリックします。
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "orange" })
.ClickAsync();
テストIDで取得
Page.GetByTestId()メソッドを使用して、リスト内の要素を特定します。テストIDがまだない場合は、HTMLを修正してテストIDを追加する必要があるかもしれません。
例えば、次のDOM構造を考えてみましょう。
- apple
- banana
- orange
<ul>
<li data-testid='apple'>apple</li>
<li data-testid='banana'>banana</li>
<li data-testid='orange'>orange</li>
</ul>
テストIDが「orange」のアイテムを特定し、クリックします。
await page.GetByTestId("orange").ClickAsync();
nthアイテムで取得
同じ要素のリストがあり、それらを区別する唯一の方法が順序である場合、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>
「Mary」と「Say goodbye」を含む行のスクリーンショットを撮るには
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();
複数の要素が一致する場合にどの要素を使用するかをPlaywrightに伝えることで、Locator.First、Locator.Last、およびLocator.Nth()を介して厳密性チェックを明示的にオプトアウトできます。これらのメソッドは、ページが変更されたときにPlaywrightが意図しない要素をクリックする可能性があるため、推奨されません。代わりに、上記のベストプラクティスに従って、ターゲット要素を一意に識別するロケーターを作成してください。
その他のロケーター
あまり一般的に使用されないロケーターについては、その他のロケーターガイドを参照してください。