Staged repairs — browse guides

Staged repairs & the drift gardener

The loop that fixes broken selectors for you: the green gate, what it may never rewrite, and the one switch deciding whether it applies or only proposes.

Most test maintenance is not interesting. A button gained a wrapper, an id moved, a form grew a field, and four tests now fail on a selector rather than on anything about your product. The drift gardener is the loop that does that work.

Read the next sentence before turning anything on.

The gardener is an actor, not a suggestion engine. In its shipped mode it rewrites the steps of your saved tests, by itself, on a schedule, with nobody in the room. The mode where it only proposes is the other one, and which of the two a project gets is a single checkbox.

It is a Pro plan feature (auto-repair), it needs the workspace's own AI key, and it never runs on a project that has not opted in.

The two modes, and the one switch

Each project chooses exactly one. The switch is Project settings → General → Autonomous repair, "Repair failed runs automatically".

Auto-repairWhat triggers itWhat it does
OnA failed unattended run — a scheduled suite, or a run finished by the workerA proven fix is written to the test immediately. Nothing is staged for review.
OffA radar sweep finding an app change that touches a selector a test usesThe fix is proven the same way and staged in a queue. Nothing is written until you approve it.

Note what triggers each. The applying gardener is failure-driven: nothing happens until a run goes red. The proposing one is change-driven: it works from what the radar noticed, which is why it needs the radar switched on to do anything at all.

This is one gardener per project, enforced where the work is claimed rather than where it is requested: the staging pass skips a project that has auto-repair on, and re-checks the flag per test. A test is never handled by both.

The consequence is stated on the queue itself: on an auto-repair project, an empty "Staged repairs" list means handled elsewhere, never nothing is broken.

The ladder, and the green gate

Both modes climb the same three rungs, stopping at the first that works:

  1. Self-heal — snapshot the live DOM, ask the model for a corrected selector for the step that failed. On the same re-run, an intent block whose recorded steps broke is re-derived from its goal.
  2. Suggest-fix — ask for a set of step operations (insert / replace / delete) against the whole test.
  3. Diagnose-drift — diagnose the failure first, and only then repair; this rung is also where a failure is classified as an app bug or an environment problem rather than test drift, in which case nothing is repaired at all.

Every candidate is applied to an in-memory copy of the test and re-run. The entire safety story is that re-run: a fix is never persisted unless the test goes green with it applied. Nothing is written on a model's confidence.

If the ladder exhausts without a green run, no fix is written and an AI triage issue is filed instead.

When the self-heal re-run goes green because an intent block was re-derived, the block's new steps are part of the fix. They replace the recorded steps that no longer replay, between the block's markers, and the goal and values stay as they were. The strategy reads intent. The fix is written only if the run confirmed it: the block did what it did on its last green run, or an outcome assertion after the block passed. Otherwise it is staged in the queue, even on an auto-repair project. Revert restores the old block.

What the gardener may not do

The green gate has a floor beneath it, and it exists because the gate alone can be gamed. Rewriting expect /dashboard to expect /login re-runs green precisely because it no longer asserts anything — the gate would certify the very regression that broke the test.

So any operation that deletes or rewrites an outcome assertion is refused before it reaches the gate, on both rungs, at any confidence. The rest of the proposal continues without it, and the refusals are shown with their reasons:

Refused: rewriting the expectation at step 4 from "url contains /dashboard" to "url contains /login". Outcome assertions are only changed by a human — an auto-rewrite to observed reality cannot fail.

Repair rewrites the locator, never the expectation. If every proposed change was refused, the modal says so rather than reporting "no fix found".

The same screen covers every step that checks something, not only url, text, title and value assertions:

