Skip to content

Ready the iOS simulator before Maestro and restart a driver that exits on first launch - #74

Merged
arun279 merged 1 commit into
feat/expo-nativefrom
ci/simulator-readiness
Sep 26, 2026
Merged

arun279 merged 1 commit into
feat/expo-nativefrom
ci/simulator-readiness

Conversation

@arun279

@arun279 arun279 commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Why

On a runner that is still settling, the iOS lanes (the three native-e2e-ios-light shards and ui-screenshots-ios-dark) lost the Maestro XCUITest driver during the first flow's launch command. In run 36213528963 (first attempt, detail shard) maestro.log ends the first Launch app "app.cuetracker" with clear state with Transport unreachable while processing setPermissions, latching and the JUnit failure is maestro.DeviceUnreachableException: Device became unreachable during setPermissions. No assertion had run; the flow never got past its first command.

What changes

  1. Boot. scripts/prepare-ios-ui.sh already boots the simulator first (xcrun simctl boot, overlapping the Maestro download) and blocks on xcrun simctl bootstatus <udid> -b before installing. This PR keeps that.

  2. App warm-up. After simctl install, the script launches the app once with xcrun simctl launch --terminate-running-process, polls xcrun simctl spawn <udid> launchctl list for a numeric PID on the app.cuetracker label (for up to 30 s), then runs xcrun simctl terminate. If the app never shows up as running, terminate fails and the job fails in the prepare step instead of inside Maestro. Maestro's clearState reinstalls the app, so the app's own container is recreated. What stays warm is the system side: SpringBoard, the dyld cache, and the launch services path.

  3. One bounded driver restart. scripts/maestro-ios-test.sh wraps maestro --device "$DEVICE_ID" test --debug-output <dir>. When the run fails, it reruns maestro test exactly once, and only if the run's maestro.log meets both conditions:

    • it contains Maestro's transport latch line Transport unreachable while processing, and
    • the only non-structural command started was the first Launch app (no assertion, tap, or other step had begun).

    A new maestro test process restarts the XCUITest runner (restartXCTestRunner uninstalls, installs, and starts it at session start). Assertion failures, launch failures without the latch, and driver deaths later in a flow fail on the first attempt with no retry. Run 36213699490's activity shard is a mid-flow driver death (the latch fires during Tap on id: snackbar-undo, after several assertions passed), so it is deliberately not retried.

Tests

test/ci/ios-maestro-readiness.test.ts runs the wrapper against a fake maestro that replays log excerpts copied from the failing runs:

  • A pass runs once.
  • A first-launch driver exit runs twice and passes on the second run. Two exits in a row run twice and fail.
  • A mid-flow driver exit, an assertion failure, and a launch failure without the latch each run once and fail.

A workflow-structure test checks that every macOS job that runs Maestro is exactly native-e2e-ios-light and ui-screenshots-ios-dark, that each runs scripts/prepare-ios-ui.sh before scripts/maestro-ios-test.sh, and that neither calls maestro directly.

Mutations, each caught by a failing test:

  • retry on any failure
  • keep the latch check but drop the first-launch condition
  • keep the first-launch condition but drop the latch check
  • revert the dark lane to a direct maestro call

The job parser from release-paths.test.ts moved to test/support/workflow-jobs.ts so both tests share it.

Cost on a healthy runner

  • Boot: no change. Five of the seven jobs read printed Device already booted, nothing to do. by the time bootstatus ran. The other two waited in bootstatus while the boot overlapped the Maestro download, as before.
  • Warm-up: about 5 s, based on the same simctl calls timed inside Maestro's own log in run 36213699490 (activity shard): simctl launch took 2.8 s (launch start to command completion) and simctl terminate took 1.9 s. This PR adds one launchctl list poll that I have not timed.
  • Retry: 0 s when the first run passes or fails for a real reason. When the retry does fire, it costs one Maestro startup, which took 2 to 3 minutes in these logs (step start to Waiting for flows to complete...).

Not measured: whether the warm-up reduces first-launch driver exits. That needs CI runs on this branch.

Sources

@github-actions

Copy link
Copy Markdown

Pull request footprint

measurement base to head
Product code lines in core/src, native/src, native/app, and native/modules +0
Test lines in test, tests, and e2e paths +0
Product comment lines identified by a comment prefix +0

User-delivered artifact sizes

measurement base head delta
Expo iOS JavaScript bundle, raw file 5.36 MB 5.36 MB 0 B
Expo Android JavaScript bundle, raw file 5.39 MB 5.39 MB 0 B
Firebase tester APK, arm64-v8a and all densities 41.95 MB 41.95 MB 0 B
Play download estimate, XXXHDPI arm64-v8a English Android 15 20.15 MB 20.15 MB 0 B

@arun279
arun279 merged commit 6fb16c4 into feat/expo-native Sep 26, 2026
35 of 37 checks passed
@arun279
arun279 deleted the ci/simulator-readiness branch September 26, 2026 04:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant