Playwright で locator が要素を特定できない(strict mode violation)
strict mode violation は locator が 2 つ以上の要素に一致したときのエラー。
getByRole で属性まで指定する、filter() で絞り込む、要素に test id を付与するのが基本の解決策。
first() での回避は最終手段。
公開: 更新:
要約
Error: strict mode violation: locator('button') resolved to 3 elements は、locator が複数の要素に一致した ことを示すエラー。
Playwright の locator は strict mode がデフォルトで、操作対象は 1 要素に特定されている必要がある。
セレクタを曖昧なまま first() で逃げるのではなく、一意に特定できる locator に書き換える のが正攻法。
実行例
「保存」「キャンセル」「注文する」と行ごとの「編集」が並ぶページを用意して、曖昧な locator と一意化した locator を続けて走らせてみる。locator("button") は 5 要素、getByRole("button", { name: "編集" }) は行ごとの 2 要素に一致して落ちるが、Playwright はそのとき一致した要素を aka getByRole('row', ...) のように一意化の候補付きで並べてくれる。
$ npx playwright test bad.spec.js --reporter=line
Running 2 tests using 1 worker
[1/2] bad.spec.js:4:1 › button で保存を押す
1) bad.spec.js:4:1 › button で保存を押す ───────────────────────────────────────────────────────────────
Error: locator.click: Error: strict mode violation: locator('button') resolved to 5 elements:
1) <button onclick="say('保存しました')">保存</button> aka getByRole('button', { name: '保存' })
2) <button onclick="say('キャンセルしました')">キャンセル</button> aka getByRole('button', { name: 'キャンセル' })
3) <button onclick="say('注文しました')" data-testid="submit-order">注文する</button> aka getByTestId('submit-order')
4) <button onclick="say('Osaka を編集')">編集</button> aka getByRole('row', { name: 'Osaka 編集' }).getByRole('button')
5) <button onclick="say('Tokyo を編集')">編集</button> aka getByRole('row', { name: 'Tokyo 編集' }).getByRole('button')
Call log:
- waiting for locator('button')
4 | test("button で保存を押す", async ({ page }) => {
5 | await page.setContent(html);
> 6 | await page.locator("button").click();
| ^
7 | });
8 |
9 | test("編集ボタンを押す", async ({ page }) => {
at /tmp/tmp.9ydIK8Xyaq/bad.spec.js:6:32
Error Context: test-results/bad-button-で保存を押す/error-context.md
[2/2] bad.spec.js:9:1 › 編集ボタンを押す
2) bad.spec.js:9:1 › 編集ボタンを押す ────────────────────────────────────────────────────────────────────
Error: locator.click: Error: strict mode violation: getByRole('button', { name: '編集' }) resolved to 2 elements:
1) <button onclick="say('Osaka を編集')">編集</button> aka getByRole('row', { name: 'Osaka 編集' }).getByRole('button')
2) <button onclick="say('Tokyo を編集')">編集</button> aka getByRole('row', { name: 'Tokyo 編集' }).getByRole('button')
Call log:
- waiting for getByRole('button', { name: '編集' })
9 | test("編集ボタンを押す", async ({ page }) => {
10 | await page.setContent(html);
> 11 | await page.getByRole("button", { name: "編集" }).click();
| ^
12 | });
13 |
at /tmp/tmp.9ydIK8Xyaq/bad.spec.js:11:50
Error Context: test-results/bad-編集ボタンを押す/error-context.md
2 failed
bad.spec.js:4:1 › button で保存を押す ────────────────────────────────────────────────────────────────
bad.spec.js:9:1 › 編集ボタンを押す ─────────────────────────────────────────────────────────────────────
終了コード: 1$ npx playwright test fixed.spec.js --reporter=list
Running 3 tests using 1 worker
✓ 1 fixed.spec.js:4:1 › ロールと名前で特定する (316ms)
✓ 2 fixed.spec.js:10:1 › filter で行を絞り込む (195ms)
✓ 3 fixed.spec.js:20:1 › test id で特定する (258ms)
3 passed (1.9s)
終了コード: 0— 2026-09-16 時点の出力
検証環境
- 検証日
- 実行環境
node:20Debian GNU/Linux 12 (bookworm)- バージョン
- Node.js 20.20.2
- npm 10.8.2
- Python 3.11.2
- Git 2.39.5
この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。
よくある原因
- セレクタが汎用的:
buttonや.itemのようなセレクタがページ全体で複数一致する - 繰り返し要素: 一覧やテーブルで同じテキストのボタン(「編集」「削除」など)が行ごとに描画される
- 重複 DOM: レスポンシブ対応でモバイル用・デスクトップ用に同じ要素が 2 セット存在する
- コンポーネント再利用: 同一構造のカードやフォームが複数描画される
解決策
1. ロールと名前で一意化する
// NG: strict mode violation になりやすい
await page.locator("button").click();
// OK: ロール + アクセシブルネームで特定
await page.getByRole("button", { name: "保存" }).click();推奨される locator の選び方は 公式ドキュメントの Locators(新しいタブで開く) にまとまっている。
2. filter で絞り込む
// 「Tokyo」を含む行の中の「編集」ボタン
await page
.getByRole("row")
.filter({ hasText: "Tokyo" })
.getByRole("button", { name: "編集" })
.click();繰り返し要素は「どの行・どのカードの中か」という文脈ごと指定すると、データの増減に強いテストになる。
3. test id を付与する
<button data-testid="submit-order">注文する</button>await page.getByTestId("submit-order").click();アクセシブルな属性で特定しづらい要素には data-testid を付与する。
4. nth / first は意図がある場合のみ
await page.getByRole("listitem").first().click();「先頭の項目を選ぶ」ことが仕様として正しい場合のみ使う。
一意化を諦めるための first() は、DOM の並び替えで別要素を操作する flaky テストにつながる。