1+ Absolutely—here’s a complete scripts/run-ios-ui-tests.sh that drops in as-is. It keeps your watchdog, artifacts, PR comment, screenshot decode, etc., but replaces the fragile runtime/device selection with a robust block that won’t abort under set -euo pipefail, and it uses the UDID we just created (no flaky “resolve by name” step). It also emits simulator listings into artifacts for debugging.
2+
13#! /usr/bin/env bash
24# Run Codename One iOS UI tests on the simulator and compare screenshots
35set -euo pipefail
@@ -160,76 +162,96 @@ else
160162 ri_log " Scheme file not found for env injection: $SCHEME_FILE "
161163fi
162164
163- # --- begin: pick latest available iOS runtime + an iPhone device type (JSON-based ) ---
165+ # --- begin: pick latest available iOS runtime + an iPhone device type (robust, non-fatal probing ) ---
164166require_cmd () { command -v " $1 " > /dev/null 2>&1 || { ri_log " FATAL: '$1 ' not found" ; exit 3; }; }
165167require_cmd xcrun
166- require_cmd python3
167168
168- # Allow manual override from env if you want to pin a specific runtime/device on a given runner:
169- # IOS_RUNTIME_ID="com.apple.CoreSimulator.SimRuntime.iOS-18-5"
170- # IOS_DEVICE_TYPE="com.apple.CoreSimulator.SimDeviceType.iPhone-16"
171169RUNTIME_ID=" ${IOS_RUNTIME_ID:- } "
172170DEVICE_TYPE=" ${IOS_DEVICE_TYPE:- } "
171+ RUNTIMES_JSON=" " ; DEVICETYPES_JSON=" "
173172
174- if [ -z " $RUNTIME_ID " ]; then
175- RUNTIME_ID=" $( xcrun simctl list runtimes --json 2> /dev/null | python3 - << 'PY '
173+ # Don’t let set -e kill us while probing/parsing
174+ set +e
175+
176+ # Try JSON first (if python3 exists)
177+ if command -v python3 > /dev/null 2>&1 ; then
178+ RUNTIMES_JSON=" $( xcrun simctl list runtimes --json 2> /dev/null || true) "
179+ DEVICETYPES_JSON=" $( xcrun simctl list devicetypes --json 2> /dev/null || true) "
180+ if [ -z " $RUNTIME_ID " ] && [ -n " $RUNTIMES_JSON " ]; then
181+ RUNTIME_ID=" $( printf ' %s' " $RUNTIMES_JSON " | python3 - << 'PY ' || true
176182import json, sys
177- try:
178- data=json.load(sys.stdin)
179- except Exception:
180- sys.exit(1)
183+ try: data=json.load(sys.stdin)
184+ except: sys.exit(0)
181185c=[]
182186for r in data.get("runtimes", []):
183- # Newer Xcodes use platform == "iOS"; older had name strings. Check both.
184187 plat = r.get("platform") or r.get("name","")
185188 if "iOS" not in str(plat): continue
186189 if not r.get("isAvailable", False): continue
187190 ident = r.get("identifier") or ""
188191 ver = r.get("version") or ""
189- # Turn version into a sortable tuple
190192 parts=[]
191193 for p in str(ver).split("."):
192194 try: parts.append(int(p))
193195 except: parts.append(0)
194196 c.append((parts, ident))
195- if not c:
196- sys.exit(2)
197- c.sort()
198- print(c[-1][1])
197+ if c:
198+ c.sort()
199+ print(c[-1][1])
199200PY
200- ) "
201- fi
202-
203- if [ -z " $RUNTIME_ID " ]; then
204- ri_log " FATAL: No *available* iOS simulator runtime found on this runner"
205- xcrun simctl list runtimes || true
206- exit 3
207- fi
208-
209- if [ -z " $DEVICE_TYPE " ]; then
210- DEVICE_TYPE=" $( xcrun simctl list devicetypes --json 2> /dev/null | python3 - << 'PY '
201+ ) "
202+ fi
203+ if [ -z " $DEVICE_TYPE " ] && [ -n " $DEVICETYPES_JSON " ]; then
204+ DEVICE_TYPE=" $( printf ' %s' " $DEVICETYPES_JSON " | python3 - << 'PY ' || true
211205import json, sys
212- try:
213- data=json.load(sys.stdin)
214- except Exception:
215- sys.exit(1)
216- dts=data.get("devicetypes",[])
217- # prefer newest iPhone names if present, else any iPhone, else first available
218- prefs = ["iPhone 16 Pro Max","iPhone 16 Pro","iPhone 16","iPhone 15 Pro Max","iPhone 15 Pro","iPhone 15","iPhone"]
206+ try: d=json.load(sys.stdin)
207+ except: sys.exit(0)
208+ prefs = ["iPhone 16 Pro Max","iPhone 16 Pro","iPhone 16",
209+ "iPhone 15 Pro Max","iPhone 15 Pro","iPhone 15","iPhone"]
210+ dts = d.get("devicetypes", [])
219211for pref in prefs:
220212 for dt in dts:
221213 if pref in (dt.get("name") or ""):
222214 print(dt.get("identifier","")); sys.exit(0)
223- if dts:
224- print(dts[0].get("identifier","")); sys.exit(0)
225- sys.exit(2)
215+ for dt in dts:
216+ if "iPhone" in (dt.get("name") or ""):
217+ print(dt.get("identifier","")); sys.exit(0)
218+ if dts: print(dts[0].get("identifier",""))
226219PY
227- ) "
220+ ) "
221+ fi
222+ fi
223+
224+ # Fallback to text parsing if needed
225+ if [ -z " $RUNTIME_ID " ]; then
226+ RUNTIME_ID=" $( xcrun simctl list runtimes 2> /dev/null | awk '
227+ $0 ~ /iOS/ && $0 ~ /(Available|installed)/ {
228+ id=$NF; gsub(/[()]/,"",id); last=id
229+ } END { if (last!="") print last }
230+ ' || true) "
231+ fi
232+ if [ -z " $DEVICE_TYPE " ]; then
233+ DEVICE_TYPE=" $( xcrun simctl list devicetypes 2> /dev/null | awk -F ' [()]' '
234+ /iPhone/ && /identifier/ { print $2; found=1; exit }
235+ END { if (!found) print "" }
236+ ' || true) "
228237fi
229238
239+ set -e
240+
241+ # Emit debug to artifacts
242+ if [ -n " ${ARTIFACTS_DIR:- } " ]; then
243+ printf ' %s\n' " ${RUNTIMES_JSON:- } " > " $ARTIFACTS_DIR /sim-runtimes.json" 2> /dev/null || true
244+ printf ' %s\n' " ${DEVICETYPES_JSON:- } " > " $ARTIFACTS_DIR /sim-devicetypes.json" 2> /dev/null || true
245+ xcrun simctl list runtimes > " $ARTIFACTS_DIR /sim-runtimes.txt" 2>&1 || true
246+ xcrun simctl list devicetypes > " $ARTIFACTS_DIR /sim-devicetypes.txt" 2>&1 || true
247+ fi
248+
249+ if [ -z " $RUNTIME_ID " ]; then
250+ ri_log " FATAL: No *available* iOS simulator runtime found on this runner"
251+ exit 3
252+ fi
230253if [ -z " $DEVICE_TYPE " ]; then
231254 ri_log " FATAL: Could not determine an iPhone device type"
232- xcrun simctl list devicetypes || true
233255 exit 3
234256fi
235257
@@ -249,7 +271,7 @@ if ! xcrun simctl bootstatus "$SIM_UDID" -b -t 180; then
249271fi
250272ri_log " Simulator booted: $SIM_UDID "
251273SIM_DESTINATION=" id=$SIM_UDID "
252- # --- end: pick latest available iOS runtime + an iPhone device type (JSON-based ) ---
274+ # --- end: pick latest available iOS runtime + an iPhone device type (robust, non-fatal probing ) ---
253275
254276ri_log " Running UI tests on destination '$SIM_DESTINATION '"
255277
@@ -286,7 +308,7 @@ if [ -n "$AUT_APP" ] && [ -d "$AUT_APP" ]; then
286308 export CN1_AUT_BUNDLE_ID=" $AUT_BUNDLE_ID "
287309 ri_log " Exported CN1_AUT_BUNDLE_ID=$AUT_BUNDLE_ID "
288310 # Inject AUT bundle id into the scheme, if a placeholder exists
289- if [ -f " $SCHEME_FILE " ] && [ -n " $AUT_BUNDLE_ID " ] ; then
311+ if [ -f " $SCHEME_FILE " ]; then
290312 if sed --version > /dev/null 2>&1 ; then
291313 sed -i -e " s|__CN1_AUT_BUNDLE_ID__|$AUT_BUNDLE_ID |g" " $SCHEME_FILE "
292314 else
@@ -296,64 +318,27 @@ if [ -n "$AUT_APP" ] && [ -d "$AUT_APP" ]; then
296318 fi
297319 fi
298320
299- # Resolve a UDID for the chosen destination name (no regex groups to keep BSD awk happy)
300- SIM_NAME=" $( printf ' %s\n' " $SIM_DESTINATION " | sed -n ' s/.*name=\([^,]*\).*/\1/p' ) "
301- SIM_UDID=" $(
302- xcrun simctl list devices available 2> /dev/null | \
303- awk -v name=" $SIM_NAME " '
304- # Match a line that contains the selected device name followed by " ("
305- index($0, name" (") && index($0, "[") {
306- ud=$0
307- sub(/^.*\[/,"",ud) # drop everything up to and including the first ' ['
308- sub(/\].*$/,"",ud) # drop everything after the closing ' ]'
309- if (length(ud)>0) { print ud; exit }
310- }'
311- ) "
312-
313- ri_log " Simulator devices (available):"
314- xcrun simctl list devices available || true
315-
316- if [ -n " $SIM_UDID " ]; then
317- ri_log " Boot status for $SIM_UDID :"
318- xcrun simctl bootstatus " $SIM_UDID " -b || true
319-
320- ri_log " Processes in simulator:"
321- xcrun simctl spawn " $SIM_UDID " launchctl print system | head -n 200 || true
322- fi
323-
324- if [ -n " $SIM_UDID " ]; then
325- xcrun simctl bootstatus " $SIM_UDID " -b || xcrun simctl boot " $SIM_UDID "
326- xcrun simctl install " $SIM_UDID " " $AUT_APP " || true
327- if [ -n " $AUT_BUNDLE_ID " ]; then
328- ri_log " Warm-launching $AUT_BUNDLE_ID "
329- xcrun simctl terminate " $SIM_UDID " " $AUT_BUNDLE_ID " > /dev/null 2>&1 || true
330- xcrun simctl launch " $SIM_UDID " " $AUT_BUNDLE_ID " --args -AppleLocale en_US -AppleLanguages " (en)" || true
331- xcrun simctl io " $SIM_UDID " screenshot " $ARTIFACTS_DIR /pre-xctest.png" || true
332- fi
333-
334- # Start syslog capture for this simulator
335- SIM_SYSLOG=" $ARTIFACTS_DIR /simulator-syslog.txt"
336- ri_log " Capturing simulator syslog at $SIM_SYSLOG "
337- ( xcrun simctl spawn " $SIM_UDID " log stream --style syslog --level debug \
338- || xcrun simctl spawn " $SIM_UDID " log stream --style compact ) > " $SIM_SYSLOG " 2>&1 &
339- SYSLOG_PID=$!
340-
341- # Start video recording
342- RUN_VIDEO=" $ARTIFACTS_DIR /run.mp4"
343- ri_log " Recording simulator video to $RUN_VIDEO "
344- ( xcrun simctl io " $SIM_UDID " recordVideo " $RUN_VIDEO " & echo $! > " $SCREENSHOT_TMP_DIR /video.pid" ) || true
345- VIDEO_PID=" $( cat " $SCREENSHOT_TMP_DIR /video.pid" 2> /dev/null || true) "
346-
347- if [ -n " $AUT_BUNDLE_ID " ] && [ -n " $SIM_UDID " ]; then
348- ri_log " Warm-launching $AUT_BUNDLE_ID "
349- xcrun simctl terminate " $SIM_UDID " " $AUT_BUNDLE_ID " > /dev/null 2>&1 || true
350- LAUNCH_OUT=" $( xcrun simctl launch " $SIM_UDID " " $AUT_BUNDLE_ID " --args -AppleLocale en_US -AppleLanguages " (en)" 2>&1 || true) "
351- ri_log " simctl launch output: $LAUNCH_OUT "
352- ri_log " Simulator screenshot (pre-XCTest)"
353- xcrun simctl io " $SIM_UDID " screenshot " $ARTIFACTS_DIR /pre-xctest.png" || true
354- fi
355- else
356- ri_log " WARN: Could not resolve simulator UDID for '$SIM_NAME '; skipping warm launch"
321+ # Start syslog capture for this simulator
322+ SIM_SYSLOG=" $ARTIFACTS_DIR /simulator-syslog.txt"
323+ ri_log " Capturing simulator syslog at $SIM_SYSLOG "
324+ ( xcrun simctl spawn " $SIM_UDID " log stream --style syslog --level debug \
325+ || xcrun simctl spawn " $SIM_UDID " log stream --style compact ) > " $SIM_SYSLOG " 2>&1 &
326+ SYSLOG_PID=$!
327+
328+ # Start video recording
329+ RUN_VIDEO=" $ARTIFACTS_DIR /run.mp4"
330+ ri_log " Recording simulator video to $RUN_VIDEO "
331+ ( xcrun simctl io " $SIM_UDID " recordVideo " $RUN_VIDEO " & echo $! > " $SCREENSHOT_TMP_DIR /video.pid" ) || true
332+ VIDEO_PID=" $( cat " $SCREENSHOT_TMP_DIR /video.pid" 2> /dev/null || true) "
333+
334+ # Warm-launch for pre-XCTest telemetry
335+ if [ -n " ${AUT_BUNDLE_ID:- } " ]; then
336+ ri_log " Warm-launching $AUT_BUNDLE_ID "
337+ xcrun simctl terminate " $SIM_UDID " " $AUT_BUNDLE_ID " > /dev/null 2>&1 || true
338+ LAUNCH_OUT=" $( xcrun simctl launch " $SIM_UDID " " $AUT_BUNDLE_ID " --args -AppleLocale en_US -AppleLanguages " (en)" 2>&1 || true) "
339+ ri_log " simctl launch output: $LAUNCH_OUT "
340+ ri_log " Simulator screenshot (pre-XCTest)"
341+ xcrun simctl io " $SIM_UDID " screenshot " $ARTIFACTS_DIR /pre-xctest.png" || true
357342 fi
358343fi
359344
@@ -409,10 +394,8 @@ else
409394fi
410395# --- End: xcresult JSON export ---
411396
412-
413-
414397ri_log " Final simulator screenshot"
415- xcrun simctl io booted screenshot " $ARTIFACTS_DIR /final.png" || true
398+ xcrun simctl io " $SIM_UDID " screenshot " $ARTIFACTS_DIR /final.png" || true
416399# --- End: Stop video + final screenshots ---
417400
418401set +o pipefail
@@ -572,5 +555,4 @@ if ! cn1ss_post_pr_comment "$COMMENT_FILE" "$SCREENSHOT_PREVIEW_DIR"; then
572555 comment_rc=$?
573556fi
574557
575- exit $comment_rc
576-
558+ exit $comment_rc
0 commit comments