@mgcrea/mcp-ios-simulator 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/scripts/wda.sh ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env bash
2
+ # Build and run WebDriverAgent against an iOS Simulator — the half of
3
+ # mcp-ios-simulator that can read the accessibility tree and touch the screen.
4
+ #
5
+ # Screenshots do NOT need this: `simctl io screenshot` works with nothing
6
+ # installed. Only ios_simulator_ui_tree and the input tools do.
7
+ #
8
+ # Compared with the physical-device version of this script, everything hard is
9
+ # gone: no Apple Developer team, no provisioning profile, no code signing, no
10
+ # untrusted-developer prompt on the device, no tunnel. A simulator build is not
11
+ # signed at all.
12
+ #
13
+ # scripts/wda.sh setup clone + build the runner (once, ~2 minutes)
14
+ # scripts/wda.sh run start it and keep it running (leave this open)
15
+ # scripts/wda.sh status is it answering?
16
+ #
17
+ # Environment:
18
+ # IOS_SIMULATOR_ID UDID or name (default: the only booted simulator)
19
+ # IOS_SIMULATOR_WDA_PORT port to bind (default: 8100)
20
+ # USE_PORT same thing, for people driving xcodebuild by hand
21
+ # NODE node binary (default: node off PATH)
22
+ # WDA_DIR checkout location (default: ~/.cache/mcp-ios-simulator/WebDriverAgent)
23
+ # WDA_REF git tag to pin (default: v16.12.3)
24
+ set -euo pipefail
25
+
26
+ WDA_DIR="${WDA_DIR:-$HOME/.cache/mcp-ios-simulator/WebDriverAgent}"
27
+ WDA_REF="${WDA_REF:-v16.12.3}"
28
+ DERIVED="${WDA_DIR}/.build"
29
+ PORT="${USE_PORT:-${IOS_SIMULATOR_WDA_PORT:-8100}}"
30
+ # Not always on PATH: a host that embeds its own runtime leaves nothing called
31
+ # `node` to find, and every invocation below fails with "command not found".
32
+ NODE="${NODE:-node}"
33
+
34
+ die() { echo "error: $*" >&2; exit 1; }
35
+
36
+ # The simulator record, straight from simctl's own JSON — the same source the
37
+ # server uses, so the script and the server can never disagree about which
38
+ # simulator they mean.
39
+ sim_json() {
40
+ xcrun simctl list devices -j -e | "$NODE" -e '
41
+ let raw = ""; process.stdin.on("data", (d) => (raw += d)).on("end", () => {
42
+ const all = Object.values(JSON.parse(raw).devices ?? {}).flat();
43
+ const want = process.env.IOS_SIMULATOR_ID;
44
+ const usable = all.filter((d) => d.isAvailable !== false);
45
+ const booted = usable.filter((d) => d.state === "Booted");
46
+ const match = want && want !== "booted"
47
+ ? usable.find((d) => d.udid.toLowerCase() === want.toLowerCase() || d.name === want)
48
+ : booted.length === 1 ? booted[0] : undefined;
49
+ if (!match) {
50
+ console.error(booted.length > 1
51
+ ? "Several simulators are booted. Set IOS_SIMULATOR_ID to one of: " +
52
+ booted.map((d) => `${d.name} = ${d.udid}`).join(", ")
53
+ : "No booted simulator. Boot one in Simulator.app, or `xcrun simctl boot <udid>`.");
54
+ process.exit(1);
55
+ }
56
+ console.log(JSON.stringify({ udid: match.udid, name: match.name, state: match.state }));
57
+ });
58
+ '
59
+ }
60
+
61
+ field() { "$NODE" -e 'const d=JSON.parse(process.argv[1]);process.stdout.write(String(d[process.argv[2]]??""))' "$1" "$2"; }
62
+
63
+ cmd_setup() {
64
+ local sim udid name
65
+ sim="$(sim_json)"; udid="$(field "$sim" udid)"; name="$(field "$sim" name)"
66
+ echo "==> simulator: $name ($udid)"
67
+
68
+ if [ ! -d "$WDA_DIR/.git" ]; then
69
+ echo "==> cloning appium/WebDriverAgent $WDA_REF"
70
+ mkdir -p "$(dirname "$WDA_DIR")"
71
+ git clone --depth 1 --branch "$WDA_REF" https://github.com/appium/WebDriverAgent.git "$WDA_DIR"
72
+ else
73
+ echo "==> reusing $WDA_DIR"
74
+ git -C "$WDA_DIR" fetch --depth 1 origin "refs/tags/$WDA_REF:refs/tags/$WDA_REF" 2>/dev/null || true
75
+ git -C "$WDA_DIR" checkout -q "$WDA_REF"
76
+ fi
77
+
78
+ # A separate checkout from the device server's on purpose. The two could share
79
+ # one — the products land in Debug-iphonesimulator/ rather than Debug-iphoneos/
80
+ # — but they would then share a derivedDataPath two xcodebuild processes can
81
+ # enter at once, and a `git checkout $WDA_REF` in one would yank the ref out
82
+ # from under the other the first time the pins diverge.
83
+ echo "==> building the runner (no signing: a simulator build needs no team)"
84
+ xcodebuild build-for-testing \
85
+ -project "$WDA_DIR/WebDriverAgent.xcodeproj" \
86
+ -scheme WebDriverAgentRunner \
87
+ -destination "platform=iOS Simulator,id=$udid" \
88
+ -derivedDataPath "$DERIVED" \
89
+ CODE_SIGNING_ALLOWED=NO
90
+
91
+ echo
92
+ echo "Built. Now run: scripts/wda.sh run"
93
+ }
94
+
95
+ cmd_run() {
96
+ local sim udid name xctestrun patched
97
+ sim="$(sim_json)"; udid="$(field "$sim" udid)"; name="$(field "$sim" name)"
98
+ xctestrun="$(ls "$DERIVED"/Build/Products/WebDriverAgentRunner_iphonesimulator*.xctestrun 2>/dev/null | head -1 || true)"
99
+ [ -n "$xctestrun" ] || die "no build found in $DERIVED — run \`scripts/wda.sh setup\` first"
100
+
101
+ # Pin the port, always. Left unset, WebDriverAgent scans 8100-8199 and binds
102
+ # the first free one (FBConfiguration.m: DefaultStartingPort 8100, range 100),
103
+ # so a second simulator's runner comes up healthy on 8101 and a server
104
+ # configured for 8100 drives the *first* simulator while every tool reports
105
+ # success. With USE_PORT set the range collapses to one and a collision is a
106
+ # loud bind failure instead of a silent wrong-target bug.
107
+ #
108
+ # Patched into a copy of the xctestrun rather than the original, so a second
109
+ # simulator on a different port does not race the first.
110
+ # Beside the original, not in $DERIVED: an xctestrun resolves its product
111
+ # paths against __TESTROOT__, which is its own directory, so a copy one level
112
+ # up looks for the .xctest bundle in a place that does not exist and fails
113
+ # with "Missing test product".
114
+ patched="$(dirname "$xctestrun")/wda-$PORT.xctestrun"
115
+ cp "$xctestrun" "$patched"
116
+ /usr/bin/plutil -replace WebDriverAgentRunner.EnvironmentVariables.USE_PORT \
117
+ -string "$PORT" "$patched" 2>/dev/null \
118
+ || die "could not set USE_PORT in $patched — check the xctestrun's FormatVersion"
119
+
120
+ echo "==> starting WebDriverAgent on $name ($udid), port $PORT"
121
+ echo " it stays up for as long as this command runs; Ctrl-C stops it"
122
+ # `test-without-building` is what keeps the runner alive: the XCTest session is
123
+ # the process, so the HTTP server dies with it. There is no daemon mode.
124
+ exec env USE_PORT="$PORT" xcodebuild test-without-building \
125
+ -xctestrun "$patched" \
126
+ -destination "platform=iOS Simulator,id=$udid"
127
+ }
128
+
129
+ cmd_status() {
130
+ local url; url="http://127.0.0.1:${PORT}/status"
131
+ echo "==> GET $url"
132
+ if curl -fsS --max-time 5 "$url"; then
133
+ echo
134
+ else
135
+ echo "not answering — run \`scripts/wda.sh run\`, or check the port with lsof -iTCP:$PORT" >&2
136
+ exit 1
137
+ fi
138
+ }
139
+
140
+ case "${1:-}" in
141
+ setup) cmd_setup ;;
142
+ run) cmd_run ;;
143
+ status) cmd_status ;;
144
+ *) echo "usage: $0 {setup|run|status}" >&2; exit 2 ;;
145
+ esac