Skip to main content
mobster test pr runs the checks a branch needs, not the whole suite, and writes the pull request comment. CI posts it on every pull request.

Test the checks a branch needs

It prints a line per check:
  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).
  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, plus: 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). 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 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). With fewer than two, or with --repeat, it stops before the build.

Test every pull request on this Mac

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:
  • 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, 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 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 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.