ロケーター
はじめに
ロケーターは、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>
「Sign in」という名前のbuttonの役割で要素を特定します。
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()を使用して、一致する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つのロケーターを連結することもできます。例えば、特定のダイアログ内の「Save」ボタンを見つける場合などです。
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を使用して、どちらか一方のみを一致させることができます。
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構造を考えてみましょう
- りんご
- バナナ
- オレンジ
<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()を使用して、リストに「apple」、「banana」、「orange」というテキストがあることを確認します。
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」の役割でアイテムを特定し、次に「orange」のテキストでフィルタリングし、クリックします。
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>
「orange」のテスト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>
「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が意図しない要素をクリックする可能性があるため、推奨されません。代わりに、上記のベストプラクティスに従って、ターゲット要素を一意に識別するロケーターを作成してください。
その他のロケーター
あまり一般的に使用されないロケーターについては、その他のロケーターガイドを参照してください。