メインコンテンツにスキップ

ロケーター

はじめに

ロケーターは、Playwrightの自動待機と再試行可能性の中心となるものです。簡単に言えば、ロケーターはいつでもページ上の要素を見つける方法を表します。

クイックガイド

以下は推奨される組み込みロケーターです。

  • page.get_by_role() で明示的および暗黙的なアクセシビリティ属性によって要素を特定します。
  • page.get_by_text() でテキストコンテンツによって要素を特定します。
  • page.get_by_label() で関連付けられたラベルのテキストによってフォームコントロールを特定します。
  • page.get_by_placeholder() でプレースホルダーによって入力を特定します。
  • page.get_by_alt_text() でテキストの代替によって要素(通常は画像)を特定します。
  • page.get_by_title() でtitle属性によって要素を特定します。
  • page.get_by_test_id()data-testid 属性に基づいて要素を特定します (他の属性も設定可能です)。
page.get_by_label("User Name").fill("John")

page.get_by_label("Password").fill("secret-password")

page.get_by_role("button", name="Sign in").click()

expect(page.get_by_text("Welcome, John!")).to_be_visible()

要素の特定

Playwrightには複数の組み込みロケーターが付属しています。テストの回復力を高めるために、page.get_by_role() のようなユーザー向け属性と明示的な契約を優先することをお勧めします。

例えば、以下のDOM構造を考えてみましょう。

https://:3000
<button>Sign in</button>

「Sign in」という名前のbuttonの役割で要素を特定します。

page.get_by_role("button", name="Sign in").click()

コードジェネレーターを使用してロケーターを生成し、必要に応じて編集してください。

ロケーターがアクションに使用されるたびに、最新のDOM要素がページ内で特定されます。以下のスニペットでは、基になるDOM要素が各アクションの前に2回特定されます。これは、再レンダリングによって呼び出しの間にDOMが変更された場合、ロケーターに対応する新しい要素が使用されることを意味します。

locator = page.get_by_role("button", name="Sign in")

locator.hover()
locator.click()

page.get_by_label() のようにロケーターを作成するすべてのメソッドは、Locator クラスと FrameLocator クラスでも利用できるため、それらを連結してロケーターを反復的に絞り込むことができます。

locator = page.frame_locator("my-frame").get_by_role("button", name="Sign in")

locator.click()

役割による特定

page.get_by_role() ロケーターは、ユーザーと支援技術がページをどのように認識するか、例えばある要素がボタンなのかチェックボックスなのかを反映します。ロールによって要素を特定する場合、通常はアクセシブルネームも渡して、ロケーターが正確な要素を特定できるようにする必要があります。

例えば、以下のDOM構造を考えてみましょう。

https://:3000

サインアップ

<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>

各要素をその暗黙的な役割で特定できます。

expect(page.get_by_role("heading", name="Sign up")).to_be_visible()

page.get_by_role("checkbox", name="Subscribe").check()

page.get_by_role("button", name=re.compile("submit", re.IGNORECASE)).click()

役割ロケーターには、ボタン、チェックボックス、見出し、リンク、リスト、テーブルなどが含まれ、ARIAロールARIA属性アクセシブルな名前に関するW3C仕様に従います。<button>のような多くのhtml要素には、役割ロケーターによって認識される暗黙的に定義された役割があることに注意してください。

役割ロケーターは、アクセシビリティ監査や適合性テストを置き換えるものではないことに注意してください。むしろ、ARIAガイドラインに関する早期フィードバックを提供します。

役割ロケーターを使用する場合

ユーザーや支援技術がページを認識する方法に最も近い方法であるため、要素を特定するために役割ロケーターを優先することをお勧めします。

ラベルによる特定

ほとんどのフォームコントロールには、フォームとのやり取りに便利な専用のラベルが付いています。この場合、page.get_by_label() を使用して、関連付けられたラベルによってコントロールを特定できます。

例えば、以下のDOM構造を考えてみましょう。

https://:3000
<label>Password <input type="password" /></label>

ラベルテキストで要素を特定した後、入力を埋めることができます。

page.get_by_label("Password").fill("secret")
ラベルロケーターを使用する場合

フォームフィールドを特定する場合にこのロケーターを使用します。

プレースホルダーによる特定

入力には、ユーザーに入力すべき値をヒントとして示すプレースホルダー属性がある場合があります。page.get_by_placeholder() を使用してそのような入力を特定できます。

例えば、以下のDOM構造を考えてみましょう。

