You've seen this before. The test passes on your machine, fails in CI, and the error is something like "locator.click: Timeout 30000ms exceeded." You re-run it. It passes. You re-run it again. It fails. Congratulations — you've met a flaky test, and the culprit is almost never the assertion. It's the locator.
The Three Failure
Modes nth-child selectors- `page.locator('div.container > div:nth-child(3) > button')
works today because the button happens to be the third child. Tomorrow a designer adds a banner above it, and the selector silently points at the wrong element — or nothing at all. The test doesn't error; it clicks the wrong thing, and your suite lies to you.
Fixed sleeps- await page.waitForTimeout(3000) is the most common anti-pattern in Playwright codebases. It assumes the page loads in three seconds. On a fast machine it wastes time; on a slow CI runner it times out. Either way, you're not waiting for the right thing — you're waiting for a clock.
CSS paths. .btn-primary-v2 breaks the moment someone renames the class during a redesign. And if the class was generated by a CSS-in-JS library, it changes on every build.
The Fix
Prefer getByRole and getByText. These locators target what the user sees and what assistive tech reads — accessible names, roles, visible text. A button's role doesn't change when its styling does.
// Fragile
await page.locator('#submit-btn-47').click();
// Resilient
await page.getByRole('button', { name: 'Submit' }).click();
Replace fixed sleeps with web-first assertions. Playwright's expect auto-retries until the condition passes or the timeout hits, so you never guess at timing:
// Fragile
await page.waitForTimeout(3000);
await expect(page.getByText('Order confirmed')).toBeVisible();
// Resilient
await expect(page.getByText('Order confirmed')).toBeVisible();
`
The assertion itself is the wait. No sleep needed.
Practice
Reading about this is one thing. Writing locators under pressure is another. I built LocatorLeague — a free playground with twenty-nine browser challenges where you write a Playwright locator, the platform runs it in a real Chromium instance, and you get pass/fail feedback plus a video of the run. No install, no setup. The Foundations track is free at https://locatorleague.dev.
Closing
What's the flakiest test in your suite? Drop it in the comments — half the time the fix is one locator swap away.
Top comments (0)