BEST PRACTICES

Choosing test selectors that don't break

Updated June 2026 · 7 min read

Most "flaky" E2E failures aren't timing flakiness at all — they're selectors that quietly stopped matching after a UI change. The fix is to pick hooks the app is contractually committed to keeping stable.

Why CSS and XPath selectors rot

A selector like .btn-primary.mt-4 > span:nth-child(2) encodes styling and layout — the two things designers and developers change most often. Restyle the button, wrap it in a flex container, swap a utility class, and the selector breaks even though the feature works perfectly. XPath that walks the DOM tree is worse: any structural edit upstream invalidates it.

These selectors fail for reasons that have nothing to do with the behavior under test. That's the definition of a brittle test.

The hierarchy of selector stability

  1. Dedicated test idsdata-testid, data-cy, data-test. They exist only for tests, so nobody changes them for styling reasons. Most stable.
  2. Roles & accessible namesgetByRole('button', { name: 'Submit' }). Stable and they assert accessibility. Playwright and Testing Library both recommend these.
  3. Visible text — durable until copy changes; good for content, riskier for frequently-reworded UI.
  4. Ids and names — okay if they're semantic and intentional, fragile if auto-generated.
  5. CSS classes / structural XPath — avoid for selection. They're styling, not contract.

Make test ids a contract

A data-testid only helps if everyone treats it as load-bearing. Two practices make that real:

The gap stable selectors don't close

Even perfect data-testid hygiene doesn't help when someone renames or removes one. The selector is stable right up until the PR that changes it — and that PR's reviewer usually has no idea a spec depends on it. This is exactly the blind spot test impact analysis at PR time exists to cover.

Testward recognizes the full family of stable hooks — data-testid, data-cy, getByRole/getByTestId, aria-labels, ids — and flags the moment a PR changes one a spec depends on, including across repos. Good selectors make your suite durable; Testward tells you when someone's about to break the contract anyway.

Catch the test-id rename in review.

Install Testward and get flagged the moment a PR touches a selector your specs depend on.

Install free on GitHub