Maestro on Limrun iOS
Run the stock upstream Maestro CLI against a remote Limrun iOS simulator.
wires
to the instance transparently: it
installs and launches the Maestro XCTest runner on the simulator when needed,
then routes the driver traffic to it. No fork of Maestro, no local simulator,
no local Xcode.
Prerequisites
- CLI 0.22.0 or newer: . Auth is or
(it may be set outside the project, so don't ask for it just
because it's missing from or the shell).
- Maestro CLI on PATH:
curl -fsSL https://get.maestro.mobile.dev | bash
.
Maestro needs Java 17+ ( to check). Both Maestro 2.5.x and
2.6+ work; adapts to the installed version automatically.
The CLI is the source of truth: if a flag errors or you need one not shown
here, check
lim ios <subcommand> --help
instead of guessing.
Verify the setup
Before touching the user's app, prove the whole pipeline with a flow against
the built-in Settings app; it needs no app install, tunnel, or build:
bash
ID=$(lim ios create --install-asset appstore/maestro-ios-runner-2.5.1.tar.gz \
--no-open --quiet --json | jq -r .metadata.id)
cat > hello-flow.yaml <<'EOF'
appId: com.apple.Preferences
---
- launchApp
- assertVisible: General
- takeScreenshot: settings-check
EOF
lim ios maestro --id "$ID" test hello-flow.yaml
All three steps reporting
means Maestro, the runner, and the
remote wiring all work; anything failing after this point is about the app or
the flow, not the setup.
Run a flow
bash
lim ios maestro test flow.yaml
lim ios maestro test flows/
lim ios maestro --id <ios-id> test flow.yaml
Without
this targets the most recently created iOS instance in the
current workspace (workspaces follow the git repo or worktree you run from);
pass
(before
) in scripts, agents, or when running from
a different directory. The first run on an instance takes a few extra seconds
to launch the runner (plus the install when it wasn't preinstalled); later
runs skip that. Extra Maestro flags go after
:
bash
lim ios maestro -- test flow.yaml --include-tags smoke --test-output-dir artifacts
Do not pass
,
,
,
, or
;
sets those itself and rejects duplicates.
Real Maestro exit codes and reports are preserved, so CI wiring works as with
local Maestro.
Instance setup
Any running iOS instance works; the runner is installed on first use. Creating
the instance with the runner preinstalled skips that step:
bash
lim ios create --install-asset appstore/maestro-ios-runner-2.5.1.tar.gz --no-open
skips opening the stream URL in a browser (important on headless
and CI machines). The runner asset name above is the only published one and it
is version-agnostic: the same runner serves Maestro 2.5.x through 2.7.x, so do
not look for an asset matching your Maestro version.
Install the app under test as usual (
lim ios create --install app.ipa
,
, or a build skill), then reference its bundle id via
in the flow. For Expo Go testing, also preinstall
appstore/Expo-Go-54.0.6.tar.gz
and open the project URL with
(env vars must be prefixed
to be visible in flows):
bash
MAESTRO_EXPO_URL='exp://<tunnel-host>' lim ios maestro test flow.yaml
Expo dev-client builds
Expo Go is the quickest path, but a dev-client build works too.
lands on the dev launcher rather than your app, so open the dev-client URL
instead:
followed by
- openLink: <scheme>://expo-development-client/?url=<url-encoded-metro-url>
.
Use
for building the dev client, starting Metro, and
deriving that URL.
Flow gotchas on Limrun
- / YAML commands are not supported (the
simulator is remote). Record around the run instead:
lim ios record start --id <ios-id>
returns immediately (recording happens on the instance), and
after the flow lim ios record stop --id <ios-id> -o video.mp4
downloads
the video to the local path.
- works and saves the PNG locally into the working
directory (or ), like stock Maestro.
- and flow commands that reference local simulator file paths are
not supported.
- HTTP calls from / must use URLs. Plain
calls are refused (except to the driver itself), because Maestro's
plain-HTTP traffic is routed through a local bridge that only forwards to
the remote simulator.
- When a flow re-runs against an app that is already open (for example Expo
Go), start it with before /; deep links
can be dropped by an app that is mid-foreground, and stale screens fail
early assertions.
- Fleet variance: anchor assertions on stable accessibility identifiers and
text, not on timing. Use with a generous timeout for
first app load.
- Text selectors match the element's accessibility label, and on iOS a label
often folds in sibling content (icon names, adjacent text) or non-breaking
spaces. Read the exact label with before writing the
selector instead of guessing from what the screen shows.
Validation signals
Running maestro <version> against <ios-id>...
then
Running on Limrun iPhone - iOS ...
: the driver is connected end to end.
Launching the Maestro runner...
: first use on this instance.
Installing the Maestro runner...
additionally appears only when the
instance was created without the runner asset. Subsequent runs skip both.
- Maestro itself prints several JDK lines (reflection, native
access) on every run; they are benign upstream noise, not Limrun errors.
- Flow failures print Maestro's own debug output directory with screenshots
and the UI hierarchy;
lim ios element-tree --id <ios-id>
shows the live
screen when debugging selectors.
Cleanup
Delete the instance when done:
(
is not valid
for delete). The wiring
starts is torn down when the command
exits.