StepA repair mayA repair may not
assert url / text / title / value / variablemove its selectordelete it, or change its condition, expected value (or a variable assertion's actual)
assert visible / hidden, assertToast, assertModal, assertValidationErrormove its selectordelete it, flip visible ↔ hidden, or change the text it expects
assert-console, assert-request—delete it, or change what it matches (level, method, status, URL pattern, expected match)
a11yAudit with a failure thresholdmove its scopedelete it, or loosen the threshold or rules
wait for a URL—delete it or change the URL

Waits for an element or a time, screenshots, extracts and report-only accessibility audits are not checks, so a repair may change or remove them. The screen applies to the gardener, the Auto Fix modal, drift diagnosis and chat edits alike: a chat request such as "remove the console-error check" is answered with the change withheld and a pointer to the step editor, where a person can make it.

The queue (auto-repair off)

Proposals live under Staged repairs on the project's App radar page, because that is where their cause is. Each row carries:

  • the proposed step changes, as an insert/replace/delete diff you can expand;
  • what triggered it — the app changes that implicated this test, with their URLs;
  • the failing step and the error from the fresh run that proved the test broken;
  • how long it has been staged, and how many later sweeps restated it.

Two actions:

  • Approve and apply — writes the change to the test and records who approved it. Revertable afterwards from the test's repair history.
  • Dismiss — declines the fix. It is not proposed again unless the test or the app changes.

At most one open proposal per test. A test has one step list, so two open proposals could never both be applicable — approving either instantly staleifies the other. A further app change that implicates the same test is recorded on the open row instead of stacking a second one.

A proposal also goes stale on its own. Its operations index into the steps the diff was computed from; edit the test in the builder and approving is refused rather than splicing a diff into a list that no longer exists. Re-run the sweep to get a fresh one.

Statuses a row can end in: proposed, applied, dismissed, stale, superseded.

Two more things the staging pass does before it spends anything. It dry-runs the test first — no AI — and if the test still passes, nothing is proposed and no model call is made. And it only considers a test whose selectors the change touched; a test that merely visits the changed page is left alone, because repairing something nothing broke is the fastest way to spend a key on nothing.

Why the queue may be empty

The queue says which of these it is, and they are not interchangeable:

  • This project repairs itself — auto-repair is on. Fixes are applied without passing through here.
  • Staged repairs need an AI key — the radar and its at-risk list work without AI and are unaffected; writing a repair is the only part that needs a key.
  • Nothing has been swept yet — no baseline, so nobody has looked.
  • No repairs waiting — every at-risk test still passes. This is the good one.

A staging pass dry-runs at most 10 tests. When more are at risk, the rest are named in the pass summary and picked up by the next sweep, never dropped. That summary is written to the server log; it has no screen and no email. The staged count is also not in the daily digest today — the queue on the radar page is where you find out.

The dry-runs and the proving re-runs happen server-side, even on a project whose tests are configured to run on your local agent.

What an auto-applied repair leaves behind

When auto-repair is on and a fix lands, the test's steps change under you. What makes that reviewable rather than silent:

  • The test records its last repair: which run triggered it, which strategy won, the exact operations, the timestamp, and — the important one — the complete pre-repair step list.
  • The test is badged Auto-repaired in the builder and on the run, opening a review modal with the diff. It also appears in the test's AI history alongside the generations and interactions.
  • Revert restores the pre-repair steps exactly. The confirmation is honest about the consequence: the selector the repair fixed will fail again until it is repaired another way. Mark reviewed clears the flag and keeps the fix.
  • The outcome fans out to your notifications as an auto-repair event, so a repaired test is not a run you never hear about.

Three limits on that record, all worth knowing before you switch this on:

  • It is one slot per test, not a history. A second repair overwrites the first, and with it the only snapshot you could have reverted to.
  • It carries no author, because an auto-applied repair has none. A repair you approved from the staged queue does record who approved it.
  • There is no page listing what the gardener changed — no "repaired this week" filter on the Tests page, no report. You find an auto repair by opening the test or the run it came from.

It does not go back through review

Say this out loud, because the two features look adjacent and are not. Human review of AI-written tests holds a newly generated test out of service until somebody approves it. A repair edits a test that is already in service, and applying one does not move it back to pending. The next schedule, suite and CI gate runs the rewritten steps.

What both gardeners refuse is a test that has not reached service yet — see the third bullet below.

If you want a person between every machine edit and your suite, the setting for that is auto-repair off plus the staged queue. The review gate answers a different question.

Three things the gardener will not touch

  • A negative control run. A sensitivity check runs a test against a deliberately falsified expectation; it is required to fail. Handing that to a repair loop would spend a model call teaching a sentinel to pass, and a "repair" that succeeds there has corrupted the test. Control runs are skipped outright.
  • A scenario probe. Only saved tests are tended. A throwaway scenario run has nothing to keep green.
  • A test awaiting review. Both gardeners ask the same run-selection gate, so a test that is pending, rejected, draft or archived is never dry-run, staged or repaired. This matters most on the applying side, because a pending test with a failed run is the ordinary case — the generator's own first run produces one, and so does a reviewer re-running the test before deciding. Repairing it would rewrite machine-written steps nobody had approved, and leave the review queue showing a recording of a version that no longer exists. The run is left alone; approve the test and its next failure is tended like any other. A reviewer who wants the fix before approving can still ask for one by hand, which is the next section.

Doing it by hand

Nothing above removes the manual path, and it works whatever the project setting says. On a failed run you can ask for a Suggested fix, Diagnose the failure, or run the whole ladder once interactively — same rungs, same green gate, same refusals, with you watching it stream. Those are Pro-gated on the same auto-repair feature, and they spend your AI key like everything else here.

A control run is refused by every one of them, for the reason above.

From an MCP client, suggest_fix proposes a repair for a failed run and apply_repair applies one. Approving or dismissing a staged proposal is a dashboard and API action; there is no MCP tool for the queue.

See also