@delacour/warden-e2e 0.0.0-stage → 0.2.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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/chunk-7ns30pbp.mjs +5832 -0
  3. package/index.d.ts +1 -0
  4. package/index.mjs +1047 -0
  5. package/package.json +49 -4
  6. package/preload.d.ts +1 -0
  7. package/preload.mjs +4788 -0
  8. package/types/core/src/allocate.d.ts +23 -0
  9. package/types/core/src/builds/app-option.schema.d.ts +37 -0
  10. package/types/core/src/builds/install.d.ts +95 -0
  11. package/types/core/src/builds/resolve.d.ts +74 -0
  12. package/types/core/src/config.defaults.d.ts +25 -0
  13. package/types/core/src/exec.d.ts +18 -0
  14. package/types/core/src/liveness.d.ts +12 -0
  15. package/types/core/src/providers/android.d.ts +132 -0
  16. package/types/core/src/providers/health.d.ts +45 -0
  17. package/types/core/src/providers/provider.types.d.ts +56 -0
  18. package/types/core/src/store.d.ts +105 -0
  19. package/types/core/src/types.d.ts +126 -0
  20. package/types/e2e/src/alert-buttons.d.ts +41 -0
  21. package/types/e2e/src/alert-reader.d.ts +25 -0
  22. package/types/e2e/src/app.d.ts +62 -0
  23. package/types/e2e/src/device-devtools.d.ts +26 -0
  24. package/types/e2e/src/device-link.d.ts +43 -0
  25. package/types/e2e/src/device.d.ts +90 -0
  26. package/types/e2e/src/devtools/cdp.d.ts +65 -0
  27. package/types/e2e/src/devtools/console.d.ts +59 -0
  28. package/types/e2e/src/devtools/devtools.types.d.ts +169 -0
  29. package/types/e2e/src/devtools/facet.d.ts +28 -0
  30. package/types/e2e/src/devtools/inspector.d.ts +48 -0
  31. package/types/e2e/src/devtools/jsonl.d.ts +4 -0
  32. package/types/e2e/src/devtools/native-log.d.ts +50 -0
  33. package/types/e2e/src/devtools/network.d.ts +33 -0
  34. package/types/e2e/src/devtools/network.script.d.ts +24 -0
  35. package/types/e2e/src/devtools/session.d.ts +82 -0
  36. package/types/e2e/src/devtools/tree.d.ts +23 -0
  37. package/types/e2e/src/devtools/tree.script.d.ts +33 -0
  38. package/types/e2e/src/driver/alerts.d.ts +29 -0
  39. package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
  40. package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
  41. package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
  42. package/types/e2e/src/driver/android/android-app.d.ts +32 -0
  43. package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
  44. package/types/e2e/src/driver/android/android-device.d.ts +44 -0
  45. package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
  46. package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
  47. package/types/e2e/src/driver/android/android-state.d.ts +69 -0
  48. package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
  49. package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
  50. package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
  51. package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
  52. package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
  53. package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
  54. package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
  55. package/types/e2e/src/driver/driver.types.d.ts +231 -0
  56. package/types/e2e/src/driver/errors.d.ts +13 -0
  57. package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
  58. package/types/e2e/src/driver/ios/hid-recovery.d.ts +36 -0
  59. package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
  60. package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
  61. package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
  62. package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
  63. package/types/e2e/src/driver/ios/simctl.d.ts +102 -0
  64. package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
  65. package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
  66. package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
  67. package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
  68. package/types/e2e/src/driver/observation-cache.d.ts +66 -0
  69. package/types/e2e/src/driver/open.d.ts +29 -0
  70. package/types/e2e/src/driver/settle.d.ts +43 -0
  71. package/types/e2e/src/driver/span-detail.d.ts +12 -0
  72. package/types/e2e/src/driver/traced-driver.d.ts +14 -0
  73. package/types/e2e/src/driver/typing.d.ts +48 -0
  74. package/types/e2e/src/driver/unwrap.d.ts +13 -0
  75. package/types/e2e/src/env.d.ts +78 -0
  76. package/types/e2e/src/errors.d.ts +26 -0
  77. package/types/e2e/src/expect/expect.d.ts +27 -0
  78. package/types/e2e/src/expect/matchers.d.ts +72 -0
  79. package/types/e2e/src/fixture-session.d.ts +38 -0
  80. package/types/e2e/src/fixtures.d.ts +14 -0
  81. package/types/e2e/src/index.d.ts +11 -0
  82. package/types/e2e/src/perf/budgets.d.ts +33 -0
  83. package/types/e2e/src/perf/device-perf.d.ts +45 -0
  84. package/types/e2e/src/perf/perf.types.d.ts +59 -0
  85. package/types/e2e/src/preload.d.ts +29 -0
  86. package/types/e2e/src/runner/artifacts.d.ts +13 -0
  87. package/types/e2e/src/runner/attempt.d.ts +63 -0
  88. package/types/e2e/src/runner/callsite.d.ts +2 -0
  89. package/types/e2e/src/runner/failure.d.ts +40 -0
  90. package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
  91. package/types/e2e/src/runner/results.d.ts +48 -0
  92. package/types/e2e/src/runner/scope.d.ts +64 -0
  93. package/types/e2e/src/runner/span.d.ts +40 -0
  94. package/types/e2e/src/runner/step.d.ts +7 -0
  95. package/types/e2e/src/runner/test.d.ts +59 -0
  96. package/types/e2e/src/runner/trace.d.ts +87 -0
  97. package/types/e2e/src/screen/actions.d.ts +93 -0
  98. package/types/e2e/src/screen/context.d.ts +134 -0
  99. package/types/e2e/src/screen/field.d.ts +121 -0
  100. package/types/e2e/src/screen/locator.d.ts +233 -0
  101. package/types/e2e/src/screen/nodes.d.ts +53 -0
  102. package/types/e2e/src/screen/poll.d.ts +57 -0
  103. package/types/e2e/src/screen/queries.d.ts +34 -0
  104. package/types/e2e/src/screen/query.d.ts +60 -0
  105. package/types/e2e/src/screen/react-locator.d.ts +30 -0
  106. package/types/e2e/src/screen/render.d.ts +42 -0
  107. package/types/e2e/src/screen/roles.d.ts +24 -0
  108. package/types/e2e/src/screen/screen.d.ts +61 -0
  109. package/types/e2e/src/screen/selector.d.ts +38 -0
  110. package/types/e2e/src/screen/text.d.ts +13 -0
  111. package/types/e2e/src/session.d.ts +124 -0
  112. package/types/e2e/src/traced-device.d.ts +9 -0
  113. package/types/e2e/src/version.d.ts +7 -0
  114. package/types/types/src/result.d.ts +12 -0
  115. package/README.md +0 -3
