How to fix Playwright "strict mode violation" for locators?
PlaywrightA Playwright strict mode violation means the locator you used resolved to more than one element, but the operation requires exactly one. The error message reads strict mode violation: locator('...') resolved to N elements and is thrown immediately — no timeout involved. It most commonly happens with generic CSS selectors or text locators that accidentally match navigation links, footers, and headings that share the same label.
Common mistake
test('deletes project', async ({ page }) => {
await page.goto('https://app.example.com/projects');
// Page has multiple "Delete" buttons — one per row
await page.getByRole('button', { name: 'Delete' }).click();
});
Using page.getByText('Save') or page.locator('.btn-danger') at the page root is another common trigger when the same text or class appears multiple times.
The fix
Option 1 — Scope to a container (preferred when structure allows it):
import { test, expect } from '@playwright/test';
test('deletes Acme project', async ({ page }) => {
await page.goto('https://app.example.com/projects');
// Scope to the specific row first
const projectRow = page.getByRole('row', { name: /Acme Corp/ });
await projectRow.getByRole('button', { name: 'Delete' }).click();
// Confirm deletion dialog
await page.getByRole('dialog').getByRole('button', { name: 'Confirm' }).click();
await expect(page.getByRole('row', { name: /Acme Corp/ })).not.toBeVisible();
});
Option 2 — Use a more specific locator when semantic scoping is not available:
// Use a data-testid tied to the specific action
await page.getByTestId('project-delete-42').click();
Option 3 — Explicitly select by index only when order is stable and intentional:
// .first() or .nth() signals intentionality but is fragile if list order changes
await page.locator('.project-row').first().getByRole('button', { name: 'Delete' }).click();
Why it works
Playwright's locator API enforces a "one element, one action" contract to prevent tests from silently acting on the wrong element. This is the strict mode violation check. When you scope a locator to a parent container, the resolution happens within that subtree rather than the full document, which eliminates unintended matches. Using accessible roles with name constraints leverages the accessibility tree rather than DOM structure, making locators more stable across HTML changes.
Tips
- Run await locator.all() in a debugging session to see every element a locator matches before tightening it.
- If the same strict mode violation appears across many tests, the issue is usually a shared component (modal buttons, nav links) — add a data-testid at the component level to give tests a stable anchor.
- Prefer scoped role locators over .nth() — index-based selection will break if the list is reordered or paginated differently.
- This error also has a companion variant in tests that extend Playwright's base fixtures — see strict mode violation locator errors for fixture-level disambiguation.