https://:3000
<input type="email" placeholder="name@example.com" />

プレースホルダーテキストで入力を見つけてから、入力できます。

page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com")
プレースホルダーロケーターを使用する場合

ラベルがなく、プレースホルダーテキストがあるフォーム要素を特定する場合に、このロケーターを使用します。

テキストによる特定

要素が含むテキストで要素を見つけます。page.get_by_text() を使用する場合、部分文字列、厳密な文字列、または正規表現で一致させることができます。

例えば、以下のDOM構造を考えてみましょう。

https://:3000
ようこそ、ジョンさん
<span>Welcome, John</span>

要素をそれが含むテキストで特定できます

expect(page.get_by_text("Welcome, John")).to_be_visible()

厳密な一致を設定する

expect(page.get_by_text("Welcome, John", exact=True)).to_be_visible()

正規表現で一致

expect(page.get_by_text(re.compile("welcome, john", re.IGNORECASE))).to_be_visible()

テキストによる一致は、厳密な一致であっても常に空白を正規化します。例えば、複数のスペースを1つにまとめ、改行をスペースに変換し、先頭と末尾の空白を無視します。

テキストロケーターを使用する場合

divspanpなどの非対話型要素を見つけるにはテキストロケーターを使用することをお勧めします。buttonainputなどの対話型要素には役割ロケーターを使用してください。

また、リスト内の特定のアイテムを見つけようとする場合に便利なテキストによるフィルタリングも可能です。

altテキストによる特定

すべての画像には、画像を説明する alt 属性が必要です。page.get_by_alt_text() を使用して、代替テキストに基づいて画像を特定できます。

例えば、以下のDOM構造を考えてみましょう。

https://:3000
playwright logo
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />

代替テキストで画像を特定した後、クリックできます。

page.get_by_alt_text("playwright logo").click()
altロケーターを使用する場合

imgarea要素など、altテキストをサポートする要素がある場合にこのロケーターを使用します。

タイトルによる特定

page.get_by_title() を使用して、一致するtitle属性を持つ要素を特定します。

例えば、以下のDOM構造を考えてみましょう。

https://:3000
25件の問題
<span title='Issues count'>25 issues</span>

タイトルテキストで問題数を特定した後、確認できます。

expect(page.get_by_title("Issues count")).to_have_text("25 issues")
タイトルロケーターを使用する場合

要素にtitle属性がある場合にこのロケーターを使用します。

テストIDによる特定

テストIDによるテストは、テキストや属性のロールが変更された場合でもテストが成功するため、最も堅牢なテスト方法です。QAと開発者は明示的なテストIDを定義し、page.get_by_test_id() でクエリする必要があります。ただし、テストIDによるテストはユーザー向けではありません。ロールやテキスト値が重要である場合は、ロールテキストロケーターなどのユーザー向けロケーターの使用を検討してください。

例えば、以下のDOM構造を考えてみましょう。

https://:3000
<button data-testid="directions">Itinéraire</button>

要素をテストIDで特定できます。

page.get_by_test_id("directions").click()
testidロケーターを使用する場合

テストID方式を使用する場合や、役割テキストで特定できない場合にもテストIDを使用できます。

カスタムテストID属性を設定する

デフォルトでは、page.get_by_test_id()data-testid 属性に基づいて要素を特定しますが、テスト設定で、または selectors.set_test_id_attribute() を呼び出すことで設定できます。

テストIDを設定して、テストにカスタムデータ属性を使用します。

playwright.selectors.set_test_id_attribute("data-pw")

HTMLでは、デフォルトのdata-testidの代わりにdata-pwをテストIDとして使用できるようになりました。

https://:3000
<button data-pw="directions">Itinéraire</button>

そして、通常どおり要素を特定します

page.get_by_test_id("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内の要素と連携します。例外は以下のとおりです。

カスタム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.get_by_text("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", has_text="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」というテキストが含まれていることを確認するには

expect(page.locator("x-details")).to_contain_text("Details")

ロケーターのフィルタリング

2番目の製品カードの購入ボタンをクリックしたいという次のDOM構造を考えてみましょう。適切なロケーターを取得するためにロケーターをフィルタリングするにはいくつかのオプションがあります。

https://:3000
  • 製品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.get_by_role("listitem").filter(has_text="Product 2").get_by_role(
"button", name="Add to cart"
).click()

正規表現を使用する

page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role(
"button", name="Add to cart"
).click()

テキストがないことによるフィルタリング

あるいは、テキストがないことによるフィルタリング

# 5 in-stock items
expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)

子/子孫によるフィルタリング

ロケーターは、別のロケーターに一致する子孫を持つ要素、または持たない要素のみを選択するオプションをサポートしています。したがって、locator.get_by_role()locator.get_by_test_id()locator.get_by_text() など、他のロケーターでフィルタリングできます。

https://:3000
  • 製品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.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
).get_by_role("button", name="Add to cart").click()

