セレクタの種類と使い方
セレクタの種類と使い方
セレクタは、HTML要素を特定するための記法です。E2Eテストでは、操作したい要素を正確に見つけるために使います。
セレクタとは?
セレクタは「どの要素を選択するか」を指定する文字列です。
// 'button' がセレクタ
await page.locator('button').click();
// '#submit' がセレクタ
await page.locator('#submit').click();
CSSセレクタ
CSSでスタイルを適用する際に使うセレクタです。最も一般的に使われます。
基本的なCSSセレクタ
| セレクタ | 説明 | 例 | HTML |
|---|---|---|---|
| タグ名 | タグで選択 | button |
<button> |
#id |
IDで選択 | #submit |
<button id="submit"> |
.class |
クラスで選択 | .btn |
<button class="btn"> |
* |
すべての要素 | * |
すべて |
// タグ名
await page.locator('button');
// ID
await page.locator('#submit-button');
// クラス
await page.locator('.primary-btn');
属性セレクタ
// 属性の存在
await page.locator('[disabled]'); // disabled属性を持つ要素
// 属性の値(完全一致)
await page.locator('[type="submit"]'); // type="submit"
// 属性の値(部分一致)
await page.locator('[class*="btn"]'); // classに"btn"を含む
await page.locator('[href^="/api"]'); // hrefが"/api"で始まる
await page.locator('[src$=".jpg"]'); // srcが".jpg"で終わる
組み合わせセレクタ
// 要素 + ID
await page.locator('button#submit'); // <button id="submit">
// 要素 + クラス
await page.locator('input.form-control'); // <input class="form-control">
// 複数クラス
await page.locator('.btn.btn-primary'); // <button class="btn btn-primary">
// 要素 + 属性
await page.locator('input[type="email"]'); // <input type="email">
階層セレクタ
// 子孫(スペース)
await page.locator('form input'); // form内のすべてのinput
// 直接の子(>)
await page.locator('ul > li'); // ulの直接の子のli
// 隣接する兄弟(+)
await page.locator('label + input'); // labelの直後のinput
// 一般的な兄弟(~)
await page.locator('h1 ~ p'); // h1の後のすべてのp
疑似クラス
// 最初の要素
await page.locator('li:first-child');
// 最後の要素
await page.locator('li:last-child');
// n番目の要素
await page.locator('li:nth-child(2)'); // 2番目のli
// テキストを含む
await page.locator('button:has-text("送信")');
// 無効化されている
await page.locator('input:disabled');
💡 セレクタの優先順位
id- 最も確実(一意なので)data-testid- テスト専用の属性class- 変更されやすい- タグ名 - 同じタグが多数ある場合は不確実
XPath
XMLやHTMLの要素を指定する記法です。複雑な条件に対応できます。
基本的なXPath
// タグ名
await page.locator('xpath=//button');
// ID
await page.locator('xpath=//*[@id="submit"]');
// クラス
await page.locator('xpath=//*[@class="btn"]');
// 属性
await page.locator('xpath=//input[@type="email"]');
// テキストで検索
await page.locator('xpath=//button[text()="送信"]');
// テキストを含む
await page.locator('xpath=//button[contains(text(), "送信")]');
階層のXPath
// 親から子へ
await page.locator('xpath=//form//input'); // formの下のすべてのinput
await page.locator('xpath=//form/div/input'); // form > div > input
// 親要素を取得
await page.locator('xpath=//input[@id="email"]/parent::div');
// 先祖要素を取得
await page.locator('xpath=//input[@id="email"]/ancestor::form');
// 兄弟要素
await page.locator('xpath=//label[@for="email"]/following-sibling::input');
XPathの例
<form id="login">
<div class="form-group">
<label>メール</label>
<input type="email" name="email">
</div>
<button type="submit">ログイン</button>
</form>
// formの中の最初のinput
await page.locator('xpath=//form[@id="login"]//input[1]');
// "ログイン"というテキストを持つbutton
await page.locator('xpath=//button[text()="ログイン"]');
// type="email"のinputの親div
await page.locator('xpath=//input[@type="email"]/parent::div');
⚠️ XPathの注意点
XPathはHTML構造が変わると壊れやすいです。可能な限りCSSセレクタを使いましょう。
XPathはHTML構造が変わると壊れやすいです。可能な限りCSSセレクタを使いましょう。
Playwrightの推奨セレクタ
Playwrightは、よりセマンティックなセレクタを推奨しています。
getByRole()
アクセシビリティのロールで要素を取得します(最も推奨)。
// ボタン
await page.getByRole('button', { name: '送信' });
// リンク
await page.getByRole('link', { name: 'ホーム' });
// テキストボックス
await page.getByRole('textbox', { name: 'メールアドレス' });
// チェックボックス
await page.getByRole('checkbox', { name: '利用規約に同意' });
主なロール:
| ロール | 対応する要素 |
|---|---|
button |
<button>, <input type="button"> |
link |
<a> |
textbox |
<input type="text">, <textarea> |
checkbox |
<input type="checkbox"> |
radio |
<input type="radio"> |
heading |
<h1> ~ <h6> |
getByText()
テキスト内容で要素を取得します。
// 完全一致
await page.getByText('ログイン');
// 部分一致
await page.getByText('ログイン', { exact: false });
// 正規表現
await page.getByText(/ログイン|サインイン/);
getByLabel()
ラベルテキストで入力欄を取得します。
// <label>メールアドレス</label> <input>
await page.getByLabel('メールアドレス');
// <label for="email">メール</label> <input id="email">
await page.getByLabel('メール');
getByPlaceholder()
プレースホルダーで入力欄を取得します。
// <input placeholder="名前を入力">
await page.getByPlaceholder('名前を入力');
getByTestId()
テスト専用のdata-testid属性で取得します。
// <button data-testid="submit-btn">送信</button>
await page.getByTestId('submit-btn');
セレクタの選び方
// 良い例(推奨順)
await page.getByRole('button', { name: '送信' }); // 1. セマンティック
await page.getByTestId('submit-btn'); // 2. テスト専用ID
await page.locator('#submit-button'); // 3. ID
await page.locator('button[type="submit"]'); // 4. 属性
// 悪い例(避けるべき)
await page.locator('div > div > button'); // 構造依存
await page.locator('button:nth-child(3)'); // 順序依存
await page.locator('.css-xyz-123'); // 自動生成クラス
🎯 セレクタ選択のベストプラクティス
getByRole(): 最優先(アクセシビリティも向上)getByTestId(): テスト専用IDを追加id属性: 一意で変更されにくい- CSSセレクタ: シンプルで読みやすい
- XPath: 最後の手段(複雑な階層が必要な場合のみ)
実践例
ログインフォームのテスト:
test('ログインフォーム', async ({ page }) => {
await page.goto('/login');
// 推奨: getByLabel()
await page.getByLabel('メールアドレス').fill('user@example.com');
await page.getByLabel('パスワード').fill('password123');
// 推奨: getByRole()
await page.getByRole('button', { name: 'ログイン' }).click();
// 代替: ID
await page.locator('#email').fill('user@example.com');
await page.locator('#password').fill('password123');
await page.locator('#login-btn').click();
// 代替: data-testid
await page.getByTestId('email-input').fill('user@example.com');
await page.getByTestId('password-input').fill('password123');
await page.getByTestId('login-button').click();
});
まとめ
| セレクタ種類 | 記法 | 推奨度 |
|---|---|---|
| getByRole() | getByRole('button') |
★★★★★ |
| getByTestId() | getByTestId('id') |
★★★★☆ |
| ID | #id |
★★★★☆ |
| 属性 | [type="submit"] |
★★★☆☆ |
| クラス | .class |
★★☆☆☆ |
| XPath | xpath=//button |
★☆☆☆☆ |
次のチャプターでは、ブラウザイベントについて学びます。