@@ -4,6 +4,48 @@ set -euo pipefail
44
55ri_log () { echo " [run-ios-ui-tests] $1 " ; }
66
7+ # --- begin: global cleanup/watchdog helpers ---
8+ VIDEO_PID=" "
9+ SYSLOG_PID=" "
10+ SIM_UDID_CREATED=" "
11+
12+ cleanup () {
13+ # Stop recorders
14+ [ -n " $VIDEO_PID " ] && kill " $VIDEO_PID " > /dev/null 2>&1 || true
15+ [ -n " $SYSLOG_PID " ] && kill " $SYSLOG_PID " > /dev/null 2>&1 || true
16+ # Shutdown and delete the temp simulator we created (if any)
17+ if [ -n " $SIM_UDID_CREATED " ]; then
18+ xcrun simctl shutdown " $SIM_UDID_CREATED " > /dev/null 2>&1 || true
19+ xcrun simctl delete " $SIM_UDID_CREATED " > /dev/null 2>&1 || true
20+ fi
21+ }
22+ trap cleanup EXIT
23+
24+ run_with_timeout () {
25+ # run_with_timeout <seconds> <cmd...>
26+ local t=" $1 " ; shift
27+ local log=" ${ARTIFACTS_DIR:- .} /xcodebuild-live.log"
28+ ( " $@ " 2>&1 | tee -a " $log " ) & # background xcodebuild
29+ local child=$!
30+ local waited=0
31+ while kill -0 " $child " > /dev/null 2>&1 ; do
32+ sleep 5
33+ waited=$(( waited+ 5 ))
34+ # heartbeat so CI doesn’t think we're idle
35+ if (( waited % 60 == 0 )) ; then echo " [run-ios-ui-tests] heartbeat: ${waited} s" ; fi
36+ if (( waited >= t )) ; then
37+ echo " [run-ios-ui-tests] WATCHDOG: Killing long-running process (>${t} s)"
38+ kill -TERM " $child " > /dev/null 2>&1 || true
39+ sleep 2
40+ kill -KILL " $child " > /dev/null 2>&1 || true
41+ wait " $child " || true
42+ return 124
43+ fi
44+ done
45+ wait " $child "
46+ }
47+ # --- end: global cleanup/watchdog helpers ---
48+
749ensure_dir () { mkdir -p " $1 " 2> /dev/null || true ; }
850
951if [ $# -lt 1 ]; then
@@ -123,72 +165,30 @@ SDKROOT_OS="${SDKROOT#iphonesimulator}"
123165DESIRED_OS_MAJOR=" ${SDKROOT_OS%% .* } "
124166DESIRED_OS_MINOR=" ${SDKROOT_OS#* .} " ; [ " $DESIRED_OS_MINOR " = " $SDKROOT_OS " ] && DESIRED_OS_MINOR=" "
125167
126- auto_select_destination () {
127- # 1) Try xcodebuild -showdestinations, but skip placeholder ids
128- if command -v xcodebuild > /dev/null 2>&1 ; then
129- sel=" $(
130- xcodebuild -workspace " $WORKSPACE_PATH " -scheme " $SCHEME " -showdestinations 2> /dev/null |
131- awk -v wantMajor=" ${DESIRED_OS_MAJOR:- } " -v wantMinor=" ${DESIRED_OS_MINOR:- } " '
132- function is_uuid(s) { return match(s, /^[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}$/) }
133- /platform:iOS Simulator/ && /name:/ && /id:/ {
134- os=""; name=""; id="";
135- for (i=1;i<=NF;i++) {
136- if ($i ~ /^OS:/) { sub(/^OS:/,"",$i); os=$i }
137- if ($i ~ /^name:/) { sub(/^name:/,"",$i); name=$i }
138- if ($i ~ /^id:/) { sub(/^id:/,"",$i); id=$i }
139- }
140- if (!is_uuid(id)) next; # skip placeholders
141- gsub(/[^0-9.]/,"",os) # keep 18.5 form if present
142- pri=(name ~ /iPhone/)?2:((name ~ /iPad/)?1:0)
143- # preference score: major-match first, then minor proximity if same major
144- split(os, p, "."); major=p[1]; minor=p[2]
145- major_ok = (wantMajor=="" || major==wantMajor) ? 1 : 0
146- minor_pen = (wantMinor=="" || major!=wantMajor) ? 999 : (minor=="" ? 500 : (minor<wantMinor ? wantMinor-minor : minor-wantMinor))
147- printf("%d|%d|%03d|%s|%s\n", pri, major_ok, minor_pen, name, id)
148- }
149- ' | sort -t' |' -k2,2nr -k1,1nr -k3,3n | head -n1 | awk -F' |' ' {print "platform=iOS Simulator,id="$5}'
150- ) "
151- [ -n " $sel " ] && { echo " $sel " ; return ; }
152- fi
153-
154- # 2) Fallback: simctl list devices available (plain text)
155- if command -v xcrun > /dev/null 2>&1 ; then
156- sel=" $(
157- xcrun simctl list devices available 2> /dev/null |
158- awk -v wantMajor=" ${DESIRED_OS_MAJOR:- } " -v wantMinor=" ${DESIRED_OS_MINOR:- } " '
159- # Example: "iPhone 16e (18.5) [UDID] (Available)"
160- /\[/ && /\)/ {
161- line=$0
162- name=line; sub(/ *\(.*/,"",name); sub(/^ +/,"",name)
163- os=""; if (match(line, /\(([0-9.]+)\)/, a)) os=a[1]
164- udid=""; if (match(line, /\[([0-9A-Fa-f-]+)\]/, b)) udid=b[1]
165- if (udid=="") next
166- pri=(name ~ /iPhone/)?2:((name ~ /iPad/)?1:0)
167- split(os, p, "."); major=p[1]; minor=p[2]
168- major_ok = (wantMajor=="" || major==wantMajor) ? 1 : 0
169- minor_pen = (wantMinor=="" || major!=wantMajor) ? 999 : (minor=="" ? 500 : (minor<wantMinor ? wantMinor-minor : minor-wantMinor))
170- printf("%d|%d|%03d|%s|%s\n", pri, major_ok, minor_pen, name, udid)
171- }
172- ' | sort -t' |' -k2,2nr -k1,1nr -k3,3n | head -n1 | awk -F' |' ' {print "platform=iOS Simulator,id="$5}'
173- ) "
174- [ -n " $sel " ] && { echo " $sel " ; return ; }
175- fi
176- }
177-
178- SIM_DESTINATION=" ${IOS_SIM_DESTINATION:- } "
179- if [ -z " $SIM_DESTINATION " ]; then
180- SELECTED_DESTINATION=" $( auto_select_destination || true) "
181- if [ -n " ${SELECTED_DESTINATION:- } " ]; then
182- SIM_DESTINATION=" $SELECTED_DESTINATION "
183- ri_log " Auto-selected simulator destination '$SIM_DESTINATION '"
184- else
185- ri_log " Simulator auto-selection did not return a destination"
186- fi
168+ # Determine an iOS 18 runtime and create a throwaway iPhone 16 device
169+ RUNTIME_ID=" $( xcrun simctl list runtimes | awk ' /iOS 18\./ && $0 ~ /Available/ {print $NF; exit}' ) "
170+ if [ -z " $RUNTIME_ID " ]; then
171+ ri_log " FATAL: No iOS 18.x simulator runtime available"
172+ exit 3
173+ fi
174+ DEVICE_TYPE=" com.apple.CoreSimulator.SimDeviceType.iPhone-16"
175+ SIM_NAME=" CN1 UI Test iPhone"
176+ SIM_UDID=" $( xcrun simctl create " $SIM_NAME " " $DEVICE_TYPE " " $RUNTIME_ID " 2> /dev/null || true) "
177+ if [ -z " $SIM_UDID " ]; then
178+ ri_log " FATAL: Failed to create simulator ($DEVICE_TYPE , $RUNTIME_ID )"
179+ exit 3
187180fi
188- if [ -z " $SIM_DESTINATION " ]; then
189- SIM_DESTINATION=" platform=iOS Simulator,name=iPhone 16,OS=latest"
190- ri_log " Falling back to default simulator destination '$SIM_DESTINATION '"
181+ SIM_UDID_CREATED=" $SIM_UDID "
182+ ri_log " Created simulator $SIM_NAME ($SIM_UDID ) with runtime $RUNTIME_ID "
183+
184+ # Boot it and wait until it's ready
185+ xcrun simctl boot " $SIM_UDID " > /dev/null 2>&1 || true
186+ if ! xcrun simctl bootstatus " $SIM_UDID " -b -t 180; then
187+ ri_log " FATAL: Simulator never reached booted state"
188+ exit 4
191189fi
190+ ri_log " Simulator booted: $SIM_UDID "
191+ SIM_DESTINATION=" id=$SIM_UDID "
192192
193193ri_log " Running UI tests on destination '$SIM_DESTINATION '"
194194
@@ -263,17 +263,25 @@ if [ -n "$AUT_APP" ] && [ -d "$AUT_APP" ]; then
263263 if [ -n " $SIM_UDID " ]; then
264264 xcrun simctl bootstatus " $SIM_UDID " -b || xcrun simctl boot " $SIM_UDID "
265265 xcrun simctl install " $SIM_UDID " " $AUT_APP " || true
266+ if [ -n " $AUT_BUNDLE_ID " ]; then
267+ ri_log " Warm-launching $AUT_BUNDLE_ID "
268+ xcrun simctl terminate " $SIM_UDID " " $AUT_BUNDLE_ID " > /dev/null 2>&1 || true
269+ xcrun simctl launch " $SIM_UDID " " $AUT_BUNDLE_ID " --args -AppleLocale en_US -AppleLanguages " (en)" || true
270+ xcrun simctl io " $SIM_UDID " screenshot " $ARTIFACTS_DIR /pre-xctest.png" || true
271+ fi
266272
267- # Now that a device is definitely booted, start syslog and video
273+ # Start syslog capture for this simulator
268274 SIM_SYSLOG=" $ARTIFACTS_DIR /simulator-syslog.txt"
269275 ri_log " Capturing simulator syslog at $SIM_SYSLOG "
270- (xcrun simctl spawn " $SIM_UDID " log stream --style syslog --level debug \
271- || xcrun simctl spawn " $SIM_UDID " log stream --style compact) > " $SIM_SYSLOG " 2>&1 &
276+ ( xcrun simctl spawn " $SIM_UDID " log stream --style syslog --level debug \
277+ || xcrun simctl spawn " $SIM_UDID " log stream --style compact ) > " $SIM_SYSLOG " 2>&1 &
272278 SYSLOG_PID=$!
273279
280+ # Start video recording
274281 RUN_VIDEO=" $ARTIFACTS_DIR /run.mp4"
275282 ri_log " Recording simulator video to $RUN_VIDEO "
276283 ( xcrun simctl io " $SIM_UDID " recordVideo " $RUN_VIDEO " & echo $! > " $SCREENSHOT_TMP_DIR /video.pid" ) || true
284+ VIDEO_PID=" $( cat " $SCREENSHOT_TMP_DIR /video.pid" 2> /dev/null || true) "
277285
278286 if [ -n " $AUT_BUNDLE_ID " ] && [ -n " $SIM_UDID " ]; then
279287 ri_log " Warm-launching $AUT_BUNDLE_ID "
@@ -302,8 +310,9 @@ XCODE_TEST_FILTERS=(
302310 -skip-testing:HelloCodenameOneTests
303311)
304312
313+ ri_log " STAGE:TEST -> xcodebuild test-without-building (destination=$SIM_DESTINATION )"
305314set -o pipefail
306- if ! xcodebuild \
315+ if ! run_with_timeout 1500 xcodebuild \
307316 -workspace " $WORKSPACE_PATH " \
308317 -scheme " $SCHEME " \
309318 -sdk iphonesimulator \
@@ -314,10 +323,17 @@ if ! xcodebuild \
314323 " ${XCODE_TEST_FILTERS[@]} " \
315324 CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \
316325 GENERATE_INFOPLIST_FILE=YES \
326+ -maximum-test-execution-time-allowance 1200 \
317327 test-without-building | tee " $TEST_LOG " ; then
318- ri_log " STAGE:XCODE_TEST_FAILED -> See $TEST_LOG "
328+ rc=$?
329+ if [ " $rc " = " 124" ]; then
330+ ri_log " STAGE:WATCHDOG_TRIGGERED -> Killed stalled xcodebuild"
331+ else
332+ ri_log " STAGE:XCODE_TEST_FAILED -> See $TEST_LOG "
333+ fi
319334 exit 10
320335fi
336+ set +o pipefail
321337
322338# --- Begin: Stop video + final screenshots ---
323339if [ -f " $SCREENSHOT_TMP_DIR /video.pid" ]; then
0 commit comments