また、製品カードが1つしかないことを確認するためにアサートすることもできます。

expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)

フィルタリングロケーターは、元のロケーターに対して相対的でなければならず、ドキュメントルートではなく、元のロケーターの一致から照会されます。したがって、以下のコードは機能しません。なぜなら、フィルタリングロケーターが、元のロケーターによって一致した<li>リストアイテムの外側にある<ul>リスト要素から一致を開始するからです。

# ✖ WRONG
expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("list").get_by_role("heading", name="Product 2")
)
).to_have_count(1)

子/子孫がないことによるフィルタリング

また、内部に一致する要素がないことでフィルタリングすることもできます。

expect(
page.get_by_role("listitem").filter(
has_not=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)

内部ロケーターは、ドキュメントルートからではなく、外部ロケーターから一致することに注意してください。

ロケーター演算子

ロケーター内部でのマッチング

page.get_by_text()locator.get_by_role() のようにロケーターを作成するメソッドを連結して、検索をページの特定の部分に絞り込むことができます。

この例では、まずlistitemの役割を特定して「product」というロケーターを作成します。次に、テキストでフィルタリングします。製品ロケーターを再度使用してボタンの役割で取得し、クリックした後、アサーションを使用して「Product 2」というテキストを持つ製品が1つだけであることを確認できます。

product = page.get_by_role("listitem").filter(has_text="Product 2")

product.get_by_role("button", name="Add to cart").click()

また、2つのロケーターを連結することもできます。例えば、特定のダイアログ内の「Save」ボタンを見つける場合などです。

save_button = page.get_by_role("button", name="Save")
# ...
dialog = page.get_by_test_id("settings-dialog")
dialog.locator(save_button).click()

2つのロケーターの同時一致

メソッド locator.and_() は、追加のロケーターと一致させることで既存のロケーターを絞り込みます。例えば、page.get_by_role()page.get_by_title() を組み合わせて、ロールとタイトルの両方で一致させることができます。

button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))

2つの代替ロケーターのいずれかに一致させる

2つ以上の要素のいずれかをターゲットにしたいが、どれになるか分からない場合は、locator.or_() を使用して、いずれかまたは両方の代替に一致するロケーターを作成します。

例えば、「新しいメール」ボタンをクリックしたいが、時々セキュリティ設定ダイアログが表示されるシナリオを考えてみましょう。この場合、「新しいメール」ボタンかダイアログのいずれかを待機し、それに応じて動作できます。

"新規メール"ボタンとセキュリティダイアログの両方が画面に表示される場合、"or"ロケーターは両方に一致し、場合によっては"厳密モード違反"エラーが発生します。この場合、locator.first を使用して、いずれか1つのみに一致させることができます。

new_email = page.get_by_role("button", name="New")
dialog = page.get_by_text("Confirm security settings")
expect(new_email.or_(dialog).first).to_be_visible()
if (dialog.is_visible()):
page.get_by_role("button", name="Dismiss").click()
new_email.click()

表示されている要素のみに一致させる

通常、可視性を確認するよりも、要素を一意に識別するより信頼性の高い方法を見つける方が良いです。

最初のボタンが非表示で、2番目のボタンが表示されている、2つのボタンがあるページを考えます。

<button style='display: none'>Invisible</button>
<button>Visible</button>
  • これにより両方のボタンが見つかり、厳密性違反エラーが発生します。

    page.locator("button").click()
  • これにより、2番目のボタンのみが見つかり、クリックされます。なぜなら、それが表示されているからです。

    page.locator("button").filter(visible=True).click()

リスト

リスト内のアイテムを数える

リスト内のアイテムを数えるために、ロケーターをアサートできます。

例えば、次のDOM構造を考えてみましょう

https://:3000
  • りんご
  • バナナ
  • オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

カウントアサーションを使用して、リストに3つのアイテムがあることを確認します。

expect(page.get_by_role("listitem")).to_have_count(3)

リスト内のすべてのテキストをアサートする

リスト内のすべてのテキストを見つけるために、ロケーターをアサートできます。

