Set the device’s state
Addconditions to an existing check file. This example runs it in dark appearance with larger text:
appearance, content_size and locale take a list as well as one value. A check with appearance: [light, dark] and content_size: [large, accessibility-large] runs four times under mobster test, once for each combination, named “Home [light, large]” and so on (at most 24 combinations). mobster verify runs one device state, so it refuses a check with lists and says to use mobster test.
Mobster checks the block when the file loads. A typo, a missing file or a value out of range stops the check before anything runs, and the message says what is wrong and what to write. A condition that can’t be set when the check runs makes it couldnt_run (class environment): the check never runs in the wrong state.
When the run ends, Mobster puts back the appearance, content size, contrast, location or route, status bar, permissions and Face ID enrollment it changed. Photos and videos added with media stay in the simulator’s library. Erase the simulator to clear them. status_bar data_network and wifi_mode change only what the status bar shows, not the connection.
On a real iPhone, Mobster sets only appearance and locale. network and stubs are refused there, and the check ends as couldn’t run. Everything else changes a phone’s state (its location, its permissions, its photos) or sends to a real token, so Mobster skips it. reset is not a condition: it is the check’s own key, with the levels none, data and reinstall (Reset levels). Each result in results.json has conditions.applied and conditions.skipped (each skipped key and why), and the report notes them.
Slow the network and stub requests
conditions.network limits the app’s connection while the check runs, and stubs answers named requests with fixed responses. Both work on a simulator only.
A stall stands for a lost packet and its retransmission.
flaky draws its stalls and resets from a fixed seed, so a check meets the same failures on every run unless its requests race each other. These profiles aren’t Apple’s Network Link Conditioner profiles.
A stub matches on host (a name, or *.example.com), path_prefix (default /) and method (default any), and the first match wins. respond takes status (default 200), a body from json or from file (a path relative to the project, up to 8 MiB) and delay_ms (up to 30,000). A request no stub matches goes to the real server, shaped by the profile. offline and stubs together are an error, because nothing can answer on a link that refuses everything.
Before the app launches, Mobster starts a proxy on 127.0.0.1 and launches the app with a small library (built with Xcode’s compiler the first time and cached in ~/Library/Caches/Mobster/netshim) that points the app’s URLSession at it. Mobster changes no rule, setting or file on your Mac, asks for no password and doesn’t touch other apps’ traffic. The proxy lives as long as the run.
- The app keeps the library each time the run opens it, after a relaunch or a quit, and a library you name in the check’s
DYLD_INSERT_LIBRARIESloads beside it. - A link that opens in the app while it has no process launches the app that way first: the check’s
open_url, theopen_urltool, and, on a simulator, a link the model opens in one of the URL schemes the app declares (CFBundleURLTypes), or one Mobster maps to an app it knows, such as Settings. - When iOS starts the app some other way, such as a tap on its Home Screen icon or a universal link, the app runs without the library, and the run ends
needs_review, classunlaunched_process. - Each run writes what the proxy saw to
network.jsonin its run folder (method, host, path without the query, status, bytes, time, and whether the request was passed, stubbed, tunneled or refused).conditions.networkinresults.jsonrepeats the first 200 rows.
URLSession (shared, default and ephemeral, over http and https), so it covers Swift and Objective-C apps, Alamofire and React Native’s fetch. It doesn’t reach:
- The app’s view of the link.
NWPathMonitor, Reachability and the status bar read your Mac’s connection, so an offline screen that appears only when the system reports no network stays hidden. Underoffline, anhttprequest fails withNSURLErrorNetworkConnectionLost(-1005) and anhttpsrequest with error 310 inkCFErrorDomainCFNetwork, neverNSURLErrorNotConnectedToInternet(-1009). - Traffic that doesn’t use
URLSession: Flutter’sdart:io, gRPC, Cronet, Firebase’s own transport, raw sockets and QUIC. When no request reaches the proxy,network.jsonsays so. - Web views, other apps and system services.
- An app that sets its own proxy in its
URLSessionConfiguration.
https stub needs the app to trust a certificate Mobster makes for the run. Mobster adds it to the simulator’s trust store with simctl keychain add-root-cert. The run’s private key is deleted when the run ends, and the public certificate stays in the simulator. Apps that pin certificates refuse a stubbed host, and the check fails with the app’s own error. An https host without a stub passes through as a tunnel Mobster never opens, so pinned apps keep working under network: alone.
Assert what the app sent and stored
expect: can also judge what the app sent over the network and what it wrote to disk, so a check can prove that “Add to cart” reached the server without reading a second screen. This works inside canvases and web views where the accessibility tree is empty, as long as the traffic uses URLSession. Set MOBSTER_EXPERIMENTAL_NETWORK=1, or Mobster refuses a check that uses it. It runs on a simulator only. On a real iPhone the check ends as couldn’t run.
path is a glob (* matches anything, including /) on the path, or a /regex/. A glob without ? ignores the query string. A body or response is a case-insensitive substring, or a /regex/. Mobster reads gzip, deflate and chunked response bodies, and the first 64 KiB of each body.
A request that matches is the evidence: POST api.example.com/v1/cart -> 200 at 4.2 s in the report, and wire.json in the run folder with the request’s headers (authorization, cookie, token and key headers are masked, other values go through the secret filter) and the first 200 characters of its body and response. A storage change shows the diff of a text or plist file, or the first rows of a query, masked the same way. results.json repeats this in the assertion’s matches.
- Mobster opens an
httpsconnection only to a host arequestorno_requestnames together with amethod,path,status,bodyorresponse, because opening it needs the run’s certificate. Name the host. Any otherhttpsconnection is a tunnel: the assertions see its host, not its paths. - A
no_requestwith amethod,pathorbodyand no host can’t be decided while the app has an unopenedhttpsconnection, and fails with the host to name. With a host, it is decided on that host alone. - If no request from the app reached the proxy at all,
requestandno_requestboth fail: the app may not useURLSession. Pair ano_requestwith arequestor a screen assertion that proves the app was routed. - The proxy records every request from launch to the end of the run, not only those after the steps.
- Pinned apps refuse the certificate for an opened host, as with stubs.
storagecompares the app’s data container at the launch screen with the container at the end. Mobster reads it in place withsimctl get_app_container. Preferences the app hasn’t flushed to disk aren’t there yet.