The component PR looks small. The bug is fixed. Then CI goes red in a file the agent never opened: checkout.spec.ts can’t find [data-testid="pay-cta"]. The agent renamed it to checkout-submit because the new name “was clearer.”

I’ve started treating silent test-id edits as a merge blocker, not a style nit. Selectors are a contract with tests you may not have in the same checkout.

Why agents rename them

Common reasons, all locally reasonable:

  • The id didn’t match the new label
  • A linter or the model’s taste prefers BEM, camelCase, or a longer name
  • The agent “cleaned up” inconsistent naming across a file
  • It moved a button into a child component and “updated the hook to match”

None of those are a reason to break a Playwright / Testing Library / Cypress suite that wasn’t in the PR.

A simple policy

Boring rules beat debate:

  • Existing data-testid, data-test, and stable aria-label hooks stay unless the test (or Storybook play function) that uses them is updated in the same PR
  • New elements may get new ids; don’t recycle or “improve” old ones in passing
  • If a rename is truly required (duplicate ids, the element went away), the PR must include the test diff and mention the rename in the description
  • Don’t replace a test id with a role+name query in application code “to be more semantic” unless the test migration is in the same change

The app code is not the only consumer. Visual tests, analytics event helpers, and QA snippets often key off the same attributes.

What to put in the prompt (or the PR template)

Give the agent the contract, not a vibe:

Do not rename, remove, or relocate existing data-testid / data-test attributes unless you also update every test that references them in this PR. If you need a new hook, add a new attribute. List any test-id changes in the PR body. 

Even better, paste the current ids for the files it may touch:

rg -o "data-testid=\"([^\"]+)\"" src/components/Checkout -N 

A short allow-list in the prompt beats hoping the model notices pay-cta is load-bearing.

How to catch it in review

Before you approve:

  1. Diff only the test-id attributes: git diff -U0 -- '*.tsx' '*.jsx' | rg "data-testid|data-test"
  2. If any existing value disappeared or changed, search the repo for the old string
  3. If tests weren’t updated, request changes — don’t “fix CI later”
  4. If the agent added a second id “just in case,” delete the unused one or document why both exist

A one-line CI grep that fails when a test-id value disappears from src/ but still appears under e2e/ / tests/ is cheap insurance.

Reject / send back

Reject

  • Renames with no test diff
  • “Cleanup” that normalizes all ids in a file you asked to patch in one place
  • Removing a test id because the agent switched to getByRole in a story you didn’t ask to rewrite
  • Reusing an old id on a different element

Accept

  • Unchanged hooks on existing elements
  • New ids on new elements, with tests when those elements need them
  • A rename bundled with the test (and any docs/QA) update, called out in the PR body

Tiny habit

When you ask an agent to fix a button, tell it the test ids in that file are frozen. If a hook has to change, the test change ships in the same PR. Fewer mysterious red pipelines. Fewer “it worked on my story” merges.