Skip to main content
Every run writes a folder, .mobster/test-results/<suite-id>/, that stands on its own: upload it as a CI artifact and every link in it works. Reports stay in your project folder unless you share them with your team.

Find the files a run writes

A safety stop is a JUnit error even when its check is quarantined. A stopped or other nonzero suite with no failing check gets a Suite verdict error, so CI doesn’t report it as passed. --junit FILE and --html DIR also write the JUnit file and the HTML report (with runs/ and videos/) where your CI expects them. --json prints results.json on stdout.

Read the HTML report

Open index.html in the folder that mobster test prints. It starts with the checks that didn’t pass, then lists every check’s file, tags and verdict on each device. --spread groups the checks by device. Open a failed check to see its expectations, what was found instead, the verdict frame, and its Logs and Crash pane. The report links to each run’s report, log and video, then lists flaky checks. Every link stays inside the folder except Mobster in the footer, which opens mobster.dev.

Read JUnit results

junit.xml has one <testsuite> per device and one <testcase classname="checks.<file>"> per check:
  • <failure> has type assertion, app_crashed or needs_review.
  • <error type="couldnt_run"> marks a check that couldn’t run. <skipped> marks quarantine.
  • Each check has the properties status, attempts, flaky, passRate and report. When measured, it also has perf.launchMs, perf.memoryMb and perf.hangs.
  • Each suite has git.branch and git.head when you run in a git checkout.
  • A crash’s top frames appear in <failure>. The app’s log around the failure appears in <system-out>.

Read results.json

Read logs, crashes and video

For every run on a simulator or a USB iPhone, Mobster keeps the app’s log and, when the app crashed, its crash report. With --video it also records the screen.
  • A crash fails the check as failed, class app_crashed, even when the expectations held. Mobster knows the app crashed the moment SpringBoard logs that its process ended on a signal (SIGSEGV, SIGABRT, SIGTRAP), the watchdog (0x8badf00d) or jetsam. Relaunches that Mobster starts don’t count.
  • The log is the app’s unified log (os_log, Logger, NSLog) from before the launch to the verdict. On a simulator Mobster reads it with xcrun simctl spawn <udid> log stream, at the info level and above. On an iPhone it reads the syslog with idevicesyslog. Each line carries the run clock, in seconds since the run started. What your app prints to stdout with print isn’t in it.
  • The log around a failure in results.json marks each line app: true when it’s the app’s own (its process, not an Apple framework). Mobster for Mac shows those lines first and folds the rest behind Show system log.
  • The video is a simulator’s screen through xcrun simctl io recordVideo, or an iPhone’s through WebDriverAgent’s video stream, encoded on the Mac to an H.264 MP4 590 pixels wide. Nothing on the phone changes. Mobster doesn’t store a frame that repeats the one before, so a still screen costs nothing.
  • The crash report comes from ~/Library/Logs/DiagnosticReports for a simulator, and from the iPhone with idevicecrashreport --keep, which copies it and leaves it on the phone. Mobster names your app’s frames with atos and the dSYM beside the check’s .app or in .mobster/build/Build/Products. A Debug build already carries its names.
The run’s report puts the Logs and Crash pane under the step the failure came in: the crash’s top frames, your own in full ink, then the app’s log 10 seconds either side of the failure with the moment marked. A launch-only check has it as its own section. index.html shows the same pane under the expectations and links to the full log and the .ips, and junit.xml carries the crash in <failure type="app_crashed"> and the log window in <system-out>. In Mobster for Mac, each project’s page shows its runs by branch and the same pane beside each failed check’s steps, with Run checks to run them all again and Run again under the failed checks to run only those that didn’t pass. results.json gives each result that didn’t pass an evidence object. Proof for every step adds the slices for each step. macOS writes a simulator’s crash report some time after the crash, so mobster test waits up to 60 seconds once, at the end of the suite, for the reports it’s missing. A report that comes later stays in ~/Library/Logs/DiagnosticReports, and the check still says it crashed and on which signal. Each log line and every string in a crash report goes through the secret filter before it’s written: passwords, one-time codes, card numbers and API keys become ••••. They stay in the run folder on this Mac, and Mobster sends them nowhere. Over MCP, the result your coding agent reads (verify_finish, run_tests) carries evidence as data. The log stops at 1 MB, then keeps its last 2,000 lines and says how many it dropped. On an iPhone over Wi-Fi, the log and crash reports need libimobiledevice to reach the phone on the network. When it can’t, the result’s note says there’s no log. A check on a WebDriverAgent address gets no log or crash report, because Mobster can’t tell which phone it is.

Export a suite

From the project folder, name the finished suite:
show prints the suite verdict and evidence count. --json gives opaque check and device IDs, statuses and evidence hashes, with no check names, device names, paths, app text or pixels. export writes that descriptor to a new local folder and uploads nothing. Both need the suite’s evidence-inventory.json and an unchanged results.json, and refuse a suite without them. To copy raw evidence, pick each kind:
Selected files are unredacted and stay on this Mac until you share them. A selected HTML report or JUnit file can contain app text, log lines or crash details. --output names a new folder. If a selected file is missing or changed, export refuses before it creates the folder. Logs, crash reports and videos stay in the suite folder and are never copied into a capsule.

Compare two suites

You pick both suites. Mobster compares their saved results and reports each check and device’s observedTransition apart from verifiedFix. It needs each suite’s inventory and an unchanged results.json. verifiedFix is true only when clean, single-attempt simulator runs show an app-state field (Fixtures) fail on one build and pass on another, while the check, fixture, environment and policy stay the same. An input Mobster couldn’t pin (What a suite pins) never counts toward a verified fix.

Shrink a failing check

When a comparison verified a fix, mobster test minimize looks for the fewest steps that still fail on the bad build and pass on the good one. Keep both simulator .app builds in the project, then run:
--isolated says you isolated any outside state for every trial: Mobster resets the app as the check requires, but it can’t reset your backend. It first reruns the original pair. It keeps a step’s removal only when the bad build fails the same assertion twice and the control build passes once between them. An unknown outcome stops without a retry. It runs at most 8 suites, the original pair included (--max-attempts N takes 5 to 8).
The result is .mobster/drafts/repro/<pair-and-refs>/result.json, with candidate.yaml only when a smaller check is confirmed. Mobster never edits your check or its approved path. It exits 0 when it finished, even with no smaller check, 3 on bad input or an unknown outcome, and 130 when you stop it.