Skip to content

The practical guide

Why Playwright locators break.

A timeout is a symptom. Find out whether the target changed, the locator matches too much, or the page is not ready. Then make a change you can verify.

Read the failure in contextEXAMPLE
CHECKOUT · PLACE ORDERpage.locator('.checkout > button')
Target no longer matches

The button is still there. Its parent changed.

  1. 01
    Observe

    Zero matches at this step

  2. 02
    Inspect

    A new wrapper around the button

  3. 03
    Verify

    The intended target and outcome

A useful failure points to the next thing to check.

THE SHORT VERSION

Inspect the failing step before rewriting the locator. Check what matched, what the action waited for, and what happened next. A changed selector will not fix missing test data or a control the app never enables.

Start with what the failure tells you.

Select the symptom closest to your run. Each path gives you checks and a code example to adapt. These are investigation steps; they do not diagnose your test automatically.

What are you seeing?
0 matches

Check that the expected UI is there.

A missing match can mean the locator changed, the page is in a different state, or the target is in another frame. Start with the screen at the failing step.

  1. Confirm the URL, account permissions, test data, and actions that open the target.
  2. Compare the actual role, accessible name, and attributes with the locator.
  3. Check the frame or container being searched. A correct selector in the wrong scope still misses.
Inside your Playwright test
const submit = page.getByRole('button', {
  name: 'Place order',
  exact: true,
});

// A snapshot of matches right now, not a wait.
console.log('Current matches:', await submit.count());
await expect(submit).toHaveCount(1);

Adapt the sample names to your app. Use inside a test with page ready and expect imported from @playwright/test.

count() reports the current matches. The toHaveCount assertion retries until its assertion timeout; neither proves why the element was missing.

Match counts and retrying assertions
Showing checks for no match.

One wrapper. A different result.

The selector below requires a button directly inside .checkout. Adding a layout wrapper preserves the visible action but breaks that relationship.

THE ORIGINAL LOCATORpage.locator('.checkout > button')
Before1 match
checkout.html
<form class="checkout">
  <button type="button">Place order</button>
</form>
After a layout change0 matches
checkout.html
<form class="checkout">
  <div class="actions">
    <button type="button">Place order</button>
  </div>
</form>

Authored example. The counts describe this markup and the original selector.

Choose a dependency that matches the test’s intent.

If the requirement is to use the Place order button, its role and accessible name can express that action without depending on the extra wrapper. This example has one such button; use a meaningful container when your app has more than one.

Inside your Playwright test
const submit = page.getByRole('button', {
  name: 'Place order',
  exact: true,
});

await expect(submit).toHaveCount(1);
await submit.click();

The name and role still need to stay meaningful. This removes the wrapper dependency; it does not make the locator immune to every UI change. Add an assertion for the result of placing the order.

See Playwright’s locator best practices.

Check what the locator depends on.

getByRole

A name or role changed.

Read the accessible name, including ARIA naming attributes. It may differ from the text you see. Confirm whether a copy, localization, or semantic change should also change the test.

getByTestId

The test identity changed.

Check the attribute configured by testIdAttribute, the value in the current markup, and any repeated components. An ID is an agreement to maintain, not a uniqueness constraint enforced by Playwright.

CSS / XPath

The markup relationship changed.

Look for generated classes, renamed attributes, inserted wrappers, and reordered siblings. A short maintained attribute selector and a long positional path make different assumptions; inspect the one your test actually uses.

Use the locator comparison guide when you are ready to choose a replacement.

Right selector, wrong frame or DOM boundary?

For an iframe, enter its context with frameLocator() before locating the control. Ordinary page locators do not search inside another frame’s document.

Playwright locators generally work through open shadow roots. XPath does not pierce them, and closed shadow roots are unsupported. Check that boundary before making the selector longer.

Frame locators · Shadow DOM behavior

Locators resolve an up-to-date element when used. A React re-render alone does not mean you are holding a stale element; inspect which matching conditions or page state changed. How locators resolve elements.

Look at the page the test actually saw.

Your browser after a fresh reload may not show the failing state. Start with an existing failed trace, or capture a focused run with the same setup.

Terminal · your test project
npx playwright test tests/checkout.spec.ts --trace on

Replace tests/checkout.spec.ts with your test file. Keep the relevant project, environment, and test data. --trace on records this diagnostic run; use the trace artifact produced by the runner. Playwright CLI options.

  1. Open the failed action.Read the error and action log. Identify what matched and which condition prevented progress.
  2. Compare the snapshots.Check the surrounding DOM, visible state, and any action snapshot showing the click target.
  3. Follow the application response.Inspect relevant requests and console output. Keep the evidence that supports or contradicts your explanation.

Explore Playwright’s Trace Viewer.

A visible button can still lack its event handler.

During hydration, the page can look ready before it responds correctly. If the click lands but nothing happens, inspect application readiness. Keeping controls disabled until they can respond addresses the behavior directly.

Playwright’s hydration guidance

Make the change. Check the behavior.

Write down the condition you found and the change intended to address it. Then verify both the element you interact with and the outcome the test is meant to protect.

  • Recreate the original setup, data, viewport, and action sequence.
  • Check that the locator identifies the intended element, including repeated controls.
  • Keep an assertion for the application result after the action.
  • Rerun the failing case and relevant neighboring cases under the original conditions.
  • Record what changed and why. A passing retry alone does not establish a fix.

If results vary without a locator change, investigate the broader causes of flaky Playwright tests. Timing, shared state, and services can require a different fix.

WHEN YOU CAN REPRODUCE IT IN THE BROWSER

Keep the element with the steps that led to it.

Inspect an element and review its selectors with our extension. When the issue depends on earlier actions, record that browser path and add context for the teammate investigating it.

Free extension for desktop Chrome. Try it before signing up.

A Samelogic recording captures the browser path you reproduce. Use a Playwright trace for evidence from the automated test run. The extension’s current Playwright snippet uses CSS with page.locator().

A few common questions.

Does a timeout mean my locator is wrong?

No. The target may be absent, delayed, disabled, covered, or in a different frame or page state. Read the failing action’s log and inspect the matching element before deciding what to change.

Why does getByRole stop matching after a UI update?

Check the actual role and accessible name, which can differ from visible text. A renamed control, localization change, or added matching control can change the result. Confirm that the UI change is intentional before updating the test.

Why does a test ID still produce multiple matches?

A test ID does not enforce uniqueness. Repeated components can reuse the same value. Locate the intended row, panel, or dialog first, then find its control. Fix unintended duplicate IDs when the test contract requires uniqueness.

Should I add a sleep, increase the timeout, or force the click?

First establish what is preventing the action. A fixed sleep can miss slower runs, and force skips some click checks without proving the app is ready. Adjust a timeout only when the expected operation needs that time; preserve an assertion for the result.

Does a React re-render make a locator stale?

A Playwright locator resolves an up-to-date element when it is used. A re-render can still change the attributes, accessible name, number of matches, or application state. Investigate that change rather than assuming the locator holds an old DOM node.

Is a Samelogic recording the same as a Playwright trace?

No. A Playwright trace comes from an automated test run. Samelogic records a browser path you deliberately reproduce so you can replay it, inspect an affected element, and add context for a teammate. Use each as evidence of the run or path it actually captured.

Sources & further reading

The API guidance follows Playwright’s documentation. Code and markup are authored examples to adapt to your test.

  1. Locators, scoping, and strictness
  2. Auto-waiting and actionability
  3. Locator API reference
  4. Trace Viewer