petrkindlmann/qa-skillsMIT3 files

Selector Drift Recovery

>-

Specification
Skill ID
petrkindlmann/qa-skills/selector-drift-recovery
Publisher
petrkindlmann
Repository
qa-skills
Installs
0
Files
3
License
MIT
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

petrkindlmann/qa-skills/selector-drift-recoveryInstalls these files
  • SKILL.md
  • references/recovery-scripts.md
  • references/score-candidates.mjs

What this skill tells the agent

<objective> A redesign shipped and 23 tests now fail with TimeoutError: locator.* exceeded — the DOM moved, not the product. This skill closes that maintenance loop: it diffs the old DOM against the new one, regenerates the broken selectors role-first against the new build, validates the whole suite, and ships the diff as one reviewable PR with a confidence score and screenshot per change. The trigger is an event (a refactor merged), not a flake. The output is a PR a human signs off on — not a silent runtime auto-heal.

This is the bulk, offline, batch counterpart to test-reliability. They share the multi-attribute locator + confidence scoring primitives but run in opposite directions: test-reliability heals one selector at runtime behind a guarded threshold; selector-drift-recovery regenerates N selectors offline against the new DOM and bundles them into a PR. If you are doing the second workflow inside test-reliability, switch here. </objective>

Quick Route

SituationGo to
One broken test, not a refactorStop — use test-reliability instead
200+ broken selectors across many filesSplit by area first (one PR per page/dir), then Phase 1
Refactor changed flows/semantics, not just structureStop — rewrite from specs with playwright-automation
Framework switch (Selenium → Playwright)Stop — use test-migration, not drift recovery
No old-DOM reference exists anywhereCapture one (Phase 1) or scope down — without it this is "rewrite tests"
Have old + new DOM, ready to mapPhase 1 → 6 below

Discovery Questions

Check .agents/qa-project-context.md first — if it exists, use it and skip anything answered there. It identifies your E2E framework, selector strategy, and known fragile areas. Then:

  1. What triggered the drift? A planned refactor (Storybook can show the new DOM before merge), a shipped redesign (new DOM is in main), a dependency upgrade, or a Tailwind/CSS migration? The trigger decides whether you run pre-emptively or react to CI failures.
  2. What is the blast radius? A single component, a page, or the whole app? Single component: scope recovery to the test files that touch it. Global: budget half a day to a day, and decide which tests should be rewritten rather than re-selected.
  3. What is your current selector strategy? If selectors are mostly data-testid and the refactor preserved testids, recovery is trivial. If they are CSS-class or XPath based, expect 30–60% to need a new strategy, not just a new locator.
  4. Is there a passing baseline? You need the old DOM somewhere: a previous CI trace artifact, a deployed staging build, a Storybook story, or git history of the components. No old-DOM reference means this degrades to "rewrite tests."
  5. Are the broken locators inline or wrapped in a Page Object? The JSON reporter's error.location points at the failing line. For inline locators that is the locator itself; for POM-wrapped locators it points at the POM helper, not the test. Know this before you trust the auto-extracted line numbers (see Failure Modes).
  6. Who reviews the resulting PR? Confidence-scored updates need a human signoff. Decide upfront whether the PR goes to the test author, the engineer who did the refactor, or the QA lead.

Core Principles

  1. Recovery is event-driven, not failure-driven. Run this when a refactor is planned or just shipped, not when one test goes flaky. One broken test → test-reliability. Ten or more from the same event → this skill.
  1. Old DOM, new DOM, mapped pair. The whole skill rests on a snapshot before the refactor and one after. Everything else is bookkeeping around that pair. Capture both as aria snapshots (await page.locator('body').ariaSnapshot()), not raw HTML — a role-tree diff is exactly the signal role-first recovery needs and ignores the cosmetic churn (class renames, wrapper divs) that a raw-HTML diff drowns in. If you cannot produce both snapshots, fix that first.
  1. Role-first replacement, every time. Even when the old test used CSS, the regenerated selector should prefer getByRole + accessible name, then getByLabel for form fields, then getByTestId when the refactor added one. The recovery PR is your chance to ratchet the average selector stability score up (0–5 rubric below, shared with test-reliability).
  1. Disambiguate by region scoping, not layout selectors. When role+name is ambiguous (two "Submit" buttons), narrow with getByRole('region', { name }).getByRole('button', …) or getByRole(...).filter({ hasText }). Do not reach for :near() / :right-of() — see the Avoid note. A score-3 candidate is one where region scoping has been applied and the locator now matches exactly one element.
  1. One PR, grouped by file, with per-change evidence. Reviewers cannot eyeball 47 selector changes spread across 30 commits. Bundle into one PR, group hunks by test file, attach a confidence score + DOM screenshot per change.
  1. The suite must pass before merge, and dead tests get deleted. A regenerated selector that doesn't run is a worse version of the original problem; the skill ends with green CI, not a generated diff. And if the refactor removed a feature, prune its tests — do not regenerate selectors for elements that no longer exist.
Avoid: Playwright layout selectors :near(), :right-of(), :left-of(), :above(), :below() as disambiguators — officially deprecated and "may be removed," because a 1px layout shift changes the match (Playwright docs, 2026). They also contradict the role-first thesis. Use region scoping / getByRole().filter() instead.

Workflow

Six phases, each gated by a check before the next.

Phase 1 — Snapshot the old DOM

You need a snapshot of every page or component the affected tests touch, in its pre-refactor state. Sources, in preference order:

  1. Last green CI trace artifact. Most teams save Playwright traces on failure (trace: 'on-first-retry'). Download the green-run artifact, open a trace with npx playwright show-trace traces/checkout.zip, select an action, and read the per-action DOM snapshot panel for each surface. (The viewer no longer has a "Copy HTML at this step" menu item; you read the snapshot panel or, for a programmatic dump, replay with page.content() / ariaSnapshot().)
  2. Storybook at a pre-refactor commit. git checkout <PRE_REFACTOR_SHA>, start Storybook, and dump each story with a tiny page.content() / ariaSnapshot() script.
  3. A staging build still on the old version. Navigate the same flows and snapshot.
  4. Git history of the components. Reconstructable but the most expensive — render in isolation.

Output: .drift-recovery/old/<page-or-component>.aria.yml (and .html if you also need raw markup) per affected unit.

Gate: You can answer "what did this page look like when the tests last passed?" from a snapshot file, not from memory.

Phase 2 — Snapshot the new DOM

Run the same surfaces in the post-refactor build — a Vercel/Netlify preview deploy is ideal, or a local dev server / the PR branch in CI. Wait for hydration (await page.waitForLoadState('networkidle')) before snapshotting, or SSR pages give you the pre-hydration tree and you miss client-rendered elements.

Output: .drift-recovery/new/<page-or-component>.aria.yml matching the old set.

Gate: Every old snapshot has a matching new one. If a route 404s in the new build, that flow was deleted — mark its tests for the deletion pile in Phase 6.

Phase 3 — Identify broken selectors and infer intent

For each test file, run against the new build with the JSON reporter, then parse it. Capture, per failure: file, line, old locator string, error type (timeout vs assertion), and inferred intent. Group the results by test file.

Use this skill in a project

Open any RiverX project, search the Skills panel for petrkindlmann/qa-skills/selector-drift-recovery, and install it in one click.

Use this skill