package/preload.mjs ADDED
@@ -0,0 +1,4788 @@
1
+ // @bun
2
+ import {
3
+ E2eError2,
4
+ noteSpanDetail,
5
+ addSpanDetail,
6
+ canObserve,
7
+ canDetectAlerts,
8
+ settleDetailOf,
9
+ DEFAULT_SETTLE_QUIET_MS,
10
+ DEFAULT_SETTLE_TIMEOUT_MS,
11
+ settleByHash,
12
+ snapshotHash,
13
+ ok,
14
+ err,
15
+ swipeVector,
16
+ screenRect,
17
+ PINCH_MS,
18
+ ROTATE_MS,
19
+ PATH_MS,
20
+ centreOf,
21
+ pinchRadius,
22
+ rotateRadius,
23
+ requireGesture,
24
+ assertScale,
25
+ assertDegrees,
26
+ isInteractiveRole,
27
+ resolveRole,
28
+ indexSnapshot,
29
+ testIdQuery,
30
+ textQuery,
31
+ roleQuery,
32
+ selectorExpression,
33
+ renderSnapshot,
34
+ renderHeader,
35
+ locateByTestIdViaReact,
36
+ isLocator,
37
+ Locator,
38
+ scrollUntilVisible,
39
+ bunExec,
40
+ execError,
41
+ driverError,
42
+ driverErr,
43
+ logEventSchema,
44
+ fatalEventSchema,
45
+ responseSchema,
46
+ mapWireError,
47
+ encodeRequest,
48
+ bunSpawn,
49
+ stdioTransport,
50
+ spawnSimd,
51
+ connectSimd,
52
+ ENV_NAMES,
53
+ DEFAULT_ASSERTION_TIMEOUT_MS,
54
+ readEnv,
55
+ setDefaultHidRecovery,
56
+ setDefaultNativeLog,
57
+ setDefaultDriverFactory,
58
+ hasDefaultDriverFactory,
59
+ setDefaultFixtureFactory,
60
+ hasDefaultFixtureFactory,
61
+ getSession,
62
+ parseBudgets,
63
+ createDevicePerf,
64
+ spawnWarden,
65
+ screenshotPath,
66
+ realNativeLogDeps,
67
+ BUN_TIMEOUT_MARGIN_MS2,
68
+ onlyWasSeen,
69
+ WORKSPACE_VERSION,
70
+ WARDEN_E2E_VERSION2
71
+ } from "./chunk-7ns30pbp.mjs";
72
+
73
+ // ../../packages/e2e/src/preload.ts
74
+ import { afterAll, setDefaultTimeout } from "bun:test";
75
+
76
+ // ../../packages/core/src/providers/android.ts
77
+ import { join as join2 } from "path";
78
+
79
+ // ../../packages/core/src/config.defaults.ts
80
+ var DEFAULT_TTL_MS = 30 * 60000;
81
+ var BARE_CLAIM_AGENT_TTL_MS = 5 * 60000;
82
+ var DEFAULT_IDLE_MS = 20 * 60000;
83
+ var DEFAULT_READY_TIMEOUT_MS = 5 * 60000;
84
+ var DEFAULT_JOB_TIMEOUT_MS = 10 * 60000;
85
+
86
+ // ../../packages/core/src/store.ts
87
+ import { dirname, join } from "path";
88
+ function wardenHome(env = process.env) {
89
+ if (env.WARDEN_HOME)
90
+ return env.WARDEN_HOME;
91
+ return join(env.HOME ?? "~", ".warden");
92
+ }
93
+
94
+ // ../../packages/core/src/providers/android.ts
95
+ function androidTools(env) {
96
+ const sdk = env.ANDROID_HOME || env.ANDROID_SDK_ROOT;
97
+ if (!sdk)
98
+ return { adb: "adb", emulator: "emulator" };
99
+ return { adb: join2(sdk, "platform-tools", "adb"), emulator: join2(sdk, "emulator", "emulator") };
100
+ }
101
+ var avdLists = new WeakMap;
102
+
103
+ // ../../packages/e2e/src/driver/android/android-driver.ts
104
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
105
+ import { dirname as dirname2 } from "path";
106
+
107
+ // ../../packages/e2e/src/driver/gesture-geometry.ts
108
+ var GESTURE_FRAME_MS = 16;
109
+ var GESTURE_MAX_FRAMES = 1000;
110
+ var GESTURE_MAX_MS = 60000;
111
+ var GESTURE_MAX_POINTERS = 2;
112
+ var DEFAULT_GESTURE_MS = 300;
113
+ var DEFAULT_PINCH_MS = 400;
114
+ var DEFAULT_ROTATE_MS = 500;
115
+ var DEFAULT_RADIUS = 100;
116
+ var TWO_FINGER_RELEASE_MS = 50;
117
+ var ARC_STEP_DEGREES = 5;
118
+ function bad(message) {
119
+ return { success: false, error: driverError("E_BAD_REQUEST", message) };
120
+ }
121
+ function finite(value) {
122
+ return Number.isFinite(value);
123
+ }
124
+ function round(value) {
125
+ return Math.round(value * 100) / 100;
126
+ }
127
+ function nonNegative(name, value) {
128
+ if (value === undefined)
129
+ return;
130
+ return finite(value) && value >= 0 ? undefined : `${name} must be a finite number >= 0`;
131
+ }
132
+ function timeByLength(points, durationMs) {
133
+ if (points.length === 1)
134
+ return points.map((p) => ({ x: p.x, y: p.y, t: 0 }));
135
+ const lengths = [0];
136
+ for (let i = 1;i < points.length; i++) {
137
+ const a = points[i - 1];
138
+ const b = points[i];
139
+ lengths.push(lengths[i - 1] + Math.hypot(b.x - a.x, b.y - a.y));
140
+ }
141
+ const total = lengths.at(-1);
142
+ return points.map((p, i) => ({
143
+ x: p.x,
144
+ y: p.y,
145
+ t: total > 0 ? lengths[i] / total * durationMs : i / (points.length - 1) * durationMs
146
+ }));
147
+ }
148
+ function positionAt(path, at) {
149
+ let i = 0;
150
+ while (i < path.length - 1 && path[i + 1].t <= at)
151
+ i++;
152
+ const from = path[i];
153
+ const to = path[i + 1];
154
+ if (!to)
155
+ return { x: from.x, y: from.y };
156
+ const f = (at - from.t) / (to.t - from.t);
157
+ return { x: from.x + (to.x - from.x) * f, y: from.y + (to.y - from.y) * f };
158
+ }
159
+ function checkOptions(opts, timed) {
160
+ if (timed && opts?.durationMs !== undefined)
161
+ return "`durationMs` is for untimed paths; these carry `t`";
162
+ const durationMs = opts?.durationMs ?? DEFAULT_GESTURE_MS;
163
+ if (!finite(durationMs) || durationMs <= 0)
164
+ return "durationMs must be a finite number > 0";
165
+ const problem = nonNegative("holdMs", opts?.holdMs) ?? nonNegative("releaseMs", opts?.releaseMs);
166
+ if (problem)
167
+ return problem;
168
+ return { durationMs, holdMs: opts?.holdMs ?? 0, releaseMs: opts?.releaseMs ?? 0, timed };
169
+ }
170
+ function checkInput(paths, opts, maxPointers) {
171
+ if (paths.length < 1 || paths.length > GESTURE_MAX_POINTERS) {
172
+ return `a gesture takes 1 to ${GESTURE_MAX_POINTERS} finger paths, got ${paths.length}`;
173
+ }
174
+ if (paths.length > maxPointers) {
175
+ return driverError("E_UNSUPPORTED", `this driver takes at most ${maxPointers} finger(s), got ${paths.length}`);
176
+ }
177
+ const points = paths.flatMap((path) => path.points);
178
+ if (paths.some((path) => path.points.length === 0))
179
+ return "every finger path needs at least one point";
180
+ if (points.some((p) => !finite(p.x) || !finite(p.y)))
181
+ return "gesture points must be finite";
182
+ const timed = points.filter((p) => p.t !== undefined).length;
183
+ if (timed !== 0 && timed !== points.length)
184
+ return "give `t` on every point of a gesture or on none";
185
+ return checkOptions(opts, timed > 0);
186
+ }
187
+ function timedFinger(path) {
188
+ const finger = path.points.map((p) => ({ x: p.x, y: p.y, t: p.t }));
189
+ if (finger[0]?.t !== 0)
190
+ return "a timed path starts at t = 0 (touch-down)";
191
+ if (finger.some((p, i) => !finite(p.t) || i > 0 && p.t < finger[i - 1].t)) {
192
+ return "`t` must be finite and must not decrease along a path";
193
+ }
194
+ return finger;
195
+ }
196
+ function planGesture(paths, opts, maxPointers) {
197
+ const timing = checkInput(paths, opts, maxPointers);
198
+ if (typeof timing === "string")
199
+ return bad(timing);
200
+ if ("code" in timing)
201
+ return { success: false, error: timing };
202
+ const { durationMs, holdMs, releaseMs } = timing;
203
+ const fingers = [];
204
+ for (const path of paths) {
205
+ const finger = timing.timed ? timedFinger(path) : timeByLength(path.points, durationMs);
206
+ if (typeof finger === "string")
207
+ return bad(finger);
208
+ fingers.push(finger);
209
+ }
210
+ const span = Math.max(...fingers.map((finger) => finger.at(-1).t));
211
+ if (span + holdMs + releaseMs > GESTURE_MAX_MS)
212
+ return bad(`a gesture lasts at most ${GESTURE_MAX_MS} ms`);
213
+ const intervals = span === 0 ? 0 : Math.min(GESTURE_MAX_FRAMES, Math.ceil(span / GESTURE_FRAME_MS));
214
+ const frameMs = intervals === 0 ? GESTURE_FRAME_MS : span / intervals;
215
+ const frames = fingers.map((finger) => Array.from({ length: intervals + 1 }, (_, k) => {
216
+ const p = positionAt(finger, k === intervals ? span : k * frameMs);
217
+ return { x: round(p.x), y: round(p.y) };
218
+ }));
219
+ return { success: true, data: { paths: frames, frameMs, holdMs, releaseMs, totalMs: span + holdMs + releaseMs } };
220
+ }
221
+ function twoFingerPaths(center, spec, durationMs) {
222
+ const scale = spec.scale ?? 1;
223
+ const degrees = spec.degrees ?? 0;
224
+ const steps = degrees === 0 ? 1 : Math.max(1, Math.ceil(Math.abs(degrees) / ARC_STEP_DEGREES));
225
+ const a = [];
226
+ const b = [];
227
+ for (let i = 0;i <= steps; i++) {
228
+ const u = i / steps;
229
+ const theta = ((spec.angle ?? 0) + degrees * u) * Math.PI / 180;
230
+ const r = spec.radius * (1 + (scale - 1) * u);
231
+ const dx = r * Math.cos(theta);
232
+ const dy = r * Math.sin(theta);
233
+ const t = u * durationMs;
234
+ a.push({ x: center.x - dx, y: center.y - dy, t });
235
+ b.push({ x: center.x + dx, y: center.y + dy, t });
236
+ }
237
+ return [{ points: a }, { points: b }];
238
+ }
239
+ function twoFinger(center, spec, opts, defaultRadius, defaultMs) {
240
+ if (!finite(center.x) || !finite(center.y))
241
+ return bad("the centre must be finite");
242
+ const radius = opts?.radius ?? defaultRadius;
243
+ if (!finite(radius) || radius <= 0)
244
+ return bad("radius must be a finite number > 0");
245
+ if (opts?.angle !== undefined && !finite(opts.angle))
246
+ return bad("angle must be finite");
247
+ const durationMs = opts?.durationMs ?? defaultMs;
248
+ if (!finite(durationMs) || durationMs <= 0)
249
+ return bad("durationMs must be a finite number > 0");
250
+ const paths = twoFingerPaths(center, { radius, ...spec, ...opts?.angle !== undefined ? { angle: opts.angle } : {} }, durationMs);
251
+ return planGesture(paths, { releaseMs: TWO_FINGER_RELEASE_MS }, 2);
252
+ }
253
+ function pinchPlan(center, scale, opts, defaultRadius) {
254
+ if (!finite(scale) || scale <= 0)
255
+ return bad("scale must be a finite number > 0");
256
+ return twoFinger(center, { scale }, opts, defaultRadius, DEFAULT_PINCH_MS);
257
+ }
258
+ function rotatePlan(center, degrees, opts, defaultRadius) {
259
+ if (!finite(degrees))
260
+ return bad("degrees must be finite");
261
+ return twoFinger(center, { degrees }, opts, defaultRadius, DEFAULT_ROTATE_MS);
262
+ }
263
+
264
+ // ../../packages/e2e/src/driver/android/adb-session.ts
265
+ function spawnAdb(argv, env) {
266
+ const proc = Bun.spawn(argv, {
267
+ stdin: "pipe",
268
+ stdout: "pipe",
269
+ stderr: "ignore",
270
+ ...env ? { env: { ...process.env, ...env } } : {}
271
+ });
272
+ return {
273
+ write(data) {
274
+ proc.stdin.write(data);
275
+ proc.stdin.flush();
276
+ },
277
+ stdout: proc.stdout,
278
+ kill() {
279
+ proc.kill("SIGKILL");
280
+ },
281
+ exited: proc.exited
282
+ };
283
+ }
284
+ var DEFAULT_ADB_TIMEOUT_MS = 1e4;
285
+ var MAX_CONSECUTIVE_CRASHES = 2;
286
+ function openAdbSession(options) {
287
+ const { adb, serial, log } = options;
288
+ const spawn = options.spawn ?? ((argv) => spawnAdb(argv));
289
+ const nonce = Math.random().toString(36).slice(2, 8);
290
+ let seq = 0;
291
+ let live;
292
+ let current;
293
+ let crashes = 0;
294
+ let closed = false;
295
+ let tail = Promise.resolve();
296
+ function check(l) {
297
+ if (!current)
298
+ return;
299
+ const start = l.buffer.indexOf(current.prefix);
300
+ if (start < 0)
301
+ return;
302
+ const afterPrefix = start + current.prefix.length;
303
+ const m = /^(\d+)__(\r?\n)?/.exec(l.buffer.slice(afterPrefix));
304
+ if (!m)
305
+ return;
306
+ const stdout = l.buffer.slice(0, start);
307
+ l.buffer = l.buffer.slice(afterPrefix + m[0].length);
308
+ const settle = current.settle;
309
+ current = undefined;
310
+ settle({ kind: "done", result: { stdout, exitCode: Number(m[1]) } });
311
+ }
312
+ function onDeath(l) {
313
+ if (l.dead)
314
+ return;
315
+ l.dead = true;
316
+ if (live === l)
317
+ live = undefined;
318
+ crashes++;
319
+ log?.(`adb[${serial}] shell died (consecutive crashes: ${crashes})`);
320
+ if (current) {
321
+ const settle = current.settle;
322
+ current = undefined;
323
+ settle({ kind: "crashed", tail: l.buffer.slice(-200) });
324
+ }
325
+ }
326
+ async function pump(l) {
327
+ const decoder = new TextDecoder;
328
+ try {
329
+ for await (const chunk of l.proc.stdout) {
330
+ if (l.dead)
331
+ return;
332
+ l.buffer += decoder.decode(chunk, { stream: true });
333
+ check(l);
334
+ }
335
+ } catch {}
336
+ onDeath(l);
337
+ }
338
+ function start() {
339
+ if (crashes >= MAX_CONSECUTIVE_CRASHES) {
340
+ return {
341
+ code: "E_HELPER_CRASHED",
342
+ message: `adb shell for ${serial} keeps dying; giving up (open a new session)`
343
+ };
344
+ }
345
+ let proc;
346
+ try {
347
+ proc = spawn([adb, "-s", serial, "shell"]);
348
+ } catch (error) {
349
+ return {
350
+ code: "E_HELPER_MISSING",
351
+ message: `cannot spawn \`${adb}\`: ${error instanceof Error ? error.message : String(error)}`
352
+ };
353
+ }
354
+ const l = { proc, buffer: "", dead: false };
355
+ live = l;
356
+ pump(l);
357
+ return l;
358
+ }
359
+ async function execute(cmd, timeoutMs) {
360
+ if (closed)
361
+ return driverErr("E_INTERNAL", `adb session for ${serial} is closed`);
362
+ const started = live && !live.dead ? live : start();
363
+ if ("code" in started)
364
+ return driverErr(started.code, started.message);
365
+ const l = started;
366
+ seq++;
367
+ const prefix = `__WDN_${nonce}x${seq}_`;
368
+ const line = `(${cmd}) 2>&1 </dev/null; echo "${prefix}$?__"
369
+ `;
370
+ const t0 = Date.now();
371
+ const outcome = await new Promise((resolve) => {
372
+ const timer = setTimeout(() => {
373
+ if (current?.prefix !== prefix)
374
+ return;
375
+ current = undefined;
376
+ resolve({ kind: "timeout" });
377
+ }, timeoutMs);
378
+ current = {
379
+ prefix,
380
+ settle: (o) => {
381
+ clearTimeout(timer);
382
+ resolve(o);
383
+ }
384
+ };
385
+ try {
386
+ l.proc.write(line);
387
+ } catch {
388
+ onDeath(l);
389
+ }
390
+ });
391
+ addSpanDetail("adbMs", Date.now() - t0);
392
+ addSpanDetail("adbCalls", 1);
393
+ if (outcome.kind === "done") {
394
+ crashes = 0;
395
+ log?.(`adb[${serial}] ${cmd} -> ${outcome.result.exitCode} in ${Date.now() - t0}ms`);
396
+ return { success: true, data: outcome.result };
397
+ }
398
+ if (outcome.kind === "timeout") {
399
+ log?.(`adb[${serial}] ${cmd} timed out after ${timeoutMs}ms; restarting shell`);
400
+ l.dead = true;
401
+ if (live === l)
402
+ live = undefined;
403
+ l.proc.kill();
404
+ return driverErr("E_TIMEOUT", `\`${cmd}\` did not finish within ${timeoutMs}ms on ${serial}`);
405
+ }
406
+ return driverErr("E_HELPER_CRASHED", `adb shell for ${serial} died while running \`${cmd}\``, {
407
+ detail: { tail: outcome.tail }
408
+ });
409
+ }
410
+ return {
411
+ run(cmd, opts) {
412
+ const next = tail.then(() => execute(cmd, opts?.timeoutMs ?? DEFAULT_ADB_TIMEOUT_MS));
413
+ tail = next.catch(() => {
414
+ return;
415
+ });
416
+ return next;
417
+ },
418
+ async close() {
419
+ closed = true;
420
+ const l = live;
421
+ live = undefined;
422
+ if (!l)
423
+ return;
424
+ l.dead = true;
425
+ l.proc.kill();
426
+ if (current) {
427
+ const settle = current.settle;
428
+ current = undefined;
429
+ settle({ kind: "crashed", tail: "" });
430
+ }
431
+ await l.proc.exited.catch(() => 0);
432
+ }
433
+ };
434
+ }
435
+
436
+ // ../../packages/e2e/src/alert-buttons.ts
437
+ var ACCEPT_BUTTONS = [
438
+ "OK",
439
+ "Allow",
440
+ "Open",
441
+ "Continue",
442
+ "Yes",
443
+ "Done",
444
+ "Allow While Using App",
445
+ "Allow Once",
446
+ "While using the app",
447
+ "Only this time"
448
+ ];
449
+ var DISMISS_BUTTONS = ["Cancel", "Don't Allow", "Not Now", "Dismiss", "No", "Close"];
450
+ function normalise(text) {
451
+ return text.replace(/[\u2018\u2019]/g, "'").trim().toLowerCase();
452
+ }
453
+ function nameOf(button) {
454
+ return button.label ?? button.text ?? button.value;
455
+ }
456
+ function findButton(buttons, wanted) {
457
+ const target = normalise(wanted);
458
+ return buttons.find((button) => {
459
+ const name = nameOf(button);
460
+ return name !== undefined && normalise(name) === target;
461
+ });
462
+ }
463
+ function firstOf(buttons, names) {
464
+ for (const name of names) {
465
+ const found = findButton(buttons, name);
466
+ if (found !== undefined)
467
+ return found;
468
+ }
469
+ return;
470
+ }
471
+ function centre(element) {
472
+ const { x, y, width, height } = element.frame;
473
+ return element.tapPoint ?? { x: x + width / 2, y: y + height / 2 };
474
+ }
475
+ var ANDROID_PERMISSION_ALLOW_IDS = [
476
+ "permission_allow_button",
477
+ "permission_allow_foreground_only_button",
478
+ "permission_allow_one_time_button"
479
+ ];
480
+ var ANDROID_PERMISSION_DENY_IDS = [
481
+ "permission_deny_button",
482
+ "permission_deny_and_dont_ask_again_button"
483
+ ];
484
+ var ANDROID_POSITIVE_ID = "button1";
485
+ var ANDROID_NEGATIVE_ID = "button2";
486
+ var ANDROID_ALLOW_IDS = [...ANDROID_PERMISSION_ALLOW_IDS, ANDROID_POSITIVE_ID];
487
+ var ANDROID_DENY_IDS = [...ANDROID_PERMISSION_DENY_IDS, ANDROID_NEGATIVE_ID];
488
+
489
+ // ../../packages/e2e/src/driver/android/android-alert.ts
490
+ var ALERT_BUTTON_IDS = new Set([
491
+ "button1",
492
+ "button2",
493
+ "button3",
494
+ "permission_allow_button",
495
+ "permission_allow_foreground_only_button",
496
+ "permission_allow_one_time_button",
497
+ "permission_deny_button",
498
+ "permission_deny_and_dont_ask_again_button",
499
+ "permission_no_upgrade_button",
500
+ "aerr_close",
501
+ "aerr_wait"
502
+ ]);
503
+ function textOf(element) {
504
+ return element?.text || element?.label || undefined;
505
+ }
506
+ var byIdentifier = (flat, id) => flat.find((e) => e.identifier === id);
507
+ function detectAlert(flat) {
508
+ const buttons = flat.filter((e) => e.identifier !== undefined && ALERT_BUTTON_IDS.has(e.identifier));
509
+ if (buttons.length === 0)
510
+ return null;
511
+ const title = textOf(byIdentifier(flat, "alertTitle")) ?? textOf(byIdentifier(flat, "permission_message"));
512
+ const message = textOf(byIdentifier(flat, "message"));
513
+ return { ...title ? { title } : {}, ...message ? { message } : {}, buttons };
514
+ }
515
+
516
+ // ../../packages/e2e/src/driver/android/android-argv.ts
517
+ var SAFE_WORD = /^[A-Za-z0-9_@%+=:,./-]+$/;
518
+ function shellQuote(word) {
519
+ if (word === "")
520
+ return "''";
521
+ if (SAFE_WORD.test(word))
522
+ return word;
523
+ return `'${word.replace(/'/g, "'\\''")}'`;
524
+ }
525
+ function shellCommand(argv) {
526
+ return argv.map(shellQuote).join(" ");
527
+ }
528
+ var px = (value) => String(Math.round(value));
529
+ function tapArgv(point) {
530
+ return ["input", "tap", px(point.x), px(point.y)];
531
+ }
532
+ var DEFAULT_SWIPE_MS = 300;
533
+ var DEFAULT_LONG_PRESS_MS = 600;
534
+ function swipeArgv(from, to, durationMs = DEFAULT_SWIPE_MS) {
535
+ return ["input", "swipe", px(from.x), px(from.y), px(to.x), px(to.y), String(Math.max(1, Math.round(durationMs)))];
536
+ }
537
+ function motionEventArgv(action, point) {
538
+ return ["input", "motionevent", action, px(point.x), px(point.y)];
539
+ }
540
+ var MOTIONEVENT_MIN_SDK = 26;
541
+ var SELECT_ALL_MIN_SDK = 33;
542
+ var DRAG_STEPS = 8;
543
+ function sleepArgv(ms) {
544
+ return ["sleep", (Math.max(0, ms) / 1000).toFixed(3)];
545
+ }
546
+ function dragMotionCommand(from, to, timing) {
547
+ const steps = Math.max(1, timing.steps ?? DRAG_STEPS);
548
+ const parts = [motionEventArgv("DOWN", from), sleepArgv(timing.pressMs)];
549
+ for (let i = 1;i <= steps; i++) {
550
+ const t = i / steps;
551
+ parts.push(motionEventArgv("MOVE", { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t }), sleepArgv(timing.durationMs / steps));
552
+ }
553
+ parts.push(sleepArgv(timing.releaseMs), motionEventArgv("UP", to));
554
+ const cancel = shellCommand(motionEventArgv("CANCEL", to));
555
+ return `${parts.map((argv) => shellCommand(argv)).join(" && ")} || (${cancel}; exit 1)`;
556
+ }
557
+ var GESTURE_LINE_MAX_MOVES = 12;
558
+ function gestureMotionCommand(plan, maxMoves = GESTURE_LINE_MAX_MOVES) {
559
+ const path = plan.paths[0] ?? [];
560
+ const first = path[0] ?? { x: 0, y: 0 };
561
+ const last = path.at(-1) ?? first;
562
+ const intervals = path.length - 1;
563
+ const count = Math.min(Math.max(1, Math.floor(maxMoves)), Math.max(0, intervals));
564
+ const travelMs = Math.max(0, intervals) * plan.frameMs;
565
+ const parts = [motionEventArgv("DOWN", first), sleepArgv(plan.holdMs)];
566
+ for (let k = 1;k <= count; k++) {
567
+ const at = path[Math.round(k * intervals / count)] ?? last;
568
+ parts.push(sleepArgv(travelMs / count), motionEventArgv("MOVE", at));
569
+ }
570
+ parts.push(sleepArgv(plan.releaseMs), motionEventArgv("UP", last));
571
+ const cancel = shellCommand(motionEventArgv("CANCEL", last));
572
+ return `${parts.map((argv) => shellCommand(argv)).join(" && ")} || (${cancel}; exit 1)`;
573
+ }
574
+ function longPressArgv(point, durationMs = DEFAULT_LONG_PRESS_MS) {
575
+ return swipeArgv(point, point, durationMs);
576
+ }
577
+ function keyeventArgv(keycode) {
578
+ return ["input", "keyevent", String(keycode)];
579
+ }
580
+ var KEYEVENT_CHUNK = 32;
581
+ function keyRepeatArgvs(keycode, times) {
582
+ const argvs = [];
583
+ for (let left = Math.max(0, Math.floor(times));left > 0; left -= KEYEVENT_CHUNK) {
584
+ argvs.push(["input", "keyevent", ...Array(Math.min(left, KEYEVENT_CHUNK)).fill(String(keycode))]);
585
+ }
586
+ return argvs;
587
+ }
588
+ function buttonKeycode(button) {
589
+ switch (button) {
590
+ case "home":
591
+ return 3;
592
+ case "back":
593
+ return 4;
594
+ case "volumeUp":
595
+ return 24;
596
+ case "volumeDown":
597
+ return 25;
598
+ case "lock":
599
+ return 26;
600
+ case "shake":
601
+ return;
602
+ }
603
+ }
604
+ function keyNameKeycode(key) {
605
+ switch (key) {
606
+ case "Enter":
607
+ return 66;
608
+ case "Backspace":
609
+ return 67;
610
+ case "Tab":
611
+ return 61;
612
+ case "Space":
613
+ return 62;
614
+ case "Escape":
615
+ return 111;
616
+ case "Delete":
617
+ return 112;
618
+ case "Up":
619
+ return 19;
620
+ case "Down":
621
+ return 20;
622
+ case "Left":
623
+ return 21;
624
+ case "Right":
625
+ return 22;
626
+ }
627
+ }
628
+ function splitForInputText(text) {
629
+ const pieces = [];
630
+ let current = "";
631
+ for (let i = 0;i < text.length; i++) {
632
+ const ch = text.charAt(i);
633
+ current += ch;
634
+ const next = text.charAt(i + 1);
635
+ if (ch === "%" && (next === " " || next === "s")) {
636
+ pieces.push(current);
637
+ current = "";
638
+ }
639
+ }
640
+ if (current !== "")
641
+ pieces.push(current);
642
+ return pieces;
643
+ }
644
+ function textArgvs(text) {
645
+ for (const ch of text) {
646
+ const code = ch.codePointAt(0) ?? 0;
647
+ if (code < 32 || code > 126) {
648
+ return driverErr("E_UNSUPPORTED_TEXT", `Android \`input text\` only types printable ASCII; got U+${code.toString(16).toUpperCase().padStart(4, "0")}`);
649
+ }
650
+ }
651
+ return { success: true, data: splitForInputText(text).map((piece) => ["input", "text", piece.replace(/ /g, "%s")]) };
652
+ }
653
+ function startUrlArgv(url, packageName) {
654
+ const argv = ["am", "start", "-a", "android.intent.action.VIEW", "-d", url];
655
+ return packageName === undefined ? argv : [...argv, "-p", packageName];
656
+ }
657
+ function monkeyLaunchArgv(packageName) {
658
+ return ["monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1"];
659
+ }
660
+ function resolveLauncherArgv(packageName) {
661
+ return ["cmd", "package", "resolve-activity", "--brief", "-c", "android.intent.category.LAUNCHER", packageName];
662
+ }
663
+ function startComponentArgv(component, options) {
664
+ return [
665
+ "am",
666
+ "start",
667
+ "-W",
668
+ ...options.stop ? ["-S"] : [],
669
+ "-a",
670
+ "android.intent.action.MAIN",
671
+ "-c",
672
+ "android.intent.category.LAUNCHER",
673
+ "-n",
674
+ component
675
+ ];
676
+ }
677
+ function forceStopArgv(packageName) {
678
+ return ["am", "force-stop", packageName];
679
+ }
680
+ function pmClearArgv(packageName) {
681
+ return ["pm", "clear", packageName];
682
+ }
683
+ var ANIMATION_SETTINGS = [
684
+ "window_animation_scale",
685
+ "transition_animation_scale",
686
+ "animator_duration_scale"
687
+ ];
688
+ function animationScaleArgvs() {
689
+ return ANIMATION_SETTINGS.map((key) => ["settings", "put", "global", key, "0"]);
690
+ }
691
+ function animationReadArgvs() {
692
+ return ANIMATION_SETTINGS.map((key) => ["settings", "get", "global", key]);
693
+ }
694
+ function animationRestoreArgvs(values) {
695
+ return ANIMATION_SETTINGS.map((key) => values[key] === "null" ? ["settings", "delete", "global", key] : ["settings", "put", "global", key, values[key]]);
696
+ }
697
+ function screencapArgv(adb, serial) {
698
+ return [adb, "-s", serial, "exec-out", "screencap", "-p"];
699
+ }
700
+ function getpropArgv(key) {
701
+ return ["getprop", key];
702
+ }
703
+ var WM_SIZE_ARGV = ["wm", "size"];
704
+ var WM_DENSITY_ARGV = ["wm", "density"];
705
+ var DUMP_FILE_PATH = "/sdcard/warden-ui.xml";
706
+ function dumpStdoutArgv() {
707
+ return ["uiautomator", "dump", "/dev/stdout"];
708
+ }
709
+ function dumpTtyHostArgv(adb, serial) {
710
+ return [adb, "-s", serial, "exec-out", "uiautomator", "dump", "/dev/tty"];
711
+ }
712
+ function dumpFileCommand(path = DUMP_FILE_PATH) {
713
+ const file = shellQuote(path);
714
+ return `rm -f ${file}; OUT=$(uiautomator dump ${file} 2>&1); cat ${file} 2>/dev/null || echo "$OUT"`;
715
+ }
716
+ function installArgv(adb, serial, apkPath) {
717
+ return [adb, "-s", serial, "install", "-r", apkPath];
718
+ }
719
+ function uninstallArgv(adb, serial, packageName) {
720
+ return [adb, "-s", serial, "uninstall", packageName];
721
+ }
722
+ function pmPathArgv(packageName) {
723
+ return ["pm", "path", packageName];
724
+ }
725
+ function pidofArgv(packageName) {
726
+ return ["pidof", packageName];
727
+ }
728
+ function rotationArgvs(rotation) {
729
+ return [
730
+ ["settings", "put", "system", "accelerometer_rotation", "0"],
731
+ ["settings", "put", "system", "user_rotation", String(rotation)]
732
+ ];
733
+ }
734
+ function userRotationArgv() {
735
+ return ["settings", "get", "system", "user_rotation"];
736
+ }
737
+ function uimodeGetArgv() {
738
+ return ["cmd", "uimode", "night"];
739
+ }
740
+ function uimodeSetArgv(dark) {
741
+ return ["cmd", "uimode", "night", dark ? "yes" : "no"];
742
+ }
743
+ function keycombinationArgv(keycodes) {
744
+ return ["input", "keycombination", ...keycodes.map(String)];
745
+ }
746
+ var KEYCODE_A = 29;
747
+ function selectAllArgv() {
748
+ return keycombinationArgv([modifierKeycode("ctrl"), KEYCODE_A]);
749
+ }
750
+ function modifierKeycode(modifier) {
751
+ switch (modifier) {
752
+ case "shift":
753
+ return 59;
754
+ case "cmd":
755
+ return 117;
756
+ case "alt":
757
+ return 57;
758
+ case "ctrl":
759
+ return 113;
760
+ }
761
+ }
762
+
763
+ // ../../packages/e2e/src/driver/android/android-state.ts
764
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
765
+ import { join as join3 } from "path";
766
+ import { z } from "zod";
767
+ var scaleValue = z.string().regex(/^(null|\d+(\.\d+)?)$/);
768
+ var stateSchema = z.strictObject({
769
+ sdk: z.number().int().optional(),
770
+ dumpRoute: z.enum(["stdout", "tty", "file"]).optional(),
771
+ uiadUnsupported: z.string().regex(/^[0-9a-f]{1,16}$/).optional(),
772
+ animations: z.strictObject({
773
+ window_animation_scale: scaleValue,
774
+ transition_animation_scale: scaleValue,
775
+ animator_duration_scale: scaleValue
776
+ }).optional()
777
+ });
778
+ var nodeStateFs = {
779
+ readFile: (path) => readFile(path, "utf8"),
780
+ writeFile: (path, data) => writeFile(path, data),
781
+ rename,
782
+ mkdir: async (path) => {
783
+ await mkdir(path, { recursive: true });
784
+ }
785
+ };
786
+ function androidStateDir(home = wardenHome(process.env)) {
787
+ return join3(home, "android");
788
+ }
789
+ function androidStateFile(dir, serial) {
790
+ return join3(dir, `${serial.replace(/[^A-Za-z0-9._:-]/g, "_")}.json`);
791
+ }
792
+ function createAndroidStateStore(options) {
793
+ const { dir, log } = options;
794
+ const fs = options.fs ?? nodeStateFs;
795
+ let queue = Promise.resolve();
796
+ async function load(serial) {
797
+ let text;
798
+ try {
799
+ text = await fs.readFile(androidStateFile(dir, serial));
800
+ } catch {
801
+ return {};
802
+ }
803
+ try {
804
+ const parsed = stateSchema.safeParse(JSON.parse(text));
805
+ return parsed.success ? stripUndefined(parsed.data) : {};
806
+ } catch {
807
+ return {};
808
+ }
809
+ }
810
+ async function save(serial, change) {
811
+ const before = await load(serial);
812
+ const after = stripUndefined(change(before));
813
+ const text = JSON.stringify(after);
814
+ if (text === JSON.stringify(before))
815
+ return;
816
+ const file = androidStateFile(dir, serial);
817
+ try {
818
+ await fs.mkdir(dir);
819
+ const temp = `${file}.${process.pid}.tmp`;
820
+ await fs.writeFile(temp, text);
821
+ await fs.rename(temp, file);
822
+ } catch (error) {
823
+ log?.(`could not save ${file}: ${error instanceof Error ? error.message : String(error)}`);
824
+ }
825
+ }
826
+ return {
827
+ read: load,
828
+ update(serial, change) {
829
+ const run = queue.then(() => save(serial, change));
830
+ queue = run.catch(() => {
831
+ return;
832
+ });
833
+ return run.catch(() => {
834
+ return;
835
+ });
836
+ }
837
+ };
838
+ }
839
+ function forSdk(state, sdk) {
840
+ return state.sdk === sdk ? state : {};
841
+ }
842
+ function patchFor(sdk, patch) {
843
+ return (state) => ({ ...forSdk(state, sdk), ...sdk !== undefined ? { sdk } : {}, ...patch });
844
+ }
845
+ function stripUndefined(state) {
846
+ return {
847
+ ...state.sdk !== undefined ? { sdk: state.sdk } : {},
848
+ ...state.dumpRoute !== undefined ? { dumpRoute: state.dumpRoute } : {},
849
+ ...state.uiadUnsupported !== undefined ? { uiadUnsupported: state.uiadUnsupported } : {},
850
+ ...state.animations !== undefined ? { animations: state.animations } : {}
851
+ };
852
+ }
853
+ var SCALE_LINE = /^(null|\d+(\.\d+)?)$/;
854
+ function parseAnimationValues(stdout) {
855
+ const lines = stdout.split(`
856
+ `).map((line) => line.trim()).filter((line) => SCALE_LINE.test(line));
857
+ const [window, transition, animator] = lines;
858
+ if (lines.length !== ANIMATION_SETTINGS.length || window === undefined || transition === undefined || animator === undefined) {
859
+ return;
860
+ }
861
+ return { window_animation_scale: window, transition_animation_scale: transition, animator_duration_scale: animator };
862
+ }
863
+ function planAnimations(current, pending) {
864
+ const original = (key) => current[key] === "0" && pending ? pending[key] : current[key];
865
+ const originals = {
866
+ window_animation_scale: original("window_animation_scale"),
867
+ transition_animation_scale: original("transition_animation_scale"),
868
+ animator_duration_scale: original("animator_duration_scale")
869
+ };
870
+ return ANIMATION_SETTINGS.every((key) => originals[key] === "0") ? undefined : originals;
871
+ }
872
+
873
+ // ../../packages/e2e/src/driver/android/android-animations.ts
874
+ var sameValues = (a, b) => JSON.stringify(a) === JSON.stringify(b);
875
+ async function disableAnimations(deps) {
876
+ const { session, serial, sdk, state, log } = deps;
877
+ const command = [...animationReadArgvs(), ...animationScaleArgvs()].map((argv) => shellCommand(argv)).join("; ");
878
+ let restorable = deps.pending;
879
+ const ran = await session.run(command);
880
+ if (!ran.success || ran.data.exitCode !== 0) {
881
+ log?.("could not disable animations");
882
+ } else {
883
+ const found = parseAnimationValues(ran.data.stdout);
884
+ if (found === undefined)
885
+ log?.("could not read the animation scales, so they will not be restored");
886
+ else
887
+ restorable = planAnimations(found, deps.pending);
888
+ }
889
+ if (state && !sameValues(restorable, deps.pending)) {
890
+ await state.update(serial, patchFor(sdk, { animations: restorable }));
891
+ }
892
+ return {
893
+ async restore() {
894
+ const originals = restorable;
895
+ if (originals === undefined)
896
+ return;
897
+ restorable = undefined;
898
+ const put = await session.run(animationRestoreArgvs(originals).map((argv) => shellCommand(argv)).join("; "));
899
+ if (!put.success || put.data.exitCode !== 0) {
900
+ log?.("could not restore the animation scales; the next driver on this device will");
901
+ return;
902
+ }
903
+ await state?.update(serial, patchFor(sdk, { animations: undefined }));
904
+ }
905
+ };
906
+ }
907
+
908
+ // ../../packages/e2e/src/driver/android/android-device.ts
909
+ var BOOT_TIMEOUT_MS = 30000;
910
+ var BOOT_POLL_MS = 500;
911
+ var realClock = { now: () => Date.now(), sleep: (ms) => Bun.sleep(ms) };
912
+ function parseWmSize(text) {
913
+ const override = /Override size:\s*(\d+)x(\d+)/.exec(text);
914
+ const physical = /Physical size:\s*(\d+)x(\d+)/.exec(text);
915
+ const m = override ?? physical;
916
+ if (!m)
917
+ return;
918
+ return { width: Number(m[1]), height: Number(m[2]) };
919
+ }
920
+ function parseWmDensity(text) {
921
+ const m = /Override density:\s*(\d+)/.exec(text) ?? /Physical density:\s*(\d+)/.exec(text);
922
+ return m ? Number(m[1]) / 160 : undefined;
923
+ }
924
+ var isFatal = (error) => error.code === "E_HELPER_MISSING" || error.code === "E_INTERNAL";
925
+ var BOOT_PROBE = [
926
+ getpropArgv("sys.boot_completed"),
927
+ getpropArgv("ro.build.version.sdk"),
928
+ WM_SIZE_ARGV,
929
+ WM_DENSITY_ARGV
930
+ ].map((argv) => shellCommand(argv)).join("; ");
931
+ function sdkFromProbe(stdout) {
932
+ const line = stdout.trimStart().split(`
933
+ `)[1]?.trim() ?? "";
934
+ return /^\d+$/.test(line) ? Number(line) : undefined;
935
+ }
936
+ function screenFromProbe(stdout) {
937
+ const size = parseWmSize(stdout);
938
+ if (!size)
939
+ return;
940
+ return { ...size, scale: parseWmDensity(stdout) ?? 1, rotation: 0 };
941
+ }
942
+ async function waitForBoot(session, serial, clock, opts = {}) {
943
+ const timeoutMs = opts.timeoutMs ?? BOOT_TIMEOUT_MS;
944
+ const deadline = clock.now() + timeoutMs;
945
+ for (;; ) {
946
+ const res = await session.run(BOOT_PROBE, { timeoutMs: 5000 });
947
+ if (res.success && res.data.stdout.trimStart().startsWith("1")) {
948
+ return { success: true, data: { screen: screenFromProbe(res.data.stdout), sdk: sdkFromProbe(res.data.stdout) } };
949
+ }
950
+ if (!res.success && isFatal(res.error))
951
+ return res;
952
+ if (clock.now() >= deadline) {
953
+ return driverErr("E_NOT_BOOTED", `${serial} did not report sys.boot_completed=1 within ${timeoutMs}ms`, {
954
+ detail: res.success ? { stdout: res.data.stdout } : { error: res.error }
955
+ });
956
+ }
957
+ opts.log?.(`waiting for ${serial} to boot`);
958
+ await clock.sleep(BOOT_POLL_MS);
959
+ }
960
+ }
961
+ async function readScreen(session, serial) {
962
+ const size = await session.run(shellCommand(WM_SIZE_ARGV));
963
+ if (!size.success)
964
+ return size;
965
+ const parsed = parseWmSize(size.data.stdout);
966
+ if (!parsed) {
967
+ return driverErr("E_INTERNAL", `cannot parse \`wm size\` for ${serial}: ${size.data.stdout.trim().slice(0, 120)}`);
968
+ }
969
+ const density = await session.run(shellCommand(WM_DENSITY_ARGV));
970
+ const scale = (density.success ? parseWmDensity(density.data.stdout) : undefined) ?? 1;
971
+ return { success: true, data: { ...parsed, scale, rotation: 0 } };
972
+ }
973
+ function hierarchyRotation(xml) {
974
+ const m = /<hierarchy[^>]*\brotation="(\d)"/.exec(xml);
975
+ return m ? Number(m[1]) : 0;
976
+ }
977
+ function rotatedScreen(base, rotation) {
978
+ const landscape = rotation % 2 === 1;
979
+ const portraitW = Math.min(base.width, base.height);
980
+ const portraitH = Math.max(base.width, base.height);
981
+ return {
982
+ ...base,
983
+ width: landscape ? portraitH : portraitW,
984
+ height: landscape ? portraitW : portraitH,
985
+ rotation
986
+ };
987
+ }
988
+ function rotationToOrientation(rotation) {
989
+ switch (rotation) {
990
+ case 1:
991
+ return "landscapeLeft";
992
+ case 2:
993
+ return "portraitUpsideDown";
994
+ case 3:
995
+ return "landscapeRight";
996
+ default:
997
+ return "portrait";
998
+ }
999
+ }
1000
+ function orientationToRotation(orientation) {
1001
+ switch (orientation) {
1002
+ case "landscapeLeft":
1003
+ return 1;
1004
+ case "portraitUpsideDown":
1005
+ return 2;
1006
+ case "landscapeRight":
1007
+ return 3;
1008
+ case "portrait":
1009
+ return 0;
1010
+ }
1011
+ }
1012
+ async function runChecked(session, cmd, failure = "E_HID", timeoutMs) {
1013
+ const res = await session.run(cmd, timeoutMs === undefined ? undefined : { timeoutMs });
1014
+ if (!res.success)
1015
+ return res;
1016
+ if (res.data.exitCode !== 0) {
1017
+ return driverErr(failure, `\`${cmd}\` exited ${res.data.exitCode}: ${res.data.stdout.trim().slice(0, 200)}`);
1018
+ }
1019
+ return { success: true, data: res.data.stdout };
1020
+ }
1021
+
1022
+ // ../../packages/e2e/src/driver/android/android-app.ts
1023
+ var ok2 = { success: true, data: undefined };
1024
+ var NO_ACTIVITY = /No activities found|monkey aborted|Error type 3|does not exist|Unknown package/i;
1025
+ var START_WAIT_TIMEOUT_MS = 30000;
1026
+ var START_PROBE_TIMEOUT_MS = 5000;
1027
+ var COMPONENT = /^[A-Za-z0-9_.]+\/[A-Za-z0-9_.$]+$/;
1028
+ function parseLauncherComponent(stdout) {
1029
+ const last = stdout.split(`
1030
+ `).map((line) => line.trim()).filter((line) => line !== "").at(-1);
1031
+ return last !== undefined && COMPONENT.test(last) ? last : undefined;
1032
+ }
1033
+ function parseStartWait(stdout) {
1034
+ const field = (name) => new RegExp(`^${name}: *(.+)$`, "m").exec(stdout)?.[1]?.trim();
1035
+ const parsed = {};
1036
+ const status = field("Status");
1037
+ const launchState = field("LaunchState");
1038
+ const totalTime = Number(field("TotalTime"));
1039
+ const error = /^Error: *(.+)$/m.exec(stdout)?.[1]?.trim();
1040
+ if (status !== undefined)
1041
+ parsed.status = status;
1042
+ if (launchState !== undefined)
1043
+ parsed.launchState = launchState;
1044
+ if (Number.isFinite(totalTime) && field("TotalTime") !== undefined)
1045
+ parsed.totalTimeMs = totalTime;
1046
+ if (error !== undefined)
1047
+ parsed.error = error;
1048
+ return parsed;
1049
+ }
1050
+ function noteStarted(started) {
1051
+ noteSpanDetail("launch", "am-start");
1052
+ if (started.status !== undefined)
1053
+ noteSpanDetail("startStatus", started.status);
1054
+ if (started.launchState !== undefined)
1055
+ noteSpanDetail("launchState", started.launchState);
1056
+ if (started.totalTimeMs !== undefined)
1057
+ noteSpanDetail("totalTimeMs", started.totalTimeMs);
1058
+ }
1059
+ function createAppCommands(deps) {
1060
+ const { session, exec, adb, serial } = deps;
1061
+ const launchers = new Map;
1062
+ const launcherOf = async (bundleId) => {
1063
+ const known = launchers.get(bundleId);
1064
+ if (known !== undefined)
1065
+ return known;
1066
+ const res = await session.run(shellCommand(resolveLauncherArgv(bundleId)));
1067
+ if (!res.success || res.data.exitCode !== 0)
1068
+ return;
1069
+ const component = parseLauncherComponent(res.data.stdout);
1070
+ if (component !== undefined)
1071
+ launchers.set(bundleId, component);
1072
+ return component;
1073
+ };
1074
+ const hasProcess = async (bundleId) => {
1075
+ const probe = await session.run(shellCommand(pidofArgv(bundleId)), {
1076
+ timeoutMs: deps.startProbeTimeoutMs ?? START_PROBE_TIMEOUT_MS
1077
+ });
1078
+ return probe.success && probe.data.exitCode === 0 && /\d/.test(probe.data.stdout);
1079
+ };
1080
+ const startWaiting = async (bundleId, relaunch) => {
1081
+ const component = await launcherOf(bundleId);
1082
+ if (component === undefined)
1083
+ return { success: true, data: false };
1084
+ const res = await session.run(shellCommand(startComponentArgv(component, { stop: relaunch })), {
1085
+ timeoutMs: deps.startWaitTimeoutMs ?? START_WAIT_TIMEOUT_MS
1086
+ });
1087
+ if (!res.success && res.error.code === "E_TIMEOUT") {
1088
+ if (!await hasProcess(bundleId))
1089
+ return res;
1090
+ noteStarted({ launchState: "timeout" });
1091
+ return { success: true, data: true };
1092
+ }
1093
+ if (!res.success)
1094
+ return res;
1095
+ const started = parseStartWait(res.data.stdout);
1096
+ if (res.data.exitCode !== 0 || started.error !== undefined) {
1097
+ launchers.delete(bundleId);
1098
+ return { success: true, data: false };
1099
+ }
1100
+ noteStarted(started);
1101
+ return { success: true, data: true };
1102
+ };
1103
+ async function hostAdb(argv) {
1104
+ try {
1105
+ const res = await exec(argv, { timeoutMs: 120000 });
1106
+ return { ok: res.exitCode === 0, text: `${res.stdout}
1107
+ ${res.stderr}`.trim() };
1108
+ } catch (error) {
1109
+ return { ok: false, text: error instanceof Error ? error.message : String(error) };
1110
+ }
1111
+ }
1112
+ const terminate = async (bundleId) => {
1113
+ const res = await runChecked(session, shellCommand(forceStopArgv(bundleId)), "E_APP_UNAVAILABLE");
1114
+ return res.success ? ok2 : res;
1115
+ };
1116
+ const isInstalled = async (bundleId) => {
1117
+ const res = await session.run(shellCommand(pmPathArgv(bundleId)));
1118
+ if (!res.success)
1119
+ return res;
1120
+ return { success: true, data: res.data.exitCode === 0 && res.data.stdout.includes("package:") };
1121
+ };
1122
+ const isRunning = async (bundleId) => {
1123
+ const res = await session.run(shellCommand(pidofArgv(bundleId)));
1124
+ if (!res.success)
1125
+ return res;
1126
+ return { success: true, data: res.data.exitCode === 0 && /\d/.test(res.data.stdout) };
1127
+ };
1128
+ const monkeyLaunch = async (bundleId, relaunch) => {
1129
+ if (relaunch) {
1130
+ const stopped = await terminate(bundleId);
1131
+ if (!stopped.success)
1132
+ return stopped;
1133
+ }
1134
+ noteSpanDetail("launch", "monkey");
1135
+ const res = await session.run(shellCommand(monkeyLaunchArgv(bundleId)));
1136
+ if (!res.success)
1137
+ return res;
1138
+ if (res.data.exitCode !== 0 || NO_ACTIVITY.test(res.data.stdout)) {
1139
+ return driverErr("E_APP_UNAVAILABLE", `cannot launch ${bundleId}: ${res.data.stdout.trim().slice(0, 200)}`);
1140
+ }
1141
+ return ok2;
1142
+ };
1143
+ const launch = async (bundleId, opts) => {
1144
+ if ((opts?.args?.length ?? 0) > 0 || Object.keys(opts?.env ?? {}).length > 0) {
1145
+ return driverErr("E_UNSUPPORTED", "Android launch does not support args/env in v1 (use openUrl with a deep link for dev-client Metro urls)");
1146
+ }
1147
+ const relaunch = opts?.relaunch !== false;
1148
+ const waited = await startWaiting(bundleId, relaunch);
1149
+ if (!waited.success)
1150
+ return waited;
1151
+ return waited.data ? ok2 : monkeyLaunch(bundleId, relaunch);
1152
+ };
1153
+ const openUrl = async (url) => {
1154
+ const res = await session.run(shellCommand(startUrlArgv(url, deps.appPackage)));
1155
+ if (!res.success)
1156
+ return res;
1157
+ if (res.data.exitCode !== 0 || /^Error:|Exception/m.test(res.data.stdout)) {
1158
+ return driverErr("E_APP_UNAVAILABLE", `no activity handles ${url}: ${res.data.stdout.trim().slice(0, 200)}`);
1159
+ }
1160
+ return ok2;
1161
+ };
1162
+ const install = async (path) => {
1163
+ launchers.clear();
1164
+ const res = await hostAdb(installArgv(adb, serial, path));
1165
+ return res.ok && res.text.includes("Success") ? ok2 : driverErr("E_IO", `adb install failed: ${res.text.slice(-200)}`);
1166
+ };
1167
+ const uninstall = async (bundleId) => {
1168
+ launchers.delete(bundleId);
1169
+ const installed = await isInstalled(bundleId);
1170
+ if (!installed.success)
1171
+ return installed;
1172
+ if (!installed.data)
1173
+ return ok2;
1174
+ const res = await hostAdb(uninstallArgv(adb, serial, bundleId));
1175
+ return res.ok && res.text.includes("Success") ? ok2 : driverErr("E_IO", `adb uninstall failed: ${res.text.slice(-200)}`);
1176
+ };
1177
+ const clearData = async (bundleId) => {
1178
+ const res = await runChecked(session, shellCommand(pmClearArgv(bundleId)), "E_APP_UNAVAILABLE");
1179
+ if (!res.success)
1180
+ return res;
1181
+ return res.data.includes("Success") ? ok2 : driverErr("E_APP_UNAVAILABLE", `pm clear ${bundleId}: ${res.data.trim()}`);
1182
+ };
1183
+ return { launch, terminate, openUrl, install, uninstall, isInstalled, isRunning, clearData };
1184
+ }
1185
+
1186
+ // ../../packages/e2e/src/driver/android/android-dump.ts
1187
+ var DUMP_ROUTES = ["stdout", "tty", "file"];
1188
+ var TTY_FIRST_SDK = 30;
1189
+ function routeOrder(sdk, known) {
1190
+ const guess = sdk !== undefined && sdk >= TTY_FIRST_SDK ? ["tty", "stdout", "file"] : DUMP_ROUTES;
1191
+ return known ? [known, ...guess.filter((r) => r !== known)] : [...guess];
1192
+ }
1193
+ var DUMP_TIMEOUT_MS = 20000;
1194
+ function createDumper(deps) {
1195
+ let known = deps.initialRoute;
1196
+ async function fetchRoute(route) {
1197
+ if (route === "tty") {
1198
+ try {
1199
+ const res = await deps.exec(dumpTtyHostArgv(deps.adb, deps.serial), { timeoutMs: DUMP_TIMEOUT_MS });
1200
+ return { text: res.stdout };
1201
+ } catch (error) {
1202
+ return { error: { code: "E_IO", message: String(error), retryable: false } };
1203
+ }
1204
+ }
1205
+ const cmd = route === "stdout" ? shellCommand(dumpStdoutArgv()) : dumpFileCommand();
1206
+ const res = await deps.session.run(cmd, { timeoutMs: DUMP_TIMEOUT_MS });
1207
+ return res.success ? { text: res.data.stdout } : { error: res.error };
1208
+ }
1209
+ async function tryRoutes(order) {
1210
+ const failure = {};
1211
+ for (const route of order) {
1212
+ const fetchStarted = Date.now();
1213
+ const result = await fetchRoute(route);
1214
+ addSpanDetail("dumpMs", Date.now() - fetchStarted);
1215
+ addSpanDetail("dumpRoutesTried", 1);
1216
+ if ("error" in result) {
1217
+ failure.error = result.error;
1218
+ deps.log?.(`uiautomator dump via ${route} failed: ${result.error.code}`);
1219
+ } else if (result.text.includes("<hierarchy")) {
1220
+ return { xml: result.text, route };
1221
+ } else {
1222
+ failure.output = result.text;
1223
+ deps.log?.(`uiautomator dump via ${route} returned no hierarchy`);
1224
+ }
1225
+ }
1226
+ return failure;
1227
+ }
1228
+ return {
1229
+ route: () => known,
1230
+ async dump() {
1231
+ const found = await tryRoutes(routeOrder(deps.sdk, known));
1232
+ if ("xml" in found) {
1233
+ noteSpanDetail("dumpRoute", found.route);
1234
+ if (known !== found.route)
1235
+ deps.log?.(`uiautomator dump route: ${found.route}`);
1236
+ if (known !== found.route)
1237
+ deps.onRoute?.(found.route);
1238
+ known = found.route;
1239
+ return { success: true, data: found };
1240
+ }
1241
+ if (found.output === undefined && found.error)
1242
+ return { success: false, error: found.error };
1243
+ const output = found.output ?? "";
1244
+ return driverErr("E_AX_UNAVAILABLE", `uiautomator produced no hierarchy: ${output.trim().slice(0, 160)}`, {
1245
+ detail: { output: output.slice(0, 400) }
1246
+ });
1247
+ }
1248
+ };
1249
+ }
1250
+
1251
+ // ../../packages/e2e/src/driver/android/uiad-protocol.ts
1252
+ import { z as z2 } from "zod";
1253
+ var UIAD_PROTOCOL = 1;
1254
+ var UIAD_MAIN_CLASS = "nz.co.delacour.warden.uiad.Main";
1255
+ var STRING_FIELDS = ["class", "pkg", "id", "text", "desc", "hint"];
1256
+ var FLAG_FIELDS = [
1257
+ "clickable",
1258
+ "longClickable",
1259
+ "checkable",
1260
+ "checked",
1261
+ "scrollable",
1262
+ "selected",
1263
+ "focused",
1264
+ "focusable",
1265
+ "password",
1266
+ "editable",
1267
+ "disabled",
1268
+ "invisible"
1269
+ ];
1270
+ function isBounds(value) {
1271
+ return Array.isArray(value) && value.length === 4 && value.every((n) => typeof n === "number");
1272
+ }
1273
+ function isRecord(value) {
1274
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1275
+ }
1276
+ function copyFields(node, from, keys, kind, path) {
1277
+ for (const key of keys) {
1278
+ const field = from[key];
1279
+ if (field === undefined)
1280
+ continue;
1281
+ if (typeof field !== kind)
1282
+ return `${path}.${key}: not a ${kind}`;
1283
+ Object.assign(node, { [key]: field });
1284
+ }
1285
+ return;
1286
+ }
1287
+ function readNode(value, path = "elements") {
1288
+ if (!isRecord(value))
1289
+ return `${path}: not an object`;
1290
+ if (!isBounds(value.bounds))
1291
+ return `${path}.bounds: not [l, t, r, b]`;
1292
+ const node = { bounds: value.bounds };
1293
+ const wrong = copyFields(node, value, STRING_FIELDS, "string", path) ?? copyFields(node, value, FLAG_FIELDS, "boolean", path) ?? copyFields(node, value, ["order"], "number", path);
1294
+ if (wrong !== undefined)
1295
+ return wrong;
1296
+ if (value.children === undefined)
1297
+ return node;
1298
+ if (!Array.isArray(value.children))
1299
+ return `${path}.children: not an array`;
1300
+ const children = [];
1301
+ for (const [index, child] of value.children.entries()) {
1302
+ const read = readNode(child, `${path}.children[${index}]`);
1303
+ if (typeof read === "string")
1304
+ return read;
1305
+ children.push(read);
1306
+ }
1307
+ node.children = children;
1308
+ return node;
1309
+ }
1310
+ var screenSchema = z2.object({
1311
+ width: z2.number(),
1312
+ height: z2.number(),
1313
+ scale: z2.number().optional(),
1314
+ rotation: z2.number().optional()
1315
+ });
1316
+ var healthSchema = z2.looseObject({
1317
+ version: z2.string(),
1318
+ protocol: z2.number(),
1319
+ sdk: z2.number(),
1320
+ release: z2.string().optional(),
1321
+ model: z2.string().optional(),
1322
+ uid: z2.number().optional(),
1323
+ pid: z2.number().optional(),
1324
+ uiAutomation: z2.looseObject({ state: z2.string(), connect: z2.string().optional(), connectMs: z2.number().optional() }),
1325
+ events: z2.looseObject({ count: z2.number(), lastAgoMs: z2.number() }).optional(),
1326
+ uptimeMs: z2.number().optional(),
1327
+ screen: screenSchema.optional(),
1328
+ displayError: z2.string().optional()
1329
+ });
1330
+ var settleDataSchema = z2.looseObject({
1331
+ quiet: z2.boolean(),
1332
+ waitedMs: z2.number(),
1333
+ via: z2.literal("events"),
1334
+ events: z2.number(),
1335
+ firstEventMs: z2.number().optional(),
1336
+ reason: z2.string().optional()
1337
+ });
1338
+ var textDataSchema = z2.looseObject({
1339
+ text: z2.string(),
1340
+ caret: z2.number().optional(),
1341
+ caretPlaced: z2.boolean().optional()
1342
+ });
1343
+ var snapshotEnvelope = z2.looseObject({
1344
+ backend: z2.literal("uiad"),
1345
+ screen: screenSchema,
1346
+ windows: z2.array(z2.unknown()).optional(),
1347
+ elements: z2.array(z2.unknown()),
1348
+ truncated: z2.boolean(),
1349
+ nodes: z2.number(),
1350
+ readMs: z2.number(),
1351
+ ms: z2.number()
1352
+ });
1353
+ function readSnapshotData(data) {
1354
+ const envelope = snapshotEnvelope.safeParse(data);
1355
+ if (!envelope.success) {
1356
+ const issue = envelope.error.issues[0];
1357
+ return issue ? `${issue.path.join(".") || "(snapshot)"}: ${issue.message}` : "invalid snapshot";
1358
+ }
1359
+ const elements = [];
1360
+ for (const [index, raw] of envelope.data.elements.entries()) {
1361
+ const node = readNode(raw, `elements[${index}]`);
1362
+ if (typeof node === "string")
1363
+ return node;
1364
+ elements.push(node);
1365
+ }
1366
+ return { ...envelope.data, elements };
1367
+ }
1368
+ var readyEventSchema = z2.object({
1369
+ event: z2.literal("ready"),
1370
+ protocol: z2.number(),
1371
+ version: z2.string(),
1372
+ data: healthSchema
1373
+ });
1374
+ var eventSchema = z2.discriminatedUnion("event", [readyEventSchema, logEventSchema, fatalEventSchema]);
1375
+ function parseUiadLine(line) {
1376
+ let json;
1377
+ try {
1378
+ json = JSON.parse(line);
1379
+ } catch {
1380
+ return { success: false, reason: "not JSON" };
1381
+ }
1382
+ if (isRecord(json) && "event" in json) {
1383
+ const event = eventSchema.safeParse(json);
1384
+ return event.success ? { success: true, message: { kind: "event", event: event.data } } : { success: false, reason: `bad event: ${event.error.message}` };
1385
+ }
1386
+ const response = responseSchema.safeParse(json);
1387
+ return response.success ? { success: true, message: { kind: "response", response: response.data } } : { success: false, reason: `bad response: ${response.error.message}` };
1388
+ }
1389
+
1390
+ // ../../packages/e2e/src/driver/android/uiad-client.ts
1391
+ var DEFAULT_READY_TIMEOUT_MS2 = 5000;
1392
+ var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
1393
+ var DEFAULT_GRACE_MS = 1000;
1394
+ var SHUTDOWN_WAIT_MS = 300;
1395
+ var NOT_SENT = { sent: false };
1396
+ function schemaReader(schema) {
1397
+ return (data) => {
1398
+ const parsed = schema.safeParse(data);
1399
+ return parsed.success ? { ok: true, value: parsed.data } : { ok: false, reason: parsed.error.message };
1400
+ };
1401
+ }
1402
+ var readSnapshot = (data) => {
1403
+ const value = readSnapshotData(data);
1404
+ return typeof value === "string" ? { ok: false, reason: value } : { ok: true, value };
1405
+ };
1406
+ function through(result, read, op) {
1407
+ if (!result.success)
1408
+ return result;
1409
+ const checked = read(result.data);
1410
+ return checked.ok ? { success: true, data: checked.value } : {
1411
+ success: false,
1412
+ error: driverError("E_INTERNAL", `warden-uiad \`${op}\` reply is malformed: ${checked.reason}`)
1413
+ };
1414
+ }
1415
+ function exitMessage(info) {
1416
+ const exit = info.exitCode === null ? "" : ` (exit ${info.exitCode})`;
1417
+ const tail = info.stderrTail.length > 0 ? `: ${info.stderrTail.slice(-3).join(" | ")}` : "";
1418
+ return `warden-uiad exited${exit}${tail}`;
1419
+ }
1420
+ async function startUiad(options) {
1421
+ const { log } = options;
1422
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
1423
+ const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS2;
1424
+ const pending = new Map;
1425
+ const deathHandlers = [];
1426
+ let seq = 0;
1427
+ let alive = true;
1428
+ let closing = false;
1429
+ let transport;
1430
+ let settleHandshake = () => {
1431
+ return;
1432
+ };
1433
+ let handshaken = false;
1434
+ const failAll = (error) => {
1435
+ for (const [id, entry] of pending) {
1436
+ clearTimeout(entry.timer);
1437
+ pending.delete(id);
1438
+ entry.resolve({ success: false, error });
1439
+ }
1440
+ };
1441
+ const onEvent = (event) => {
1442
+ if (event.event === "log") {
1443
+ log?.(`uiad ${event.level}: ${event.message}`);
1444
+ } else if (event.event === "ready") {
1445
+ settleHandshake(event.protocol === UIAD_PROTOCOL ? { success: true, data: event.data } : {
1446
+ success: false,
1447
+ error: driverError("E_UNSUPPORTED", `warden-uiad speaks protocol ${event.protocol}, this client speaks ${UIAD_PROTOCOL}`)
1448
+ });
1449
+ } else {
1450
+ const error = mapWireError(event.error);
1451
+ settleHandshake({ success: false, error });
1452
+ failAll(error);
1453
+ }
1454
+ };
1455
+ const onResponse = (response) => {
1456
+ const entry = pending.get(response.id);
1457
+ if (!entry) {
1458
+ log?.(`uiad: reply to nothing (id ${response.id})`);
1459
+ return;
1460
+ }
1461
+ clearTimeout(entry.timer);
1462
+ pending.delete(response.id);
1463
+ entry.resolve(response.ok ? { success: true, data: response.data } : { success: false, error: mapWireError(response.error) });
1464
+ };
1465
+ const onLine = (line) => {
1466
+ const parsed = parseUiadLine(line);
1467
+ if (!parsed.success) {
1468
+ log?.(`uiad: ignoring a line (${parsed.reason}): ${line.slice(0, 120)}`);
1469
+ } else if (parsed.message.kind === "event") {
1470
+ onEvent(parsed.message.event);
1471
+ } else {
1472
+ onResponse(parsed.message.response);
1473
+ }
1474
+ };
1475
+ const onClose = (info) => {
1476
+ const wasAlive = alive;
1477
+ alive = false;
1478
+ const crash = driverError("E_HELPER_CRASHED", exitMessage(info), {
1479
+ detail: { exitCode: info.exitCode, stderrTail: info.stderrTail }
1480
+ });
1481
+ settleHandshake({ success: false, error: crash });
1482
+ failAll(crash);
1483
+ if (wasAlive && !closing)
1484
+ for (const handler of deathHandlers)
1485
+ handler(info);
1486
+ };
1487
+ const factory = stdioTransport({
1488
+ cmd: options.cmd,
1489
+ ...options.spawn ? { spawn: options.spawn } : { spawn: bunSpawn },
1490
+ graceMs: options.graceMs ?? DEFAULT_GRACE_MS
1491
+ });
1492
+ const waiting = new Promise((resolve) => {
1493
+ settleHandshake = (result) => {
1494
+ if (handshaken)
1495
+ return;
1496
+ handshaken = true;
1497
+ resolve(result);
1498
+ };
1499
+ });
1500
+ const opened = await factory({
1501
+ onLine,
1502
+ onDiagnostic: (line) => log?.(`uiad stderr: ${line}`),
1503
+ onClose
1504
+ });
1505
+ if (!opened.success)
1506
+ return opened;
1507
+ transport = opened.data;
1508
+ const timer = setTimeout(() => settleHandshake({
1509
+ success: false,
1510
+ error: driverError("E_TIMEOUT", `warden-uiad sent no ready event within ${readyTimeoutMs}ms`)
1511
+ }), readyTimeoutMs);
1512
+ const ready = await waiting;
1513
+ clearTimeout(timer);
1514
+ if (!ready.success) {
1515
+ alive = false;
1516
+ closing = true;
1517
+ await transport.close().catch(() => {
1518
+ return;
1519
+ });
1520
+ return ready;
1521
+ }
1522
+ const request = (op, params = {}, opts = {}) => new Promise((resolve) => {
1523
+ if (!alive || transport === undefined) {
1524
+ resolve({
1525
+ success: false,
1526
+ error: driverError("E_HELPER_CRASHED", "warden-uiad is not running", { detail: NOT_SENT })
1527
+ });
1528
+ return;
1529
+ }
1530
+ const id = String(++seq);
1531
+ const timeoutMs = opts.timeoutMs ?? requestTimeoutMs;
1532
+ const entry = {
1533
+ op,
1534
+ resolve,
1535
+ timer: setTimeout(() => {
1536
+ pending.delete(id);
1537
+ const error = driverError("E_TIMEOUT", `warden-uiad did not answer \`${op}\` within ${timeoutMs}ms`);
1538
+ resolve({ success: false, error });
1539
+ alive = false;
1540
+ failAll(error);
1541
+ transport?.close().catch(() => {
1542
+ return;
1543
+ });
1544
+ }, timeoutMs)
1545
+ };
1546
+ pending.set(id, entry);
1547
+ try {
1548
+ transport.send(encodeRequest({ ...params, id, op }));
1549
+ } catch (error) {
1550
+ clearTimeout(entry.timer);
1551
+ pending.delete(id);
1552
+ resolve({
1553
+ success: false,
1554
+ error: driverError("E_HELPER_CRASHED", `cannot write to warden-uiad: ${error instanceof Error ? error.message : String(error)}`, { detail: NOT_SENT })
1555
+ });
1556
+ }
1557
+ });
1558
+ return {
1559
+ success: true,
1560
+ data: {
1561
+ health: ready.data,
1562
+ alive: () => alive,
1563
+ request,
1564
+ async snapshot(params = {}, opts = {}) {
1565
+ return through(await request("snapshot", { ...params }, opts), readSnapshot, "snapshot");
1566
+ },
1567
+ async settle(params) {
1568
+ return through(await request("settle", params, { timeoutMs: params.timeoutMs + requestTimeoutMs }), schemaReader(settleDataSchema), "settle");
1569
+ },
1570
+ async typeText(text) {
1571
+ return through(await request("typeText", { text }), schemaReader(textDataSchema), "typeText");
1572
+ },
1573
+ onDeath(handler) {
1574
+ deathHandlers.push(handler);
1575
+ },
1576
+ async close() {
1577
+ if (closing)
1578
+ return;
1579
+ closing = true;
1580
+ if (alive && transport !== undefined) {
1581
+ alive = false;
1582
+ const bye = new Promise((resolve) => {
1583
+ pending.set("shutdown", {
1584
+ op: "shutdown",
1585
+ resolve: () => resolve(),
1586
+ timer: setTimeout(resolve, SHUTDOWN_WAIT_MS)
1587
+ });
1588
+ });
1589
+ try {
1590
+ transport.send(encodeRequest({ id: "shutdown", op: "shutdown" }));
1591
+ await bye;
1592
+ } catch {}
1593
+ }
1594
+ failAll(driverError("E_HELPER_CRASHED", "warden-uiad was closed"));
1595
+ await transport?.close().catch(() => {
1596
+ return;
1597
+ });
1598
+ }
1599
+ }
1600
+ };
1601
+ }
1602
+ function neverSent(error) {
1603
+ const detail = error.detail;
1604
+ return typeof detail === "object" && detail !== null && "sent" in detail && detail.sent === false;
1605
+ }
1606
+ function isBusy(error) {
1607
+ const detail = error.detail;
1608
+ return error.code === "E_AX_UNAVAILABLE" && typeof detail === "object" && detail !== null && "reason" in detail && detail.reason === "uiautomation_busy";
1609
+ }
1610
+
1611
+ // ../../packages/e2e/src/driver/android/uiad-dex.generated.ts
1612
+ var UIAD_DEX = {
1613
+ protocol: 1,
1614
+ version: "0.3.0",
1615
+ bytes: 31124,
1616
+ dexSha256: "d6cb11a3bffc6b00d3ea46c4fdd1721c23973b72b0769f26c2478e7a1769be4c",
1617
+ sourceSha256: "d03ff47b9a5890fcc8de5d76b504b05c90402edda60ac69f765c78ec8195cfdc",
1618
+ base64: "ZGV4CjAzOAALAG3vu0DO4qAAXoGM16R6HlujKaD7mhyUeQAAcAAAAHhWNBIAAAAAAAAAAMR4AAA8AgAAcAAAAF0AAABgCQAAeQAAANQKAAA+AAAAgBAAAOkAAABwEgAACAAAALgZAADcXgAAuBoAAM5TAADQUwAA01MAAN9TAADqUwAA9lMAAPpTAAD9UwAAAFQAABBUAAAdVAAAKFQAADlUAABIVAAAV1QAAGFUAABzVAAAglQAAJBUAACiVAAAs1QAAL9UAADLVAAA2lQAAOVUAADzVAAAAFUAAAtVAAATVQAAHlUAADRVAABIVQAAX1UAAGpVAABzVQAAfVUAAItVAAChVQAArVUAAMJVAADPVQAA3lUAAAZWAAAUVgAAH1YAACtWAAA2VgAAQFYAAHFWAAB6VgAAhFYAAI1WAACoVgAAtlYAAMNWAADGVgAAzVYAANFWAADYVgAA4lYAAOpWAADvVgAAElcAADdXAABeVwAAjFcAALVXAAC8VwAAz1cAANJXAADWVwAA2VcAAN1XAADiVwAA9FcAAANYAAAKWAAAFlgAABxYAAAqWAAAOVgAADxYAABQWAAAYFgAAGNYAABoWAAAbFgAAHFYAAB0WAAAeFgAAHxYAACBWAAAhVgAAIpYAACVWAAAmFgAAJxYAACgWAAAq1gAALRYAAC4WAAAvVgAAMVYAADJWAAA0FgAANVYAADeWAAA51gAAPhYAAD8WAAAA1kAAAhZAAANWQAAE1kAABdZAABQWQAAiVkAAKVZAADPWQAA6lkAAANaAAAfWgAAM1oAAEhaAABkWgAAeVoAAI9aAACpWgAAwFoAAN9aAAD3WgAAElsAACtbAABVWwAAg1sAAJ9bAADQWwAABFwAADpcAABeXAAAg1wAAKNcAADGXAAA7FwAAAtdAAAnXQAARl0AAGBdAACBXQAAm10AALddAADOXQAA5V0AAAJeAAAaXgAAMV4AAENeAABYXgAAcl4AAIVeAACZXgAAsF4AAMNeAADYXgAA+l4AAAxfAAAvXwAAQ18AAFlfAABtXwAAiF8AAJxfAACwXwAAx18AAOJfAAADYAAAMmAAAE5gAABqYAAAgWAAAJdgAACpYAAAu2AAAN5gAAADYQAAJmEAAEphAAB0YQAApmEAAM1hAADvYQAABWIAAB9iAAA2YgAAPWIAAEhiAABRYgAAVmIAAGBiAABpYgAAcmIAAINiAACJYgAAk2IAAJpiAADZYgAACmMAABRjAAAXYwAAIGMAACRjAAArYwAAL2MAADZjAAA/YwAASWMAAE1jAABSYwAAWmMAAF9jAABlYwAAamMAAG5jAAB0YwAAd2MAAHtjAAB/YwAAhGMAAIhjAACOYwAAlGMAAJljAACeYwAApWMAAKxjAACwYwAAtGMAAN9jAAAOZAAAImQAADdkAABMZAAAUmQAAFZkAABaZAAAXmQAAGJkAABmZAAAamQAAHpkAAB+ZAAAoGQAALtkAADHZAAA02QAAN9kAADrZAAA92QAAARlAAAMZQAAGGUAAB1lAAAiZQAATGUAAGBlAACFZQAAqWUAANhlAADgZQAA7GUAAPhlAAAGZgAADmYAAB1mAAAlZgAAMGYAADlmAABAZgAATGYAAFdmAABeZgAAZGYAAG5mAAB3ZgAAgmYAAJBmAACbZgAAp2YAALdmAADBZgAAyGYAAM9mAADYZgAA3mYAAOdmAADxZgAA/WYAAAZnAAAUZwAAGmcAACZnAAAwZwAAQGcAAEhnAABNZwAAWGcAAGRnAAB1ZwAAgGcAAIxnAACSZwAAmWcAAL1nAADIZwAAzmcAANVnAADcZwAA52cAAPBnAAD+ZwAAB2gAABBoAAAuaAAANWgAADxoAABFaAAAaGgAAIRoAACgaAAArmgAALNoAADGaAAA0GgAANpoAADkaAAA82gAAP1oAAALaQAAImkAADppAABNaQAAWGkAAGlpAAB3aQAAgmkAAI9pAACWaQAAo2kAAKtpAAC5aQAAyGkAANJpAADdaQAA6WkAAPRpAAD9aQAADWoAAB1qAAAtagAANmoAAE1qAABaagAAamoAAHVqAAB+agAAk2oAAKpqAAC0agAAvWoAANRqAADeagAA6moAAPJqAAD7agAABWsAAA1rAAAVawAAI2sAAC1rAAAzawAAO2sAAF5rAABiawAAamsAAG5rAACAawAAjWsAAJVrAACeawAAp2sAALZrAADIawAA42sAAAdsAAAXbAAALmwAADlsAABEbAAAT2wAAFdsAABhbAAAbmwAAHlsAACGbAAAkmwAAJtsAACmbAAAs2wAAL5sAADKbAAA22wAAOJsAADubAAA/GwAAAhtAAAbbQAALG0AADZtAABFbQAASm0AAFNtAABgbQAAbW0AAHNtAAB7bQAAgG0AAI9tAACVbQAAm20AAKBtAACqbQAAtW0AALptAADCbQAAyW0AANBtAADWbQAA3W0AAOptAADwbQAAAm4AACNuAABHbgAAiG4AAKRuAACrbgAAtm4AAL5uAADUbgAA2G4AAORuAADvbgAA924AAABvAAALbwAAEG8AABpvAAAhbwAAMG8AADVvAABMbwAAX28AAGhvAABybwAAg28AAItvAACObwAAl28AAKNvAACqbwAAtG8AAL1vAADGbwAA0W8AAN1vAADobwAA728AAPtvAAACcAAAB3AAAAtwAAAScAAAHnAAACpwAAA2cAAAQXAAAEtwAABRcAAAWHAAAGdwAABycAAAiXAAAKpwAACycAAAwHAAANBwAADbcAAA5HAAAOxwAAD2cAAAA3EAAAlxAAAYcQAAH3EAAClxAAA6cQAAQXEAAExxAABUcQAAW3EAAHhxAAB9cQAAinEAAJFxAACXcQAAp3EAAMVxAADKcQAA0HEAAB9yAAAncgAAL3IAADpyAABBcgAATnIAAFtyAABlcgAAb3IAAHRyAAB6cgAAhXIAAI9yAACTcgAApnIAALRyAADCcgAAyXIAANJyAADhcgAA53IAAPRyAAAEcwAAC3MAABhzAAAhcwAALnMAADVzAAA4cwAAPHMAAEBzAABDcwAAR3MAAEtzAABocwAAcnMAAH5zAACZcwAAvHMAANVzAAD/cwAAC3QAABN0AAAfdAAAKnQAADR0AABBdAAATHQAAFB0AABTdAAAaXQAAHh0AAB9dAAARAAAAEYAAABQAAAAUwAAAFcAAAByAAAAcwAAAHQAAAB1AAAAdgAAAHcAAAB4AAAAeQAAAHoAAAB7AAAAfAAAAH0AAAB+AAAAfwAAAIAAAACBAAAAggAAAIMAAACEAAAAhQAAAIYAAACHAAAAiAAAAIkAAACKAAAAiwAAAIwAAACNAAAAjgAAAI8AAACQAAAAkQAAAJIAAACTAAAAlAAAAJUAAACWAAAAlwAAAJgAAACZAAAAmgAAAJsAAACcAAAAnQAAAJ4AAACfAAAAoAAAAKEAAACiAAAAowAAAKQAAAClAAAApgAAAKcAAACoAAAAqQAAAKoAAACrAAAArAAAAK0AAACuAAAArwAAALAAAACxAAAAsgAAALMAAAC0AAAAtgAAALcAAAC4AAAAuQAAALoAAAC7AAAAvAAAAL0AAAC+AAAAvwAAAMAAAADPAAAA3wAAAOoAAADrAAAA7AAAAO0AAADuAAAA7wAAAPAAAADxAAAARQAAAAAAAAA0UQAARwAAAAEAAAA8UQAASAAAAAEAAABEUQAAUwAAAAMAAAAAAAAAVAAAAAMAAABMUQAAVQAAAAMAAAA8UQAAVgAAAAMAAABUUQAAVwAAAAQAAAAAAAAAWAAAAAQAAABcUQAAWQAAAAQAAABkUQAAWgAAAAQAAABsUQAAXAAAAAQAAAB0UQAAWwAAAAQAAAB8UQAAXAAAAAQAAACEUQAAXgAAAAUAAAAAAAAAXgAAAAkAAAAAAAAAbwAAAAoAAACMUQAAXgAAAA8AAAAAAAAAZwAAABkAAACUUQAAaQAAABkAAACgUQAAawAAABkAAACwUQAAagAAABkAAADQUQAAXgAAABsAAAAAAAAAYwAAABsAAAA0UQAAZgAAABsAAABkUQAAcQAAAC8AAADgUQAAXgAAADAAAAAAAAAAZAAAADAAAABMUQAAbAAAADAAAADoUQAAXgAAADEAAAAAAAAAbAAAADEAAAA8UQAAYwAAADUAAAA0UQAAXgAAADkAAAAAAAAAYwAAADkAAAA0UQAAbwAAADkAAADwUQAAbwAAADkAAAD4UQAAbAAAADkAAAB8UQAAbAAAADkAAAAAUgAAXgAAADsAAAAAAAAAYQAAADsAAAAIUgAAYgAAADsAAAAcUgAAYwAAADsAAAA0UQAAZQAAADsAAAAsUgAAaAAAADsAAABsUQAAbAAAADsAAAA8UgAAbAAAADsAAAA8UQAAbwAAADsAAABEUgAAcAAAADsAAABMUgAAbAAAADsAAABYUgAAbQAAADsAAABgUgAAbgAAADsAAABsUgAAXwAAADwAAAB0UgAAYAAAADwAAAB8UgAAYwAAADwAAAA0UQAAZgAAADwAAABkUQAAbAAAADwAAACEUgAAbAAAADwAAAA8UgAAbAAAADwAAAA8UQAAbAAAADwAAACMUgAAcQAAADwAAADgUQAAXgAAAD8AAAAAAAAAbAAAAEEAAACUUgAAbwAAAEMAAACcUgAAbAAAAEQAAAA8UQAAXgAAAEYAAAAAAAAAXgAAAEcAAAAAAAAAbAAAAEcAAAA8UQAAbAAAAEwAAAA8UQAAbAAAAE0AAACkUgAAYwAAAFAAAAA0UQAAbAAAAFAAAAA8UQAAYwAAAFIAAAA0UQAAzwAAAFMAAAAAAAAA0QAAAFMAAAA0UQAA0gAAAFMAAACsUgAA0wAAAFMAAABkUQAA1AAAAFMAAAC4UgAA1QAAAFMAAADEUgAA1gAAAFMAAADUUgAA1wAAAFMAAADoUgAA1wAAAFMAAAAsUQAA1wAAAFMAAADwUgAA1wAAAFMAAAD4UgAA3AAAAFMAAAAAUwAA1wAAAFMAAAAIUwAA1wAAAFMAAAAQUwAA1wAAAFMAAAAYUwAA2gAAAFMAAAAgUwAA1wAAAFMAAAAoUwAA1wAAAFMAAAAwUwAA2gAAAFMAAAA4UwAA1wAAAFMAAAA8UQAA2AAAAFMAAABUUQAA2gAAAFMAAABAUwAA2gAAAFMAAABEUgAA2wAAAFMAAABMUgAA1wAAAFMAAABIUwAA2QAAAFMAAABQUwAA2gAAAFMAAABgUwAA2gAAAFMAAABoUwAA2gAAAFMAAACMUQAA1wAAAFMAAAB8UQAA3QAAAFMAAADgUQAA1wAAAFMAAABwUwAA1wAAAFMAAAB4UwAA3wAAAFQAAAAAAAAA4AAAAFQAAABcUQAA4QAAAFQAAAA0UQAA4gAAAFQAAACAUwAA5QAAAFQAAACIUwAA4wAAAFQAAADwUgAA5wAAAFQAAAAAUwAA5AAAAFQAAACUUwAA4wAAAFQAAACEUgAA4wAAAFQAAAA8UgAA5wAAAFQAAACgUwAA6AAAAFQAAACoUwAA6QAAAFQAAAC0UwAA5gAAAFQAAADAUwAAXgAAAFUAAAAAAAAAbAAAAFUAAADIUwAABQADADQBAAAFAAMAOgEAAAgACADEAAAACgADAA8BAAAKAAMApgEAAAoAAwDbAQAACgADAA8CAAALADsAxgAAAAsAAwDHAAAADAA7AMEAAAATAAIAIwEAABMAAwB4AQAAEwADAB4CAAAXAAIAzgEAABcAAgD0AQAAFwACACICAAAXAAIAJQIAABgAAwB9AQAAGAADAA4CAAAnACcAxgEAAC8AMQDJAAAANAAxAMkAAAA1ADEAyQAAAD0ALQAvAQAAPQAqAH8BAABKAEQAQgAAAEoAAwDFAAAASgA7ANAAAABKACgA+wEAAEsATwAHAgAATAA7ABgBAABMAFQA2gEAAE0AVABCAQAATQAEAH4BAABNAAMArQEAAE0ABADcAQAATQA7AOABAABNAFQA9QEAAE0AVAAfAgAATgADAHcBAABOAFQAoQEAAE4AAwCtAQAATgADALwBAABOADwA3wEAAE4AVAARAgAATgADAB0CAABPAAMAUQAAAE8ABABSAAAATwAEAB0BAABPADsAHgEAAE8AFAAoAQAATwA7ACkBAABPAAQAMQEAAE8AOQAzAQAATwBUAIABAABPAFQAiQEAAE8AQwCKAQAATwAEAKQBAABPAAQApQEAAE8ABAD6AQAATwAOAAgCAABPAAcAEwIAAAcAaQAVAQAABwAWAGcBAAAHAA4AaQEAAAcAQQByAQAABwBvAIYBAAAHAFAA6wEAAAcATwDuAQAABwAPAAICAAAJAG0AGQEAAAkAAwBXAQAACQADAHEBAAAKAEgAOwAAAAoASgA7AAAACgBuAIwBAAANAEgAOwAAAA0AXQDPAQAADQBcANABAAAOAFsAOwAAAA4AEQBfAQAADgBpANMBAAAOAGYA6QEAAA4ASAD5AQAADwBIAKoBAAAPAEgAzAEAABAAAwCxAQAAEAADALIBAAARAEsA9gEAABEABwAWAgAAEgAyAC0BAAATAEgAOwAAABQAUgBlAQAAFAADAGgBAAAWAE0AOwAAABYASQDvAQAAFwBIADsAAAAYAEgAOwAAABkAEwC+AQAAGQAUAL4BAAAZAEgA1gEAABkASQDvAQAAGgADAFYBAAAbAEIANwEAABsAFwA4AQAAGwBRAEoBAAAbABcATQEAABsAAwBOAQAAGwAaAFABAAAbABoAUQEAABsAAwBVAQAAGwAaAFgBAAAbABoAYwEAABsAGgBrAQAAGwADAGwBAAAbAAMAbQEAABsAJgBwAQAAGwBpAJABAAAbAGkAkQEAABsAaQCSAQAAGwBpAJMBAAAbAGkAlQEAABsAaQCWAQAAGwBpAJcBAAAbAGkAmQEAABsAaQCbAQAAGwBpAJwBAAAbAGkAnQEAABsAaQCeAQAAGwBpAJ8BAAAbAGwAyQEAABsAaQDXAQAAHABRAEoBAAAcAAMAWQEAABwAAwBeAQAAHAAWAGYBAAAcABoAbgEAABwAAwBvAQAAHABpAI8BAAAcAGkAlwEAACQAVgA7AAAAJQBZADsAAAAlACYA1QEAACYASQA7AAAAJgADAPQBAAAmAHcACwIAACgAVQA7AAAAKABIADsBAAAoAGcAIQIAACsAVwA7AAAALwBpAA4BAAAvABkAGAIAADAAAAARAQAAMAADAKcBAAAwABsA/wEAADAAJgANAgAAMQAeAD8BAAAxAD0AUgEAADEAPgBTAQAAMQA+AGEBAAAxACYAYgEAADIAagCYAQAAMgBqAJoBAAAzAFsAOwAAADUAKQAMAgAANQAfABgCAAA3AAQArAEAADcACgCsAQAANwAEAK8BAAA3AAoArwEAADcACADdAQAAOQBIADsAAAA5AB0ATwEAADkASAC9AQAAOQBLABoCAAA7AHEAIAEAADsAcgAuAQAAOwB4AEsBAAA7AAMAdQEAADsAaQCUAQAAOwADAKcBAAA7ACYAEAIAADsALAAYAgAAPABIADsAAAA8AEkAOwAAADwAWwA7AAAAPAAzAAsBAAA8ADQACwEAADwANQALAQAAPAA2AAsBAAA8ADcACwEAADwAOAALAQAAPAA5AAsBAAA8ADsACwEAADwAJgANAgAAPQBJADUBAAA9AFgA7AEAAD4AWgA7AAAAPgBIAPkBAAA/ACYAYAEAAD8AJgANAgAAQAAiALUBAABBACUAtQEAAEEAZgDoAQAAQgA8AEwBAABDACMAjgEAAEMAZgDoAQAARAA/AD8BAABFAEgAOwAAAEYAaQB0AQAARgAgALYBAABHAHIABAEAAEcAIQBJAQAARwBpAJQBAABHAEAAoAEAAEcAAwD0AQAASABIADsAAABIAHQANgEAAEgAdQA5AQAASABiAP4BAABJAEgAOwAAAEkASADeAQAASgBIADoAAABKAEgAOwAAAEoASAD8AAAASgAvAP0AAABKAFsA/gAAAEoALwAwAQAASgB2AHMBAABKAF4AqAEAAEoAaACrAQAASgAtANQBAABKAFsA5gEAAEoASADnAQAASgBEAPgBAABLAGUAOwAAAEsAVAC/AQAATABfADsAAABMACYAYAEAAE0ASAA7AAAATgBhADsAAABPAEgAOwAAAE8AawD8AAAATwAkAP0AAABPAA0A/wAAAE8ADAAAAQAATwAYAAMBAABPAEMADQEAAE8ATAAQAQAATwBIABUBAABPAEgAFwEAAE8ASAAaAQAATwBkACoBAABPAAkAMgEAAE8AFgA+AQAATwAxAEQBAABPABUASAEAAE8AJgB2AQAATwAcAHkBAABPAEgA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ABAsImRpc3BsYXlFcnJvciI6AA0sImVsZW1lbnRzIjpbAAwsImVuY29kZU1zIjoAECwiZmlyc3RFdmVudE1zIjoADywiZm9jdXNlZCI6dHJ1ZQAKLCJncmFiTXMiOgAKLCJoZWlnaHQiOgANLCJsYXN0QWdvTXMiOgAJLCJsYXllciI6AAwsIm1heExhZ01zIjoACywibWVzc2FnZSI6AAksIm1vZGVsIjoABiwibXMiOgAJLCJub2RlcyI6ABQsIm9rIjpmYWxzZSwiZXJyb3IiOgASLCJvayI6dHJ1ZSwiZGF0YSI6ABUsIm9rIjp0cnVlLCJkYXRhIjp7fX0ACSwib3JkZXIiOgAHLCJwaWQiOgAILCJwbmciOiIADCwicG9pbnRlcnMiOgAULCJwcm90b2NvbCI6MSwic2RrIjoACiwicmVhZE1zIjoAEywicmVhc29uIjoidGltZW91dCIACywicmVsZWFzZSI6AA0sInJldHJ5YWJsZSI6ACYsInJldHJ5YWJsZSI6ZmFsc2UsImRldGFpbCI6eyJyZWFzb24iOgAMLCJyb3RhdGlvbiI6AAksInNjYWxlIjoACiwic2NyZWVuIjoACSwidGl0bGUiOgAILCJ0eXBlIjoALywidWlBdXRvbWF0aW9uIjp7InN0YXRlIjoiY29ubmVjdGVkIiwiY29ubmVjdCI6AAcsInVpZCI6AAgsInVwTXMiOgAHLCJ2aWEiOgAZLCJ2aWEiOiJldmVudHMiLCJldmVudHMiOgAMLCJ3YWl0ZWRNcyI6AAssIndpbmRvd3MiOgABLwAFMC4zLjAAAjogAAU6dHJ1ZQAIPGNsaW5pdD4ABjxpbml0PgADPjspACFBQ1RJT05fQVJHVU1FTlRfU0VMRUNUSU9OX0VORF9JTlQAI0FDVElPTl9BUkdVTUVOVF9TRUxFQ1RJT05fU1RBUlRfSU5UACVBQ1RJT05fQVJHVU1FTlRfU0VUX1RFWFRfQ0hBUlNFUVVFTkNFACxBQ1RJT05fU0VUX1NFTEVDVElPTiB3YXMgcmVmdXNlZCBieSB0aGUgbm9kZQAnQUNUSU9OX1NFVF9URVhUIHdhcyByZWZ1c2VkIGJ5IHRoZSBub2RlAAVBU0NJSQARQWxyZWFkeSBjb25uZWN0ZWQAAUMAAkNJAAFEAAJETAADRExEABBFX0FYX1VOQVZBSUxBQkxFAA1FX0JBRF9SRVFVRVNUAAVFX0hJRAAKRV9JTlRFUk5BTAAERV9JTwAMRV9OT19FTEVNRU5UAA1FX1VOU1VQUE9SVEVEAAFGABJHRVNUVVJFX01BWF9GUkFNRVMADkdFU1RVUkVfTUFYX01TAAFJAANJSUkAAklMAANJTEkAAUoAAkpEAAJKSgADSkpKAAJKTAADSkxKAAlKc29uLmphdmEAAUwAAkxDAAJMRgAJTEZGRkZKSUpKAAdMRkZJSkpaAAJMSQADTElJAAZMSUlJSloAAkxKAAVMSklGRgADTEpKAAdMSkpJRkZJAAdMSkpJSUxJAA9MSkpJSUxMSUlGRklJSUkAAkxMAAVMTERKSgADTExJAANMTEwABExMTFoAAkxaADdMYW5kcm9pZC9hY2Nlc3NpYmlsaXR5c2VydmljZS9BY2Nlc3NpYmlsaXR5U2VydmljZUluZm87ADdMYW5kcm9pZC9hcHAvVWlBdXRvbWF0aW9uJE9uQWNjZXNzaWJpbGl0eUV2ZW50TGlzdGVuZXI7ABpMYW5kcm9pZC9hcHAvVWlBdXRvbWF0aW9uOwAoTGFuZHJvaWQvZ3JhcGhpY3MvQml0bWFwJENvbXByZXNzRm9ybWF0OwAZTGFuZHJvaWQvZ3JhcGhpY3MvQml0bWFwOwAXTGFuZHJvaWQvZ3JhcGhpY3MvUmVjdDsAGkxhbmRyb2lkL29zL0J1aWxkJFZFUlNJT047ABJMYW5kcm9pZC9vcy9CdWlsZDsAE0xhbmRyb2lkL29zL0J1bmRsZTsAGkxhbmRyb2lkL29zL0hhbmRsZXJUaHJlYWQ7ABNMYW5kcm9pZC9vcy9Mb29wZXI7ABRMYW5kcm9pZC9vcy9Qcm9jZXNzOwAYTGFuZHJvaWQvb3MvU3lzdGVtQ2xvY2s7ABVMYW5kcm9pZC91dGlsL0Jhc2U2NDsAHUxhbmRyb2lkL3V0aWwvRGlzcGxheU1ldHJpY3M7ABZMYW5kcm9pZC92aWV3L0Rpc3BsYXk7ABlMYW5kcm9pZC92aWV3L0lucHV0RXZlbnQ7ABdMYW5kcm9pZC92aWV3L0tleUV2ZW50OwAoTGFuZHJvaWQvdmlldy9Nb3Rpb25FdmVudCRQb2ludGVyQ29vcmRzOwAsTGFuZHJvaWQvdmlldy9Nb3Rpb25FdmVudCRQb2ludGVyUHJvcGVydGllczsAGkxhbmRyb2lkL3ZpZXcvTW90aW9uRXZlbnQ7AC9MYW5kcm9pZC92aWV3L2FjY2Vzc2liaWxpdHkvQWNjZXNzaWJpbGl0eUV2ZW50OwAyTGFuZHJvaWQvdmlldy9hY2Nlc3NpYmlsaXR5L0FjY2Vzc2liaWxpdHlOb2RlSW5mbzsANExhbmRyb2lkL3ZpZXcvYWNjZXNzaWJpbGl0eS9BY2Nlc3NpYmlsaXR5V2luZG93SW5mbzsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAI0xkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdNZXRob2Q7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJDbGFzczsAIUxkYWx2aWsvYW5ub3RhdGlvbi9NZW1iZXJDbGFzc2VzOwAkTGRhbHZpay9hbm5vdGF0aW9uL01ldGhvZFBhcmFtZXRlcnM7AB1MZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwAaTGRhbHZpay9hbm5vdGF0aW9uL1Rocm93czsAHUxqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW07ABhMamF2YS9pby9CdWZmZXJlZFJlYWRlcjsAH0xqYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbTsAGExqYXZhL2lvL0ZpbGVEZXNjcmlwdG9yOwAaTGphdmEvaW8vRmlsZU91dHB1dFN0cmVhbTsAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwAVTGphdmEvaW8vSW5wdXRTdHJlYW07ABtMamF2YS9pby9JbnB1dFN0cmVhbVJlYWRlcjsAFkxqYXZhL2lvL091dHB1dFN0cmVhbTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwAQTGphdmEvaW8vUmVhZGVyOwATTGphdmEvbGFuZy9Cb29sZWFuOwAYTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7ABFMamF2YS9sYW5nL0NsYXNzOwASTGphdmEvbGFuZy9Eb3VibGU7ABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsAEUxqYXZhL2xhbmcvRmxvYXQ7ABNMamF2YS9sYW5nL0ludGVnZXI7ACBMamF2YS9sYW5nL0ludGVycnVwdGVkRXhjZXB0aW9uOwAQTGphdmEvbGFuZy9NYXRoOwAhTGphdmEvbGFuZy9Ob1N1Y2hNZXRob2RFeGNlcHRpb247ABJMamF2YS9sYW5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsAEkxqYXZhL2xhbmcvU3lzdGVtOwASTGphdmEvbGFuZy9UaHJlYWQ7ABVMamF2YS9sYW5nL1Rocm93YWJsZTsAGUxqYXZhL2xhbmcvcmVmbGVjdC9BcnJheTsAH0xqYXZhL2xhbmcvcmVmbGVjdC9Db25zdHJ1Y3RvcjsALUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uVGFyZ2V0RXhjZXB0aW9uOwAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsAGkxqYXZhL25pby9jaGFyc2V0L0NoYXJzZXQ7ABVMamF2YS91dGlsL0FycmF5TGlzdDsAFExqYXZhL3V0aWwvSXRlcmF0b3I7ABBMamF2YS91dGlsL0xpc3Q7ABBMamF2YS91dGlsL0xpc3Q8ACFMbnovY28vZGVsYWNvdXIvd2FyZGVuL3VpYWQvSnNvbjsAI0xuei9jby9kZWxhY291ci93YXJkZW4vdWlhZC9NYWluJDE7ACFMbnovY28vZGVsYWNvdXIvd2FyZGVuL3VpYWQvTWFpbjsAIkxuei9jby9kZWxhY291ci93YXJkZW4vdWlhZC9VaWEkMTsAKExuei9jby9kZWxhY291ci93YXJkZW4vdWlhZC9VaWEkT3BFcnJvcjsAMExuei9jby9kZWxhY291ci93YXJkZW4vdWlhZC9VaWEkU25hcHNob3RPcHRpb25zOwAlTG56L2NvL2RlbGFjb3VyL3dhcmRlbi91aWFkL1VpYSRXYWxrOwAgTG56L2NvL2RlbGFjb3VyL3dhcmRlbi91aWFkL1VpYTsAFExvcmcvanNvbi9KU09OQXJyYXk7ABhMb3JnL2pzb24vSlNPTkV4Y2VwdGlvbjsAFUxvcmcvanNvbi9KU09OT2JqZWN0OwAFTU9ERUwACU1haW4uamF2YQAHT3BFcnJvcgADUE5HAAhQUk9UT0NPTAAHUkVMRUFTRQAHU0RLX0lOVAAPU25hcHNob3RPcHRpb25zAARUWVBFAAhVUy1BU0NJSQAFVVRGLTgAPVVpQXV0b21hdGlvbihMb29wZXIsIElVaUF1dG9tYXRpb25Db25uZWN0aW9uKSBtaXNzaW5nIG9uIEFQSSAAL1VpQXV0b21hdGlvbkNvbm5lY3Rpb24gaXMgbm90IHJlYWNoYWJsZSBvbiBBUEkgAAhVaWEuamF2YQABVgAHVkVSU0lPTgACVkkABVZJSUlJAAJWSgAFVkpJTEkAB1ZKSklJSUkACFZKSklJTElaAAJWTAADVkxJAAZWTElJSVoAA1ZMTAAEVkxMWgADVkxaAAJWWgAEV2FsawABWgACWkQAAlpJAANaSUwAAlpMAARaTElJAARaTElMAANaTEwAA1pMWgAFWkxaTEwABVpMWkxaAAJbQgACW0kAKVtMYW5kcm9pZC92aWV3L01vdGlvbkV2ZW50JFBvaW50ZXJDb29yZHM7AC1bTGFuZHJvaWQvdmlldy9Nb3Rpb25FdmVudCRQb2ludGVyUHJvcGVydGllczsAEltMamF2YS9sYW5nL0NsYXNzOwATW0xqYXZhL2xhbmcvT2JqZWN0OwATW0xqYXZhL2xhbmcvU3RyaW5nOwAEW1tbRgACXCIAAlxcAAJcbgACXHIAAlx0AAJcdQAOXSwidHJ1bmNhdGVkIjoAAl19ACBhIGdlc3R1cmUgbGFzdHMgYXQgbW9zdCA2MDAwMCBtcwAZYSBwYXRoIGhhcyAxLi4xMDAwIHBvaW50cwAKYWNjZXNzJDAwMAAKYWNjZXNzJDEwMAAKYWNjZXNzJDIwMAAKYWNjZXNzJDIwMgAKYWNjZXNzJDMwOAALYWNjZXNzRmxhZ3MABmFjdGl2ZQAKYWN0aXZlUm9vdAADYWRkAANhbGwAKGFsbCBwYXRocyBuZWVkIHRoZSBzYW1lIG51bWJlciBvZiBwb2ludHMAEmFscmVhZHkgcmVnaXN0ZXJlZAAjYW5kcm9pZC5hcHAuSVVpQXV0b21hdGlvbkNvbm5lY3Rpb24AImFuZHJvaWQuYXBwLlVpQXV0b21hdGlvbkNvbm5lY3Rpb24ALWFuZHJvaWQuaGFyZHdhcmUuZGlzcGxheS5EaXNwbGF5TWFuYWdlckdsb2JhbAAGYXBwZW5kAApiYWQgcGF0aDogAApiYWRHZXN0dXJlAAxib29sZWFuVmFsdWUABmJvdHRvbQANY2FuY2VsR2VzdHVyZQAGY2hhckF0AAljaGVja2FibGUAB2NoZWNrZWQABWNsYXNzAApjbGVhckNhY2hlAAljbGlja2FibGUABWNsb3NlAARjb2RlAAhjb21wcmVzcwAHY29ubmVjdAAJY29ubmVjdCgpAAxjb25uZWN0KGludCkACWNvbm5lY3RNcwAKY29ubmVjdFZpYQAOY29ubmVjdF9mYWlsZWQACGNvbnRhaW5zAAVjb3VudAAFZGVidWcAB2RlbnNpdHkABGRlc2MAB2Rlc3Ryb3kACGRpc2FibGVkAApkaXNjb25uZWN0AAdkaXNwbGF5AAxkaXNwbGF5RXJyb3IABGR1bXAACmR1cmF0aW9uTXMACGVkaXRhYmxlAA5lbmNvZGVUb1N0cmluZwAGZXF1YWxzAANlcnIACWVycm9ySnNvbgAKZXZlbnRDb3VudAAPZXZlbnRDb3VudFNpbmNlAAlldmVudExvY2sACmV2ZW50VHlwZXMABGV4aXQABWZpZWxkACJmaW5kQWNjZXNzaWJpbGl0eU5vZGVJbmZvc0J5Vmlld0lkAAlmaW5kRm9jdXMABGZsYWcABWZsYWdzAAVmbHVzaAAJZm9jdXNhYmxlAAdmb2N1c2VkAAxmb2N1c2VkSW5wdXQAB2Zvck5hbWUAB2ZyYW1lTXMAHGZyYW1lTXMgbXVzdCBiZSBpbiAoMCwgMTAwMF0ABWZyZXNoAAVnYXBNcwAHZ2VzdHVyZQAhZ2VzdHVyZSBmYWlsZWQgYmVmb3JlIGFueSBpbnB1dDogABpnZXN0dXJlIGludGVycnVwdGVkIGFmdGVyIAAaZ2VzdHVyZSB0YWtlcyAxIG9yIDIgcGF0aHMADGdlc3R1cmVFdmVudAADZ2V0ABFnZXRCb3VuZHNJblNjcmVlbgAIZ2V0Qnl0ZXMACGdldENhdXNlAAhnZXRDaGlsZAANZ2V0Q2hpbGRDb3VudAAIZ2V0Q2xhc3MADGdldENsYXNzTmFtZQAVZ2V0Q29udGVudERlc2NyaXB0aW9uABZnZXREZWNsYXJlZENvbnN0cnVjdG9yABFnZXREZWNsYXJlZE1ldGhvZAAJZ2V0RG91YmxlAA9nZXREcmF3aW5nT3JkZXIADGdldEV2ZW50VHlwZQAJZ2V0SGVpZ2h0AAtnZXRIaW50VGV4dAAFZ2V0SWQAC2dldEluc3RhbmNlAAZnZXRJbnQADGdldEpTT05BcnJheQANZ2V0SlNPTk9iamVjdAAIZ2V0TGF5ZXIACWdldExvb3BlcgAKZ2V0TWVzc2FnZQAJZ2V0TWV0aG9kAAdnZXROYW1lAA5nZXRQYWNrYWdlTmFtZQAOZ2V0UmVhbERpc3BsYXkADmdldFJlYWxNZXRyaWNzAAdnZXRSb290ABVnZXRSb290SW5BY3RpdmVXaW5kb3cAC2dldFJvdGF0aW9uAA5nZXRTZXJ2aWNlSW5mbwAJZ2V0U3RyaW5nAAdnZXRUZXh0ABNnZXRUZXh0U2VsZWN0aW9uRW5kABVnZXRUZXh0U2VsZWN0aW9uU3RhcnQACGdldFRpdGxlAAdnZXRUeXBlABVnZXRWaWV3SWRSZXNvdXJjZU5hbWUACGdldFdpZHRoAApnZXRXaW5kb3dzAAZoYW5kbGUAB2hhc05leHQACGhhc2hDb2RlAAZoZWFsdGgABmhlaWdodAAMaGVpZ2h0UGl4ZWxzAAhoZWxkVGV4dAAEaGludAAGaG9sZE1zACFob2xkTXMgYW5kIHJlbGVhc2VNcyBtdXN0IGJlID49IDAAAmlkAAZpZGxlTXMAAmluABBpbmNsdWRlSW52aXNpYmxlAAtpbml0RGlzcGxheQAGaW5qZWN0AAdpbmplY3QyAAdpbmplY3QzAA1pbmplY3RHZXN0dXJlABBpbmplY3RJbnB1dEV2ZW50ABlpbmplY3RJbnB1dEV2ZW50IGZhaWxlZDogACJpbmplY3RJbnB1dEV2ZW50IHJlZnVzZWQgdGhlIGV2ZW50AA5pbmplY3RMb29rZWRVcAAVaW5qZWN0Tm9BbmltYXRpb25XYWl0AAlpbmplY3RPbmUACWludGVyc2VjdAAJaW52aXNpYmxlAAZpbnZva2UACGlzQWN0aXZlAAtpc0NoZWNrYWJsZQAJaXNDaGVja2VkAAtpc0NsaWNrYWJsZQAKaXNFZGl0YWJsZQAHaXNFbXB0eQAJaXNFbmFibGVkAAtpc0ZvY3VzYWJsZQAJaXNGb2N1c2VkAAppc0luZmluaXRlAA9pc0xvbmdDbGlja2FibGUABWlzTmFOAAppc1Bhc3N3b3JkAAxpc1Njcm9sbGFibGUACmlzU2VsZWN0ZWQAEWlzU2hvd2luZ0hpbnRUZXh0AA9pc1Zpc2libGVUb1VzZXIACGl0ZXJhdG9yAA1rZWVwSW52aXNpYmxlAANrZXkAB2tleUNvZGUAC2xhc3RFdmVudE1zAAtsYXN0SW5wdXRNcwAEbGVmdAAGbGVuZ3RoAANsb2cADWxvbmdDbGlja2FibGUABGxvb3AABG1haW4AA21heAAIbWF4Tm9kZXMACW1ldGFTdGF0ZQADbWluAAZtb3Rpb24ABW15UGlkAAVteVVpZAAEbmFtZQAFbmFtZXMAC25ld0luc3RhbmNlAARuZXh0ABBubyBmb2N1c2VkIGlucHV0AB9ubyBmb2N1c2VkIGlucHV0IHRvIHNldCB0ZXh0IG9uACJubyByb290IG5vZGUgZm9yIHRoZSBhY3RpdmUgd2luZG93AD9ubyByb290IG5vZGUgZm9yIHRoZSBhY3RpdmUgd2luZG93IChVaUF1dG9tYXRpb24gcmV0dXJuZWQgbnVsbCkAGm5vIHdpbmRvdyB3aXRoIGEgcm9vdCBub2RlAAVub2RlcwAJbm90aWZ5QWxsAAZvYnRhaW4AFG9uQWNjZXNzaWJpbGl0eUV2ZW50AAJvcAAKb3B0Qm9vbGVhbgAJb3B0RG91YmxlAAZvcHRJbnQAB29wdExvbmcACW9wdFN0cmluZwADb3V0AAhwYXNzd29yZAAFcGF0aHMADXBlcmZvcm1BY3Rpb24AA3BrZwAVcG9pbnRzIG11c3QgYmUgZmluaXRlABFwcmVwYXJlTWFpbkxvb3BlcgAHcHJlc3NNcwAIcHJlc3N1cmUAD3B1dENoYXJTZXF1ZW5jZQAGcHV0SW50AAFxAAdxdWlldE1zAApxdWl0U2FmZWx5AAVxdW90ZQAIcmVhZExpbmUAB3JlY3ljbGUAB3JlZnJlc2gACXJlbGVhc2VNcwAKcmVzb3VyY2VJZAAJcmV0cnlhYmxlAAVyaWdodAAKcm9vdFdhaXRNcwAFcm91bmQAA3J1bgACc2IABXNjb3BlAApzY3JlZW5Kc29uAApzY3JlZW5zaG90AApzY3JvbGxhYmxlAAlzZWxlY3RBbGwACHNlbGVjdGVkAARzZW5kAAVzZXJ2ZQANc2V0QWNjZXNzaWJsZQAJc2V0RGFlbW9uABVzZXRJbnRlcmFjdGl2ZVdpbmRvd3MAH3NldE9uQWNjZXNzaWJpbGl0eUV2ZW50TGlzdGVuZXIABnNldE91dAAMc2V0U2VsZWN0aW9uAA5zZXRTZXJ2aWNlSW5mbwAJc2V0U291cmNlAAdzZXRUZXh0AAZzZXR0bGUACHNodXRkb3duAAtzaWduaWZpY2FudAAEc2l6ZQANc2tpcEludmlzaWJsZQAFc2xlZXAACHNuYXBzaG90AA9zbmFwc2hvdE9wdGlvbnMABXN0YXJ0AAlzdGFydGVkTXMABnN0ZG91dAAFc3RlcHMAG3N0b2NrIHNlcnZpY2UgaW5mbyBmbGFncz0weAADc3RyAAtzdWJTZXF1ZW5jZQAFc3dpcGUABHN5bmMADnRha2VTY3JlZW5zaG90ABx0YWtlU2NyZWVuc2hvdCByZXR1cm5lZCBudWxsAAN0YXAABHRleHQATXRoZSB0ZXh0IG9mIGEgcGFzc3dvcmQgZmllbGQgaXMgbm90IHJlYWRhYmxlLCBzbyBpdCBjYW5ub3QgYmUgZWRpdGVkIGluIHBsYWNlAAZ0aGlzJDAABnRocmVhZAAJdGltZW91dE1zAAV0aW1lcwALdG9CeXRlQXJyYXkAC3RvSGV4U3RyaW5nAAh0b1N0cmluZwAIdG9vbFR5cGUAA3RvcAAEdHJpbQAJdHJ1bmNhdGVkAAh0eXBlVGV4dAACdWEAEXVpYXV0b21hdGlvbl9idXN5AAx1bmtub3duIG9wOiAADHVwdGltZU1pbGxpcwAFdmFsdWUAB3ZhbHVlT2YADXZpc2libGVCb3VuZHMABHdhaXQAC3dhcmRlbi11aWFkAA53YXJkZW4tdWlhZC1pbwAFd2lkdGgAC3dpZHRoUGl4ZWxzAAd3aW5kb3dzAAt3aW5kb3dzSnNvbgAFd3JpdGUAAXgAAngxAAJ4MgABeQACeTEAAnkyABt7ImJhY2tlbmQiOiJ1aWFkIiwic2NyZWVuIjoACHsiY29kZSI6AAp7ImRvd25NcyI6ABl7ImV2ZW50IjoiZmF0YWwiLCJlcnJvciI6ACF7ImV2ZW50IjoiZmF0YWwiLCJlcnJvciI6eyJjb2RlIjoAF3siZXZlbnQiOiJsb2ciLCJsZXZlbCI6ACh7ImV2ZW50IjoicmVhZHkiLCJwcm90b2NvbCI6MSwidmVyc2lvbiI6AAp7ImZyYW1lcyI6AAZ7ImlkIjoACnsibGVuZ3RoIjoACXsicXVpZXQiOgAIeyJ0ZXh0IjoAC3sidmVyc2lvbiI6AAl7IndpZHRoIjoAAnt9AAF9ABR9LCJldmVudHMiOnsiY291bnQiOgANfSwidXB0aW1lTXMiOgADfX19AJ4Bfn5EOHsiYmFja2VuZCI6ImRleCIsImNvbXBpbGF0aW9uLW1vZGUiOiJyZWxlYXNlIiwiaGFzLWNoZWNrc3VtcyI6ZmFsc2UsIm1pbi1hcGkiOjI2LCJzaGEtMSI6IjA4NGE4OTEyNmFlMDU5NmM1OTkxNDNkZjU3YTY1NDY0NmFmMjgzMTMiLCJ2ZXJzaW9uIjoiOS4yLjQtZGV2In0AAh4BlwQaqAIfAoECBACzAx4CIwGXBBwBGDMCIQKBAhwBRBCAALQDHAEeAh4BlwQavQIfAoECBBizAxfDAh0BlwQYTwIfAoECBBizAxfIAh8CgQIEGrMDF94CIgGXBBwFFwYXtReJFzwXqQIjAZcEHAEYTAIgAZcEHAMYTBhNGE4AAAQAmgGCgAS8NgEIuDUBCIQ2AQjUNgAAAQGeAYCABMQ4nwEB3DgEAA0AGRoBGAEYAQqgAYiABPxKAYKABKBLAYgguEsBiCCcSAGIINBLAQq4SAEK0DkBCOhLAQnITAEKoEkBioAIhE0BCpROAQrUSQABAQEdkCCtAYCABNRSrgEB8FIAAgEAHhABEK8BgIAE6FMABwEAIAABAAEAAQABAAEAAQCxAYCABIhUAAcBACcQARABEAEAARABAAEQsgGAgATQVAIOGw0uGgEaMAABAgECAQIBAgESAQIBAgECAQIBAgEQAQIBArMBgIAE/IgBAYgg8FgBiCCAXQGIIOyHAQGIIISIAQECvFcBCtiIAQECuIkBAQL0iQEDAviPAQECpIgBAQKIWAIKyFUCCpxcAQKElQEBApSWAQECjJcBAQKMWQGCAcyXAQIChFcBCrxvAQKQmAEDAqyZAQEC5FoDCqBbBQL8VAEKmIUBvAEAnIoBAQCciwEEAJhdAgD0agcArG4EAPBvAQCEcgMAhHMBALx1AgCweAEA+H0BAOx/AQCEggEDHgQBFzcCJOgDRmDqAAIAAAAedQAAJXUAAAEAAAAvdQAAAQAAADh1AAACAAAASHUAACV1AAACAAAAWnUAAE91AAACAAAAWnUAAGF1AAACAAAAWnUAAGx1AAABAAAAd3UAAAEAAACIdQAAAQAAAJF1AABQdwAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAKIAAABcdwAAqwAAAFx3AABsdwAAAAAAAAEAAAAAAAAArQAAAGR3AAB4dwAAAAAAAAAAAAAAAAAAhHcAAAAAAAAAAAAAAAAAAJB3AAAAAAAAAAAAAAAAAACsdwAAAAAAABEAAAAAAAAAvQAAAFx3AADAAAAApHcAAMEAAACkdwAAxAAAAKR3AADGAAAApHcAAMcAAACkdwAAyAAAAFx3AADJAAAAXHcAAMoAAACkdwAAzgAAAKR3AADPAAAApHcAANIAAACkdwAA1QAAAKR3AADWAAAApHcAANcAAACkdwAA2AAAAKR3AADaAAAAnHcAABEAAAAAAAAAAQAAAAAAAAABAAAAPAIAAHAAAAACAAAAXQAAAGAJAAADAAAAeQAAANQKAAAEAAAAPgAAAIAQAAAFAAAA6QAAAHASAAAGAAAACAAAALgZAAABIAAAQAAAALgaAAADIAAAPQAAAPhMAAABEAAARQAAACRRAAACIAAAPAIAAM5TAAAEIAAADAAAAB51AAAAIAAACAAAAJ51AAAFIAAAAgAAAEJ3AAADEAAACgAAAFB3AAAGIAAABwAAALR3AAAAEAAAAQAAAMR4AAA="
1619
+ };
1620
+
1621
+ // ../../packages/e2e/src/driver/android/uiad-device.ts
1622
+ var UIAD_DEVICE_DIR = "/data/local/tmp";
1623
+ var UIAD_MIN_SDK = 26;
1624
+ function uiadDevicePath(dex = UIAD_DEX) {
1625
+ return `${UIAD_DEVICE_DIR}/warden-uiad-${dex.dexSha256.slice(0, 8)}.dex`;
1626
+ }
1627
+ function uiadBuildId(dex = UIAD_DEX) {
1628
+ return dex.dexSha256.slice(0, 8);
1629
+ }
1630
+ function uiadLaunchCommand(devicePath) {
1631
+ return `exec env CLASSPATH=${shellQuote(devicePath)} app_process / ${UIAD_MAIN_CLASS}`;
1632
+ }
1633
+ function uiadLaunchArgv(adb, serial, devicePath) {
1634
+ return [adb, "-s", serial, "shell", uiadLaunchCommand(devicePath)];
1635
+ }
1636
+ function uiadInstallCommand(devicePath) {
1637
+ const part = shellQuote(`${devicePath}.part`);
1638
+ return `cat > ${part} && chmod 444 ${part} && mv ${part} ${shellQuote(devicePath)}`;
1639
+ }
1640
+ function uiadInstallArgv(adb, serial, devicePath) {
1641
+ return [adb, "-s", serial, "shell", uiadInstallCommand(devicePath)];
1642
+ }
1643
+ function uiadPresentCommand(devicePath, bytes) {
1644
+ const file = shellQuote(devicePath);
1645
+ return `[ -f ${file} ] && [ "$(wc -c < ${file} | tr -d ' ')" = ${bytes} ] && echo present`;
1646
+ }
1647
+ var bunPushDex = async (argv, bytes) => {
1648
+ try {
1649
+ const proc = Bun.spawn([...argv], { stdin: bytes, stdout: "pipe", stderr: "pipe", timeout: 15000 });
1650
+ const [out, err, exitCode] = await Promise.all([
1651
+ new Response(proc.stdout).text(),
1652
+ new Response(proc.stderr).text(),
1653
+ proc.exited
1654
+ ]);
1655
+ return { exitCode, output: `${out}${err}`.trim() };
1656
+ } catch (error) {
1657
+ return { exitCode: 127, output: error instanceof Error ? error.message : String(error) };
1658
+ }
1659
+ };
1660
+ async function ensureUiadDex(deps) {
1661
+ const dex = deps.dex ?? UIAD_DEX;
1662
+ const path = uiadDevicePath(dex);
1663
+ const present = await deps.session.run(uiadPresentCommand(path, dex.bytes));
1664
+ if (present.success && present.data.stdout.includes("present"))
1665
+ return { success: true, data: path };
1666
+ const push = deps.push ?? bunPushDex;
1667
+ const wrote = await push(uiadInstallArgv(deps.adb, deps.serial, path), Buffer.from(dex.base64, "base64"));
1668
+ if (wrote.exitCode !== 0) {
1669
+ return driverErr("E_IO", `cannot write warden-uiad to ${deps.serial}:${path}: ${wrote.output.slice(0, 200)}`);
1670
+ }
1671
+ deps.log?.(`uiad: wrote ${dex.bytes} B to ${path}`);
1672
+ return { success: true, data: path };
1673
+ }
1674
+ var HOLDERS_COMMAND = "ps -A -o PID,ARGS";
1675
+ var FOREIGN_HOLDER = /\binstrument\b|uiautomator|UiAutomation/;
1676
+ function parseHolders(ps) {
1677
+ const holders = [];
1678
+ for (const line of ps.split(`
1679
+ `)) {
1680
+ const match = /^\s*(\d+)\s+(.+?)\s*$/.exec(line);
1681
+ const pid = Number(match?.[1]);
1682
+ const args = match?.[2];
1683
+ if (args === undefined || Number.isNaN(pid))
1684
+ continue;
1685
+ if (args.includes(UIAD_MAIN_CLASS))
1686
+ holders.push({ pid, args, ours: true });
1687
+ else if (FOREIGN_HOLDER.test(args) && !args.startsWith("ps "))
1688
+ holders.push({ pid, args, ours: false });
1689
+ }
1690
+ return holders;
1691
+ }
1692
+ function busyError(serial, holders, cause) {
1693
+ const named = holders.length > 0 ? holders.map((h) => `pid ${h.pid}: ${h.args}`).join("; ") : "an unknown process";
1694
+ return driverError("E_AX_UNAVAILABLE", `another UiAutomation client holds ${serial} (${named}); warden will not touch it. Stop it, or release the device.`, {
1695
+ retryable: false,
1696
+ detail: { reason: "uiautomation_busy", holders, cause: cause.message }
1697
+ });
1698
+ }
1699
+ var STALE_KILL_SETTLE_MS = 300;
1700
+ async function launchUiad(deps) {
1701
+ const ensured = await ensureUiadDex(deps);
1702
+ if (!ensured.success)
1703
+ return ensured;
1704
+ const cmd = uiadLaunchArgv(deps.adb, deps.serial, ensured.data);
1705
+ const start = () => startUiad({
1706
+ cmd,
1707
+ ...deps.spawn ? { spawn: deps.spawn } : {},
1708
+ ...deps.log ? { log: deps.log } : {},
1709
+ ...deps.readyTimeoutMs !== undefined ? { readyTimeoutMs: deps.readyTimeoutMs } : {},
1710
+ ...deps.requestTimeoutMs !== undefined ? { requestTimeoutMs: deps.requestTimeoutMs } : {}
1711
+ });
1712
+ const first = await start();
1713
+ if (first.success || !isBusy(first.error))
1714
+ return first;
1715
+ const listed = await deps.session.run(HOLDERS_COMMAND);
1716
+ const holders = listed.success ? parseHolders(listed.data.stdout) : [];
1717
+ const stale = holders.filter((h) => h.ours);
1718
+ if (stale.length === 0 || holders.some((h) => !h.ours))
1719
+ return { success: false, error: busyError(deps.serial, holders, first.error) };
1720
+ deps.log?.(`uiad: ${deps.serial} is held by our own server (pid ${stale.map((h) => h.pid).join(", ")}); killing it`);
1721
+ await deps.session.run(`kill ${stale.map((h) => h.pid).join(" ")}`);
1722
+ await (deps.sleep ?? Bun.sleep)(STALE_KILL_SETTLE_MS);
1723
+ const second = await start();
1724
+ if (second.success || !isBusy(second.error))
1725
+ return second;
1726
+ const again = await deps.session.run(HOLDERS_COMMAND);
1727
+ return {
1728
+ success: false,
1729
+ error: busyError(deps.serial, again.success ? parseHolders(again.data.stdout) : [], second.error)
1730
+ };
1731
+ }
1732
+
1733
+ // ../../packages/e2e/src/driver/android/uiad-backend.ts
1734
+ var MAX_RESTARTS = 3;
1735
+ var MAX_START_FAILURES = 2;
1736
+ var CONNECT_FAILURES_TO_PERSIST = 2;
1737
+ var BUSY_BACKOFF_MS = 2000;
1738
+ var UIAD_ENV = "WARDEN_ANDROID_UIAD";
1739
+ function uiadFromEnv(env) {
1740
+ return env[UIAD_ENV] === "0" ? undefined : true;
1741
+ }
1742
+ function isPersistentStartFailure(error) {
1743
+ const detail = error.detail;
1744
+ const reason = typeof detail === "object" && detail !== null && "reason" in detail ? detail.reason : undefined;
1745
+ return error.code === "E_UNSUPPORTED" || reason === "connect_failed";
1746
+ }
1747
+ function createUiadBackend(deps) {
1748
+ const { log, clock } = deps;
1749
+ const launch = deps.launch ?? launchUiad;
1750
+ let off = deps.unsupportedBuild === uiadBuildId() ? "this build does not run on this device" : undefined;
1751
+ let current;
1752
+ let starting;
1753
+ let started = 0;
1754
+ let startFailures = 0;
1755
+ let connectFailures = 0;
1756
+ let busy;
1757
+ const giveUp = (why) => {
1758
+ off = why;
1759
+ log?.(`uiad: off for this driver (${why}); using uiautomator dump and input`);
1760
+ };
1761
+ async function start() {
1762
+ if (started > MAX_RESTARTS) {
1763
+ giveUp(`it died ${started} times`);
1764
+ return;
1765
+ }
1766
+ const startedAt = Date.now();
1767
+ const launched = await launch({
1768
+ session: deps.session,
1769
+ adb: deps.adb,
1770
+ serial: deps.serial,
1771
+ ...log ? { log } : {},
1772
+ ...deps.options.spawn ? { spawn: deps.options.spawn } : {},
1773
+ ...deps.options.push ? { push: deps.options.push } : {},
1774
+ ...deps.options.readyTimeoutMs !== undefined ? { readyTimeoutMs: deps.options.readyTimeoutMs } : {},
1775
+ ...deps.options.requestTimeoutMs !== undefined ? { requestTimeoutMs: deps.options.requestTimeoutMs } : {},
1776
+ sleep: clock.sleep
1777
+ });
1778
+ addSpanDetail("uiadStartMs", Date.now() - startedAt);
1779
+ if (launched.success) {
1780
+ started++;
1781
+ startFailures = 0;
1782
+ connectFailures = 0;
1783
+ busy = undefined;
1784
+ current = launched.data;
1785
+ return current;
1786
+ }
1787
+ return failedStart(launched.error);
1788
+ }
1789
+ function failedStart(error) {
1790
+ if (isBusy(error)) {
1791
+ busy = { error, at: clock.now() };
1792
+ connectFailures = 0;
1793
+ log?.(`uiad: ${error.message}`);
1794
+ return;
1795
+ }
1796
+ log?.(`uiad: could not start (${error.code}: ${error.message})`);
1797
+ busy = undefined;
1798
+ startFailures++;
1799
+ connectFailures = error.code !== "E_UNSUPPORTED" && isPersistentStartFailure(error) ? connectFailures + 1 : 0;
1800
+ if (error.code === "E_UNSUPPORTED" || connectFailures >= CONNECT_FAILURES_TO_PERSIST) {
1801
+ deps.onUnsupported?.(uiadBuildId());
1802
+ giveUp(error.message);
1803
+ } else if (startFailures >= MAX_START_FAILURES) {
1804
+ giveUp(`${startFailures} starts failed in a row`);
1805
+ }
1806
+ return;
1807
+ }
1808
+ return {
1809
+ wanted: () => off === undefined,
1810
+ blocked: () => busy?.error,
1811
+ status() {
1812
+ if (off !== undefined)
1813
+ return `off: ${off}`;
1814
+ if (busy !== undefined)
1815
+ return "busy";
1816
+ return current?.alive() ? "up" : "idle";
1817
+ },
1818
+ async server() {
1819
+ if (off !== undefined)
1820
+ return;
1821
+ if (current?.alive())
1822
+ return current;
1823
+ if (busy !== undefined && clock.now() - busy.at < BUSY_BACKOFF_MS)
1824
+ return;
1825
+ starting ??= start().finally(() => {
1826
+ starting = undefined;
1827
+ });
1828
+ return starting;
1829
+ },
1830
+ async failed(error) {
1831
+ log?.(`uiad: ${error.code}: ${error.message}`);
1832
+ const dead = current;
1833
+ current = undefined;
1834
+ await dead?.close();
1835
+ },
1836
+ async stop() {
1837
+ const live = current;
1838
+ current = undefined;
1839
+ await starting?.catch(() => {
1840
+ return;
1841
+ });
1842
+ const late = current;
1843
+ current = undefined;
1844
+ await Promise.all([live?.close(), late?.close()]);
1845
+ }
1846
+ };
1847
+ }
1848
+
1849
+ // ../../packages/e2e/src/driver/android/uiautomator.ts
1850
+ class UiautomatorParseError extends Error {
1851
+ constructor(message) {
1852
+ super(message);
1853
+ this.name = "UiautomatorParseError";
1854
+ }
1855
+ }
1856
+ var ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
1857
+ function decodeEntities(value) {
1858
+ if (!value.includes("&"))
1859
+ return value;
1860
+ return value.replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z]+);/g, (whole, body) => {
1861
+ if (body.startsWith("#x"))
1862
+ return safeCodePoint(Number.parseInt(body.slice(2), 16), whole);
1863
+ if (body.startsWith("#"))
1864
+ return safeCodePoint(Number.parseInt(body.slice(1), 10), whole);
1865
+ return ENTITIES[body] ?? whole;
1866
+ });
1867
+ }
1868
+ function safeCodePoint(code, fallback) {
1869
+ return Number.isInteger(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : fallback;
1870
+ }
1871
+ var isSpace = (ch) => /\s/.test(ch);
1872
+ function skipMarkup(xml, lt) {
1873
+ if (xml.startsWith("<?", lt)) {
1874
+ const end = xml.indexOf("?>", lt + 2);
1875
+ return end < 0 ? -1 : end + 2;
1876
+ }
1877
+ if (xml.startsWith("<!--", lt)) {
1878
+ const end = xml.indexOf("-->", lt + 4);
1879
+ return end < 0 ? -1 : end + 3;
1880
+ }
1881
+ return;
1882
+ }
1883
+ function readAttribute(xml, from) {
1884
+ let p = from;
1885
+ while (p < xml.length && !/[\s=/>]/.test(xml.charAt(p)))
1886
+ p++;
1887
+ const key = xml.slice(from, p);
1888
+ while (p < xml.length && isSpace(xml.charAt(p)))
1889
+ p++;
1890
+ if (xml.charAt(p) !== "=")
1891
+ return { key, value: "", end: p };
1892
+ p++;
1893
+ while (p < xml.length && isSpace(xml.charAt(p)))
1894
+ p++;
1895
+ const quote = xml.charAt(p);
1896
+ if (quote !== '"' && quote !== "'")
1897
+ return { key, value: "", end: p };
1898
+ const valueEnd = xml.indexOf(quote, p + 1);
1899
+ if (valueEnd < 0)
1900
+ return;
1901
+ return { key, value: decodeEntities(xml.slice(p + 1, valueEnd)), end: valueEnd + 1 };
1902
+ }
1903
+ function readOpenTag(xml, lt) {
1904
+ let p = lt + 1;
1905
+ while (p < xml.length && !/[\s/>]/.test(xml.charAt(p)))
1906
+ p++;
1907
+ const name = xml.slice(lt + 1, p);
1908
+ const attrs = new Map;
1909
+ let selfClosing = false;
1910
+ while (p < xml.length) {
1911
+ const ch = xml.charAt(p);
1912
+ if (ch === ">")
1913
+ return { name, attrs, selfClosing, end: p + 1 };
1914
+ if (ch === "/") {
1915
+ selfClosing = true;
1916
+ p++;
1917
+ } else if (isSpace(ch)) {
1918
+ p++;
1919
+ } else {
1920
+ const attribute = readAttribute(xml, p);
1921
+ if (!attribute)
1922
+ return;
1923
+ attrs.set(attribute.key, attribute.value);
1924
+ p = attribute.end;
1925
+ }
1926
+ }
1927
+ return;
1928
+ }
1929
+ function* tokenize(xml) {
1930
+ let i = 0;
1931
+ while (i < xml.length) {
1932
+ const lt = xml.indexOf("<", i);
1933
+ if (lt < 0)
1934
+ return;
1935
+ const skipped = skipMarkup(xml, lt);
1936
+ if (skipped !== undefined) {
1937
+ if (skipped < 0)
1938
+ return;
1939
+ i = skipped;
1940
+ continue;
1941
+ }
1942
+ if (xml.startsWith("</", lt)) {
1943
+ const end = xml.indexOf(">", lt + 2);
1944
+ if (end < 0)
1945
+ return;
1946
+ yield { kind: "close", name: xml.slice(lt + 2, end).trim() };
1947
+ i = end + 1;
1948
+ continue;
1949
+ }
1950
+ const tag = readOpenTag(xml, lt);
1951
+ if (!tag)
1952
+ return;
1953
+ yield { kind: "open", name: tag.name, attrs: tag.attrs, selfClosing: tag.selfClosing };
1954
+ i = tag.end;
1955
+ }
1956
+ }
1957
+ function parseForest(xml) {
1958
+ const roots = [];
1959
+ const stack = [];
1960
+ let sawHierarchy = false;
1961
+ const open = (attrs, selfClosing) => {
1962
+ const node = { attrs, children: [] };
1963
+ (stack[stack.length - 1]?.children ?? roots).push(node);
1964
+ if (!selfClosing)
1965
+ stack.push(node);
1966
+ };
1967
+ for (const token of tokenize(xml)) {
1968
+ if (token.name === "hierarchy")
1969
+ sawHierarchy = true;
1970
+ else if (token.name !== "node")
1971
+ continue;
1972
+ else if (token.kind === "close")
1973
+ stack.pop();
1974
+ else if (sawHierarchy)
1975
+ open(token.attrs, token.selfClosing);
1976
+ }
1977
+ return sawHierarchy ? roots : undefined;
1978
+ }
1979
+ var BOUNDS = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/;
1980
+ function parseBounds(value) {
1981
+ const m = BOUNDS.exec(value ?? "");
1982
+ if (!m)
1983
+ return { x: 0, y: 0, width: 0, height: 0 };
1984
+ const [l, t, r, b] = [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
1985
+ return { x: l, y: t, width: Math.max(0, r - l), height: Math.max(0, b - t) };
1986
+ }
1987
+ function simpleName(cls) {
1988
+ return cls.slice(cls.lastIndexOf(".") + 1);
1989
+ }
1990
+ var ROLE_SUFFIXES = [
1991
+ ["ToggleButton", "switch"],
1992
+ ["CheckBox", "checkbox"],
1993
+ ["RadioButton", "checkbox"],
1994
+ ["SeekBar", "slider"],
1995
+ ["Button", "button"],
1996
+ ["TextView", "text"],
1997
+ ["ImageView", "image"],
1998
+ ["RecyclerView", "list"],
1999
+ ["ListView", "list"],
2000
+ ["ScrollView", "scrollview"]
2001
+ ];
2002
+ var CLICKABLE_CONTAINERS = new Set(["ViewGroup", "FrameLayout", "View"]);
2003
+ function roleFor(cls, flags) {
2004
+ const name = simpleName(cls);
2005
+ if (name.includes("Switch"))
2006
+ return "switch";
2007
+ if (name.endsWith("EditText"))
2008
+ return flags.password ? "securefield" : "textfield";
2009
+ const bySuffix = ROLE_SUFFIXES.find(([suffix]) => name.endsWith(suffix));
2010
+ if (bySuffix)
2011
+ return bySuffix[1];
2012
+ return flags.clickable && CLICKABLE_CONTAINERS.has(name) ? "button" : "other";
2013
+ }
2014
+ function overlapsScreen(frame, screen) {
2015
+ return frame.width > 0 && frame.height > 0 && frame.x < screen.width && frame.y < screen.height && frame.x + frame.width > 0 && frame.y + frame.height > 0;
2016
+ }
2017
+ function readFlags(attrs) {
2018
+ const flag = (name) => attrs.get(name) === "true";
2019
+ return {
2020
+ clickable: flag("clickable"),
2021
+ longClickable: flag("long-clickable"),
2022
+ checkable: flag("checkable"),
2023
+ checked: flag("checked"),
2024
+ scrollable: flag("scrollable"),
2025
+ selected: flag("selected"),
2026
+ focused: flag("focused"),
2027
+ password: flag("password"),
2028
+ enabled: attrs.get("enabled") !== "false"
2029
+ };
2030
+ }
2031
+ function traitsOf(f) {
2032
+ const named = [
2033
+ [f.clickable, "clickable"],
2034
+ [f.longClickable, "long-clickable"],
2035
+ [f.checkable, "checkable"],
2036
+ [f.checked, "checked"],
2037
+ [f.focused, "focused"],
2038
+ [f.scrollable, "scrollable"],
2039
+ [f.selected, "selected"],
2040
+ [f.password, "password"]
2041
+ ];
2042
+ return named.filter(([on]) => on).map(([, name]) => name);
2043
+ }
2044
+ function normalize(frame, screen) {
2045
+ if (screen.width <= 0 || screen.height <= 0)
2046
+ return { x: 0, y: 0, width: 0, height: 0 };
2047
+ return {
2048
+ x: frame.x / screen.width,
2049
+ y: frame.y / screen.height,
2050
+ width: frame.width / screen.width,
2051
+ height: frame.height / screen.height
2052
+ };
2053
+ }
2054
+ function toElement(raw, id, depth, screen) {
2055
+ const a = raw.attrs;
2056
+ const f = readFlags(a);
2057
+ const cls = a.get("class") ?? "";
2058
+ const role = roleFor(cls, f);
2059
+ const frame = parseBounds(a.get("bounds"));
2060
+ const identifier = (a.get("resource-id") ?? "").replace(/^[^:/]+:id\//, "");
2061
+ const label = a.get("content-desc") ?? "";
2062
+ const text = a.get("text") ?? "";
2063
+ const hint = a.get("hint") ?? "";
2064
+ const isInput = role === "textfield" || role === "securefield";
2065
+ return {
2066
+ id,
2067
+ role,
2068
+ rawType: cls,
2069
+ ...label ? { label } : {},
2070
+ ...identifier ? { identifier } : {},
2071
+ ...isInput ? { value: text } : {},
2072
+ ...text ? { text } : {},
2073
+ ...hint ? { placeholder: hint } : {},
2074
+ frame,
2075
+ normFrame: normalize(frame, screen),
2076
+ traits: traitsOf(f),
2077
+ enabled: f.enabled,
2078
+ focused: f.focused,
2079
+ selected: f.selected,
2080
+ ...f.checkable ? { checked: f.checked } : {},
2081
+ hittable: f.clickable && f.enabled && overlapsScreen(frame, screen),
2082
+ tapPoint: { x: Math.round(frame.x + frame.width / 2), y: Math.round(frame.y + frame.height / 2) },
2083
+ depth,
2084
+ children: []
2085
+ };
2086
+ }
2087
+ function flatten(elements, out = []) {
2088
+ for (const element of elements) {
2089
+ out.push(element);
2090
+ flatten(element.children, out);
2091
+ }
2092
+ return out;
2093
+ }
2094
+ function parseUiautomatorXml(xml, screen) {
2095
+ const start = xml.indexOf("<hierarchy");
2096
+ if (start < 0)
2097
+ throw new UiautomatorParseError(`no <hierarchy> in uiautomator output: ${xml.slice(0, 120)}`);
2098
+ const end = xml.lastIndexOf("</hierarchy>");
2099
+ const body = end >= 0 ? xml.slice(start, end + "</hierarchy>".length) : xml.slice(start);
2100
+ const forest = parseForest(body);
2101
+ if (!forest)
2102
+ throw new UiautomatorParseError("unparseable uiautomator output");
2103
+ return buildTree(forest, screen);
2104
+ }
2105
+ function buildTree(forest, screen) {
2106
+ const build = (raw, id, depth) => {
2107
+ const element = toElement(raw, id, depth, screen);
2108
+ element.children = raw.children.map((child, i) => build(child, `${id}.${i}`, depth + 1));
2109
+ return element;
2110
+ };
2111
+ const elements = forest.map((raw, i) => build(raw, String(i), 0));
2112
+ return { elements, flat: flatten(elements) };
2113
+ }
2114
+ function rawFromUiad(node) {
2115
+ const [l, t, r, b] = node.bounds;
2116
+ const attrs = new Map;
2117
+ const text = (key, value) => {
2118
+ if (value !== undefined)
2119
+ attrs.set(key, value);
2120
+ };
2121
+ const flag = (key, on) => {
2122
+ if (on)
2123
+ attrs.set(key, "true");
2124
+ };
2125
+ text("class", node.class);
2126
+ text("resource-id", node.id);
2127
+ text("text", node.text);
2128
+ text("content-desc", node.desc);
2129
+ text("hint", node.hint);
2130
+ attrs.set("bounds", `[${l},${t}][${r},${b}]`);
2131
+ flag("clickable", node.clickable);
2132
+ flag("long-clickable", node.longClickable);
2133
+ flag("checkable", node.checkable);
2134
+ flag("checked", node.checked);
2135
+ flag("scrollable", node.scrollable);
2136
+ flag("selected", node.selected);
2137
+ flag("focused", node.focused);
2138
+ flag("password", node.password);
2139
+ if (node.disabled)
2140
+ attrs.set("enabled", "false");
2141
+ return { attrs, children: (node.children ?? []).map(rawFromUiad) };
2142
+ }
2143
+ function parseUiadNodes(nodes, screen) {
2144
+ return buildTree(nodes.map(rawFromUiad), screen);
2145
+ }
2146
+ function isInteractive(element) {
2147
+ return element.traits.includes("clickable") || element.traits.includes("long-clickable") || element.traits.includes("checkable") || element.traits.includes("scrollable") || simpleName(element.rawType).endsWith("EditText");
2148
+ }
2149
+ function interactiveFilter(elements) {
2150
+ const prune = (element) => {
2151
+ const children = element.children.flatMap((child) => prune(child) ?? []);
2152
+ if (!isInteractive(element) && children.length === 0)
2153
+ return;
2154
+ return { ...element, children };
2155
+ };
2156
+ const kept = elements.flatMap((element) => prune(element) ?? []);
2157
+ return { elements: kept, flat: flatten(kept) };
2158
+ }
2159
+
2160
+ // ../../packages/e2e/src/driver/android/uiad-ops.ts
2161
+ var REFUSALS = new Set(["E_HID", "E_NO_ELEMENT", "E_UNSUPPORTED"]);
2162
+ var SNAPSHOT_ATTEMPTS = 3;
2163
+ var SNAPSHOT_RETRY_MS = 400;
2164
+ var EDIT_QUIET_MS = 80;
2165
+ var EDIT_QUIET_TIMEOUT_MS = 1000;
2166
+ var KEY_GAP_MS = 30;
2167
+ var KEY_BASE_TIMEOUT_MS = 1e4;
2168
+ var KEY_PRESS_BUDGET_MS = 150;
2169
+ var ok3 = { success: true, data: undefined };
2170
+ function isNoWindowYet(error) {
2171
+ return error.code === "E_AX_UNAVAILABLE" && !isBusy(error);
2172
+ }
2173
+ function snapshotFromUiad(data, baseScreen, takenAt) {
2174
+ const screen = rotatedScreen(baseScreen, data.screen.rotation ?? 0);
2175
+ return {
2176
+ platform: "android",
2177
+ backend: "uiautomator",
2178
+ takenAt,
2179
+ screen,
2180
+ ...parseUiadNodes(data.elements, screen),
2181
+ truncated: data.truncated
2182
+ };
2183
+ }
2184
+ function createUiadOps(deps) {
2185
+ const { backend, clock, log } = deps;
2186
+ const blocked = () => {
2187
+ const error = backend.blocked();
2188
+ return error === undefined ? undefined : { success: false, error };
2189
+ };
2190
+ async function judge(op, error, canRetry) {
2191
+ if (REFUSALS.has(error.code)) {
2192
+ log?.(`uiad ${op} refused (${error.code}: ${error.message}); doing it the old way`);
2193
+ return "fallback";
2194
+ }
2195
+ await backend.failed(error);
2196
+ return neverSent(error) && canRetry ? "retry" : "surface";
2197
+ }
2198
+ async function call(op, use, idempotent) {
2199
+ for (let attempt = 0;attempt < 2; attempt++) {
2200
+ const server = await backend.server();
2201
+ if (server === undefined)
2202
+ return;
2203
+ const res = await use(server);
2204
+ if (res.success)
2205
+ return res;
2206
+ const verdict = await judge(op, res.error, attempt === 0);
2207
+ if (verdict === "surface" && !idempotent)
2208
+ return res;
2209
+ if (verdict !== "retry")
2210
+ return;
2211
+ }
2212
+ return;
2213
+ }
2214
+ const effect = (op, params, opts) => call(op, async (server) => mapVoid(await server.request(op, params, opts)), false);
2215
+ const edit = (op, send) => call(op, async (server) => {
2216
+ const sent = mapVoid(await send(server));
2217
+ if (sent.success)
2218
+ await server.settle({ quietMs: EDIT_QUIET_MS, timeoutMs: EDIT_QUIET_TIMEOUT_MS });
2219
+ return sent;
2220
+ }, false);
2221
+ async function snapshot() {
2222
+ for (let attempt = 1;; attempt++) {
2223
+ const server = await backend.server();
2224
+ if (server === undefined)
2225
+ return blocked();
2226
+ const res = await server.snapshot();
2227
+ if (res.success) {
2228
+ noteSpanDetail("backend", "uiad");
2229
+ noteSpanDetail("uiadMs", res.data.ms);
2230
+ return { success: true, data: snapshotFromUiad(res.data, deps.baseScreen, clock.now()) };
2231
+ }
2232
+ if (!isNoWindowYet(res.error)) {
2233
+ await backend.failed(res.error);
2234
+ return;
2235
+ }
2236
+ if (attempt >= SNAPSHOT_ATTEMPTS)
2237
+ return res;
2238
+ log?.(`uiad: no window to read yet, retrying (${attempt}/${SNAPSHOT_ATTEMPTS - 1})`);
2239
+ await clock.sleep(SNAPSHOT_RETRY_MS);
2240
+ }
2241
+ }
2242
+ async function settle(opts) {
2243
+ const server = await backend.server();
2244
+ if (server === undefined)
2245
+ return blocked();
2246
+ const res = await server.settle(opts);
2247
+ if (!res.success) {
2248
+ await backend.failed(res.error);
2249
+ return;
2250
+ }
2251
+ noteSpanDetail("backend", "uiad");
2252
+ noteSpanDetail("via", "stream");
2253
+ const { quiet, waitedMs, reason } = res.data;
2254
+ return {
2255
+ success: true,
2256
+ data: { quiet, waitedMs, via: "stream", ...reason !== undefined ? { reason } : {} }
2257
+ };
2258
+ }
2259
+ return {
2260
+ enabled: backend.wanted,
2261
+ status: backend.status,
2262
+ snapshot,
2263
+ settle,
2264
+ tap: (point, opts) => effect("tap", {
2265
+ x: point.x,
2266
+ y: point.y,
2267
+ ...opts?.count !== undefined && opts.count > 1 ? { count: opts.count } : {},
2268
+ ...opts?.durationMs ? { holdMs: opts.durationMs } : {}
2269
+ }),
2270
+ longPress: (point, durationMs = DEFAULT_LONG_PRESS_MS) => effect("tap", { x: point.x, y: point.y, holdMs: durationMs }),
2271
+ swipe: (from, to, durationMs = DEFAULT_SWIPE_MS) => effect("swipe", { x1: from.x, y1: from.y, x2: to.x, y2: to.y, durationMs }),
2272
+ drag: (from, to, timing) => effect("swipe", {
2273
+ x1: from.x,
2274
+ y1: from.y,
2275
+ x2: to.x,
2276
+ y2: to.y,
2277
+ durationMs: timing.durationMs,
2278
+ pressMs: timing.pressMs,
2279
+ releaseMs: timing.releaseMs,
2280
+ steps: DRAG_STEPS
2281
+ }, { timeoutMs: KEY_BASE_TIMEOUT_MS + timing.pressMs + timing.durationMs + timing.releaseMs }),
2282
+ gesture: (plan) => effect("gesture", { paths: plan.paths, frameMs: plan.frameMs, holdMs: plan.holdMs, releaseMs: plan.releaseMs }, { timeoutMs: KEY_BASE_TIMEOUT_MS + plan.totalMs }),
2283
+ key: (keyCode, times = 1) => edit("key", (server) => server.request("key", { keyCode, ...times > 1 ? { times, gapMs: KEY_GAP_MS } : {} }, { timeoutMs: KEY_BASE_TIMEOUT_MS + times * KEY_PRESS_BUDGET_MS })),
2284
+ selectAll: () => call("selectAll", async (server) => mapVoid(await server.request("selectAll")), true),
2285
+ typeText: (text) => edit("typeText", (server) => server.typeText(text)),
2286
+ setText: (text) => edit("setText", (server) => server.request("setText", { text })),
2287
+ stop: () => backend.stop()
2288
+ };
2289
+ }
2290
+ function mapVoid(result) {
2291
+ return result.success ? ok3 : result;
2292
+ }
2293
+
2294
+ // ../../packages/e2e/src/driver/android/android-driver.ts
2295
+ var bunExecBinary = async (argv, opts = {}) => {
2296
+ try {
2297
+ const proc = Bun.spawn([...argv], { stdin: "ignore", stdout: "pipe", stderr: "ignore", timeout: opts.timeoutMs });
2298
+ const [bytes, exitCode] = await Promise.all([new Response(proc.stdout).arrayBuffer(), proc.exited]);
2299
+ return { exitCode, stdout: new Uint8Array(bytes) };
2300
+ } catch {
2301
+ return { exitCode: 127, stdout: new Uint8Array };
2302
+ }
2303
+ };
2304
+ var CAPABILITIES = {
2305
+ pinch: false,
2306
+ maxPointers: 0,
2307
+ unicodeText: false,
2308
+ nativeSettle: false,
2309
+ alerts: true,
2310
+ reachability: true
2311
+ };
2312
+ var DUMP_IDLE_MS = 1000;
2313
+ var DUMP_IDLE_TIMEOUT_MS = 1e4;
2314
+ var SNAPSHOT_ATTEMPTS2 = 3;
2315
+ var INPUT_TEXT_BASE_MS = 1e4;
2316
+ var INPUT_TEXT_PIECE_MS = 8000;
2317
+ var SNAPSHOT_RETRY_MS2 = 400;
2318
+ var SCREENSHOT_TIMEOUT_MS = 1e4;
2319
+ var DRAG_PRESS_MS = 500;
2320
+ var DRAG_MOVE_MS = 600;
2321
+ var DRAG_PROCESS_BUDGET_MS = 3000;
2322
+ var PNG_MAGIC = [137, 80, 78, 71];
2323
+ var unit = { success: true, data: undefined };
2324
+ function elementAt(flat, point) {
2325
+ const inside = flat.filter((e) => point.x >= e.frame.x && point.x < e.frame.x + e.frame.width && point.y >= e.frame.y && point.y < e.frame.y + e.frame.height);
2326
+ const deepest = (list) => list.reduce((best, e) => best === undefined || e.depth >= best.depth ? e : best, undefined);
2327
+ return deepest(inside.filter((e) => e.hittable)) ?? deepest(inside) ?? null;
2328
+ }
2329
+ async function openAndroidDriver(options) {
2330
+ const { serial, adb, log } = options;
2331
+ const clock = options.clock ?? realClock;
2332
+ const execBinary = options.execBinary ?? bunExecBinary;
2333
+ const session = openAdbSession({
2334
+ adb,
2335
+ serial,
2336
+ ...options.spawn ? { spawn: options.spawn } : {},
2337
+ ...log ? { log } : {}
2338
+ });
2339
+ const failed = async (error) => {
2340
+ await session.close();
2341
+ return { success: false, error };
2342
+ };
2343
+ const savedState = options.state?.read(serial);
2344
+ const booted = await waitForBoot(session, serial, clock, {
2345
+ ...options.bootTimeoutMs !== undefined ? { timeoutMs: options.bootTimeoutMs } : {},
2346
+ ...log ? { log } : {}
2347
+ });
2348
+ if (!booted.success)
2349
+ return failed(booted.error);
2350
+ const sdk = booted.data.sdk;
2351
+ const probed = options.screen ?? booted.data.screen;
2352
+ const screen = probed ? { success: true, data: probed } : await readScreen(session, serial);
2353
+ if (!screen.success)
2354
+ return failed(screen.error);
2355
+ const saved = forSdk(await savedState ?? {}, sdk);
2356
+ const animations = await disableAnimations({
2357
+ session,
2358
+ serial,
2359
+ sdk,
2360
+ pending: saved.animations,
2361
+ ...options.state ? { state: options.state } : {},
2362
+ ...log ? { log } : {}
2363
+ });
2364
+ return {
2365
+ success: true,
2366
+ data: buildDriver(options, session, screen.data, clock, execBinary, {
2367
+ sdk,
2368
+ savedRoute: saved.dumpRoute,
2369
+ savedUiadUnsupported: saved.uiadUnsupported,
2370
+ animations
2371
+ })
2372
+ };
2373
+ }
2374
+ function openUiad(input) {
2375
+ const { options, session, baseScreen, clock, facts, saving } = input;
2376
+ const { sdk } = facts;
2377
+ const asked = options.uiad === true ? {} : options.uiad || undefined;
2378
+ if (asked === undefined || sdk === undefined || sdk < UIAD_MIN_SDK)
2379
+ return;
2380
+ const { serial, adb, log, state } = options;
2381
+ const backend = createUiadBackend({
2382
+ session,
2383
+ adb,
2384
+ serial,
2385
+ clock,
2386
+ options: asked,
2387
+ ...log ? { log } : {},
2388
+ ...facts.savedUiadUnsupported !== undefined ? { unsupportedBuild: facts.savedUiadUnsupported } : {},
2389
+ onUnsupported: (buildId) => {
2390
+ if (state)
2391
+ saving.push(state.update(serial, patchFor(sdk, { uiadUnsupported: buildId })));
2392
+ }
2393
+ });
2394
+ return createUiadOps({ backend, baseScreen, clock, ...log ? { log } : {} });
2395
+ }
2396
+ function buildDriver(options, session, baseScreen, clock, execBinary, facts) {
2397
+ const { serial, adb, exec, log } = options;
2398
+ const { sdk } = facts;
2399
+ const initialRoute = options.dumpRoute ?? facts.savedRoute;
2400
+ const saving = [];
2401
+ const dumper = createDumper({
2402
+ session,
2403
+ exec,
2404
+ adb,
2405
+ serial,
2406
+ ...sdk !== undefined ? { sdk } : {},
2407
+ ...log ? { log } : {},
2408
+ ...initialRoute ? { initialRoute } : {},
2409
+ onRoute: (route) => {
2410
+ options.onDumpRoute?.(route);
2411
+ if (options.state)
2412
+ saving.push(options.state.update(serial, patchFor(sdk, { dumpRoute: route })));
2413
+ }
2414
+ });
2415
+ const uiad = openUiad({ options, session, baseScreen, clock, facts, saving });
2416
+ async function dumpWithRetry() {
2417
+ let dumped = await dumper.dump();
2418
+ for (let attempt = 1;attempt < SNAPSHOT_ATTEMPTS2 && !dumped.success && dumped.error.code === "E_AX_UNAVAILABLE"; attempt++) {
2419
+ log?.(`uiautomator unavailable, retrying (${attempt}/${SNAPSHOT_ATTEMPTS2 - 1})`);
2420
+ await clock.sleep(SNAPSHOT_RETRY_MS2);
2421
+ dumped = await dumper.dump();
2422
+ }
2423
+ return dumped;
2424
+ }
2425
+ async function dumpSnapshot() {
2426
+ noteSpanDetail("backend", "dump");
2427
+ const dumped = await dumpWithRetry();
2428
+ if (!dumped.success)
2429
+ return dumped;
2430
+ let parsed;
2431
+ const rotation = hierarchyRotation(dumped.data.xml);
2432
+ const screen = rotatedScreen(baseScreen, rotation);
2433
+ try {
2434
+ parsed = parseUiautomatorXml(dumped.data.xml, screen);
2435
+ } catch (error) {
2436
+ if (error instanceof UiautomatorParseError)
2437
+ return driverErr("E_AX_UNAVAILABLE", error.message);
2438
+ throw error;
2439
+ }
2440
+ const taken = {
2441
+ platform: "android",
2442
+ backend: "uiautomator",
2443
+ takenAt: clock.now(),
2444
+ screen,
2445
+ ...parsed,
2446
+ truncated: false
2447
+ };
2448
+ return { success: true, data: taken };
2449
+ }
2450
+ async function takeSnapshot() {
2451
+ return await uiad?.snapshot() ?? dumpSnapshot();
2452
+ }
2453
+ const input = async (cmd, timeoutMs) => {
2454
+ const res = await runChecked(session, cmd, "E_HID", timeoutMs);
2455
+ return res.success ? unit : res;
2456
+ };
2457
+ const snapshot = async (opts) => {
2458
+ if (opts?.backend !== undefined && opts.backend !== "uiautomator") {
2459
+ return driverErr("E_UNSUPPORTED", `Android only has the uiautomator backend, not ${opts.backend}`);
2460
+ }
2461
+ const taken = await takeSnapshot();
2462
+ if (!taken.success || !opts?.interactiveOnly)
2463
+ return taken;
2464
+ return { success: true, data: { ...taken.data, ...interactiveFilter(taken.data.elements) } };
2465
+ };
2466
+ const elementAtPoint = async (point) => {
2467
+ const snap = await takeSnapshot();
2468
+ return snap.success ? { success: true, data: elementAt(snap.data.flat, point) } : snap;
2469
+ };
2470
+ const tap = async (point, opts) => {
2471
+ const t0 = clock.now();
2472
+ const count = Math.max(1, opts?.count ?? 1);
2473
+ const once = opts?.durationMs ? longPressArgv(point, opts.durationMs) : tapArgv(point);
2474
+ const res = await uiad?.tap(point, { count, ...opts?.durationMs ? { durationMs: opts.durationMs } : {} }) ?? await input(Array.from({ length: count }, () => shellCommand(once)).join("; "));
2475
+ log?.(`tap ${Math.round(point.x)},${Math.round(point.y)} x${count} in ${clock.now() - t0}ms`);
2476
+ return res;
2477
+ };
2478
+ const typeText = async (text) => {
2479
+ const fast = text === "" ? undefined : await uiad?.typeText(text);
2480
+ if (fast !== undefined)
2481
+ return fast;
2482
+ const argvs = textArgvs(text);
2483
+ if (!argvs.success)
2484
+ return argvs;
2485
+ if (argvs.data.length === 0)
2486
+ return unit;
2487
+ return input(argvs.data.map((argv) => shellCommand(argv)).join(" && "), INPUT_TEXT_BASE_MS + INPUT_TEXT_PIECE_MS * argvs.data.length);
2488
+ };
2489
+ const key = async (name, modifiers = []) => {
2490
+ const code = keyNameKeycode(name);
2491
+ if (modifiers.length === 0)
2492
+ return await uiad?.key(code) ?? input(shellCommand(keyeventArgv(code)));
2493
+ const res = await session.run(shellCommand(keycombinationArgv([...modifiers.map(modifierKeycode), code])));
2494
+ if (!res.success)
2495
+ return res;
2496
+ return res.data.exitCode === 0 ? unit : driverErr("E_UNSUPPORTED", "key modifiers need Android 13+ (`input keycombination`)");
2497
+ };
2498
+ const pressKeyRepeat = async (name, times) => {
2499
+ const argvs = keyRepeatArgvs(keyNameKeycode(name), times);
2500
+ if (argvs.length === 0)
2501
+ return unit;
2502
+ const fast = await uiad?.key(keyNameKeycode(name), Math.floor(times));
2503
+ if (fast !== undefined)
2504
+ return fast;
2505
+ return input(argvs.map((argv) => shellCommand(argv)).join(" && "), INPUT_TEXT_BASE_MS + INPUT_TEXT_PIECE_MS * argvs.length);
2506
+ };
2507
+ const selectAll = async () => {
2508
+ const fast = await uiad?.selectAll();
2509
+ if (fast !== undefined)
2510
+ return fast;
2511
+ if (sdk === undefined || sdk < SELECT_ALL_MIN_SDK) {
2512
+ return driverErr("E_UNSUPPORTED", "select-all needs the warden-uiad server or Android 13+ (`input keycombination`)");
2513
+ }
2514
+ return input(shellCommand(selectAllArgv()));
2515
+ };
2516
+ const setText = async (text) => await uiad?.setText(text) ?? driverErr("E_UNSUPPORTED", "setText needs the warden-uiad server");
2517
+ const drag = async (from, to, opts) => {
2518
+ const timing = {
2519
+ pressMs: opts?.pressMs ?? DRAG_PRESS_MS,
2520
+ durationMs: opts?.durationMs ?? DRAG_MOVE_MS,
2521
+ releaseMs: opts?.releaseMs ?? 0
2522
+ };
2523
+ const fast = await uiad?.drag(from, to, timing);
2524
+ if (fast !== undefined)
2525
+ return fast;
2526
+ if (sdk === undefined || sdk < MOTIONEVENT_MIN_SDK) {
2527
+ return input(shellCommand(swipeArgv(from, to, timing.pressMs + timing.durationMs + timing.releaseMs)));
2528
+ }
2529
+ const total = timing.pressMs + timing.durationMs + timing.releaseMs;
2530
+ return input(dragMotionCommand(from, to, timing), INPUT_TEXT_BASE_MS + total + (DRAG_STEPS + 2) * DRAG_PROCESS_BUDGET_MS);
2531
+ };
2532
+ const runGesture = async (plan) => {
2533
+ const fast = await uiad?.gesture(plan);
2534
+ if (fast !== undefined)
2535
+ return fast;
2536
+ if (plan.paths.length > 1) {
2537
+ return driverErr("E_UNSUPPORTED", "two-finger gestures need the warden-uiad server (API 26+, WARDEN_ANDROID_UIAD not 0)");
2538
+ }
2539
+ if (sdk === undefined || sdk < MOTIONEVENT_MIN_SDK) {
2540
+ return driverErr("E_UNSUPPORTED", "gestures need Android 8+ (`input motionevent`) or the warden-uiad server");
2541
+ }
2542
+ return input(gestureMotionCommand(plan), INPUT_TEXT_BASE_MS + plan.totalMs + (GESTURE_LINE_MAX_MOVES + 2) * DRAG_PROCESS_BUDGET_MS);
2543
+ };
2544
+ const maxPointers = () => {
2545
+ if (uiad?.enabled())
2546
+ return 2;
2547
+ return sdk !== undefined && sdk >= MOTIONEVENT_MIN_SDK ? 1 : 0;
2548
+ };
2549
+ const radius = DEFAULT_RADIUS * baseScreen.scale;
2550
+ const pinch = async (center, scale, opts) => {
2551
+ const plan = pinchPlan(center, scale, opts, radius);
2552
+ return plan.success ? runGesture(plan.data) : plan;
2553
+ };
2554
+ const rotate = async (center, degrees, opts) => {
2555
+ const plan = rotatePlan(center, degrees, opts, radius);
2556
+ return plan.success ? runGesture(plan.data) : plan;
2557
+ };
2558
+ const gesture = async (paths, opts) => {
2559
+ const plan = planGesture(paths, opts, maxPointers());
2560
+ return plan.success ? runGesture(plan.data) : plan;
2561
+ };
2562
+ const pressButton = async (button) => {
2563
+ const code = buttonKeycode(button);
2564
+ if (code === undefined)
2565
+ return driverErr("E_UNSUPPORTED", `Android has no ${button} button`);
2566
+ return await uiad?.key(code) ?? input(shellCommand(keyeventArgv(code)));
2567
+ };
2568
+ const screenshot = async (opts) => {
2569
+ const res = await execBinary(screencapArgv(adb, serial), { timeoutMs: SCREENSHOT_TIMEOUT_MS });
2570
+ if (res.exitCode !== 0 || !PNG_MAGIC.every((b, i) => res.stdout[i] === b)) {
2571
+ return driverErr("E_IO", `screencap on ${serial} gave no PNG (exit ${res.exitCode}, ${res.stdout.length} bytes)`);
2572
+ }
2573
+ if (!opts?.path)
2574
+ return { success: true, data: { png: res.stdout } };
2575
+ try {
2576
+ await mkdir2(dirname2(opts.path), { recursive: true });
2577
+ await writeFile2(opts.path, res.stdout);
2578
+ } catch (error) {
2579
+ return driverErr("E_IO", `cannot write ${opts.path}: ${error instanceof Error ? error.message : String(error)}`);
2580
+ }
2581
+ return { success: true, data: { path: opts.path } };
2582
+ };
2583
+ const orientation = async (set) => {
2584
+ if (set) {
2585
+ for (const argv of rotationArgvs(orientationToRotation(set))) {
2586
+ const res = await input(shellCommand(argv));
2587
+ if (!res.success)
2588
+ return res;
2589
+ }
2590
+ return { success: true, data: set };
2591
+ }
2592
+ const res = await session.run(shellCommand(userRotationArgv()));
2593
+ if (!res.success)
2594
+ return res;
2595
+ const n = Number.parseInt(res.data.stdout.trim(), 10);
2596
+ return { success: true, data: rotationToOrientation(Number.isNaN(n) ? 0 : n) };
2597
+ };
2598
+ const settle = async (opts) => {
2599
+ const quietMs = opts?.quietMs ?? DEFAULT_SETTLE_QUIET_MS;
2600
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_SETTLE_TIMEOUT_MS;
2601
+ const fast = await uiad?.settle({ quietMs, timeoutMs });
2602
+ if (fast !== undefined)
2603
+ return fast;
2604
+ if (quietMs <= DUMP_IDLE_MS) {
2605
+ const start = clock.now();
2606
+ const taken = await dumpSnapshot();
2607
+ const waitedMs = clock.now() - start;
2608
+ noteSpanDetail("via", "snapshot");
2609
+ return {
2610
+ success: true,
2611
+ data: { quiet: taken.success && waitedMs < DUMP_IDLE_TIMEOUT_MS, waitedMs, via: "snapshot" }
2612
+ };
2613
+ }
2614
+ const poll = async () => {
2615
+ const taken = await takeSnapshot();
2616
+ if (!taken.success)
2617
+ return "unavailable";
2618
+ return snapshotHash({ flat: taken.data.flat.map((e) => ({ ...e, value: e.value ?? e.text })) });
2619
+ };
2620
+ const outcome = await settleByHash(poll, {
2621
+ quietMs,
2622
+ timeoutMs,
2623
+ now: clock.now,
2624
+ sleep: clock.sleep
2625
+ });
2626
+ noteSpanDetail("via", "hash");
2627
+ return { success: true, data: { ...outcome, via: "hash" } };
2628
+ };
2629
+ const alert = async () => {
2630
+ const taken = await takeSnapshot();
2631
+ return taken.success ? { success: true, data: detectAlert(taken.data.flat) } : taken;
2632
+ };
2633
+ const appearance = async (set) => {
2634
+ if (set) {
2635
+ const res = await runChecked(session, shellCommand(uimodeSetArgv(set === "dark")), "E_UNSUPPORTED");
2636
+ return res.success ? { success: true, data: set } : res;
2637
+ }
2638
+ const res = await runChecked(session, shellCommand(uimodeGetArgv()), "E_UNSUPPORTED");
2639
+ if (!res.success)
2640
+ return res;
2641
+ return { success: true, data: /Night mode:\s*yes/i.test(res.data) ? "dark" : "light" };
2642
+ };
2643
+ const clipboard = async () => driverErr("E_UNSUPPORTED", "Android has no public shell clipboard access; type ASCII with typeText");
2644
+ const health = async () => {
2645
+ const [release, sdk, model] = await Promise.all(["ro.build.version.release", "ro.build.version.sdk", "ro.product.model"].map((k) => session.run(shellCommand(getpropArgv(k)))));
2646
+ const text = (r) => r?.success ? r.data.stdout.trim() : "";
2647
+ const info = {
2648
+ platform: "android",
2649
+ id: serial,
2650
+ version: `Android ${text(release)} (sdk ${text(sdk)})`,
2651
+ transport: "adb",
2652
+ screen: baseScreen,
2653
+ detail: { model: text(model), dumpRoute: dumper.route() ?? null, uiad: uiad?.status() ?? "off" }
2654
+ };
2655
+ return { success: true, data: info };
2656
+ };
2657
+ return {
2658
+ platform: "android",
2659
+ id: serial,
2660
+ get capabilities() {
2661
+ return uiad?.enabled() ? { ...CAPABILITIES, pinch: true, maxPointers: 2, unicodeText: true, nativeSettle: true } : { ...CAPABILITIES, maxPointers: maxPointers() };
2662
+ },
2663
+ health,
2664
+ snapshot,
2665
+ elementAtPoint,
2666
+ tap,
2667
+ longPress: async (point, opts) => await uiad?.longPress(point, opts?.durationMs) ?? input(shellCommand(longPressArgv(point, opts?.durationMs))),
2668
+ swipe: async (from, to, opts) => await uiad?.swipe(from, to, opts?.durationMs) ?? input(shellCommand(swipeArgv(from, to, opts?.durationMs))),
2669
+ drag,
2670
+ pinch,
2671
+ rotate,
2672
+ gesture,
2673
+ typeText,
2674
+ key,
2675
+ pressKeyRepeat,
2676
+ ...uiad !== undefined || sdk !== undefined && sdk >= SELECT_ALL_MIN_SDK ? { selectAll } : {},
2677
+ ...uiad !== undefined ? { setText } : {},
2678
+ pressButton,
2679
+ screenshot,
2680
+ orientation,
2681
+ settle,
2682
+ alert,
2683
+ alertIn: (snapshot) => detectAlert(snapshot.flat),
2684
+ appearance,
2685
+ clipboard,
2686
+ app: createAppCommands({
2687
+ session,
2688
+ exec,
2689
+ adb,
2690
+ serial,
2691
+ ...log ? { log } : {},
2692
+ ...options.appPackage ? { appPackage: options.appPackage } : {}
2693
+ }),
2694
+ close: async () => {
2695
+ await uiad?.stop();
2696
+ await facts.animations.restore();
2697
+ await Promise.all(saving.splice(0));
2698
+ await session.close();
2699
+ }
2700
+ };
2701
+ }
2702
+
2703
+ // ../../packages/e2e/src/driver/ios/ios-driver.ts
2704
+ import { existsSync as existsSync2 } from "fs";
2705
+ import { dirname as dirname4, join as join6 } from "path";
2706
+
2707
+ // ../../packages/e2e/src/driver/ios/ios-session.ts
2708
+ var SHUTDOWN_TIMEOUT_MS = 500;
2709
+ function swappableClient(initial, o) {
2710
+ let current = initial;
2711
+ let generation = 0;
2712
+ let restarting;
2713
+ const request = async (op, params, opts) => {
2714
+ const started = o.now();
2715
+ const result = await current.request(op, params, opts);
2716
+ if (o.debug) {
2717
+ const failure = result.success ? "" : ` ${result.error.code}`;
2718
+ o.log?.(`[ios] op ${op} ${Math.round(o.now() - started)} ms${failure}`);
2719
+ }
2720
+ return result;
2721
+ };
2722
+ const client = {
2723
+ get ready() {
2724
+ return current.ready;
2725
+ },
2726
+ request,
2727
+ close: () => current.close()
2728
+ };
2729
+ async function doRestart(crashed) {
2730
+ const { reopen } = o;
2731
+ if (!reopen)
2732
+ return { success: false, error: driverError("E_INTERNAL", "no way to reopen warden-simd") };
2733
+ const stale = current;
2734
+ if (!crashed)
2735
+ await stale.request("shutdown", {}, { timeoutMs: SHUTDOWN_TIMEOUT_MS });
2736
+ await stale.close();
2737
+ const next = await reopen();
2738
+ if (!next.success)
2739
+ return next;
2740
+ current = next.data;
2741
+ generation += 1;
2742
+ return { success: true, data: undefined };
2743
+ }
2744
+ return {
2745
+ client,
2746
+ generation: () => generation,
2747
+ restart(seenGeneration, r) {
2748
+ if (seenGeneration !== generation)
2749
+ return Promise.resolve({ success: true, data: undefined });
2750
+ restarting ??= doRestart(r?.crashed === true).finally(() => {
2751
+ restarting = undefined;
2752
+ });
2753
+ return restarting;
2754
+ }
2755
+ };
2756
+ }
2757
+
2758
+ // ../../packages/e2e/src/driver/ios/locate-helper.ts
2759
+ import { existsSync, realpathSync } from "fs";
2760
+ import { dirname as dirname3, join as join4, resolve } from "path";
2761
+ var HELPER_NAME = "warden-simd";
2762
+ var DOCTOR_HINT = "run `warden doctor` to check the iOS driver helper, or set WARDEN_SIMD=/path/to/warden-simd";
2763
+ function locateHelper(input) {
2764
+ const resolveLink = input.realpath ?? ((path) => path);
2765
+ const realpath = (path) => {
2766
+ try {
2767
+ return resolveLink(path);
2768
+ } catch {
2769
+ return path;
2770
+ }
2771
+ };
2772
+ const tried = [];
2773
+ const missing = (message) => ({
2774
+ success: false,
2775
+ error: driverError("E_HELPER_MISSING", `${message}; ${DOCTOR_HINT}`, { detail: { tried } })
2776
+ });
2777
+ const explicitFile = input.env[ENV_NAMES.simd];
2778
+ if (explicitFile) {
2779
+ tried.push(explicitFile);
2780
+ return input.exists(explicitFile) ? { success: true, data: { path: explicitFile, source: "WARDEN_SIMD" } } : missing(`WARDEN_SIMD=${explicitFile} does not exist`);
2781
+ }
2782
+ const helperDir = input.env[ENV_NAMES.helperDir];
2783
+ if (helperDir) {
2784
+ const candidate = join4(helperDir, HELPER_NAME);
2785
+ tried.push(candidate);
2786
+ return input.exists(candidate) ? { success: true, data: { path: candidate, source: "WARDEN_HELPER_DIR" } } : missing(`WARDEN_HELPER_DIR=${helperDir} has no ${HELPER_NAME}`);
2787
+ }
2788
+ const candidates = [
2789
+ { path: join4(dirname3(realpath(input.execPath)), HELPER_NAME), source: "execPath" },
2790
+ { path: resolve(input.importDir, "../../../native/dist", HELPER_NAME), source: "source-build" }
2791
+ ];
2792
+ if (input.version) {
2793
+ candidates.push({ path: join4(input.wardenHome, "simd", input.version, HELPER_NAME), source: "warden-home" });
2794
+ }
2795
+ for (const candidate of candidates) {
2796
+ tried.push(candidate.path);
2797
+ if (input.exists(candidate.path))
2798
+ return { success: true, data: candidate };
2799
+ }
2800
+ return missing(`${HELPER_NAME} not found (tried ${tried.join(", ")})`);
2801
+ }
2802
+ function resolveHelper(o) {
2803
+ if (o.helperPath !== undefined)
2804
+ return { success: true, data: o.helperPath };
2805
+ const located = locateHelper({
2806
+ env: { [ENV_NAMES.simd]: o.env?.simd, [ENV_NAMES.helperDir]: o.env?.helperDir },
2807
+ execPath: o.execPath ?? process.execPath,
2808
+ importDir: o.importDir ?? import.meta.dir,
2809
+ wardenHome: o.wardenHome ?? wardenHome(),
2810
+ exists: o.exists ?? existsSync,
2811
+ realpath: o.realpath ?? realpathSync,
2812
+ ...o.version ? { version: o.version } : {}
2813
+ });
2814
+ return located.success ? { success: true, data: located.data.path } : located;
2815
+ }
2816
+
2817
+ // ../../packages/e2e/src/driver/ios/normalize.ts
2818
+ function normFrameOf(frame, screen) {
2819
+ if (screen.width <= 0 || screen.height <= 0)
2820
+ return { x: 0, y: 0, width: 0, height: 0 };
2821
+ return {
2822
+ x: frame.x / screen.width,
2823
+ y: frame.y / screen.height,
2824
+ width: frame.width / screen.width,
2825
+ height: frame.height / screen.height
2826
+ };
2827
+ }
2828
+ var STRING_FIELDS2 = ["label", "identifier", "value", "text", "placeholder"];
2829
+ var FLAG_FIELDS2 = ["focused", "hidden", "selected", "checked", "hittable"];
2830
+ function optionalFields(node) {
2831
+ const fields = {};
2832
+ for (const key of STRING_FIELDS2) {
2833
+ const value = node[key];
2834
+ if (typeof value === "string")
2835
+ fields[key] = value;
2836
+ }
2837
+ for (const key of FLAG_FIELDS2) {
2838
+ const value = node[key];
2839
+ if (typeof value === "boolean")
2840
+ fields[key] = value;
2841
+ }
2842
+ if (node.traits?.includes("is-editing"))
2843
+ fields.focused = true;
2844
+ if (node.tapPoint)
2845
+ fields.tapPoint = node.tapPoint;
2846
+ if (typeof node.pid === "number")
2847
+ fields.pid = node.pid;
2848
+ return fields;
2849
+ }
2850
+ function normalizeElements(wire, screen, prefix = "", depth = 0) {
2851
+ const flat = [];
2852
+ const elements = wire.map((node, index) => {
2853
+ const path = prefix === "" ? String(index) : `${prefix}.${index}`;
2854
+ const element = {
2855
+ ...optionalFields(node),
2856
+ id: node.id ?? path,
2857
+ role: node.role,
2858
+ rawType: node.rawType,
2859
+ frame: node.frame,
2860
+ depth: node.depth ?? depth,
2861
+ normFrame: node.normFrame ?? normFrameOf(node.frame, screen),
2862
+ traits: node.traits ?? [],
2863
+ enabled: node.enabled ?? !(node.traits ?? []).includes("not-enabled"),
2864
+ children: []
2865
+ };
2866
+ flat.push(element);
2867
+ const inner = normalizeElements(node.children ?? [], screen, path, depth + 1);
2868
+ element.children = inner.elements;
2869
+ flat.push(...inner.flat);
2870
+ return element;
2871
+ });
2872
+ return { elements, flat };
2873
+ }
2874
+ function normalizeElement(wire, screen) {
2875
+ const [element] = normalizeElements([wire], screen).elements;
2876
+ if (!element)
2877
+ throw new Error("normalizeElement: no element");
2878
+ return element;
2879
+ }
2880
+ function toSnapshot(data, takenAt) {
2881
+ const { elements, flat } = normalizeElements(data.elements, data.screen);
2882
+ const snapshot = {
2883
+ platform: "ios",
2884
+ backend: data.backend,
2885
+ takenAt,
2886
+ screen: data.screen,
2887
+ elements,
2888
+ flat,
2889
+ truncated: data.truncated
2890
+ };
2891
+ if (data.modal)
2892
+ snapshot.modal = data.modal;
2893
+ return snapshot;
2894
+ }
2895
+
2896
+ // ../../packages/e2e/src/driver/ios/simctl.ts
2897
+ import { mkdtemp, readFile as readFile2, rm as rm2 } from "fs/promises";
2898
+ import { homedir, tmpdir } from "os";
2899
+ import { join as join5 } from "path";
2900
+
2901
+ // ../../packages/core/src/builds/install.ts
2902
+ import { mkdir as mkdir3, readdir, rm, stat } from "fs/promises";
2903
+ var INSTALL_TIMEOUT_MS = 10 * 60000;
2904
+ function probeArgv(target, bundleId, env) {
2905
+ return target.platform === "ios" ? ["xcrun", "simctl", "get_app_container", target.deviceId, bundleId] : [androidTools(env).adb, "-s", target.deviceId, "shell", "pm", "path", bundleId];
2906
+ }
2907
+ async function probeApp(deps, target, bundleId) {
2908
+ const res = await deps.exec(probeArgv(target, bundleId, deps.env));
2909
+ if (target.platform === "ios") {
2910
+ const container = res.stdout.trim();
2911
+ return ok(res.exitCode === 0 && container !== "" ? { present: true, container } : { present: false });
2912
+ }
2913
+ return ok({ present: res.exitCode === 0 && res.stdout.includes("package:") });
2914
+ }
2915
+ async function isAppInstalled(deps, target, bundleId) {
2916
+ const probed = await probeApp(deps, target, bundleId);
2917
+ return probed.success ? ok(probed.data.present) : probed;
2918
+ }
2919
+ var nodeResetFs = {
2920
+ readdir: (dir) => readdir(dir),
2921
+ rm: (path) => rm(path, { recursive: true, force: true }),
2922
+ mkdir: async (path) => {
2923
+ await mkdir3(path, { recursive: true });
2924
+ }
2925
+ };
2926
+ var CONTAINER_METADATA = ".com.apple.mobile_container_manager.metadata.plist";
2927
+ var DATA_SUBDIRS = ["Documents", "Library/Caches", "Library/Preferences", "tmp"];
2928
+ function resetArgv(target, bundleId, env) {
2929
+ return target.platform === "ios" ? ["xcrun", "simctl", "privacy", target.deviceId, "reset", "all", bundleId] : [androidTools(env).adb, "-s", target.deviceId, "shell", "pm", "clear", bundleId];
2930
+ }
2931
+ async function wipeDir(fs, dir) {
2932
+ for (const entry of await fs.readdir(dir)) {
2933
+ if (entry !== CONTAINER_METADATA)
2934
+ await fs.rm(`${dir}/${entry}`);
2935
+ }
2936
+ }
2937
+ async function wipeGroups(deps, udid, bundleId) {
2938
+ const groups = await deps.exec(["xcrun", "simctl", "get_app_container", udid, bundleId, "groups"]);
2939
+ if (groups.exitCode !== 0)
2940
+ return;
2941
+ for (const line of groups.stdout.split(`
2942
+ `)) {
2943
+ const [, ...rest] = line.trim().split(/\s+/);
2944
+ const path = rest.join(" ");
2945
+ if (path !== "")
2946
+ await wipeDir(deps.fs, path);
2947
+ }
2948
+ }
2949
+ async function resetIos(deps, target, bundleId, opts) {
2950
+ const udid = target.deviceId;
2951
+ await deps.exec(["xcrun", "simctl", "terminate", udid, bundleId]);
2952
+ await deps.exec(["xcrun", "simctl", "spawn", udid, "defaults", "delete", bundleId]);
2953
+ const dataArgv = ["xcrun", "simctl", "get_app_container", udid, bundleId, "data"];
2954
+ const data = await deps.exec(dataArgv);
2955
+ const dataDir = data.stdout.trim();
2956
+ if (data.exitCode !== 0 || dataDir === "")
2957
+ return err(`data container not found: ${execError(dataArgv, data)}`);
2958
+ try {
2959
+ await wipeDir(deps.fs, dataDir);
2960
+ for (const sub of DATA_SUBDIRS)
2961
+ await deps.fs.mkdir(`${dataDir}/${sub}`);
2962
+ await wipeGroups(deps, udid, bundleId);
2963
+ } catch (e) {
2964
+ return err(`wiping app data failed: ${e instanceof Error ? e.message : String(e)}`);
2965
+ }
2966
+ const privacy = resetArgv(target, bundleId, deps.env);
2967
+ const priv = await deps.exec(privacy);
2968
+ if (priv.exitCode !== 0)
2969
+ return err(`privacy reset failed: ${execError(privacy, priv)}`);
2970
+ return opts.keychain ? resetSimKeychain(deps, target) : ok(undefined);
2971
+ }
2972
+ async function resetSimKeychain(deps, target) {
2973
+ if (target.platform !== "ios")
2974
+ return ok(undefined);
2975
+ const argv = ["xcrun", "simctl", "keychain", target.deviceId, "reset"];
2976
+ const res = await deps.exec(argv);
2977
+ return res.exitCode === 0 ? ok(undefined) : err(`keychain reset failed: ${execError(argv, res)}`);
2978
+ }
2979
+ async function resetAppData(deps, target, bundleId, opts = {}) {
2980
+ const present = await isAppInstalled(deps, target, bundleId);
2981
+ if (!present.success)
2982
+ return present;
2983
+ if (!present.data)
2984
+ return err(`${bundleId} is not installed on ${target.deviceId}`);
2985
+ if (target.platform === "ios")
2986
+ return resetIos(deps, target, bundleId, opts);
2987
+ const argv = resetArgv(target, bundleId, deps.env);
2988
+ const res = await deps.exec(argv);
2989
+ if (res.exitCode !== 0 || !res.stdout.includes("Success"))
2990
+ return err(`pm clear failed: ${execError(argv, res)}`);
2991
+ return ok(undefined);
2992
+ }
2993
+
2994
+ // ../../packages/e2e/src/driver/ios/simctl.ts
2995
+ var XCRUN = ["xcrun", "simctl"];
2996
+ var PLIST_READ_MS = 2000;
2997
+ var SHORT_MS = 15000;
2998
+ var LAUNCH_MS = 30000;
2999
+ var INSTALL_MS = 10 * 60000;
3000
+ var SIMCTL_CHILD_PREFIX = "SIMCTL_CHILD_";
3001
+ function launchArgv(udid, bundleId, opts = {}) {
3002
+ return [
3003
+ ...XCRUN,
3004
+ "launch",
3005
+ ...opts.relaunch === false ? [] : ["--terminate-running-process"],
3006
+ udid,
3007
+ bundleId,
3008
+ ...opts.args ?? []
3009
+ ];
3010
+ }
3011
+ function launchEnv(env) {
3012
+ return Object.fromEntries(Object.entries(env ?? {}).map(([key, value]) => [`${SIMCTL_CHILD_PREFIX}${key}`, value]));
3013
+ }
3014
+ var terminateArgv = (udid, bundleId) => [...XCRUN, "terminate", udid, bundleId];
3015
+ var openUrlArgv = (udid, url) => [...XCRUN, "openurl", udid, url];
3016
+ var installArgv2 = (udid, appPath) => [...XCRUN, "install", udid, appPath];
3017
+ var uninstallArgv2 = (udid, bundleId) => [...XCRUN, "uninstall", udid, bundleId];
3018
+ var appContainerArgv = (udid, bundleId) => [
3019
+ ...XCRUN,
3020
+ "get_app_container",
3021
+ udid,
3022
+ bundleId
3023
+ ];
3024
+ var launchctlListArgv = (udid) => [...XCRUN, "spawn", udid, "launchctl", "list"];
3025
+ var pbpasteArgv = (udid) => [...XCRUN, "pbpaste", udid];
3026
+ var appearanceArgv = (udid, set) => [
3027
+ ...XCRUN,
3028
+ "ui",
3029
+ udid,
3030
+ "appearance",
3031
+ ...set ? [set] : []
3032
+ ];
3033
+ var screenshotArgv = (udid, path) => [
3034
+ ...XCRUN,
3035
+ "io",
3036
+ udid,
3037
+ "screenshot",
3038
+ "--type=png",
3039
+ path
3040
+ ];
3041
+ var automationArgv = (udid) => [
3042
+ ...XCRUN,
3043
+ "spawn",
3044
+ udid,
3045
+ "defaults",
3046
+ "write",
3047
+ "com.apple.Accessibility",
3048
+ "AutomationEnabled",
3049
+ "-bool",
3050
+ "true"
3051
+ ];
3052
+ var defaultDevicesRoot = () => join5(homedir(), "Library", "Developer", "CoreSimulator", "Devices");
3053
+ var automationPlistPath = (devicesRoot, udid) => join5(devicesRoot, udid, "data", "Library", "Preferences", "com.apple.Accessibility.plist");
3054
+ var automationReadArgv = (plist) => [
3055
+ "plutil",
3056
+ "-extract",
3057
+ "AutomationEnabled",
3058
+ "raw",
3059
+ "-o",
3060
+ "-",
3061
+ plist
3062
+ ];
3063
+ function statusBarOverrideArgv(udid, o = {}) {
3064
+ const time = o.time ?? "9:41";
3065
+ const flags = [
3066
+ ["--time", time],
3067
+ ["--batteryState", o.batteryState ?? "charged"],
3068
+ ["--batteryLevel", String(o.batteryLevel ?? 100)],
3069
+ ["--wifiBars", String(o.wifiBars ?? 3)],
3070
+ ["--cellularBars", String(o.cellularBars ?? 4)]
3071
+ ];
3072
+ return [...XCRUN, "status_bar", udid, "override", ...flags.flat()];
3073
+ }
3074
+ var statusBarClearArgv = (udid) => [...XCRUN, "status_bar", udid, "clear"];
3075
+ function pbcopyArgv(udid, text) {
3076
+ return ["sh", "-c", 'printf %s "$1" | xcrun simctl pbcopy "$2"', "sh", text, udid];
3077
+ }
3078
+ function devicectlPbcopyArgv(udid, text) {
3079
+ return [
3080
+ "sh",
3081
+ "-c",
3082
+ 'printf %s "$1" | xcrun devicectl device pasteboard copy --quiet --device "$2"',
3083
+ "sh",
3084
+ text,
3085
+ udid
3086
+ ];
3087
+ }
3088
+ var PBCOPY_CHANNELS = [
3089
+ { name: "devicectl pasteboard copy", argv: devicectlPbcopyArgv },
3090
+ { name: "simctl pbcopy", argv: pbcopyArgv }
3091
+ ];
3092
+ function escapeRegExp(text) {
3093
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3094
+ }
3095
+ function parseRunning(launchctlOutput, bundleId) {
3096
+ const row = new RegExp(`^\\s*(\\d+)\\s+\\S+\\s+UIKitApplication:${escapeRegExp(bundleId)}\\[`, "m");
3097
+ return row.test(launchctlOutput);
3098
+ }
3099
+ function simctlFailure(argv, res, appCommand = false) {
3100
+ const text = `${res.stderr}
3101
+ ${res.stdout}`;
3102
+ const message = execError(argv, res);
3103
+ const detail = { argv, exitCode: res.exitCode };
3104
+ if (/current state:\s*(Shutdown|Creating|Shutting Down)/i.test(text) || /Unable to .* not booted/i.test(text)) {
3105
+ return driverError("E_NOT_BOOTED", message, { detail });
3106
+ }
3107
+ if (/Invalid device|No devices are booted|device .* not found/i.test(text)) {
3108
+ return driverError("E_UNKNOWN_DEVICE", message, { detail });
3109
+ }
3110
+ if (appCommand && /not installed|found nothing|FBSOpenApplicationServiceErrorDomain|No such (file|app)|unknown app/i.test(text)) {
3111
+ return driverError("E_APP_UNAVAILABLE", message, { detail });
3112
+ }
3113
+ return driverError("E_IO", message, { detail });
3114
+ }
3115
+ var ok4 = (data) => ({ success: true, data });
3116
+ function createSimctl(deps) {
3117
+ const { udid, exec } = deps;
3118
+ const env = deps.env ?? {};
3119
+ async function run(argv, timeoutMs, appCommand = false, envOverride) {
3120
+ const res = await exec(argv, { timeoutMs, ...envOverride ? { env: envOverride } : {} });
3121
+ return res.exitCode === 0 ? { ok: true, res } : { ok: false, error: simctlFailure(argv, res, appCommand) };
3122
+ }
3123
+ async function runVoid(argv, timeoutMs = SHORT_MS, appCommand = false) {
3124
+ const result = await run(argv, timeoutMs, appCommand);
3125
+ return result.ok ? ok4(undefined) : { success: false, error: result.error };
3126
+ }
3127
+ const probe = async (bundleId) => {
3128
+ const argv = appContainerArgv(udid, bundleId);
3129
+ const res = await exec(argv, { timeoutMs: SHORT_MS });
3130
+ return res.exitCode === 0 && res.stdout.trim() !== "";
3131
+ };
3132
+ async function screenshotToScratch() {
3133
+ const dir = await (deps.scratchDir ?? (() => mkdtemp(join5(tmpdir(), "warden-shot-"))))();
3134
+ const file = join5(dir, "screen.png");
3135
+ try {
3136
+ const done = await runVoid(screenshotArgv(udid, file), LAUNCH_MS);
3137
+ if (!done.success)
3138
+ return done;
3139
+ const bytes = await (deps.readFile ?? ((p) => readFile2(p)))(file);
3140
+ return ok4({ png: new Uint8Array(bytes) });
3141
+ } catch (error) {
3142
+ const reason = error instanceof Error ? error.message : String(error);
3143
+ return { success: false, error: driverError("E_IO", `reading screenshot: ${reason}`) };
3144
+ } finally {
3145
+ await (deps.rmDir ?? ((p) => rm2(p, { recursive: true, force: true })))(dir).catch(() => {
3146
+ return;
3147
+ });
3148
+ }
3149
+ }
3150
+ return {
3151
+ async launch(bundleId, opts = {}) {
3152
+ const result = await run(launchArgv(udid, bundleId, opts), LAUNCH_MS, true, launchEnv(opts.env));
3153
+ return result.ok ? ok4(undefined) : { success: false, error: result.error };
3154
+ },
3155
+ async terminate(bundleId) {
3156
+ const argv = terminateArgv(udid, bundleId);
3157
+ const res = await exec(argv, { timeoutMs: SHORT_MS });
3158
+ if (res.exitCode === 0 || /found nothing to terminate/i.test(`${res.stderr}${res.stdout}`))
3159
+ return ok4(undefined);
3160
+ return { success: false, error: simctlFailure(argv, res) };
3161
+ },
3162
+ openUrl: (url) => runVoid(openUrlArgv(udid, url), SHORT_MS, true),
3163
+ install: (path) => runVoid(installArgv2(udid, path), INSTALL_MS),
3164
+ async uninstall(bundleId) {
3165
+ if (!await probe(bundleId))
3166
+ return ok4(undefined);
3167
+ return runVoid(uninstallArgv2(udid, bundleId), INSTALL_MS);
3168
+ },
3169
+ async isInstalled(bundleId) {
3170
+ return ok4(await probe(bundleId));
3171
+ },
3172
+ async isRunning(bundleId) {
3173
+ const result = await run(launchctlListArgv(udid), SHORT_MS);
3174
+ return result.ok ? ok4(parseRunning(result.res.stdout, bundleId)) : { success: false, error: result.error };
3175
+ },
3176
+ async clearData(bundleId, opts = {}) {
3177
+ const reset = await resetAppData({ exec, env, fs: deps.resetFs ?? nodeResetFs }, { platform: "ios", deviceId: udid }, bundleId, opts.keychain ? { keychain: true } : {});
3178
+ if (reset.success)
3179
+ return ok4(undefined);
3180
+ const code = /is not installed/.test(reset.error) ? "E_APP_UNAVAILABLE" : "E_IO";
3181
+ return { success: false, error: driverError(code, reset.error) };
3182
+ },
3183
+ async pbcopy(text) {
3184
+ const outcomes = [];
3185
+ for (const channel of PBCOPY_CHANNELS) {
3186
+ const copied = await exec(channel.argv(udid, text), { timeoutMs: SHORT_MS });
3187
+ if (copied.exitCode !== 0) {
3188
+ outcomes.push(`${channel.name} exit ${copied.exitCode}`);
3189
+ continue;
3190
+ }
3191
+ const read = await run(pbpasteArgv(udid), SHORT_MS);
3192
+ if (!read.ok)
3193
+ return { success: false, error: read.error };
3194
+ if (read.res.stdout === text)
3195
+ return ok4(undefined);
3196
+ outcomes.push(`${channel.name} exit 0 but pbpaste reads other text`);
3197
+ }
3198
+ return {
3199
+ success: false,
3200
+ error: driverError("E_IO", `simulator pasteboard did not take the text (${outcomes.join("; ")})`)
3201
+ };
3202
+ },
3203
+ async pbpaste() {
3204
+ const result = await run(pbpasteArgv(udid), SHORT_MS);
3205
+ return result.ok ? ok4(result.res.stdout) : { success: false, error: result.error };
3206
+ },
3207
+ async appearance(set) {
3208
+ if (set) {
3209
+ const done = await runVoid(appearanceArgv(udid, set));
3210
+ return done.success ? ok4(set) : done;
3211
+ }
3212
+ const result = await run(appearanceArgv(udid), SHORT_MS);
3213
+ if (!result.ok)
3214
+ return { success: false, error: result.error };
3215
+ const value = result.res.stdout.trim();
3216
+ if (value === "light" || value === "dark")
3217
+ return ok4(value);
3218
+ return { success: false, error: driverError("E_UNSUPPORTED", `simctl ui appearance reported \`${value}\``) };
3219
+ },
3220
+ statusBar: (overrides) => runVoid(overrides === "clear" ? statusBarClearArgv(udid) : statusBarOverrideArgv(udid, overrides)),
3221
+ async screenshot(path) {
3222
+ if (!path)
3223
+ return screenshotToScratch();
3224
+ const done = await runVoid(screenshotArgv(udid, path), LAUNCH_MS);
3225
+ return done.success ? ok4({ path }) : done;
3226
+ },
3227
+ enableAutomation: async () => {
3228
+ const plist = automationPlistPath(deps.devicesRoot ?? defaultDevicesRoot(), udid);
3229
+ const current = await exec(automationReadArgv(plist), { timeoutMs: PLIST_READ_MS }).catch(() => {
3230
+ return;
3231
+ });
3232
+ if (current?.exitCode === 0 && current.stdout.trim() === "true")
3233
+ return ok4(undefined);
3234
+ return runVoid(automationArgv(udid));
3235
+ }
3236
+ };
3237
+ }
3238
+
3239
+ // ../../packages/e2e/src/driver/ios/type-budget.ts
3240
+ var TYPE_BASE_MS = 1e4;
3241
+ var TYPE_PER_CHAR_MS = 70;
3242
+ var TYPE_PER_REPEAT_MS = 300;
3243
+ function typeTextBudgetMs(text) {
3244
+ const characters = Array.from(text);
3245
+ let repeats = 0;
3246
+ for (let index = 1;index < characters.length; index += 1) {
3247
+ if (characters[index] === characters[index - 1])
3248
+ repeats += 1;
3249
+ }
3250
+ return TYPE_BASE_MS + characters.length * TYPE_PER_CHAR_MS + repeats * TYPE_PER_REPEAT_MS;
3251
+ }
3252
+
3253
+ // ../../packages/e2e/src/driver/ios/ios-driver.ts
3254
+ var DEFAULT_QUIET_MS = 250;
3255
+ var DEFAULT_SETTLE_TIMEOUT_MS2 = 5000;
3256
+ var SETTLE_MARGIN_MS = 2000;
3257
+ var REBIND_TIMEOUT_MS = 3000;
3258
+ var GESTURE_MARGIN_MS = 5000;
3259
+ var FRONTMOST_TIMEOUT_MS = 3000;
3260
+ var KEY_USAGE = {
3261
+ Enter: 40,
3262
+ Escape: 41,
3263
+ Backspace: 42,
3264
+ Tab: 43,
3265
+ Space: 44,
3266
+ Delete: 76,
3267
+ Right: 79,
3268
+ Left: 80,
3269
+ Down: 81,
3270
+ Up: 82
3271
+ };
3272
+ var BUTTONS = {
3273
+ home: "home",
3274
+ lock: "lock",
3275
+ volumeUp: "volumeUp",
3276
+ volumeDown: "volumeDown",
3277
+ shake: "shake"
3278
+ };
3279
+ var SCREENSHOT_FALLBACK = new Set(["E_UNSUPPORTED", "E_IO", "E_BAD_REQUEST", "E_INTERNAL"]);
3280
+ var RETRIED_AFTER_CRASH = new Set([
3281
+ "snapshot",
3282
+ "health",
3283
+ "alertButtons",
3284
+ "frontmost",
3285
+ "settle",
3286
+ "elementAtPoint",
3287
+ "screenshot"
3288
+ ]);
3289
+ var SOCKET_RETRY_MS = 500;
3290
+ function params(values) {
3291
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
3292
+ }
3293
+ var okVoid = { success: true, data: undefined };
3294
+ function fail(error) {
3295
+ return { success: false, error };
3296
+ }
3297
+ function nativeDetail(data) {
3298
+ const { quiet, waitedMs, via, reason, frontmostPid } = data;
3299
+ const detail = {
3300
+ quiet,
3301
+ waitedMs,
3302
+ ...via === undefined ? {} : { via },
3303
+ ...reason === undefined ? {} : { reason },
3304
+ ...frontmostPid === undefined ? {} : { frontmostPid }
3305
+ };
3306
+ if (via !== undefined)
3307
+ noteSpanDetail("via", via);
3308
+ if (reason !== undefined)
3309
+ noteSpanDetail("reason", reason);
3310
+ if (frontmostPid !== undefined)
3311
+ noteSpanDetail("frontmostPid", frontmostPid);
3312
+ return detail;
3313
+ }
3314
+
3315
+ class DriverFailure extends Error {
3316
+ error;
3317
+ constructor(error) {
3318
+ super(error.message);
3319
+ this.error = error;
3320
+ }
3321
+ }
3322
+ function restOf(error, text) {
3323
+ const detail = error.detail;
3324
+ if (typeof detail === "object" && detail !== null && "rest" in detail && typeof detail.rest === "string") {
3325
+ return detail.rest;
3326
+ }
3327
+ return text.length === 0 ? "" : undefined;
3328
+ }
3329
+ function decodeBase64(png) {
3330
+ return new Uint8Array(Buffer.from(png, "base64"));
3331
+ }
3332
+ function createIosDriver(swap, simctl, opts) {
3333
+ const { udid, now, sleep } = opts;
3334
+ const { client } = swap;
3335
+ let axFailures = 0;
3336
+ let launchedBundle;
3337
+ const { ready } = client;
3338
+ const nativeSettle = ready.ax.default === "axbridge" && ready.ax.bridge === "available";
3339
+ const bridgeUp = ready.ax.bridge === "available";
3340
+ let settleUnsupported = !nativeSettle;
3341
+ let rebindUnsupported = false;
3342
+ let gestureUnsupported = false;
3343
+ const capabilities = {
3344
+ pinch: true,
3345
+ maxPointers: 2,
3346
+ unicodeText: true,
3347
+ nativeSettle,
3348
+ alerts: true,
3349
+ reachability: true,
3350
+ cheapHitTest: true
3351
+ };
3352
+ let lost;
3353
+ let answeredGeneration = swap.generation();
3354
+ async function replaceLost(op, seen, crash) {
3355
+ if (seen !== swap.generation())
3356
+ return;
3357
+ if (seen !== answeredGeneration)
3358
+ return crash;
3359
+ opts.log?.(`[ios] warden-simd went away during \`${op}\` (E_HELPER_CRASHED: ${crash.message}); reconnecting`);
3360
+ const restarted = await swap.restart(seen, { crashed: true });
3361
+ if (restarted.success)
3362
+ return;
3363
+ const why = `${restarted.error.code}: ${restarted.error.message}`;
3364
+ opts.log?.(`[ios] could not replace warden-simd (${why})`);
3365
+ lost ??= driverError("E_HELPER_CRASHED", `${crash.message}; could not replace it (${why})`, { retryable: false });
3366
+ return lost;
3367
+ }
3368
+ async function request(op, p = {}, o = {}) {
3369
+ if (lost)
3370
+ return fail(lost);
3371
+ const seen = swap.generation();
3372
+ const first = await client.request(op, p, o);
3373
+ if (first.success) {
3374
+ answeredGeneration = seen;
3375
+ return first;
3376
+ }
3377
+ if (first.error.code !== "E_HELPER_CRASHED")
3378
+ return first;
3379
+ const stop = await replaceLost(op, seen, first.error);
3380
+ if (stop)
3381
+ return fail(stop);
3382
+ if (!RETRIED_AFTER_CRASH.has(op))
3383
+ return first;
3384
+ const generation = swap.generation();
3385
+ const again = await client.request(op, p, o);
3386
+ if (again.success)
3387
+ answeredGeneration = generation;
3388
+ return again;
3389
+ }
3390
+ const send = (op, p = {}, timeoutMs) => request(op, p, timeoutMs === undefined ? {} : { timeoutMs });
3391
+ const sendVoid = async (op, p = {}, timeoutMs) => {
3392
+ const result = await send(op, p, timeoutMs);
3393
+ return result.success ? okVoid : result;
3394
+ };
3395
+ const gestureTimeout = (durationMs, base = 0) => (durationMs ?? 0) + base + GESTURE_MARGIN_MS;
3396
+ const helperOutdated = () => fail(driverError("E_UNSUPPORTED", `this warden-simd predates the gesture op: restart the helper (warden ui close --udid ${udid}) or rebuild it (bun run --cwd packages/e2e native:build)`, { detail: { reason: "helper_outdated" } }));
3397
+ const sendGesture = async (planned) => {
3398
+ if (!planned.success)
3399
+ return planned;
3400
+ if (gestureUnsupported)
3401
+ return helperOutdated();
3402
+ const plan = planned.data;
3403
+ const result = await sendVoid("gesture", { paths: plan.paths, frameMs: plan.frameMs, holdMs: plan.holdMs, releaseMs: plan.releaseMs }, gestureTimeout(plan.totalMs, 300));
3404
+ if (!result.success && result.error.code === "E_BAD_REQUEST" && /unknown (input )?op/.test(result.error.message)) {
3405
+ gestureUnsupported = true;
3406
+ return helperOutdated();
3407
+ }
3408
+ return result;
3409
+ };
3410
+ const fetchSnapshot = async (o) => {
3411
+ const result = await request("snapshot", params({ interactiveOnly: o?.interactiveOnly, backend: o?.backend }));
3412
+ if (!result.success)
3413
+ return result;
3414
+ const { fallback } = result.data;
3415
+ if (fallback)
3416
+ opts.log?.(`[ios] snapshot fell back ${fallback.from} -> ${fallback.to}: ${fallback.reason}`);
3417
+ return { success: true, data: toSnapshot(result.data, now()) };
3418
+ };
3419
+ async function snapshot(o) {
3420
+ const generation = swap.generation();
3421
+ const first = await fetchSnapshot(o);
3422
+ if (first.success) {
3423
+ axFailures = 0;
3424
+ return first;
3425
+ }
3426
+ if (first.error.code !== "E_AX_UNAVAILABLE") {
3427
+ axFailures = 0;
3428
+ return first;
3429
+ }
3430
+ axFailures += 1;
3431
+ if (axFailures < 2)
3432
+ return first;
3433
+ opts.log?.("[ios] E_AX_UNAVAILABLE twice in a row: AX bridge looks stale, restarting warden-simd");
3434
+ const restarted = await swap.restart(generation);
3435
+ if (!restarted.success)
3436
+ return restarted;
3437
+ axFailures = 0;
3438
+ return fetchSnapshot(o);
3439
+ }
3440
+ async function typeText(text) {
3441
+ const typed = await sendVoid("typeText", { text }, typeTextBudgetMs(text));
3442
+ if (typed.success || typed.error.code !== "E_UNSUPPORTED_TEXT")
3443
+ return typed;
3444
+ const rest = restOf(typed.error, text);
3445
+ if (rest === undefined)
3446
+ return typed;
3447
+ const copied = await simctl.pbcopy(rest);
3448
+ if (!copied.success)
3449
+ return copied;
3450
+ opts.log?.(`[ios] typeText: pasting ${rest.length} unmappable character(s)`);
3451
+ return sendVoid("paste");
3452
+ }
3453
+ async function hashSettle(o) {
3454
+ try {
3455
+ const outcome = await settleByHash(async () => {
3456
+ const snap = await snapshot();
3457
+ if (!snap.success)
3458
+ throw new DriverFailure(snap.error);
3459
+ return snapshotHash(snap.data);
3460
+ }, { quietMs: o.quietMs, timeoutMs: o.timeoutMs, now, sleep });
3461
+ noteSpanDetail("via", "hash");
3462
+ return { success: true, data: { ...outcome, via: "hash" } };
3463
+ } catch (error) {
3464
+ if (error instanceof DriverFailure)
3465
+ return fail(error.error);
3466
+ throw error;
3467
+ }
3468
+ }
3469
+ async function rebind(bundleId) {
3470
+ if (rebindUnsupported || settleUnsupported)
3471
+ return false;
3472
+ const reply = await request("rebind", { bundleId, rebindTimeoutMs: REBIND_TIMEOUT_MS }, { timeoutMs: REBIND_TIMEOUT_MS + SETTLE_MARGIN_MS });
3473
+ if (!reply.success) {
3474
+ if (reply.error.code === "E_UNSUPPORTED" || reply.error.code === "E_BAD_REQUEST")
3475
+ rebindUnsupported = true;
3476
+ noteSpanDetail("rebind", reply.error.code);
3477
+ opts.log?.(`[ios] rebind after launch failed (${reply.error.code}): ${reply.error.message}`);
3478
+ return false;
3479
+ }
3480
+ noteSpanDetail("rebind", reply.data.bound ? "bound" : reply.data.reason ?? "unbound");
3481
+ noteSpanDetail("rebindMs", reply.data.waitedMs);
3482
+ if (reply.data.restarts !== undefined && reply.data.restarts > 0)
3483
+ noteSpanDetail("rebindRestarts", reply.data.restarts);
3484
+ if (reply.data.recycled === true)
3485
+ noteSpanDetail("rebindRecycled", true);
3486
+ return reply.data.bound;
3487
+ }
3488
+ async function settle(o = {}) {
3489
+ const quietMs = o.quietMs ?? DEFAULT_QUIET_MS;
3490
+ const timeoutMs = o.timeoutMs ?? DEFAULT_SETTLE_TIMEOUT_MS2;
3491
+ if (!settleUnsupported) {
3492
+ const expected = launchedBundle;
3493
+ const native = await request("settle", { quietMs, settleTimeoutMs: timeoutMs, ...expected === undefined ? {} : { bundleId: expected } }, { timeoutMs: timeoutMs + SETTLE_MARGIN_MS });
3494
+ if (native.success) {
3495
+ if (expected !== undefined && native.data.reason !== "frontmost_mismatch")
3496
+ launchedBundle = undefined;
3497
+ return { success: true, data: nativeDetail(native.data) };
3498
+ }
3499
+ if (native.error.code === "E_TIMEOUT") {
3500
+ noteSpanDetail("reason", "helper_timeout");
3501
+ return {
3502
+ success: true,
3503
+ data: { quiet: false, waitedMs: timeoutMs + SETTLE_MARGIN_MS, reason: "helper_timeout" }
3504
+ };
3505
+ }
3506
+ if (native.error.code !== "E_UNSUPPORTED")
3507
+ return native;
3508
+ settleUnsupported = true;
3509
+ opts.log?.("[ios] native settle unsupported, polling snapshot hashes");
3510
+ }
3511
+ return hashSettle({ quietMs, timeoutMs });
3512
+ }
3513
+ async function screenshot(o) {
3514
+ const viaHelper = await request("screenshot", params({ path: o?.path, scale: o?.scale }));
3515
+ if (viaHelper.success) {
3516
+ const { path, png } = viaHelper.data;
3517
+ if (o?.path)
3518
+ return { success: true, data: { path: path ?? o.path } };
3519
+ if (png !== undefined)
3520
+ return { success: true, data: { png: decodeBase64(png) } };
3521
+ if (path !== undefined)
3522
+ return { success: true, data: { path } };
3523
+ return fail(driverError("E_INTERNAL", "warden-simd screenshot returned neither path nor png"));
3524
+ }
3525
+ if (!SCREENSHOT_FALLBACK.has(viaHelper.error.code))
3526
+ return viaHelper;
3527
+ opts.log?.(`[ios] helper screenshot failed (${viaHelper.error.code}), using simctl io`);
3528
+ return simctl.screenshot(o?.path);
3529
+ }
3530
+ function toAlertInfo(data) {
3531
+ const { title, message, buttons } = data;
3532
+ const info = { buttons: normalizeElements(buttons, client.ready.screen).elements };
3533
+ if (title !== undefined)
3534
+ info.title = title;
3535
+ if (message !== undefined)
3536
+ info.message = message;
3537
+ return info;
3538
+ }
3539
+ async function readAlert(backend) {
3540
+ const result = await request("alertButtons", params({ backend }));
3541
+ if (!result.success)
3542
+ return result;
3543
+ return { success: true, data: result.data === null ? null : toAlertInfo(result.data) };
3544
+ }
3545
+ const alert = () => readAlert();
3546
+ const alertOn = (backend) => readAlert(backend === "axbridge" && !bridgeUp ? "ax" : backend);
3547
+ async function isFrontmost(bundleId) {
3548
+ if (!bridgeUp)
3549
+ return fail(driverError("E_UNSUPPORTED", "frontmost needs the axbridge backend"));
3550
+ const result = await request("frontmost", { bundleId }, { timeoutMs: FRONTMOST_TIMEOUT_MS });
3551
+ return result.success ? { success: true, data: result.data.frontmost } : result;
3552
+ }
3553
+ async function health() {
3554
+ const result = await request("health");
3555
+ if (!result.success)
3556
+ return result;
3557
+ const d = result.data;
3558
+ return {
3559
+ success: true,
3560
+ data: {
3561
+ platform: "ios",
3562
+ id: udid,
3563
+ version: d.version,
3564
+ transport: d.hid.transport,
3565
+ bridge: d.ax.bridge === "degraded" ? "available" : d.ax.bridge,
3566
+ screen: d.screen,
3567
+ detail: {
3568
+ axDefault: d.ax.default,
3569
+ ...d.ax.bridge === "degraded" ? { axBridge: "degraded" } : {},
3570
+ ...d.ax.lastError ? { axLastError: d.ax.lastError } : {},
3571
+ ...d.ax.error !== undefined ? { axError: d.ax.error } : {},
3572
+ ...d.socket ? { socket: d.socket } : {},
3573
+ ...d.xcode ? { xcode: d.xcode } : {},
3574
+ ...d.state ? { state: d.state } : {}
3575
+ }
3576
+ }
3577
+ };
3578
+ }
3579
+ async function orientation(set) {
3580
+ const result = await request("orientation", params({ set }));
3581
+ if (!result.success)
3582
+ return result;
3583
+ const { orientation: read, source } = result.data;
3584
+ if (source !== undefined && source !== "device")
3585
+ opts.log?.(`[ios] orientation ${read} is ${source}, not read from the device`);
3586
+ if (read === "faceUp" || read === "faceDown" || read === "unknown") {
3587
+ opts.log?.(`[ios] device reports ${read}; exposing it as portrait`);
3588
+ return { success: true, data: "portrait" };
3589
+ }
3590
+ return { success: true, data: read };
3591
+ }
3592
+ async function pressButton(button) {
3593
+ if (button === "back")
3594
+ return fail(driverError("E_UNSUPPORTED", "iOS has no hardware back button"));
3595
+ return sendVoid("pressButton", { button: BUTTONS[button] });
3596
+ }
3597
+ const driver = {
3598
+ platform: "ios",
3599
+ id: udid,
3600
+ capabilities,
3601
+ health,
3602
+ snapshot,
3603
+ async elementAtPoint(point, o) {
3604
+ const result = await request("elementAtPoint", params({ x: point.x, y: point.y, reachability: o?.reachability }));
3605
+ if (!result.success)
3606
+ return result;
3607
+ return {
3608
+ success: true,
3609
+ data: result.data === null ? null : normalizeElement(result.data, client.ready.screen)
3610
+ };
3611
+ },
3612
+ tap: (point, o) => sendVoid("tap", params({ x: point.x, y: point.y, count: o?.count, durationMs: o?.durationMs })),
3613
+ longPress: (point, o) => sendVoid("longPress", params({ x: point.x, y: point.y, durationMs: o?.durationMs }), gestureTimeout(o?.durationMs, 1000)),
3614
+ swipe: (from, to, o) => sendVoid("swipe", params({ from, to, durationMs: o?.durationMs, momentum: o?.momentum }), gestureTimeout(o?.durationMs, 300)),
3615
+ drag: (from, to, o) => sendVoid("drag", params({ from, to, pressMs: o?.pressMs, durationMs: o?.durationMs, releaseMs: o?.releaseMs }), gestureTimeout((o?.pressMs ?? 0) + (o?.durationMs ?? 0) + (o?.releaseMs ?? 0), 300)),
3616
+ pinch: (center, scale, o) => sendVoid("pinch", params({ center, scale, durationMs: o?.durationMs, radius: o?.radius, angle: o?.angle }), gestureTimeout(o?.durationMs, 300)),
3617
+ rotate: (center, degrees, o) => sendGesture(rotatePlan(center, degrees, o, DEFAULT_RADIUS)),
3618
+ gesture: (paths, o) => sendGesture(planGesture(paths, o, 2)),
3619
+ typeText,
3620
+ key: (key, modifiers) => sendVoid("key", params({ usage: KEY_USAGE[key], modifiers: modifiers && [...modifiers] })),
3621
+ selectAll: () => sendVoid("selectAll"),
3622
+ pressButton,
3623
+ screenshot,
3624
+ orientation,
3625
+ settle,
3626
+ alert,
3627
+ alertOn,
3628
+ isFrontmost,
3629
+ appearance: (set) => simctl.appearance(set),
3630
+ async clipboard(set) {
3631
+ if (set === undefined)
3632
+ return simctl.pbpaste();
3633
+ const copied = await simctl.pbcopy(set);
3634
+ return copied.success ? { success: true, data: set } : copied;
3635
+ },
3636
+ app: {
3637
+ launch: async (bundleId, o) => {
3638
+ const launched = await simctl.launch(bundleId, o);
3639
+ if (launched.success) {
3640
+ launchedBundle = bundleId;
3641
+ if (await rebind(bundleId))
3642
+ launchedBundle = undefined;
3643
+ }
3644
+ return launched;
3645
+ },
3646
+ terminate: async (bundleId) => {
3647
+ const terminated = await simctl.terminate(bundleId);
3648
+ if (terminated.success && launchedBundle === bundleId)
3649
+ launchedBundle = undefined;
3650
+ return terminated;
3651
+ },
3652
+ openUrl: (url) => simctl.openUrl(url),
3653
+ install: (path) => simctl.install(path),
3654
+ uninstall: (bundleId) => simctl.uninstall(bundleId),
3655
+ isInstalled: (bundleId) => simctl.isInstalled(bundleId),
3656
+ isRunning: (bundleId) => simctl.isRunning(bundleId),
3657
+ clearData: (bundleId, o) => simctl.clearData(bundleId, o)
3658
+ },
3659
+ close: () => client.close()
3660
+ };
3661
+ return driver;
3662
+ }
3663
+ var timeoutOption = (o) => o.defaultTimeoutMs ? { defaultTimeoutMs: o.defaultTimeoutMs } : {};
3664
+ async function trySocket(o, log) {
3665
+ const socketPath = o.socketPath ?? o.env?.simdSocket;
3666
+ if (!socketPath)
3667
+ return;
3668
+ const first = await connectSimd({ socketPath, log, ...timeoutOption(o) });
3669
+ if (first.success)
3670
+ return first.data;
3671
+ if (!first.error.retryable) {
3672
+ log(`[ios] WARNING: ${first.error.message}; spawning a helper on stdio instead`);
3673
+ return;
3674
+ }
3675
+ log(`[ios] WARNING: ${first.error.message}; retrying once in ${SOCKET_RETRY_MS} ms`);
3676
+ await (o.sleep ?? ((ms) => Bun.sleep(ms)))(SOCKET_RETRY_MS);
3677
+ const second = await connectSimd({ socketPath, log, ...timeoutOption(o) });
3678
+ if (second.success)
3679
+ return second.data;
3680
+ log(`[ios] WARNING: ${second.error.message}; spawning a helper on stdio instead (a live daemon on ${socketPath} would now share the simulator with it)`);
3681
+ return;
3682
+ }
3683
+ async function connect(o, log) {
3684
+ const socket = await trySocket(o, log);
3685
+ if (socket)
3686
+ return { success: true, data: socket };
3687
+ const helper = resolveHelper(o);
3688
+ if (!helper.success)
3689
+ return helper;
3690
+ const resources = join6(dirname4(helper.data), "Resources");
3691
+ return spawnSimd({
3692
+ helperPath: helper.data,
3693
+ udid: o.udid,
3694
+ log,
3695
+ ...(o.exists ?? existsSync2)(resources) ? { resources } : {},
3696
+ ...o.spawn ? { spawn: o.spawn } : {},
3697
+ ...timeoutOption(o)
3698
+ });
3699
+ }
3700
+ async function openIosDriver(o) {
3701
+ const log = o.log ?? (() => {
3702
+ return;
3703
+ });
3704
+ const now = o.now ?? Date.now;
3705
+ const exec = o.debug ? timedExec(o.exec, log, now) : o.exec;
3706
+ const simctl = createSimctl({ udid: o.udid, exec, ...o.devicesRoot ? { devicesRoot: o.devicesRoot } : {} });
3707
+ if (!o.client && !(o.socketPath ?? o.env?.simdSocket)) {
3708
+ const helper = resolveHelper(o);
3709
+ if (!helper.success)
3710
+ return helper;
3711
+ }
3712
+ const connecting = o.client ? Promise.resolve({ success: true, data: o.client }) : connect(o, log);
3713
+ const [client, automation] = await Promise.all([connecting, simctl.enableAutomation()]);
3714
+ if (!automation.success)
3715
+ log(`[ios] could not enable AX automation: ${automation.error.message}`);
3716
+ if (!client.success)
3717
+ return client;
3718
+ const swap = swappableClient(client.data, {
3719
+ now,
3720
+ log,
3721
+ reopen: () => connect(o, log),
3722
+ ...o.debug ? { debug: true } : {}
3723
+ });
3724
+ return {
3725
+ success: true,
3726
+ data: createIosDriver(swap, simctl, { udid: o.udid, now, sleep: o.sleep ?? ((ms) => Bun.sleep(ms)), log })
3727
+ };
3728
+ }
3729
+ function timedExec(exec, log, now) {
3730
+ return async (cmd, opts) => {
3731
+ const started = now();
3732
+ const res = await exec(cmd, opts);
3733
+ const label = cmd[0] === "sh" ? "sh (pbcopy)" : cmd.slice(0, 3).join(" ");
3734
+ log(`[ios] exec ${label} ${Math.round(now() - started)} ms (exit ${res.exitCode})`);
3735
+ return res;
3736
+ };
3737
+ }
3738
+
3739
+ // ../../packages/e2e/src/driver/open.ts
3740
+ var DEBUG_ENV = "WARDEN_E2E_DEBUG";
3741
+ var stderrLog = (line) => {
3742
+ process.stderr.write(`${line}
3743
+ `);
3744
+ };
3745
+ function androidOptions(env, deps, debug) {
3746
+ const android = deps.android ?? {};
3747
+ const log = deps.log ?? (debug ? stderrLog : undefined);
3748
+ return {
3749
+ ...android,
3750
+ serial: env.udid,
3751
+ adb: android.adb ?? androidTools(process.env).adb,
3752
+ exec: deps.exec ?? bunExec,
3753
+ appPackage: android.appPackage ?? env.bundleId,
3754
+ ...android.uiad === undefined && android.spawn === undefined ? { uiad: uiadFromEnv(process.env) } : {},
3755
+ state: android.state ?? createAndroidStateStore({
3756
+ dir: androidStateDir(deps.wardenHome ?? wardenHome(process.env)),
3757
+ ...log ? { log } : {}
3758
+ }),
3759
+ ...log ? { log } : {}
3760
+ };
3761
+ }
3762
+ async function openDriver(env, deps = {}) {
3763
+ const debug = deps.debug ?? process.env[DEBUG_ENV] === "1";
3764
+ switch (env.platform) {
3765
+ case "ios":
3766
+ return openIosDriver({
3767
+ ...deps,
3768
+ udid: env.udid,
3769
+ env,
3770
+ exec: deps.exec ?? bunExec,
3771
+ debug,
3772
+ ...deps.log ? {} : debug ? { log: stderrLog } : {}
3773
+ });
3774
+ case "android":
3775
+ return openAndroidDriver(androidOptions(env, deps, debug));
3776
+ }
3777
+ }
3778
+ var defaultDriverFactory = (env) => openDriver(env);
3779
+
3780
+ // ../../packages/e2e/src/driver/unwrap.ts
3781
+ var TIMEOUTS = new Set(["E_TIMEOUT", "E_APP_NOT_RESPONDING"]);
3782
+ var INFRASTRUCTURE = new Set([
3783
+ "E_HELPER_MISSING",
3784
+ "E_HELPER_CRASHED",
3785
+ "E_NOT_BOOTED",
3786
+ "E_UNKNOWN_DEVICE",
3787
+ "E_AX_UNAVAILABLE",
3788
+ "E_APP_UNAVAILABLE",
3789
+ "E_INTERNAL"
3790
+ ]);
3791
+ function codeFor(code) {
3792
+ if (TIMEOUTS.has(code))
3793
+ return "TIMEOUT";
3794
+ if (INFRASTRUCTURE.has(code))
3795
+ return "DRIVER";
3796
+ return "ACTION_FAILED";
3797
+ }
3798
+ function toE2eError(error, action) {
3799
+ return new E2eError2(codeFor(error.code), `${action} failed: ${error.code}: ${error.message}`);
3800
+ }
3801
+ function aborted(action, signal) {
3802
+ const reason = signal.reason;
3803
+ const detail = reason instanceof Error ? `: ${reason.message}` : "";
3804
+ return new E2eError2("TIMEOUT", `${action} aborted (test deadline reached or cancelled)${detail}`);
3805
+ }
3806
+ async function settled(call, action, signal) {
3807
+ if (signal === undefined)
3808
+ return call();
3809
+ if (signal.aborted)
3810
+ throw aborted(action, signal);
3811
+ let onAbort;
3812
+ const abort = new Promise((_, reject) => {
3813
+ onAbort = () => reject(aborted(action, signal));
3814
+ signal.addEventListener("abort", onAbort, { once: true });
3815
+ });
3816
+ try {
3817
+ return await Promise.race([call(), abort]);
3818
+ } finally {
3819
+ if (onAbort !== undefined)
3820
+ signal.removeEventListener("abort", onAbort);
3821
+ }
3822
+ }
3823
+ async function unwrap(call, action, signal) {
3824
+ const result = await settled(call, action, signal);
3825
+ if (!result.success)
3826
+ throw toE2eError(result.error, action);
3827
+ return result.data;
3828
+ }
3829
+
3830
+ // ../../packages/e2e/src/fixture-session.ts
3831
+ var systemClock = {
3832
+ now: () => Date.now(),
3833
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
3834
+ };
3835
+
3836
+ // ../../packages/e2e/src/app.ts
3837
+ var CONTENT_POLL_MS = [100, 200, 400];
3838
+ var DECOR_IDS = new Set([
3839
+ "content",
3840
+ "action_bar_root",
3841
+ "action_bar_container",
3842
+ "action_bar",
3843
+ "decor_content_parent",
3844
+ "statusBarBackground",
3845
+ "navigationBarBackground",
3846
+ "splashscreen_view",
3847
+ "splashscreen_icon_view",
3848
+ "splashscreen_branding_view"
3849
+ ]);
3850
+ function isContent(element, parent, platform) {
3851
+ if (element.hidden === true)
3852
+ return false;
3853
+ const id = element.identifier;
3854
+ if (id !== undefined && id !== "" && !(platform === "android" && DECOR_IDS.has(id)))
3855
+ return true;
3856
+ return isInteractiveRole(resolveRole(element, parent).role);
3857
+ }
3858
+ function hasContent(snapshot) {
3859
+ const visit = (element, parent) => isContent(element, parent, snapshot.platform) || element.children.some((child) => visit(child, element));
3860
+ return snapshot.elements.some((element) => visit(element, undefined));
3861
+ }
3862
+ function parseWait(wait) {
3863
+ if (wait === undefined || wait === "none")
3864
+ return { kind: "none" };
3865
+ if (wait === "quiet")
3866
+ return { kind: "quiet" };
3867
+ if (typeof wait === "object" && wait !== null && isLocator(wait.until)) {
3868
+ const { timeout } = wait;
3869
+ if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) {
3870
+ throw new E2eError2("INVALID_ARGUMENT", `app.open wait timeout must be a non-negative number, got ${timeout}`);
3871
+ }
3872
+ return timeout === undefined ? { kind: "until", until: wait.until } : { kind: "until", until: wait.until, timeout };
3873
+ }
3874
+ throw new E2eError2("INVALID_ARGUMENT", `app.open wait must be "quiet", "none" or { until: <locator made by screen> }, got ${describeWait(wait)}`);
3875
+ }
3876
+ function describeWait(wait) {
3877
+ if (typeof wait === "string")
3878
+ return JSON.stringify(wait);
3879
+ if (wait === null || typeof wait !== "object")
3880
+ return String(wait);
3881
+ return `{ ${Object.keys(wait).join(", ")} }`;
3882
+ }
3883
+ var stderrLog2 = (line) => {
3884
+ process.stderr.write(`${line}
3885
+ `);
3886
+ };
3887
+ function createApp({ session, driver, env, log = stderrLog2, clock = systemClock }) {
3888
+ const bundleId = env.bundleId;
3889
+ let droppedLogged = false;
3890
+ const envLaunchArgs = () => {
3891
+ if (driver.platform !== "android" || env.launchArgs.length === 0)
3892
+ return env.launchArgs;
3893
+ if (!droppedLogged) {
3894
+ droppedLogged = true;
3895
+ log(`[warden-e2e] Android launch takes no args: ignoring WARDEN_LAUNCH_ARGS ${JSON.stringify(env.launchArgs)}`);
3896
+ }
3897
+ return [];
3898
+ };
3899
+ const look = async () => {
3900
+ const read = await settled(() => canObserve(driver) ? driver.observe({ reuse: false }) : driver.snapshot(), `read ${bundleId}`, session.signal);
3901
+ return read.success ? read.data : undefined;
3902
+ };
3903
+ const waitForContent = async () => {
3904
+ const deadline = clock.now() + DEFAULT_SETTLE_TIMEOUT_MS;
3905
+ for (let poll = 0;; poll++) {
3906
+ const snapshot = await look();
3907
+ if (snapshot !== undefined && hasContent(snapshot))
3908
+ return;
3909
+ const left = deadline - clock.now();
3910
+ if (left <= 0)
3911
+ return;
3912
+ const gap = CONTENT_POLL_MS[Math.min(poll, CONTENT_POLL_MS.length - 1)] ?? 100;
3913
+ await clock.sleep(Math.min(gap, left));
3914
+ }
3915
+ };
3916
+ const waitUntilQuiet = async () => {
3917
+ const quiet = await settled(() => driver.settle({ quietMs: DEFAULT_SETTLE_QUIET_MS, timeoutMs: DEFAULT_SETTLE_TIMEOUT_MS }), `wait for ${bundleId}`, session.signal);
3918
+ if (!quiet.success && quiet.error.code !== "E_UNSUPPORTED")
3919
+ throw toE2eError(quiet.error, `wait for ${bundleId}`);
3920
+ };
3921
+ const launch = async (options = {}) => {
3922
+ const wait = parseWait(options.wait);
3923
+ const opts = {
3924
+ args: [...envLaunchArgs(), ...options.args ?? []],
3925
+ relaunch: true
3926
+ };
3927
+ if (options.env !== undefined)
3928
+ opts.env = options.env;
3929
+ await unwrap(() => driver.app.launch(bundleId, opts), `launch ${bundleId}`, session.signal);
3930
+ if (options.url !== undefined) {
3931
+ const url = options.url;
3932
+ await unwrap(() => driver.app.openUrl(url), `open ${url}`, session.signal);
3933
+ }
3934
+ if (wait.kind === "quiet") {
3935
+ await waitForContent();
3936
+ await waitUntilQuiet();
3937
+ }
3938
+ if (wait.kind === "until") {
3939
+ await wait.until.waitFor({ state: "visible", timeout: wait.timeout ?? env.assertionTimeoutMs });
3940
+ await waitUntilQuiet();
3941
+ }
3942
+ };
3943
+ const terminate = () => unwrap(() => driver.app.terminate(bundleId), `terminate ${bundleId}`, session.signal);
3944
+ return {
3945
+ open: launch,
3946
+ close: terminate,
3947
+ async restart() {
3948
+ await launch();
3949
+ },
3950
+ async reinstall() {
3951
+ const path = env.appPath;
3952
+ if (path === undefined) {
3953
+ throw new E2eError2("INVALID_ARGUMENT", "app.reinstall() needs WARDEN_APP_PATH (the build to install) to be set");
3954
+ }
3955
+ const installed = await unwrap(() => driver.app.isInstalled(bundleId), `check ${bundleId}`, session.signal);
3956
+ if (installed)
3957
+ await unwrap(() => driver.app.uninstall(bundleId), `uninstall ${bundleId}`, session.signal);
3958
+ await unwrap(() => driver.app.install(path), `install ${path}`, session.signal);
3959
+ },
3960
+ async clearData() {
3961
+ await unwrap(() => driver.app.clearData(bundleId, { keychain: true }), `clear data ${bundleId}`, session.signal);
3962
+ },
3963
+ isRunning() {
3964
+ return unwrap(() => driver.app.isRunning(bundleId), `check ${bundleId} running`, session.signal);
3965
+ }
3966
+ };
3967
+ }
3968
+
3969
+ // ../../packages/e2e/src/device.ts
3970
+ import { mkdirSync, readdirSync, writeFileSync } from "fs";
3971
+ import { dirname as dirname5, join as join7 } from "path";
3972
+
3973
+ // ../../packages/e2e/src/driver/alerts.ts
3974
+ function canReadAlertOn(driver) {
3975
+ return typeof driver.alertOn === "function";
3976
+ }
3977
+ function canProbeFrontmost(driver) {
3978
+ return typeof driver.isFrontmost === "function";
3979
+ }
3980
+
3981
+ // ../../packages/e2e/src/alert-reader.ts
3982
+ var ALERT_READ_ATTEMPTS = 13;
3983
+ var ALERT_READ_RETRY_MS = 100;
3984
+ var ALERT_MODAL_LOOKS = 3;
3985
+ var ALERT_MODAL_RETRY_MS = 200;
3986
+ function createAlertReader({ driver, clock, signal }) {
3987
+ const read = (backend) => settled(() => backend !== undefined && canReadAlertOn(driver) ? driver.alertOn(backend) : driver.alert(), "read alert", signal());
3988
+ const readOne = async (backend, modalLooks) => {
3989
+ for (let looked = 0;; looked += 1) {
3990
+ const result = await read(backend);
3991
+ if (!result.success)
3992
+ return result;
3993
+ const info = result.data;
3994
+ if (info === null || info.buttons.length > 0 || looked >= modalLooks)
3995
+ return result;
3996
+ await clock.sleep(ALERT_MODAL_RETRY_MS);
3997
+ }
3998
+ };
3999
+ const readBoth = async () => {
4000
+ for (let failures = 1;; failures += 1) {
4001
+ const result = await read();
4002
+ if (result.success)
4003
+ return result.data;
4004
+ if (!result.error.retryable || failures >= ALERT_READ_ATTEMPTS)
4005
+ throw toE2eError(result.error, "read alert");
4006
+ await clock.sleep(ALERT_READ_RETRY_MS);
4007
+ }
4008
+ };
4009
+ return async (backend, modalLooks = ALERT_MODAL_LOOKS) => {
4010
+ if (backend !== undefined && canReadAlertOn(driver)) {
4011
+ const one = await readOne(backend, modalLooks);
4012
+ if (one.success)
4013
+ return one.data;
4014
+ if (backend === "axbridge") {
4015
+ const host = await readOne("ax", 0);
4016
+ if (host.success)
4017
+ return host.data;
4018
+ }
4019
+ }
4020
+ return readBoth();
4021
+ };
4022
+ }
4023
+
4024
+ // ../../packages/e2e/src/device-devtools.ts
4025
+ function createDeviceDevtools(source) {
4026
+ const connect = async () => {
4027
+ const attached = await source.devtools();
4028
+ if (attached.success)
4029
+ return attached.data;
4030
+ throw new E2eError2("SESSION", `devtools unavailable: ${attached.error.message}`);
4031
+ };
4032
+ const attach = () => source.devtools();
4033
+ return {
4034
+ async evaluate(expression, options) {
4035
+ const attached = await attach();
4036
+ if (!attached.success)
4037
+ return { success: false, error: attached.error };
4038
+ return attached.data.evaluate(expression, options);
4039
+ },
4040
+ async reloadJs() {
4041
+ const attached = await attach();
4042
+ return attached.success ? attached.data.reloadJs() : attached;
4043
+ },
4044
+ async tree(options) {
4045
+ const attached = await attach();
4046
+ return attached.success ? attached.data.tree(options) : attached;
4047
+ },
4048
+ network: {
4049
+ async entries(query) {
4050
+ return (await connect()).network.entries(query);
4051
+ },
4052
+ async clear() {
4053
+ await (await connect()).network.clear();
4054
+ }
4055
+ },
4056
+ console: {
4057
+ async lines() {
4058
+ return (await connect()).console.lines();
4059
+ },
4060
+ async clear() {
4061
+ (await connect()).console.clear();
4062
+ },
4063
+ async mark(label) {
4064
+ (await connect()).console.mark(label);
4065
+ }
4066
+ },
4067
+ connect
4068
+ };
4069
+ }
4070
+
4071
+ // ../../packages/e2e/src/device-link.ts
4072
+ var OPEN_LINK_WAIT_MS = 8000;
4073
+ var FAST_POLL_MS = 100;
4074
+ var FAST_WINDOW_MS = 1000;
4075
+ var SLOW_POLL_MS = 250;
4076
+ var QUIET_MS = 1200;
4077
+ var MAX_PRESSES = 3;
4078
+ var SHEET_SETTLE_MS = 500;
4079
+ var PRESS_GAP_MS = 600;
4080
+ var sheetlessLinks = new WeakMap;
4081
+ function sheetlessFor(session) {
4082
+ const known = sheetlessLinks.get(session) ?? new Set;
4083
+ sheetlessLinks.set(session, known);
4084
+ return known;
4085
+ }
4086
+ var linksInFlight = new WeakMap;
4087
+ function linkInFlight(session) {
4088
+ return (linksInFlight.get(session) ?? 0) > 0;
4089
+ }
4090
+ async function whileLinkInFlight(session, fn) {
4091
+ linksInFlight.set(session, (linksInFlight.get(session) ?? 0) + 1);
4092
+ try {
4093
+ return await fn();
4094
+ } finally {
4095
+ linksInFlight.set(session, (linksInFlight.get(session) ?? 1) - 1);
4096
+ }
4097
+ }
4098
+ function isOpenInSheet(info) {
4099
+ return info.title !== undefined && /^open in\b/i.test(info.title.trim());
4100
+ }
4101
+ function linkKey(url) {
4102
+ const trimmed = url.trim();
4103
+ const web = /^(https?):\/\/([^/?#]+)/i.exec(trimmed);
4104
+ if (web !== null)
4105
+ return `${web[1]?.toLowerCase()}://${web[2]?.toLowerCase()}`;
4106
+ return /^([a-z][a-z0-9+.-]*):/i.exec(trimmed)?.[1]?.toLowerCase() ?? "";
4107
+ }
4108
+ var NOT_LOOKING = { sheetUp: false, confirmed: false, finished: false };
4109
+ async function lookForSheet(deps, plan, watch, started, remembered) {
4110
+ const { clock } = deps;
4111
+ if (!watch.looking)
4112
+ return NOT_LOOKING;
4113
+ if (remembered && clock.now() - started < QUIET_MS)
4114
+ return { sheetUp: false, confirmed: true, finished: false };
4115
+ const cheap = clock.now() - watch.lastSheetAt < QUIET_MS;
4116
+ const info = await deps.readAlert(cheap ? "ax" : undefined, 0);
4117
+ const open = info === null ? undefined : findButton(info.buttons, "Open");
4118
+ if (open === undefined) {
4119
+ if (watch.presses > 0)
4120
+ watch.looking = false;
4121
+ watch.sheetSeenAt = undefined;
4122
+ return { sheetUp: false, confirmed: !cheap, finished: false };
4123
+ }
4124
+ const now = clock.now();
4125
+ watch.lastSheetAt = now;
4126
+ watch.sheetSeenAt ??= now;
4127
+ const since = (at) => at === undefined ? Number.POSITIVE_INFINITY : now - at;
4128
+ const settled = since(watch.sheetSeenAt) >= SHEET_SETTLE_MS && since(watch.lastPressAt) >= PRESS_GAP_MS;
4129
+ if (!settled)
4130
+ return { sheetUp: true, confirmed: false, finished: false };
4131
+ if (watch.presses >= MAX_PRESSES)
4132
+ return { sheetUp: true, confirmed: false, finished: plan.until === undefined };
4133
+ await deps.press(open);
4134
+ watch.presses += 1;
4135
+ watch.lastPressAt = clock.now();
4136
+ return { sheetUp: true, confirmed: false, finished: false };
4137
+ }
4138
+ async function handedOver(deps, watch, look, remembered) {
4139
+ if (!watch.looking || !look.confirmed)
4140
+ return false;
4141
+ if (deps.clock.now() - watch.lastSheetAt < (remembered ? 0 : QUIET_MS))
4142
+ return false;
4143
+ return await deps.appInFront() === true;
4144
+ }
4145
+ async function reachedTarget(deps, plan, watch, look) {
4146
+ if (plan.until === undefined)
4147
+ return !watch.looking;
4148
+ return !look.sheetUp && await deps.isVisible(plan.until);
4149
+ }
4150
+ function nextPollMs(sinceSheet) {
4151
+ const poll = sinceSheet < FAST_WINDOW_MS ? FAST_POLL_MS : SLOW_POLL_MS;
4152
+ const untilQuiet = QUIET_MS - sinceSheet;
4153
+ return untilQuiet > 0 ? Math.min(poll, untilQuiet) : poll;
4154
+ }
4155
+ async function waitForLink(deps, plan) {
4156
+ const { clock, sheetless } = deps;
4157
+ const started = clock.now();
4158
+ const deadline = started + plan.waitMs;
4159
+ const remembered = plan.answersSheet && sheetless.has(plan.key);
4160
+ const watch = {
4161
+ looking: plan.answersSheet,
4162
+ presses: 0,
4163
+ sheetSeenAt: undefined,
4164
+ lastPressAt: undefined,
4165
+ lastSheetAt: started
4166
+ };
4167
+ for (;; ) {
4168
+ const look = await lookForSheet(deps, plan, watch, started, remembered);
4169
+ if (look.finished || await reachedTarget(deps, plan, watch, look) || clock.now() >= deadline)
4170
+ return;
4171
+ if (await handedOver(deps, watch, look, remembered)) {
4172
+ sheetless.add(plan.key);
4173
+ if (plan.until === undefined)
4174
+ return;
4175
+ watch.looking = false;
4176
+ }
4177
+ await clock.sleep(nextPollMs(clock.now() - watch.lastSheetAt));
4178
+ }
4179
+ }
4180
+
4181
+ // ../../packages/e2e/src/device.ts
4182
+ function nextShotNumber(dir, issued) {
4183
+ let highest = issued.get(dir) ?? 0;
4184
+ try {
4185
+ for (const file of readdirSync(join7(dir, "screenshots"))) {
4186
+ const n = /^(\d{3})-/.exec(file)?.[1];
4187
+ if (n !== undefined)
4188
+ highest = Math.max(highest, Number(n));
4189
+ }
4190
+ } catch {}
4191
+ return highest + 1;
4192
+ }
4193
+ function androidButton(buttons, ids) {
4194
+ for (const id of ids) {
4195
+ const found = buttons.find((button) => button.identifier === id);
4196
+ if (found !== undefined)
4197
+ return found;
4198
+ }
4199
+ return;
4200
+ }
4201
+ function alertTarget(info, action, platform) {
4202
+ const { buttons } = info;
4203
+ if (action === "accept") {
4204
+ if (platform !== "android")
4205
+ return firstOf(buttons, ACCEPT_BUTTONS) ?? buttons[buttons.length - 1];
4206
+ return androidButton(buttons, ANDROID_PERMISSION_ALLOW_IDS) ?? firstOf(buttons, ACCEPT_BUTTONS) ?? androidButton(buttons, [ANDROID_POSITIVE_ID]) ?? buttons[buttons.length - 1];
4207
+ }
4208
+ if (action === "dismiss" && isOpenInSheet(info))
4209
+ return findButton(buttons, "Open");
4210
+ if (action === "dismiss") {
4211
+ if (platform !== "android")
4212
+ return firstOf(buttons, DISMISS_BUTTONS) ?? buttons[0];
4213
+ return androidButton(buttons, ANDROID_PERMISSION_DENY_IDS) ?? firstOf(buttons, DISMISS_BUTTONS) ?? androidButton(buttons, [ANDROID_NEGATIVE_ID]) ?? buttons[0];
4214
+ }
4215
+ const target = findButton(buttons, action.button);
4216
+ if (target === undefined) {
4217
+ const names = buttons.map((b) => nameOf(b) ?? "?").join(", ");
4218
+ throw new E2eError2("ACTION_FAILED", `alert has no "${action.button}" button (buttons: ${names})`);
4219
+ }
4220
+ return target;
4221
+ }
4222
+ function createDevice({ session, driver, env, clock = systemClock, perfExec }) {
4223
+ const issued = new Map;
4224
+ const signal = () => session.signal;
4225
+ const pressAlertButton = async (button, action) => {
4226
+ await unwrap(() => driver.tap(centre(button)), action, signal());
4227
+ };
4228
+ const readAlert = createAlertReader({ driver, clock, signal });
4229
+ const appInFront = async () => {
4230
+ if (env.bundleId === "" || !canProbeFrontmost(driver))
4231
+ return;
4232
+ const result = await settled(() => driver.isFrontmost(env.bundleId), "probe frontmost app", signal());
4233
+ return result.success ? result.data : undefined;
4234
+ };
4235
+ const isVisible = async (locator) => {
4236
+ try {
4237
+ return await locator.isVisible();
4238
+ } catch (error) {
4239
+ if (signal()?.aborted)
4240
+ throw error;
4241
+ return false;
4242
+ }
4243
+ };
4244
+ const readSnapshot = (opts) => unwrap(() => driver.snapshot(opts), "snapshot", signal());
4245
+ const { devtools: attach, peekDevtools } = session;
4246
+ let devtools;
4247
+ const lazyDevtools = () => {
4248
+ if (env.metroUrl === undefined || attach === undefined || peekDevtools === undefined)
4249
+ return;
4250
+ if (devtools === undefined) {
4251
+ devtools = createDeviceDevtools({
4252
+ devtools: () => attach.call(session),
4253
+ peekDevtools: () => peekDevtools.call(session)
4254
+ });
4255
+ attach.call(session);
4256
+ }
4257
+ return devtools;
4258
+ };
4259
+ const perf = createDevicePerf({
4260
+ exec: perfExec ?? spawnWarden(env.wardenBin),
4261
+ udid: env.udid,
4262
+ budgets: parseBudgets(env.perfBudgets),
4263
+ attemptDir: () => session.attemptDir,
4264
+ addFile: (relative) => session.attempt?.files.add(relative),
4265
+ ...env.wardenBin === undefined && perfExec === undefined ? { unavailable: "WARDEN_BIN is not set (run the spec with `warden e2e`)" } : {}
4266
+ });
4267
+ const device = {
4268
+ get devtools() {
4269
+ return lazyDevtools();
4270
+ },
4271
+ perf,
4272
+ async alert(action) {
4273
+ const info = await readAlert("axbridge");
4274
+ if (info === null || info.buttons.length === 0)
4275
+ return false;
4276
+ if (isOpenInSheet(info) && typeof action === "string" && linkInFlight(session))
4277
+ return false;
4278
+ const target = alertTarget(info, action, driver.platform);
4279
+ if (target === undefined)
4280
+ return false;
4281
+ await pressAlertButton(target, "press alert button");
4282
+ return true;
4283
+ },
4284
+ async openLink(url, options = {}) {
4285
+ const waitMs = options.waitMs ?? OPEN_LINK_WAIT_MS;
4286
+ if (!Number.isFinite(waitMs) || waitMs < 0) {
4287
+ throw new E2eError2("INVALID_ARGUMENT", `openLink waitMs must be a non-negative number, got ${String(waitMs)}`);
4288
+ }
4289
+ await unwrap(() => driver.app.openUrl(url), `open ${url}`, signal());
4290
+ const answersSheet = options.accept !== false && driver.platform === "ios" && driver.capabilities.alerts;
4291
+ if (!answersSheet && options.until === undefined)
4292
+ return;
4293
+ await whileLinkInFlight(session, () => waitForLink({
4294
+ clock,
4295
+ readAlert,
4296
+ press: (button) => pressAlertButton(button, "accept open-in sheet"),
4297
+ appInFront,
4298
+ isVisible,
4299
+ sheetless: sheetlessFor(session)
4300
+ }, { key: linkKey(url), answersSheet, waitMs, ...options.until === undefined ? {} : { until: options.until } }));
4301
+ },
4302
+ async home() {
4303
+ await unwrap(() => driver.pressButton("home"), "press home", signal());
4304
+ },
4305
+ async back() {
4306
+ if (driver.platform === "ios") {
4307
+ throw new E2eError2("INVALID_ARGUMENT", "device.back() is Android only: iOS has no back key");
4308
+ }
4309
+ await unwrap(() => driver.pressButton("back"), "press back", signal());
4310
+ },
4311
+ orientation(set) {
4312
+ return unwrap(() => driver.orientation(set), "orientation", signal());
4313
+ },
4314
+ appearance(set) {
4315
+ return unwrap(() => driver.appearance(set), "appearance", signal());
4316
+ },
4317
+ clipboard() {
4318
+ return unwrap(() => driver.clipboard(), "read clipboard", signal());
4319
+ },
4320
+ async setClipboard(text) {
4321
+ await unwrap(() => driver.clipboard(text), "set clipboard", signal());
4322
+ },
4323
+ async hideKeyboard() {
4324
+ const snapshot = await readSnapshot();
4325
+ if (!snapshot.flat.some((element) => element.role === "keyboard"))
4326
+ return;
4327
+ if (driver.platform === "ios")
4328
+ await unwrap(() => driver.key("Enter"), "hide keyboard", signal());
4329
+ else
4330
+ await unwrap(() => driver.pressButton("back"), "hide keyboard", signal());
4331
+ },
4332
+ async screenshot(name) {
4333
+ const dir = session.attemptDir ?? join7(session.out, "adhoc");
4334
+ const n = nextShotNumber(dir, issued);
4335
+ issued.set(dir, n);
4336
+ const path = screenshotPath(dir, n, name);
4337
+ mkdirSync(dirname5(path), { recursive: true });
4338
+ const shot = await unwrap(() => driver.screenshot({ path }), "screenshot", signal());
4339
+ if (shot.path === undefined && shot.png !== undefined)
4340
+ writeFileSync(path, shot.png);
4341
+ session.trace?.write({ type: "screenshot", ts: clock.now(), kind: "device", label: name, path });
4342
+ return path;
4343
+ },
4344
+ async snapshot(options = {}) {
4345
+ const interactiveOnly = options.interactiveOnly ?? false;
4346
+ const snapshot = await readSnapshot({ interactiveOnly });
4347
+ const rendered = renderSnapshot(snapshot, { interactiveOnly, refs: new Map });
4348
+ return [renderHeader({ snapshot, rendered, interactiveOnly }), ...rendered.lines].join(`
4349
+ `);
4350
+ },
4351
+ async settle(options = {}) {
4352
+ const quietMs = options.quietMs ?? DEFAULT_SETTLE_QUIET_MS;
4353
+ const timeoutMs = options.timeout ?? DEFAULT_SETTLE_TIMEOUT_MS;
4354
+ const native = await settled(() => driver.settle({ quietMs, timeoutMs }), "settle", signal());
4355
+ if (native.success)
4356
+ return settleDetailOf(native.data);
4357
+ if (native.error.code !== "E_UNSUPPORTED")
4358
+ throw toE2eError(native.error, "settle");
4359
+ const polled = await settleByHash(async () => snapshotHash(await readSnapshot()), {
4360
+ quietMs,
4361
+ timeoutMs,
4362
+ now: clock.now,
4363
+ sleep: clock.sleep
4364
+ });
4365
+ return { ...polled, via: "hash" };
4366
+ }
4367
+ };
4368
+ return device;
4369
+ }
4370
+
4371
+ // ../../packages/e2e/src/screen/poll.ts
4372
+ var systemClock2 = {
4373
+ now: () => Date.now(),
4374
+ sleep(ms, signal) {
4375
+ return new Promise((resolve) => {
4376
+ if (signal?.aborted)
4377
+ return resolve();
4378
+ const onAbort = () => {
4379
+ clearTimeout(timer);
4380
+ resolve();
4381
+ };
4382
+ const timer = setTimeout(() => {
4383
+ signal?.removeEventListener("abort", onAbort);
4384
+ resolve();
4385
+ }, ms);
4386
+ signal?.addEventListener("abort", onAbort, { once: true });
4387
+ });
4388
+ }
4389
+ };
4390
+ var DEFAULT_POLL_INTERVAL_MS = 100;
4391
+ var DEFAULT_POLL_SCHEDULE = {
4392
+ fastMs: DEFAULT_POLL_INTERVAL_MS,
4393
+ fastForMs: 1000,
4394
+ slowMs: 250
4395
+ };
4396
+ async function poll(step, options) {
4397
+ const now = options.now ?? systemClock2.now;
4398
+ const sleep = options.sleep ?? systemClock2.sleep;
4399
+ const schedule = options.schedule ?? DEFAULT_POLL_SCHEDULE;
4400
+ const startedAt = now();
4401
+ const deadline = startedAt + options.timeoutMs;
4402
+ const gap = (at) => options.intervalMs ?? (at - startedAt < schedule.fastForMs ? schedule.fastMs : schedule.slowMs);
4403
+ let attempts = 0;
4404
+ let last;
4405
+ for (;; ) {
4406
+ if (options.signal?.aborted)
4407
+ return { status: "aborted", attempts };
4408
+ attempts += 1;
4409
+ const outcome = await step(attempts);
4410
+ if (outcome.done)
4411
+ return { status: "done", value: outcome.value, attempts };
4412
+ last = outcome.state;
4413
+ const at = now();
4414
+ const remaining = deadline - at;
4415
+ if (remaining <= 0)
4416
+ return { status: "timeout", last, attempts };
4417
+ const wanted = Math.min(gap(at), outcome.maxWaitMs === undefined ? Number.POSITIVE_INFINITY : outcome.maxWaitMs);
4418
+ await sleep(Math.max(0, Math.min(wanted, remaining)), options.signal);
4419
+ }
4420
+ }
4421
+
4422
+ // ../../packages/e2e/src/screen/context.ts
4423
+ var EXCERPT_LINES = 40;
4424
+ var MAX_ALERT_ANSWERS = 3;
4425
+ var REACT_THROTTLE_MS = 450;
4426
+ var REACT_TIMEOUT_MS = 2000;
4427
+
4428
+ class Runtime {
4429
+ host;
4430
+ reactCache;
4431
+ indexed = new WeakMap;
4432
+ constructor(host) {
4433
+ this.host = host;
4434
+ }
4435
+ get clock() {
4436
+ return this.host.clock ?? systemClock2;
4437
+ }
4438
+ actionTimeout() {
4439
+ return this.host.actionTimeout?.() ?? this.host.env.actionTimeoutMs;
4440
+ }
4441
+ assertionTimeout() {
4442
+ return this.host.assertionTimeout?.() ?? this.host.env.assertionTimeoutMs ?? DEFAULT_ASSERTION_TIMEOUT_MS;
4443
+ }
4444
+ budget(timeoutMs) {
4445
+ const total = timeoutMs ?? this.actionTimeout();
4446
+ const deadline = this.clock.now() + total;
4447
+ return { remaining: () => Math.max(0, deadline - this.clock.now()), total };
4448
+ }
4449
+ step(label, fn) {
4450
+ return this.host.step === undefined ? fn() : this.host.step(label, fn);
4451
+ }
4452
+ span(op, fn, options) {
4453
+ return this.host.span === undefined ? fn() : this.host.span(op, fn, options);
4454
+ }
4455
+ indexOf(snapshot) {
4456
+ const known = this.indexed.get(snapshot);
4457
+ if (known !== undefined)
4458
+ return known;
4459
+ const index = indexSnapshot(snapshot);
4460
+ this.indexed.set(snapshot, index);
4461
+ return index;
4462
+ }
4463
+ async tryObserve(options = {}) {
4464
+ const driver = await this.host.driver();
4465
+ let reuse = options.reuse === true;
4466
+ for (let answered = 0;; answered += 1) {
4467
+ const before = options.withPrevious === true && canObserve(driver) ? driver.previous?.() : undefined;
4468
+ const result = canObserve(driver) ? await driver.observe({ reuse }) : await driver.snapshot();
4469
+ if (!result.success)
4470
+ return { ok: false, error: result.error };
4471
+ const observation = {
4472
+ ok: true,
4473
+ observed: { driver, snapshot: result.data, index: this.indexOf(result.data) }
4474
+ };
4475
+ if (before !== undefined)
4476
+ observation.earlier = this.earlierThan(driver, before, result.data);
4477
+ if (options.alerts === false || answered >= MAX_ALERT_ANSWERS)
4478
+ return observation;
4479
+ if (!await this.answeredAlert(driver, result.data))
4480
+ return observation;
4481
+ reuse = false;
4482
+ }
4483
+ }
4484
+ earlierThan(driver, before, look) {
4485
+ const after = canObserve(driver) ? driver.previous?.() : undefined;
4486
+ const same = after !== undefined && after.generation === before.generation && after.snapshot === look;
4487
+ const earlier = same && before.snapshot !== look ? before.snapshot : undefined;
4488
+ return {
4489
+ previous: earlier === undefined ? undefined : { driver, snapshot: earlier, index: this.indexOf(earlier) },
4490
+ sinceInputMs: before.sinceInputMs
4491
+ };
4492
+ }
4493
+ async answeredAlert(driver, snapshot) {
4494
+ const answer = this.host.answerSystemAlert;
4495
+ if (answer === undefined)
4496
+ return false;
4497
+ const shown = snapshot.modal !== undefined || canDetectAlerts(driver) && driver.alertIn(snapshot) !== null;
4498
+ return shown && await answer();
4499
+ }
4500
+ async forgetObservation() {
4501
+ const driver = await this.host.driver();
4502
+ if (canObserve(driver))
4503
+ driver.invalidateObservations();
4504
+ }
4505
+ async observe(options = {}) {
4506
+ const reuse = options.reuse ?? true;
4507
+ const result = await this.poll(async (attempt) => {
4508
+ const observation = await this.tryObserve({ reuse: reuse && attempt === 1 });
4509
+ if (observation.ok)
4510
+ return { done: true, value: observation.observed };
4511
+ if (!observation.error.retryable)
4512
+ throw driverFailure("snapshot", observation.error);
4513
+ return { done: false, state: observation.error };
4514
+ }, this.actionTimeout());
4515
+ if (result.status === "done")
4516
+ return result.value;
4517
+ throw driverFailure("snapshot", result.last ?? { code: "E_TIMEOUT", message: "no snapshot", retryable: true });
4518
+ }
4519
+ async reactMatches(testId, observed) {
4520
+ const facet = this.host.devtools?.();
4521
+ if (facet === undefined)
4522
+ return [];
4523
+ const key = typeof testId === "string" ? `s:${testId}` : `r:${testId.toString()}`;
4524
+ const cached = this.reactCache;
4525
+ if (cached !== undefined && cached.key === key && this.clock.now() - cached.at < REACT_THROTTLE_MS) {
4526
+ return cached.matches;
4527
+ }
4528
+ const view = { platform: observed.snapshot.platform, screen: observed.snapshot.screen };
4529
+ const matches = await locateByTestIdViaReact(facet, testId, view, { timeoutMs: REACT_TIMEOUT_MS });
4530
+ this.reactCache = { key, at: this.clock.now(), matches };
4531
+ return matches;
4532
+ }
4533
+ excerpt(snapshot) {
4534
+ const rendered = renderSnapshot(snapshot, { interactiveOnly: true, refs: new Map, maxLines: EXCERPT_LINES });
4535
+ return [renderHeader({ snapshot, rendered, interactiveOnly: true }), ...rendered.lines].join(`
4536
+ `);
4537
+ }
4538
+ async poll(step, timeoutMs, options = {}) {
4539
+ const signal = this.host.signal;
4540
+ const result = await poll(step, {
4541
+ timeoutMs,
4542
+ ...options.intervalMs === undefined ? {} : { intervalMs: options.intervalMs },
4543
+ ...signal === undefined ? {} : { signal },
4544
+ now: this.clock.now,
4545
+ sleep: this.clock.sleep
4546
+ });
4547
+ if (result.status === "aborted")
4548
+ throw abortError(signal);
4549
+ return result;
4550
+ }
4551
+ drive(verb, result) {
4552
+ if (!result.success)
4553
+ throw driverFailure(verb, result.error, "ACTION_FAILED");
4554
+ return result.data;
4555
+ }
4556
+ }
4557
+ function driverFailure(verb, error, code = "DRIVER") {
4558
+ return new E2eError2(code, `${verb} failed: ${error.code}: ${error.message}`);
4559
+ }
4560
+ function abortError(signal) {
4561
+ const reason = signal?.reason;
4562
+ if (reason instanceof E2eError2)
4563
+ return reason;
4564
+ return new E2eError2("TIMEOUT", "the test was aborted before the action finished");
4565
+ }
4566
+
4567
+ // ../../packages/e2e/src/screen/screen.ts
4568
+ function isFinitePoint(point) {
4569
+ return Number.isFinite(point.x) && Number.isFinite(point.y);
4570
+ }
4571
+ function createScreen(host) {
4572
+ const rt = new Runtime(host);
4573
+ const lazy = (query) => new Locator({ kind: "query", query }, rt);
4574
+ const swipe = async (options) => {
4575
+ const keys = Object.keys(options);
4576
+ const allowed = "direction" in options ? ["direction", "momentum"] : ["from", "to", "durationMs"];
4577
+ const unknown = keys.filter((key) => !allowed.includes(key));
4578
+ if (unknown.length > 0 || keys.length === 0 || "direction" in options && "from" in options) {
4579
+ throw new E2eError2("INVALID_ARGUMENT", "swipe takes either { direction, momentum? } or { from, to, durationMs? }, not a mix");
4580
+ }
4581
+ return rt.step("swipe", async () => {
4582
+ if ("direction" in options) {
4583
+ const observed = await rt.observe();
4584
+ const vector = swipeVector(options.direction, screenRect(observed.snapshot.screen), options.momentum);
4585
+ rt.drive("swipe", await observed.driver.swipe(vector.from, vector.to, vector.options));
4586
+ return;
4587
+ }
4588
+ if (!isFinitePoint(options.from) || !isFinitePoint(options.to)) {
4589
+ throw new E2eError2("INVALID_ARGUMENT", "swipe needs finite coordinates for from and to");
4590
+ }
4591
+ const driver = await host.driver();
4592
+ const opts = options.durationMs === undefined ? undefined : { durationMs: options.durationMs };
4593
+ rt.drive("swipe", await driver.swipe(options.from, options.to, opts));
4594
+ });
4595
+ };
4596
+ return {
4597
+ getByTestId: (id, options) => lazy(testIdQuery(id, options)),
4598
+ getByText: (text, options) => lazy(textQuery("text", text, options)),
4599
+ getByLabel: (text, options) => lazy(textQuery("label", text, options)),
4600
+ getByPlaceholder: (text, options) => lazy(textQuery("placeholder", text, options)),
4601
+ getByValue: (value, options) => lazy(textQuery("value", value, options)),
4602
+ getByRole: (role, nameOrOptions, options) => lazy(roleQuery(role, nameOrOptions, options)),
4603
+ locator: (selector) => new Locator(selectorExpression(selector), rt),
4604
+ async tapAt(point) {
4605
+ if (!isFinitePoint(point))
4606
+ throw new E2eError2("INVALID_ARGUMENT", `tapAt needs finite x and y, got ${JSON.stringify(point)}`);
4607
+ return rt.step(`tapAt (${point.x}, ${point.y})`, async () => {
4608
+ const driver = await host.driver();
4609
+ rt.drive("tapAt", await driver.tap(point));
4610
+ });
4611
+ },
4612
+ swipe,
4613
+ gesture: {
4614
+ async pinch(options) {
4615
+ assertScale(options.scale);
4616
+ return rt.step(`pinch \xD7${options.scale}`, async () => {
4617
+ const observed = await rt.observe();
4618
+ const rect = screenRect(observed.snapshot.screen);
4619
+ requireGesture(observed.driver, "pinch", "screen.gesture.pinch");
4620
+ rt.drive("pinch", await observed.driver.pinch(options.center ?? centreOf(rect), options.scale, {
4621
+ durationMs: options.durationMs ?? PINCH_MS,
4622
+ radius: options.radius ?? pinchRadius(rect, options.scale, observed.snapshot.screen)
4623
+ }));
4624
+ });
4625
+ },
4626
+ async rotate(options) {
4627
+ assertDegrees(options.degrees);
4628
+ return rt.step(`rotate ${options.degrees}\xB0`, async () => {
4629
+ const observed = await rt.observe();
4630
+ const rect = screenRect(observed.snapshot.screen);
4631
+ requireGesture(observed.driver, "pinch", "screen.gesture.rotate");
4632
+ rt.drive("rotate", await observed.driver.rotate(options.center ?? centreOf(rect), options.degrees, {
4633
+ durationMs: options.durationMs ?? ROTATE_MS,
4634
+ radius: options.radius ?? rotateRadius(rect, observed.snapshot.screen)
4635
+ }));
4636
+ });
4637
+ },
4638
+ async path(options) {
4639
+ const { paths } = options;
4640
+ if (paths.length < 1 || paths.length > 2) {
4641
+ throw new E2eError2("INVALID_ARGUMENT", `gesture.path takes 1 or 2 paths, got ${paths.length}`);
4642
+ }
4643
+ if (paths.some((points) => points.length < 1 || !points.every(isFinitePoint))) {
4644
+ throw new E2eError2("INVALID_ARGUMENT", "gesture.path needs at least one point per path, all with finite x and y");
4645
+ }
4646
+ return rt.step(`gesture path (${paths.length} ${paths.length === 1 ? "finger" : "fingers"})`, async () => {
4647
+ const driver = await host.driver();
4648
+ requireGesture(driver, "path", "screen.gesture.path", paths.length);
4649
+ rt.drive("gesture", await driver.gesture(paths.map((points) => ({ points: [...points] })), { durationMs: options.durationMs ?? PATH_MS }));
4650
+ });
4651
+ },
4652
+ swipe
4653
+ },
4654
+ scrollUntilVisible: (target, options) => scrollUntilVisible(rt, target, options)
4655
+ };
4656
+ }
4657
+
4658
+ // ../../packages/e2e/src/traced-device.ts
4659
+ function traceDevice(device, span) {
4660
+ if (span === undefined)
4661
+ return device;
4662
+ return {
4663
+ get devtools() {
4664
+ return device.devtools;
4665
+ },
4666
+ get perf() {
4667
+ return device.perf;
4668
+ },
4669
+ alert: (action) => span("device.alert", () => device.alert(action), { detail: (answered) => ({ answered }) }),
4670
+ openLink: (url, options) => span("device.openLink", () => device.openLink(url, options)),
4671
+ home: () => span("device.home", () => device.home()),
4672
+ back: () => span("device.back", () => device.back()),
4673
+ orientation: (set) => span("device.orientation", () => device.orientation(set)),
4674
+ appearance: (set) => span("device.appearance", () => device.appearance(set)),
4675
+ clipboard: () => span("device.clipboard", () => device.clipboard()),
4676
+ setClipboard: (text) => span("device.setClipboard", () => device.setClipboard(text)),
4677
+ hideKeyboard: () => span("device.hideKeyboard", () => device.hideKeyboard()),
4678
+ screenshot: (name) => span("device.screenshot", () => device.screenshot(name)),
4679
+ snapshot: (options) => span("device.snapshot", () => device.snapshot(options)),
4680
+ settle: (options) => span("device.settle", () => device.settle(options), {
4681
+ detail: ({ quiet, waitedMs, via, reason, frontmostPid }) => ({
4682
+ quiet,
4683
+ waitedMs,
4684
+ ...via === undefined ? {} : { via },
4685
+ ...reason === undefined ? {} : { reason },
4686
+ ...frontmostPid === undefined ? {} : { frontmostPid }
4687
+ })
4688
+ })
4689
+ };
4690
+ }
4691
+
4692
+ // ../../packages/e2e/src/fixtures.ts
4693
+ function screenHostFor(ctx, device) {
4694
+ const { session } = ctx;
4695
+ const alerts = ctx.systemAlerts;
4696
+ const answerSystemAlert = device === undefined || alerts === undefined || alerts === "off" ? undefined : async () => !linkInFlight(session) && await device.alert(alerts);
4697
+ const reactTree = session.env.metroUrl === undefined ? undefined : {
4698
+ async tree(options) {
4699
+ const attached = await session.devtools();
4700
+ return attached.success ? attached.data.tree(options) : attached;
4701
+ }
4702
+ };
4703
+ return {
4704
+ devtools: () => reactTree,
4705
+ driver: () => session.driver(),
4706
+ env: { actionTimeoutMs: ctx.actionTimeoutMs, assertionTimeoutMs: ctx.assertionTimeoutMs },
4707
+ get signal() {
4708
+ return session.signal;
4709
+ },
4710
+ step: (label, fn) => session.step("locator", label, fn),
4711
+ span: (op, fn, options) => session.span(op, fn, options),
4712
+ ...answerSystemAlert === undefined ? {} : { answerSystemAlert }
4713
+ };
4714
+ }
4715
+ async function defaultFixtureFactory(ctx) {
4716
+ const { session } = ctx;
4717
+ const driver = await session.driver();
4718
+ const device = traceDevice(createDevice({ session, driver, env: session.env }), session.span);
4719
+ return {
4720
+ app: createApp({ session, driver, env: session.env }),
4721
+ device,
4722
+ screen: createScreen(screenHostFor(ctx, device)),
4723
+ platform: session.env.platform
4724
+ };
4725
+ }
4726
+
4727
+ // ../../packages/e2e/src/preload.ts
4728
+ function versionMismatch(wardenVersion, e2eVersion) {
4729
+ if (wardenVersion === undefined || wardenVersion === e2eVersion || e2eVersion === WORKSPACE_VERSION)
4730
+ return;
4731
+ return `warden e2e: @delacour/warden-e2e ${e2eVersion} does not match warden ${wardenVersion}, refusing to run; install the matching version: bun add -d @delacour/warden-e2e@${wardenVersion}`;
4732
+ }
4733
+ function isCi(env) {
4734
+ const value = env.CI;
4735
+ return value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false";
4736
+ }
4737
+ function installPreload(deps) {
4738
+ const read = readEnv(deps.env);
4739
+ if (!read.success) {
4740
+ deps.stderr(`warden e2e: invalid environment
4741
+ ${read.error}
4742
+ `);
4743
+ deps.exit(2);
4744
+ return;
4745
+ }
4746
+ if (read.data === undefined)
4747
+ return;
4748
+ const warning = versionMismatch(read.data.wardenVersion, deps.version);
4749
+ if (warning !== undefined) {
4750
+ deps.stderr(`${warning}
4751
+ `);
4752
+ deps.exit(2);
4753
+ return;
4754
+ }
4755
+ deps.setDefaultTimeout(read.data.timeoutMs + BUN_TIMEOUT_MARGIN_MS2);
4756
+ const session = deps.createSession({ env: read.data });
4757
+ deps.afterAll(async () => {
4758
+ await session.close();
4759
+ if (isCi(deps.env) && deps.onlySeen()) {
4760
+ throw new E2eError2("INVALID_ARGUMENT", "test.only / test(..., { only: true }) is not allowed in CI");
4761
+ }
4762
+ });
4763
+ }
4764
+ if (!hasDefaultDriverFactory()) {
4765
+ setDefaultDriverFactory(defaultDriverFactory);
4766
+ setDefaultNativeLog(realNativeLogDeps(bunExec, androidTools(process.env).adb));
4767
+ setDefaultHidRecovery({ exec: bunExec });
4768
+ }
4769
+ if (!hasDefaultFixtureFactory())
4770
+ setDefaultFixtureFactory(defaultFixtureFactory);
4771
+ installPreload({
4772
+ env: process.env,
4773
+ setDefaultTimeout,
4774
+ afterAll,
4775
+ createSession: getSession,
4776
+ version: WARDEN_E2E_VERSION2,
4777
+ onlySeen: onlyWasSeen,
4778
+ stderr: (text) => {
4779
+ process.stderr.write(text);
4780
+ },
4781
+ exit: (code) => process.exit(code)
4782
+ });
4783
+ export {
4784
+ BUN_TIMEOUT_MARGIN_MS2 as BUN_TIMEOUT_MARGIN_MS,
4785
+ installPreload,
4786
+ isCi,
4787
+ versionMismatch
4788
+ };