From 61178aa9dba787c478e56b405ab59d06f3b2510c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Sun, 8 Nov 2026 10:52:20 +0100 Subject: [PATCH 1/3] ci(ios): wait for the freshly booted simulator to settle before Detox tests --- .github/workflows/ios-actions.yml | 5 ++++ scripts/ci/wait-for-settled-host.sh | 37 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100755 scripts/ci/wait-for-settled-host.sh diff --git a/.github/workflows/ios-actions.yml b/.github/workflows/ios-actions.yml index 1d3367bb5..a6a423e86 100644 --- a/.github/workflows/ios-actions.yml +++ b/.github/workflows/ios-actions.yml @@ -152,6 +152,11 @@ jobs: xcrun simctl boot "$UDID" time xcrun simctl bootstatus "$UDID" + # The first map load in the app needs locationd, storekitd etc., which take over + # a minute to answer while the simulator is still busy with its first boot + - name: Wait for the simulator to settle + run: "$GITHUB_WORKSPACE/scripts/ci/wait-for-settled-host.sh" + - name: Test with detox if: inputs.E2E_MODE == 'test' run: | diff --git a/scripts/ci/wait-for-settled-host.sh b/scripts/ci/wait-for-settled-host.sh new file mode 100755 index 000000000..272aeab0f --- /dev/null +++ b/scripts/ci/wait-for-settled-host.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# Waits, up to MAX_WAIT seconds, until the 1-minute load average stays below twice the CPU count, +# as a freshly booted simulator renders posters and widgets for minutes and swaps the runner. +set -eu + +MAX_WAIT=${MAX_WAIT:-600} +INTERVAL=10 +SETTLED_SAMPLES=3 + +CPUS=$(sysctl -n hw.ncpu) +LIMIT=$((CPUS * 2)) +START=$(date +%s) +settled=0 + +while :; do + ELAPSED=$(($(date +%s) - START)) + LOAD=$(sysctl -n vm.loadavg | awk '{print $2}') + FREE_MB=$(vm_stat | awk '/Pages free/ {gsub("\\.", "", $3); print int($3 * 16384 / 1048576)}') + SIM_PROCS=$(ps -Ao command | grep -c 'CoreSimulator/Volumes/' || true) + echo "${ELAPSED}s load=${LOAD} free=${FREE_MB}MB simulator_processes=${SIM_PROCS}" + + if awk -v l="$LOAD" -v m="$LIMIT" 'BEGIN { exit !(l < m) }'; then + settled=$((settled + 1)) + if [ "$settled" -ge "$SETTLED_SAMPLES" ]; then + echo "Settled after ${ELAPSED}s (load < ${LIMIT})" + exit 0 + fi + else + settled=0 + fi + + if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then + echo "::warning::Runner still busy after ${ELAPSED}s (load=${LOAD}, limit ${LIMIT}), running the tests anyway" + exit 0 + fi + sleep "$INTERVAL" +done From c33b121da1fd17f6c4911df68db5c3d574af92c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Sun, 8 Nov 2026 19:06:14 +0100 Subject: [PATCH 2/3] ci(ios): boot the Detox simulator before the build, cap the settle wait at 3 min --- .github/workflows/ios-actions.yml | 21 ++++++++++---------- scripts/ci/wait-for-settled-host.sh | 30 +++++++++-------------------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ios-actions.yml b/.github/workflows/ios-actions.yml index a6a423e86..2193d2534 100644 --- a/.github/workflows/ios-actions.yml +++ b/.github/workflows/ios-actions.yml @@ -67,6 +67,14 @@ jobs: - name: Checkout uses: actions/checkout@v4 + # A new simulator renders posters and widgets for over 10 minutes after its first boot, starving + # the app under test, so boot the one Detox uses now and let that overlap with the build + - name: Boot simulator + run: | + UDID=$("$GITHUB_WORKSPACE/scripts/ci/pick-ios-simulator.sh") + echo "DETOX_DEVICE_UDID=$UDID" >> "$GITHUB_ENV" + xcrun simctl boot "$UDID" + # Fork PRs get no secrets; fall back to the public rotated CI token (see rnmapbox/ci-config) - name: Access Token run: | @@ -145,17 +153,10 @@ jobs: xcodebuild -version set -o pipefail && NSUnbufferedIO=YES yarn detox build --configuration "$DETOX_CONFIGURATION" 2>&1 - - name: Boot simulator + - name: Wait for the simulator run: | - UDID=$("$GITHUB_WORKSPACE/scripts/ci/pick-ios-simulator.sh") - echo "DETOX_DEVICE_UDID=$UDID" >> "$GITHUB_ENV" - xcrun simctl boot "$UDID" - time xcrun simctl bootstatus "$UDID" - - # The first map load in the app needs locationd, storekitd etc., which take over - # a minute to answer while the simulator is still busy with its first boot - - name: Wait for the simulator to settle - run: "$GITHUB_WORKSPACE/scripts/ci/wait-for-settled-host.sh" + time xcrun simctl bootstatus "$DETOX_DEVICE_UDID" + "$GITHUB_WORKSPACE/scripts/ci/wait-for-settled-host.sh" - name: Test with detox if: inputs.E2E_MODE == 'test' diff --git a/scripts/ci/wait-for-settled-host.sh b/scripts/ci/wait-for-settled-host.sh index 272aeab0f..f376ee3a1 100755 --- a/scripts/ci/wait-for-settled-host.sh +++ b/scripts/ci/wait-for-settled-host.sh @@ -1,37 +1,25 @@ #!/bin/sh -# Waits, up to MAX_WAIT seconds, until the 1-minute load average stays below twice the CPU count, -# as a freshly booted simulator renders posters and widgets for minutes and swaps the runner. +# Waits up to MAX_WAIT seconds for the 1-minute load average to drop below LOAD_LIMIT; never fails. set -eu -MAX_WAIT=${MAX_WAIT:-600} -INTERVAL=10 -SETTLED_SAMPLES=3 - -CPUS=$(sysctl -n hw.ncpu) -LIMIT=$((CPUS * 2)) +MAX_WAIT=${MAX_WAIT:-180} +LOAD_LIMIT=${LOAD_LIMIT:-$(($(sysctl -n hw.ncpu) * 8))} START=$(date +%s) -settled=0 while :; do ELAPSED=$(($(date +%s) - START)) LOAD=$(sysctl -n vm.loadavg | awk '{print $2}') - FREE_MB=$(vm_stat | awk '/Pages free/ {gsub("\\.", "", $3); print int($3 * 16384 / 1048576)}') + FREE_MB=$(vm_stat | awk '/Pages free/ {print int($3 * 16384 / 1048576)}') SIM_PROCS=$(ps -Ao command | grep -c 'CoreSimulator/Volumes/' || true) echo "${ELAPSED}s load=${LOAD} free=${FREE_MB}MB simulator_processes=${SIM_PROCS}" - if awk -v l="$LOAD" -v m="$LIMIT" 'BEGIN { exit !(l < m) }'; then - settled=$((settled + 1)) - if [ "$settled" -ge "$SETTLED_SAMPLES" ]; then - echo "Settled after ${ELAPSED}s (load < ${LIMIT})" - exit 0 - fi - else - settled=0 + if awk -v l="$LOAD" -v m="$LOAD_LIMIT" 'BEGIN { exit !(l < m) }'; then + echo "Settled after ${ELAPSED}s (load < ${LOAD_LIMIT})" + exit 0 fi - if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then - echo "::warning::Runner still busy after ${ELAPSED}s (load=${LOAD}, limit ${LIMIT}), running the tests anyway" + echo "::warning::Runner still busy after ${ELAPSED}s (load=${LOAD}, limit ${LOAD_LIMIT}), running the tests anyway" exit 0 fi - sleep "$INTERVAL" + sleep 10 done From 2b8e261278a61c504f6bc609dc34a506e2a85030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Sun, 8 Nov 2026 20:46:36 +0100 Subject: [PATCH 3/3] ci(ios): drop the post-build settle wait, log one load snapshot instead --- .github/workflows/ios-actions.yml | 2 +- scripts/ci/wait-for-settled-host.sh | 25 ------------------------- 2 files changed, 1 insertion(+), 26 deletions(-) delete mode 100755 scripts/ci/wait-for-settled-host.sh diff --git a/.github/workflows/ios-actions.yml b/.github/workflows/ios-actions.yml index 2193d2534..840dd015a 100644 --- a/.github/workflows/ios-actions.yml +++ b/.github/workflows/ios-actions.yml @@ -156,7 +156,7 @@ jobs: - name: Wait for the simulator run: | time xcrun simctl bootstatus "$DETOX_DEVICE_UDID" - "$GITHUB_WORKSPACE/scripts/ci/wait-for-settled-host.sh" + echo "load: $(sysctl -n vm.loadavg), free: $(vm_stat | awk '/Pages free/ {print int($3 * 16384 / 1048576)}') MB" - name: Test with detox if: inputs.E2E_MODE == 'test' diff --git a/scripts/ci/wait-for-settled-host.sh b/scripts/ci/wait-for-settled-host.sh deleted file mode 100755 index f376ee3a1..000000000 --- a/scripts/ci/wait-for-settled-host.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -# Waits up to MAX_WAIT seconds for the 1-minute load average to drop below LOAD_LIMIT; never fails. -set -eu - -MAX_WAIT=${MAX_WAIT:-180} -LOAD_LIMIT=${LOAD_LIMIT:-$(($(sysctl -n hw.ncpu) * 8))} -START=$(date +%s) - -while :; do - ELAPSED=$(($(date +%s) - START)) - LOAD=$(sysctl -n vm.loadavg | awk '{print $2}') - FREE_MB=$(vm_stat | awk '/Pages free/ {print int($3 * 16384 / 1048576)}') - SIM_PROCS=$(ps -Ao command | grep -c 'CoreSimulator/Volumes/' || true) - echo "${ELAPSED}s load=${LOAD} free=${FREE_MB}MB simulator_processes=${SIM_PROCS}" - - if awk -v l="$LOAD" -v m="$LOAD_LIMIT" 'BEGIN { exit !(l < m) }'; then - echo "Settled after ${ELAPSED}s (load < ${LOAD_LIMIT})" - exit 0 - fi - if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then - echo "::warning::Runner still busy after ${ELAPSED}s (load=${LOAD}, limit ${LOAD_LIMIT}), running the tests anyway" - exit 0 - fi - sleep 10 -done