preflite 1.1.0 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ UDID="${1:-}"
5
+ WDA_PORT="${2:-}"
6
+
7
+ EXIT_MISSING_ARGS=1
8
+ EXIT_DEVICE_NOT_CONNECTED=2
9
+ EXIT_WDA_START_FAILED=3
10
+ EXIT_DEPENDENCY_MISSING=4
11
+
12
+ if [[ -z "${UDID}" || -z "${WDA_PORT}" ]]; then
13
+ echo "[start-ios-wda] usage: $0 <UDID> <WDA_PORT>"
14
+ exit "${EXIT_MISSING_ARGS}"
15
+ fi
16
+
17
+ if ! [[ "${WDA_PORT}" =~ ^[0-9]+$ ]]; then
18
+ echo "[start-ios-wda] invalid WDA_PORT: ${WDA_PORT}"
19
+ exit "${EXIT_MISSING_ARGS}"
20
+ fi
21
+
22
+ # Local MJPEG port: matches Agent / Midscene convention, default = WDA local port + 1000 → device:9100 (WDA video stream)
23
+ MJPEG_LOCAL_PORT=$((WDA_PORT + 1000))
24
+ if [[ "${MJPEG_LOCAL_PORT}" -gt 65535 ]]; then
25
+ echo "[start-ios-wda] WDA_PORT+1000 exceeds max port: ${WDA_PORT} -> ${MJPEG_LOCAL_PORT}"
26
+ exit "${EXIT_MISSING_ARGS}"
27
+ fi
28
+
29
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
31
+ AGENT_HOME="${AGENT_HOME:-${HOME}/.preflight}"
32
+
33
+ WDA_ROOT="${WDA_PROJECT_ROOT:-${REPO_ROOT}/third_party/WebDriverAgent}"
34
+ WDA_PROJECT="${WDA_PROJECT_PATH:-${WDA_ROOT}/WebDriverAgent.xcodeproj}"
35
+ WDA_SCHEME="${WDA_SCHEME:-WebDriverAgentRunner}"
36
+ WDA_DERIVED_DATA="${WDA_DERIVED_DATA:-${AGENT_HOME}/state/wda-derived-data/${UDID}}"
37
+
38
+ WDA_LOG_DIR="${WDA_LOG_DIR:-${AGENT_HOME}/logs/wda}"
39
+ WDA_LOG_FILE="${WDA_LOG_DIR}/wda-${UDID}-${WDA_PORT}.log"
40
+ IPROXY_LOG_FILE="${WDA_LOG_DIR}/iproxy-${UDID}-${WDA_PORT}.log"
41
+
42
+ WDA_STATUS_URL="http://127.0.0.1:${WDA_PORT}/status"
43
+
44
+ mkdir -p "${WDA_LOG_DIR}" "${WDA_DERIVED_DATA}"
45
+
46
+ # Lock file to prevent concurrent script instances (the watchdog polls every 5s,
47
+ # but build-for-testing can take minutes). Uses PID-based stale detection.
48
+ BUILD_LOCK_DIR="${AGENT_HOME}/state/wda-locks"
49
+ mkdir -p "${BUILD_LOCK_DIR}"
50
+ BUILD_LOCK="${BUILD_LOCK_DIR}/wda-start-${UDID}-${WDA_PORT}.pid"
51
+
52
+ try_acquire_lock() {
53
+ if [[ -f "${BUILD_LOCK}" ]]; then
54
+ local old_pid
55
+ old_pid=$(cat "${BUILD_LOCK}" 2>/dev/null)
56
+ if [[ -n "${old_pid}" ]] && kill -0 "${old_pid}" 2>/dev/null; then
57
+ echo "[start-ios-wda] another instance already running (pid=${old_pid}), skipping"
58
+ return 1
59
+ fi
60
+ echo "[start-ios-wda] removing stale lock from pid=${old_pid}"
61
+ rm -f "${BUILD_LOCK}"
62
+ fi
63
+ echo "$$" > "${BUILD_LOCK}"
64
+ trap 'rm -f "${BUILD_LOCK}"' EXIT
65
+ return 0
66
+ }
67
+
68
+ if [[ ! -d "${WDA_PROJECT}" ]]; then
69
+ echo "[start-ios-wda] WDA project not found: ${WDA_PROJECT}"
70
+ echo "[start-ios-wda] run: npm run clone:wda"
71
+ exit 1
72
+ fi
73
+
74
+ for cmd in xcrun curl iproxy xcodebuild; do
75
+ if ! command -v "${cmd}" >/dev/null 2>&1; then
76
+ echo "[start-ios-wda] missing dependency: ${cmd}"
77
+ if [[ "${cmd}" == "iproxy" ]]; then
78
+ echo "[start-ios-wda] install it with: brew install libimobiledevice"
79
+ fi
80
+ exit "${EXIT_DEPENDENCY_MISSING}"
81
+ fi
82
+ done
83
+
84
+ device_is_online() {
85
+ # Check physical devices first (== Devices ==)
86
+ xcrun xctrace list devices 2>/dev/null | awk '
87
+ /^== Devices ==/ { in_devices=1; next }
88
+ /^== / { in_devices=0 }
89
+ in_devices { print }
90
+ ' | grep -F "(${UDID})" >/dev/null 2>&1 && return 0
91
+
92
+ # Then check booted simulators
93
+ xcrun simctl list devices booted 2>/dev/null | grep -F "(${UDID})" >/dev/null 2>&1
94
+ }
95
+
96
+ device_is_simulator() {
97
+ xcrun simctl list devices booted 2>/dev/null | grep -F "(${UDID})" >/dev/null 2>&1
98
+ }
99
+
100
+ wda_is_healthy() {
101
+ curl -fsS --max-time 2 "${WDA_STATUS_URL}" >/dev/null 2>&1
102
+ }
103
+
104
+ kill_iproxy_for_udid() {
105
+ pkill -f "iproxy.*-u[[:space:]]+${UDID}" >/dev/null 2>&1 || true
106
+ pkill -f "iproxy.*${UDID}" >/dev/null 2>&1 || true
107
+ }
108
+
109
+ kill_xcodebuild_for_udid() {
110
+ pkill -f "xcodebuild.*id=${UDID}" >/dev/null 2>&1 || true
111
+ }
112
+
113
+ xcodebuild_is_running_for_udid() {
114
+ pgrep -f "xcodebuild.*id=${UDID}" >/dev/null 2>&1
115
+ }
116
+
117
+ start_iproxy() {
118
+ if device_is_simulator; then
119
+ echo "[start-ios-wda] simulator mode, WDA binds directly to port ${WDA_PORT} (USE_PORT) — no forwarding needed"
120
+ return 0
121
+ fi
122
+ echo "[start-ios-wda] starting iproxy: 127.0.0.1:${WDA_PORT}->device:8100, 127.0.0.1:${MJPEG_LOCAL_PORT}->device:9100 (MJPEG)"
123
+ nohup iproxy -u "${UDID}" "${WDA_PORT}:8100" "${MJPEG_LOCAL_PORT}:9100" >>"${IPROXY_LOG_FILE}" 2>&1 &
124
+ }
125
+
126
+ # Modify .xctestrun plist to inject environment variables into the test process.
127
+ # This is the only reliable way to set env vars (USE_PORT, MJPEG_SERVER_PORT)
128
+ # for simulator test runners, because testmanagerd does NOT forward macOS env vars
129
+ # into the simulator's iOS runtime.
130
+ # Find the test-bundle key (top-level key excluding __xctestrun_metadata__) in xctestrun.
131
+ # Recent Xcode uses flat <bundleName> as key instead of TestConfigurations array.
132
+ get_xctestrun_bundle_key() {
133
+ local xctestrun="$1"
134
+ plutil -p "${xctestrun}" 2>/dev/null | grep '^ "' | grep -v '__xctestrun_metadata__' | head -1 | sed 's/^ "\(.*\)" =>.*/\1/'
135
+ }
136
+
137
+ inject_env_vars_into_xctestrun() {
138
+ local xctestrun="$1"
139
+ local port="$2"
140
+ local mjpeg_port="$3"
141
+ local bundle_key
142
+
143
+ bundle_key=$(get_xctestrun_bundle_key "${xctestrun}")
144
+ if [[ -z "${bundle_key}" ]]; then
145
+ echo "[start-ios-wda] could not determine bundle key in xctestrun"
146
+ return 1
147
+ fi
148
+ echo "[start-ios-wda] xctestrun bundle key: ${bundle_key}"
149
+
150
+ # Method 1: Inject USE_PORT / MJPEG_SERVER_PORT into EnvironmentVariables dict
151
+ # This is consumed by FBConfiguration's NSProcessInfo.processInfo.environment check.
152
+ local env_path="${bundle_key}.EnvironmentVariables.USE_PORT"
153
+ echo "[start-ios-wda] injecting USE_PORT=${port} at ${env_path}"
154
+ if plutil -insert "${env_path}" -string "${port}" "${xctestrun}" 2>/dev/null; then
155
+ echo "[start-ios-wda] injected USE_PORT"
156
+ else
157
+ # Key may already exist — replace instead
158
+ plutil -replace "${env_path}" -string "${port}" "${xctestrun}" 2>/dev/null && \
159
+ echo "[start-ios-wda] replaced USE_PORT" || \
160
+ echo "[start-ios-wda] WARNING: could not set USE_PORT"
161
+ fi
162
+
163
+ plutil -insert "${bundle_key}.EnvironmentVariables.MJPEG_SERVER_PORT" \
164
+ -string "${mjpeg_port}" "${xctestrun}" 2>/dev/null || \
165
+ plutil -replace "${bundle_key}.EnvironmentVariables.MJPEG_SERVER_PORT" \
166
+ -string "${mjpeg_port}" "${xctestrun}" 2>/dev/null || true
167
+
168
+ # Method 2: Inject --port into CommandLineArguments (HIGHEST priority in FBConfiguration:
169
+ # bindingPortRangeFromArguments reads NSProcessInfo.processInfo.arguments for "--port").
170
+ local cla_path="${bundle_key}.CommandLineArguments"
171
+ echo "[start-ios-wda] injecting --port ${port} into CommandLineArguments"
172
+ if plutil -replace "${cla_path}" \
173
+ -json "[\"--port\",\"${port}\",\"--mjpeg-server-port\",\"${mjpeg_port}\"]" \
174
+ "${xctestrun}" 2>/dev/null; then
175
+ echo "[start-ios-wda] injected --port via CommandLineArguments"
176
+ else
177
+ echo "[start-ios-wda] WARNING: could not set CommandLineArguments"
178
+ fi
179
+ }
180
+
181
+ start_xcodebuild() {
182
+ echo "[start-ios-wda] starting xcodebuild for WDA"
183
+
184
+ if device_is_simulator; then
185
+ echo "[start-ios-wda] simulator mode: using build-for-testing + xctestrun approach"
186
+ echo "[start-ios-wda] (macOS env vars do NOT propagate into simulator test processes)"
187
+
188
+ # Step 1: Build the test bundle and generate .xctestrun (blocking)
189
+ xcodebuild build-for-testing \
190
+ -project "${WDA_PROJECT}" \
191
+ -scheme "${WDA_SCHEME}" \
192
+ -destination "id=${UDID}" \
193
+ -derivedDataPath "${WDA_DERIVED_DATA}" \
194
+ >>"${WDA_LOG_FILE}" 2>&1
195
+
196
+ BUILD_RESULT=$?
197
+ if [[ "${BUILD_RESULT}" -ne 0 ]]; then
198
+ echo "[start-ios-wda] build-for-testing failed (exit=${BUILD_RESULT})"
199
+ echo "[start-ios-wda] check the log for details: ${WDA_LOG_FILE}"
200
+ return 1
201
+ fi
202
+
203
+ # Step 2: Find the .xctestrun file in derived data
204
+ XCTESTRUN=$(find "${WDA_DERIVED_DATA}" -name "*.xctestrun" -print -quit 2>/dev/null)
205
+ if [[ -z "${XCTESTRUN}" ]]; then
206
+ echo "[start-ios-wda] .xctestrun not found in ${WDA_DERIVED_DATA}"
207
+ echo "[start-ios-wda] falling back to env-var approach (may not work)"
208
+ USE_PORT="${WDA_PORT}" MJPEG_SERVER_PORT="${MJPEG_LOCAL_PORT}" \
209
+ nohup xcodebuild \
210
+ -project "${WDA_PROJECT}" \
211
+ -scheme "${WDA_SCHEME}" \
212
+ -destination "id=${UDID}" \
213
+ test >>"${WDA_LOG_FILE}" 2>&1 &
214
+ else
215
+ # Step 3: Inject environment variables into the xctestrun plist
216
+ inject_env_vars_into_xctestrun "${XCTESTRUN}" "${WDA_PORT}" "${MJPEG_LOCAL_PORT}"
217
+
218
+ echo "[start-ios-wda] modified xctestrun: ${XCTESTRUN}"
219
+
220
+ # Step 4: Run with modified xctestrun (skip rebuild, background)
221
+ nohup xcodebuild test-without-building \
222
+ -xctestrun "${XCTESTRUN}" \
223
+ -destination "id=${UDID}" \
224
+ -derivedDataPath "${WDA_DERIVED_DATA}" \
225
+ >>"${WDA_LOG_FILE}" 2>&1 &
226
+ fi
227
+ else
228
+ echo "[start-ios-wda] real-device mode: iproxy handles port forwarding, starting xcodebuild"
229
+ nohup xcodebuild \
230
+ -project "${WDA_PROJECT}" \
231
+ -scheme "${WDA_SCHEME}" \
232
+ -destination "id=${UDID}" \
233
+ test >>"${WDA_LOG_FILE}" 2>&1 &
234
+ fi
235
+
236
+ XCODEBUILD_PID=$!
237
+ }
238
+
239
+ wait_for_wda() {
240
+ local timeout_seconds="${1:-90}"
241
+
242
+ echo "[start-ios-wda] waiting for WDA status: ${WDA_STATUS_URL}"
243
+
244
+ for _ in $(seq 1 "${timeout_seconds}"); do
245
+ if ! device_is_online; then
246
+ echo "[start-ios-wda] device disconnected while starting: ${UDID}"
247
+ exit "${EXIT_DEVICE_NOT_CONNECTED}"
248
+ fi
249
+
250
+ if wda_is_healthy; then
251
+ echo "[start-ios-wda] wda ready: ${WDA_STATUS_URL}"
252
+ echo "[start-ios-wda] mjpeg_url=http://127.0.0.1:${MJPEG_LOCAL_PORT}/ (local -> device:9100)"
253
+ echo "[start-ios-wda] wda_log=${WDA_LOG_FILE}"
254
+ echo "[start-ios-wda] iproxy_log=${IPROXY_LOG_FILE}"
255
+ return 0
256
+ fi
257
+
258
+ sleep 1
259
+ done
260
+
261
+ return 1
262
+ }
263
+
264
+ echo "[start-ios-wda] checking device online: ${UDID}"
265
+
266
+ if ! device_is_online; then
267
+ echo "[start-ios-wda] device not connected or not online: ${UDID}"
268
+ echo "[start-ios-wda] only devices under '== Devices ==' are considered online"
269
+ exit "${EXIT_DEVICE_NOT_CONNECTED}"
270
+ fi
271
+
272
+ # Acquire exclusive lock to prevent concurrent watchdog instances from racing.
273
+ if ! try_acquire_lock; then
274
+ exit 0
275
+ fi
276
+
277
+ # Case 1: requested local port is already healthy.
278
+ if wda_is_healthy; then
279
+ echo "[start-ios-wda] wda already healthy on requested port: ${WDA_STATUS_URL}"
280
+ exit 0
281
+ fi
282
+
283
+ # Case 2: status is unhealthy. Check whether xcodebuild is already running.
284
+ if xcodebuild_is_running_for_udid; then
285
+ echo "[start-ios-wda] xcodebuild already running for udid=${UDID}, waiting for WDA on port ${WDA_PORT}"
286
+
287
+ if wait_for_wda 60; then
288
+ echo "[start-ios-wda] WDA became healthy on port ${WDA_PORT}"
289
+ exit 0
290
+ fi
291
+
292
+ # xcodebuild is running but WDA not healthy after waiting.
293
+ # Could be still building, or WDA is on a different port.
294
+ # Don't kill xcodebuild — let the next poll cycle retry.
295
+ echo "[start-ios-wda] xcodebuild running but WDA not healthy on ${WDA_PORT} after 60s, will retry"
296
+ exit 1
297
+ fi
298
+
299
+ # Case 3: no running xcodebuild — start fresh.
300
+ echo "[start-ios-wda] no existing xcodebuild for udid, starting xcodebuild"
301
+ if ! start_xcodebuild; then
302
+ echo "[start-ios-wda] start_xcodebuild failed, aborting"
303
+ echo "[start-ios-wda] check the log: ${WDA_LOG_FILE}"
304
+ tail -n 40 "${WDA_LOG_FILE}" 2>/dev/null || true
305
+ exit "${EXIT_WDA_START_FAILED}"
306
+ fi
307
+ start_iproxy
308
+
309
+ if wait_for_wda 90; then
310
+ echo "[start-ios-wda] started udid=${UDID} wda_local=${WDA_PORT} mjpeg_local=${MJPEG_LOCAL_PORT}"
311
+ if [[ -n "${XCODEBUILD_PID:-}" ]]; then
312
+ echo "[start-ios-wda] xcodebuild_pid=${XCODEBUILD_PID}"
313
+ fi
314
+ exit 0
315
+ fi
316
+
317
+ echo "[start-ios-wda] failed to start WDA within timeout"
318
+ echo "[start-ios-wda] status_url=${WDA_STATUS_URL}"
319
+ echo "[start-ios-wda] wda_log=${WDA_LOG_FILE}"
320
+ echo "[start-ios-wda] iproxy_log=${IPROXY_LOG_FILE}"
321
+
322
+ echo "[start-ios-wda] last WDA logs:"
323
+ tail -n 80 "${WDA_LOG_FILE}" 2>/dev/null || true
324
+
325
+ echo "[start-ios-wda] last iproxy logs:"
326
+ tail -n 40 "${IPROXY_LOG_FILE}" 2>/dev/null || true
327
+
328
+ exit "${EXIT_WDA_START_FAILED}"
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ stop_all_by_name() {
5
+ local process_name="$1"
6
+ local -a pids=()
7
+ local -a remaining=()
8
+
9
+ # macOS built-in bash (3.2) has no mapfile; collect pids via read loop.
10
+ while IFS= read -r pid; do
11
+ if [[ -n "${pid}" ]]; then
12
+ pids+=("${pid}")
13
+ fi
14
+ done < <(pgrep -x "${process_name}" || true)
15
+
16
+ if [[ "${#pids[@]}" -eq 0 ]]; then
17
+ echo "[stop-ios-wda] no ${process_name} process found"
18
+ return 0
19
+ fi
20
+
21
+ echo "[stop-ios-wda] stopping ${process_name} pids: ${pids[*]}"
22
+ kill "${pids[@]}" 2>/dev/null || true
23
+ sleep 1
24
+
25
+ for pid in "${pids[@]}"; do
26
+ if kill -0 "${pid}" >/dev/null 2>&1; then
27
+ remaining+=("${pid}")
28
+ fi
29
+ done
30
+
31
+ if [[ "${#remaining[@]}" -gt 0 ]]; then
32
+ echo "[stop-ios-wda] force killing ${process_name} pids: ${remaining[*]}"
33
+ kill -9 "${remaining[@]}" 2>/dev/null || true
34
+ fi
35
+
36
+ echo "[stop-ios-wda] ${process_name} cleaned"
37
+ }
38
+
39
+ echo "[stop-ios-wda] cleanup started"
40
+ stop_all_by_name "iproxy"
41
+ stop_all_by_name "xcodebuild"
42
+ # Clean up simulator WDA Node.js TCP forwarding (pattern: connect(8100, '127.0.0.1'))
43
+ pkill -f "node.*connect.8100.*127.0.0.1" 2>/dev/null || true
44
+ echo "[stop-ios-wda] cleanup finished"