Running

Healing and re-recording

When the UI changes: healing

A cached step fails mid-run → bounded retries → the agent attaches to the same browser at the exact point of failure, diagnoses (drift vs defect), and either finishes the scenario (yellow + proposal) or fails it with an explanation (red). A heal is paid for once: until you accept it, the next run replays it at zero AI before calling the agent (§5). Then:

npx saffron accept                                    # inspect the heal
npx saffron accept <file> --propagate                 # fix every cache using the same locator or request
npx saffron accept <file> --with-feature-edit         # also rewrite the adapted steps in the feature file
  • --propagate: if the heal fixed a locator other scenarios also use (same role/name core), they're repaired in the same accept, one heal, N scenarios, zero extra tokens. The same holds for an API request: when the heal rewrote a request (a field the API now requires, a renamed path), every other cache sending that same request under the same step text, as scenarios sharing the step's wording do, takes the new one; accept prints the request as it was and as it is now. A request fix is not copied when it writes a value of the healed scenario's own (a table cell, a doc string, its Outline row), adds a <column> or {captured:...} value, drops a value the request extracted, sends the request to another API, or when the heal kept the request as it was in one place and rewrote it in another: accept says why, and those scenarios heal on their own run. Neither is a step whose heal added or removed an action. Pass --propagate on the accept itself: a plain accept names the other caches and leaves them, and the proposal is gone afterwards, so there is nothing left to propagate later.
  • --with-feature-edit: applies the agent's corrected Gherkin to your feature file (an edit that would leave a step empty is refused when the proposal is read, and never applied) (Background steps are skipped: those you edit yourself) and keeps the cache in sync. saffron diff lists these rewrites under "Feature file edits", each step as the proposal recorded it against its new text, so you see them before you apply them. A step from a step set is marked: accept rewrites it in the step set, for every scenario that uses it.

Read a heal's adaptations for the kind of change. The agent names it (a locator or label change, a page removed or merged, a page added or reordered) and the page it happened on. Notes starting flow change: come from Saffron itself, not the agent: they list every click, key press, goto or tab move the healed recording dropped or added against the committed one, after a page at failure: note naming where replay stopped. For example:

⚠ page at failure: replay stopped at step 4 ("I enter my guest details") on /checkout, where the heal started.
⚠ flow change: step 4 ("I enter my guest details") no longer performs 1 navigation action from the committed recording: click button "Continue to payment". A page or navigation step may have been removed or merged.

A flow change is worth a second look before accepting: confirm the app really changed its flow and did not break it.

Notes starting behavior change: are Saffron's too. They appear when a healed step no longer enters, picks or uploads what the committed recording did, and name what the step checks instead:

⚠ behavior change: step 3 ("I enter my card details") no longer enters fill label "CVC" with "123", and checks expect text "3 digits" to be visible instead. The scenario may now test something else: accept it only if it still exercises what the scenario is about, or edit the scenario.

Every heal is also checked against the requirement contract of the recording it repairs: each value a step enters, each request it sends (method and path) and each check it makes, read from the committed recording. A repair may reach them through another element; it keeps a requirement only when the same step still does that same thing. saffron diff and the review page (--html) list each requirement with the repaired action that keeps it:

Requirement contract: 3 of 4 kept
    kept     step 2 enters "4242 4242": fill label "Card number" with "4242 4242"
    NOT KEPT step 2 sends POST /payments

A request or check the repair no longer makes adds a requirement not kept: note, and accept --all leaves it out like a behavior change (an input it no longer enters is that behavior change already). This shows what stayed and the evidence for it; it does not prove the repaired scenario means the same thing in general, so a click that reaches the page another way is not a requirement, and the reviewer still reads the steps.

A heal serves the scenario's intent first, then the step's, and the step's wording last. It may change how a step is done (another control, a merged page, a missing click), never what the scenario exercises: typing card details never becomes checking a hint, and a real payment or sign-in route never becomes a simulator's button, even when the final Then still passes. A value moved to another control (a text field that became a dropdown) is still entered and raises no note. accept --all leaves a proposal with a behavior change out; accept it by file once you have confirmed the scenario still tests what it says. When the route a scenario needs is missing from the environment (a product, a payment method, an issuer redirect), the agent fails and names it instead of working around it.

