intermediate

Avoiding brittle selectors

Prefer user-facing roles, labels, and explicit test contracts over implementation selectors that change during harmless refactors.

Brittle selectors break on harmless refactors: CSS modules hashes, nth-child, deep DOM paths, and IDs generated at runtime. Prefer accessible names—role, label, placeholder—and treat `data-testid` as an explicit contract between dev and QA when semantics are insufficient.

In Playwright, `getByRole('button', { name: 'Save' })` survives markup changes that preserve UX.

On interviews: rewrite a brittle XPath/CSS selector into Testing Library queries and explain the maintenance difference.

Common pitfalls: test IDs on every div, selecting by visible text that changes with i18n, and coupling to third-party widget internals.

The trade-off is slightly more upfront accessibility work versus dramatically lower E2E churn.

Checklist:

  • Query priority: role, label, text, then test id.
  • Add test ids only for non-semantic targets.
  • Avoid selectors tied to layout structure.