ci: wait for the iOS simulator to be ready before running integration tests - #78
Open
vahidlazio wants to merge 2 commits into
Open
ci: wait for the iOS simulator to be ready before running integration tests#78vahidlazio wants to merge 2 commits into
vahidlazio wants to merge 2 commits into
Conversation
… tests `xcrun simctl boot` returns as soon as boot is initiated, not when the device is usable. Booting inline in the test step therefore let `flutter test` race the Dart VM debug connection against a still-booting simulator. When it lost that race the app built, installed and launched but the harness never attached, so the job sat silent until the 30 minute timeout with "No tests ran." and "Error waiting for a debug connection: The log reader failed unexpectedly". Measured locally: `simctl boot` returned after 2s while the device needed 3s more when warm and 17s more when cold, most of that "Waiting on Data Migration" running migrator plugins. Since every CI job starts cold, the outcome was effectively a coin flip per run. `bootstatus -b` boots the device if needed and blocks until it reports Finished, and exits non-zero for an invalid device, so a boot failure now fails its own step rather than surfacing as a test timeout later. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The simulator readiness gate did not stop the stall. On the first run of this branch the gate succeeded in 94s and the device was fully booted, and `flutter test` still hung after "Xcode build done." until the 30 minute timeout. That rules out the boot race: the failure is `flutter test` failing to attach to the Dart VM, which readiness does not affect. It is intermittent rather than deterministic — the same commit passes on some runs and stalls on others — and a second attempt with the app already built and the simulator warm has consistently succeeded. Two bounded 13 minute attempts fit inside the job's 45 minute budget. The previous single 30 minute attempt consumed almost the entire budget before failing, leaving no room to recover. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Test-iOSintermittently burned its full 30-minute timeout without running a single test. This makes it recover instead.The symptom
The app builds, installs and launches, but the test harness never attaches, so the job sits silent until it is killed:
What this is not
My first theory was a boot race —
simctl bootreturns as soon as boot is initiated, not when the device is usable, soflutter testcould race a still-booting simulator. That is real and measurable: on a CI runnerbootstatustakes 92-94s to reach ready, while the barebootit replaced returned in ~2s. That left a ~90 second window.But it is not the cause. The first run of this branch had the readiness gate in place, the gate succeeded in 94s with the device fully booted, and
flutter teststalled anyway and hit the 30-minute timeout. So the failure isflutter testfailing to attach to the Dart VM, which simulator readiness does not affect.The gate is kept regardless: it closes a genuine race, and a boot failure now fails its own step (
bootstatusexits non-zero on an invalid device — observed exit148) rather than surfacing later as an unexplained test timeout.What actually fixes it
The stall is intermittent, not deterministic — the same commit passes on some runs and stalls on others. A second attempt, with the app already built and the simulator warm, succeeds quickly.
This run demonstrates the whole cycle:
Attempt 1 stalled with exactly the diagnosed error and hit its bound; attempt 2 passed in 87 seconds. Total step time 14m51s, comfortably inside the job's 45-minute budget — whereas the previous single 30-minute attempt consumed almost the entire budget before failing, leaving no room to recover.
Changes
simctl bootstatus -b, so the wait is visible in the timeline and a boot failure is attributed correctly.nick-fields/retry@v3with 2 bounded 13-minute attempts (26 min total, vs the 45 min job budget).flutter config --no-enable-swift-package-managermoved to its own step so it is not repeated per attempt.Honest caveat
A retry hides the flake, it does not cure it. The underlying
flutter testattach failure on cold runners is still there, and if it ever becomes deterministic both attempts will stall and the job will fail — more slowly than before, though still inside budget. The real fix is upstream in the Flutter tooling or in how the harness attaches.Not changed:
Test-AndroidTest-Androiddelegates emulator lifecycle toreactivecircus/android-emulator-runner, so there is no un-gatedbootcall of ours to fix. Its observed failure (Unable to connect to adb daemon on port: 5037) happened inside that action's own setup, which cannot be gated from this workflow. Separately, that job has notimeout-minutes, so a hang runs to GitHub's 360-minute default — worth setting, but out of scope here.Known follow-up: the integration tests hit the live backend
example/test/widget_test.dartasserts on real flag resolution against the Confidence backend, so both integration jobs are network- and credential-dependent. Oneandroid-testfailure showed flag resolution returning""alongsideFailed to upload events. http code 400. That coupling makes these jobs flaky independent of anything here, and is worth decoupling separately.🤖 Generated with Claude Code