Skip to main content

E2E Tests Break When UI Selectors Change

Short answer

A CSS class rename should not break checkout. Prefer data-testid, roles, and labels agreed with frontend; use ai.act / ai.verify sparingly only where copy or layout genuinely varies (i18n, AI chat)—never on stable forms that should have test ids.

Part of Common E2E testing gotchas.

Symptom

CI fails with errors like:

Error: locator.click: Timeout 30000ms exceeded.
waiting for locator('.btn-primary.checkout-v2')

The UI works manually. A designer or agent refactor renamed classes or reordered components—your spec still targets the old tree.

Root cause

Brittle selectors bind tests to implementation detail:

  • CSS modules (Button_button__x7f2a)
  • Deep XPath (div/div/span[2])
  • Text that changes with marketing copy or locale

Record-replay tools encode the same brittle paths—every regen becomes maintenance (record-replay vs TestChimp).

PriorityLocatorWhen
1getByRole('button', { name: 'Pay now' })Accessible name is stable
2getByTestId('checkout-submit')Eng adds data-testid on critical actions
3getByLabel('Email')Forms
Last resortai.act('Complete checkout')Volatile marketing hero or AI-generated layout only
// Prefer test id on actions that must not break silently
await page.getByTestId('checkout-submit').click();

// Role + name when label is product-stable
await page.getByRole('button', { name: /pay now/i }).click();

Team contract: PRs that remove or rename data-testid on checkout/auth paths require updating linked SmartTests—// @Scenario: links make that visible in review (requirement traceability).

When ai.act is appropriate

Use semantic steps only for genuine variance:

  • Rotating hero headlines
  • AI chat bubbles and chip labels
  • i18n copy you refuse to freeze in CI

Do not use ai.act for payment buttons, login, or admin tables that should have test ids—see when to use ai.act.

Anti-patterns

Anti-patternWhy it failsBetter approach
CSS class from design systemRenamed every sprintdata-testid on contract elements
text=Submit on i18n appLocale changesgetByRole + probe Assert
ai.act on every clickCost, flake, no precisionStable ids + probes for outcomes
Ignoring failureCoverage rots/testchimp test on PR repairs with scenario context

TestChimp workflow

On the next PR that breaks locators, /testchimp test reads markdown scenarios and repairs SmartTests in Git—faster than re-recording. Stable selectors still win: less agent churn, clearer reviews.

Frequently asked questions

Should we use data-testid on every element?

No—prioritize critical paths: checkout, auth, admin mutations. Use roles/labels elsewhere. Over-tagging creates noise; under-tagging creates refactor pain.

Our eng team refuses test ids—what now?

Use getByRole and getByLabel where accessible names are stable. For volatile regions, ai.act with probe Assert on backend outcomes—not toast text alone.

Can /testchimp test fix broken selectors automatically?

Yes on PRs with scenario context—agents update SmartTests linked via // @Scenario: rather than one-off chat scripts. Stable ids reduce how often you need repair.

Is XPath ever OK?

Rarely—for legacy DOM with no hooks. Prefer adding test ids in the same PR as the UI change.

Do record-replay tools solve selector drift?

They re-capture brittle paths—they do not establish a contract. Refactors still break recordings unless you re-record every time.

How do we prevent regressions when removing test ids?

Link SmartTests to markdown scenarios; code review treats test id removal like API breakage. Requirement traceability shows which specs cover the element.

Apply these patterns in your repo

Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.

Start free on TestChimp · Book a demo