Playwright Locators & Assertions

Most end-to-end test flakiness comes from two things: brittle selectors that break when markup changes, and timing assumptions that fail when the app is a little slower than usual. Playwright addresses both. Locators describe how to find elements the way users perceive them (by role, label, and text) and are re-evaluated every time they're used. Web-first assertions automatically retry until the expected state appears or a timeout passes, with no manual sleeps or waits.

Using locators and assertions the way Playwright intends is the biggest factor in a fast, stable E2E suite. It also nudges your app toward better accessibility, because role- and label-based locators only work when the UI exposes proper semantics.

TL;DR

Quick Example

Core Concepts

Recommended Locators

getByRole is the top recommendation: it matches how users and assistive technologies perceive the page, and it survives refactors of class names and DOM structure. Options like exact, checked, pressed, expanded, level (headings), and includeHidden refine matches. Text matching is case-insensitive substring by default; use exact: true or regex for precision.

CSS and XPath (page.locator('css=…')) remain available for edge cases, but selectors like .btn-primary > span:nth-child(2) couple tests to implementation details and break often.

Chaining and Filtering

Scoping locators to a container (a dialog, a table row, a card) is the cleanest way to disambiguate repeated elements.

Strictness

Actions that target one element (click, fill, textContent) throw if the locator matches multiple elements. That's intentional: it prevents clicking the wrong "Delete" button. Fix it by making the locator more specific rather than reaching for .first(), which hides ambiguity. For lists, use count(), all(), or assertions like toHaveCount.

Auto-Waiting and Actionability

Before an action, Playwright waits until the element is:

That removes most explicit waits. If an action times out, the error lists which check failed, for example "element is not stable" or "another element would receive the click".

Web-First Assertions

expect(locator) assertions retry until they pass or the timeout expires (5s by default):

Use expect.soft() to continue after a failure and report all of them, expect.poll() to retry arbitrary async checks (such as API state), and expect(...).not. for negation, which is also retried.

Best Practices

Test Like a User

Locate by what users see and interact with: roles, labels, visible text. If a locator is hard to write, the UI may lack accessible names, which is worth fixing for real users too.

Add Test IDs Deliberately

For elements without meaningful roles or text (a canvas, a dynamic count badge, repeated icons), add data-testid. Keep test IDs stable and semantic (cart-count), and treat them as part of the component contract.

Never Use Fixed Waits

page.waitForTimeout(2000) makes tests slow and flaky. Assert the condition you're waiting for (await expect(spinner).toBeHidden()), or wait for a specific response with page.waitForResponse.

Generate and Debug Locators With Tooling

npx playwright codegen records actions with recommended locators. The VS Code extension, the UI mode (--ui), and the trace viewer's "pick locator" help build and verify them.

Common Mistakes

Non-Retrying Assertions

Using first() to Silence Strictness Errors

.first() makes the test pass today, and click the wrong element tomorrow when order changes. Scope the locator to the right container instead.

Selectors Tied to Styling

page.locator('.MuiButton-root.css-1x2y3z') breaks on any CSS change or library upgrade. Use roles, labels, or test IDs.

FAQ

What's the best locator strategy in Playwright?

Prefer getByRole with an accessible name, then getByLabel, getByText, and the other user-facing locators, and use getByTestId where no stable user-facing handle exists. Avoid CSS and XPath tied to DOM structure or class names.

Why do I get a "strict mode violation" error?

Your locator matched more than one element, and you tried to perform a single-element action. Make it more specific (add a name, scope it within a container, or use filter) rather than picking an index arbitrarily.

Do I need explicit waits in Playwright?

Rarely. Actions auto-wait for elements to be actionable, and expect assertions retry until conditions are met. Explicit waits are mainly for specific network responses (waitForResponse) or events. Never use fixed sleeps.

How is Playwright different from Cypress for selectors?

Both support user-facing and test-ID selectors. Playwright's locator API emphasizes accessibility-based locators with strictness and built-in auto-waiting for every action, runs across Chromium, Firefox, and WebKit, and supports multiple tabs, origins, and contexts natively. See Cypress.

Related Topics

References