> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobster.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Replay and heals

> Replay a passing check with no model, and review the steps it healed when your app changed.

`mobster test` reuses a passing check's recorded steps. Use `--no-heal` to require an approved recording and stop when it no longer matches your app.

## Replay a check with no model

After a check with steps has passed and its recorded path is approved, run:

```sh theme={"theme":"css-variables"}
mobster test --no-heal
```

The run stops if a screen changed or a complete approved path is missing. Without `--no-heal`, replay can call a model to heal a changed step.

Each recorded tap, swipe and typed field keeps what it needs to find on the screen: the app, the screen's labels and roles, the control's label and role, and which of the controls with that label and role it was, counted top to bottom. Each step has to find its recorded screen and control, and the assertions judge every run. A tap that seemed to change nothing is kept too: a tap can act without changing what Mobster reads, and on a busy Mac its screen can arrive after the read that follows it. A tap that changed nothing and that Smart repeated from the same screen counts once, even with a swipe that moved nothing between the two. A tap that changed the screen counts each time.

| flag | what it does |
| - | - |
| `--replay` | The default: replay, and heal a changed step. |
| `--fresh` | Run every step with the model and record the steps again. |
| `--no-heal` | Replay without a model. Stop at a changed screen (`drift`) or refuse a missing or incomplete approved path (`replay`). |

Strict replay also refuses an approved path for another iOS major version or device model, or a path that still needs a live model step. It refuses before app reset, install or launch, and without calling a model. Record a complete path with `--fresh` for the current app and device first.

