Updated September 5, 2026

iOS simulator tests on GitHub Actions

Simulator tests are the part of macOS CI that behaves differently on a fresh VM than on a laptop. Three things explain most of the flakiness.

First boot is the slow part

A simulator that has never been booted on this VM takes tens of seconds to come up, and xcodebuild test will happily time out waiting for it. Manzanita's Standard image installs the iOS 26.5 runtime, boots an iPhone 17 Pro once during image build, and shuts it down cleanly, so that device is warm on every job. Other devices exposed by the installed runtime can be created, but they pay the cold-boot cost.

If you need a different device on any runner, boot it explicitly before testing so the wait is visible and bounded:

- run: |
    UDID=$(xcrun simctl list devices available -j | jq -r '.devices | to_entries[] | .value[] | select(.name=="iPhone 17 Pro") | .udid' | head -1)
    xcrun simctl boot "$UDID" || true
    xcrun simctl bootstatus "$UDID" -b

Name destinations so they survive updates

Hard-coding OS=26.5 breaks the day the runtime moves. Use OS=latest and the device name; pin the OS only in a dedicated compatibility lane:

xcodebuild test -scheme App \
  -destination 'platform=iOS Simulator,OS=latest,name=iPhone 17 Pro' \
  -parallel-testing-enabled NO

Parallel testing spawns clones of the simulator; on a 6-vCPU profile it is usually slower than serial, and it multiplies cold boots. Measure before enabling it.

Older iOS versions

The Manzanita Standard image promises one runtime, iOS 26.5, and does not currently ship visionOS, watchOS or tvOS runtimes. If you need behavioural coverage on iOS 17 or 18, keep those matrix lanes on a runner that has the runtime installed, or download it in the job with xcodebuild -downloadPlatform iOS -buildVersion ... and accept the download time. The list of promised runtimes lives in the docs and changes only through the changelog.

GUI session and accessibility

XCUITest and Accessibility-based tests need a logged-in Aqua session with Accessibility enabled, persistent scrollbars and animations off. The Manzanita image sets all of these at build time, which is the difference between a UI test that passes locally and one that hangs on waitForExistence in CI.

Try it on your own workflow. One Standard runner is free for 14 days or 300 build minutes, no card. Change runs-on to manzanita-standard and keep everything else. Start free · Read the docs

Related