THE SHORT VERSION
Make the locator follow the thing the test is about. When sorting or new data changes the target, identify the intended record, scope the action, and assert what happened. A longer wait cannot give a row number a stable identity.
First, prove the target changed.
A selector problem is one possible cause of an intermittent failure. Before rewriting it, compare a passing and failing run under the same intended setup.
- Keep the conditions.
Record the test, browser project, account, data, and worker settings. Keep both traces when available, including the failed attempt that a retry passed.
- Compare the actual target.
At the failing action, inspect the matched element, surrounding rows, and page state. Did sorting move the record? Did another control appear? Did an attribute change?
- Test one explanation.
Recreate that difference and change only the relevant locator assumption. Keep the same outcome assertion so the before and after results are comparable.
A selector that consistently stops matching after a deployment is a regression. Intermittent behavior needs a changing condition to investigate. If the target stays the same, look at readiness, shared state, and services.
Start with the locator-failures guide if you still need to distinguish a missing match, a strictness error, or a blocked action. Playwright’s Trace Viewer provides evidence from the automated run.
Same locator. A different invoice.
The test should open INV-1042. A locator using nth(1) instead chooses the second matching button. Change the list below, then compare that rule with a locator scoped to the invoice.
INV-1042| Row | Invoice | Action |
|---|---|---|
| 01 | INV-1041 | |
| 02 | INV-1042 | Match |
| 03 | INV-1043 |
The second position happens to be right in this state.
The intended invoice happens to be second. Both locators reach it in this state.
const invoices = page.getByRole('table', {
name: 'Invoices', exact: true,
});
// Assumes the intended invoice is always second.
await invoices
.getByRole('button', { name: 'Open', exact: true })
.nth(1)
.click();This model illustrates the two rules; it does not run Playwright. The code assumes an Invoices table and a unique INV-1042 cell. Adapt it inside a test with page ready and expect imported from @playwright/test.
The example models a known dependency on order. It is not a measured failure rate. See Playwright’s guidance on positional locators.
Replace the assumption that failed.
In the example, changing syntax alone is not enough: the brittle locator already uses getByRole. The meaningful change is identifying the invoice before choosing its Open button.
Position changed.
Use the record’s identity within the relevant table, list, or panel. Keep position when the requirement is itself positional, such as selecting the top-ranked result.
Markup changed.
Review generated classes and long parent-child paths. Prefer a user-facing role and name or a maintained attribute when that better expresses the target.
The name or test ID changed.
Confirm the UI change with its owner. Update the intended contract or fix an unintended duplicate; choosing the first match can hide the distinction the test needs.
When a test ID is the better identity
If the visible record label is not a suitable contract, agree on a stable test attribute. This example assumes the default data-testid configuration and exactly one row for the seeded invoice.
<tr data-testid="invoice-1042">
<td>INV-1042</td>
<td><button type="button">Open</button></td>
</tr>const invoice = page.getByTestId('invoice-1042');
await expect(invoice).toHaveCount(1);
await invoice
.getByRole('button', { name: 'Open', exact: true })
.click();
await expect(page.getByRole('heading', {
name: 'Invoice INV-1042', exact: true,
})).toBeVisible();Use the ID from your test fixture in a real suite. A test ID does not enforce uniqueness or create missing test data. Keep an assertion for the invoice that opens.
Use the locator comparison guide to weigh role, test ID, and CSS dependencies. The goal is an explicit, maintained target.
A passing retry is not the finish line.
Repeat the focused test with retries disabled for the investigation, so each attempt’s result remains clear. Capture traces and keep the original project and environment settings.
npx playwright test tests/invoices.spec.ts \
--retries=0 --repeat-each=5 --trace=onReplace tests/invoices.spec.ts with your test file. Add the relevant project or test filter. Five runs are a starting sample, not a reliability guarantee; preserve the concurrency that matters to the failure. Command-line options.
- Recreate the original data, account, browser project, and worker settings.
- Try the relevant ordering, extra rows, and repeated controls that exposed the problem.
- Check the intended record and the application result after the click.
- Run the focused case again, then the neighboring tests and original suite conditions.
- Keep any remaining failure and its trace. Record the number of runs and what varied.
The scoped example asserts the invoice heading after opening it. Choose an equivalent confirmation in your app: the intended record, saved value, or completed state. Retrying assertions can wait for that result to appear.
Playwright assertionsIf the same target still behaves inconsistently, follow the broader flaky-test investigation. A selector change will not repair unrelated setup or service failures.
Leave the next engineer a reason to trust the change.
Keep a short review note beside the test change. Explain what varied, why the new locator follows the right identity, and which conditions you checked.
Selector change review
Test and original conditions: [file, project, data, workers]
Expected target and result: [record, action, confirmation]
Passing / failing evidence: [trace links and relevant step]
Changed assumption: [what differed between the runs]
Replacement and reason: [locator and maintained identity]
Verification: [conditions tested, results, remaining failures]
Owner: [who maintains the UI or test contract]Add the actual results and evidence links. An investigation outline is useful only when it describes what you observed.
WHEN THE BROWSER PATH MATTERS
Keep the changing UI in the story.
Inspect an element and review its selectors with our extension. If sorting, filtering, or earlier actions matter, record that browser path so your teammate can see the context behind the change.
Free extension for desktop Chrome. Try it before signing up.Use a Playwright trace for the automated run and a Samelogic recording for the browser path you reproduce. The extension’s current Playwright snippet uses CSS with page.locator(); review and verify it in your test.
A few common questions.
How do I know a selector is causing the flake?
Compare the target and surrounding state in passing and failing runs. Evidence such as a reordered list changing which record receives the click supports a selector explanation. A timeout or a passing retry alone does not. If the intended target is unchanged, investigate readiness, test data, shared state, and services too.
Is nth() always a bad choice?
No. Position can be part of the requirement, such as opening the top-ranked result. But if the test is about a specific invoice, user, or project, its incidental position is a weak identifier. Scope to that identity and assert the intended result.
Will changing CSS to getByRole fix the test?
Only if the new locator removes the assumption that caused the failure. A role locator followed by nth() can still choose the wrong record after sorting. CSS with an intentionally maintained attribute can be useful. Review the actual dependency, scope, and result rather than changing syntax alone.
Can retries make an unstable selector reliable?
A retry can encounter a different page state and pass, but it does not correct the locator. Playwright labels tests that fail initially and pass on retry as flaky. Keep the failed attempt and use it to test an explanation; do not treat the later pass as proof of a fix.
How many passing runs prove the fix?
There is no universal number. Repeat the conditions that exposed the problem and relevant variations, then review the wider suite. Five repetitions are a useful starting sample for this guide, not a guarantee. Record what you tested and keep investigating any remaining failures.
Can Samelogic repair my Playwright tests automatically?
Use Playwright to run and verify your tests. Samelogic helps you inspect an element and record a browser path you can reproduce, so a teammate can review the context. The extension’s current Playwright snippet uses CSS with page.locator(); the scoped role examples here are educational code to adapt and verify.
Sources & further reading
The API guidance follows Playwright’s documentation. The invoice data, examples, and review note are authored for this guide.