device-devtools-mcp 0.1.0
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.
- package/AGENTS.md +327 -0
- package/LICENSE +21 -0
- package/README.md +491 -0
- package/VERSION +1 -0
- package/bin/device-devtools-mcp.js +106 -0
- package/bin/devicetools +315 -0
- package/config.example.json +63 -0
- package/integrations/agent-pointer.sh +48 -0
- package/integrations/claude/SKILL.md +8 -0
- package/integrations/cursor/devicetools.mdc +8 -0
- package/integrations/gemini/GEMINI.md +5 -0
- package/integrations/mcp/README.md +184 -0
- package/integrations/mcp/mcp.json +10 -0
- package/integrations/mcp/reference.sh +119 -0
- package/integrations/mcp/selftest.sh +158 -0
- package/integrations/mcp/server.sh +343 -0
- package/package.json +50 -0
- package/scripts/android/app.sh +241 -0
- package/scripts/android/back.sh +73 -0
- package/scripts/android/controls.sh +56 -0
- package/scripts/android/devices.sh +77 -0
- package/scripts/android/doctor.sh +253 -0
- package/scripts/android/lib.sh +699 -0
- package/scripts/android/logs.sh +289 -0
- package/scripts/android/permission.sh +119 -0
- package/scripts/android/settings.sh +63 -0
- package/scripts/android/setup.sh +97 -0
- package/scripts/android/tree.awk +166 -0
- package/scripts/android/tree.sh +127 -0
- package/scripts/android/type.sh +191 -0
- package/scripts/common/find.sh +95 -0
- package/scripts/common/key.sh +65 -0
- package/scripts/common/lib.sh +14 -0
- package/scripts/common/measure.sh +170 -0
- package/scripts/common/open.sh +131 -0
- package/scripts/common/screenshot.sh +102 -0
- package/scripts/common/scroll.sh +177 -0
- package/scripts/common/snapshot.sh +69 -0
- package/scripts/common/swipe.sh +171 -0
- package/scripts/common/tap.sh +226 -0
- package/scripts/common/wait.sh +212 -0
- package/scripts/common/waypoint.sh +141 -0
- package/scripts/dispatch.sh +21 -0
- package/scripts/flow.sh +266 -0
- package/scripts/init.sh +101 -0
- package/scripts/ios/app.sh +404 -0
- package/scripts/ios/back.sh +95 -0
- package/scripts/ios/controls.sh +68 -0
- package/scripts/ios/devices.sh +80 -0
- package/scripts/ios/doctor.sh +386 -0
- package/scripts/ios/lib.sh +864 -0
- package/scripts/ios/logs.sh +272 -0
- package/scripts/ios/permission.sh +108 -0
- package/scripts/ios/settings.sh +76 -0
- package/scripts/ios/setup.sh +175 -0
- package/scripts/ios/tree.sh +128 -0
- package/scripts/ios/type.sh +178 -0
- package/scripts/lib.sh +1032 -0
- package/scripts/links.tsv +44 -0
- package/scripts/relink.sh +121 -0
- package/scripts/run.sh +415 -0
- package/scripts/selftest.sh +1709 -0
- package/scripts/snapshot.awk +362 -0
- package/scripts/verify-npm-package.js +133 -0
- package/tests/fixtures/ios-contacts-list.expected +52 -0
- package/tests/fixtures/ios-contacts-list.rows +140 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# doctor.sh — verify (and cheaply repair) everything the other scripts assume.
|
|
3
|
+
#
|
|
4
|
+
# Run this before the first action of a session, and again whenever any script
|
|
5
|
+
# exits 3. Output is one fixed line per check, always the same checks in the same
|
|
6
|
+
# order, so it can be parsed positionally:
|
|
7
|
+
#
|
|
8
|
+
# <STATUS> <name> — <detail>
|
|
9
|
+
#
|
|
10
|
+
# STATUS is OK, WARN or FAIL. Only FAIL affects the exit code.
|
|
11
|
+
# exit 0 every required check passed
|
|
12
|
+
# exit 2 at least one required check failed
|
|
13
|
+
#
|
|
14
|
+
# Repairs: iproxy is started when missing, always, because it is cheap, local
|
|
15
|
+
# and idempotent. WebDriverAgent is started only with --start-wda, because that
|
|
16
|
+
# is a long-running xcodebuild against the device and starting it implicitly
|
|
17
|
+
# would hide the reason it stopped.
|
|
18
|
+
#
|
|
19
|
+
# --start-wda start the WebDriverAgent runner if it is not answering
|
|
20
|
+
# --recover the same, spelled the way every other script spells it when
|
|
21
|
+
# it tells you to run this
|
|
22
|
+
#
|
|
23
|
+
# The runner does die on its own — twice during a single day of development —
|
|
24
|
+
# so a caller with no human attached needs a way to get back on its feet. This
|
|
25
|
+
# is that way, and it is opt-in rather than automatic.
|
|
26
|
+
|
|
27
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
28
|
+
|
|
29
|
+
START_WDA=0
|
|
30
|
+
while [ $# -gt 0 ]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
--start-wda) START_WDA=1; shift ;;
|
|
33
|
+
# --recover is the platform-neutral spelling, so that any caller can ask any
|
|
34
|
+
# adapter to put itself right without knowing what "right" involves here.
|
|
35
|
+
--recover) START_WDA=1; shift ;;
|
|
36
|
+
-h|--help) awk 'NR > 1 { if (!/^#/) exit; sub(/^# ?/, ""); print }' "${BASH_SOURCE[0]}"; exit 0 ;;
|
|
37
|
+
*) die "unknown argument: $1 (usage: doctor.sh [--start-wda|--recover])" 1 ;;
|
|
38
|
+
esac
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
FAILED=0
|
|
42
|
+
|
|
43
|
+
# How long a freshly started WebDriverAgent runner is given to answer. See the
|
|
44
|
+
# loop that uses it for why it is no longer ninety seconds.
|
|
45
|
+
WDA_START_SECONDS=240
|
|
46
|
+
|
|
47
|
+
# The status word is the whole point of these nine lines, so it is the only
|
|
48
|
+
# thing tinted. The check names stay plain: they are a fixed list in a fixed
|
|
49
|
+
# order and colouring them would suggest one matters more than another.
|
|
50
|
+
say() {
|
|
51
|
+
local col=""
|
|
52
|
+
case "$1" in OK) col="$C_PASS" ;; WARN) col="$C_NOTE" ;; FAIL) col="$C_FAIL" ;; esac
|
|
53
|
+
printf '%s%s%s %s — %s\n' "$col" "$1" "${col:+$C_OFF}" "$2" "$3"
|
|
54
|
+
[ "$1" = FAIL ] && FAILED=1
|
|
55
|
+
return 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Why the runner started and never answered, read out of xcodebuild's own log.
|
|
59
|
+
# One line, cause first and remedy second, because the caller may be a fleet
|
|
60
|
+
# worker with nobody watching and this line is all it will report.
|
|
61
|
+
#
|
|
62
|
+
# The order is most-specific first: a signing failure also prints a launch
|
|
63
|
+
# failure underneath it, and naming the outer symptom would send someone to the
|
|
64
|
+
# wrong place.
|
|
65
|
+
wda_start_failure() {
|
|
66
|
+
local log; log="$(state_dir)/wda.log"
|
|
67
|
+
[ -f "$log" ] || { echo "started the runner but it never answered"; return; }
|
|
68
|
+
|
|
69
|
+
if grep -q "Timed out while enabling automation mode" "$log"; then
|
|
70
|
+
echo "the device refused automation mode — turn on Settings > Developer > Enable UI Automation on this device, and leave the screen unlocked"
|
|
71
|
+
elif grep -qE "ApplicationVerificationFailed|valid provisioning profile|Failed to codesign|code signature" "$log"; then
|
|
72
|
+
echo "the runner is not signed for this device — register $UDID in team $(cfg '.wda.team_id' '?') and rebuild WebDriverAgent"
|
|
73
|
+
elif grep -q "Missing test product" "$log"; then
|
|
74
|
+
echo "the .xctestrun points at a test bundle that is not beside it — rebuild WebDriverAgent"
|
|
75
|
+
elif grep -qE "device is locked|passcode|Unlock the device" "$log"; then
|
|
76
|
+
echo "the device is locked — unlock it and leave it awake"
|
|
77
|
+
elif grep -qE "Unable to (install|launch)|Failed to install" "$log"; then
|
|
78
|
+
echo "the runner could not be installed on the device"
|
|
79
|
+
else
|
|
80
|
+
# Unknown, so quote what xcodebuild actually said rather than inventing a
|
|
81
|
+
# cause. Truncated because this has to stay one line.
|
|
82
|
+
local raw
|
|
83
|
+
raw="$(grep -m1 -E "^Testing failed:|encountered an error" "$log" 2>/dev/null \
|
|
84
|
+
| tr -s ' \t' ' ' | sed 's/^ *//' | head -c 160)"
|
|
85
|
+
echo "started the runner but it never answered${raw:+ — $raw}"
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# --- 1. xcode -----------------------------------------------------------------
|
|
90
|
+
if ! command -v xcodebuild >/dev/null 2>&1; then
|
|
91
|
+
say FAIL xcode "xcodebuild not found — install Xcode and run xcode-select --switch"
|
|
92
|
+
else
|
|
93
|
+
say OK xcode "$(xcodebuild -version 2>/dev/null | tr '\n' ' ' | sed 's/ *$//') at $(xcode-select -p)"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# --- 2. jq --------------------------------------------------------------------
|
|
97
|
+
if command -v jq >/dev/null 2>&1; then
|
|
98
|
+
say OK jq "$(jq --version)"
|
|
99
|
+
else
|
|
100
|
+
say FAIL jq "not found — brew install jq"
|
|
101
|
+
exit 2 # every later check needs jq to read the config
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
load_config
|
|
105
|
+
UDID="$(cfg '.device.udid')"
|
|
106
|
+
WANT_IOS="$(cfg '.device.ios_version' '')"
|
|
107
|
+
|
|
108
|
+
KIND="$(ios_kind)"
|
|
109
|
+
|
|
110
|
+
# --- 3. device ----------------------------------------------------------------
|
|
111
|
+
#
|
|
112
|
+
# The check names and their order never change between a simulator and a real
|
|
113
|
+
# device, because AGENTS.md promises callers a fixed nine lines. What changes is
|
|
114
|
+
# what each one means, and the detail says which.
|
|
115
|
+
DEVICE_OK=0
|
|
116
|
+
if [ "$KIND" = simulator ]; then
|
|
117
|
+
if ! xcrun simctl list devices -j 2>/dev/null | jq -e --arg u "$UDID" \
|
|
118
|
+
'[.devices[][] | select(.udid == $u)] | length > 0' >/dev/null 2>&1; then
|
|
119
|
+
say FAIL device "simulator $UDID not found — xcrun simctl list devices"
|
|
120
|
+
elif ! sim_booted; then
|
|
121
|
+
say FAIL device "simulator $UDID is not booted — xcrun simctl boot $UDID"
|
|
122
|
+
else
|
|
123
|
+
simname="$(xcrun simctl list devices -j 2>/dev/null | jq -r --arg u "$UDID" \
|
|
124
|
+
'[.devices | to_entries[] | .key as $rt | .value[] | select(.udid == $u) | "\(.name) (\($rt | sub(".*SimRuntime.";"") | gsub("-";" ")))"][0] // "?"')"
|
|
125
|
+
DEVICE_OK=1
|
|
126
|
+
say OK device "simulator $simname ($UDID)"
|
|
127
|
+
fi
|
|
128
|
+
elif ! command -v idevice_id >/dev/null 2>&1; then
|
|
129
|
+
say FAIL device "idevice_id not found — brew install libimobiledevice"
|
|
130
|
+
elif ! idevice_id -l 2>/dev/null | grep -qx "$UDID"; then
|
|
131
|
+
attached="$(idevice_id -l 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')"
|
|
132
|
+
say FAIL device "$UDID not attached — attached: ${attached:-none}"
|
|
133
|
+
else
|
|
134
|
+
got_ios="$(ideviceinfo -u "$UDID" -k ProductVersion 2>/dev/null || echo '?')"
|
|
135
|
+
got_name="$(ideviceinfo -u "$UDID" -k DeviceName 2>/dev/null || echo '?')"
|
|
136
|
+
DEVICE_OK=1
|
|
137
|
+
if [ -n "$WANT_IOS" ] && [ "$got_ios" != "$WANT_IOS" ]; then
|
|
138
|
+
say WARN device "$got_name iOS $got_ios attached, config says $WANT_IOS"
|
|
139
|
+
else
|
|
140
|
+
say OK device "$got_name iOS $got_ios ($UDID)"
|
|
141
|
+
fi
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# --- 4. developer disk image --------------------------------------------------
|
|
145
|
+
# Required to launch an XCUITest runner. Unmounts on every device reboot, and
|
|
146
|
+
# Xcode remounts it only when it drives the device itself, so this check catches
|
|
147
|
+
# the single most common "WDA suddenly will not start" cause.
|
|
148
|
+
if [ "$KIND" = simulator ]; then
|
|
149
|
+
say OK ddi "not applicable — a simulator needs no developer disk image"
|
|
150
|
+
elif [ "$DEVICE_OK" -eq 0 ]; then
|
|
151
|
+
say FAIL ddi "skipped — device check failed"
|
|
152
|
+
elif ! command -v ideviceimagemounter >/dev/null 2>&1; then
|
|
153
|
+
say FAIL ddi "ideviceimagemounter not found — brew install libimobiledevice"
|
|
154
|
+
else
|
|
155
|
+
# COUNT THE SIGNATURES, DO NOT READ THE HIGHEST INDEX.
|
|
156
|
+
#
|
|
157
|
+
# The first version took the number out of `ImageSignature[0]:` and required
|
|
158
|
+
# it to be greater than zero. On an iOS 15 device several images are listed,
|
|
159
|
+
# so an index of 1 or more happens to appear and the check passed by luck. An
|
|
160
|
+
# iOS 27 device mounts exactly one, at index 0, and the check read the single
|
|
161
|
+
# mounted image as none — reporting a perfectly healthy phone as broken.
|
|
162
|
+
n="$(ideviceimagemounter -u "$UDID" list 2>/dev/null | grep -c '^ImageSignature\[')"
|
|
163
|
+
if [ "${n:-0}" -gt 0 ] 2>/dev/null; then
|
|
164
|
+
say OK ddi "developer disk image mounted ($n signature(s))"
|
|
165
|
+
else
|
|
166
|
+
say FAIL ddi "no developer disk image mounted — see README.md 'Developer disk image'"
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# --- 5. iproxy ----------------------------------------------------------------
|
|
171
|
+
LPORT="$(cfg '.wda.local_port' '8100')"
|
|
172
|
+
DPORT="$(cfg '.wda.device_port' '8100')"
|
|
173
|
+
IPROXY_OK=0
|
|
174
|
+
if [ "$KIND" = simulator ]; then
|
|
175
|
+
# A simulator shares the host's network stack, so WebDriverAgent is already on
|
|
176
|
+
# localhost and there is nothing to forward.
|
|
177
|
+
say OK iproxy "not needed — a simulator listens on the host directly"
|
|
178
|
+
IPROXY_OK=1
|
|
179
|
+
elif port_open "$LPORT"; then
|
|
180
|
+
say OK iproxy "already forwarding ${LPORT}->${DPORT}"
|
|
181
|
+
IPROXY_OK=1
|
|
182
|
+
elif [ "$DEVICE_OK" -eq 0 ]; then
|
|
183
|
+
say FAIL iproxy "skipped — device check failed"
|
|
184
|
+
elif ! command -v iproxy >/dev/null 2>&1; then
|
|
185
|
+
say FAIL iproxy "iproxy not found — brew install libimobiledevice"
|
|
186
|
+
else
|
|
187
|
+
sd="$(state_dir)"
|
|
188
|
+
nohup iproxy -u "$UDID" "$LPORT:$DPORT" >"$sd/iproxy.log" 2>&1 &
|
|
189
|
+
echo $! >"$sd/iproxy.pid"
|
|
190
|
+
for _ in $(seq 1 25); do
|
|
191
|
+
port_open "$LPORT" && break
|
|
192
|
+
sleep 0.2
|
|
193
|
+
done
|
|
194
|
+
if port_open "$LPORT"; then
|
|
195
|
+
say OK iproxy "started ${LPORT}->${DPORT} (pid $(cat "$sd/iproxy.pid"))"
|
|
196
|
+
IPROXY_OK=1
|
|
197
|
+
else
|
|
198
|
+
say FAIL iproxy "failed to bind $LPORT — see $sd/iproxy.log"
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# --- 6. WebDriverAgent HTTP ---------------------------------------------------
|
|
203
|
+
WDA_OK=0
|
|
204
|
+
if [ "$IPROXY_OK" -eq 0 ]; then
|
|
205
|
+
say FAIL wda "skipped — no port forward"
|
|
206
|
+
else
|
|
207
|
+
status="$(curl -sS -m "$(http_timeout)" "$(wda_base)/status" 2>/dev/null || true)"
|
|
208
|
+
|
|
209
|
+
if { [ -z "$status" ] || ! printf '%s' "$status" | jq -e '.value' >/dev/null 2>&1; } \
|
|
210
|
+
&& [ "$START_WDA" -eq 1 ]; then
|
|
211
|
+
xctestrun="$(ls "$(wda_derived)"/Build/Products/*.xctestrun 2>/dev/null | head -1 || true)"
|
|
212
|
+
if [ -z "$xctestrun" ]; then
|
|
213
|
+
say FAIL wda "no .xctestrun in $(wda_derived) — WebDriverAgent has not been built for this target; run: scripts/setup.sh"
|
|
214
|
+
else
|
|
215
|
+
sd="$(state_dir)"
|
|
216
|
+
|
|
217
|
+
# ON A SIMULATOR THE PORT IS WDA'S OWN, NOT A TUNNEL'S.
|
|
218
|
+
#
|
|
219
|
+
# A physical device is reached through iproxy: WebDriverAgent always
|
|
220
|
+
# listens on 8100 on the device, and the host side picks whatever local
|
|
221
|
+
# port it likes. Nothing has to be told anything.
|
|
222
|
+
#
|
|
223
|
+
# A simulator listens on the host directly, so the port in the config is
|
|
224
|
+
# the port WDA itself must bind — and the only way to tell it is USE_PORT
|
|
225
|
+
# in the runner's environment, which for xcodebuild means the .xctestrun
|
|
226
|
+
# rather than this shell. Without this, every simulator would bind 8100
|
|
227
|
+
# and the second one would lose, silently, to a fleet that then drove the
|
|
228
|
+
# first simulator twice.
|
|
229
|
+
#
|
|
230
|
+
# MJPEG_SERVER_PORT gets the same treatment for the same reason; WDA
|
|
231
|
+
# defaults it to 9100 and two of them would collide.
|
|
232
|
+
#
|
|
233
|
+
# The modified copy is written BESIDE THE ORIGINAL, not into the state
|
|
234
|
+
# directory. Every path inside an .xctestrun is relative to __TESTROOT__,
|
|
235
|
+
# which xcodebuild resolves to the directory the file is sitting in — move
|
|
236
|
+
# the file and it looks for the test bundle next to the copy, does not
|
|
237
|
+
# find it, and fails with "Missing test product" naming a directory that
|
|
238
|
+
# was never going to contain one. Measured, once.
|
|
239
|
+
if [ "$KIND" = simulator ]; then
|
|
240
|
+
port_xctestrun="$(dirname "$xctestrun")/devicetools-$LPORT.xctestrun"
|
|
241
|
+
if cp "$xctestrun" "$port_xctestrun" 2>/dev/null \
|
|
242
|
+
&& plutil -replace WebDriverAgentRunner.EnvironmentVariables.USE_PORT \
|
|
243
|
+
-string "$LPORT" "$port_xctestrun" 2>/dev/null \
|
|
244
|
+
&& plutil -replace WebDriverAgentRunner.EnvironmentVariables.MJPEG_SERVER_PORT \
|
|
245
|
+
-string "$(( LPORT + 1000 ))" "$port_xctestrun" 2>/dev/null; then
|
|
246
|
+
xctestrun="$port_xctestrun"
|
|
247
|
+
else
|
|
248
|
+
rm -f "$port_xctestrun"
|
|
249
|
+
fi
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# A RUNNER THAT STOPPED ANSWERING IS STILL A RUNNER.
|
|
253
|
+
#
|
|
254
|
+
# Reaching here means /status did not answer, and --recover is the retry
|
|
255
|
+
# path a caller takes when it loses the connection. It used to start a
|
|
256
|
+
# second xcodebuild and overwrite wda.pid, orphaning the first: two
|
|
257
|
+
# runners driving one simulator, both bound to the same port, answering
|
|
258
|
+
# curl 52 (empty reply) about half the time. Measured — three of them
|
|
259
|
+
# accumulated in one session, and the run they broke looked like an
|
|
260
|
+
# application fault, which is the worst way for this to present.
|
|
261
|
+
#
|
|
262
|
+
# A flaky connection is exactly the condition that calls this repeatedly,
|
|
263
|
+
# so an unbounded supply of runners is what the old code promised.
|
|
264
|
+
if [ -f "$sd/wda.pid" ]; then
|
|
265
|
+
old="$(cat "$sd/wda.pid" 2>/dev/null || true)"
|
|
266
|
+
if [ -n "$old" ] && kill -0 "$old" 2>/dev/null; then
|
|
267
|
+
kill "$old" 2>/dev/null || true
|
|
268
|
+
for _ in 1 2 3 4 5; do kill -0 "$old" 2>/dev/null || break; sleep 1; done
|
|
269
|
+
kill -9 "$old" 2>/dev/null || true
|
|
270
|
+
fi
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
nohup xcodebuild test-without-building -xctestrun "$xctestrun" \
|
|
274
|
+
-destination "id=$UDID" >"$sd/wda.log" 2>&1 &
|
|
275
|
+
echo $! >"$sd/wda.pid"
|
|
276
|
+
# A COLD MACHINE IS NOT A WARM ONE, AND CI IS ALWAYS COLD.
|
|
277
|
+
#
|
|
278
|
+
# Ninety seconds was measured on a laptop whose simulator had been booted
|
|
279
|
+
# for hours. On a GitHub runner the simulator was minted a minute earlier
|
|
280
|
+
# and the runner app is being launched for the first time: one CI run
|
|
281
|
+
# answered inside the budget and the next one did not, on identical code.
|
|
282
|
+
# Nothing waits this long normally — it polls, so a warm start still
|
|
283
|
+
# returns in a second or two, and the number only decides how patient the
|
|
284
|
+
# failure is. The Android adapter learned the same lesson on the same day.
|
|
285
|
+
for _ in $(seq 1 "$WDA_START_SECONDS"); do
|
|
286
|
+
curl -sS -m 3 "$(wda_base)/status" >/dev/null 2>&1 && break
|
|
287
|
+
sleep 1
|
|
288
|
+
done
|
|
289
|
+
status="$(curl -sS -m "$(http_timeout)" "$(wda_base)/status" 2>/dev/null || true)"
|
|
290
|
+
fi
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
if [ -z "$status" ] || ! printf '%s' "$status" | jq -e '.value' >/dev/null 2>&1; then
|
|
294
|
+
if [ "$START_WDA" -eq 1 ]; then
|
|
295
|
+
# THE LOG KNOWS WHY. SAY IT.
|
|
296
|
+
#
|
|
297
|
+
# "see the log" is the answer a tool gives when it has not looked. Every
|
|
298
|
+
# cause below is a fixed string xcodebuild prints, and each one has a
|
|
299
|
+
# different remedy — a per-device setting, a signing problem, and a
|
|
300
|
+
# locked screen are not the same failure and must not read as one.
|
|
301
|
+
#
|
|
302
|
+
# Found by putting a second iPhone on the fleet: the runner installed,
|
|
303
|
+
# launched, and timed out enabling automation mode, and the only thing
|
|
304
|
+
# this line said was to go and read a 400-line xcodebuild log.
|
|
305
|
+
say FAIL wda "$(wda_start_failure) after ${WDA_START_SECONDS}s — see $(state_dir)/wda.log"
|
|
306
|
+
else
|
|
307
|
+
say FAIL wda "no answer on $(wda_base)/status — retry with: $(as_cmd doctor --recover)"
|
|
308
|
+
fi
|
|
309
|
+
else
|
|
310
|
+
ios="$(printf '%s' "$status" | jq -r '.value.os.version // "?"')"
|
|
311
|
+
ver="$(printf '%s' "$status" | jq -r '.value.build.version // .value.build.time // "?"')"
|
|
312
|
+
say OK wda "ready, WDA $ver driving iOS $ios"
|
|
313
|
+
WDA_OK=1
|
|
314
|
+
fi
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
# --- 7. WebDriverAgent session ------------------------------------------------
|
|
318
|
+
# A live HTTP port is not proof that XCUITest can still drive the UI: the runner
|
|
319
|
+
# outlives its test bundle in some failure modes. Creating a real session is.
|
|
320
|
+
if [ "$WDA_OK" -eq 0 ]; then
|
|
321
|
+
say FAIL wda-session "skipped — WDA not answering"
|
|
322
|
+
else
|
|
323
|
+
resp="$(curl -sS -m "$(http_timeout)" -X POST -H 'Content-Type: application/json' \
|
|
324
|
+
-d '{"capabilities":{"alwaysMatch":{}}}' "$(wda_base)/session" 2>/dev/null || true)"
|
|
325
|
+
sid="$(printf '%s' "$resp" | jq -r '.value.sessionId // .sessionId // empty' 2>/dev/null || true)"
|
|
326
|
+
if [ -z "$sid" ]; then
|
|
327
|
+
msg="$(printf '%s' "$resp" | jq -r '.value.error // .value.message // "unknown error"' 2>/dev/null | head -c 120)"
|
|
328
|
+
say FAIL wda-session "could not create a session — $msg"
|
|
329
|
+
else
|
|
330
|
+
# The session capabilities WDA returns carry no bundle id, so ask what is
|
|
331
|
+
# actually on screen. That also proves the runner can introspect a foreign
|
|
332
|
+
# app, which is the capability every other script depends on.
|
|
333
|
+
fg="$(curl -sS -m "$(http_timeout)" "$(wda_base)/wda/activeAppInfo" 2>/dev/null \
|
|
334
|
+
| jq -r '.value.bundleId // empty' 2>/dev/null || true)"
|
|
335
|
+
curl -sS -m "$(http_timeout)" -X DELETE "$(wda_base)/session/$sid" >/dev/null 2>&1 || true
|
|
336
|
+
say OK wda-session "created and released, foreground app ${fg:-unknown}"
|
|
337
|
+
fi
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
# --- 8. app under test --------------------------------------------------------
|
|
341
|
+
APP_ID="$(cfg '.app.bundle_id' '')"
|
|
342
|
+
if [ -z "$APP_ID" ]; then
|
|
343
|
+
say WARN app "app.bundle_id not set in $DT_CONFIG — install and launch checks unavailable"
|
|
344
|
+
elif [ "$DEVICE_OK" -eq 0 ]; then
|
|
345
|
+
say FAIL app "skipped — device check failed"
|
|
346
|
+
elif [ "$KIND" = simulator ]; then
|
|
347
|
+
if xcrun simctl get_app_container "$UDID" "$APP_ID" app >/dev/null 2>&1; then
|
|
348
|
+
say OK app "$APP_ID installed"
|
|
349
|
+
else
|
|
350
|
+
say FAIL app "$APP_ID not installed on the simulator — $(as_cmd app install '<path>')"
|
|
351
|
+
fi
|
|
352
|
+
elif ! command -v ios-deploy >/dev/null 2>&1; then
|
|
353
|
+
say FAIL app "ios-deploy not found — brew install ios-deploy"
|
|
354
|
+
elif ios-deploy --id "$UDID" --no-wifi --exists --bundle_id "$APP_ID" >/dev/null 2>&1; then
|
|
355
|
+
# The device does not volunteer a version, so what is reported is what
|
|
356
|
+
# DeviceTools put there — a smaller claim, and a true one. Anything installed
|
|
357
|
+
# by other means says so rather than being described from a stale record.
|
|
358
|
+
rec="$(last_install)"
|
|
359
|
+
rec_id="$(printf '%s' "$rec" | cut -f1)"
|
|
360
|
+
if [ -n "$rec" ] && [ "$rec_id" = "$APP_ID" ]; then
|
|
361
|
+
say OK app "$APP_ID installed — $(printf '%s' "$rec" | cut -f2) ($(printf '%s' "$rec" | cut -f3)), put there by DeviceTools at $(printf '%s' "$rec" | cut -f5)"
|
|
362
|
+
else
|
|
363
|
+
say OK app "$APP_ID installed — version unknown, not installed by DeviceTools"
|
|
364
|
+
fi
|
|
365
|
+
else
|
|
366
|
+
say FAIL app "$APP_ID not installed — $(as_cmd app install)"
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
# THERE IS NO SECTION 9, AND THERE WILL NOT BE ONE.
|
|
370
|
+
#
|
|
371
|
+
# It read the signing identities and provisioning profiles on this machine and
|
|
372
|
+
# tried to say whether a build would install. Deleted at the second complaint,
|
|
373
|
+
# and the complaint was right: this tool owns the phone from "the app is
|
|
374
|
+
# installed" onward. Whose certificate signs the app, which team owns it and
|
|
375
|
+
# which profile Xcode picked are the build's business, and a check standing
|
|
376
|
+
# outside the build can only guess at them — it guessed wrong twice, naming a
|
|
377
|
+
# team the project has nothing to do with, on a machine with fifteen of them.
|
|
378
|
+
#
|
|
379
|
+
# `app install` still refuses an unsigned bundle. That is a different question:
|
|
380
|
+
# it is about whether the verb in front of you will work, not about auditing
|
|
381
|
+
# somebody's account. It also comes from the bundle itself rather than from
|
|
382
|
+
# what happens to be lying in the keychain.
|
|
383
|
+
#
|
|
384
|
+
# profiles_for and project_signing went with it.
|
|
385
|
+
|
|
386
|
+
exit $(( FAILED ? 2 : 0 ))
|