When a check's steps say to quit and reopen the app, the model quits it and opens it again directly, and Mobster keeps that as one step: quit and reopen the app. The replay does the same with no model, and both reopen the app with the check's `launch` arguments and environment. If the model quits the app in the app switcher instead (Home, a drag up, a flick of the app's card, then the app), Mobster keeps those steps as one too. Anything else on the way (a tap, a press and hold, another app), an app left some other way than Home, or an app that was still running keeps the steps as the model took them. List those steps in the check's `relaunch:` key, such as `relaunch: [3]`, or write `relaunch: []` for none. A run then needs a quit and reopen for each listed step, or it ends `needs_review`, class `step_not_done`. A check with no key is read by its words until it has one ([the check file](https://docs.mobster.dev/checks.md) says how). A check recorded with Mobster 0.4.0 or earlier keeps its gestures until you run it with `--fresh`.

After you approve a replayed action, Mobster reads the screen again and checks that its full fingerprint and app permission still match. A changed screen stops that action. A recorded coordinate tap or dismissal also needs matching screen geometry. Older coordinate recordings without that proof stop for review.

A replay stops with `unknown_outcome` only when a step may have acted and Mobster can't see that it did. When WebDriverAgent refuses a tap before sending it, because the screen moved since it was read, nothing was sent: the replay reads the screen again and looks at the step afresh, up to 3 times. A typed step whose write stopped partway counts when a fresh read shows its field holding exactly the recorded text, and the text is never typed again. When WebDriverAgent can't say which field has focus and another field is on the screen, Mobster types nothing, since keys sent on a guess land in whichever field has focus.

## Keep a step on the right row

A step on one of several controls with the same label and role (an **Edit** on each row) finds it by its place among them, counted top to bottom, and by how many there are. It doesn't know the control by its row's text. The keyboard's keys aren't counted with your app's controls: a **Done** button on a form and the keyboard's **done** key are two controls, whether or not the keyboard is up. When their number changes, the step heals, or stops with `drift` under `--no-heal`.

A list that grows between runs changes that number. A real iPhone never clears the app's data, so a check that adds a row and then taps a per-row control drifts on every later run. Make the check delete what it adds.

A step on one of several such controls also needs the screen at least 80% alike. On a list longer than the screen, a scroll of one row or a row added above keeps the count, and the screen stays 80% alike once it shows a nav bar and a tab bar, or about seven rows. The step then taps the neighboring row's control, as it did before. Make the check reach the row the same way each run.

## Review a heal

When a step's screen changed, so its control isn't there (a button renamed, a sheet in the way, a second button with the same label), the model redoes that step only and the replay takes over again at the next recorded step. When the last step changed, no step is left to take over at, so the heal finishes the check's steps. The change is a heal, and you review it:

```text theme={"theme":"css-variables"}
✓ passed  A new user signs up on iPhone 17 Pro, iOS 26.4
    healed step 1: Tap "Continue" → Tap "Next"

1 passed in 12 s, $0.02
1 healed step waits for review: mobster test heal
```

* `results.json` lists the heal in the check's `healed` (`step`, `from`, `to`, `why`, frames before and after, and `diff`, the path of the heal file), and the HTML report shows both frames side by side.
* `<check>.heal.yaml` appears next to the check, so it shows up in your pull request. The check file itself never changes. A heal never changes an expectation: it only redoes a step whose control moved or changed, so a real bug still fails.
* The approved recording stays unchanged while the heal waits for review. Later runs replay those approved steps. If the screen still differs, the check stops with `drift` until you accept the heal or record again with `--fresh`.

```sh theme={"theme":"css-variables"}
mobster test heal                    # what healed, with the frames before and after
mobster test heal --accept signup    # keep the healed steps
mobster test heal --reject signup    # keep failing: the recorded steps come back, and fail when they change again
```

Choose `--reject` when the change is a bug. Then that step fails the check, class `drift`, until you record it again with `--fresh`.

`--accept` promotes a staged heal only while the approved path, check and external evidence still match the run that proposed it. `--reject` keeps the approved steps and marks the full replaced range strict. A stale proposal needs a new `--fresh` run. If Mobster can't stage a proposal, the run doesn't pass and the approved recording stays in place.

A project's page in Mobster for Mac, with `MOBSTER_EXPERIMENTAL_TESTS_PAGE=1`, shows the same heals under **Heals to review**, each step before and after with its two frames, with **Accept** and **Keep failing** ([Tests in Mobster for Mac](https://docs.mobster.dev/mac/tour.md#tests)). `GET /api/tests/heals?project=PATH` lists them, `GET /api/tests/heals/frame?project=PATH&path=…` reads a frame a waiting heal names, and `POST /api/tests/heals` with `{project, action, checks?}` settles them, `action` being `accept` or `reject`. Settling needs Mobster for Mac's session when it has one.

## Know when a replay stops

A replay stops when the phone doesn't confirm an action, typed text, or the screen after an action. It doesn't send that action again, doesn't go on to the next step, and the suite doesn't retry the check. Look at the app before you run it again. An action whose outcome is unknown is `couldnt_run`, class `unknown_outcome`, and Mobster never retries it.

* **The circuit breaker.** When healing a step would make more than half the recorded steps healed, Mobster stops the check (class `drift`) instead. It stops a heal before the action that would cross that line, so a heal that has taken its share can still finish. A check of one recorded step never heals: healing it would heal all of it. Record it again with `--fresh`.
* **What a `drift` stop counts as.** The check failed when an expectation failed on the screen where the replay stopped, or when a step you rejected a heal for changed again. When every expectation held, the check needs review (exit code 2), and Mobster doesn't run it again.
* **Text.** A replay types text that appears word for word in the check's steps, its link or its launch values, and search queries as they were typed. The model types other text it wrote again each run, at the cost of a model call.
* **Sends and purchases.** A replayed step that sends, posts, deletes or buys goes to the same approver as a model step. A simulator approves it unless Mobster can't classify it. An iPhone refuses it, and any other replayed step that [Ask before acting](https://docs.mobster.dev/safety.md#when-mobster-asks) would ask about ([What a simulator may do](https://docs.mobster.dev/testing/dialogs.md#what-a-simulator-may-do-that-an-iphone-may-not)).
* **iOS dialogs.** A permission alert, Allow Paste or purchase sheet that the recording never saw is answered from the check's `dialogs` between steps. It isn't a step and it isn't a heal ([System dialogs](https://docs.mobster.dev/testing/dialogs.md)).

## Know when a check records again

Mobster keeps approved steps per iOS major version, kind of device and device model, in `.mobster/cache/replay` (git ignores it). Each Mac records its own steps on its first pass. In GitHub Actions, the workflow from `mobster ci init` keeps them between runs in GitHub's cache ([The workflow](https://docs.mobster.dev/ci.md#the-workflow)).

* A new version or build of your app replays them with no model. A changed step heals or stops with `drift`.
* iOS 27 records afresh. iOS 26.4 to 26.5 keeps them.
* A changed selected model records a new path.
* Steps recorded before Mobster stopped keeping them per build record once more on a check's first run without `--no-heal` or `--repeat`. Until then, a run with either refuses the check and says so.
* A heal that waits for review when you upgrade stays tied to the old recording. It blocks heals for its check until you settle it with `mobster test heal`, and your choice lands in the old recording, so settle it before you upgrade.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.