Skip to content

Commit 80d8a19

Browse files
authored
ci(ios): add ensure-simulator-ready script for reliable boot/ready waits (#18382)
Port boot-status polling from react-native-firebase CI: wait for simctl bootstatus after simulator-action (or simctl boot)
1 parent 6c28f8e commit 80d8a19

5 files changed

Lines changed: 206 additions & 10 deletions

File tree

.github/workflows/e2e_tests_fdc.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,21 +218,25 @@ jobs:
218218
- uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5
219219
id: simulator
220220
with:
221+
# https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#installed-simulators
221222
model: "iPhone 16"
223+
- name: Ensure Simulator Ready
224+
env:
225+
SIMULATOR: ${{ steps.simulator.outputs.udid }}
226+
ENSURE_BOOT_IF_NEEDED: "0"
227+
run: .github/workflows/scripts/ensure-simulator-ready.sh
222228
- name: 'E2E Tests'
223229
working-directory: 'packages/firebase_data_connect/firebase_data_connect/example'
224230
env:
225231
SIMULATOR: ${{ steps.simulator.outputs.udid }}
226232
run: |
227233
# Uncomment following line to have simulator logs printed out for debugging purposes.
228234
# xcrun simctl spawn booted log stream --predicate 'eventMessage contains "flutter"' &
229-
# The iOS simulator sometimes fails to connect the VM Service. Keep a
230-
# limit around the full test command and retry once with a simulator reboot.
235+
# Retry once after VM Service / simulator flake (reboot + migration-aware wait).
231236
perl -e 'alarm 900; exec @ARGV' -- flutter test integration_test/e2e_test.dart -d "$SIMULATOR" --dart-define=CI=true --timeout 10x || {
232237
echo "First attempt failed or timed out. Rebooting simulator and retrying..."
233238
xcrun simctl shutdown "$SIMULATOR" || true
234-
xcrun simctl boot "$SIMULATOR"
235-
xcrun simctl bootstatus "$SIMULATOR" -b
239+
"${GITHUB_WORKSPACE}/.github/workflows/scripts/ensure-simulator-ready.sh"
236240
flutter test integration_test/e2e_test.dart -d "$SIMULATOR" --dart-define=CI=true --timeout 10x
237241
}
238242
- name: Save Firestore Emulator Cache

.github/workflows/e2e_tests_pipeline.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,17 @@ jobs:
204204
- uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5
205205
id: simulator
206206
with:
207+
# https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#installed-simulators
207208
model: "iPhone 16"
208209
- name: Build iOS (simulator)
209210
working-directory: packages/cloud_firestore/cloud_firestore/pipeline_example
210211
run: |
211212
flutter build ios --no-codesign --simulator --debug --target=./integration_test/pipeline/pipeline_live_test.dart --dart-define=CI=true
213+
- name: Ensure Simulator Ready
214+
env:
215+
SIMULATOR: ${{ steps.simulator.outputs.udid }}
216+
ENSURE_BOOT_IF_NEEDED: "0"
217+
run: .github/workflows/scripts/ensure-simulator-ready.sh
212218
- name: Run pipeline E2E tests (iOS)
213219
working-directory: packages/cloud_firestore/cloud_firestore/pipeline_example
214220
env:

.github/workflows/ios.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,13 @@ jobs:
125125
- uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5
126126
id: simulator
127127
with:
128-
# List of available simulators: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md#installed-simulators
128+
# List of available simulators: https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#installed-simulators
129129
model: "iPhone 16"
130+
- name: Ensure Simulator Ready
131+
env:
132+
SIMULATOR: ${{ steps.simulator.outputs.udid }}
133+
ENSURE_BOOT_IF_NEEDED: "0"
134+
run: .github/workflows/scripts/ensure-simulator-ready.sh
130135
- name: 'E2E Tests'
131136
working-directory: ${{ matrix.working_directory }}
132137
env:

.github/workflows/scripts/drive-example.sh

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ fi
1313

1414
if [ "$ACTION" == "ios" ]
1515
then
16-
SIMULATOR="iPhone 14"
17-
# Boot simulator and wait for System app to be ready.
18-
xcrun simctl bootstatus "$SIMULATOR" -b
16+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17+
SIMULATOR="${SIMULATOR:-iPhone 16}"
18+
export SIMULATOR
19+
"${SCRIPT_DIR}/ensure-simulator-ready.sh"
1920
xcrun simctl logverbose "$SIMULATOR" enable
20-
# Sleep to allow simulator to settle.
21-
sleep 15
2221
# Uncomment following line to have simulator logs printed out for debugging purposes.
2322
# xcrun simctl spawn booted log stream --predicate 'eventMessage contains "flutter"' &
2423
melos exec -c 1 --fail-fast --scope="$FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE" --dir-exists=integration_test -- \
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#!/bin/bash
2+
3+
# Wait until an iOS Simulator is fully ready for integration tests (including first-boot
4+
# data migration). Intended to run after futureware-tech/simulator-action (or any step
5+
# that has issued simctl boot) and before flutter test / flutter drive.
6+
#
7+
# Usage:
8+
# SIMULATOR=<udid-or-device-name> ./ensure-simulator-ready.sh
9+
# ./ensure-simulator-ready.sh <udid-or-device-name>
10+
#
11+
# Environment:
12+
# SIMULATOR UDID or device name (required if not passed as arg)
13+
# BOOT_POLL_INTERVAL_SECONDS Poll interval (default: 20)
14+
# BOOT_PROBE_TIMEOUT_SECONDS Per-probe timeout (default: 12)
15+
# BOOT_MAX_WAIT_SECONDS Max wait for full boot (default: 660)
16+
# ENSURE_OPEN_SIMULATOR_APP Open Simulator.app when booting (default: 1)
17+
# ENSURE_BOOT_IF_NEEDED simctl boot when not Booted yet (default: 1)
18+
set -euo pipefail
19+
20+
BOOT_POLL_INTERVAL_SECONDS="${BOOT_POLL_INTERVAL_SECONDS:-20}"
21+
BOOT_PROBE_TIMEOUT_SECONDS="${BOOT_PROBE_TIMEOUT_SECONDS:-12}"
22+
BOOT_MAX_WAIT_SECONDS="${BOOT_MAX_WAIT_SECONDS:-660}"
23+
ENSURE_OPEN_SIMULATOR_APP="${ENSURE_OPEN_SIMULATOR_APP:-1}"
24+
ENSURE_BOOT_IF_NEEDED="${ENSURE_BOOT_IF_NEEDED:-1}"
25+
26+
DEVICE="${SIMULATOR:-${1:-}}"
27+
if [[ -z "$DEVICE" ]]; then
28+
echo "[boot-status] ERROR: set SIMULATOR or pass device UDID/name as first argument" >&2
29+
exit 1
30+
fi
31+
32+
run_with_timeout() {
33+
local max="$1"
34+
shift
35+
"$@" &
36+
local cmd_pid=$!
37+
local waited=0
38+
while kill -0 "$cmd_pid" 2>/dev/null && (( waited < max )); do
39+
sleep 1
40+
waited=$((waited + 1))
41+
done
42+
if kill -0 "$cmd_pid" 2>/dev/null; then
43+
kill "$cmd_pid" 2>/dev/null
44+
wait "$cmd_pid" 2>/dev/null || true
45+
return 124
46+
fi
47+
wait "$cmd_pid"
48+
}
49+
50+
log_boot_status() {
51+
echo "[boot-status] $*"
52+
}
53+
54+
is_udid() {
55+
[[ "$1" =~ ^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$ ]]
56+
}
57+
58+
describe_booted_device() {
59+
local device="$1"
60+
if is_udid "$device"; then
61+
xcrun simctl list devices booted 2>/dev/null \
62+
| grep -F "$device" \
63+
| grep -v 'unavailable' \
64+
| head -1 \
65+
|| true
66+
else
67+
xcrun simctl list devices booted 2>/dev/null \
68+
| grep -i "${device} (" \
69+
| grep -v 'Phone:' \
70+
| grep -v 'unavailable' \
71+
| grep -v CoreSimulator \
72+
| head -1 \
73+
|| true
74+
fi
75+
}
76+
77+
is_device_booted() {
78+
[[ -n "$(describe_booted_device "$1")" ]]
79+
}
80+
81+
log_migration_status() {
82+
local device="$1"
83+
local migration_output probe_rc
84+
85+
log_boot_status "probing data migration (bootstatus -d, up to ${BOOT_PROBE_TIMEOUT_SECONDS}s)..."
86+
set +e
87+
migration_output="$(run_with_timeout "$BOOT_PROBE_TIMEOUT_SECONDS" xcrun simctl bootstatus "$device" -d 2>&1)"
88+
probe_rc=$?
89+
set -e
90+
91+
if [[ "$probe_rc" -eq 124 ]]; then
92+
log_boot_status " data migration / system bring-up still in progress"
93+
return 1
94+
fi
95+
96+
if [[ -n "$migration_output" ]]; then
97+
while IFS= read -r line; do
98+
[[ -z "$line" ]] && continue
99+
log_boot_status " ${line}"
100+
done <<<"$migration_output"
101+
else
102+
log_boot_status " no migration details reported"
103+
fi
104+
return 0
105+
}
106+
107+
wait_for_simulator_ready() {
108+
local device="$1"
109+
local start=$SECONDS
110+
111+
while (( SECONDS - start < BOOT_MAX_WAIT_SECONDS )); do
112+
local elapsed=$(( SECONDS - start ))
113+
local booted_line ready_rc
114+
115+
log_boot_status "elapsed=${elapsed}s phase=wait_for_full_boot device=\"${device}\""
116+
117+
booted_line="$(describe_booted_device "$device")"
118+
if [[ -z "$booted_line" ]]; then
119+
log_boot_status " simctl list: not in Booted state yet"
120+
else
121+
log_boot_status " simctl list: ${booted_line}"
122+
log_migration_status "$device" || true
123+
fi
124+
125+
set +e
126+
run_with_timeout "$BOOT_PROBE_TIMEOUT_SECONDS" xcrun simctl bootstatus "$device" >/dev/null 2>&1
127+
ready_rc=$?
128+
set -e
129+
130+
if [[ "$ready_rc" -eq 0 ]]; then
131+
log_boot_status "bootstatus: simulator ready after ${elapsed}s"
132+
log_migration_status "$device" || true
133+
return 0
134+
fi
135+
136+
if [[ "$ready_rc" -eq 124 ]]; then
137+
log_boot_status "bootstatus: still booting (probe timed out after ${BOOT_PROBE_TIMEOUT_SECONDS}s)"
138+
else
139+
log_boot_status "bootstatus: probe exited with status ${ready_rc}"
140+
fi
141+
142+
sleep "$BOOT_POLL_INTERVAL_SECONDS"
143+
done
144+
145+
log_boot_status "ERROR: timed out after ${BOOT_MAX_WAIT_SECONDS}s waiting for simulator to become ready"
146+
return 1
147+
}
148+
149+
if is_udid "$DEVICE"; then
150+
log_boot_status "phase=resolve_device udid=\"${DEVICE}\""
151+
else
152+
log_boot_status "phase=resolve_device name=\"${DEVICE}\""
153+
fi
154+
155+
if ! is_device_booted "$DEVICE"; then
156+
if [[ "$ENSURE_BOOT_IF_NEEDED" == "1" ]]; then
157+
log_boot_status "phase=boot_command device not Booted; starting simctl boot..."
158+
set +e
159+
boot_output="$(xcrun simctl boot "$DEVICE" 2>&1)"
160+
boot_rc=$?
161+
set -e
162+
if [[ "$boot_rc" -ne 0 ]]; then
163+
log_boot_status "simctl boot exited ${boot_rc}: ${boot_output}"
164+
else
165+
log_boot_status "simctl boot command returned (device may still be migrating data)"
166+
fi
167+
if [[ "$ENSURE_OPEN_SIMULATOR_APP" == "1" ]]; then
168+
log_boot_status "phase=foreground_simulator opening Simulator.app..."
169+
open -a Simulator.app || true
170+
fi
171+
else
172+
log_boot_status "phase=boot_command skipped (ENSURE_BOOT_IF_NEEDED=0); waiting for existing boot..."
173+
fi
174+
else
175+
log_boot_status "phase=boot_command device already Booted; waiting for full readiness..."
176+
fi
177+
178+
if ! wait_for_simulator_ready "$DEVICE"; then
179+
exit 1
180+
fi
181+
182+
log_boot_status "phase=complete device=\"${DEVICE}\" ready for flutter test"

0 commit comments

Comments
 (0)