Why does my locator match more than one element, and how do I fix it?
Written by Jatin Thacker, Automation Architect · Updated
This answer is one piece of Chapter 9: Locators I: What the User Sees.
- getByRole and the Accessible Name
- getByText, getByLabel, getByPlaceholder, getByTestId
- Strict Mode — One Locator, One Element
The strict mode error lists the elements your locator matched, and next to each one Playwright suggests a locator that would match it alone, such as aka getByRole('button', { name: 'Save draft' }). Read that list first: it shows how the elements differ.
The traps page has three buttons that share data-testid="save": Save draft, Save and close and Save as template. toHaveCount(3) proves it. The test then shows three fixes, each checked by the status line the page updates:
- Role and name.
getByRole('button', { name: 'Save and close' })asks for what the user sees. It is usually the best fix, because the name is what tells the buttons apart for a user too. - Filter.
save.filter({ hasText: 'Save as template' })keeps the test id and adds the distinguishing text. - Scope.
getByRole('region', { name: '9. Duplicated test id' })finds the section by its heading, and the button is then found inside it. This is the fix when the same control repeats in several cards or rows.
If the elements are identical in every way that matters, as in a list where only order distinguishes them, that is when first() or nth() belongs. Positional locators silently pick the wrong item when the order changes.
import { test, expect } from '@playwright/test';
test('three ways to narrow a test id that three buttons share', async ({ page }) => {
await page.goto('/traps.html?mock=static&seed=42');
const save = page.getByTestId('save');
await expect(save).toHaveCount(3);
// 1. Ask for what the user sees: role plus accessible name
await page.getByRole('button', { name: 'Save and close' }).click();
await expect(page.getByTestId('t9-result')).toHaveText('Save and close used.');
// 2. Keep the test id, filter by the text that tells them apart
await save.filter({ hasText: 'Save as template' }).click();
await expect(page.getByTestId('t9-result')).toHaveText('Save as template used.');
// 3. Scope to the section, then name the button
const section = page.getByRole('region', { name: '9. Duplicated test id' });
await section.getByRole('button', { name: 'Save draft' }).click();
await expect(page.getByTestId('t9-result')).toHaveText('Save draft used.');
});1 passed (1.5s)
page.getByTestId('save') matches three buttons. Which fix does Playwright's guide warn can click an element you did not intend when the page changes?Practised in the full course: the drill Three elements, one test id.
See the chapterRelated questions
AI can write the test now. The job is knowing whether it checks anything. That's what the course teaches, one chapter at a time.
163 lessons · 226 drills · start free