> ## 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.

# Test a pull request on your Mac

> Run only the checks a branch needs, build the app, and write the pull request comment.

`mobster test pr` runs the checks a branch needs, not the whole suite, and writes the pull request comment. [CI](https://docs.mobster.dev/ci.md) posts it on every pull request.

## Test the checks a branch needs

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

It prints a line per check:

```text theme={"theme":"css-variables"}
Changed screens: PaywallView, SettingsView
✗ failed  The paywall shows three plans on iPhone 17 Pro, iOS 26.4
    text “Choose your plan”: not on screen; closest: "Choose a plan"
~ draft  .mobster/drafts/pr/settings.yaml  on iPhone 17 Pro, iOS 26.4, passed

Mobster checks: 0 passed, 1 failed
Comment  .mobster/test-results/20261009-150652-1215/pr-comment.md
GIFs     .mobster/test-results/20261009-150652-1215/pr/gifs (2)
```

1. It reads `git diff` from where the branch left `origin/main` (`--base` picks another), uncommitted work included.
2. Each changed Swift view becomes a screen: its name (`PaywallView` is `paywall`), and the accessibility identifiers, strings and deep links on the changed lines. Storyboards, `.strings` and `.xcstrings` files count too.
3. It picks every saved check that names a changed id or string, mentions a changed string in a step, carries the screen as a tag or file name, or was itself changed. When it picks none, the kind of each changed file decides ([below](#know-which-files-change-the-app)).
4. It builds the app with `xcodebuild` into `.mobster/build`, for the simulator, or for the iPhone with `--device`, and points the picked checks at the new build. `--scheme` and `--project` choose what to build, and `--no-build` skips it.
5. For each changed screen that no check covers, it writes a draft to `.mobster/drafts/pr/<screen>.yaml` from the screen's new ids and strings, at most 3. A draft runs beside the checks and never fails the pull request. Move a good one into `.mobster/checks/` to keep it.

The exit codes are `mobster test`'s, counted without the drafts. A build that fails exits 3, and the comment quotes its first errors. Mobster builds one project at a time: while another Mobster build writes `.mobster/build`, such as your coding agent's in another session, the build stops at once with `Another Mobster build is writing` (next release) and exits 3.

## Know which files change the app

When no check names what changed, app and unknown files run every check, each with the reason "an app file changed and no check names it". A diff over 400 files does too. Tests, config and docs on their own run the checks tagged `smoke`.

Mobster asks your Xcode project first:

* A file or folder that an app, extension or framework target builds is app code, even when a test target builds it too.
* A file or folder that only test targets build is a test.
* A path in a target's `DEVELOPMENT_ASSET_PATHS`, such as `Preview Content`, is a test, unless a target builds that path itself.
* So a feature folder named `LabTests` is app code, and a `FirefoxTests` target is a test.
* Mobster reads every `project.pbxproj` in the repository, including one git ignores, as XcodeGen and Tuist write them, for at most 5 seconds. A project it couldn't read in time adds nothing.

A file no target builds goes by its path:

* Config: a file in a `.github`, `.vscode`, `.cursor`, `.claude`, `.codex`, `.gemini`, `.zed`, `.agents`, `.windsurf`, `.amp`, `.omp`, `.mobster`, `.changeset` or `.husky` folder, at any depth. The files `.gitignore`, `.gitattributes`, `.editorconfig`, `.mcp.json` and `opencode.json` are config too. Any other hidden folder, such as `.yarn`, is unknown.
* Test, in a SwiftPM package: a file in the `Tests` folder beside its `Package.swift`. The rest of a package is unknown.
* Test, outside a package: a file in a `Tests`, `UITests`, `Test` or `Preview Content` folder, or a file whose name ends in `Tests`, such as `CheckoutTests.swift`.
* Docs: a README, CHANGELOG, CONTRIBUTING, CODE\_OF\_CONDUCT, LICENSE or COPYING file, anywhere. A Markdown, text or reStructuredText file at the top level. A file in a `docs` or `Documentation` folder that isn't code.
* Unknown: anything else, such as a Podfile, an `Info.plist` or a package's sources.

`pr.json` lists each changed file's kind under `changes.kinds`. Of the new files git doesn't track yet, only screens, strings files and checks in `.mobster/checks/` count.

## Read the comment and its files

The suite folder gets the usual [reports](https://docs.mobster.dev/testing/reports.md), plus:

| file | what it holds |
| - | - |
| `pr.json` | `schema: "mobster.test-pr/1"`: the changed screens, each check picked and why, the drafts, the verdict, and each row's frame and GIF. `annotations` lists the file and line for each failure, warning or notice ([Line annotations](https://docs.mobster.dev/ci/github-app.md#line-annotations)) |
| `pr-comment.md` | The pull request comment: "Mobster checks: 11 passed, 1 failed", a table of check, device, what it expected, what it found and the frame, the failure's GIF, and the drafts |
| `pr/frames/` | Each check's verdict frame |
| `pr/gifs/` | A captioned GIF of each failed check first, then the drafts, then the rest: at most 6 |

For a repeated check, the repro steps, deep link and GIF come from the attempt that decided its verdict. The comment ends without a credit line. `mobster test pr --tested-with` ends it with "Tested with Mobster", linked to mobster.dev.

To post the comment when a pull request or merge request opens or changes, run `mobster ci` on your own Mac ([CI](https://docs.mobster.dev/ci.md)). In GitHub Actions, `mobster ci comment` also posts the verdict as the check `Mobster` (next release), with the job's token. When you run `mobster test pr` yourself, [Mobster Checks](https://docs.mobster.dev/ci/github-app.md) posts the same check.

## Test on your iPhone

`mobster test pr --device "Test iPhone"` builds for the phone and installs that build. With `--testflight com.example.yourapp` it skips the build and tests the TestFlight build already on the phone.

With two or more devices, `--spread` runs each picked check once, on whichever device is free ([Spread checks across phones](https://docs.mobster.dev/testing/devices.md#spread-checks-across-phones)). With fewer than two, or with `--repeat`, it stops before the build.

## Test every pull request on this Mac

> [!NOTE]
>
> You get this in the next release, not in Mobster for Mac 0.4.0 or Mobster CLI 0.4.3.

`mobster test pr --watch` tests each pull request in your clone when someone opens it or pushes to it. You need no workflow file, no runner and no CI minutes:

```sh theme={"theme":"css-variables"}
mobster test pr --watch
```

* The watcher runs `git ls-remote origin` every 30 seconds with the credential you already use to fetch. `--interval` sets 10 to 3,600 seconds. It needs no token: only [Mobster Checks](https://docs.mobster.dev/ci/github-app.md), when you install it, calls GitHub's API to post.
* It tests a pull request by itself when its head is also the head of a branch in the repository: someone who can push there pushed it. That includes branches bots such as Dependabot push, and their builds run third-party install scripts.
* It waits before it tests a head from a fork, because its build scripts would run on your Mac. Read the changes at that head, then restart the watcher with `mobster test pr --watch --allow N`. It tests the head that was waiting when it started, and a later push waits again.
* It gives each pull request its own worktree in `~/Library/Caches/Mobster/watch/<id>/<number>`, where `<id>` is 12 hex digits from your clone's git folder (`MOBSTER_CACHE_DIR` moves `~/Library/Caches/Mobster`). One watcher at a time watches a repository.
* A worktree holds only the files your repository tracks, and the watcher installs nothing. An Expo or React Native app without committed `node_modules`, or a project with a `Podfile` and no committed `Pods`, is skipped with one line. Native projects with Swift packages run. Unless you pass `--base`, it uses the base `mobster test pr` picks: `origin/main`, then `origin/master`, `main` and `master`.
* Each worktree keeps its own replay recordings and history, so a check with steps runs with a model on its first run there. You can't use `--comment`, `--json` or `--share` with `--watch`, and the project's Slack rule doesn't run for watched pull requests.
* On its first pass, it records the heads it finds and tests none. It tests 1 pull request at a time, and prints 1 line for each: the pull request, its verdict, the seconds from the listing that showed its head to the result, and the suite folder. With [Mobster Checks](https://docs.mobster.dev/ci/github-app.md) installed, the verdict goes on the pull request as the check `Mobster`, and `--github comment` adds the comment.
* Press `ctrl+c` to stop it. When you restart it, it tests only the heads that changed since.

Mobster for Mac, the terminal UI and `mobster serve` run this watcher by themselves for your own open pull requests, in each of your [projects](https://docs.mobster.dev/projects.md#pull-requests-on-github) that has saved checks and whose `origin` is a GitHub repository with Mobster Checks installed. Their watcher tests only pull requests opened by the GitHub account you connected, and anyone else's waits as a fork's does. While Mobster watches a repository, `mobster test pr --watch` in that clone says another watcher holds it. To turn that watcher off for a project, send `{"on": false}` to `POST /api/projects/{id}/pull-watch`.


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