Skip to content
221 changes: 187 additions & 34 deletions scripts/run-ios-ui-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,33 @@ if ! command -v xcrun >/dev/null 2>&1; then
exit 3
fi

HOST_ARCH="$(uname -m)"
ARM64_AVAILABLE=0
if [ "$HOST_ARCH" = "x86_64" ]; then
ARM64_AVAILABLE="$(sysctl -n hw.optional.arm64 2>/dev/null || echo 0)"
fi
declare -a XCODE_ARCH_PREFIX=()
if [ "$HOST_ARCH" = "x86_64" ] && [ "$ARM64_AVAILABLE" = "1" ]; then
ri_log "Detected Rosetta shell; running Xcode tools under arm64"
XCODE_ARCH_PREFIX=(arch -arm64)
fi

xcodebuild_cmd() {
if [ ${#XCODE_ARCH_PREFIX[@]} -gt 0 ]; then
"${XCODE_ARCH_PREFIX[@]}" xcodebuild "$@"
else
xcodebuild "$@"
fi
}

xcrun_cmd() {
if [ ${#XCODE_ARCH_PREFIX[@]} -gt 0 ]; then
"${XCODE_ARCH_PREFIX[@]}" xcrun "$@"
else
xcrun "$@"
fi
}

JAVA17_BIN="$JAVA17_HOME/bin/java"

cn1ss_setup "$JAVA17_BIN" "$CN1SS_HELPER_SOURCE_DIR"
Expand Down Expand Up @@ -165,10 +192,35 @@ normalize_destination() {
printf '%s\n' "$joined"
}

strip_id_from_destination() {
local raw="$1"
IFS=',' read -r -a parts <<< "$raw"
local components=() part key value
for part in "${parts[@]}"; do
part="$(trim_whitespace "$part")"
key="${part%%=*}"
value="${part#*=}"
key="$(trim_whitespace "$key")"
if [ "$key" = "id" ]; then
continue
fi
components+=("$part")
done
local joined=""
for part in "${components[@]}"; do
if [ -z "$joined" ]; then
joined="$part"
else
joined+=",$part"
fi
done
printf '%s\n' "$joined"
}

auto_select_destination() {
local show_dest rc=0 best_line="" best_key="" line payload platform id name os priority key part value
set +e
show_dest="$(xcodebuild -workspace "$WORKSPACE_PATH" -scheme "$SCHEME" -sdk iphonesimulator -showdestinations 2>/dev/null)"
show_dest="$(xcodebuild_cmd -workspace "$WORKSPACE_PATH" -scheme "$SCHEME" -sdk iphonesimulator -showdestinations 2>/dev/null)"
rc=$?
set -e

Expand Down Expand Up @@ -237,6 +289,34 @@ auto_select_destination() {
return $rc
}

auto_select_destination_retry() {
local attempt=1
local selected=""
while [ "$attempt" -le 3 ]; do
selected="$(auto_select_destination || true)"
if [ -n "$selected" ]; then
printf '%s\n' "$selected"
return 0
fi
ri_log "Auto-select attempt $attempt did not return a destination; retrying" >&2
sleep 5
attempt=$((attempt + 1))
done
return 1
}

destination_visible_to_xcodebuild() {
local destination="$1"
local id=""
id="$(printf '%s\n' "$destination" | sed -n 's/.*id=\([^,]*\).*/\1/p' | tr -d '\r[:space:]')"
[ -z "$id" ] && return 1
set +e
xcodebuild_cmd -workspace "$WORKSPACE_PATH" -scheme "$SCHEME" -sdk iphonesimulator -showdestinations 2>/dev/null | grep -q "$id"
local rc=$?
set -e
return $rc
}

fallback_sim_destination() {
if ! command -v xcrun >/dev/null 2>&1; then
return
Expand Down Expand Up @@ -304,7 +384,7 @@ fallback_sim_destination() {
[ -n "$current_version" ] && best_line="$best_line,OS=$current_version"
best_line="$best_line,name=$name"
fi
done < <(xcrun simctl list devices 2>/dev/null)
done < <(xcrun_cmd simctl list devices 2>/dev/null)

if [ -n "$best_line" ]; then
printf '%s\n' "$best_line"
Expand All @@ -315,7 +395,7 @@ fallback_sim_destination() {

SIM_DESTINATION="${IOS_SIM_DESTINATION:-}"
if [ -z "$SIM_DESTINATION" ]; then
SELECTED_DESTINATION="$(auto_select_destination || true)"
SELECTED_DESTINATION="$(auto_select_destination_retry || true)"
if [ -n "${SELECTED_DESTINATION:-}" ]; then
SIM_DESTINATION="$SELECTED_DESTINATION"
ri_log "Auto-selected simulator destination '$SIM_DESTINATION'"
Expand All @@ -328,24 +408,47 @@ if [ -z "$SIM_DESTINATION" ]; then
if [ -n "${FALLBACK_DESTINATION:-}" ]; then
SIM_DESTINATION="$FALLBACK_DESTINATION"
ri_log "Using fallback simulator destination '$SIM_DESTINATION'"
BUILD_DESTINATION="$(strip_id_from_destination "$SIM_DESTINATION")"
else
SIM_DESTINATION="platform=iOS Simulator,name=iPhone 16"
ri_log "Falling back to default simulator destination '$SIM_DESTINATION'"
BUILD_DESTINATION="$SIM_DESTINATION"
fi
fi

SIM_DESTINATION="$(normalize_destination "$SIM_DESTINATION")"
if [ -z "${BUILD_DESTINATION:-}" ]; then
BUILD_DESTINATION="$SIM_DESTINATION"
fi

# Extract UDID and prefer id-only destination to avoid OS/SDK mismatches
# Extract UDID and prefer platform+id destination for xcodebuild stability
SIM_UDID="$(printf '%s\n' "$SIM_DESTINATION" | sed -n 's/.*id=\([^,]*\).*/\1/p' | tr -d '\r[:space:]')"
if [ -n "$SIM_UDID" ]; then
ri_log "Booting simulator $SIM_UDID"
BOOT_START=$(date +%s)
xcrun simctl boot "$SIM_UDID" >/dev/null 2>&1 || true
xcrun simctl bootstatus "$SIM_UDID" -b
xcrun_cmd simctl boot "$SIM_UDID" >/dev/null 2>&1 || true
xcrun_cmd simctl bootstatus "$SIM_UDID" -b
BOOT_END=$(date +%s)
echo "Simulator Boot : $(( (BOOT_END - BOOT_START) * 1000 )) ms" >> "$ARTIFACTS_DIR/ios-test-stats.txt"
SIM_DESTINATION="id=$SIM_UDID"
SIM_DESTINATION="platform=iOS Simulator,id=$SIM_UDID"
BUILD_DESTINATION="$SIM_DESTINATION"
fi
if ! destination_visible_to_xcodebuild "$SIM_DESTINATION"; then
ri_log "Selected simulator destination not visible to xcodebuild; attempting re-selection"
SELECTED_DESTINATION="$(auto_select_destination_retry || true)"
if [ -n "${SELECTED_DESTINATION:-}" ]; then
SIM_DESTINATION="$(normalize_destination "$SELECTED_DESTINATION")"
SIM_UDID="$(printf '%s\n' "$SIM_DESTINATION" | sed -n 's/.*id=\([^,]*\).*/\1/p' | tr -d '\r[:space:]')"
if [ -n "$SIM_UDID" ]; then
SIM_DESTINATION="platform=iOS Simulator,id=$SIM_UDID"
fi
BUILD_DESTINATION="$SIM_DESTINATION"
ri_log "Re-selected simulator destination '$SIM_DESTINATION'"
else
ri_log "Auto-selection still did not return a destination; continuing with '$SIM_DESTINATION'"
BUILD_DESTINATION="$(strip_id_from_destination "$SIM_DESTINATION")"
ri_log "Using name-based simulator destination for build '$BUILD_DESTINATION'"
fi
fi
ri_log "Running DeviceRunner on destination '$SIM_DESTINATION'"

Expand All @@ -355,23 +458,73 @@ BUILD_LOG="$ARTIFACTS_DIR/xcodebuild-build.log"

ri_log "Building simulator app with xcodebuild"
COMPILE_START=$(date +%s)
if ! xcodebuild \
-workspace "$WORKSPACE_PATH" \
-scheme "$SCHEME" \
-sdk iphonesimulator \
-configuration Debug \
-destination "$SIM_DESTINATION" \
-destination-timeout 120 \
-derivedDataPath "$DERIVED_DATA_DIR" \
build | tee "$BUILD_LOG"; then
ri_log "STAGE:XCODE_BUILD_FAILED -> See $BUILD_LOG"
exit 10
build_with_destination() {
local dest="$1"
local args=(
-workspace "$WORKSPACE_PATH"
-scheme "$SCHEME"
-sdk iphonesimulator
-configuration Debug
-derivedDataPath "$DERIVED_DATA_DIR"
build
)
if [ -n "$dest" ]; then
args=(
-workspace "$WORKSPACE_PATH"
-scheme "$SCHEME"
-sdk iphonesimulator
-configuration Debug
-destination "$dest"
-destination-timeout 120
-derivedDataPath "$DERIVED_DATA_DIR"
build
)
fi
xcodebuild_cmd "${args[@]}" | tee "$BUILD_LOG"
}

if ! build_with_destination "$BUILD_DESTINATION"; then
if grep -q "Unable to find a destination matching the provided destination specifier" "$BUILD_LOG"; then
ri_log "xcodebuild could not find destination; retrying after re-selection"
SELECTED_DESTINATION="$(auto_select_destination_retry || true)"
if [ -n "${SELECTED_DESTINATION:-}" ]; then
SIM_DESTINATION="$(normalize_destination "$SELECTED_DESTINATION")"
SIM_UDID="$(printf '%s\n' "$SIM_DESTINATION" | sed -n 's/.*id=\([^,]*\).*/\1/p' | tr -d '\r[:space:]')"
if [ -n "$SIM_UDID" ]; then
SIM_DESTINATION="platform=iOS Simulator,id=$SIM_UDID"
fi
BUILD_DESTINATION="$SIM_DESTINATION"
ri_log "Retrying xcodebuild with destination '$BUILD_DESTINATION'"
if ! build_with_destination "$BUILD_DESTINATION"; then
ri_log "Retrying xcodebuild with name-based simulator destination"
if ! build_with_destination "$(strip_id_from_destination "$SIM_DESTINATION")"; then
ri_log "STAGE:XCODE_BUILD_FAILED -> See $BUILD_LOG"
exit 10
fi
fi
else
ri_log "Retrying xcodebuild with name-based simulator destination"
if ! build_with_destination "$(strip_id_from_destination "$SIM_DESTINATION")"; then
ri_log "STAGE:XCODE_BUILD_FAILED -> See $BUILD_LOG"
exit 10
fi
fi
elif grep -q "Found no destinations for the scheme" "$BUILD_LOG"; then
ri_log "xcodebuild found no destinations; retrying with name-based simulator destination"
if ! build_with_destination "$(strip_id_from_destination "$SIM_DESTINATION")"; then
ri_log "STAGE:XCODE_BUILD_FAILED -> See $BUILD_LOG"
exit 10
fi
else
ri_log "STAGE:XCODE_BUILD_FAILED -> See $BUILD_LOG"
exit 10
fi
fi
COMPILE_END=$(date +%s)
COMPILATION_TIME=$((COMPILE_END - COMPILE_START))
ri_log "Compilation time: ${COMPILATION_TIME}s"

BUILD_SETTINGS="$(xcodebuild -workspace "$WORKSPACE_PATH" -scheme "$SCHEME" -sdk iphonesimulator -configuration Debug -showBuildSettings 2>/dev/null || true)"
BUILD_SETTINGS="$(xcodebuild_cmd -workspace "$WORKSPACE_PATH" -scheme "$SCHEME" -sdk iphonesimulator -configuration Debug -showBuildSettings 2>/dev/null || true)"
TARGET_BUILD_DIR="$(printf '%s\n' "$BUILD_SETTINGS" | awk -F' = ' '/ TARGET_BUILD_DIR /{print $2; exit}')"
WRAPPER_NAME="$(printf '%s\n' "$BUILD_SETTINGS" | awk -F' = ' '/ WRAPPER_NAME /{print $2; exit}')"
if [ -z "$WRAPPER_NAME" ]; then
Expand Down Expand Up @@ -436,21 +589,21 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}"
resolved_id="$(trim_whitespace "$id_part")"
break
fi
done < <(xcrun simctl list devices 2>/dev/null)
done < <(xcrun_cmd simctl list devices 2>/dev/null)
SIM_DEVICE_ID="$resolved_id"
fi
fi

if [ -n "$SIM_DEVICE_ID" ]; then
ri_log "Booting simulator $SIM_DEVICE_ID"
BOOT_START=$(date +%s)
xcrun simctl boot "$SIM_DEVICE_ID" >/dev/null 2>&1 || true
xcrun simctl bootstatus "$SIM_DEVICE_ID" -b
xcrun_cmd simctl boot "$SIM_DEVICE_ID" >/dev/null 2>&1 || true
xcrun_cmd simctl bootstatus "$SIM_DEVICE_ID" -b
BOOT_END=$(date +%s)
echo "Simulator Boot (Run) : $(( (BOOT_END - BOOT_START) * 1000 )) ms" >> "$ARTIFACTS_DIR/ios-test-stats.txt"
else
ri_log "Warning: simulator UDID not resolved; relying on default booted device"
xcrun simctl bootstatus booted -b || true
xcrun_cmd simctl bootstatus booted -b || true
fi

LOG_STREAM_PID=0
Expand All @@ -460,22 +613,22 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}"
wait "$LOG_STREAM_PID" 2>/dev/null || true
fi
if [ -n "$SIM_DEVICE_ID" ] && [ -n "$BUNDLE_IDENTIFIER" ]; then
xcrun simctl terminate "$SIM_DEVICE_ID" "$BUNDLE_IDENTIFIER" >/dev/null 2>&1 || true
xcrun_cmd simctl terminate "$SIM_DEVICE_ID" "$BUNDLE_IDENTIFIER" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT

ri_log "Streaming simulator logs to $TEST_LOG"
if [ -n "$SIM_DEVICE_ID" ]; then
xcrun simctl terminate "$SIM_DEVICE_ID" "$BUNDLE_IDENTIFIER" >/dev/null 2>&1 || true
xcrun simctl uninstall "$SIM_DEVICE_ID" "$BUNDLE_IDENTIFIER" >/dev/null 2>&1 || true
xcrun_cmd simctl terminate "$SIM_DEVICE_ID" "$BUNDLE_IDENTIFIER" >/dev/null 2>&1 || true
xcrun_cmd simctl uninstall "$SIM_DEVICE_ID" "$BUNDLE_IDENTIFIER" >/dev/null 2>&1 || true

xcrun simctl spawn "$SIM_DEVICE_ID" \
xcrun_cmd simctl spawn "$SIM_DEVICE_ID" \
log stream --style json --level debug \
--predicate 'eventMessage CONTAINS "CN1SS"' \
> "$TEST_LOG" 2>&1 &
else
xcrun simctl spawn booted log stream --style compact --level debug --predicate 'composedMessage CONTAINS "CN1SS"' > "$TEST_LOG" 2>&1 &
xcrun_cmd simctl spawn booted log stream --style compact --level debug --predicate 'composedMessage CONTAINS "CN1SS"' > "$TEST_LOG" 2>&1 &
fi
LOG_STREAM_PID=$!
sleep 2
Expand All @@ -487,7 +640,7 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}"
local attempt=1
while true; do
local output
if output="$(xcrun simctl launch "$target" "$BUNDLE_IDENTIFIER" 2>&1)"; then
if output="$(xcrun_cmd simctl launch "$target" "$BUNDLE_IDENTIFIER" 2>&1)"; then
printf '%s\n' "$output" >> "$LAUNCH_LOG"
return 0
fi
Expand All @@ -496,7 +649,7 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}"
return 1
fi
ri_log "simctl launch failed (attempt $attempt), retrying"
xcrun simctl bootstatus "$target" -b >/dev/null 2>&1 || true
xcrun_cmd simctl bootstatus "$target" -b >/dev/null 2>&1 || true
sleep 5
attempt=$((attempt + 1))
done
Expand All @@ -505,7 +658,7 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}"
ri_log "Installing simulator app bundle"
INSTALL_START=$(date +%s)
if [ -n "$SIM_DEVICE_ID" ]; then
if ! xcrun simctl install "$SIM_DEVICE_ID" "$APP_BUNDLE_PATH"; then
if ! xcrun_cmd simctl install "$SIM_DEVICE_ID" "$APP_BUNDLE_PATH"; then
ri_log "FATAL: simctl install failed"
exit 11
fi
Expand All @@ -518,7 +671,7 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}"
fi
LAUNCH_END=$(date +%s)
else
if ! xcrun simctl install booted "$APP_BUNDLE_PATH"; then
if ! xcrun_cmd simctl install booted "$APP_BUNDLE_PATH"; then
ri_log "FATAL: simctl install failed"
exit 11
fi
Expand Down Expand Up @@ -560,13 +713,13 @@ wait "$LOG_STREAM_PID" 2>/dev/null || true
LOG_STREAM_PID=0

FALLBACK_LOG="$ARTIFACTS_DIR/device-runner-fallback.log"
xcrun simctl spawn "$SIM_DEVICE_ID" \
xcrun_cmd simctl spawn "$SIM_DEVICE_ID" \
log show --style syslog --last 30m \
--predicate 'eventMessage CONTAINS "CN1SS"' \
> "$FALLBACK_LOG" 2>/dev/null || true

if [ -n "$SIM_DEVICE_ID" ]; then
xcrun simctl terminate "$SIM_DEVICE_ID" "$BUNDLE_IDENTIFIER" >/dev/null 2>&1 || true
xcrun_cmd simctl terminate "$SIM_DEVICE_ID" "$BUNDLE_IDENTIFIER" >/dev/null 2>&1 || true
fi

declare -a CN1SS_SOURCES=("SIMLOG:$TEST_LOG")
Expand Down
Loading