DEV Community

Babatunde Hammed
Babatunde Hammed

Posted on

Why Your Playwright Selectors Keep Breaking (And How to Fix Them)

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)