例えば、次のDOM構造を考えてみましょう

https://:3000
  • りんご
  • バナナ
  • オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

リストに「apple」、「banana」、「orange」のテキストが含まれていることを確認するには、expect(locator).to_have_text() を使用します。

expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])

特定のアイテムを取得する

リスト内の特定のアイテムを取得する方法はたくさんあります。

テキストで取得する

page.get_by_text() メソッドを使用して、リスト内の要素をテキストコンテンツで特定し、クリックします。

例えば、次のDOM構造を考えてみましょう

https://:3000
  • りんご
  • バナナ
  • オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

要素をそのテキストコンテンツで特定し、クリックします。

page.get_by_text("orange").click()

テキストでフィルタリングする

locator.filter() を使用して、リスト内の特定のアイテムを特定します。

例えば、次のDOM構造を考えてみましょう

https://:3000
  • りんご
  • バナナ
  • オレンジ
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

「listitem」の役割でアイテムを特定し、次に「orange」のテキストでフィルタリングし、クリックします。

page.get_by_role("listitem").filter(has_text="orange").click()

テストIDで取得する

page.get_by_test_id() メソッドを使用して、リスト内の要素を特定します。テストIDがまだない場合は、HTMLを修正してテストIDを追加する必要があるかもしれません。

例えば、次のDOM構造を考えてみましょう

https://:3000
  • りんご
  • バナナ
  • オレンジ
<ul>
<li data-testid='apple'>apple</li>
<li data-testid='banana'>banana</li>
<li data-testid='orange'>orange</li>
</ul>

「orange」のテストIDでアイテムを特定し、クリックします。

page.get_by_test_id("orange").click()

n番目のアイテムで取得する

同一の要素のリストがあり、それらを区別する唯一の方法が順序である場合は、locator.firstlocator.last、または locator.nth() を使用して、リストから特定の要素を選択できます。

banana = page.get_by_role("listitem").nth(1)

ただし、この方法は注意して使用してください。多くの場合、ページが変更され、ロケーターが予期していたものとはまったく異なる要素を指す可能性があります。代わりに、厳密性基準を満たす一意のロケーターを作成するようにしてください。

フィルタの連鎖

様々な類似点を持つ要素がある場合、locator.filter() メソッドを使用して適切な要素を選択できます。また、複数のフィルターを連結して選択範囲を絞り込むこともできます。

例えば、次のDOM構造を考えてみましょう

https://:3000
  • ジョン
  • メアリー
  • ジョン
  • メアリー
<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」を含む行のスクリーンショットを撮るには

row_locator = page.get_by_role("listitem")

row_locator.filter(has_text="Mary").filter(
has=page.get_by_role("button", name="Say goodbye")
).screenshot(path="screenshot.png")

プロジェクトのルートディレクトリに「screenshot.png」ファイルが作成されているはずです。

まれなユースケース

リスト内の各要素で何かをする

要素を反復処理する

for row in page.get_by_role("listitem").all():
print(row.text_content())

通常のforループで反復処理する

rows = page.get_by_role("listitem")
count = rows.count()
for i in range(count):
print(rows.nth(i).text_content())

ページ内で評価する

locator.evaluate_all() 内のコードはページで実行され、そこでDOM APIを呼び出すことができます。

rows = page.get_by_role("listitem")
texts = rows.evaluate_all("list => list.map(element => element.textContent)")

厳密性

ロケーターは厳密です。これは、特定のターゲットDOM要素を意味するロケーターに対するすべての操作が、複数の要素が一致した場合に例外をスローすることを意味します。例えば、DOMに複数のボタンがある場合、次の呼び出しは例外をスローします。

複数ある場合にエラーをスローする

page.get_by_role("button").click()

一方、Playwrightは複数の要素操作を実行する場合を理解しているため、ロケーターが複数の要素に解決された場合でも、次の呼び出しは完全に機能します。

複数の要素があっても問題なく動作する

page.get_by_role("button").count()

複数の要素が一致する場合にPlaywrightにどの要素を使用するかを、locator.firstlocator.last、および locator.nth() を介して明示的に伝えることで、厳密性チェックをオプトアウトできます。これらの方法は、ページが変更されたときにPlaywrightが意図しない要素をクリックする可能性があるため、推奨されません。代わりに、上記のベストプラクティスに従って、ターゲット要素を一意に識別するロケーターを作成してください。

その他のロケーター

あまり一般的でないロケーターについては、その他のロケーターガイドをご覧ください。