PRACTICAL GUIDE / playwright strict mode violation
Debug Playwright Strict Mode Violations in Repeating and Dynamic UI
Trace Playwright strict mode violations to duplicate UI states, inventory every match, refine semantic locators, and lock uniqueness with regression tests.
In this guide8 sections
- Reproduce the violation before changing the locator
- Inventory every match instead of guessing
- Classify the source of ambiguity
- Refine through a semantic container
- Model dynamic UI as an explicit state transition
- Use positional locators only for positional contracts
- Set boundaries between test and product fixes
- Add a regression that protects the boundary
What you will learn
- Reproduce the violation before changing the locator
- Inventory every match instead of guessing
- Classify the source of ambiguity
- Refine through a semantic container
A strict mode failure is useful evidence: the test has not proved which element represents the user's intended action. Do not silence it by choosing the first match. Inventory the candidates, identify why the page exposes more than one valid-looking target, and make the locator express the missing business distinction.
Repeating cards, responsive navigation, optimistic updates, and transition remnants create different kinds of ambiguity. The durable fix depends on which boundary is unclear: accessible identity, containing record, current UI state, or stable test contract. Treat the error message as the start of that investigation.
Reproduce the violation before changing the locator
Playwright applies strictness to operations that imply one target, such as click(), while multiple-element operations such as count() can legitimately return several matches. The official locator guide also warns that first(), last(), and nth() can select an unintended element when the page changes. Preserve the failing action until you understand the extra candidates.
Run the single test with the same project, viewport, account, flags, and seed data as the failure. If it is timing-sensitive, record a trace rather than adding a delay. A strictness error that disappears under a slower debugger may be caused by a brief overlap between outgoing and incoming UI states.
Animated field map
Strict Locator Root-Cause Flow
Preserve the failure, expose every candidate, name the missing distinction, refine the locator, and prove uniqueness over time.
01 / strictness error
Strictness error
Reproduce the one-target action with its original state and data.
02 / match inventory
Match inventory
Capture count, text, visibility, attributes, and containing regions.
03 / semantic gap
Semantic gap
Identify the record, role, state, or ownership detail that is missing.
04 / locator refinement
Locator refinement
Scope and filter through user-facing or explicit test contracts.
05 / uniqueness proof
Uniqueness proof
Assert the intended state and guard against duplicate regressions.
Inventory every match instead of guessing
Start with the exact failing locator and collect facts about every candidate. Text alone is insufficient because duplicate controls often have the same label by design. Include visibility, accessible state, a stable record identifier when available, and a short ancestor description. This turns "two buttons" into evidence such as "one Save button in the page form and one in an open dialog."
import { expect, test, type Locator, type TestInfo } from '@playwright/test';
async function attachMatchInventory(
locator: Locator,
testInfo: TestInfo,
): Promise<void> {
const matches = await locator.evaluateAll((elements) =>
elements.map((element, index) => ({
index,
tag: element.tagName,
text: element.textContent?.trim().replace(/\s+/g, ' '),
hidden: element.getAttribute('aria-hidden'),
disabled: element.getAttribute('aria-disabled'),
testId: element.getAttribute('data-testid'),
region: element.closest('[data-region]')?.getAttribute('data-region'),
})),
);
await testInfo.attach('locator-matches.json', {
body: Buffer.from(JSON.stringify(matches, null, 2)),
contentType: 'application/json',
});
}
test('renames the selected workspace', async ({ page }, testInfo) => {
await page.goto('/workspaces/alpha/settings');
const save = page.getByRole('button', { name: 'Save' });
await attachMatchInventory(save, testInfo);
await expect(save).toHaveCount(1);
await save.click();
});Keep this helper diagnostic, not a permanent excuse for broad selectors. If evaluation itself races with a re-render, the trace DOM snapshots around the action can reveal which generations overlapped.
Classify the source of ambiguity
Most violations follow one of four paths. A repeated-data path has one legitimate control per row or card. A layout path keeps desktop and mobile variants in the DOM. A state-transition path briefly renders old and new components together. An ownership path places identical actions in separate regions, such as a page toolbar and modal footer.
The classification determines the fix. Repeated data needs a record-level scope. Responsive duplicates may indicate acceptable DOM structure but require a visible, named region. Transition duplicates call for a state readiness contract or a product cleanup. Separate regions need the test to state which workflow surface it owns.
Also inspect whether the duplication is a real product defect. Two visible dialogs with the same title, duplicate IDs, or two primary actions that assistive technology cannot distinguish should be fixed in the application. A selector rewrite must not normalize a confusing user experience.
Feature flags and authorization can add a fifth path: two implementations may render while ownership migrates from an old component to a new one. Capture the flag set and role in the inventory. The right repair may be mutual exclusion in the product rather than a locator aware of both implementations. A test that accepts either duplicate during migration allows the invalid overlap to survive after rollout.
Refine through a semantic container
For repeating UI, locate the business object first and then the action within it. The object identity should come from content or metadata that users and the product consider stable. This is stronger than combining unrelated page-wide text conditions.
import { expect, test } from '@playwright/test';
test('archives the annual plan', async ({ page }) => {
await page.goto('/billing/plans');
const annualPlan = page
.getByRole('listitem')
.filter({
has: page.getByRole('heading', { name: 'Annual plan', exact: true }),
});
await expect(annualPlan).toHaveCount(1);
await annualPlan.getByRole('button', { name: 'Archive' }).click();
await expect(annualPlan).toContainText('Archived');
});The container assertion matters. Without it, a unique inner button can still belong to the wrong duplicate card. Prefer role and accessible name when they express the user contract. Use getByTestId() when identity is internal, but scope it to the record so a duplicated test id still fails loudly.
Model dynamic UI as an explicit state transition
A re-render is not automatically a problem because locators resolve against the current DOM for each action. The problem is an interval where two candidates satisfy the same intent. Identify the application signal that closes that interval: a dialog becomes hidden, a list reports its refreshed revision, or a pending row leaves the tree.
Assert that signal before the one-target action. Do not wait for an arbitrary timeout or poll count() until it happens to equal one; either can let a broken transition pass. The readiness condition should explain why only the current generation is actionable.
const results = page.getByTestId('search-results');
await page.getByRole('searchbox', { name: 'Products' }).fill('monitor');
await expect(results).toHaveAttribute('data-query', 'monitor');
await expect(results.getByRole('status')).toHaveText('Results updated');
const monitor = results
.getByRole('listitem')
.filter({ hasText: 'Studio Monitor' });
await expect(monitor).toHaveCount(1);
await monitor.getByRole('button', { name: 'Add to cart' }).click();If the application offers no stable completion signal, add one as part of testability design or assert the observable business result that proves completion. Repeatedly checking raw DOM shape couples the test to implementation without clarifying state.
Use positional locators only for positional contracts
nth() is legitimate when order is the behavior under test: the third stop in an itinerary, the first ranked result after sorting, or each cell in a fixed matrix. In those cases, first assert the collection and ordering, then select the position. The position is evidence, not an escape hatch.
It is unsafe when the intended target is "the active account" but the code says nth(0). A new banner, hidden template, or sort change can redirect the click while the test stays green. If reviewers cannot explain why index zero is a product requirement, replace it with identity-based scoping.
Set boundaries between test and product fixes
A test-owned fix improves how the scenario states existing semantics: adding a dialog scope, filtering a card by heading, or using an approved test id. A product-owned fix removes duplicate live controls, gives regions distinct accessible names, or exposes a reliable state marker. Some incidents need both.
Avoid CSS chains that encode current DOM depth. They may reduce the match count today while making the locator fragile under harmless layout work. Avoid { visible: true } as the first response too. It can be appropriate when visible presentation is the actual distinction, but it does not explain duplicate visible controls and may hide an accessibility issue involving off-screen content.
Write the ownership decision in the defect or review. If markup is valid and the scenario omitted its region, fix the test. If users encounter indistinguishable live actions, fix the product and keep an assertion that exposes the duplicate. If an internal identifier is the only durable distinction, agree that identifier with the component owner rather than inventing a selector from generated classes.
Add a regression that protects the boundary
The final test should fail for the original ambiguity, not merely complete the click. Assert uniqueness at the meaningful scope, perform the action, and assert the business outcome. When the defect involved a transient duplicate, add a focused component or integration test for the transition rather than slowing every end-to-end scenario.
Run the repaired scenario across the layout boundaries that can change candidate structure, especially mobile navigation, localized labels, and permission-driven actions. Do not duplicate every end-to-end test across every viewport; target the scenario that previously exposed the ambiguity. The regression should demonstrate that the component owns one live action in each supported state and that switching states does not leave an overlapping generation behind.
Before closing the incident, verify:
- The original data, viewport, and feature flags reproduce the old failure.
- The match inventory identifies every candidate and its owning region.
- The revised locator describes user intent or an explicit testing contract.
- No positional shortcut can silently redirect the action after reordering.
- The product state signal proves the previous UI generation is finished.
- The post-action assertion confirms the intended record changed.
Fix the missing distinction, rerun across the layouts and data states that created the duplicate, and keep the uniqueness assertion beside the action. A strict mode violation is resolved only when the test can explain why one target is correct and the suite will detect that contract becoming ambiguous again.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Playwright documentation
Microsoft
Canonical API, locator, fixture, browser, and test-runner behavior.
- 02Playwright best practices
Microsoft
Official guidance for resilient tests, isolation, and user-facing locators.
- 03
FAQ / QUICK ANSWERS
Questions testers ask
Why does a Playwright locator pass a count assertion but fail when clicked?
Counting is a multiple-element operation, so several matches are valid. A click implies one target and triggers strictness; refine the locator until that action has exactly one semantically intended match.
Should I fix a strict mode violation with locator.first()?
Only when first position is itself the tested product contract. Otherwise first hides ambiguity and can silently select a different element after sorting, responsive rendering, or a new duplicate appears.
Can a hidden duplicate cause a Playwright strictness error?
Yes. A locator can match visible and hidden DOM copies, including responsive menus and transition remnants. Inspect all matches and use a stable container or product contract instead of assuming hidden markup is irrelevant.
How do I debug a locator that is unique locally but ambiguous in CI?
Record a trace, attach a match inventory, and reproduce the CI data, viewport, feature flags, and timing. The extra match often belongs to a state or layout that local execution never reaches.
When is a test id the right fix for an ambiguous locator?
Use a test id when the target has no durable user-facing identity and the application can expose an explicit testing contract. Keep role, name, and scope assertions where those behaviors matter to users.
RELATED GUIDES
Continue the learning route
GUIDE 01
Playwright Locators for Virtualized Tables and Repeating Rows
Learn reliable Playwright locators for virtualized tables, recycled rows, exact cell filters, scrolling, pagination, and stable row assertions.
GUIDE 02
20 Playwright Locator and Actionability Interview Scenarios for Senior SDETs
Practice 20 senior Playwright locator and actionability scenarios covering strictness, auto-waiting, overlays, dynamic DOMs, and reliable diagnosis.
GUIDE 03
Debug Playwright Tests with UI Mode, Inspector, and Live Locators
Debug Playwright failures with UI Mode timelines, Inspector breakpoints, actionability logs, and live locator experiments in a focused workflow.
GUIDE 04
Playwright Actionability Checks: Diagnose Clicks Before Using Force
Diagnose Playwright click failures with actionability checks, trial actions, overlay inspection, trace evidence, and disciplined use of force.