Selenium・Playwright でブラウザ自動化をするとき、最初の難関は「要素をどう特定するか」です。CSS セレクターと XPath の書き方を理解すれば、テストコードの安定性が大きく変わります。本記事ではロケーターの基礎から、壊れにくい書き方のコツまで解説します。
📌 この記事の対象読者
| ✅ Selenium・Playwright を始めたばかりで、要素の指定方法がよく分からない方 |
| ✅ CSS セレクターと XPath の違いを整理したい方 |
| ✅ テストコードが UI 変更のたびに壊れて困っている方 |
✅ Playwright の新しいロケーター(get_by_role など)に入門したい方 |
📖 この記事を読むとできること
| ✔ HTML を見て CSS セレクター・XPath を自分で書けるようになる |
| ✔ Selenium・Playwright の両方でロケーターを正しく使えるようになる |
| ✔ 壊れにくいロケーターの書き方を選べるようになる |
✔ data-testid を活用してメンテナンスコストを下げられるようになる |
筆者は複数のプロジェクトで Selenium・Playwright を使ったブラウザ自動化テストを実践してきました。
ロケーターが原因で E2E テストが不安定になるケースは非常に多く、本記事はその経験をもとに実務で使える書き方に絞って解説します。
✅ この記事の結論
- ロケーターの優先順位は
data-testid→ ID → CSS → XPath の順が一般的 - Playwright では
get_by_role・get_by_textなど意味のあるロケーターを使うと壊れにくい - 絶対 XPath・位置依存の CSS は避けると長期メンテナンスが楽になる
ロケーターとは何か
ロケーター(Locator)とは、Web ページ上の特定の要素(ボタン・テキストボックス・リンクなど)を見つけるための指定方法です。
Selenium や Playwright がブラウザを操作するとき、「どの要素をクリックするか」「どのテキストボックスに文字を入力するか」をロケーターで指定します。
# ロケーターの例(Selenium)
driver.find_element(By.ID, "login-btn") # ID で探す
driver.find_element(By.CSS_SELECTOR, "#form input[type='email']") # CSS で探す
driver.find_element(By.XPATH, "//button[@type='submit']") # XPath で探す
ロケーターが壊れる=テストが失敗するという直接的な関係があるため、壊れにくいロケーターを選ぶことがテストの安定性を大きく左右します。
ロケーターの種類と使い分け
| 種類 | 例 | 安定性 | 推奨度 |
|---|---|---|---|
data-testid | [data-testid="login-btn"] | ⭐⭐⭐ 最高 | 🔴 最優先 |
| ID | #login-btn | ⭐⭐⭐ 高い | 🟡 推奨 |
| name 属性 | [name="username"] | ⭐⭐⭐ 高い | 🟡 推奨 |
| CSS セレクター | .login-form input | ⭐⭐ 中程度 | 🟡 許容 |
| XPath(相対) | //button[@type='submit'] | ⭐⭐ 中程度 | 🟡 許容 |
| テキスト内容 | //button[text()='ログイン'] | ⭐ 低い | ⚠️ 状況次第 |
| XPath(絶対) | /html/body/div[2]/form/button | ⭐ 最低 | ❌ 非推奨 |
CSS セレクターの書き方
CSS セレクターは HTML の構造を使って要素を特定する書き方です。フロントエンド開発者には馴染みが深く、Selenium・Playwright の両方で使えます。
基本的な書き方
# HTML の例
# <input id="email" class="form-input" type="email" name="email" />
# ID で指定(# を付ける)
"#email"
# クラスで指定(. を付ける)
".form-input"
# 属性で指定([ ] で囲む)
"[type='email']"
"[name='email']"
# タグ + クラスの組み合わせ
"input.form-input"
# 親子関係(スペースで区切る:間接の子孫)
".login-form input"
# 直接の子要素(> で区切る)
".login-form > input"
ロケーター選択フロー
| data-testid 属性がある? | |
| ✅ YES → data-testid を使う(最優先) | ❌ NO → 次の条件へ ↓ |
| 固有の ID がある? | |
| ✅ YES → #id を使う | ❌ NO → 次の条件へ ↓ |
| Playwright で role / label で特定できる? | |
| ✅ YES → get_by_role / get_by_label を使う | ❌ NO → 次の条件へ ↓ |
| name・type などの属性で特定できる? | |
| ✅ YES → CSS セレクターを使う | ❌ NO → 次の条件へ ↓ |
| 🔴 最後の手段:XPath(相対)を使う | |
迷ったら「ユーザーが認識できる要素」を優先する考え方が Playwright では推奨されています。画面を見てユーザーが「ログインボタン」と認識できるなら get_by_role("button", name="ログイン")、「メールアドレス欄」と認識できるなら get_by_label("メールアドレス") が自然な選択です。
# data-testid(最も安定)
"[data-testid='login-button']"
# フォームの入力欄
"input[type='email']"
"input[type='password']"
"input[placeholder='メールアドレス']"
# ボタン
"button[type='submit']"
"button.btn-primary"
# リンク
"a[href='/dashboard']"
# 部分一致(^=始まり、$=終わり、*=含む)
"[data-testid^='item-']" # data-testid が "item-" から始まる要素
"[class*='active']" # class に "active" を含む要素
XPath の書き方
XPath は XML・HTML ドキュメントを辿るためのクエリ言語です。CSS セレクターより表現力が高く、テキスト内容や要素の兄弟関係など CSS では難しい条件も書けます。
基本的な書き方
# //タグ[@属性='値'] ← 最も基本的な相対 XPath
"//button[@type='submit']"
"//input[@id='email']"
# テキスト内容で指定(多言語サービスでは注意)
"//button[text()='ログイン']"
"//button[contains(text(),'ログイン')]" # 部分一致
# 複数条件(and で結ぶ)
"//input[@type='text' and @name='username']"
# 親要素から指定(./)
"//form[@id='login-form']//button[@type='submit']"
# 絶対 XPath(非推奨:HTML 構造が変わると即壊れる)
# /html/body/div[1]/main/form/button[2] ← これは使わない
CSS vs XPath の使い分け
| 場面 | CSS | XPath |
|---|---|---|
| ID・クラス・属性で特定できる | ◎ 短く書ける | ○ やや冗長 |
| テキスト内容で特定したい | ✕ 難しい | ◎ text() で書ける |
| 親・兄弟・祖先要素を辿りたい | ✕ 上方向は不可 | ◎ parent:: / following-sibling:: / ancestor:: で可能 |
| 可読性・メンテナンス性 | ◎ 見やすい | △ 長くなりがち |
| 実行速度 | ◎ やや速い | △ やや遅い |
ID・属性・クラスで書けるなら CSS セレクターを優先します。テキスト内容で特定する必要があるとき、または CSS では構造上書けないときに XPath を使います。どちらも書けるなら、可読性の高い方を選びましょう。
Selenium でのロケーターの使い方
Selenium 4.x では By クラスを使って要素を指定します。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://example.com/login") # サンプルURL
# 要素を取得して操作する
email_input = driver.find_element(By.ID, "email")
email_input.send_keys("user@example.com") # テキスト入力
password_input = driver.find_element(By.CSS_SELECTOR, "input[type='password']")
password_input.send_keys("password123") # テキスト入力
submit_btn = driver.find_element(By.CSS_SELECTOR, "[data-testid='login-button']")
submit_btn.click() # クリック
# 要素が表示されるまで待機してから取得・操作(推奨)
wait = WebDriverWait(driver, 10)
error_msg = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, "[data-testid='error-message']"))
)
assert "パスワードが正しくありません" in error_msg.text
Selenium 4 以降では
driver.find_element("id", "email") のような文字列指定は非推奨です。必ず By.ID・By.CSS_SELECTOR などの定数を使ってください。Selenium vs Playwright:待機戦略の違い
| 比較項目 | Selenium | Playwright |
|---|---|---|
| デフォルトの待機 | なし(即時検索) | ◎ 自動待機(標準) |
| 明示的な待機 | WebDriverWait を自分で書く | 基本不要(操作前に自動で待つ) |
| time.sleep 依存リスク | △ 書きがち | ○ 少ない |
Playwright でのロケーターの使い方
Playwright は Selenium より新しいロケーター API を持っており、意味のある(セマンティックな)ロケーターが使えます。HTML 構造ではなく「役割」や「テキスト」で指定するため、デザイン変更に強いのが特徴です。
Selenium vs Playwright:ロケーター機能比較
| 機能 | Selenium | Playwright |
|---|---|---|
| CSS セレクター | ✅ | ✅ |
| XPath | ✅ | ✅ |
| get_by_role / get_by_text | ❌ | ✅ |
| data-testid 専用API | △ CSS で代用 | ✅ get_by_test_id |
| 自動待機 | △ 明示的に書く必要あり | ✅ 標準で組み込み済み |
| Open Shadow DOM | △ 追加手順が必要 | ✅ 透過的に扱える |
推奨されるロケーター
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com/login") # サンプルURL
# data-testid で指定(最も安定)
page.get_by_test_id("login-button").click()
# role + name で指定(ARIA ロールを活用)
page.get_by_role("button", name="ログイン").click()
page.get_by_role("textbox", name="メールアドレス").fill("user@example.com")
# テキスト内容で指定
page.get_by_text("ログイン").click() # 完全一致
page.get_by_text("ログ", exact=False).click() # 部分一致
# label テキストでフォーム要素を指定
page.get_by_label("メールアドレス").fill("user@example.com")
# プレースホルダーで指定
page.get_by_placeholder("example@email.com").fill("user@example.com")
# CSS セレクターで指定(従来通り使える)
page.locator("[data-testid='login-button']").click()
page.locator("input[type='password']").fill("password123")
browser.close()
Playwright のロケーター優先順位
| 優先度 | ロケーター | 使用場面 |
|---|---|---|
| ① 最優先 | get_by_test_id() | data-testid 属性が整備されている場合。開発チームと連携して付与するのが前提 |
| ② 推奨 | get_by_role() | ボタン・テキストボックス・チェックボックスなど |
| ③ 推奨 | get_by_label() | フォームの入力欄(label タグに関連付けられているもの) |
| ④ 許容 | get_by_text() | テキスト内容で特定できる要素(多言語サービスは要注意) |
| ⑤ 許容 | locator()(CSS/XPath) | 上記で書けないとき、または Selenium との共通化が必要なとき |
data-testid で壊れにくいテストを作る
data-testid 属性はテスト専用の識別子で、デザイン変更やリファクタリングの影響を受けません。開発チームと協力してテスト対象の要素に付与することで、メンテナンスコストを大幅に下げられます。
<!-- HTML 側に data-testid を付与する -->
<button type="submit" data-testid="login-submit-btn">
ログイン
</button>
<input type="email"
data-testid="login-email-input"
placeholder="メールアドレス" />
# Selenium での使用
driver.find_element(By.CSS_SELECTOR, "[data-testid='login-submit-btn']").click()
# Playwright での使用
page.get_by_test_id("login-submit-btn").click()
page.get_by_test_id("login-email-input").fill("user@example.com")
ページ名-コンポーネント名-アクション の形式(例:login-email-input・checkout-submit-btn)で統一すると、テストコードを読んだだけで何を操作しているか分かります。実務では、ボタン・入力欄・リンクなどユーザーが操作する主要なインタラクティブ要素を中心に付与するのが一般的です。装飾用の div や span などに全て付けても管理コストが増えるだけになります。
ロケーターではまりやすいポイント 7選
⚠️ ロケーターでよくある落とし穴
- 絶対 XPath を使ってしまう
ブラウザの DevTools で「XPath をコピー」すると絶対 XPath が生成されることがあります。/html/body/div[2]/main/form/button[1]のような形式は HTML 構造が少し変わっただけで壊れます。必ず属性を使った相対 XPath に書き直しましょう。 - 動的に変わる class 名・ID を使ってしまう
React・Vue などのフレームワークでは、ビルドのたびにclass="btn-abc123"のようなハッシュ付きのクラス名が生成されることがあります。こうした値をロケーターに使うと毎回壊れます。data-testidや固定の属性を使いましょう。 - 複数の要素がマッチしても find_element() は最初の1件を返す
find_element(単数)は複数マッチしても例外を出さずに最初の1件を返します。そのため意図せず別の要素を操作しているケースがあります。find_elements(複数)でマッチ件数を確認し、ロケーターをより具体的に絞り込む習慣をつけましょう。 - iframe の中の要素を取得できない
iframe の内部にある要素は、そのままfind_elementしても取得できません。Selenium ではdriver.switch_to.frame()、Playwright ではpage.frame_locator()で切り替えが必要です。 - 要素が表示されていないのに操作しようとしている
ページ読み込み中や非同期通信中に要素を取得するとElementNotInteractableExceptionが発生します。Selenium ではWebDriverWait、Playwright では自動待機が効きますが、隠れた要素には追加の対応が必要です。 - テキストで指定したロケーターが多言語対応で壊れる
//button[text()='ログイン']は、英語表示のとき'Login'になって失敗します。多言語サービスではテキストではなくdata-testidや role を使いましょう。 - Shadow DOM の中の要素を取得できない
Web Components や一部のライブラリでは Shadow DOM が使われています。Playwright は Open Shadow DOM を標準で透過的に扱えるため、多くの場合は通常のpage.locator()をそのまま利用できます。ただし Closed Shadow DOM は取得できません。Selenium では Shadow DOM の操作に追加の手順が必要です。
よくある質問(FAQ)
Playwright Codegen(playwright codegen https://example.com)を使うと、ブラウザを操作しながらロケーターを自動生成できます。生成されたコードの出発点として使い、必要に応じて data-testid ベースに書き換えるのがおすすめです。Chrome DevTools のコンソールでも document.querySelector('.btn') や $x('//button') でその場で試せます。
一般的に CSS セレクターの方がわずかに速いとされています。ブラウザのレンダリングエンジンが CSS に最適化されているためです。ただし実際のテスト実行時間への影響は体感できないほど小さく、パフォーマンスより可読性・安定性を優先してロケーターを選ぶ方が重要です。
CSS セレクターを先に覚えることを推奨します。可読性が高く・書きやすく・実務でも頻繁に使います。XPath は「テキストで要素を特定したい」「親要素を辿りたい」といった CSS では難しいケースに使います。両方少しずつ覚えると実用的です。
Chrome DevTools でページを開き、要素を右クリック→「検証」で HTML を確認できます。DevTools のコンソールで document.querySelector('#email') と入力すると CSS セレクターをその場で試せます。XPath は $x("//button[@type='submit']") で確認できます。
ARIA ロールに基づいており、button・textbox・checkbox・link・heading・combobox・listbox などが使えます。ほとんどの HTML 要素には対応するロールがあります。詳細は Playwright 公式ドキュメントの「getByRole」を参照してください。
「テスト自動化のために要素に識別子が必要です。data-testid 属性を主要なインタラクティブ要素(ボタン・入力欄・リンクなど)に付与してください」と依頼します。data-testid はテスト専用属性でプロダクションコードの動作に影響しない点も強調すると受け入れられやすいです。命名規則をチームで決めておくとスムーズです。
新規プロジェクトなら Playwright の方がロケーターの表現力が高く、壊れにくいテストを書きやすいです。get_by_role・get_by_test_id のようなセマンティックなロケーターが標準で使えます。ただし既存プロジェクトで Selenium を使っている場合は data-testid × CSS セレクターで十分な安定性を確保できます。
まとめ
| ポイント | 内容 |
|---|---|
| ロケーターの優先順位 | data-testid → ID → CSS → XPath(相対) |
| CSS セレクター | #id・.class・[属性]・親子関係。可読性が高い |
| XPath | テキスト・親要素など CSS で書けない条件に使う。絶対 XPath は非推奨 |
| Playwright ロケーター | get_by_test_id → get_by_role → get_by_label の順で優先 |
| data-testid | 開発者と連携してテスト専用属性を付与。デザイン変更に強い |
| 避けるべきもの | 絶対 XPath・動的クラス名・位置指定(div[2] など) |
ロケーターの選び方を改善するだけで、テストの安定性は大きく変わります。
まず手持ちのテストコードで絶対 XPath や動的クラス名を使っているロケーターを探して、data-testid や属性ベースのロケーターに書き換えるところから始めてみましょう。
ロケーターが安定すれば、次のステップとして Page Object Model(POM)を使ったテストコードの整理に取り組むとさらに保守しやすくなります。