Notes starting insensitive check: come from the sensitivity check that follows a passing proof. A proof shows a recording replays; it does not show that its checks would notice anything. So Saffron replays the scenario once more, with no AI, and runs each Then on the page as it was just before the action step it follows. A check that passes there too sees nothing that step did: the day picked was already picked, or the price "changed" from a value it always had. The step it follows is the last one that acted on the page: a step that only waits (for an element, a response, a download) or only reads (a captured value, a watch armed for a message) and an API request are passed over, and a StepSet's last check, its exit guard, is not probed against the set's own last action, as it says the whole set got where it should. A set of checks only, or of waits and checks, is probed against the step before it like any other check.

A "not shown" check in a Then that holds other checks is also run on its own there. One whose element was not on the page before the step either (a selector that matches nothing, say) holds whatever the page shows, and gets the same note, naming that check: the step's other checks failing before the step had hidden it. Check an element the step makes disappear.

A "not shown" check naming a control by the start of its name (a textbox Firstname, where the page shows Firstname Required) finds no element before or after the step. Its note says so and names the control shown before the step: name it in full.

⚠ insensitive check: step 3 ("the summary reads Mon") also passes before step 2 ("I choose Mon") runs, so it proves nothing about what that step does. Make the step change something the check reads, or the check read what the step changes; accept it by file only if that is intended.

Fix the scenario (pick a day that is not already picked, compare against a value captured before the change), or accept it by file when the check is meant to hold either way. accept --all leaves it out. A check that fails before its step, or cannot run there, counts as sensitive, and the report shows "checks proven sensitive in N of M" beside the proofs that passed. The check runs only after a proof passed, so --no-verify skips it too. A check of an HTTP response is not run this way: it reads the latest response, and before its step that is another request's.

Notes starting recording: are also Saffron's own. They appear when the agent did something outside the steps it was given: for example, clicks a heal made before announcing the failed step (discarded: that is diagnosis, not part of the fix), actions a seeded recording made before its first step (filed under that step) or actions under a step that replay had already passed (discarded; that step keeps its recording). A heal never rewrites the steps before the one that failed.

Assertions never heal (default). If a Then fails as written, the run is red and the report explains why. If the change is intentional, update the feature file (or accept the suggested edit) and let the scenario re-record. Projects that want checkpoint flexibility can opt into --assertion-policy adaptable-mid: mid-scenario assertions may then be adapted (with an adaptation note, a suggested feature-file edit, proof replay, and your accept gate), but the final assertion block of every scenario stays strict under any policy.

What the agent may change

Every change a proposal makes to a committed recording falls in one of three classes. saffron diff marks each changed step with its class (adaptation in --json, with a count of each in summary), and the review page and the run report show it beside the step.

Class What it covers What you do
repair Only how a step finds the same element again or waits for it: a fallback selector added or changed (beside the role, name, label or test id that still name the element; the iframe it lives in names no element), a longer timeout, a wait for an element added or dropped. The step does the same actions on the same elements, in the same order, with the same values. A quick look. The proof replay already showed the step works.
review What a step does: an action added, removed or of another kind, a value typed or picked, a URL, a request, a response a step waits for and checks (its status, its body), an element named another way (a new name, label, test id, frame, or selector when that is all that names it: another name can be another element, "Pay with card" or "Pay with test simulator"), a step added or removed, and any change to a check (under adaptable-mid). A step one of Saffron's notes names (a behavior change, a volatile locator, an insensitive check) is review whatever changed. Read it: confirm the app changed, not broke.
protected What the agent never changes: the scenario's wording, its Then steps under the default policy, the values the scenario, its Examples and data/ give. The agent can only suggest a wording change, as a feature edit. Apply a feature edit yourself, with accept --with-feature-edit.

A first recording has no class: there is nothing it changed. The class is worked out from the recordings themselves, never from the agent's account, so an agent cannot call a change a repair. A renamed button is review: the proof replay found a button by the new name, and only you can tell it is the same one.

  [review] I click the login button
    - click button "Login"
    + click button "Sign in"
        name: "Login" → "Sign in"

1 to review (what a step does changed).

When a recording is wrong

Caches are never hand-edited; the honest fix is saffron run --rerecord on the affected scenarios, which discards their caches and records them fresh (seeded from every other recording) into a reviewable proposal. Two guards keep bad recordings out of the tree: a fill recorded with an empty value on a step that passes one marks the proposal UNVERIFIED with the reason, and saffron accept --all skips UNVERIFIED proposals unless told otherwise. The recorder also understands every way the agent can type (fill, pressSequentially, type) and drops the clearing fill('') that precedes typing into the same field.