@delacour/warden-e2e 0.0.0-stage → 0.1.1

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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/chunk-qtkmfjbq.mjs +4585 -0
  3. package/index.d.ts +1 -0
  4. package/index.mjs +998 -0
  5. package/package.json +49 -4
  6. package/preload.d.ts +1 -0
  7. package/preload.mjs +5512 -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 +124 -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 +91 -0
  19. package/types/core/src/types.d.ts +106 -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 +82 -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/network.d.ts +33 -0
  33. package/types/e2e/src/devtools/network.script.d.ts +24 -0
  34. package/types/e2e/src/devtools/session.d.ts +82 -0
  35. package/types/e2e/src/devtools/tree.d.ts +23 -0
  36. package/types/e2e/src/devtools/tree.script.d.ts +33 -0
  37. package/types/e2e/src/driver/alerts.d.ts +29 -0
  38. package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
  39. package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
  40. package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
  41. package/types/e2e/src/driver/android/android-app.d.ts +32 -0
  42. package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
  43. package/types/e2e/src/driver/android/android-device.d.ts +44 -0
  44. package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
  45. package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
  46. package/types/e2e/src/driver/android/android-state.d.ts +69 -0
  47. package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
  48. package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
  49. package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
  50. package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
  51. package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
  52. package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
  53. package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
  54. package/types/e2e/src/driver/driver.types.d.ts +231 -0
  55. package/types/e2e/src/driver/errors.d.ts +13 -0
  56. package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
  57. package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
  58. package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
  59. package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
  60. package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
  61. package/types/e2e/src/driver/ios/simctl.d.ts +87 -0
  62. package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
  63. package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
  64. package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
  65. package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
  66. package/types/e2e/src/driver/observation-cache.d.ts +66 -0
  67. package/types/e2e/src/driver/open.d.ts +29 -0
  68. package/types/e2e/src/driver/settle.d.ts +43 -0
  69. package/types/e2e/src/driver/span-detail.d.ts +12 -0
  70. package/types/e2e/src/driver/traced-driver.d.ts +14 -0
  71. package/types/e2e/src/driver/typing.d.ts +48 -0
  72. package/types/e2e/src/driver/unwrap.d.ts +13 -0
  73. package/types/e2e/src/env.d.ts +61 -0
  74. package/types/e2e/src/errors.d.ts +26 -0
  75. package/types/e2e/src/expect/expect.d.ts +27 -0
  76. package/types/e2e/src/expect/matchers.d.ts +72 -0
  77. package/types/e2e/src/fixture-session.d.ts +34 -0
  78. package/types/e2e/src/fixtures.d.ts +14 -0
  79. package/types/e2e/src/index.d.ts +8 -0
  80. package/types/e2e/src/preload.d.ts +29 -0
  81. package/types/e2e/src/runner/artifacts.d.ts +13 -0
  82. package/types/e2e/src/runner/attempt.d.ts +63 -0
  83. package/types/e2e/src/runner/callsite.d.ts +2 -0
  84. package/types/e2e/src/runner/failure.d.ts +28 -0
  85. package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
  86. package/types/e2e/src/runner/results.d.ts +48 -0
  87. package/types/e2e/src/runner/scope.d.ts +62 -0
  88. package/types/e2e/src/runner/span.d.ts +40 -0
  89. package/types/e2e/src/runner/step.d.ts +7 -0
  90. package/types/e2e/src/runner/test.d.ts +52 -0
  91. package/types/e2e/src/runner/trace.d.ts +87 -0
  92. package/types/e2e/src/screen/actions.d.ts +93 -0
  93. package/types/e2e/src/screen/context.d.ts +134 -0
  94. package/types/e2e/src/screen/field.d.ts +121 -0
  95. package/types/e2e/src/screen/locator.d.ts +233 -0
  96. package/types/e2e/src/screen/nodes.d.ts +53 -0
  97. package/types/e2e/src/screen/poll.d.ts +57 -0
  98. package/types/e2e/src/screen/queries.d.ts +34 -0
  99. package/types/e2e/src/screen/query.d.ts +60 -0
  100. package/types/e2e/src/screen/react-locator.d.ts +30 -0
  101. package/types/e2e/src/screen/render.d.ts +42 -0
  102. package/types/e2e/src/screen/roles.d.ts +24 -0
  103. package/types/e2e/src/screen/screen.d.ts +61 -0
  104. package/types/e2e/src/screen/selector.d.ts +38 -0
  105. package/types/e2e/src/screen/text.d.ts +13 -0
  106. package/types/e2e/src/session.d.ts +105 -0
  107. package/types/e2e/src/traced-device.d.ts +9 -0
  108. package/types/e2e/src/version.d.ts +7 -0
  109. package/types/types/src/result.d.ts +12 -0
  110. package/README.md +0 -3
@@ -0,0 +1,4585 @@
1
+ // @bun
2
+ // ../../packages/e2e/src/errors.ts
3
+ class E2eError extends Error {
4
+ code;
5
+ screen;
6
+ matches;
7
+ constructor(code, message, screen, matches) {
8
+ super(message);
9
+ this.name = "E2eError";
10
+ this.code = code;
11
+ if (screen !== undefined)
12
+ this.screen = screen;
13
+ if (matches !== undefined)
14
+ this.matches = matches;
15
+ }
16
+ static from(shape) {
17
+ return shape instanceof E2eError ? shape : new E2eError(shape.code, shape.message, shape.screen, shape.matches);
18
+ }
19
+ }
20
+ function e2eErr(error) {
21
+ return { success: false, error: E2eError.from(error) };
22
+ }
23
+
24
+ // ../../packages/e2e/src/env.ts
25
+ import { z } from "zod";
26
+ var ENV_NAMES = {
27
+ udid: "WARDEN_UDID",
28
+ deviceName: "WARDEN_DEVICE_NAME",
29
+ platform: "WARDEN_PLATFORM",
30
+ bundleId: "WARDEN_BUNDLE_ID",
31
+ appPath: "WARDEN_APP_PATH",
32
+ metroUrl: "WARDEN_METRO_URL",
33
+ launchArgs: "WARDEN_LAUNCH_ARGS",
34
+ out: "WARDEN_E2E_OUT",
35
+ timeout: "WARDEN_E2E_TIMEOUT",
36
+ actionTimeout: "WARDEN_E2E_ACTION_TIMEOUT",
37
+ assertionTimeout: "WARDEN_E2E_ASSERTION_TIMEOUT",
38
+ flowsDir: "WARDEN_E2E_FLOWS_DIR",
39
+ helperDir: "WARDEN_HELPER_DIR",
40
+ simd: "WARDEN_SIMD",
41
+ simdSocket: "WARDEN_SIMD_SOCKET",
42
+ headless: "WARDEN_HEADLESS",
43
+ logTimestamps: "WARDEN_LOG_TIMESTAMPS",
44
+ worker: "WARDEN_WORKER",
45
+ version: "WARDEN_VERSION"
46
+ };
47
+ var DEFAULT_TIMEOUT_MS = 120000;
48
+ var DEFAULT_ACTION_TIMEOUT_MS = 30000;
49
+ var DEFAULT_ASSERTION_TIMEOUT_MS = 5000;
50
+ var toggle = z.enum(["1", "true", "0", "false"]).transform((v) => v === "1" || v === "true");
51
+ var millis = z.string().regex(/^\d+$/, "must be a whole number of milliseconds").transform(Number).pipe(z.number().int().positive());
52
+ var launchArgs = z.string().transform((raw, ctx) => {
53
+ try {
54
+ const parsed = z.array(z.string()).safeParse(JSON.parse(raw));
55
+ if (parsed.success)
56
+ return parsed.data;
57
+ } catch {}
58
+ ctx.addIssue({ code: "custom", message: "must be a JSON array of strings" });
59
+ return z.NEVER;
60
+ });
61
+ var REQUIRED = "required when WARDEN_UDID is set";
62
+ var schema = z.object({
63
+ udid: z.string().min(1),
64
+ deviceName: z.string().optional(),
65
+ platform: z.enum(["ios", "android"]).default("ios"),
66
+ bundleId: z.string({ error: REQUIRED }).min(1, REQUIRED),
67
+ appPath: z.string().optional(),
68
+ metroUrl: z.string().optional(),
69
+ launchArgs: launchArgs.default([]),
70
+ out: z.string().optional(),
71
+ timeout: millis.default(DEFAULT_TIMEOUT_MS),
72
+ actionTimeout: millis.default(DEFAULT_ACTION_TIMEOUT_MS),
73
+ assertionTimeout: millis.default(DEFAULT_ASSERTION_TIMEOUT_MS),
74
+ flowsDir: z.string().optional(),
75
+ helperDir: z.string().optional(),
76
+ simd: z.string().optional(),
77
+ simdSocket: z.string().optional(),
78
+ headless: toggle.default(false),
79
+ logTimestamps: toggle.default(false),
80
+ worker: z.string().optional(),
81
+ version: z.string().optional()
82
+ });
83
+ function defined(values) {
84
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
85
+ }
86
+ function toEnv(v, now) {
87
+ return {
88
+ udid: v.udid,
89
+ platform: v.platform,
90
+ bundleId: v.bundleId,
91
+ launchArgs: v.launchArgs,
92
+ out: v.out ?? `.warden-e2e/${now().toISOString()}`,
93
+ timeoutMs: v.timeout,
94
+ actionTimeoutMs: v.actionTimeout,
95
+ assertionTimeoutMs: v.assertionTimeout,
96
+ headless: v.headless,
97
+ logTimestamps: v.logTimestamps,
98
+ ...defined({
99
+ deviceName: v.deviceName,
100
+ appPath: v.appPath,
101
+ metroUrl: v.metroUrl,
102
+ flowsDir: v.flowsDir,
103
+ helperDir: v.helperDir,
104
+ simd: v.simd,
105
+ simdSocket: v.simdSocket,
106
+ worker: v.worker,
107
+ wardenVersion: v.version
108
+ })
109
+ };
110
+ }
111
+ var isEnvKey = (key) => typeof key === "string" && (key in ENV_NAMES);
112
+ function readEnv(env, opts = {}) {
113
+ const udid = env[ENV_NAMES.udid];
114
+ if (udid === undefined || udid === "")
115
+ return { success: true, data: undefined };
116
+ const raw = {};
117
+ for (const [key, name] of Object.entries(ENV_NAMES)) {
118
+ const value = env[name];
119
+ raw[key] = value === undefined || value === "" ? undefined : value;
120
+ }
121
+ const parsed = schema.safeParse(raw);
122
+ if (!parsed.success) {
123
+ const lines = parsed.error.issues.map((issue) => {
124
+ const key = issue.path[0];
125
+ return `${isEnvKey(key) ? ENV_NAMES[key] : String(key)}: ${issue.message}`;
126
+ });
127
+ return { success: false, error: lines.join(`
128
+ `) };
129
+ }
130
+ return { success: true, data: toEnv(parsed.data, opts.now ?? (() => new Date)) };
131
+ }
132
+
133
+ // ../../packages/e2e/src/session.ts
134
+ import { mkdirSync as mkdirSync2, writeSync } from "fs";
135
+ import { resolve } from "path";
136
+
137
+ // ../../packages/e2e/src/devtools/devtools.types.ts
138
+ function devtoolsErr(code, message) {
139
+ return { success: false, error: { code, message } };
140
+ }
141
+
142
+ // ../../packages/e2e/src/devtools/facet.ts
143
+ import { join } from "path";
144
+
145
+ // ../../packages/types/src/result.ts
146
+ function ok(data) {
147
+ return { success: true, data };
148
+ }
149
+ function err(error) {
150
+ return {
151
+ success: false,
152
+ error: error instanceof Error ? error.message : String(error)
153
+ };
154
+ }
155
+
156
+ // ../../packages/e2e/src/devtools/console.ts
157
+ import { z as z3 } from "zod";
158
+
159
+ // ../../packages/e2e/src/devtools/cdp.ts
160
+ import { z as z2 } from "zod";
161
+ var DEFAULT_TIMEOUT_MS2 = 1e4;
162
+ var envelopeSchema = z2.looseObject({
163
+ id: z2.number().optional(),
164
+ result: z2.unknown().optional(),
165
+ error: z2.looseObject({ message: z2.string().optional() }).optional(),
166
+ method: z2.string().optional(),
167
+ params: z2.unknown().optional()
168
+ });
169
+ var exceptionDetailsSchema = z2.looseObject({
170
+ text: z2.string().optional(),
171
+ lineNumber: z2.number().optional(),
172
+ columnNumber: z2.number().optional(),
173
+ url: z2.string().optional(),
174
+ exception: z2.looseObject({
175
+ type: z2.string().optional(),
176
+ subtype: z2.string().optional(),
177
+ className: z2.string().optional(),
178
+ description: z2.string().optional(),
179
+ value: z2.unknown().optional()
180
+ }).optional()
181
+ });
182
+ var bindingPayloadSchema = z2.looseObject({ requestId: z2.string() });
183
+ var bindingCalledSchema = z2.looseObject({ name: z2.string().optional(), payload: z2.string() });
184
+ var evaluateReplySchema = z2.looseObject({
185
+ result: z2.looseObject({ type: z2.string().optional(), value: z2.unknown().optional() }).optional(),
186
+ exceptionDetails: exceptionDetailsSchema.optional()
187
+ });
188
+ function readEvaluateReply(reply) {
189
+ const parsed = evaluateReplySchema.safeParse(reply);
190
+ if (!parsed.success)
191
+ return { value: undefined, type: undefined, exceptionDetails: undefined };
192
+ return {
193
+ value: parsed.data.result?.value,
194
+ type: parsed.data.result?.type,
195
+ exceptionDetails: parsed.data.exceptionDetails
196
+ };
197
+ }
198
+ function describeException(details) {
199
+ const thrown = details.exception;
200
+ if (thrown?.description)
201
+ return thrown.description;
202
+ if (thrown?.value !== undefined)
203
+ return String(thrown.value);
204
+ return details.text ?? "JS exception";
205
+ }
206
+ function textOf(data) {
207
+ if (typeof data === "string")
208
+ return data;
209
+ if (data instanceof ArrayBuffer)
210
+ return new TextDecoder().decode(data);
211
+ if (ArrayBuffer.isView(data))
212
+ return new TextDecoder().decode(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
213
+ return;
214
+ }
215
+ function inspectorOrigin(wsUrl) {
216
+ if (!URL.canParse(wsUrl))
217
+ return;
218
+ const url = new URL(wsUrl);
219
+ const port = url.port || (url.protocol === "wss:" ? "443" : "80");
220
+ return `http://127.0.0.1:${port}`;
221
+ }
222
+ async function openCdp(wsUrl, options = {}) {
223
+ const Impl = options.WebSocket ?? WebSocket;
224
+ const defaultTimeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
225
+ let ws;
226
+ try {
227
+ const origin = inspectorOrigin(wsUrl);
228
+ ws = origin ? new Impl(wsUrl, { headers: { Origin: origin } }) : new Impl(wsUrl);
229
+ } catch (error) {
230
+ return devtoolsErr("devtools_unavailable", `bad inspector url ${wsUrl}: ${String(error)}`);
231
+ }
232
+ const opened = await new Promise((resolve) => {
233
+ const timer = setTimeout(() => {
234
+ resolve(devtoolsErr("devtools_unavailable", `inspector socket ${wsUrl} did not open within ${defaultTimeout} ms`));
235
+ ws.close();
236
+ }, defaultTimeout);
237
+ ws.addEventListener("open", () => {
238
+ clearTimeout(timer);
239
+ resolve(ok(true));
240
+ });
241
+ const failed = () => {
242
+ clearTimeout(timer);
243
+ resolve(devtoolsErr("devtools_unavailable", `could not connect to inspector socket ${wsUrl}`));
244
+ };
245
+ ws.addEventListener("error", failed);
246
+ ws.addEventListener("close", failed);
247
+ });
248
+ if (!opened.success)
249
+ return opened;
250
+ let nextId = 1;
251
+ let intentional = false;
252
+ let closed = false;
253
+ let bindingAvailable;
254
+ const pending = new Map;
255
+ const bindings = new Map;
256
+ const listeners = new Map;
257
+ const closeHandlers = new Set;
258
+ const anyHandlers = new Set;
259
+ const send = (method, params = {}, opts = {}) => {
260
+ if (closed || ws.readyState !== ws.OPEN)
261
+ return Promise.resolve(devtoolsErr("cdp_closed", `CDP socket is closed (${method})`));
262
+ const id = nextId++;
263
+ const timeoutMs = opts.timeoutMs ?? defaultTimeout;
264
+ return new Promise((resolve) => {
265
+ const timer = setTimeout(() => {
266
+ pending.delete(id);
267
+ resolve(devtoolsErr("cdp_timeout", `CDP ${method} (id ${id}) got no reply within ${timeoutMs} ms`));
268
+ }, timeoutMs);
269
+ pending.set(id, { resolve, timer });
270
+ ws.send(JSON.stringify({ id, method, params }));
271
+ });
272
+ };
273
+ const dispatch = (method, params) => {
274
+ if (method === "Runtime.bindingCalled") {
275
+ const call = bindingCalledSchema.safeParse(params);
276
+ if (call.success) {
277
+ let payload;
278
+ try {
279
+ payload = JSON.parse(call.data.payload);
280
+ } catch {
281
+ payload = undefined;
282
+ }
283
+ const tagged = bindingPayloadSchema.safeParse(payload);
284
+ if (tagged.success)
285
+ bindings.get(tagged.data.requestId)?.settle(ok(payload));
286
+ }
287
+ }
288
+ for (const handler of [...anyHandlers])
289
+ handler(method, params);
290
+ for (const handler of [...listeners.get(method) ?? []])
291
+ handler(params);
292
+ };
293
+ ws.addEventListener("message", (event) => {
294
+ const text = textOf(event.data);
295
+ if (text === undefined)
296
+ return;
297
+ let json;
298
+ try {
299
+ json = JSON.parse(text);
300
+ } catch {
301
+ return;
302
+ }
303
+ const message = envelopeSchema.safeParse(json);
304
+ if (!message.success)
305
+ return;
306
+ const { id, error, result, method, params } = message.data;
307
+ if (id !== undefined) {
308
+ const request = pending.get(id);
309
+ if (!request)
310
+ return;
311
+ pending.delete(id);
312
+ clearTimeout(request.timer);
313
+ request.resolve(error ? devtoolsErr("cdp_protocol", error.message ?? JSON.stringify(error)) : ok(result ?? {}));
314
+ return;
315
+ }
316
+ if (method)
317
+ dispatch(method, params);
318
+ });
319
+ ws.addEventListener("close", () => {
320
+ if (closed)
321
+ return;
322
+ closed = true;
323
+ for (const [, request] of pending) {
324
+ clearTimeout(request.timer);
325
+ request.resolve(devtoolsErr("cdp_closed", "CDP connection closed"));
326
+ }
327
+ pending.clear();
328
+ for (const binding of [...bindings.values()])
329
+ binding.settle(devtoolsErr("cdp_closed", "CDP connection closed"));
330
+ if (!intentional)
331
+ for (const handler of [...closeHandlers])
332
+ handler();
333
+ });
334
+ const subscribe = (set, handler) => {
335
+ set.add(handler);
336
+ return () => {
337
+ set.delete(handler);
338
+ };
339
+ };
340
+ const client = {
341
+ send,
342
+ on(event, handler) {
343
+ let set = listeners.get(event);
344
+ if (!set) {
345
+ set = new Set;
346
+ listeners.set(event, set);
347
+ }
348
+ return subscribe(set, handler);
349
+ },
350
+ onClose: (handler) => subscribe(closeHandlers, handler),
351
+ onAny: (handler) => subscribe(anyHandlers, handler),
352
+ async addBinding(name) {
353
+ const added = await send("Runtime.addBinding", { name });
354
+ if (!added.success)
355
+ return added;
356
+ const probe = await send("Runtime.evaluate", { expression: `typeof ${name}`, returnByValue: true });
357
+ const value = probe.success ? readEvaluateReply(probe.data).value : undefined;
358
+ bindingAvailable = value !== "undefined";
359
+ return ok({ available: bindingAvailable });
360
+ },
361
+ evaluateWithBinding(script, requestId, timeoutMs = defaultTimeout) {
362
+ if (bindingAvailable === false) {
363
+ return Promise.resolve(devtoolsErr("binding_unavailable", "this JS runtime acknowledges Runtime.addBinding but never installs the binding (legacy Hermes, React Native <= 0.72)"));
364
+ }
365
+ return new Promise((resolve) => {
366
+ const timer = setTimeout(() => {
367
+ settle(devtoolsErr("cdp_timeout", `no binding callback for requestId ${requestId} within ${timeoutMs} ms`));
368
+ }, timeoutMs);
369
+ const settle = (result) => {
370
+ clearTimeout(timer);
371
+ bindings.delete(requestId);
372
+ resolve(result);
373
+ };
374
+ bindings.set(requestId, { settle });
375
+ send("Runtime.evaluate", { expression: script, returnByValue: false, awaitPromise: false }, { timeoutMs }).then((reply) => {
376
+ if (!reply.success)
377
+ return settle(reply);
378
+ const { exceptionDetails } = readEvaluateReply(reply.data);
379
+ if (exceptionDetails)
380
+ settle(devtoolsErr("eval_failed", describeException(exceptionDetails)));
381
+ });
382
+ });
383
+ },
384
+ get bindingAvailable() {
385
+ return bindingAvailable;
386
+ },
387
+ close() {
388
+ intentional = true;
389
+ if (!closed)
390
+ ws.close();
391
+ }
392
+ };
393
+ return ok(client);
394
+ }
395
+
396
+ // ../../packages/e2e/src/devtools/jsonl.ts
397
+ import { appendFile, mkdir, writeFile } from "fs/promises";
398
+ import { dirname } from "path";
399
+ var encode = (rows) => rows.map((row) => `${JSON.stringify(row)}
400
+ `).join("");
401
+ async function writeJsonl(path, rows) {
402
+ await mkdir(dirname(path), { recursive: true });
403
+ await writeFile(path, encode(rows));
404
+ }
405
+
406
+ // ../../packages/e2e/src/devtools/console.ts
407
+ var DEFAULT_RING = 500;
408
+ var MAX_FUNCTION_TEXT = 60;
409
+ var propertyPreviewSchema = z3.looseObject({
410
+ name: z3.string(),
411
+ type: z3.string(),
412
+ subtype: z3.string().optional(),
413
+ value: z3.string().optional()
414
+ });
415
+ var objectPreviewSchema = z3.looseObject({
416
+ subtype: z3.string().optional(),
417
+ overflow: z3.boolean().optional(),
418
+ properties: z3.array(propertyPreviewSchema).optional()
419
+ });
420
+ var remoteObjectSchema = z3.looseObject({
421
+ type: z3.string(),
422
+ subtype: z3.string().optional(),
423
+ className: z3.string().optional(),
424
+ value: z3.unknown().optional(),
425
+ unserializableValue: z3.string().optional(),
426
+ description: z3.string().optional(),
427
+ preview: objectPreviewSchema.optional()
428
+ });
429
+ var stackTraceSchema = z3.looseObject({
430
+ callFrames: z3.array(z3.looseObject({
431
+ url: z3.string().optional(),
432
+ lineNumber: z3.number().optional(),
433
+ columnNumber: z3.number().optional()
434
+ }))
435
+ });
436
+ var consoleCalledSchema = z3.looseObject({
437
+ type: z3.string(),
438
+ args: z3.array(remoteObjectSchema).default([]),
439
+ timestamp: z3.number().optional(),
440
+ stackTrace: stackTraceSchema.optional()
441
+ });
442
+ var exceptionThrownSchema = z3.looseObject({
443
+ timestamp: z3.number().optional(),
444
+ exceptionDetails: exceptionDetailsSchema
445
+ });
446
+ var LEVELS = {
447
+ log: "log",
448
+ info: "info",
449
+ debug: "debug",
450
+ warning: "warn",
451
+ error: "error",
452
+ assert: "error"
453
+ };
454
+ function formatProperty(property) {
455
+ const value = property.value ?? property.type;
456
+ return property.type === "string" ? JSON.stringify(value) : value;
457
+ }
458
+ function formatPreview(object) {
459
+ const preview = object.preview;
460
+ if (!preview?.properties)
461
+ return;
462
+ const more = preview.overflow ? ", \u2026" : "";
463
+ if (preview.subtype === "array")
464
+ return `[${preview.properties.map(formatProperty).join(", ")}${more}]`;
465
+ const body = preview.properties.map((p) => `${p.name}: ${formatProperty(p)}`).join(", ");
466
+ const name = object.className && object.className !== "Object" ? `${object.className} ` : "";
467
+ return `${name}{${body}${more}}`;
468
+ }
469
+ function formatValue(object) {
470
+ switch (object.type) {
471
+ case "string":
472
+ return String(object.value ?? "");
473
+ case "undefined":
474
+ return "undefined";
475
+ case "number":
476
+ case "boolean":
477
+ case "bigint":
478
+ return object.unserializableValue ?? object.description ?? String(object.value);
479
+ case "function": {
480
+ const first = (object.description ?? "function").split(`
481
+ `)[0] ?? "function";
482
+ return first.length > MAX_FUNCTION_TEXT ? `${first.slice(0, MAX_FUNCTION_TEXT)}\u2026` : first;
483
+ }
484
+ case "object": {
485
+ if (object.subtype === "null")
486
+ return "null";
487
+ if (object.value !== undefined)
488
+ return JSON.stringify(object.value);
489
+ if (object.subtype === "error")
490
+ return object.description ?? "Error";
491
+ return formatPreview(object) ?? object.description ?? object.className ?? "Object";
492
+ }
493
+ default:
494
+ return object.description ?? `[${object.type}]`;
495
+ }
496
+ }
497
+ function applyFormat(format, rest) {
498
+ let used = 0;
499
+ const text = format.replace(/%([sdifoOjc%])/g, (whole, spec) => {
500
+ if (spec === "%")
501
+ return "%";
502
+ const arg = rest[used];
503
+ if (arg === undefined)
504
+ return whole;
505
+ used++;
506
+ if (spec === "c")
507
+ return "";
508
+ if (spec === "d" || spec === "i" || spec === "f") {
509
+ const n = Number(arg.value ?? arg.description);
510
+ return Number.isNaN(n) ? "NaN" : String(spec === "f" ? n : Math.trunc(n));
511
+ }
512
+ return formatValue(arg);
513
+ });
514
+ return { text, used };
515
+ }
516
+ function formatConsoleArgs(args) {
517
+ const rendered = args.map(formatValue);
518
+ const [first, ...rest] = args;
519
+ if (first?.type !== "string" || !String(first.value).includes("%"))
520
+ return { text: rendered.join(" "), args: rendered };
521
+ const { text, used } = applyFormat(String(first.value), rest);
522
+ return { text: [text, ...rendered.slice(1 + used)].join(" "), args: rendered };
523
+ }
524
+ function sourceOf(url, line, col) {
525
+ if (!url)
526
+ return;
527
+ return { url, line: (line ?? 0) + 1, col: (col ?? 0) + 1 };
528
+ }
529
+ var lineKey = (ts, text) => `${ts}\x00${text}`;
530
+ function createConsoleCapture(options = {}) {
531
+ const ring = options.ring ?? DEFAULT_RING;
532
+ const now = options.now ?? Date.now;
533
+ let buffer = [];
534
+ const seen = new Map;
535
+ let epochConnection = -1;
536
+ let epochCounts = new Map;
537
+ const remember = (key, count) => {
538
+ seen.set(key, count);
539
+ if (seen.size > ring * 4) {
540
+ const oldest = seen.keys().next();
541
+ if (!oldest.done)
542
+ seen.delete(oldest.value);
543
+ }
544
+ };
545
+ const isNew = (event, line) => {
546
+ const key = lineKey(line.ts, line.text);
547
+ if (!event.replay) {
548
+ remember(key, (seen.get(key) ?? 0) + 1);
549
+ return true;
550
+ }
551
+ if (event.connection !== epochConnection) {
552
+ epochConnection = event.connection;
553
+ epochCounts = new Map;
554
+ }
555
+ const inBurst = (epochCounts.get(key) ?? 0) + 1;
556
+ epochCounts.set(key, inBurst);
557
+ if (inBurst <= (seen.get(key) ?? 0))
558
+ return false;
559
+ remember(key, inBurst);
560
+ return true;
561
+ };
562
+ const record = (line) => {
563
+ buffer.push(line);
564
+ if (buffer.length > ring)
565
+ buffer = buffer.slice(buffer.length - ring);
566
+ options.sink?.(line);
567
+ };
568
+ const fromConsole = (params) => {
569
+ const parsed = consoleCalledSchema.safeParse(params);
570
+ if (!parsed.success)
571
+ return;
572
+ const { type, args, timestamp, stackTrace } = parsed.data;
573
+ const formatted = formatConsoleArgs(args);
574
+ const frame = stackTrace?.callFrames[0];
575
+ const source = sourceOf(frame?.url, frame?.lineNumber, frame?.columnNumber);
576
+ return {
577
+ ts: timestamp ?? now(),
578
+ level: LEVELS[type] ?? "log",
579
+ kind: "console",
580
+ text: formatted.text,
581
+ args: formatted.args,
582
+ ...source ? { source } : {}
583
+ };
584
+ };
585
+ const fromException = (params) => {
586
+ const parsed = exceptionThrownSchema.safeParse(params);
587
+ if (!parsed.success)
588
+ return;
589
+ const { exceptionDetails: details, timestamp } = parsed.data;
590
+ const source = sourceOf(details.url, details.lineNumber, details.columnNumber);
591
+ return {
592
+ ts: timestamp ?? now(),
593
+ level: "error",
594
+ kind: "exception",
595
+ text: describeException(details),
596
+ ...source ? { source } : {}
597
+ };
598
+ };
599
+ return {
600
+ handle(event) {
601
+ let line;
602
+ if (event.method === "Runtime.consoleAPICalled")
603
+ line = fromConsole(event.params);
604
+ else if (event.method === "Runtime.exceptionThrown")
605
+ line = fromException(event.params);
606
+ if (line && isNew(event, line))
607
+ record(line);
608
+ },
609
+ lines: () => [...buffer],
610
+ clear() {
611
+ buffer = [];
612
+ },
613
+ mark(label) {
614
+ record({ ts: now(), level: "info", kind: "note", text: label });
615
+ },
616
+ writeJsonl: (path) => writeJsonl(path, buffer)
617
+ };
618
+ }
619
+
620
+ // ../../packages/e2e/src/devtools/network.ts
621
+ import { z as z4 } from "zod";
622
+
623
+ // ../../packages/e2e/src/devtools/network.script.ts
624
+ var NETWORK_SCRIPT = `function (cfg, g) {
625
+ if (g.__warden_net_patched) return "already";
626
+ if (typeof g.fetch !== "function") return "no fetch";
627
+ g.__warden_net_patched = true;
628
+ var original = g.fetch;
629
+ var log = [];
630
+ var maxBody = cfg.maxBody || 65536;
631
+ var textual = /json|text|xml|javascript|x-www-form-urlencoded|graphql/i;
632
+
633
+ function skipped(url) {
634
+ for (var i = 0; i < cfg.skipPrefixes.length; i++) {
635
+ var prefix = cfg.skipPrefixes[i];
636
+ if (url.indexOf(prefix) === 0 && "/?#".indexOf(url.charAt(prefix.length) || "/") !== -1) return true;
637
+ }
638
+ return false;
639
+ }
640
+
641
+ function headersToObject(h) {
642
+ var out = {};
643
+ if (!h) return out;
644
+ if (Array.isArray(h)) {
645
+ for (var i = 0; i < h.length; i++) out[String(h[i][0]).toLowerCase()] = String(h[i][1]);
646
+ } else if (typeof h.forEach === "function") {
647
+ h.forEach(function (value, key) { out[String(key).toLowerCase()] = String(value); });
648
+ } else {
649
+ for (var k in h) if (Object.prototype.hasOwnProperty.call(h, k)) out[k.toLowerCase()] = String(h[k]);
650
+ }
651
+ return out;
652
+ }
653
+
654
+ function requestBodyText(body) {
655
+ if (typeof body === "string") return body.length > maxBody ? body.slice(0, maxBody) : body;
656
+ if (body && typeof body.toString === "function" && body.constructor && body.constructor.name === "URLSearchParams") {
657
+ return body.toString();
658
+ }
659
+ return undefined;
660
+ }
661
+
662
+ function push(entry) {
663
+ log.push(entry);
664
+ if (log.length > cfg.ring) log.shift();
665
+ }
666
+
667
+ g.__warden_net_drain = function () {
668
+ var out = log;
669
+ log = [];
670
+ return JSON.stringify(out);
671
+ };
672
+
673
+ g.fetch = function (input, init) {
674
+ var self = this;
675
+ var args = arguments;
676
+ var url = typeof input === "string" ? input : (input && input.url) || String(input);
677
+ if (skipped(url)) return original.apply(self, args);
678
+
679
+ var started = Date.now();
680
+ var entry = {
681
+ ts: started,
682
+ method: String((init && init.method) || (input && input.method) || "GET").toUpperCase(),
683
+ url: url,
684
+ status: 0,
685
+ durationMs: 0,
686
+ requestHeaders: headersToObject((init && init.headers) || (input && input.headers)),
687
+ responseHeaders: {}
688
+ };
689
+ if (cfg.includeBodies && init && init.body !== undefined && init.body !== null) {
690
+ var sent = requestBodyText(init.body);
691
+ if (sent !== undefined) entry.requestBody = sent;
692
+ }
693
+
694
+ return original.apply(self, args).then(function (res) {
695
+ entry.status = res.status;
696
+ entry.durationMs = Date.now() - started;
697
+ entry.responseHeaders = headersToObject(res.headers);
698
+ var type = entry.responseHeaders["content-type"] || "";
699
+ if (!cfg.includeBodies || !textual.test(type)) {
700
+ push(entry);
701
+ return res;
702
+ }
703
+ try {
704
+ res.clone().text().then(function (text) {
705
+ if (text.length > maxBody) {
706
+ entry.responseBody = text.slice(0, maxBody);
707
+ entry.responseBodyTruncated = true;
708
+ } else {
709
+ entry.responseBody = text;
710
+ }
711
+ push(entry);
712
+ }, function () { push(entry); });
713
+ } catch (e) {
714
+ push(entry);
715
+ }
716
+ return res;
717
+ }, function (error) {
718
+ entry.durationMs = Date.now() - started;
719
+ entry.error = error && error.message ? error.message : String(error);
720
+ push(entry);
721
+ throw error;
722
+ });
723
+ };
724
+ return "patched";
725
+ }`;
726
+ function networkInstallExpression(config) {
727
+ const global = `(typeof globalThis !== "undefined" ? globalThis : (0, eval)("this"))`;
728
+ return `(${NETWORK_SCRIPT})(${JSON.stringify(config)}, ${global})`;
729
+ }
730
+ var NETWORK_DRAIN_EXPRESSION = `typeof __warden_net_drain === "function" ? __warden_net_drain() : null`;
731
+
732
+ // ../../packages/e2e/src/devtools/network.ts
733
+ var DEFAULT_RING2 = 2000;
734
+ var DEFAULT_POLL_MS = 500;
735
+ var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "10.0.2.2"];
736
+ function metroSkipPrefixes(metroUrl) {
737
+ let url;
738
+ try {
739
+ url = new URL(metroUrl);
740
+ } catch {
741
+ return [];
742
+ }
743
+ const port = url.port ? `:${url.port}` : "";
744
+ const prefixes = [url.origin, ...LOOPBACK_HOSTS.map((host) => `${url.protocol}//${host}${port}`)];
745
+ return [...new Set(prefixes)];
746
+ }
747
+ var pageEntrySchema = z4.looseObject({
748
+ ts: z4.number(),
749
+ method: z4.string(),
750
+ url: z4.string(),
751
+ status: z4.number(),
752
+ durationMs: z4.number(),
753
+ requestHeaders: z4.record(z4.string(), z4.string()),
754
+ responseHeaders: z4.record(z4.string(), z4.string()),
755
+ requestBody: z4.string().optional(),
756
+ responseBody: z4.string().optional(),
757
+ responseBodyTruncated: z4.boolean().optional(),
758
+ error: z4.string().optional()
759
+ });
760
+ var timerSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
761
+ async function attachNetwork(session, options = {}) {
762
+ const ring = options.ring ?? DEFAULT_RING2;
763
+ const sleep = options.sleep ?? timerSleep;
764
+ const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
765
+ const installExpression = networkInstallExpression({
766
+ ring,
767
+ includeBodies: options.includeBodies ?? false,
768
+ skipPrefixes: options.metroUrl ? metroSkipPrefixes(options.metroUrl) : []
769
+ });
770
+ let buffer = [];
771
+ let nextId = 1;
772
+ let stopped = false;
773
+ let queue = Promise.resolve();
774
+ const serial = (task) => {
775
+ const run = queue.then(task, task);
776
+ queue = run.catch(() => {
777
+ return;
778
+ });
779
+ return run;
780
+ };
781
+ const install = async () => {
782
+ const installed = await session.evaluate(installExpression);
783
+ if (installed.success)
784
+ return ok(true);
785
+ return { success: false, error: { code: installed.error.code ?? "eval_failed", message: installed.error.message } };
786
+ };
787
+ const take = (text) => {
788
+ let rows;
789
+ try {
790
+ rows = JSON.parse(text);
791
+ } catch {
792
+ return;
793
+ }
794
+ if (!Array.isArray(rows))
795
+ return;
796
+ for (const row of rows) {
797
+ const parsed = pageEntrySchema.safeParse(row);
798
+ if (!parsed.success)
799
+ continue;
800
+ const entry = { ...parsed.data, id: nextId++ };
801
+ buffer.push(entry);
802
+ options.sink?.(entry);
803
+ }
804
+ if (buffer.length > ring)
805
+ buffer = buffer.slice(buffer.length - ring);
806
+ };
807
+ const drain = async () => {
808
+ const drained = await session.evaluate(NETWORK_DRAIN_EXPRESSION);
809
+ if (!drained.success)
810
+ return;
811
+ if (typeof drained.data === "string")
812
+ take(drained.data);
813
+ else if (drained.data === null)
814
+ await install();
815
+ };
816
+ const first = await install();
817
+ if (!first.success)
818
+ return first;
819
+ session.on("reconnected", () => {
820
+ serial(install);
821
+ });
822
+ const poll = async () => {
823
+ for (;; ) {
824
+ await sleep(pollMs);
825
+ if (stopped)
826
+ return;
827
+ await serial(drain);
828
+ }
829
+ };
830
+ poll();
831
+ return ok({
832
+ async entries(query = {}) {
833
+ await serial(drain);
834
+ if (query.id !== undefined)
835
+ return buffer.filter((entry) => entry.id === query.id);
836
+ return query.last === undefined ? [...buffer] : buffer.slice(-query.last);
837
+ },
838
+ async clear() {
839
+ await serial(drain);
840
+ buffer = [];
841
+ },
842
+ async writeJsonl(path) {
843
+ await serial(drain);
844
+ await writeJsonl(path, buffer);
845
+ },
846
+ stop() {
847
+ stopped = true;
848
+ }
849
+ });
850
+ }
851
+
852
+ // ../../packages/e2e/src/devtools/inspector.ts
853
+ import { z as z5 } from "zod";
854
+ var DECOY_VM = "don't use";
855
+ var STATUS_RUNNING = "packager-status:running";
856
+ var HTTP_TIMEOUT_MS = 3000;
857
+ var inspectorTargetSchema = z5.looseObject({
858
+ id: z5.string(),
859
+ title: z5.string().optional(),
860
+ description: z5.string().optional(),
861
+ vm: z5.string().optional(),
862
+ deviceName: z5.string().optional(),
863
+ appId: z5.string().optional(),
864
+ webSocketDebuggerUrl: z5.string(),
865
+ reactNative: z5.looseObject({
866
+ logicalDeviceId: z5.string().optional(),
867
+ capabilities: z5.looseObject({ prefersFuseboxFrontend: z5.boolean().optional() }).optional()
868
+ }).optional()
869
+ });
870
+ function unavailable(message) {
871
+ return devtoolsErr("devtools_unavailable", message);
872
+ }
873
+ async function get(fetch2, url) {
874
+ try {
875
+ const res = await fetch2(url, { signal: AbortSignal.timeout(HTTP_TIMEOUT_MS) });
876
+ return { ok: res.ok, status: res.status, body: await res.text() };
877
+ } catch {
878
+ return;
879
+ }
880
+ }
881
+ function parseTargets(body) {
882
+ let json;
883
+ try {
884
+ json = JSON.parse(body);
885
+ } catch {
886
+ return;
887
+ }
888
+ if (!Array.isArray(json))
889
+ return;
890
+ const targets = [];
891
+ for (const entry of json) {
892
+ const parsed = inspectorTargetSchema.safeParse(entry);
893
+ if (parsed.success)
894
+ targets.push(parsed.data);
895
+ }
896
+ return targets;
897
+ }
898
+ async function listTargets(metroUrl, fetch2) {
899
+ const base = metroUrl.replace(/\/+$/, "");
900
+ const status = await get(fetch2, `${base}/status`);
901
+ if (!status)
902
+ return unavailable(`Metro is not reachable at ${base}`);
903
+ if (!status.body.includes(STATUS_RUNNING)) {
904
+ return unavailable(`${base} did not answer like Metro (got: ${status.body.slice(0, 80)})`);
905
+ }
906
+ for (const path of ["/json/list", "/json"]) {
907
+ const res = await get(fetch2, `${base}${path}`);
908
+ if (!res?.ok)
909
+ continue;
910
+ const targets = parseTargets(res.body);
911
+ if (targets)
912
+ return ok(targets.filter((t) => t.vm !== DECOY_VM));
913
+ }
914
+ return unavailable(`Metro at ${base} listed no inspector targets (/json/list and /json failed)`);
915
+ }
916
+ function urlDevice(target) {
917
+ try {
918
+ return new URL(target.webSocketDebuggerUrl).searchParams.get("device") ?? undefined;
919
+ } catch {
920
+ return;
921
+ }
922
+ }
923
+ function logicalIdOf(target) {
924
+ return target.reactNative?.logicalDeviceId ?? urlDevice(target);
925
+ }
926
+ function deviceKey(target) {
927
+ return logicalIdOf(target) ?? target.deviceName ?? target.id;
928
+ }
929
+ function pageNumber(target) {
930
+ const n = Number.parseInt(target.id.slice(target.id.lastIndexOf("-") + 1), 10);
931
+ return Number.isNaN(n) ? -1 : n;
932
+ }
933
+ function newestFirst(a, b) {
934
+ return pageNumber(b) - pageNumber(a) || (b.id > a.id ? 1 : b.id < a.id ? -1 : 0);
935
+ }
936
+ var isFusebox = (t) => t.reactNative?.capabilities?.prefersFuseboxFrontend === true || t.description?.endsWith("[C++ connection]") === true;
937
+ function describeDevices(targets) {
938
+ const seen = new Map;
939
+ for (const t of targets) {
940
+ const key = deviceKey(t);
941
+ if (!seen.has(key))
942
+ seen.set(key, `${t.deviceName ?? "unknown"} (${logicalIdOf(t) ?? "no logicalDeviceId"})`);
943
+ }
944
+ return [...seen.values()].join(", ");
945
+ }
946
+ function normalizeWsUrl(wsUrl, metroUrl) {
947
+ const ws = new URL(wsUrl);
948
+ const metro = new URL(metroUrl);
949
+ ws.protocol = metro.protocol === "https:" ? "wss:" : "ws:";
950
+ ws.hostname = metro.hostname;
951
+ ws.port = metro.port;
952
+ return ws.toString();
953
+ }
954
+ function selectTarget(targets, query, metroUrl) {
955
+ if (targets.length === 0)
956
+ return unavailable("Metro has no inspector targets: is the app running a dev client?");
957
+ let candidates = [...targets];
958
+ const wanted = [
959
+ [
960
+ query.logicalDeviceId,
961
+ (t) => t.reactNative?.logicalDeviceId === query.logicalDeviceId || urlDevice(t) === query.logicalDeviceId,
962
+ "logicalDeviceId"
963
+ ],
964
+ [query.deviceName, (t) => t.deviceName === query.deviceName, "deviceName"]
965
+ ];
966
+ for (const [value, matches, label] of wanted) {
967
+ if (!value)
968
+ continue;
969
+ const filtered = candidates.filter(matches);
970
+ if (filtered.length > 0) {
971
+ candidates = filtered;
972
+ break;
973
+ }
974
+ if (new Set(candidates.map(deviceKey)).size > 1) {
975
+ return devtoolsErr("device_mismatch", `No debugger target matches ${label} "${value}". Metro serves: ${describeDevices(candidates)}`);
976
+ }
977
+ }
978
+ if (query.appId) {
979
+ const byApp = candidates.filter((t) => t.appId === query.appId);
980
+ if (byApp.length > 0)
981
+ candidates = byApp;
982
+ }
983
+ candidates.sort(newestFirst);
984
+ const fuseboxTarget = candidates.find(isFusebox);
985
+ const bridge = candidates.find((t) => t.title?.startsWith("React Native Bridge"));
986
+ const chosen = fuseboxTarget ?? bridge ?? candidates[0];
987
+ if (!chosen)
988
+ return unavailable("Metro has no inspector targets: is the app running a dev client?");
989
+ return ok({
990
+ wsUrl: normalizeWsUrl(chosen.webSocketDebuggerUrl, metroUrl),
991
+ deviceName: chosen.deviceName ?? chosen.title ?? "unknown",
992
+ appId: chosen.appId,
993
+ logicalDeviceId: logicalIdOf(chosen),
994
+ fusebox: fuseboxTarget !== undefined,
995
+ id: chosen.id,
996
+ title: chosen.title
997
+ });
998
+ }
999
+
1000
+ // ../../packages/e2e/src/devtools/session.ts
1001
+ var WARDEN_BINDING = "__warden_cb";
1002
+ var RECONNECT_BACKOFF_MS = 200;
1003
+ var FAST_RECONNECT_ATTEMPTS = 10;
1004
+ var MAX_RECONNECT_BACKOFF_MS = 1000;
1005
+ var RECONNECT_BUDGET_MS = 30000;
1006
+ var RELOAD_WAIT_MS = 5000;
1007
+ var HANDSHAKE_OPTIONAL_TIMEOUT_MS = 3000;
1008
+ var RELOAD_EXPRESSION = '(function(){var g=globalThis;if(g.expo&&typeof g.expo.reloadAppAsync==="function"){setTimeout(function(){g.expo.reloadAppAsync("warden e2e reloadJs")},0);return "expo"}var m=g.nativeModuleProxy&&g.nativeModuleProxy.DevSettings;if(m&&typeof m.reload==="function"){setTimeout(function(){m.reload()},0);return "devsettings"}return false})()';
1009
+ var timerSleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1010
+ async function connectDevtools(o) {
1011
+ const { metroUrl } = o;
1012
+ if (!metroUrl) {
1013
+ return devtoolsErr("devtools_unavailable", "no Metro URL: release builds have no devtools (start a dev client)");
1014
+ }
1015
+ const sleep = o.sleep ?? timerSleep2;
1016
+ const log = o.log ?? (() => {
1017
+ return;
1018
+ });
1019
+ const now = o.now ?? Date.now;
1020
+ const backoffMs = o.reconnectBackoffMs ?? RECONNECT_BACKOFF_MS;
1021
+ const maxBackoffMs = Math.max(backoffMs, o.maxReconnectBackoffMs ?? MAX_RECONNECT_BACKOFF_MS);
1022
+ const maxAttempts = o.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
1023
+ const budgetMs = o.reconnectBudgetMs ?? RECONNECT_BUDGET_MS;
1024
+ const backoffAfter = (attempt) => attempt <= FAST_RECONNECT_ATTEMPTS ? backoffMs : Math.min(maxBackoffMs, backoffMs * 2 ** (attempt - FAST_RECONNECT_ATTEMPTS));
1025
+ let pinnedLogicalId = o.device.logicalDeviceId;
1026
+ let state = { kind: "closed" };
1027
+ const phase = () => state.kind;
1028
+ let epoch = 0;
1029
+ const epochWaiters = new Set;
1030
+ const cdpHandlers = new Map;
1031
+ const eventHandlers = {
1032
+ reconnected: new Set,
1033
+ lost: new Set
1034
+ };
1035
+ let unsubscribeCdp = [];
1036
+ let connection = 0;
1037
+ const taps = new Set(o.taps ?? []);
1038
+ async function handshake(cdp, target) {
1039
+ const optional = { timeoutMs: HANDSHAKE_OPTIONAL_TIMEOUT_MS };
1040
+ await cdp.send("FuseboxClient.setClientMetadata", {}, optional);
1041
+ await cdp.send("ReactNativeApplication.enable", {}, optional);
1042
+ const enabled = await cdp.send("Runtime.enable");
1043
+ if (!enabled.success)
1044
+ return enabled;
1045
+ await cdp.send("Runtime.runIfWaitingForDebugger", {}, optional);
1046
+ const binding = await cdp.addBinding(WARDEN_BINDING);
1047
+ if (!binding.success)
1048
+ return binding;
1049
+ return ok({ cdp, target });
1050
+ }
1051
+ async function establish() {
1052
+ const listed = await listTargets(metroUrl, o.fetch);
1053
+ if (!listed.success)
1054
+ return listed;
1055
+ const selected = selectTarget(listed.data, { deviceName: o.device.deviceName, logicalDeviceId: pinnedLogicalId, appId: o.device.appId }, metroUrl);
1056
+ if (!selected.success)
1057
+ return selected;
1058
+ const opened = await openCdp(selected.data.wsUrl, { WebSocket: o.WebSocket, timeoutMs: o.timeoutMs });
1059
+ if (!opened.success)
1060
+ return opened;
1061
+ const connectionNumber = ++connection;
1062
+ let replay = true;
1063
+ opened.data.onAny((method, params) => {
1064
+ for (const tap of [...taps])
1065
+ tap({ method, params, connection: connectionNumber, replay });
1066
+ });
1067
+ const attached = await handshake(opened.data, selected.data);
1068
+ replay = false;
1069
+ if (!attached.success)
1070
+ opened.data.close();
1071
+ return attached;
1072
+ }
1073
+ function attach(attached) {
1074
+ state = { kind: "up", attached };
1075
+ pinnedLogicalId = attached.target.logicalDeviceId ?? pinnedLogicalId;
1076
+ for (const off of unsubscribeCdp)
1077
+ off();
1078
+ unsubscribeCdp = [];
1079
+ for (const [method, handlers] of cdpHandlers) {
1080
+ for (const handler of handlers)
1081
+ unsubscribeCdp.push(attached.cdp.on(method, handler));
1082
+ }
1083
+ attached.cdp.onClose(() => {
1084
+ if (state.kind === "up" && state.attached.cdp === attached.cdp)
1085
+ reconnect("socket closed");
1086
+ });
1087
+ attached.cdp.on("Runtime.executionContextsCleared", () => {
1088
+ if (state.kind === "up" && state.attached.cdp === attached.cdp)
1089
+ reconnect("JS context cleared");
1090
+ });
1091
+ }
1092
+ function reconnect(reason) {
1093
+ if (state.kind === "reconnecting")
1094
+ return state.done;
1095
+ if (state.kind !== "up")
1096
+ return Promise.resolve(devtoolsErr("cdp_closed", "devtools session is closed"));
1097
+ const previous = state.attached;
1098
+ log(`devtools: ${reason}, reconnecting`);
1099
+ const done = (async () => {
1100
+ previous.cdp.close();
1101
+ let last = { code: "devtools_unavailable", message: "reconnect failed" };
1102
+ const startedAt = now();
1103
+ let slept = 0;
1104
+ const leftMs = () => budgetMs - Math.max(now() - startedAt, slept);
1105
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
1106
+ if (phase() === "closed")
1107
+ return devtoolsErr("cdp_closed", "devtools session is closed");
1108
+ const result = await establish();
1109
+ if (result.success) {
1110
+ if (phase() === "closed") {
1111
+ result.data.cdp.close();
1112
+ return devtoolsErr("cdp_closed", "devtools session is closed");
1113
+ }
1114
+ attach(result.data);
1115
+ log(`devtools: reconnected to ${result.data.target.id}`);
1116
+ for (const handler of [...eventHandlers.reconnected])
1117
+ handler(result.data.target);
1118
+ return result;
1119
+ }
1120
+ last = result.error;
1121
+ const left = leftMs();
1122
+ const cap = Number.isFinite(maxAttempts) ? `/${maxAttempts}` : "";
1123
+ log(`devtools: reconnect attempt ${attempt}${cap} failed: ${last.message}`);
1124
+ if (attempt >= maxAttempts || left <= 0)
1125
+ break;
1126
+ const pause = Math.min(backoffAfter(attempt), left);
1127
+ slept += pause;
1128
+ await sleep(pause);
1129
+ }
1130
+ if (phase() !== "closed") {
1131
+ state = { kind: "lost", error: last };
1132
+ for (const handler of [...eventHandlers.lost])
1133
+ handler(last);
1134
+ }
1135
+ return { success: false, error: last };
1136
+ })();
1137
+ state = { kind: "reconnecting", done };
1138
+ epoch++;
1139
+ for (const wake of [...epochWaiters])
1140
+ wake();
1141
+ return done;
1142
+ }
1143
+ function revive() {
1144
+ log("devtools: lost, trying to attach again");
1145
+ const done = (async () => {
1146
+ const result = await establish();
1147
+ if (phase() === "closed") {
1148
+ if (result.success)
1149
+ result.data.cdp.close();
1150
+ return devtoolsErr("cdp_closed", "devtools session is closed");
1151
+ }
1152
+ if (!result.success) {
1153
+ state = { kind: "lost", error: result.error };
1154
+ return result;
1155
+ }
1156
+ attach(result.data);
1157
+ log(`devtools: reconnected to ${result.data.target.id}`);
1158
+ for (const handler of [...eventHandlers.reconnected])
1159
+ handler(result.data.target);
1160
+ return result;
1161
+ })();
1162
+ state = { kind: "reconnecting", done };
1163
+ return done;
1164
+ }
1165
+ async function current() {
1166
+ if (state.kind === "up")
1167
+ return ok(state.attached);
1168
+ if (state.kind === "reconnecting")
1169
+ return state.done;
1170
+ if (state.kind === "lost")
1171
+ return revive();
1172
+ return devtoolsErr("cdp_closed", "devtools session is closed");
1173
+ }
1174
+ const first = await establish();
1175
+ if (!first.success)
1176
+ return first;
1177
+ attach(first.data);
1178
+ log(`devtools: attached to ${first.data.target.id} (${first.data.target.fusebox ? "Fusebox" : "legacy"})`);
1179
+ const session = {
1180
+ async evaluate(expression, options = {}) {
1181
+ const live = await current();
1182
+ if (!live.success)
1183
+ return { success: false, error: { message: live.error.message, code: live.error.code } };
1184
+ const reply = await live.data.cdp.send("Runtime.evaluate", { expression, returnByValue: true, awaitPromise: options.await ?? false }, { timeoutMs: options.timeoutMs });
1185
+ if (!reply.success)
1186
+ return { success: false, error: { message: reply.error.message, code: reply.error.code } };
1187
+ const { value, exceptionDetails } = readEvaluateReply(reply.data);
1188
+ if (exceptionDetails) {
1189
+ return {
1190
+ success: false,
1191
+ error: { message: describeException(exceptionDetails), code: "eval_failed", exceptionDetails }
1192
+ };
1193
+ }
1194
+ return ok(value);
1195
+ },
1196
+ async reloadJs() {
1197
+ const live = await current();
1198
+ if (!live.success)
1199
+ return live;
1200
+ const startedAt = epoch;
1201
+ const waitMs = o.reloadWaitMs ?? RELOAD_WAIT_MS;
1202
+ const probe = await live.data.cdp.send("Runtime.evaluate", {
1203
+ expression: RELOAD_EXPRESSION,
1204
+ returnByValue: true
1205
+ });
1206
+ if (!probe.success) {
1207
+ log(`devtools: reload probe failed: ${probe.error.message}`);
1208
+ return devtoolsErr(probe.error.code, `reload probe failed: ${probe.error.message}`);
1209
+ }
1210
+ const method = readReloadMethod(probe.data) ?? "http";
1211
+ if (method === "http") {
1212
+ log("devtools: reload via http");
1213
+ try {
1214
+ const res = await o.fetch(`${metroUrl.replace(/\/+$/, "")}/reload`, { method: "POST" });
1215
+ if (!res.ok) {
1216
+ return devtoolsErr("devtools_unavailable", `reload failed: POST /reload answered ${res.status}`);
1217
+ }
1218
+ } catch (error) {
1219
+ return devtoolsErr("devtools_unavailable", `reload failed: POST /reload threw ${String(error)}`);
1220
+ }
1221
+ } else {
1222
+ log(`devtools: reload via ${method}`);
1223
+ }
1224
+ if (epoch === startedAt) {
1225
+ await new Promise((resolve) => {
1226
+ const wake = () => {
1227
+ epochWaiters.delete(wake);
1228
+ resolve();
1229
+ };
1230
+ epochWaiters.add(wake);
1231
+ sleep(waitMs).then(wake);
1232
+ });
1233
+ }
1234
+ if (epoch === startedAt && state.kind === "up") {
1235
+ log(`devtools: reload via ${method} not observed after ${waitMs} ms`);
1236
+ return devtoolsErr("reload_failed", `reload via ${method} acknowledged but the JS context did not restart within ${waitMs} ms`);
1237
+ }
1238
+ if (state.kind === "reconnecting") {
1239
+ const back = await state.done;
1240
+ if (!back.success)
1241
+ return back;
1242
+ }
1243
+ return ok({ method });
1244
+ },
1245
+ get target() {
1246
+ if (state.kind === "up")
1247
+ return state.attached.target;
1248
+ return first.data.target;
1249
+ },
1250
+ get bindingAvailable() {
1251
+ return state.kind === "up" && state.attached.cdp.bindingAvailable === true;
1252
+ },
1253
+ async send(method, params, options) {
1254
+ const live = await current();
1255
+ if (!live.success)
1256
+ return live;
1257
+ return live.data.cdp.send(method, params, options);
1258
+ },
1259
+ onEvent(tap) {
1260
+ taps.add(tap);
1261
+ return () => {
1262
+ taps.delete(tap);
1263
+ };
1264
+ },
1265
+ onCdp(method, handler) {
1266
+ let set = cdpHandlers.get(method);
1267
+ if (!set) {
1268
+ set = new Set;
1269
+ cdpHandlers.set(method, set);
1270
+ }
1271
+ set.add(handler);
1272
+ const off = state.kind === "up" ? state.attached.cdp.on(method, handler) : undefined;
1273
+ if (off)
1274
+ unsubscribeCdp.push(off);
1275
+ return () => {
1276
+ set.delete(handler);
1277
+ off?.();
1278
+ };
1279
+ },
1280
+ async evaluateWithBinding(script, requestId, timeoutMs) {
1281
+ const live = await current();
1282
+ if (!live.success)
1283
+ return live;
1284
+ return live.data.cdp.evaluateWithBinding(script, requestId, timeoutMs);
1285
+ },
1286
+ on(event, handler) {
1287
+ const handlers = eventHandlers[event];
1288
+ handlers.add(handler);
1289
+ return () => {
1290
+ handlers.delete(handler);
1291
+ };
1292
+ },
1293
+ close() {
1294
+ const previous = state;
1295
+ state = { kind: "closed" };
1296
+ if (previous.kind === "up")
1297
+ previous.attached.cdp.close();
1298
+ for (const wake of [...epochWaiters])
1299
+ wake();
1300
+ }
1301
+ };
1302
+ return ok(session);
1303
+ }
1304
+ function readReloadMethod(data) {
1305
+ if (typeof data !== "object" || data === null || !("result" in data))
1306
+ return;
1307
+ const { result } = data;
1308
+ if (typeof result !== "object" || result === null || !("value" in result))
1309
+ return;
1310
+ return result.value === "expo" || result.value === "devsettings" ? result.value : undefined;
1311
+ }
1312
+
1313
+ // ../../packages/e2e/src/devtools/tree.ts
1314
+ import { z as z6 } from "zod";
1315
+
1316
+ // ../../packages/e2e/src/devtools/tree.script.ts
1317
+ var TREE_SCRIPT = `function (cfg, g) {
1318
+ var rid = cfg.requestId;
1319
+ var MAX = typeof cfg.max === "number" && cfg.max > 0 ? Math.min(Math.floor(cfg.max), 5000) : 5000;
1320
+ var GUARD = typeof cfg.guardMs === "number" && cfg.guardMs > 0 ? cfg.guardMs : 1200;
1321
+ var ALL = cfg.all === true;
1322
+ var ONLY = typeof cfg.screen === "string" && cfg.screen !== "" ? cfg.screen : null;
1323
+ var BUDGET = 300000;
1324
+ var MAX_DEPTH = 1500;
1325
+ var MEASURE_CAP = 8000;
1326
+ var TEXT_CAP = 200;
1327
+ var HOST_NAMES = {
1328
+ RCTView: "View",
1329
+ RCTText: "Text",
1330
+ RCTVirtualText: "Text",
1331
+ RCTScrollView: "ScrollView",
1332
+ RCTImageView: "Image",
1333
+ RCTSinglelineTextInputView: "TextInput",
1334
+ RCTMultilineTextInputView: "TextInput",
1335
+ AndroidTextInput: "TextInput"
1336
+ };
1337
+ var EMPTY = {};
1338
+ var visits = 0;
1339
+ var budgetHit = false;
1340
+ var delivered = false;
1341
+ var finished = false;
1342
+ var timer = null;
1343
+ var bestScreen = null;
1344
+ var bestDepth = -1;
1345
+ var onlyFound = false;
1346
+ var hostMap = new Map();
1347
+ var hostList = [];
1348
+ var rects = [];
1349
+ var forest = [];
1350
+ var pending = 0;
1351
+
1352
+ function isObj(v) {
1353
+ return v !== null && typeof v === "object";
1354
+ }
1355
+
1356
+ function str(v) {
1357
+ return typeof v === "string" && v !== "" ? v : undefined;
1358
+ }
1359
+
1360
+ function message(e) {
1361
+ return String(e && e.message ? e.message : e);
1362
+ }
1363
+
1364
+ function deliver(result) {
1365
+ if (delivered) return;
1366
+ delivered = true;
1367
+ var cb = g[cfg.binding];
1368
+ if (typeof cb === "function") {
1369
+ cb(JSON.stringify({ requestId: rid, result: result }));
1370
+ return;
1371
+ }
1372
+ var store = g.__warden_tree_results;
1373
+ if (!isObj(store)) {
1374
+ store = {};
1375
+ g.__warden_tree_results = store;
1376
+ }
1377
+ store[rid] = JSON.stringify(result);
1378
+ }
1379
+
1380
+ function lastDisplay(style, depth) {
1381
+ if (!style || depth > 6) return undefined;
1382
+ if (Array.isArray(style)) {
1383
+ var found;
1384
+ for (var i = 0; i < style.length; i++) {
1385
+ var d = lastDisplay(style[i], depth + 1);
1386
+ if (d !== undefined) found = d;
1387
+ }
1388
+ return found;
1389
+ }
1390
+ return typeof style === "object" ? style.display : undefined;
1391
+ }
1392
+
1393
+ function isHidden(props) {
1394
+ return props.activityState === 0 ||
1395
+ props.isPageFocused === false ||
1396
+ props.freeze === true ||
1397
+ props["aria-hidden"] === true ||
1398
+ props.accessibilityElementsHidden === true ||
1399
+ props.importantForAccessibility === "no-hide-descendants" ||
1400
+ lastDisplay(props.style, 0) === "none";
1401
+ }
1402
+
1403
+ function typeName(t, depth) {
1404
+ if (typeof t === "function") return t.displayName || t.name || "";
1405
+ if (typeof t === "string") return t;
1406
+ if (isObj(t) && depth < 4) {
1407
+ return t.displayName || typeName(t.render, depth + 1) || typeName(t.type, depth + 1) || "";
1408
+ }
1409
+ return "";
1410
+ }
1411
+
1412
+ function nameOf(f) {
1413
+ var t = f.type;
1414
+ if (f.tag === 5 && typeof t === "string") {
1415
+ return Object.prototype.hasOwnProperty.call(HOST_NAMES, t) ? HOST_NAMES[t] : t;
1416
+ }
1417
+ return typeName(t, 0) || "Component";
1418
+ }
1419
+
1420
+ function rectHostOf(f) {
1421
+ if (f.tag === 5) return f;
1422
+ var cur = f.child;
1423
+ var n = 0;
1424
+ while (cur && n < 30) {
1425
+ if (cur.sibling) return null;
1426
+ if (cur.tag === 5) return cur;
1427
+ if (cur.tag === 6) return null;
1428
+ cur = cur.child;
1429
+ n++;
1430
+ }
1431
+ return null;
1432
+ }
1433
+
1434
+ function registerHost(h) {
1435
+ if (!h) return -1;
1436
+ var i = hostMap.get(h);
1437
+ if (i === undefined) {
1438
+ if (hostList.length >= MEASURE_CAP) return -1;
1439
+ i = hostList.length;
1440
+ hostList.push(h);
1441
+ hostMap.set(h, i);
1442
+ }
1443
+ return i;
1444
+ }
1445
+
1446
+ function clip(t) {
1447
+ return t.length > TEXT_CAP ? t.slice(0, TEXT_CAP) : t;
1448
+ }
1449
+
1450
+ function buildChildren(f, ctx, depth) {
1451
+ var nodes = [];
1452
+ var strings = [];
1453
+ if (depth >= MAX_DEPTH) {
1454
+ budgetHit = true;
1455
+ return { nodes: nodes, strings: strings };
1456
+ }
1457
+ var c = f.child;
1458
+ while (c) {
1459
+ var r = buildFiber(c, ctx, depth + 1);
1460
+ for (var i = 0; i < r.nodes.length; i++) nodes.push(r.nodes[i]);
1461
+ for (var j = 0; j < r.strings.length; j++) strings.push(r.strings[j]);
1462
+ c = c.sibling;
1463
+ }
1464
+ return { nodes: nodes, strings: strings };
1465
+ }
1466
+
1467
+ function buildFiber(f, ctx, depth) {
1468
+ visits++;
1469
+ if (visits > BUDGET) {
1470
+ budgetHit = true;
1471
+ return { nodes: [], strings: [] };
1472
+ }
1473
+ if (f.tag === 6) {
1474
+ var s = f.memoizedProps;
1475
+ if (typeof s === "string" || typeof s === "number") return { nodes: [], strings: [String(s)] };
1476
+ return { nodes: [], strings: [] };
1477
+ }
1478
+ var props = isObj(f.memoizedProps) ? f.memoizedProps : EMPTY;
1479
+ var route = props.route;
1480
+ var nav = props.navigation;
1481
+ var screenRoot = isObj(route) && typeof route.name === "string" && isObj(nav);
1482
+ var focused;
1483
+ if (screenRoot && typeof nav.isFocused === "function") {
1484
+ try {
1485
+ focused = nav.isFocused() === true;
1486
+ } catch (e) {
1487
+ focused = undefined;
1488
+ }
1489
+ }
1490
+ var hidden = isHidden(props) || focused === false;
1491
+ if (ONLY === null && hidden && !ALL) return { nodes: [], strings: [] };
1492
+ var inactive = ctx.inactive || (ONLY === null && hidden);
1493
+ var inScreen = ctx.inScreen;
1494
+ if (ONLY !== null && screenRoot && route.name === ONLY) {
1495
+ inScreen = true;
1496
+ onlyFound = true;
1497
+ }
1498
+ if (ONLY === null && screenRoot && focused === true && !inactive && depth > bestDepth) {
1499
+ bestDepth = depth;
1500
+ bestScreen = route.name;
1501
+ }
1502
+ var childCtx = ctx;
1503
+ if (inactive !== ctx.inactive || inScreen !== ctx.inScreen) childCtx = { inactive: inactive, inScreen: inScreen };
1504
+ var kids = buildChildren(f, childCtx, depth);
1505
+
1506
+ var type = f.type;
1507
+ var isHost = f.tag === 5 && typeof type === "string";
1508
+ var isText = isHost && (type === "RCTText" || type === "Text");
1509
+ var isVirtual = isHost && type === "RCTVirtualText";
1510
+ var testID = str(props.testID);
1511
+ var label = str(props.accessibilityLabel) || str(props["aria-label"]);
1512
+ var strings = kids.strings;
1513
+ var text;
1514
+ var outStrings = strings;
1515
+ if (isText) {
1516
+ var joined = clip(strings.join(""));
1517
+ text = joined === "" ? undefined : joined;
1518
+ outStrings = [];
1519
+ }
1520
+ var emit = (ONLY === null || inScreen) &&
1521
+ (testID !== undefined || label !== undefined || screenRoot || (isText && text !== undefined));
1522
+ if (!emit) return { nodes: kids.nodes, strings: outStrings };
1523
+ if (!isText && strings.length > 0) {
1524
+ text = clip(strings.join(isVirtual ? "" : " "));
1525
+ if (!isVirtual) outStrings = [];
1526
+ }
1527
+ var node = {
1528
+ name: nameOf(f),
1529
+ testID: testID,
1530
+ label: label,
1531
+ text: text,
1532
+ hostIdx: registerHost(rectHostOf(f)),
1533
+ kids: kids.nodes,
1534
+ inactive: inactive,
1535
+ screenRoot: screenRoot,
1536
+ rect: undefined
1537
+ };
1538
+ return { nodes: [node], strings: outStrings };
1539
+ }
1540
+
1541
+ function r1(v) {
1542
+ return Math.round(v * 10) / 10;
1543
+ }
1544
+
1545
+ function accept(i, x, y, w, h) {
1546
+ if (finished) return;
1547
+ if (typeof x !== "number" || typeof y !== "number" || typeof w !== "number" || typeof h !== "number") return;
1548
+ if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h) || w <= 0 || h <= 0) return;
1549
+ rects[i] = { x: r1(x), y: r1(y), w: r1(w), h: r1(h) };
1550
+ }
1551
+
1552
+ function done() {
1553
+ pending--;
1554
+ if (pending === 0) finish();
1555
+ }
1556
+
1557
+ function measure(i) {
1558
+ var sn = hostList[i].stateNode;
1559
+ var settled = false;
1560
+ function viaWindow(x, y, w, h) {
1561
+ if (settled) return;
1562
+ settled = true;
1563
+ accept(i, x, y, w, h);
1564
+ done();
1565
+ }
1566
+ function viaShadow(x, y, w, h, px, py) {
1567
+ if (settled) return;
1568
+ settled = true;
1569
+ accept(i, px, py, w, h);
1570
+ done();
1571
+ }
1572
+ try {
1573
+ if (isObj(sn)) {
1574
+ var pub = isObj(sn.canonical) ? sn.canonical.publicInstance : null;
1575
+ if (isObj(pub) && typeof pub.measureInWindow === "function") {
1576
+ pub.measureInWindow(viaWindow);
1577
+ return;
1578
+ }
1579
+ if (typeof sn.measureInWindow === "function") {
1580
+ sn.measureInWindow(viaWindow);
1581
+ return;
1582
+ }
1583
+ var fab = g.nativeFabricUIManager;
1584
+ if (sn.node && isObj(fab) && typeof fab.measure === "function") {
1585
+ fab.measure(sn.node, viaShadow);
1586
+ return;
1587
+ }
1588
+ var tag = typeof sn._nativeTag === "number"
1589
+ ? sn._nativeTag
1590
+ : (isObj(sn.canonical) && typeof sn.canonical.nativeTag === "number" ? sn.canonical.nativeTag : undefined);
1591
+ var proxy = g.nativeModuleProxy;
1592
+ var ui = isObj(proxy) ? proxy.UIManager : null;
1593
+ if (tag !== undefined && isObj(ui) && typeof ui.measureInWindow === "function") {
1594
+ ui.measureInWindow(tag, viaWindow);
1595
+ return;
1596
+ }
1597
+ }
1598
+ } catch (e) {
1599
+ }
1600
+ if (!settled) {
1601
+ settled = true;
1602
+ done();
1603
+ }
1604
+ }
1605
+
1606
+ function sameRect(a, b) {
1607
+ if (a === undefined || b === undefined) return a === b;
1608
+ return Math.abs(a.x - b.x) <= 1 && Math.abs(a.y - b.y) <= 1 && Math.abs(a.w - b.w) <= 1 && Math.abs(a.h - b.h) <= 1;
1609
+ }
1610
+
1611
+ function canAbsorb(p, c) {
1612
+ return !p.screenRoot &&
1613
+ !!p.inactive === !!c.inactive &&
1614
+ sameRect(p.rect, c.rect) &&
1615
+ (c.testID === undefined || c.testID === p.testID) &&
1616
+ (c.label === undefined || c.label === p.label);
1617
+ }
1618
+
1619
+ function finalize(nodes) {
1620
+ var out = [];
1621
+ for (var i = 0; i < nodes.length; i++) {
1622
+ var n = nodes[i];
1623
+ n.rect = n.hostIdx >= 0 ? rects[n.hostIdx] : undefined;
1624
+ n.kids = finalize(n.kids);
1625
+ if (n.rect === undefined && n.testID === undefined) {
1626
+ for (var k = 0; k < n.kids.length; k++) out.push(n.kids[k]);
1627
+ continue;
1628
+ }
1629
+ while (n.kids.length === 1 && canAbsorb(n, n.kids[0])) {
1630
+ var c = n.kids[0];
1631
+ if (n.testID === undefined) n.testID = c.testID;
1632
+ if (n.label === undefined) n.label = c.label;
1633
+ if (n.text === undefined) n.text = c.text;
1634
+ n.screenRoot = n.screenRoot || c.screenRoot;
1635
+ n.kids = c.kids;
1636
+ }
1637
+ out.push(n);
1638
+ }
1639
+ return out;
1640
+ }
1641
+
1642
+ var comps = [];
1643
+ var more = false;
1644
+
1645
+ function flatten(nodes, parentId) {
1646
+ for (var i = 0; i < nodes.length; i++) {
1647
+ if (comps.length >= MAX) {
1648
+ more = true;
1649
+ return;
1650
+ }
1651
+ var n = nodes[i];
1652
+ var id = comps.length;
1653
+ var c = { id: id, name: n.name, parent: parentId };
1654
+ if (n.testID !== undefined) c.testID = n.testID;
1655
+ if (n.label !== undefined) c.label = n.label;
1656
+ if (n.text !== undefined) c.text = n.text;
1657
+ if (n.rect !== undefined) c.rect = n.rect;
1658
+ if (n.inactive) c.inactive = true;
1659
+ comps.push(c);
1660
+ flatten(n.kids, id);
1661
+ }
1662
+ }
1663
+
1664
+ function finish() {
1665
+ if (finished) return;
1666
+ finished = true;
1667
+ if (timer !== null && typeof g.clearTimeout === "function") g.clearTimeout(timer);
1668
+ try {
1669
+ flatten(finalize(forest), null);
1670
+ var result = { components: comps, truncated: more || budgetHit };
1671
+ var screen = ONLY !== null ? (onlyFound ? ONLY : null) : bestScreen;
1672
+ if (screen !== null) result.screen = screen;
1673
+ deliver(result);
1674
+ } catch (e) {
1675
+ deliver({ error: message(e) });
1676
+ }
1677
+ }
1678
+
1679
+ function countFibers(start) {
1680
+ var n = 0;
1681
+ var cur = start;
1682
+ while (cur && n < 20000) {
1683
+ n++;
1684
+ if (cur.child) {
1685
+ cur = cur.child;
1686
+ continue;
1687
+ }
1688
+ while (cur && cur !== start && !cur.sibling) cur = cur.return;
1689
+ if (!cur || cur === start) break;
1690
+ cur = cur.sibling;
1691
+ }
1692
+ return n;
1693
+ }
1694
+
1695
+ try {
1696
+ var hook = g.__REACT_DEVTOOLS_GLOBAL_HOOK__;
1697
+ if (!isObj(hook)) {
1698
+ deliver({ error: "no-hook" });
1699
+ return "started";
1700
+ }
1701
+ var best = null;
1702
+ var bestCount = -1;
1703
+ var consider = function (set) {
1704
+ if (!set || typeof set.forEach !== "function") return;
1705
+ set.forEach(function (root) {
1706
+ var cur = root && root.current;
1707
+ if (!cur) return;
1708
+ var n = countFibers(cur);
1709
+ if (n > bestCount) {
1710
+ bestCount = n;
1711
+ best = cur;
1712
+ }
1713
+ });
1714
+ };
1715
+ if (typeof hook.getFiberRoots === "function") {
1716
+ if (isObj(hook.renderers) && typeof hook.renderers.forEach === "function") {
1717
+ hook.renderers.forEach(function (renderer, id) {
1718
+ consider(hook.getFiberRoots(id));
1719
+ });
1720
+ }
1721
+ if (best === null) consider(hook.getFiberRoots(1));
1722
+ }
1723
+ if (best === null) {
1724
+ deliver({ error: "no-roots" });
1725
+ return "started";
1726
+ }
1727
+ forest = buildChildren(best, { inactive: false, inScreen: false }, 0).nodes;
1728
+ if (hostList.length === 0) {
1729
+ finish();
1730
+ return "started";
1731
+ }
1732
+ pending = hostList.length + 1;
1733
+ for (var i = 0; i < hostList.length; i++) measure(i);
1734
+ done();
1735
+ if (!finished) {
1736
+ if (typeof g.setTimeout === "function") timer = g.setTimeout(finish, GUARD);
1737
+ else finish();
1738
+ }
1739
+ } catch (e) {
1740
+ deliver({ error: message(e) });
1741
+ }
1742
+ return "started";
1743
+ }`;
1744
+ function treeInstallExpression(config) {
1745
+ const globalObject = 'typeof globalThis !== "undefined" ? globalThis : (typeof global !== "undefined" ? global : this)';
1746
+ return `(${TREE_SCRIPT})(${JSON.stringify(config)}, ${globalObject})`;
1747
+ }
1748
+
1749
+ // ../../packages/e2e/src/devtools/tree.ts
1750
+ var DEFAULT_TIMEOUT_MS3 = 5000;
1751
+ var POLL_MS = 50;
1752
+ var rectSchema = z6.object({ x: z6.number(), y: z6.number(), w: z6.number(), h: z6.number() });
1753
+ var componentSchema = z6.object({
1754
+ id: z6.number(),
1755
+ name: z6.string(),
1756
+ testID: z6.string().optional(),
1757
+ label: z6.string().optional(),
1758
+ text: z6.string().optional(),
1759
+ parent: z6.number().nullable(),
1760
+ rect: rectSchema.optional(),
1761
+ inactive: z6.literal(true).optional()
1762
+ });
1763
+ var scriptResultSchema = z6.union([
1764
+ z6.object({ error: z6.string() }),
1765
+ z6.object({ screen: z6.string().optional(), components: z6.array(componentSchema), truncated: z6.boolean() })
1766
+ ]);
1767
+ var bindingPayloadSchema2 = z6.object({ requestId: z6.string(), result: z6.unknown() });
1768
+ var timerSleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1769
+ var requestCounter = 0;
1770
+ function scriptError(message) {
1771
+ if (message === "no-hook")
1772
+ return devtoolsErr("devtools_unavailable", "React DevTools hook not present (release build?)");
1773
+ if (message === "no-roots")
1774
+ return devtoolsErr("devtools_unavailable", "no React root is mounted yet");
1775
+ return devtoolsErr("eval_failed", `React tree script failed: ${message}`);
1776
+ }
1777
+ var scale = (value, ratio) => Math.round(value * ratio * 10) / 10;
1778
+ function toTree(data, pixelRatio) {
1779
+ const parsed = scriptResultSchema.safeParse(data);
1780
+ if (!parsed.success)
1781
+ return devtoolsErr("cdp_protocol", `unexpected React tree payload: ${parsed.error.message}`);
1782
+ if ("error" in parsed.data)
1783
+ return scriptError(parsed.data.error);
1784
+ const { screen, components, truncated } = parsed.data;
1785
+ const mapped = components.map(({ rect, ...rest }) => ({
1786
+ ...rest,
1787
+ ...rect ? {
1788
+ rect: {
1789
+ x: scale(rect.x, pixelRatio),
1790
+ y: scale(rect.y, pixelRatio),
1791
+ width: scale(rect.w, pixelRatio),
1792
+ height: scale(rect.h, pixelRatio)
1793
+ }
1794
+ } : {}
1795
+ }));
1796
+ return ok({ ...screen === undefined ? {} : { screen }, components: mapped, truncated });
1797
+ }
1798
+ var pollExpression = (requestId) => `(function (g) { var s = g.__warden_tree_results; var v = s && s[${JSON.stringify(requestId)}]; ` + `if (typeof v !== "string") return null; delete s[${JSON.stringify(requestId)}]; return v; })` + `(typeof globalThis !== "undefined" ? globalThis : (typeof global !== "undefined" ? global : this))`;
1799
+ async function viaPolling(session, expression, requestId, timeoutMs, deps) {
1800
+ const started = deps.now();
1801
+ const installed = await session.evaluate(expression, { timeoutMs });
1802
+ if (!installed.success)
1803
+ return devtoolsErr(installed.error.code ?? "eval_failed", installed.error.message);
1804
+ const poll = pollExpression(requestId);
1805
+ for (;; ) {
1806
+ const reply = await session.evaluate(poll, { timeoutMs });
1807
+ if (!reply.success)
1808
+ return devtoolsErr(reply.error.code ?? "eval_failed", reply.error.message);
1809
+ if (typeof reply.data === "string") {
1810
+ try {
1811
+ return ok(JSON.parse(reply.data));
1812
+ } catch {
1813
+ return devtoolsErr("cdp_protocol", "React tree payload is not JSON");
1814
+ }
1815
+ }
1816
+ if (deps.now() - started >= timeoutMs) {
1817
+ return devtoolsErr("cdp_timeout", `the app did not report its React tree within ${timeoutMs} ms`);
1818
+ }
1819
+ await deps.sleep(POLL_MS);
1820
+ }
1821
+ }
1822
+ async function viaBinding(session, expression, requestId, timeoutMs) {
1823
+ const reply = await session.evaluateWithBinding(expression, requestId, timeoutMs);
1824
+ if (!reply.success)
1825
+ return reply.error.code === "binding_unavailable" ? undefined : reply;
1826
+ const tagged = bindingPayloadSchema2.safeParse(reply.data);
1827
+ return tagged.success ? ok(tagged.data.result) : devtoolsErr("cdp_protocol", "unexpected React tree binding payload");
1828
+ }
1829
+ async function fetchTree(session, options = {}, deps = {}) {
1830
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
1831
+ const resolved = { sleep: deps.sleep ?? timerSleep3, now: deps.now ?? Date.now };
1832
+ requestCounter += 1;
1833
+ const requestId = `tree-${Date.now().toString(36)}-${requestCounter}`;
1834
+ const config = {
1835
+ requestId,
1836
+ binding: WARDEN_BINDING,
1837
+ ...options.max === undefined ? {} : { max: options.max },
1838
+ ...options.screen === undefined ? {} : { screen: options.screen },
1839
+ ...options.all === undefined ? {} : { all: options.all }
1840
+ };
1841
+ const expression = treeInstallExpression(config);
1842
+ const bound = session.bindingAvailable ? await viaBinding(session, expression, requestId, timeoutMs) : undefined;
1843
+ const payload = bound ?? await viaPolling(session, expression, requestId, timeoutMs, resolved);
1844
+ if (!payload.success)
1845
+ return payload;
1846
+ return toTree(payload.data, options.pixelRatio ?? 1);
1847
+ }
1848
+
1849
+ // ../../packages/e2e/src/devtools/facet.ts
1850
+ async function openDevtoolsFacet(o) {
1851
+ const metroUrl = o.env.metroUrl;
1852
+ if (!metroUrl) {
1853
+ return devtoolsErr("devtools_unavailable", "no Metro URL (WARDEN_METRO_URL): release builds have no devtools");
1854
+ }
1855
+ const consoleCapture = createConsoleCapture({ ring: o.consoleRing, sink: o.consoleSink });
1856
+ const connected = await connectDevtools({
1857
+ metroUrl,
1858
+ device: o.device,
1859
+ fetch: o.fetch,
1860
+ WebSocket: o.WebSocket,
1861
+ log: o.log,
1862
+ sleep: o.sleep,
1863
+ timeoutMs: o.timeoutMs,
1864
+ taps: [consoleCapture.handle]
1865
+ });
1866
+ if (!connected.success)
1867
+ return connected;
1868
+ const session = connected.data;
1869
+ const attached = await attachNetwork(session, {
1870
+ ring: o.networkRing,
1871
+ metroUrl,
1872
+ includeBodies: o.includeBodies,
1873
+ pollMs: o.pollMs,
1874
+ sleep: o.sleep,
1875
+ sink: o.networkSink
1876
+ });
1877
+ if (!attached.success) {
1878
+ session.close();
1879
+ return attached;
1880
+ }
1881
+ const network = attached.data;
1882
+ return ok({
1883
+ evaluate: (expression, options) => session.evaluate(expression, options),
1884
+ reloadJs: () => session.reloadJs(),
1885
+ console: {
1886
+ lines: consoleCapture.lines,
1887
+ clear: consoleCapture.clear,
1888
+ mark: consoleCapture.mark
1889
+ },
1890
+ network: { entries: network.entries, clear: network.clear },
1891
+ tree: (options) => fetchTree(session, options, { sleep: o.sleep }),
1892
+ get target() {
1893
+ return session.target;
1894
+ },
1895
+ on: session.on,
1896
+ async attachArtifacts(dir) {
1897
+ const paths = { console: join(dir, "console.jsonl"), network: join(dir, "network.jsonl") };
1898
+ try {
1899
+ await consoleCapture.writeJsonl(paths.console);
1900
+ await network.writeJsonl(paths.network);
1901
+ } catch (error) {
1902
+ return devtoolsErr("io_failed", `could not write devtools artifacts to ${dir}: ${String(error)}`);
1903
+ }
1904
+ return ok(paths);
1905
+ },
1906
+ close() {
1907
+ network.stop();
1908
+ session.close();
1909
+ }
1910
+ });
1911
+ }
1912
+
1913
+ // ../../packages/e2e/src/driver/span-detail.ts
1914
+ import { AsyncLocalStorage } from "async_hooks";
1915
+ var collectors = new AsyncLocalStorage;
1916
+ function collectSpanDetail(notes, fn) {
1917
+ return collectors.run(notes, fn);
1918
+ }
1919
+ function noteSpanDetail(key, value) {
1920
+ const notes = collectors.getStore();
1921
+ if (notes !== undefined)
1922
+ notes[key] = value;
1923
+ }
1924
+ function addSpanDetail(key, amount) {
1925
+ const notes = collectors.getStore();
1926
+ if (notes === undefined)
1927
+ return;
1928
+ const before = notes[key];
1929
+ notes[key] = (typeof before === "number" ? before : 0) + amount;
1930
+ }
1931
+
1932
+ // ../../packages/e2e/src/driver/observation-cache.ts
1933
+ var OBSERVATION_MAX_AGE_MS = { ios: 150, android: 1000 };
1934
+ function canObserve(driver) {
1935
+ return typeof driver.observe === "function";
1936
+ }
1937
+ function canDetectAlerts(driver) {
1938
+ return typeof driver.alertIn === "function";
1939
+ }
1940
+ var READS = new Set([
1941
+ "health",
1942
+ "snapshot",
1943
+ "elementAtPoint",
1944
+ "screenshot",
1945
+ "alert",
1946
+ "alertOn",
1947
+ "alertIn",
1948
+ "isFrontmost",
1949
+ "isInstalled",
1950
+ "isRunning",
1951
+ "observe",
1952
+ "invalidateObservations",
1953
+ "previous",
1954
+ "peek"
1955
+ ]);
1956
+ var READS_WITHOUT_ARGUMENT = new Set(["orientation", "appearance", "clipboard"]);
1957
+ function isCall(value) {
1958
+ return typeof value === "function";
1959
+ }
1960
+ function isThenable(value) {
1961
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
1962
+ }
1963
+ function isWholeScreen(options) {
1964
+ return options === undefined || options.interactiveOnly !== true && options.backend === undefined;
1965
+ }
1966
+ function cacheObservations(driver, options = {}) {
1967
+ const now = options.now ?? Date.now;
1968
+ const maxAgeMs = options.maxAgeMs ?? OBSERVATION_MAX_AGE_MS[driver.platform];
1969
+ let generation = 0;
1970
+ let kept;
1971
+ let lastInputAt;
1972
+ const moveOn = () => {
1973
+ generation += 1;
1974
+ kept = undefined;
1975
+ };
1976
+ const inputEdge = () => {
1977
+ moveOn();
1978
+ lastInputAt = now();
1979
+ };
1980
+ const previous = () => ({
1981
+ snapshot: kept !== undefined && kept.generation === generation ? kept.snapshot : undefined,
1982
+ generation,
1983
+ sinceInputMs: lastInputAt === undefined ? undefined : now() - lastInputAt
1984
+ });
1985
+ const peek = () => kept !== undefined && kept.generation === generation ? { snapshot: kept.snapshot, ageMs: now() - kept.at } : undefined;
1986
+ const usable = () => kept !== undefined && kept.generation === generation && now() - kept.at < maxAgeMs ? kept : undefined;
1987
+ const keep = (snapshot2, startedIn) => {
1988
+ if (startedIn !== generation)
1989
+ return;
1990
+ kept = { snapshot: snapshot2, generation, at: now(), alertRead: false };
1991
+ return kept;
1992
+ };
1993
+ const observe = async ({ reuse }) => {
1994
+ const hit = reuse ? usable() : undefined;
1995
+ if (hit !== undefined) {
1996
+ noteSpanDetail("observation", "cached");
1997
+ return { success: true, data: hit.snapshot };
1998
+ }
1999
+ const startedIn = generation;
2000
+ const taken = await driver.snapshot();
2001
+ if (taken.success)
2002
+ keep(taken.data, startedIn);
2003
+ return taken;
2004
+ };
2005
+ const snapshot = async (opts) => {
2006
+ const startedIn = generation;
2007
+ const taken = await driver.snapshot(opts);
2008
+ if (taken.success && isWholeScreen(opts))
2009
+ keep(taken.data, startedIn);
2010
+ return taken;
2011
+ };
2012
+ const alertFromSnapshot = (reader) => {
2013
+ return async () => {
2014
+ const hit = usable();
2015
+ if (hit !== undefined && !hit.alertRead) {
2016
+ hit.alertRead = true;
2017
+ return { success: true, data: reader.alertIn(hit.snapshot) };
2018
+ }
2019
+ const startedIn = generation;
2020
+ const taken = await driver.snapshot();
2021
+ if (!taken.success)
2022
+ return taken;
2023
+ const fresh = keep(taken.data, startedIn);
2024
+ if (fresh !== undefined)
2025
+ fresh.alertRead = true;
2026
+ return { success: true, data: reader.alertIn(taken.data) };
2027
+ };
2028
+ };
2029
+ const asInput = (fn, target) => {
2030
+ return (...args) => {
2031
+ inputEdge();
2032
+ let result;
2033
+ try {
2034
+ result = Reflect.apply(fn, target, args);
2035
+ } catch (error) {
2036
+ inputEdge();
2037
+ throw error;
2038
+ }
2039
+ if (!isThenable(result)) {
2040
+ inputEdge();
2041
+ return result;
2042
+ }
2043
+ return Promise.resolve(result).finally(inputEdge);
2044
+ };
2045
+ };
2046
+ const wrapMember = (name, value, target) => {
2047
+ if (name === "app" && typeof value === "object" && value !== null)
2048
+ return wrap(value, {});
2049
+ if (!isCall(value) || READS.has(name))
2050
+ return value;
2051
+ const input = asInput(value, target);
2052
+ if (!READS_WITHOUT_ARGUMENT.has(name))
2053
+ return input;
2054
+ return (...args) => args[0] === undefined ? Reflect.apply(value, target, args) : input(...args);
2055
+ };
2056
+ const wrap = (target, own2) => {
2057
+ const wrapped = new Map;
2058
+ return new Proxy(target, {
2059
+ get(object, property) {
2060
+ const value = Reflect.get(object, property, object);
2061
+ if (typeof property !== "string")
2062
+ return value;
2063
+ if (property in own2)
2064
+ return own2[property];
2065
+ const cached = wrapped.get(property);
2066
+ if (cached !== undefined && cached.source === value)
2067
+ return cached.wrapper;
2068
+ const wrapper = wrapMember(property, value, object);
2069
+ wrapped.set(property, { source: value, wrapper });
2070
+ return wrapper;
2071
+ }
2072
+ });
2073
+ };
2074
+ const own = { observe, invalidateObservations: moveOn, previous, peek, snapshot };
2075
+ if (canDetectAlerts(driver))
2076
+ own.alert = alertFromSnapshot(driver);
2077
+ return wrap(driver, own);
2078
+ }
2079
+
2080
+ // ../../packages/e2e/src/runner/span.ts
2081
+ function codeOf(error) {
2082
+ if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
2083
+ return error.code;
2084
+ }
2085
+ return "ERROR";
2086
+ }
2087
+ function writeSpan(sink, span, outcome) {
2088
+ try {
2089
+ const { ok: ok2, code, extra } = outcome();
2090
+ const detail = { ...span.notes, ...extra };
2091
+ sink.write({
2092
+ type: "span",
2093
+ ts: span.ts,
2094
+ op: span.op,
2095
+ ms: span.ms,
2096
+ ok: ok2,
2097
+ ...code === undefined ? {} : { code },
2098
+ ...Object.keys(detail).length === 0 ? {} : { detail }
2099
+ });
2100
+ } catch {}
2101
+ }
2102
+ async function runSpan(sink, op, fn, options = {}) {
2103
+ if (sink === undefined)
2104
+ return fn();
2105
+ const now = options.now ?? Date.now;
2106
+ const notes = {};
2107
+ const ts = now();
2108
+ let value;
2109
+ try {
2110
+ value = await collectSpanDetail(notes, fn);
2111
+ } catch (error) {
2112
+ writeSpan(sink, { op, ts, ms: now() - ts, notes }, () => ({ ok: false, code: codeOf(error) }));
2113
+ throw error;
2114
+ }
2115
+ writeSpan(sink, { op, ts, ms: now() - ts, notes }, () => ({
2116
+ ...options.verdict?.(value) ?? { ok: true },
2117
+ extra: options.detail?.(value)
2118
+ }));
2119
+ return value;
2120
+ }
2121
+
2122
+ // ../../packages/e2e/src/driver/traced-driver.ts
2123
+ var UNTRACED = new Set(["close"]);
2124
+ function isThenable2(value) {
2125
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
2126
+ }
2127
+ function outcomeOf(value) {
2128
+ if (typeof value !== "object" || value === null || !("success" in value) || value.success !== false) {
2129
+ return { ok: true };
2130
+ }
2131
+ const error = "error" in value ? value.error : undefined;
2132
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
2133
+ return typeof code === "string" ? { ok: false, code } : { ok: false };
2134
+ }
2135
+ function traceDriver(driver, options) {
2136
+ const now = options.now ?? Date.now;
2137
+ const wrap = (target, prefix) => {
2138
+ const wrapped = new Map;
2139
+ return new Proxy(target, {
2140
+ get(object, property) {
2141
+ const value = Reflect.get(object, property, object);
2142
+ if (typeof property !== "string")
2143
+ return value;
2144
+ const cached = wrapped.get(property);
2145
+ if (cached !== undefined && cached.source === value)
2146
+ return cached.wrapper;
2147
+ if (property === "app" && typeof value === "object" && value !== null) {
2148
+ const app = wrap(value, `${prefix}app.`);
2149
+ wrapped.set(property, { source: value, wrapper: app });
2150
+ return app;
2151
+ }
2152
+ if (typeof value !== "function" || UNTRACED.has(property))
2153
+ return value;
2154
+ const call = (...args) => {
2155
+ const sink = options.trace();
2156
+ if (sink === undefined)
2157
+ return Reflect.apply(value, object, args);
2158
+ const span = { op: `${prefix}${property}`, ts: now(), ms: 0, notes: {} };
2159
+ const end = () => ({ ...span, ms: now() - span.ts });
2160
+ let result;
2161
+ try {
2162
+ result = collectSpanDetail(span.notes, () => Reflect.apply(value, object, args));
2163
+ } catch (error) {
2164
+ writeSpan(sink, end(), () => ({ ok: false, code: codeOf(error) }));
2165
+ throw error;
2166
+ }
2167
+ if (!isThenable2(result))
2168
+ return result;
2169
+ return Promise.resolve(result).then((resolved) => {
2170
+ writeSpan(sink, end(), () => outcomeOf(resolved));
2171
+ return resolved;
2172
+ }, (error) => {
2173
+ writeSpan(sink, end(), () => ({ ok: false, code: codeOf(error) }));
2174
+ throw error;
2175
+ });
2176
+ };
2177
+ wrapped.set(property, { source: value, wrapper: call });
2178
+ return call;
2179
+ }
2180
+ });
2181
+ };
2182
+ return wrap(driver, "driver.");
2183
+ }
2184
+
2185
+ // ../../packages/e2e/src/runner/results.ts
2186
+ import { appendFileSync, mkdirSync } from "fs";
2187
+ import { dirname as dirname2 } from "path";
2188
+ import { z as z7 } from "zod";
2189
+ var testStatusSchema = z7.enum(["passed", "failed", "skipped", "timed-out"]);
2190
+ var testResultSchema = z7.object({
2191
+ file: z7.string(),
2192
+ title: z7.array(z7.string()),
2193
+ status: testStatusSchema,
2194
+ durationMs: z7.number(),
2195
+ startedAt: z7.number(),
2196
+ only: z7.boolean().optional(),
2197
+ error: z7.object({
2198
+ message: z7.string(),
2199
+ code: z7.string().optional(),
2200
+ stack: z7.string().optional()
2201
+ }).optional(),
2202
+ artifacts: z7.object({ dir: z7.string(), files: z7.array(z7.string()) }).optional(),
2203
+ device: z7.object({
2204
+ udid: z7.string(),
2205
+ platform: z7.enum(["ios", "android"]),
2206
+ name: z7.string().optional()
2207
+ })
2208
+ });
2209
+ function openResults(path) {
2210
+ let ready = false;
2211
+ return {
2212
+ path,
2213
+ append(result) {
2214
+ if (!ready) {
2215
+ mkdirSync(dirname2(path), { recursive: true });
2216
+ ready = true;
2217
+ }
2218
+ appendFileSync(path, `${JSON.stringify(result)}
2219
+ `);
2220
+ }
2221
+ };
2222
+ }
2223
+
2224
+ // ../../packages/e2e/src/runner/step.ts
2225
+ function abortReason(signal) {
2226
+ return signal.reason ?? new Error("aborted");
2227
+ }
2228
+ function aborted(signal) {
2229
+ let dispose = () => {
2230
+ return;
2231
+ };
2232
+ const promise = new Promise((_, reject) => {
2233
+ if (signal.aborted) {
2234
+ reject(abortReason(signal));
2235
+ return;
2236
+ }
2237
+ const onAbort = () => reject(abortReason(signal));
2238
+ signal.addEventListener("abort", onAbort, { once: true });
2239
+ dispose = () => signal.removeEventListener("abort", onAbort);
2240
+ });
2241
+ promise.catch(() => {
2242
+ return;
2243
+ });
2244
+ return { promise, dispose };
2245
+ }
2246
+ async function runStep(attempt, kind, label, fn, now = Date.now) {
2247
+ if (attempt === undefined)
2248
+ return fn();
2249
+ if (attempt.signal.aborted)
2250
+ throw abortReason(attempt.signal);
2251
+ const startedAt = now();
2252
+ attempt.trace.write({ type: "step-start", ts: startedAt, kind, label });
2253
+ attempt.depth += 1;
2254
+ const abort = aborted(attempt.signal);
2255
+ try {
2256
+ const value = await Promise.race([fn(), abort.promise]);
2257
+ attempt.depth -= 1;
2258
+ attempt.trace.write({ type: "step-end", ts: now(), kind, label, status: "passed", ms: now() - startedAt });
2259
+ return value;
2260
+ } catch (error) {
2261
+ attempt.depth -= 1;
2262
+ attempt.trace.write({
2263
+ type: "step-end",
2264
+ ts: now(),
2265
+ kind,
2266
+ label,
2267
+ status: "failed",
2268
+ ms: now() - startedAt,
2269
+ error: error instanceof Error ? error.message : String(error)
2270
+ });
2271
+ throw error;
2272
+ } finally {
2273
+ abort.dispose();
2274
+ }
2275
+ }
2276
+
2277
+ // ../../packages/e2e/src/session.ts
2278
+ function devtoolsTarget(env) {
2279
+ return { appId: env.bundleId, ...env.deviceName === undefined ? {} : { deviceName: env.deviceName } };
2280
+ }
2281
+ var openDevtools = (env) => openDevtoolsFacet({
2282
+ env: { metroUrl: env.metroUrl },
2283
+ device: devtoolsTarget(env),
2284
+ fetch,
2285
+ WebSocket,
2286
+ timeoutMs: 5000,
2287
+ log: (line) => process.stderr.write(`[devtools] ${line}
2288
+ `)
2289
+ });
2290
+ var SESSION_KEY = Symbol.for("@delacour/warden-e2e/session");
2291
+ var slot = () => globalThis;
2292
+ var defaultFactory;
2293
+ var defaultFixtures;
2294
+ function setDefaultDriverFactory(factory) {
2295
+ defaultFactory = factory;
2296
+ }
2297
+ function hasDefaultDriverFactory() {
2298
+ return defaultFactory !== undefined;
2299
+ }
2300
+ function setDefaultFixtureFactory(factory) {
2301
+ defaultFixtures = factory;
2302
+ }
2303
+ function hasDefaultFixtureFactory() {
2304
+ return defaultFixtures !== undefined;
2305
+ }
2306
+ var missingFactory = async () => ({
2307
+ success: false,
2308
+ error: {
2309
+ code: "E_INTERNAL",
2310
+ message: "no driver factory configured (setDefaultDriverFactory was never called)",
2311
+ retryable: false
2312
+ }
2313
+ });
2314
+ function writeLine(line) {
2315
+ try {
2316
+ writeSync(1, `${line}
2317
+ `);
2318
+ } catch {}
2319
+ }
2320
+ function resolveEnv(env) {
2321
+ if (env !== undefined)
2322
+ return env;
2323
+ const read = readEnv(process.env);
2324
+ if (!read.success)
2325
+ throw new E2eError("SESSION", `invalid warden e2e environment:
2326
+ ${read.error}`);
2327
+ if (read.data === undefined)
2328
+ throw new E2eError("SESSION", "not running under warden: WARDEN_UDID is not set");
2329
+ return read.data;
2330
+ }
2331
+ function createSession(opts = {}) {
2332
+ const resolved = resolveEnv(opts.env);
2333
+ const factory = opts.driverFactory ?? defaultFactory ?? missingFactory;
2334
+ const out = resolve(resolved.out);
2335
+ mkdirSync2(out, { recursive: true });
2336
+ const results = openResults(resolve(out, "results.jsonl"));
2337
+ const idle = new AbortController().signal;
2338
+ let opened;
2339
+ let current;
2340
+ let closing;
2341
+ let devtoolsOpening;
2342
+ let facet;
2343
+ const openFacet = opts.devtools ?? openDevtools;
2344
+ let attempt;
2345
+ const session = {
2346
+ env: resolved,
2347
+ out,
2348
+ results,
2349
+ fixtures: opts.fixtures ?? defaultFixtures,
2350
+ log: opts.log ?? writeLine,
2351
+ get attempt() {
2352
+ return attempt;
2353
+ },
2354
+ get signal() {
2355
+ return attempt?.signal ?? idle;
2356
+ },
2357
+ get attemptDir() {
2358
+ return attempt?.dir;
2359
+ },
2360
+ get trace() {
2361
+ return attempt?.trace;
2362
+ },
2363
+ driver() {
2364
+ if (closing !== undefined) {
2365
+ return Promise.reject(new E2eError("SESSION", "session is closed"));
2366
+ }
2367
+ opened ??= factory(resolved).then((result) => {
2368
+ if (!result.success) {
2369
+ opened = undefined;
2370
+ const { code, message } = result.error;
2371
+ throw new E2eError("DRIVER", `${code}: ${message}`);
2372
+ }
2373
+ current = cacheObservations(traceDriver(result.data, { trace: () => attempt?.trace }));
2374
+ return current;
2375
+ }, (cause) => {
2376
+ opened = undefined;
2377
+ throw new E2eError("DRIVER", cause instanceof Error ? cause.message : String(cause));
2378
+ });
2379
+ return opened;
2380
+ },
2381
+ peekDriver() {
2382
+ return current;
2383
+ },
2384
+ devtools() {
2385
+ if (closing !== undefined)
2386
+ return Promise.resolve(devtoolsErr("devtools_unavailable", "session is closed"));
2387
+ if (resolved.metroUrl === undefined) {
2388
+ return Promise.resolve(devtoolsErr("devtools_unavailable", "no Metro URL (WARDEN_METRO_URL): release builds have no devtools"));
2389
+ }
2390
+ if (devtoolsOpening !== undefined)
2391
+ return devtoolsOpening;
2392
+ const opening = openFacet(resolved).then((result) => {
2393
+ if (!result.success) {
2394
+ if (devtoolsOpening === opening)
2395
+ devtoolsOpening = undefined;
2396
+ return result;
2397
+ }
2398
+ const opened2 = result.data;
2399
+ facet = opened2;
2400
+ opened2.on("lost", () => {
2401
+ if (facet !== opened2)
2402
+ return;
2403
+ facet = undefined;
2404
+ if (devtoolsOpening === opening)
2405
+ devtoolsOpening = undefined;
2406
+ opened2.close();
2407
+ });
2408
+ return result;
2409
+ }, (cause) => {
2410
+ if (devtoolsOpening === opening)
2411
+ devtoolsOpening = undefined;
2412
+ return devtoolsErr("devtools_unavailable", cause instanceof Error ? cause.message : String(cause));
2413
+ });
2414
+ devtoolsOpening = opening;
2415
+ return opening;
2416
+ },
2417
+ peekDevtools() {
2418
+ return facet;
2419
+ },
2420
+ step(kind, label, fn) {
2421
+ return runStep(attempt, kind, label, fn);
2422
+ },
2423
+ span(op, fn, options) {
2424
+ return runSpan(attempt?.trace, op, fn, options);
2425
+ },
2426
+ enterAttempt(next, replacing) {
2427
+ if (replacing !== undefined && attempt !== replacing)
2428
+ return;
2429
+ attempt = next;
2430
+ },
2431
+ leaveAttempt(leaving) {
2432
+ if (leaving !== undefined && attempt !== leaving)
2433
+ return;
2434
+ attempt = undefined;
2435
+ },
2436
+ close() {
2437
+ closing ??= (async () => {
2438
+ if (opened !== undefined) {
2439
+ await opened.then((d) => d.close(), () => {
2440
+ return;
2441
+ });
2442
+ }
2443
+ current = undefined;
2444
+ if (devtoolsOpening !== undefined) {
2445
+ await devtoolsOpening.then((result) => result.success ? result.data.close() : undefined, () => {
2446
+ return;
2447
+ });
2448
+ }
2449
+ facet = undefined;
2450
+ if (slot()[SESSION_KEY] === session)
2451
+ delete slot()[SESSION_KEY];
2452
+ if (exitListener !== undefined)
2453
+ process.off("exit", exitListener);
2454
+ })();
2455
+ return closing;
2456
+ }
2457
+ };
2458
+ const exitListener = opts.exitHook === false ? undefined : () => {
2459
+ session.close();
2460
+ };
2461
+ if (exitListener !== undefined)
2462
+ process.on("exit", exitListener);
2463
+ return session;
2464
+ }
2465
+ function attemptSession(session, binding) {
2466
+ const context = () => binding().context;
2467
+ const trace = () => binding().finished ? undefined : context().trace;
2468
+ return {
2469
+ get env() {
2470
+ return session.env;
2471
+ },
2472
+ get out() {
2473
+ return session.out;
2474
+ },
2475
+ get results() {
2476
+ return session.results;
2477
+ },
2478
+ get fixtures() {
2479
+ return session.fixtures;
2480
+ },
2481
+ set fixtures(factory) {
2482
+ session.fixtures = factory;
2483
+ },
2484
+ get log() {
2485
+ return session.log;
2486
+ },
2487
+ get attempt() {
2488
+ return binding().finished ? undefined : context();
2489
+ },
2490
+ get signal() {
2491
+ return context().signal;
2492
+ },
2493
+ get attemptDir() {
2494
+ return context().dir;
2495
+ },
2496
+ get trace() {
2497
+ return trace();
2498
+ },
2499
+ driver: () => session.driver(),
2500
+ peekDriver: () => session.peekDriver(),
2501
+ devtools: () => session.devtools(),
2502
+ peekDevtools: () => session.peekDevtools(),
2503
+ step(kind, label, fn) {
2504
+ return runStep(context(), kind, label, fn);
2505
+ },
2506
+ span(op, fn, options) {
2507
+ return runSpan(trace(), op, fn, options);
2508
+ },
2509
+ enterAttempt: (next, replacing) => session.enterAttempt(next, replacing),
2510
+ leaveAttempt: (leaving) => session.leaveAttempt(leaving),
2511
+ close: () => session.close()
2512
+ };
2513
+ }
2514
+ function getSession(opts = {}) {
2515
+ const existing = slot()[SESSION_KEY];
2516
+ if (existing !== undefined)
2517
+ return existing;
2518
+ const session = createSession(opts);
2519
+ slot()[SESSION_KEY] = session;
2520
+ return session;
2521
+ }
2522
+
2523
+ // ../../packages/e2e/src/runner/callsite.ts
2524
+ import { join as join2 } from "path";
2525
+ var OWN_FILES = new Set(["callsite.ts", "scope.ts", "test.ts", "attempt.ts"].map((name) => join2(import.meta.dir, name)).concat(join2(import.meta.dir, "..", "index.ts")));
2526
+ var FRAME = /(?:\(|\s)((?:file:\/\/)?\/[^\s()]+?):\d+(?::\d+)?\)?\s*$/;
2527
+ function callerFile(stack = new Error().stack) {
2528
+ for (const line of (stack ?? "").split(`
2529
+ `)) {
2530
+ const raw = FRAME.exec(line)?.[1];
2531
+ if (raw === undefined)
2532
+ continue;
2533
+ const path = raw.startsWith("file://") ? raw.slice("file://".length) : raw;
2534
+ if (OWN_FILES.has(path) || path.includes("/node_modules/"))
2535
+ continue;
2536
+ return path;
2537
+ }
2538
+ return "unknown";
2539
+ }
2540
+
2541
+ // ../../packages/e2e/src/runner/scope.ts
2542
+ var BUN_TIMEOUT_MARGIN_MS = 30000;
2543
+ function newScope(title, parent, serial) {
2544
+ return { title, parent, use: {}, beforeEach: [], afterEach: [], serial };
2545
+ }
2546
+ var roots = new Map;
2547
+ var stack = [];
2548
+ function rootFor(file) {
2549
+ let root = roots.get(file);
2550
+ if (root === undefined) {
2551
+ root = newScope([], undefined, false);
2552
+ roots.set(file, root);
2553
+ }
2554
+ return root;
2555
+ }
2556
+ function currentScope() {
2557
+ return stack[stack.length - 1] ?? rootFor(callerFile());
2558
+ }
2559
+ function createScope(title, serial) {
2560
+ const parent = currentScope();
2561
+ return newScope([...parent.title, title], parent, serial);
2562
+ }
2563
+ function runInScope(scope, body) {
2564
+ stack.push(scope);
2565
+ try {
2566
+ body();
2567
+ } finally {
2568
+ stack.pop();
2569
+ }
2570
+ }
2571
+ function scopeChain(scope) {
2572
+ const chain = [];
2573
+ for (let s = scope;s !== undefined; s = s.parent)
2574
+ chain.unshift(s);
2575
+ return chain;
2576
+ }
2577
+ function hooksFor(scope, kind) {
2578
+ const chain = scopeChain(scope);
2579
+ if (kind === "afterEach")
2580
+ chain.reverse();
2581
+ return chain.flatMap((s) => s[kind]);
2582
+ }
2583
+ function resolveUse(scope, defaults, testTimeout) {
2584
+ const resolved = { ...defaults, launch: "fresh", systemAlerts: "off" };
2585
+ for (const s of scopeChain(scope)) {
2586
+ if (s.use.timeout !== undefined)
2587
+ resolved.timeoutMs = s.use.timeout;
2588
+ if (s.use.actionTimeout !== undefined)
2589
+ resolved.actionTimeoutMs = s.use.actionTimeout;
2590
+ if (s.use.assertionTimeout !== undefined)
2591
+ resolved.assertionTimeoutMs = s.use.assertionTimeout;
2592
+ if (s.use.launch !== undefined)
2593
+ resolved.launch = s.use.launch;
2594
+ if (s.use.systemAlerts !== undefined)
2595
+ resolved.systemAlerts = s.use.systemAlerts;
2596
+ }
2597
+ if (testTimeout !== undefined)
2598
+ resolved.timeoutMs = testTimeout;
2599
+ return resolved;
2600
+ }
2601
+ function registrationDefaults(env = process.env) {
2602
+ const read = readEnv(env);
2603
+ const parsed = read.success ? read.data : undefined;
2604
+ return {
2605
+ timeoutMs: parsed?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
2606
+ actionTimeoutMs: parsed?.actionTimeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS,
2607
+ assertionTimeoutMs: parsed?.assertionTimeoutMs ?? DEFAULT_ASSERTION_TIMEOUT_MS,
2608
+ platform: parsed?.platform
2609
+ };
2610
+ }
2611
+ var ONLY_KEY = Symbol.for("@delacour/warden-e2e/only");
2612
+ function markOnly() {
2613
+ globalThis[ONLY_KEY] = true;
2614
+ }
2615
+ function onlyWasSeen() {
2616
+ return globalThis[ONLY_KEY] === true;
2617
+ }
2618
+
2619
+ // ../../packages/e2e/src/version.ts
2620
+ var WORKSPACE_VERSION = "0.0.0-workspace";
2621
+ var WARDEN_E2E_VERSION = "0.1.1";
2622
+
2623
+ // ../../packages/e2e/src/driver/settle.ts
2624
+ var isVia = (value) => value === "stream" || value === "snapshot" || value === "hash";
2625
+ function settleDetailOf(outcome) {
2626
+ const reply = { ...outcome };
2627
+ const { via, reason, frontmostPid } = reply;
2628
+ return {
2629
+ quiet: outcome.quiet,
2630
+ waitedMs: outcome.waitedMs,
2631
+ ...isVia(via) ? { via } : {},
2632
+ ...typeof reason === "string" ? { reason } : {},
2633
+ ...typeof frontmostPid === "number" ? { frontmostPid } : {}
2634
+ };
2635
+ }
2636
+ var DEFAULT_SETTLE_QUIET_MS = 250;
2637
+ var DEFAULT_SETTLE_TIMEOUT_MS = 3000;
2638
+ var DEFAULT_POLL_MS2 = 50;
2639
+ async function settleByHash(poll, deps) {
2640
+ const { quietMs, timeoutMs, now, sleep } = deps;
2641
+ const pollMs = deps.pollMs ?? DEFAULT_POLL_MS2;
2642
+ const start = now();
2643
+ let previous = await poll();
2644
+ let lastChange = now();
2645
+ for (;; ) {
2646
+ const elapsed = now() - start;
2647
+ if (elapsed >= timeoutMs)
2648
+ return { quiet: false, waitedMs: elapsed };
2649
+ await sleep(Math.min(pollMs, Math.max(0, timeoutMs - elapsed)));
2650
+ const hash = await poll();
2651
+ const t = now();
2652
+ if (hash !== previous) {
2653
+ previous = hash;
2654
+ lastChange = t;
2655
+ } else if (t - lastChange >= quietMs) {
2656
+ return { quiet: true, waitedMs: t - start };
2657
+ }
2658
+ if (t - start >= timeoutMs)
2659
+ return { quiet: false, waitedMs: t - start };
2660
+ }
2661
+ }
2662
+ function tuple(element) {
2663
+ const { x, y, width, height } = element.frame;
2664
+ return JSON.stringify([
2665
+ element.role,
2666
+ element.identifier ?? null,
2667
+ element.label ?? null,
2668
+ element.value ?? null,
2669
+ Math.round(x),
2670
+ Math.round(y),
2671
+ Math.round(width),
2672
+ Math.round(height)
2673
+ ]);
2674
+ }
2675
+ function snapshotHash(snapshot) {
2676
+ let text = "";
2677
+ for (const element of snapshot.flat)
2678
+ text += `${tuple(element)}
2679
+ `;
2680
+ return Bun.hash(text).toString(36);
2681
+ }
2682
+
2683
+ // ../../packages/e2e/src/driver/typing.ts
2684
+ var KEY_PACE_MS = 0;
2685
+ var SPLIT_TEXT = false;
2686
+ function canRepeatKeys(driver) {
2687
+ return typeof driver.pressKeyRepeat === "function";
2688
+ }
2689
+ function canSelectAll(driver) {
2690
+ return typeof driver.selectAll === "function";
2691
+ }
2692
+ function canSetText(driver) {
2693
+ return typeof driver.setText === "function";
2694
+ }
2695
+ var segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
2696
+ function graphemesOf(text) {
2697
+ return Array.from(segmenter.segment(text), (part) => part.segment);
2698
+ }
2699
+ async function pace(driver, sleep) {
2700
+ if (driver.platform === "ios" && KEY_PACE_MS > 0)
2701
+ await sleep(KEY_PACE_MS);
2702
+ }
2703
+ async function typeText(driver, sleep, text) {
2704
+ if (driver.platform !== "ios" || !SPLIT_TEXT) {
2705
+ const typed = await driver.typeText(text);
2706
+ if (typed.success)
2707
+ await pace(driver, sleep);
2708
+ return typed;
2709
+ }
2710
+ for (const character of graphemesOf(text)) {
2711
+ const typed = await driver.typeText(character);
2712
+ if (!typed.success)
2713
+ return typed;
2714
+ await pace(driver, sleep);
2715
+ }
2716
+ return ok(undefined);
2717
+ }
2718
+ async function pressKeys(driver, sleep, key, times) {
2719
+ if (times > 1 && canRepeatKeys(driver))
2720
+ return driver.pressKeyRepeat(key, times);
2721
+ for (let press = 0;press < times; press += 1) {
2722
+ const pressed = await driver.key(key);
2723
+ if (!pressed.success)
2724
+ return pressed;
2725
+ await pace(driver, sleep);
2726
+ }
2727
+ return ok(undefined);
2728
+ }
2729
+
2730
+ // ../../packages/e2e/src/screen/actions.ts
2731
+ var MOMENTUM = {
2732
+ none: { fraction: 0.5, durationMs: 350, momentum: false },
2733
+ slow: { fraction: 0.5, durationMs: 200, momentum: true },
2734
+ fast: { fraction: 0.7, durationMs: 120, momentum: true }
2735
+ };
2736
+ var FOCUS_X = 0.9;
2737
+ var MAX_CLEAR_KEYS = 300;
2738
+ function tapPointOf(entry, position) {
2739
+ const { frame, tapPoint } = entry.element;
2740
+ if (position !== undefined)
2741
+ return { x: frame.x + position.x, y: frame.y + position.y };
2742
+ return tapPoint ?? { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 };
2743
+ }
2744
+ function focusPoint(frame) {
2745
+ return { x: frame.x + frame.width * FOCUS_X, y: frame.y + frame.height / 2 };
2746
+ }
2747
+ function swipeVector(direction, rect, momentum = "none") {
2748
+ const preset = MOMENTUM[momentum];
2749
+ const options = { durationMs: preset.durationMs, momentum: preset.momentum };
2750
+ return { ...contentPath(direction, rect, preset.fraction), options };
2751
+ }
2752
+ function contentPath(direction, rect, fraction) {
2753
+ const centre = { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
2754
+ const halfX = rect.width * fraction / 2;
2755
+ const halfY = rect.height * fraction / 2;
2756
+ switch (direction) {
2757
+ case "down":
2758
+ return { from: { x: centre.x, y: centre.y + halfY }, to: { x: centre.x, y: centre.y - halfY } };
2759
+ case "up":
2760
+ return { from: { x: centre.x, y: centre.y - halfY }, to: { x: centre.x, y: centre.y + halfY } };
2761
+ case "right":
2762
+ return { from: { x: centre.x + halfX, y: centre.y }, to: { x: centre.x - halfX, y: centre.y } };
2763
+ case "left":
2764
+ return { from: { x: centre.x - halfX, y: centre.y }, to: { x: centre.x + halfX, y: centre.y } };
2765
+ }
2766
+ }
2767
+ function screenRect(screen) {
2768
+ return { x: 0, y: 0, width: screen.width, height: screen.height };
2769
+ }
2770
+ function visibleRect(frame, screen) {
2771
+ if (screen.width <= 0 || screen.height <= 0)
2772
+ return frame;
2773
+ const left = Math.max(frame.x, 0);
2774
+ const top = Math.max(frame.y, 0);
2775
+ const right = Math.min(frame.x + frame.width, screen.width);
2776
+ const bottom = Math.min(frame.y + frame.height, screen.height);
2777
+ return { x: left, y: top, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
2778
+ }
2779
+ function isOnScreen(point, screen) {
2780
+ if (screen.width <= 0 || screen.height <= 0)
2781
+ return true;
2782
+ return point.x >= 0 && point.y >= 0 && point.x <= screen.width && point.y <= screen.height;
2783
+ }
2784
+ function fieldValue(entry) {
2785
+ if (entry.secure)
2786
+ return "";
2787
+ const { element } = entry;
2788
+ const own = entry.value ?? (entry.role === "textbox" || entry.role === "searchbox" ? element.text : undefined);
2789
+ if (own === undefined || own === "")
2790
+ return "";
2791
+ if (element.placeholder !== undefined && own === element.placeholder && entry.states.focused !== true)
2792
+ return "";
2793
+ return own;
2794
+ }
2795
+ var segmenter2 = new Intl.Segmenter(undefined, { granularity: "grapheme" });
2796
+ function graphemes(text) {
2797
+ let count = 0;
2798
+ for (const _ of segmenter2.segment(text))
2799
+ count += 1;
2800
+ return count;
2801
+ }
2802
+ var SECURE_DOTS = new Set(["\u2022", "\u25CF"]);
2803
+ function countDots(value) {
2804
+ let count = 0;
2805
+ for (const part of segmenter2.segment(value))
2806
+ if (SECURE_DOTS.has(part.segment))
2807
+ count += 1;
2808
+ return count;
2809
+ }
2810
+ function fieldContent(entry) {
2811
+ if (entry.secure) {
2812
+ const count = countDots(entry.element.value ?? "");
2813
+ return count === 0 ? { kind: "empty" } : { kind: "dots", count };
2814
+ }
2815
+ const text = fieldValue(entry);
2816
+ return text === "" ? { kind: "empty" } : { kind: "text", text };
2817
+ }
2818
+ function clearKeyPresses(entry) {
2819
+ const content = fieldContent(entry);
2820
+ switch (content.kind) {
2821
+ case "empty":
2822
+ return 0;
2823
+ case "text":
2824
+ return Math.min(graphemes(content.text) + 1, MAX_CLEAR_KEYS);
2825
+ case "dots":
2826
+ return Math.min(content.count + 1, MAX_CLEAR_KEYS);
2827
+ }
2828
+ }
2829
+ var PINCH_MS = 500;
2830
+ var ROTATE_MS = 500;
2831
+ var PATH_MS = 600;
2832
+ var SCALE_RANGE = [0.1, 10];
2833
+ var MAX_ROTATE_DEGREES = 720;
2834
+ var REACH = 0.8;
2835
+ var MIN_RADIUS = 0.04;
2836
+ var ROTATE_SHARE = 0.3;
2837
+ function centreOf(rect) {
2838
+ return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
2839
+ }
2840
+ function pinchRadius(rect, scale2, screen) {
2841
+ const reach = Math.min(rect.width, rect.height) / 2 * REACH;
2842
+ const floor = MIN_RADIUS * Math.min(screen.width, screen.height);
2843
+ return Math.max(reach / Math.max(1, scale2), floor / Math.min(1, scale2));
2844
+ }
2845
+ function rotateRadius(rect, screen) {
2846
+ const side = Math.min(screen.width, screen.height);
2847
+ return Math.min(Math.max(Math.min(rect.width, rect.height) * ROTATE_SHARE, MIN_RADIUS * side), ROTATE_SHARE * side);
2848
+ }
2849
+ function requireGesture(driver, cap, verb, fingers = 1) {
2850
+ const { capabilities, platform } = driver;
2851
+ if (cap === "pinch" ? capabilities.pinch : capabilities.maxPointers >= fingers)
2852
+ return;
2853
+ const detail = cap === "pinch" ? "capabilities.pinch is false" : `capabilities.maxPointers is ${capabilities.maxPointers}, needs ${fingers}`;
2854
+ throw new E2eError("INVALID_ARGUMENT", `${verb} is not supported on ${platform} (driver capability: ${detail}); use test({ platforms: [...] }) to skip`);
2855
+ }
2856
+ function assertScale(scale2) {
2857
+ if (!Number.isFinite(scale2) || scale2 < SCALE_RANGE[0] || scale2 > SCALE_RANGE[1] || scale2 === 1) {
2858
+ throw new E2eError("INVALID_ARGUMENT", `pinch scale must be a finite number from ${SCALE_RANGE[0]} to ${SCALE_RANGE[1]} and not 1, got ${scale2}`);
2859
+ }
2860
+ }
2861
+ function assertDegrees(degrees) {
2862
+ if (!Number.isFinite(degrees) || degrees === 0 || Math.abs(degrees) > MAX_ROTATE_DEGREES) {
2863
+ throw new E2eError("INVALID_ARGUMENT", `rotate degrees must be finite, not 0 and at most ${MAX_ROTATE_DEGREES} either way, got ${degrees}`);
2864
+ }
2865
+ }
2866
+
2867
+ // ../../packages/e2e/src/screen/roles.ts
2868
+ var QUERY_ROLES = [
2869
+ "button",
2870
+ "link",
2871
+ "textbox",
2872
+ "searchbox",
2873
+ "checkbox",
2874
+ "radio",
2875
+ "switch",
2876
+ "slider",
2877
+ "image",
2878
+ "heading",
2879
+ "text",
2880
+ "tab",
2881
+ "tablist",
2882
+ "menu",
2883
+ "menuitem",
2884
+ "list",
2885
+ "listitem",
2886
+ "row",
2887
+ "alert",
2888
+ "dialog",
2889
+ "navigation",
2890
+ "group",
2891
+ "other"
2892
+ ];
2893
+ var QUERY_ROLE_SET = new Set(QUERY_ROLES);
2894
+ function isQueryRole(value) {
2895
+ return QUERY_ROLE_SET.has(value);
2896
+ }
2897
+ var INTERACTIVE_ROLES = new Set([
2898
+ "button",
2899
+ "link",
2900
+ "textbox",
2901
+ "searchbox",
2902
+ "checkbox",
2903
+ "radio",
2904
+ "switch",
2905
+ "slider",
2906
+ "tab",
2907
+ "menuitem"
2908
+ ]);
2909
+ function isInteractiveRole(role) {
2910
+ return INTERACTIVE_ROLES.has(role);
2911
+ }
2912
+ var DRIVER_ROLE_MAP = {
2913
+ button: "button",
2914
+ text: "text",
2915
+ textfield: "textbox",
2916
+ securefield: "textbox",
2917
+ searchfield: "searchbox",
2918
+ image: "image",
2919
+ switch: "switch",
2920
+ checkbox: "checkbox",
2921
+ slider: "slider",
2922
+ link: "link",
2923
+ cell: "listitem",
2924
+ list: "list",
2925
+ scrollview: "group",
2926
+ tab: "tab",
2927
+ tabbar: "tablist",
2928
+ alert: "alert",
2929
+ sheet: "dialog",
2930
+ keyboard: "other",
2931
+ navbar: "navigation",
2932
+ toolbar: "group",
2933
+ window: "other",
2934
+ application: "other",
2935
+ other: "other"
2936
+ };
2937
+ var SPELLED_ROLES = {
2938
+ alert: "alert",
2939
+ checkbox: "checkbox",
2940
+ "combo box": "other",
2941
+ menu: "menu",
2942
+ "menu bar": "menu",
2943
+ "menu item": "menuitem",
2944
+ "progress bar": "other",
2945
+ "radio button": "radio",
2946
+ "radio group": "group",
2947
+ "scroll bar": "other",
2948
+ "spin button": "other",
2949
+ switch: "switch",
2950
+ tab: "tab",
2951
+ "tab list": "tablist",
2952
+ timer: "other",
2953
+ "tool bar": "group"
2954
+ };
2955
+ var SPELLED_STATES = {
2956
+ checked: { checked: true },
2957
+ unchecked: { checked: false },
2958
+ expanded: { expanded: true },
2959
+ collapsed: { expanded: false }
2960
+ };
2961
+ var SPELLED_LOOSE_WORDS = new Set(["mixed", "busy"]);
2962
+ function parseSpelledValue(value) {
2963
+ if (value === undefined)
2964
+ return;
2965
+ const parts = value.split(", ");
2966
+ const role = SPELLED_ROLES[parts[0]?.toLowerCase() ?? ""];
2967
+ if (role === undefined)
2968
+ return;
2969
+ const states = {};
2970
+ const rest = [];
2971
+ let position = 1;
2972
+ for (;position < parts.length; position += 1) {
2973
+ const part = parts[position] ?? "";
2974
+ const word = part.toLowerCase();
2975
+ const state = SPELLED_STATES[word];
2976
+ if (state !== undefined) {
2977
+ Object.assign(states, state);
2978
+ continue;
2979
+ }
2980
+ if (SPELLED_LOOSE_WORDS.has(word)) {
2981
+ rest.push(part);
2982
+ continue;
2983
+ }
2984
+ break;
2985
+ }
2986
+ rest.push(...parts.slice(position));
2987
+ return { ...states, role, value: rest.length === 0 ? undefined : rest.join(", ") };
2988
+ }
2989
+ var ANDROID_ROLE_MAP = {
2990
+ "text-view": "text",
2991
+ "edit-text": "textbox",
2992
+ "auto-complete-text-view": "textbox",
2993
+ "text-input-edit-text": "textbox",
2994
+ "search-view": "searchbox",
2995
+ button: "button",
2996
+ "image-button": "button",
2997
+ "material-button": "button",
2998
+ "image-view": "image",
2999
+ switch: "switch",
3000
+ "switch-compat": "switch",
3001
+ "switch-material": "switch",
3002
+ "toggle-button": "switch",
3003
+ "check-box": "checkbox",
3004
+ "material-check-box": "checkbox",
3005
+ "radio-button": "radio",
3006
+ "seek-bar": "slider",
3007
+ slider: "slider",
3008
+ "tab-layout": "tablist",
3009
+ "tab-widget": "tablist",
3010
+ "bottom-navigation-view": "tablist",
3011
+ "recycler-view": "list",
3012
+ "list-view": "list",
3013
+ "abs-list-view": "list",
3014
+ "grid-view": "list",
3015
+ "scroll-view": "group",
3016
+ "horizontal-scroll-view": "group",
3017
+ "view-group": "group",
3018
+ view: "group",
3019
+ "compose-view": "group",
3020
+ "view-factory-holder": "group",
3021
+ "card-view": "group"
3022
+ };
3023
+ var ANDROID_GENERIC_CONTAINERS = new Set([
3024
+ "view-group",
3025
+ "view",
3026
+ "compose-view",
3027
+ "view-factory-holder",
3028
+ "card-view"
3029
+ ]);
3030
+ function kebabSimpleName(rawType) {
3031
+ const simple = rawType.slice(rawType.lastIndexOf(".") + 1);
3032
+ return simple.replaceAll(/([a-z0-9])([A-Z])/g, "$1-$2").replaceAll(/[\s_$]+/g, "-").toLowerCase();
3033
+ }
3034
+ function isAndroidClass(rawType) {
3035
+ return rawType.includes(".");
3036
+ }
3037
+ function upgradeByTraits(role, traits) {
3038
+ if (role !== "other" && role !== "group" && role !== "text")
3039
+ return role;
3040
+ if (traits.has("header") || traits.has("heading"))
3041
+ return "heading";
3042
+ if (role === "text" && !traits.has("link") && !traits.has("button"))
3043
+ return role;
3044
+ if (traits.has("link"))
3045
+ return "link";
3046
+ if (traits.has("button"))
3047
+ return "button";
3048
+ if (role === "text")
3049
+ return role;
3050
+ if (traits.has("image"))
3051
+ return "image";
3052
+ if (traits.has("adjustable"))
3053
+ return "slider";
3054
+ if (traits.has("searchfield"))
3055
+ return "searchbox";
3056
+ return role;
3057
+ }
3058
+ function resolveAndroid(element, traits) {
3059
+ const kind = kebabSimpleName(element.rawType);
3060
+ const mapped = ANDROID_ROLE_MAP[kind];
3061
+ const isLayout = kind.endsWith("layout");
3062
+ const driverRole = DRIVER_ROLE_MAP[element.role];
3063
+ const role = mapped ?? (isLayout && driverRole === "other" ? "group" : driverRole);
3064
+ const container = role === "group" || role === "other";
3065
+ if (traits.has("heading") && (container || role === "text"))
3066
+ return "heading";
3067
+ const promotable = container && (mapped === undefined || isLayout || ANDROID_GENERIC_CONTAINERS.has(kind));
3068
+ if (!promotable)
3069
+ return role;
3070
+ if (traits.has("checkable"))
3071
+ return "checkbox";
3072
+ return traits.has("clickable") ? "button" : role;
3073
+ }
3074
+ function resolveRole(element, parent) {
3075
+ const traits = new Set(element.traits.map((trait) => trait.toLowerCase()));
3076
+ if (isAndroidClass(element.rawType))
3077
+ return { role: resolveAndroid(element, traits) };
3078
+ if (element.role === "other") {
3079
+ const spelled = parseSpelledValue(element.value);
3080
+ if (spelled !== undefined)
3081
+ return { role: spelled.role, spelled };
3082
+ }
3083
+ if (element.role === "button" && parent?.role === "tabbar")
3084
+ return { role: "tab" };
3085
+ return { role: upgradeByTraits(DRIVER_ROLE_MAP[element.role], traits) };
3086
+ }
3087
+
3088
+ // ../../packages/e2e/src/screen/text.ts
3089
+ function normalizeText(text) {
3090
+ return text.replaceAll(/\s+/g, " ").trim();
3091
+ }
3092
+ function matchesText(value, match, options = {}) {
3093
+ const normalized = normalizeText(value);
3094
+ if (typeof match !== "string") {
3095
+ const flags = match.flags.replaceAll(/[gy]/g, "");
3096
+ return new RegExp(match.source, flags).test(normalized);
3097
+ }
3098
+ const wanted = normalizeText(match);
3099
+ if (options.exact === false)
3100
+ return normalized.toLowerCase().includes(wanted.toLowerCase());
3101
+ return normalized === wanted;
3102
+ }
3103
+
3104
+ // ../../packages/e2e/src/screen/nodes.ts
3105
+ var CHECKABLE_ROLES = new Set(["switch", "checkbox", "radio"]);
3106
+ var CHECKED_WORDS = new Set(["1", "on", "true", "checked"]);
3107
+ var UNCHECKED_WORDS = new Set(["0", "off", "false", "unchecked"]);
3108
+ function nonEmpty(value) {
3109
+ return value === undefined || value === "" ? undefined : value;
3110
+ }
3111
+ function isSecure(element, traits) {
3112
+ return element.role === "securefield" || traits.has("password") || traits.has("secure") || element.rawType.toLowerCase().includes("secure");
3113
+ }
3114
+ function deriveChecked(element, role, spelled, traits, value) {
3115
+ if (spelled?.checked !== undefined)
3116
+ return spelled.checked;
3117
+ if (element.checked !== undefined)
3118
+ return element.checked;
3119
+ if (traits.has("checkable"))
3120
+ return traits.has("checked");
3121
+ if (CHECKABLE_ROLES.has(role) && value !== undefined) {
3122
+ const word = value.trim().toLowerCase();
3123
+ if (CHECKED_WORDS.has(word))
3124
+ return true;
3125
+ if (UNCHECKED_WORDS.has(word))
3126
+ return false;
3127
+ }
3128
+ return;
3129
+ }
3130
+ function intersectsScreen(frame, screen) {
3131
+ if (frame.width <= 0 || frame.height <= 0)
3132
+ return false;
3133
+ if (screen.width <= 0 || screen.height <= 0)
3134
+ return true;
3135
+ return frame.x < screen.width && frame.y < screen.height && frame.x + frame.width > 0 && frame.y + frame.height > 0;
3136
+ }
3137
+ function ownFragments(name, element, value) {
3138
+ const seen = new Set;
3139
+ for (const raw of [element.label, element.text, value]) {
3140
+ const text = raw === undefined ? "" : normalizeText(raw);
3141
+ if (text !== "")
3142
+ seen.add(text);
3143
+ }
3144
+ if (name !== undefined && normalizeText(name) !== "")
3145
+ seen.add(normalizeText(name));
3146
+ return [...seen];
3147
+ }
3148
+ function subtreeFragments(entry) {
3149
+ const below = entry.children.flatMap((child) => child.fragments);
3150
+ const belowText = below.join(" ");
3151
+ const own = ownFragments(entry.name, entry.element, entry.value).filter((text) => text !== belowText && !below.includes(text));
3152
+ return [...own, ...below];
3153
+ }
3154
+ function indexSnapshot(snapshot) {
3155
+ const entries = [];
3156
+ const byId = new Map;
3157
+ const build = (element, depth, parent) => {
3158
+ const traits = new Set(element.traits.map((trait) => trait.toLowerCase()));
3159
+ const { role, spelled } = resolveRole(element, parent?.element);
3160
+ const secure = isSecure(element, traits);
3161
+ const rawValue = spelled === undefined ? element.value : spelled.value;
3162
+ const value = secure ? undefined : nonEmpty(rawValue);
3163
+ const name = nonEmpty(element.label) ?? nonEmpty(element.text);
3164
+ const hidden = element.hidden === true || parent?.states.hidden === true;
3165
+ let fragments;
3166
+ let subtreeText;
3167
+ const entry = {
3168
+ position: entries.length,
3169
+ element,
3170
+ depth,
3171
+ parent,
3172
+ children: [],
3173
+ role,
3174
+ name,
3175
+ value,
3176
+ states: {
3177
+ checked: deriveChecked(element, role, spelled, traits, value),
3178
+ expanded: spelled?.expanded,
3179
+ disabled: !element.enabled,
3180
+ selected: element.selected === true,
3181
+ focused: element.focused === true,
3182
+ hidden
3183
+ },
3184
+ secure,
3185
+ visible: !hidden && intersectsScreen(element.frame, snapshot.screen),
3186
+ get fragments() {
3187
+ fragments ??= subtreeFragments(entry);
3188
+ return fragments;
3189
+ },
3190
+ get subtreeText() {
3191
+ subtreeText ??= entry.fragments.join(" ");
3192
+ return subtreeText;
3193
+ }
3194
+ };
3195
+ entries.push(entry);
3196
+ byId.set(element.id, entry);
3197
+ for (const child of element.children)
3198
+ entry.children.push(build(child, depth + 1, entry));
3199
+ return entry;
3200
+ };
3201
+ for (const element of snapshot.elements)
3202
+ build(element, 0, undefined);
3203
+ return { entries, byId, screen: snapshot.screen };
3204
+ }
3205
+ function isWithin(entry, ancestor) {
3206
+ for (let current = entry.parent;current !== undefined; current = current.parent) {
3207
+ if (current === ancestor)
3208
+ return true;
3209
+ }
3210
+ return false;
3211
+ }
3212
+ function descendantsOf(universe, ancestors) {
3213
+ if (ancestors.length === 0)
3214
+ return [];
3215
+ const wanted = new Set(ancestors);
3216
+ return universe.filter((entry) => {
3217
+ for (let current = entry.parent;current !== undefined; current = current.parent) {
3218
+ if (wanted.has(current))
3219
+ return true;
3220
+ }
3221
+ return false;
3222
+ });
3223
+ }
3224
+ function entryOfElement(element, template) {
3225
+ const flat = [];
3226
+ const walk = (e) => {
3227
+ flat.push(e);
3228
+ for (const child of e.children)
3229
+ walk(child);
3230
+ };
3231
+ walk(element);
3232
+ const index = indexSnapshot({
3233
+ platform: template.platform,
3234
+ backend: "axbridge",
3235
+ takenAt: 0,
3236
+ screen: { width: 0, height: 0, scale: 1 },
3237
+ elements: [element],
3238
+ flat,
3239
+ truncated: false
3240
+ });
3241
+ return index.entries[0];
3242
+ }
3243
+
3244
+ // ../../packages/e2e/src/screen/field.ts
3245
+ var FIELD_POLL_MS = 50;
3246
+ var FIELD_PROGRESS_MS = 1000;
3247
+ var CONFIRM_READS = 2;
3248
+ var STALE_RISK_READS = 4;
3249
+ function confirmReadsFor(entry, text) {
3250
+ if (entry === undefined || text === "")
3251
+ return CONFIRM_READS;
3252
+ return fieldValue(entry) === text ? STALE_RISK_READS : CONFIRM_READS;
3253
+ }
3254
+ async function confirmFieldValue(request) {
3255
+ const { now, sleep } = request;
3256
+ const start = now();
3257
+ const cap = request.maxMs === undefined ? Number.POSITIVE_INFINITY : start + request.maxMs;
3258
+ let deadline = Math.min(start + request.budgetMs, cap);
3259
+ let held = "(gone)";
3260
+ let streak = 0;
3261
+ for (;; ) {
3262
+ const read = await request.read();
3263
+ if (!read.ok)
3264
+ return { status: "failed", error: read.error };
3265
+ const { reading } = read;
3266
+ if (reading.kind === "unreadable") {
3267
+ streak = 0;
3268
+ held = reading.note;
3269
+ } else if (reading.value === request.text) {
3270
+ streak += 1;
3271
+ if (streak >= (request.reads ?? CONFIRM_READS))
3272
+ return { status: "confirmed" };
3273
+ held = reading.value;
3274
+ deadline = Math.max(deadline, Math.min(now() + 2 * FIELD_POLL_MS, cap));
3275
+ } else {
3276
+ streak = 0;
3277
+ if (reading.value !== held)
3278
+ deadline = Math.max(deadline, Math.min(now() + FIELD_PROGRESS_MS, cap));
3279
+ held = reading.value;
3280
+ }
3281
+ if (now() >= deadline)
3282
+ return { status: "mismatch", held };
3283
+ await sleep(FIELD_POLL_MS);
3284
+ }
3285
+ }
3286
+ async function hitTestField(driver, wanted) {
3287
+ const { x, y, width, height } = wanted.element.frame;
3288
+ const hit = await driver.elementAtPoint({ x: x + width / 2, y: y + height / 2 });
3289
+ if (!hit.success)
3290
+ return { ok: false, error: hit.error };
3291
+ if (!hit.data)
3292
+ return { ok: true, entry: undefined };
3293
+ const entry = entryOfElement(hit.data, { platform: driver.platform });
3294
+ return { ok: true, entry: entry?.role === wanted.role ? entry : undefined };
3295
+ }
3296
+ var FOCUS_POLL_MS = 20;
3297
+ var FOCUS_POLLS = 25;
3298
+ function isSameField(hit, entry) {
3299
+ const identifier = entry.element.identifier;
3300
+ return hit.role === entry.element.role && (identifier === undefined || hit.identifier === identifier);
3301
+ }
3302
+ async function tapToFocus(driver, sleep, point, entry) {
3303
+ const tapped = await driver.tap(point);
3304
+ if (!tapped.success || driver.platform !== "ios" || entry === undefined || entry.states.focused === true)
3305
+ return tapped;
3306
+ for (let poll = 1;poll <= FOCUS_POLLS; poll += 1) {
3307
+ const hit = await driver.elementAtPoint(point);
3308
+ if (!hit.success || !hit.data || !isSameField(hit.data, entry) || hit.data.focused === true)
3309
+ break;
3310
+ if (poll < FOCUS_POLLS)
3311
+ await sleep(FOCUS_POLL_MS);
3312
+ }
3313
+ return ok(undefined);
3314
+ }
3315
+ var SETTEXT_ENV = "WARDEN_ANDROID_SETTEXT";
3316
+ function setTextOptedIn(env) {
3317
+ return env[SETTEXT_ENV] === "1";
3318
+ }
3319
+ async function focusAndSelect(driver, sleep, point, entry, method) {
3320
+ const focused = await tapToFocus(driver, sleep, point, entry);
3321
+ if (!focused.success)
3322
+ return focused;
3323
+ return prepareFocused(driver, sleep, entry, method);
3324
+ }
3325
+ async function prepareFocused(driver, sleep, entry, method) {
3326
+ if (entry === undefined)
3327
+ return ok({ pending: "none" });
3328
+ const content = fieldContent(entry);
3329
+ if (content.kind === "empty")
3330
+ return ok({ pending: "none" });
3331
+ if (content.kind === "text" && method !== "counted" && canSelectAll(driver)) {
3332
+ const selected = await driver.selectAll();
3333
+ if (selected.success)
3334
+ return ok({ pending: "selection" });
3335
+ }
3336
+ const pressed = await pressKeys(driver, sleep, "Backspace", clearKeyPresses(entry));
3337
+ return pressed.success ? ok({ pending: "none" }) : pressed;
3338
+ }
3339
+ async function focusAndClear(driver, sleep, point, entry, method = "auto") {
3340
+ const prepared = await focusAndSelect(driver, sleep, point, entry, method);
3341
+ if (!prepared.success)
3342
+ return prepared;
3343
+ return prepared.data.pending === "selection" ? pressKeys(driver, sleep, "Backspace", 1) : ok(undefined);
3344
+ }
3345
+ async function focusClearType(driver, sleep, point, entry, text, method = "auto", env = process.env) {
3346
+ const wantsSetText = method === "setText" || method === "auto" && setTextOptedIn(env);
3347
+ if (wantsSetText && entry !== undefined && fieldContent(entry).kind === "text" && canSetText(driver)) {
3348
+ const focused = await driver.tap(point);
3349
+ if (!focused.success)
3350
+ return focused;
3351
+ const replaced = await driver.setText(text);
3352
+ if (replaced.success)
3353
+ return replaced;
3354
+ return typeOver(driver, sleep, await prepareFocused(driver, sleep, entry, method), text);
3355
+ }
3356
+ return typeOver(driver, sleep, await focusAndSelect(driver, sleep, point, entry, method), text);
3357
+ }
3358
+ async function typeOver(driver, sleep, prepared, text) {
3359
+ if (!prepared.success)
3360
+ return prepared;
3361
+ if (text === "")
3362
+ return prepared.data.pending === "selection" ? pressKeys(driver, sleep, "Backspace", 1) : ok(undefined);
3363
+ return typeText(driver, sleep, text);
3364
+ }
3365
+
3366
+ // ../../packages/e2e/src/screen/selector.ts
3367
+ var VALUE_KEYS = ["id", "label", "text", "role", "value"];
3368
+ var KEY_ORDER = [...VALUE_KEYS, "editable", "nth"];
3369
+ var REGEX_FLAGS = /^[dgimsuvy]*$/;
3370
+ function isSpace(char) {
3371
+ return char === undefined || /\s/.test(char);
3372
+ }
3373
+ function readQuoted(input, start) {
3374
+ let end = start + 1;
3375
+ while (end < input.length && input[end] !== '"')
3376
+ end += input[end] === "\\" ? 2 : 1;
3377
+ if (end >= input.length)
3378
+ return err("unterminated quoted value");
3379
+ const literal = input.slice(start, end + 1);
3380
+ try {
3381
+ const value = JSON.parse(literal);
3382
+ if (typeof value !== "string")
3383
+ return err(`invalid escape in ${literal}`);
3384
+ return ok({ value, end: end + 1 });
3385
+ } catch {
3386
+ return err(`invalid escape in ${literal}`);
3387
+ }
3388
+ }
3389
+ function readRegex(input, start) {
3390
+ let end = start + 1;
3391
+ while (end < input.length && input[end] !== "/")
3392
+ end += input[end] === "\\" ? 2 : 1;
3393
+ if (end >= input.length)
3394
+ return err("unterminated regex value");
3395
+ const source = input.slice(start + 1, end);
3396
+ if (source === "")
3397
+ return err("empty regex //");
3398
+ let flagsEnd = end + 1;
3399
+ while (flagsEnd < input.length && !isSpace(input[flagsEnd]))
3400
+ flagsEnd += 1;
3401
+ const flags = input.slice(end + 1, flagsEnd);
3402
+ try {
3403
+ if (!REGEX_FLAGS.test(flags))
3404
+ throw new Error("bad flags");
3405
+ new RegExp(source, flags);
3406
+ } catch {
3407
+ return err(`invalid regex /${source}/${flags}`);
3408
+ }
3409
+ return ok({ value: { kind: "regex", source, flags }, end: flagsEnd });
3410
+ }
3411
+ function readValue(input, start, key) {
3412
+ const first = input[start];
3413
+ if (isSpace(first))
3414
+ return err(`missing value for ${key}=`);
3415
+ if (first === '"') {
3416
+ const quoted = readQuoted(input, start);
3417
+ if (!quoted.success)
3418
+ return quoted;
3419
+ if (!isSpace(input[quoted.data.end]))
3420
+ return err(`unexpected text after quoted value of ${key}=`);
3421
+ return ok({ value: { kind: "text", value: quoted.data.value }, end: quoted.data.end });
3422
+ }
3423
+ if (first === "/")
3424
+ return readRegex(input, start);
3425
+ let end = start;
3426
+ while (!isSpace(input[end]))
3427
+ end += 1;
3428
+ return ok({ value: { kind: "text", value: input.slice(start, end) }, end });
3429
+ }
3430
+ function tokenize(input) {
3431
+ const tokens = [];
3432
+ let position = 0;
3433
+ for (;; ) {
3434
+ while (position < input.length && isSpace(input[position]))
3435
+ position += 1;
3436
+ if (position >= input.length)
3437
+ return ok(tokens);
3438
+ const equals = input.indexOf("=", position);
3439
+ let wordEnd = position;
3440
+ while (!isSpace(input[wordEnd]))
3441
+ wordEnd += 1;
3442
+ if (equals === -1 || equals > wordEnd)
3443
+ return err(`expected key=value, got "${input.slice(position, wordEnd)}"`);
3444
+ const key = input.slice(position, equals);
3445
+ const read = readValue(input, equals + 1, key);
3446
+ if (!read.success)
3447
+ return read;
3448
+ tokens.push({ key, raw: input.slice(position, read.data.end), value: read.data.value });
3449
+ position = read.data.end;
3450
+ }
3451
+ }
3452
+ function isSelectorKey(key) {
3453
+ return KEY_ORDER.includes(key);
3454
+ }
3455
+ function isValueKey(key) {
3456
+ return VALUE_KEYS.includes(key);
3457
+ }
3458
+ function parseSelector(input) {
3459
+ if (input.trim() === "")
3460
+ return err("empty selector");
3461
+ const tokens = tokenize(input);
3462
+ if (!tokens.success)
3463
+ return tokens;
3464
+ const selector = {};
3465
+ const seen = new Set;
3466
+ for (const token of tokens.data) {
3467
+ if (!isSelectorKey(token.key)) {
3468
+ return err(`unknown key "${token.key}" (expected ${KEY_ORDER.join(", ")})`);
3469
+ }
3470
+ if (seen.has(token.key))
3471
+ return err(`duplicate key "${token.key}"`);
3472
+ seen.add(token.key);
3473
+ const failure = applyToken(selector, token.key, token);
3474
+ if (failure !== undefined)
3475
+ return err(failure);
3476
+ }
3477
+ return ok(selector);
3478
+ }
3479
+ function applyToken(selector, key, token) {
3480
+ if (isValueKey(key)) {
3481
+ selector[key] = token.value;
3482
+ return;
3483
+ }
3484
+ const word = token.value.kind === "text" ? token.value.value : "";
3485
+ if (key === "editable") {
3486
+ if (word !== "true" && word !== "false")
3487
+ return `editable must be true or false, got ${token.raw}`;
3488
+ selector.editable = word === "true";
3489
+ return;
3490
+ }
3491
+ if (!/^\d+$/.test(word))
3492
+ return `nth must be a non-negative integer, got ${token.raw}`;
3493
+ selector.nth = Number(word);
3494
+ return;
3495
+ }
3496
+ function formatValue2(value) {
3497
+ if (value.kind === "regex")
3498
+ return `/${value.source}/${value.flags}`;
3499
+ return /^[^\s"/][^\s]*$/.test(value.value) ? value.value : JSON.stringify(value.value);
3500
+ }
3501
+ function formatSelector(selector) {
3502
+ const parts = [];
3503
+ for (const key of VALUE_KEYS) {
3504
+ const value = selector[key];
3505
+ if (value !== undefined)
3506
+ parts.push(`${key}=${formatValue2(value)}`);
3507
+ }
3508
+ if (selector.editable !== undefined)
3509
+ parts.push(`editable=${selector.editable}`);
3510
+ if (selector.nth !== undefined)
3511
+ parts.push(`nth=${selector.nth}`);
3512
+ return parts.join(" ");
3513
+ }
3514
+ function selectorToQuery(selector) {
3515
+ const { nth, ...rest } = selector;
3516
+ const base = { kind: "query", query: { kind: "selector", selector: rest } };
3517
+ return nth === undefined ? base : { kind: "index", source: base, index: nth };
3518
+ }
3519
+ function matchesValue(actual, value, mode) {
3520
+ if (actual === undefined)
3521
+ return false;
3522
+ if (value.kind === "regex")
3523
+ return matchesText(actual, new RegExp(value.source, value.flags));
3524
+ if (mode === "exact")
3525
+ return actual === value.value;
3526
+ return matchesText(actual, value.value, { exact: false });
3527
+ }
3528
+ function kebabType(rawType) {
3529
+ const simple = rawType.slice(rawType.lastIndexOf(".") + 1);
3530
+ return simple.replaceAll(/([a-z0-9])([A-Z])/g, "$1-$2").replaceAll(/[\s_]+/g, "-").toLowerCase();
3531
+ }
3532
+ function roleNames(entry) {
3533
+ return [entry.role, entry.element.role, kebabType(entry.element.rawType), entry.element.rawType.toLowerCase()];
3534
+ }
3535
+ function matchesRole(entry, value) {
3536
+ const names = roleNames(entry);
3537
+ if (value.kind === "regex")
3538
+ return names.some((name) => matchesValue(name, value, "exact"));
3539
+ const wanted = value.value.toLowerCase().replaceAll(/[\s_]+/g, "-");
3540
+ return names.some((name) => name === wanted || name.replaceAll("-", "") === wanted.replaceAll("-", ""));
3541
+ }
3542
+ function textCandidates(entry) {
3543
+ return [entry.element.label, entry.element.text, entry.value];
3544
+ }
3545
+ function matchesSelector(entry, selector) {
3546
+ if (selector.id !== undefined && !matchesValue(entry.element.identifier, selector.id, "exact"))
3547
+ return false;
3548
+ if (selector.label !== undefined && !matchesValue(entry.name, selector.label, "substring"))
3549
+ return false;
3550
+ if (selector.text !== undefined) {
3551
+ const wanted = selector.text;
3552
+ if (!textCandidates(entry).some((candidate) => matchesValue(candidate, wanted, "substring")))
3553
+ return false;
3554
+ }
3555
+ if (selector.value !== undefined && !matchesValue(entry.value, selector.value, "substring"))
3556
+ return false;
3557
+ if (selector.role !== undefined && !matchesRole(entry, selector.role))
3558
+ return false;
3559
+ if (selector.editable !== undefined) {
3560
+ const editable = entry.role === "textbox" || entry.role === "searchbox";
3561
+ if (editable !== selector.editable)
3562
+ return false;
3563
+ }
3564
+ return true;
3565
+ }
3566
+
3567
+ // ../../packages/e2e/src/screen/queries.ts
3568
+ var TEXT_KEYS = ["exact", "visible"];
3569
+ var TEST_ID_KEYS = ["visible"];
3570
+ var ROLE_KEYS = ["name", "exact", "checked", "disabled", "selected", "visible"];
3571
+ function assertKeys(method, options, allowed) {
3572
+ const unknown = Object.keys(options).filter((key) => !allowed.includes(key));
3573
+ if (unknown.length > 0) {
3574
+ throw new E2eError("INVALID_LOCATOR", `${method} does not take ${unknown.map((key) => `"${key}"`).join(", ")}; it takes ${allowed.join(", ")}`);
3575
+ }
3576
+ }
3577
+ function defined2(source) {
3578
+ return Object.fromEntries(Object.entries(source).filter(([, value]) => value !== undefined));
3579
+ }
3580
+ function testIdQuery(id, options = {}) {
3581
+ assertKeys("getByTestId", options, TEST_ID_KEYS);
3582
+ return { kind: "testId", value: id, ...defined2(options) };
3583
+ }
3584
+ var METHOD = {
3585
+ text: "getByText",
3586
+ label: "getByLabel",
3587
+ placeholder: "getByPlaceholder",
3588
+ value: "getByValue"
3589
+ };
3590
+ function textQuery(kind, value, options = {}) {
3591
+ assertKeys(METHOD[kind], options, TEXT_KEYS);
3592
+ return { kind, value, ...defined2(options) };
3593
+ }
3594
+ function isRoleOptions(value) {
3595
+ return value !== undefined && typeof value === "object" && !(value instanceof RegExp);
3596
+ }
3597
+ function roleQuery(role, nameOrOptions, options = {}) {
3598
+ if (!isQueryRole(role)) {
3599
+ throw new E2eError("INVALID_LOCATOR", `getByRole does not know the role "${String(role)}"; use one of ${QUERY_ROLES.join(", ")}`);
3600
+ }
3601
+ const merged = isRoleOptions(nameOrOptions) ? { ...nameOrOptions, ...options } : { ...nameOrOptions === undefined ? {} : { name: nameOrOptions }, ...options };
3602
+ assertKeys("getByRole", merged, ROLE_KEYS);
3603
+ return { kind: "role", role, ...defined2(merged) };
3604
+ }
3605
+ function selectorExpression(text) {
3606
+ const parsed = parseSelector(text);
3607
+ if (!parsed.success)
3608
+ throw new E2eError("INVALID_LOCATOR", `invalid selector ${JSON.stringify(text)}: ${parsed.error}`);
3609
+ return selectorToQuery(parsed.data);
3610
+ }
3611
+ function withScope(expr, scope) {
3612
+ switch (expr.kind) {
3613
+ case "query":
3614
+ return {
3615
+ kind: "query",
3616
+ query: expr.query,
3617
+ scope: expr.scope === undefined ? scope : withScope(expr.scope, scope)
3618
+ };
3619
+ case "filter":
3620
+ return { ...expr, source: withScope(expr.source, scope) };
3621
+ case "index":
3622
+ return { ...expr, source: withScope(expr.source, scope) };
3623
+ }
3624
+ }
3625
+
3626
+ // ../../packages/e2e/src/screen/render.ts
3627
+ var MAX_NAME = 80;
3628
+ var MAX_VALUE = 60;
3629
+ function truncate(text, max) {
3630
+ return text.length > max ? `${text.slice(0, max)}\u2026` : text;
3631
+ }
3632
+ function quote(text, max) {
3633
+ return JSON.stringify(truncate(text, max));
3634
+ }
3635
+ function identifierToken(id) {
3636
+ return /^[^\s"]+$/.test(id) ? id : JSON.stringify(id);
3637
+ }
3638
+ function stateWords(entry) {
3639
+ const { states, element } = entry;
3640
+ const words = [states.disabled ? "disabled" : "enabled"];
3641
+ if (element.hittable === true)
3642
+ words.push("hittable");
3643
+ if (states.checked !== undefined)
3644
+ words.push(states.checked ? "checked" : "unchecked");
3645
+ if (states.selected)
3646
+ words.push("selected");
3647
+ if (states.focused)
3648
+ words.push("focused");
3649
+ if (entry.secure)
3650
+ words.push("secure");
3651
+ if (states.hidden)
3652
+ words.push("hidden");
3653
+ if (states.expanded !== undefined)
3654
+ words.push(states.expanded ? "expanded" : "collapsed");
3655
+ return words;
3656
+ }
3657
+ function head(entry) {
3658
+ const parts = [`[${entry.role}]`];
3659
+ if (entry.name !== undefined)
3660
+ parts.push(quote(entry.name, MAX_NAME));
3661
+ const id = entry.element.identifier;
3662
+ if (id !== undefined && id !== "")
3663
+ parts.push(`id=${identifierToken(id)}`);
3664
+ return parts;
3665
+ }
3666
+ function describeEntry(entry) {
3667
+ return head(entry).join(" ");
3668
+ }
3669
+ function renderEntryLine(entry, options = {}) {
3670
+ const parts = [];
3671
+ if (options.ref !== undefined)
3672
+ parts.push(options.ref);
3673
+ parts.push(...head(entry));
3674
+ if (entry.value !== undefined)
3675
+ parts.push(`value=${quote(entry.value, MAX_VALUE)}`);
3676
+ parts.push(...stateWords(entry));
3677
+ const { x, y, width, height } = entry.element.frame;
3678
+ parts.push(`(${Math.round(x)},${Math.round(y)} ${Math.round(width)}\xD7${Math.round(height)})`);
3679
+ return `${" ".repeat(options.indent ?? 0)}${parts.join(" ")}`;
3680
+ }
3681
+ function isListed(entry) {
3682
+ if (entry.states.hidden)
3683
+ return false;
3684
+ const id = entry.element.identifier;
3685
+ return isInteractiveRole(entry.role) || id !== undefined && id !== "";
3686
+ }
3687
+ function renderSnapshot(snapshot, options) {
3688
+ const index = indexSnapshot(snapshot);
3689
+ const listed = options.interactiveOnly ? index.entries.filter(isListed) : index.entries;
3690
+ const limit = options.maxLines === undefined ? listed.length : Math.max(0, options.maxLines);
3691
+ const visible = listed.slice(0, limit);
3692
+ const lines = visible.map((entry) => renderEntryLine(entry, {
3693
+ indent: options.interactiveOnly ? 0 : entry.depth,
3694
+ ...options.refs.has(entry.element.id) ? { ref: options.refs.get(entry.element.id) } : {}
3695
+ }));
3696
+ const omitted = listed.length - visible.length;
3697
+ if (omitted > 0)
3698
+ lines.push(`\u2026 ${omitted} more (use --all)`);
3699
+ return { lines, shown: visible.length, total: index.entries.length, omitted };
3700
+ }
3701
+ function renderHeader(input) {
3702
+ const { snapshot, rendered } = input;
3703
+ const { width, height, scale: scale2 } = snapshot.screen;
3704
+ if (input.style === "cli") {
3705
+ const count = rendered.shown === rendered.total ? `${rendered.total}` : `${rendered.shown} of ${rendered.total}`;
3706
+ const parts2 = [
3707
+ ...input.generation === undefined ? [] : [`generation ${input.generation}`],
3708
+ snapshot.backend,
3709
+ `${width}\xD7${height}@${scale2}x`,
3710
+ `${count} elements`
3711
+ ];
3712
+ return parts2.join(" \xB7 ") + (snapshot.truncated ? ", truncated" : "");
3713
+ }
3714
+ const parts = [`# ${snapshot.platform} ${snapshot.backend} ${width}\xD7${height}@${scale2}x`];
3715
+ if (input.generation !== undefined)
3716
+ parts.push(`gen ${input.generation}`);
3717
+ let line = parts.join(" ");
3718
+ line += ` \xB7 ${rendered.shown} of ${rendered.total} elements (${input.interactiveOnly ? "interactive" : "all"})`;
3719
+ if (snapshot.truncated)
3720
+ line += " \xB7 truncated by backend";
3721
+ if (snapshot.modal !== undefined)
3722
+ line += " \xB7 modal present";
3723
+ return line;
3724
+ }
3725
+
3726
+ // ../../packages/e2e/src/screen/query.ts
3727
+ function textOptions(exact) {
3728
+ return { exact: exact ?? true };
3729
+ }
3730
+ function matchesQuery(entry, query) {
3731
+ if (query.kind === "role")
3732
+ return matchesRoleQuery(entry, query);
3733
+ if (query.visible === true && entry.states.hidden)
3734
+ return false;
3735
+ switch (query.kind) {
3736
+ case "testId": {
3737
+ const id = entry.element.identifier;
3738
+ return id !== undefined && id !== "" && matchesText(id, query.value);
3739
+ }
3740
+ case "text": {
3741
+ const options = textOptions(query.exact);
3742
+ return [entry.element.label, entry.element.text].some((text) => text !== undefined && text !== "" && matchesText(text, query.value, options));
3743
+ }
3744
+ case "label":
3745
+ return entry.name !== undefined && matchesText(entry.name, query.value, textOptions(query.exact));
3746
+ case "placeholder": {
3747
+ const placeholder = entry.element.placeholder;
3748
+ return placeholder !== undefined && matchesText(placeholder, query.value, textOptions(query.exact));
3749
+ }
3750
+ case "value":
3751
+ return entry.value !== undefined && matchesText(entry.value, query.value, textOptions(query.exact));
3752
+ case "selector":
3753
+ return matchesSelector(entry, query.selector);
3754
+ }
3755
+ }
3756
+ function matchesRoleQuery(entry, query) {
3757
+ if (entry.role !== query.role || entry.states.hidden)
3758
+ return false;
3759
+ if (query.name !== undefined && !matchesText(entry.name ?? "", query.name, textOptions(query.exact)))
3760
+ return false;
3761
+ if (query.checked !== undefined && (entry.states.checked ?? false) !== query.checked)
3762
+ return false;
3763
+ if (query.disabled !== undefined && entry.states.disabled !== query.disabled)
3764
+ return false;
3765
+ if (query.selected !== undefined && entry.states.selected !== query.selected)
3766
+ return false;
3767
+ return true;
3768
+ }
3769
+ var ECHOED_KINDS = new Set(["text", "label"]);
3770
+ function innermostOnly(matches) {
3771
+ const matched = new Set(matches);
3772
+ return matches.filter((entry) => {
3773
+ for (const other of matched) {
3774
+ if (other !== entry && isWithin(other, entry))
3775
+ return false;
3776
+ }
3777
+ return true;
3778
+ });
3779
+ }
3780
+ function subtreeHasText(entry, hasText) {
3781
+ const options = { exact: false };
3782
+ return entry.fragments.some((fragment) => matchesText(fragment, hasText, options)) || matchesText(entry.subtreeText, hasText, options);
3783
+ }
3784
+ function evaluate(expr, universe) {
3785
+ switch (expr.kind) {
3786
+ case "query": {
3787
+ const candidates = expr.scope === undefined ? universe : descendantsOf(universe, evaluate(expr.scope, universe));
3788
+ const matches = candidates.filter((entry) => matchesQuery(entry, expr.query));
3789
+ return ECHOED_KINDS.has(expr.query.kind) ? innermostOnly(matches) : matches;
3790
+ }
3791
+ case "filter":
3792
+ return evaluate(expr.source, universe).filter((entry) => {
3793
+ if (expr.hasText !== undefined && !subtreeHasText(entry, expr.hasText))
3794
+ return false;
3795
+ if (expr.has !== undefined && evaluate(expr.has, descendantsOf(universe, [entry])).length === 0)
3796
+ return false;
3797
+ return true;
3798
+ });
3799
+ case "index": {
3800
+ const source = evaluate(expr.source, universe);
3801
+ const position = expr.index === "first" ? 0 : expr.index === "last" ? source.length - 1 : expr.index;
3802
+ const picked = source[position];
3803
+ return picked === undefined ? [] : [picked];
3804
+ }
3805
+ }
3806
+ }
3807
+ function resolve2(expr, index) {
3808
+ return evaluate(expr, index.entries);
3809
+ }
3810
+ var MAX_LISTED = 5;
3811
+ function exactlyOne(expr, matches, options = {}) {
3812
+ const screen = options.screen === undefined ? {} : { screen: options.screen };
3813
+ const [first] = matches;
3814
+ if (first === undefined) {
3815
+ return e2eErr({ code: "LOCATOR_NOT_FOUND", message: `${describeExpression(expr)} matched no elements`, ...screen });
3816
+ }
3817
+ if (matches.length === 1)
3818
+ return ok(first);
3819
+ const listed = matches.slice(0, MAX_LISTED).map((entry) => ` ${describeEntry(entry)}`);
3820
+ if (matches.length > MAX_LISTED)
3821
+ listed.push(` ... and ${matches.length - MAX_LISTED} more`);
3822
+ return e2eErr({
3823
+ code: "LOCATOR_AMBIGUOUS",
3824
+ message: `${describeExpression(expr)} matched ${matches.length} elements:
3825
+ ${listed.join(`
3826
+ `)}`,
3827
+ matches: matches.length,
3828
+ ...screen
3829
+ });
3830
+ }
3831
+ function describeMatch(match) {
3832
+ return typeof match === "string" ? JSON.stringify(match) : String(match);
3833
+ }
3834
+ function describeOptions(options) {
3835
+ const present = options.filter(([, value]) => value !== undefined);
3836
+ return present.length === 0 ? "" : `{ ${present.map(([key, value]) => `${key}: ${String(value)}`).join(", ")} }`;
3837
+ }
3838
+ function describeQuery(query) {
3839
+ switch (query.kind) {
3840
+ case "testId":
3841
+ return `getByTestId(${[describeMatch(query.value), describeOptions([["visible", query.visible]])].filter(Boolean).join(", ")})`;
3842
+ case "role": {
3843
+ const options = describeOptions([
3844
+ ["exact", query.exact],
3845
+ ["checked", query.checked],
3846
+ ["disabled", query.disabled],
3847
+ ["selected", query.selected],
3848
+ ["visible", query.visible]
3849
+ ]);
3850
+ const args = [
3851
+ JSON.stringify(query.role),
3852
+ query.name === undefined ? "" : describeMatch(query.name),
3853
+ options
3854
+ ].filter(Boolean);
3855
+ return `getByRole(${args.join(", ")})`;
3856
+ }
3857
+ case "selector":
3858
+ return `locator(${JSON.stringify(formatSelector(query.selector))})`;
3859
+ case "text":
3860
+ case "label":
3861
+ case "placeholder":
3862
+ case "value": {
3863
+ const method = { text: "getByText", label: "getByLabel", placeholder: "getByPlaceholder", value: "getByValue" }[query.kind];
3864
+ const options = describeOptions([
3865
+ ["exact", query.exact],
3866
+ ["visible", query.visible]
3867
+ ]);
3868
+ return `${method}(${[describeMatch(query.value), options].filter(Boolean).join(", ")})`;
3869
+ }
3870
+ }
3871
+ }
3872
+ function describeExpression(expr) {
3873
+ switch (expr.kind) {
3874
+ case "query":
3875
+ return expr.scope === undefined ? describeQuery(expr.query) : `${describeExpression(expr.scope)}.${describeQuery(expr.query)}`;
3876
+ case "filter": {
3877
+ const parts = [
3878
+ expr.hasText === undefined ? "" : `hasText: ${describeMatch(expr.hasText)}`,
3879
+ expr.has === undefined ? "" : `has: ${describeExpression(expr.has)}`
3880
+ ].filter(Boolean);
3881
+ return `${describeExpression(expr.source)}.filter({ ${parts.join(", ")} })`;
3882
+ }
3883
+ case "index": {
3884
+ const source = describeExpression(expr.source);
3885
+ if (expr.index === "first")
3886
+ return `${source}.first()`;
3887
+ if (expr.index === "last")
3888
+ return `${source}.last()`;
3889
+ return `${source}.nth(${expr.index})`;
3890
+ }
3891
+ }
3892
+ }
3893
+
3894
+ // ../../packages/e2e/src/screen/react-locator.ts
3895
+ async function locateByTestIdViaReact(facet, testId, view, options = {}) {
3896
+ const treeOptions = {
3897
+ ...view.platform === "android" ? { pixelRatio: view.screen.scale } : {},
3898
+ ...options.screenName === undefined ? {} : { screen: options.screenName },
3899
+ ...options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }
3900
+ };
3901
+ let result;
3902
+ try {
3903
+ result = await facet.tree(treeOptions);
3904
+ } catch {
3905
+ return [];
3906
+ }
3907
+ if (!result.success)
3908
+ return [];
3909
+ const matches = [];
3910
+ for (const component of result.data.components) {
3911
+ const { testID, rect } = component;
3912
+ if (testID === undefined || rect === undefined || !matchesText(testID, testId))
3913
+ continue;
3914
+ matches.push({
3915
+ frame: rect,
3916
+ tapPoint: { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 },
3917
+ source: "react",
3918
+ testId: testID,
3919
+ name: component.name
3920
+ });
3921
+ }
3922
+ return matches;
3923
+ }
3924
+ function reactTapPoint(match, position) {
3925
+ if (position === undefined)
3926
+ return match.tapPoint;
3927
+ return { x: match.frame.x + position.x, y: match.frame.y + position.y };
3928
+ }
3929
+
3930
+ // ../../packages/e2e/src/screen/locator.ts
3931
+ var WAIT_STATES = ["attached", "detached", "visible", "hidden"];
3932
+ var DEFAULT_MAX_SWIPES = 10;
3933
+ var VERIFY_MS = 1000;
3934
+ var CONFIRM_FLOOR_MS = 500;
3935
+ var SCROLL_SETTLE = { quietMs: 150, timeoutMs: 1000 };
3936
+ function assertOptions(verb, options, allowed) {
3937
+ const unknown = Object.keys(options).filter((key) => !allowed.includes(key));
3938
+ if (unknown.length > 0) {
3939
+ throw new E2eError("INVALID_ARGUMENT", `${verb} does not take ${unknown.map((key) => `"${key}"`).join(", ")}; it takes ${allowed.join(", ")}`);
3940
+ }
3941
+ }
3942
+ var REACT_ONLY = "exists only in the React tree, not in the accessibility tree, so only tap() and boundingBox() work on it; " + "expose it to accessibility (accessible, accessibilityLabel) to use anything else";
3943
+ function plainTestId(expr) {
3944
+ if (expr.kind !== "query" || expr.scope !== undefined || expr.query.kind !== "testId")
3945
+ return;
3946
+ return expr.query.value;
3947
+ }
3948
+ var LOCATOR_BRAND = Symbol.for("@delacour/warden-e2e/locator");
3949
+ function isLocator(value) {
3950
+ return typeof value === "object" && value !== null && LOCATOR_BRAND in value;
3951
+ }
3952
+
3953
+ class Locator {
3954
+ expr;
3955
+ rt;
3956
+ [LOCATOR_BRAND] = true;
3957
+ constructor(expr, rt) {
3958
+ this.expr = expr;
3959
+ this.rt = rt;
3960
+ }
3961
+ toString() {
3962
+ return describeExpression(this.expr);
3963
+ }
3964
+ scoped(query) {
3965
+ return new Locator({ kind: "query", query, scope: this.expr }, this.rt);
3966
+ }
3967
+ getByTestId(id, options) {
3968
+ return this.scoped(testIdQuery(id, options));
3969
+ }
3970
+ getByText(text, options) {
3971
+ return this.scoped(textQuery("text", text, options));
3972
+ }
3973
+ getByLabel(text, options) {
3974
+ return this.scoped(textQuery("label", text, options));
3975
+ }
3976
+ getByPlaceholder(text, options) {
3977
+ return this.scoped(textQuery("placeholder", text, options));
3978
+ }
3979
+ getByValue(value, options) {
3980
+ return this.scoped(textQuery("value", value, options));
3981
+ }
3982
+ getByRole(role, nameOrOptions, options) {
3983
+ return this.scoped(roleQuery(role, nameOrOptions, options));
3984
+ }
3985
+ locator(selector) {
3986
+ return new Locator(withScope(selectorExpression(selector), this.expr), this.rt);
3987
+ }
3988
+ filter(options) {
3989
+ const unknown = Object.keys(options).filter((key) => key !== "hasText" && key !== "has");
3990
+ if (unknown.length > 0) {
3991
+ throw new E2eError("INVALID_LOCATOR", `filter takes "hasText" and "has", not ${unknown.map((k) => `"${k}"`).join(", ")}`);
3992
+ }
3993
+ const { hasText, has } = options;
3994
+ if (hasText === undefined && has === undefined) {
3995
+ throw new E2eError("INVALID_LOCATOR", 'filter needs "hasText" or "has"');
3996
+ }
3997
+ if (has !== undefined && !(has instanceof Locator)) {
3998
+ throw new E2eError("INVALID_LOCATOR", 'filter "has" must be a locator made by screen');
3999
+ }
4000
+ return new Locator({
4001
+ kind: "filter",
4002
+ source: this.expr,
4003
+ ...hasText === undefined ? {} : { hasText },
4004
+ ...has === undefined ? {} : { has: has.expr }
4005
+ }, this.rt);
4006
+ }
4007
+ first() {
4008
+ return new Locator({ kind: "index", source: this.expr, index: "first" }, this.rt);
4009
+ }
4010
+ last() {
4011
+ return new Locator({ kind: "index", source: this.expr, index: "last" }, this.rt);
4012
+ }
4013
+ nth(index) {
4014
+ if (!Number.isInteger(index) || index < 0) {
4015
+ throw new E2eError("INVALID_LOCATOR", `nth takes a non-negative integer, got ${String(index)}`);
4016
+ }
4017
+ return new Locator({ kind: "index", source: this.expr, index }, this.rt);
4018
+ }
4019
+ get runtime() {
4020
+ return this.rt;
4021
+ }
4022
+ async look(options = {}) {
4023
+ const observed = await this.rt.observe(options);
4024
+ return { entries: resolve2(this.expr, observed.index), observed };
4025
+ }
4026
+ matches() {
4027
+ return this.look();
4028
+ }
4029
+ async single() {
4030
+ const { entries, observed } = await this.matches();
4031
+ if (entries.length === 0 && (await this.reactCandidates(observed)).length > 0) {
4032
+ throw new E2eError("NOT_ACTIONABLE", `${this} ${REACT_ONLY}`, this.rt.excerpt(observed.snapshot));
4033
+ }
4034
+ return { entry: this.exactlyOne(entries, observed), observed };
4035
+ }
4036
+ async reactCandidates(observed) {
4037
+ const testId = plainTestId(this.expr);
4038
+ return testId === undefined ? [] : this.rt.reactMatches(testId, observed);
4039
+ }
4040
+ exactlyOne(entries, observed) {
4041
+ return pickOne(this.rt, this.expr, entries, observed);
4042
+ }
4043
+ traced(name, run) {
4044
+ return this.rt.span(`read.${name}`, run, { detail: () => ({ locator: this.toString() }) });
4045
+ }
4046
+ count() {
4047
+ return this.traced("count", async () => (await this.matches()).entries.length);
4048
+ }
4049
+ async all() {
4050
+ const count = await this.count();
4051
+ return Array.from({ length: count }, (_, index) => this.nth(index));
4052
+ }
4053
+ text() {
4054
+ return this.traced("text", async () => textOf2((await this.single()).entry));
4055
+ }
4056
+ allTexts() {
4057
+ return this.traced("allTexts", async () => (await this.matches()).entries.map(textOf2));
4058
+ }
4059
+ value() {
4060
+ return this.traced("value", async () => fieldValue((await this.single()).entry));
4061
+ }
4062
+ isVisible() {
4063
+ return this.traced("isVisible", async () => this.visibleIn((await this.matches()).observed));
4064
+ }
4065
+ read() {
4066
+ return this.traced("read", async () => {
4067
+ const { entries, observed } = await this.matches();
4068
+ if (entries.length === 0)
4069
+ return { visible: false };
4070
+ const entry = this.exactlyOne(entries, observed);
4071
+ return {
4072
+ visible: entry.visible,
4073
+ text: textOf2(entry),
4074
+ value: fieldValue(entry),
4075
+ ...entry.states.checked === undefined ? {} : { checked: entry.states.checked }
4076
+ };
4077
+ });
4078
+ }
4079
+ visibleIn(observed) {
4080
+ const entries = resolve2(this.expr, observed.index);
4081
+ if (entries.length === 0)
4082
+ return false;
4083
+ return this.exactlyOne(entries, observed).visible;
4084
+ }
4085
+ async isHidden() {
4086
+ return !await this.isVisible();
4087
+ }
4088
+ isEnabled() {
4089
+ return this.traced("isEnabled", async () => !(await this.single()).entry.states.disabled);
4090
+ }
4091
+ isChecked() {
4092
+ return this.traced("isChecked", async () => (await this.single()).entry.states.checked === true);
4093
+ }
4094
+ boundingBox() {
4095
+ return this.traced("boundingBox", async () => {
4096
+ const { entries, observed } = await this.look({ reuse: false });
4097
+ if (entries.length === 0) {
4098
+ const [only, ...others] = await this.reactCandidates(observed);
4099
+ if (only !== undefined && others.length === 0)
4100
+ return { ...only.frame };
4101
+ }
4102
+ return { ...this.exactlyOne(entries, observed).element.frame };
4103
+ });
4104
+ }
4105
+ waitFor(options = {}) {
4106
+ return this.traced("waitFor", () => this.waitForState(options));
4107
+ }
4108
+ async waitForState(options) {
4109
+ assertOptions("waitFor", options, ["state", "timeout"]);
4110
+ const state = options.state ?? "visible";
4111
+ if (!WAIT_STATES.includes(state)) {
4112
+ throw new E2eError("INVALID_ARGUMENT", `waitFor state must be one of ${WAIT_STATES.join(", ")}, got "${String(state)}"`);
4113
+ }
4114
+ const timeoutMs = options.timeout ?? this.rt.actionTimeout();
4115
+ const result = await this.rt.poll(async (attempt) => {
4116
+ const observation = await this.rt.tryObserve({ reuse: attempt === 1 });
4117
+ if (!observation.ok) {
4118
+ if (!observation.error.retryable)
4119
+ throw new E2eError("DRIVER", `snapshot failed: ${observation.error.message}`);
4120
+ return { done: false, state: undefined };
4121
+ }
4122
+ const { observed } = observation;
4123
+ const entries = resolve2(this.expr, observed.index);
4124
+ const entry = entries.length === 0 ? undefined : this.exactlyOne(entries, observed);
4125
+ return reached(state, entry) ? { done: true, value: true } : { done: false, state: observed.snapshot };
4126
+ }, timeoutMs);
4127
+ if (result.status === "done")
4128
+ return;
4129
+ const message = `${this} did not become ${state} within ${timeoutMs} ms`;
4130
+ throw new E2eError("TIMEOUT", message, result.last === undefined ? undefined : this.rt.excerpt(result.last));
4131
+ }
4132
+ act(verb, run, suffix = "") {
4133
+ return this.rt.step(`${verb} ${this}${suffix}`, run);
4134
+ }
4135
+ async actionable(spec, timeoutMs, allowReact = false) {
4136
+ let extraLooks = 0;
4137
+ const result = await this.rt.poll(async () => {
4138
+ for (;; ) {
4139
+ const step = await this.actionableLook(spec, allowReact, extraLooks >= MAX_STABILITY_LOOKS);
4140
+ if (step !== "moving")
4141
+ return step;
4142
+ extraLooks += 1;
4143
+ }
4144
+ }, timeoutMs);
4145
+ if (result.status === "done")
4146
+ return result.value;
4147
+ const seen = result.last;
4148
+ if (seen?.kind === "blocked") {
4149
+ throw new E2eError("NOT_ACTIONABLE", `${this} ${seen.reason} (waited ${timeoutMs} ms)`, this.rt.excerpt(seen.snapshot));
4150
+ }
4151
+ if (seen?.kind === "react") {
4152
+ throw new E2eError("NOT_ACTIONABLE", `${this} ${REACT_ONLY} (waited ${timeoutMs} ms)`, this.rt.excerpt(seen.snapshot), seen.matches);
4153
+ }
4154
+ if (seen?.kind === "driver")
4155
+ throw new E2eError("DRIVER", `snapshot kept failing: ${seen.message}`);
4156
+ throw new E2eError("LOCATOR_NOT_FOUND", `${this} matched no elements within ${timeoutMs} ms`, seen === undefined ? undefined : this.rt.excerpt(seen.snapshot));
4157
+ }
4158
+ async actionableLook(spec, allowReact, anyFrame) {
4159
+ const observation = await this.rt.tryObserve({ reuse: false, withPrevious: true });
4160
+ if (!observation.ok) {
4161
+ if (!observation.error.retryable) {
4162
+ throw new E2eError("DRIVER", `snapshot failed: ${observation.error.code}: ${observation.error.message}`);
4163
+ }
4164
+ return { done: false, state: { kind: "driver", message: observation.error.message } };
4165
+ }
4166
+ const { observed } = observation;
4167
+ const entries = resolve2(this.expr, observed.index);
4168
+ if (entries.length === 0)
4169
+ return this.reactStep(spec, observed, allowReact);
4170
+ const entry = this.exactlyOne(entries, observed);
4171
+ const reason = blockedReason(entry, spec, observed);
4172
+ if (reason !== undefined)
4173
+ return { done: false, state: { kind: "blocked", reason, snapshot: observed.snapshot } };
4174
+ if (!anyFrame && !this.heldStill(entry, observation.earlier))
4175
+ return "moving";
4176
+ return { done: true, value: { entry, observed } };
4177
+ }
4178
+ heldStill(entry, earlier) {
4179
+ if (earlier === undefined)
4180
+ return true;
4181
+ const { previous, sinceInputMs } = earlier;
4182
+ if (previous === undefined)
4183
+ return sinceInputMs === undefined || sinceInputMs >= STABLE_INPUT_GAP_MS;
4184
+ const [before, ...others] = resolve2(this.expr, previous.index);
4185
+ return before !== undefined && others.length === 0 && sameFrame(before.element.frame, entry.element.frame);
4186
+ }
4187
+ async aimFromKept(spec, position) {
4188
+ const driver = await this.rt.host.driver();
4189
+ if (driver.capabilities.cheapHitTest !== true || !canObserve(driver))
4190
+ return;
4191
+ if (this.rt.host.answerSystemAlert !== undefined)
4192
+ return;
4193
+ const kept = driver.peek?.();
4194
+ if (kept === undefined || kept.ageMs >= OBSERVATION_MAX_AGE_MS[driver.platform] || showsAlert(driver, kept.snapshot))
4195
+ return;
4196
+ const observation = await this.rt.tryObserve({ reuse: true, alerts: false });
4197
+ if (!observation.ok || observation.observed.snapshot !== kept.snapshot)
4198
+ return;
4199
+ const { observed } = observation;
4200
+ const [entry, ...others] = resolve2(this.expr, observed.index);
4201
+ if (entry === undefined || others.length > 0 || blockedReason(entry, spec, observed) !== undefined)
4202
+ return;
4203
+ const hit = await observed.driver.elementAtPoint(tapPointOf(entry, position));
4204
+ if (!hit.success || hit.data === null || !sameElement(hit.data, entry.element))
4205
+ return;
4206
+ return { entry, observed };
4207
+ }
4208
+ async tap(options = {}) {
4209
+ assertOptions("tap", options, ["position", "timeout", "until", "retries", "windowMs", "pollMs"]);
4210
+ const retry = retryPlan(options);
4211
+ const suffix = retry === undefined ? "" : ` until ${describeUntil(retry.until)}`;
4212
+ return this.act("tap", async () => {
4213
+ let before = await this.tapOnce(options);
4214
+ if (retry === undefined)
4215
+ return;
4216
+ for (let resent = 0;resent < retry.retries; resent += 1) {
4217
+ const outcome = await this.arrivedWithin(retry, snapshotHash(before));
4218
+ if (outcome !== "press-again")
4219
+ return;
4220
+ before = await this.tapOnce(options);
4221
+ }
4222
+ }, suffix);
4223
+ }
4224
+ async tapOnce(options) {
4225
+ const withPosition = options.position !== undefined;
4226
+ const spec = { enabled: !withPosition, tapPointOnScreen: !withPosition };
4227
+ const found = (options.until === undefined ? await this.aimFromKept(spec, options.position) : undefined) ?? await this.actionable(spec, options.timeout ?? this.rt.actionTimeout(), true);
4228
+ const point = "react" in found ? reactTapPoint(found.react, options.position) : tapPointOf(found.entry, options.position);
4229
+ this.rt.drive("tap", await found.observed.driver.tap(point));
4230
+ return found.observed.snapshot;
4231
+ }
4232
+ async arrivedWithin(plan, pressedOn) {
4233
+ const { until } = plan;
4234
+ const watch = { pressedOn, started: this.rt.clock.now(), changed: false, unchanged: 0 };
4235
+ const result = await this.rt.poll(async (attempt) => {
4236
+ if (typeof until !== "function")
4237
+ return this.checkLocatorUntil(until, watch, attempt);
4238
+ if (attempt > 1)
4239
+ await this.rt.forgetObservation();
4240
+ return await until() ? { done: true, value: "arrived" } : { done: false, state: undefined };
4241
+ }, plan.windowMs, { intervalMs: plan.pollMs });
4242
+ if (result.status === "done")
4243
+ return result.value;
4244
+ return watch.changed ? "landed" : "press-again";
4245
+ }
4246
+ async checkLocatorUntil(until, watch, attempt) {
4247
+ const observation = await until.runtime.tryObserve({ reuse: attempt === 1 });
4248
+ if (!observation.ok) {
4249
+ if (!observation.error.retryable) {
4250
+ throw new E2eError("DRIVER", `snapshot failed: ${observation.error.code}: ${observation.error.message}`);
4251
+ }
4252
+ watch.unchanged = 0;
4253
+ return { done: false, state: undefined };
4254
+ }
4255
+ if (until.visibleIn(observation.observed))
4256
+ return { done: true, value: "arrived" };
4257
+ if (snapshotHash(observation.observed.snapshot) === watch.pressedOn)
4258
+ watch.unchanged += 1;
4259
+ else {
4260
+ watch.changed = true;
4261
+ watch.unchanged = 0;
4262
+ }
4263
+ const dropped = !watch.changed && watch.unchanged >= UNCHANGED_CHECKS && this.rt.clock.now() - watch.started >= UNCHANGED_AFTER_MS;
4264
+ return dropped ? { done: true, value: "press-again" } : { done: false, state: undefined };
4265
+ }
4266
+ async longPress(options = {}) {
4267
+ assertOptions("longPress", options, ["durationMs", "timeout"]);
4268
+ return this.act("longPress", async () => {
4269
+ const { entry, observed } = await this.actionable({ enabled: true, tapPointOnScreen: true }, options.timeout ?? this.rt.actionTimeout());
4270
+ const opts = options.durationMs === undefined ? undefined : { durationMs: options.durationMs };
4271
+ this.rt.drive("longPress", await observed.driver.longPress(tapPointOf(entry), opts));
4272
+ });
4273
+ }
4274
+ async fill(text, options = {}) {
4275
+ assertOptions("fill", options, ["timeout"]);
4276
+ return this.act("fill", async () => {
4277
+ const budget = this.rt.budget(options.timeout);
4278
+ let target = await this.actionable({ enabled: true, tapPointOnScreen: false }, budget.remaining());
4279
+ let firstHeld;
4280
+ for (let attempt = 1;; attempt += 1) {
4281
+ const point = focusPoint(target.entry.element.frame);
4282
+ this.rt.drive("fill", await focusClearType(target.observed.driver, this.pause, point, target.entry, text, clearMethodFor(attempt)));
4283
+ if (target.entry.secure)
4284
+ return;
4285
+ const outcome = await this.confirm(target, text, budget);
4286
+ if (outcome.ok)
4287
+ return;
4288
+ if (attempt === 2) {
4289
+ const earlier = firstHeld === undefined ? "" : ` (first attempt held ${JSON.stringify(firstHeld)})`;
4290
+ throw new E2eError("ACTION_FAILED", `${this}: fill did not take: expected ${JSON.stringify(text)} but the field holds ${JSON.stringify(outcome.held)}${earlier}`, outcome.screen);
4291
+ }
4292
+ firstHeld = outcome.held;
4293
+ target = await this.actionable({ enabled: true, tapPointOnScreen: false }, budget.remaining());
4294
+ }
4295
+ });
4296
+ }
4297
+ async clear(options = {}) {
4298
+ assertOptions("clear", options, ["timeout"]);
4299
+ return this.act("clear", async () => {
4300
+ const budget = this.rt.budget(options.timeout);
4301
+ let target = await this.actionable({ enabled: true, tapPointOnScreen: false }, budget.remaining());
4302
+ for (let attempt = 1;; attempt += 1) {
4303
+ await this.focusAndClear(target, clearMethodFor(attempt));
4304
+ if (target.entry.secure)
4305
+ return;
4306
+ const outcome = await this.confirm(target, "", budget);
4307
+ if (outcome.ok)
4308
+ return;
4309
+ if (attempt === 2) {
4310
+ throw new E2eError("ACTION_FAILED", `${this}: clear did not empty the field: it holds ${JSON.stringify(outcome.held)}`, outcome.screen);
4311
+ }
4312
+ target = await this.actionable({ enabled: true, tapPointOnScreen: false }, budget.remaining());
4313
+ }
4314
+ });
4315
+ }
4316
+ async type(text, options = {}) {
4317
+ assertOptions("type", options, ["timeout"]);
4318
+ return this.act("type", async () => {
4319
+ const target = await this.actionable({ enabled: true, tapPointOnScreen: false }, options.timeout ?? this.rt.actionTimeout());
4320
+ await this.focus(target);
4321
+ this.rt.drive("type", await typeText(target.observed.driver, this.pause, text));
4322
+ });
4323
+ }
4324
+ async press(key, options = {}) {
4325
+ assertOptions("press", options, ["modifiers", "timeout"]);
4326
+ return this.act("press", async () => {
4327
+ const target = await this.actionable({ enabled: true, tapPointOnScreen: false }, options.timeout ?? this.rt.actionTimeout());
4328
+ await this.focus(target);
4329
+ this.rt.drive("press", await target.observed.driver.key(key, options.modifiers));
4330
+ });
4331
+ }
4332
+ async swipe(options) {
4333
+ assertOptions("swipe", options, ["direction", "momentum", "timeout"]);
4334
+ if (options.direction === undefined) {
4335
+ throw new E2eError("INVALID_ARGUMENT", "a locator swipe needs a direction; paths are only for screen.swipe");
4336
+ }
4337
+ return this.act("swipe", async () => {
4338
+ const { entry, observed } = await this.actionable({ enabled: false, tapPointOnScreen: false }, options.timeout ?? this.rt.actionTimeout());
4339
+ const vector = swipeVector(options.direction, visibleRect(entry.element.frame, observed.snapshot.screen), options.momentum);
4340
+ this.rt.drive("swipe", await observed.driver.swipe(vector.from, vector.to, vector.options));
4341
+ });
4342
+ }
4343
+ async pinch(scale2, options = {}) {
4344
+ assertOptions("pinch", options, ["durationMs", "radius", "timeout"]);
4345
+ assertScale(scale2);
4346
+ return this.act("pinch", async () => {
4347
+ const { entry, observed } = await this.actionable({ enabled: false, tapPointOnScreen: false }, options.timeout ?? this.rt.actionTimeout());
4348
+ const screen = observed.snapshot.screen;
4349
+ const rect = visibleRect(entry.element.frame, screen);
4350
+ requireGesture(observed.driver, "pinch", "locator.pinch");
4351
+ this.rt.drive("pinch", await observed.driver.pinch(centreOf(rect), scale2, {
4352
+ durationMs: options.durationMs ?? PINCH_MS,
4353
+ radius: options.radius ?? pinchRadius(rect, scale2, screen)
4354
+ }));
4355
+ }, ` \xD7${scale2}`);
4356
+ }
4357
+ async rotate(degrees, options = {}) {
4358
+ assertOptions("rotate", options, ["durationMs", "radius", "timeout"]);
4359
+ assertDegrees(degrees);
4360
+ return this.act("rotate", async () => {
4361
+ const { entry, observed } = await this.actionable({ enabled: false, tapPointOnScreen: false }, options.timeout ?? this.rt.actionTimeout());
4362
+ const screen = observed.snapshot.screen;
4363
+ const rect = visibleRect(entry.element.frame, screen);
4364
+ requireGesture(observed.driver, "pinch", "locator.rotate");
4365
+ this.rt.drive("rotate", await observed.driver.rotate(centreOf(rect), degrees, {
4366
+ durationMs: options.durationMs ?? ROTATE_MS,
4367
+ radius: options.radius ?? rotateRadius(rect, screen)
4368
+ }));
4369
+ }, ` ${degrees}\xB0`);
4370
+ }
4371
+ scrollTo(options = {}) {
4372
+ return scrollUntilVisible(this.rt, this, options);
4373
+ }
4374
+ async reactStep(spec, observed, allowReact) {
4375
+ const found = await this.reactCandidates(observed);
4376
+ const [match, ...others] = found;
4377
+ if (match === undefined)
4378
+ return { done: false, state: { kind: "none", snapshot: observed.snapshot } };
4379
+ if (!allowReact)
4380
+ return { done: false, state: { kind: "react", snapshot: observed.snapshot, matches: found.length } };
4381
+ if (others.length > 0) {
4382
+ throw new E2eError("LOCATOR_AMBIGUOUS", `${this} matched ${found.length} components in the React tree`, this.rt.excerpt(observed.snapshot), found.length);
4383
+ }
4384
+ if (spec.tapPointOnScreen && !isOnScreen(match.tapPoint, observed.snapshot.screen)) {
4385
+ const reason = "(React tree) has its tap point outside the screen";
4386
+ return { done: false, state: { kind: "blocked", reason, snapshot: observed.snapshot } };
4387
+ }
4388
+ return { done: true, value: { react: match, observed } };
4389
+ }
4390
+ async focus({ entry, observed }) {
4391
+ if (entry.states.focused)
4392
+ return;
4393
+ this.rt.drive("focus", await tapToFocus(observed.driver, this.pause, focusPoint(entry.element.frame), entry));
4394
+ }
4395
+ async focusAndClear({ entry, observed }, method) {
4396
+ this.rt.drive("clear", await focusAndClear(observed.driver, this.pause, focusPoint(entry.element.frame), entry, method));
4397
+ }
4398
+ pause = (ms) => this.rt.clock.sleep(ms, this.rt.host.signal);
4399
+ async confirm({ entry, observed }, text, budget) {
4400
+ const span = Math.max(budget.remaining(), CONFIRM_FLOOR_MS);
4401
+ let lastSeen;
4402
+ const readValue2 = (found) => text === "" ? fieldValue(found) : rawValue(found);
4403
+ const outcome = await confirmFieldValue({
4404
+ text,
4405
+ now: this.rt.clock.now,
4406
+ sleep: this.pause,
4407
+ budgetMs: Math.min(VERIFY_MS, span),
4408
+ reads: confirmReadsFor(entry, text),
4409
+ maxMs: span,
4410
+ read: async () => {
4411
+ const hit = await hitTestField(observed.driver, entry);
4412
+ if (hit.ok && hit.entry !== undefined)
4413
+ return { ok: true, reading: { kind: "value", value: readValue2(hit.entry) } };
4414
+ const observation = await this.rt.tryObserve({ reuse: false, alerts: false });
4415
+ if (!observation.ok)
4416
+ return { ok: true, reading: { kind: "unreadable", note: "(unreadable)" } };
4417
+ lastSeen = observation.observed.snapshot;
4418
+ const found = resolve2(this.expr, observation.observed.index);
4419
+ const [only] = found;
4420
+ if (found.length !== 1 || only === undefined) {
4421
+ return { ok: true, reading: { kind: "unreadable", note: `(${found.length} matches)` } };
4422
+ }
4423
+ return { ok: true, reading: { kind: "value", value: readValue2(only) } };
4424
+ }
4425
+ });
4426
+ if (outcome.status === "confirmed")
4427
+ return { ok: true };
4428
+ const held = outcome.status === "mismatch" ? outcome.held : "(unreadable)";
4429
+ return { ok: false, held, screen: lastSeen === undefined ? undefined : this.rt.excerpt(lastSeen) };
4430
+ }
4431
+ }
4432
+ function pickOne(rt, expr, entries, observed) {
4433
+ const [only] = entries;
4434
+ if (only !== undefined && entries.length === 1)
4435
+ return only;
4436
+ const result = exactlyOne(expr, entries, { screen: rt.excerpt(observed.snapshot) });
4437
+ if (!result.success)
4438
+ throw result.error;
4439
+ return result.data;
4440
+ }
4441
+ function textOf2(entry) {
4442
+ return normalizeText(entry.name ?? "") || entry.subtreeText;
4443
+ }
4444
+ var STABLE_INPUT_GAP_MS = 300;
4445
+ var MAX_STABILITY_LOOKS = 2;
4446
+ function sameElement(hit, seen) {
4447
+ return hit.role === seen.role && hit.identifier === seen.identifier && hit.label === seen.label && hit.enabled === seen.enabled && sameFrame(hit.frame, seen.frame);
4448
+ }
4449
+ function showsAlert(driver, snapshot) {
4450
+ return snapshot.modal !== undefined || canDetectAlerts(driver) && driver.alertIn(snapshot) !== null;
4451
+ }
4452
+ function sameFrame(a, b) {
4453
+ return Math.round(a.x) === Math.round(b.x) && Math.round(a.y) === Math.round(b.y) && Math.round(a.width) === Math.round(b.width) && Math.round(a.height) === Math.round(b.height);
4454
+ }
4455
+ var TAP_RETRIES = 2;
4456
+ var TAP_WINDOW_MS = 4000;
4457
+ var TAP_CHECK_MS = 250;
4458
+ var UNCHANGED_CHECKS = 2;
4459
+ var UNCHANGED_AFTER_MS = 750;
4460
+ function wholeNumber(name, value, atLeast) {
4461
+ if (!Number.isInteger(value) || value < atLeast) {
4462
+ throw new E2eError("INVALID_ARGUMENT", `tap ${name} must be an integer of at least ${atLeast}, got ${String(value)}`);
4463
+ }
4464
+ }
4465
+ function retryPlan(options) {
4466
+ const { until, retries, windowMs, pollMs } = options;
4467
+ if (until === undefined) {
4468
+ const orphan = ["retries", "windowMs", "pollMs"].filter((key) => options[key] !== undefined);
4469
+ if (orphan.length > 0) {
4470
+ throw new E2eError("INVALID_ARGUMENT", `tap ${orphan.map((key) => `"${key}"`).join(", ")} only apply with "until"`);
4471
+ }
4472
+ return;
4473
+ }
4474
+ if (typeof until !== "function" && !isLocator(until)) {
4475
+ throw new E2eError("INVALID_ARGUMENT", 'tap "until" must be a locator made by screen, or a function returning a promise of a boolean');
4476
+ }
4477
+ const plan = {
4478
+ until,
4479
+ retries: retries ?? TAP_RETRIES,
4480
+ windowMs: windowMs ?? TAP_WINDOW_MS,
4481
+ pollMs: pollMs ?? TAP_CHECK_MS
4482
+ };
4483
+ wholeNumber("retries", plan.retries, 0);
4484
+ wholeNumber("windowMs", plan.windowMs, 1);
4485
+ wholeNumber("pollMs", plan.pollMs, 1);
4486
+ return plan;
4487
+ }
4488
+ function describeUntil(until) {
4489
+ return typeof until === "function" ? "check" : String(until);
4490
+ }
4491
+ function clearMethodFor(attempt) {
4492
+ return attempt === 1 ? "auto" : "counted";
4493
+ }
4494
+ function rawValue(entry) {
4495
+ return entry.value ?? (entry.role === "textbox" || entry.role === "searchbox" ? entry.element.text ?? "" : "");
4496
+ }
4497
+ function reached(state, entry) {
4498
+ switch (state) {
4499
+ case "attached":
4500
+ return entry !== undefined;
4501
+ case "detached":
4502
+ return entry === undefined;
4503
+ case "visible":
4504
+ return entry?.visible === true;
4505
+ case "hidden":
4506
+ return entry === undefined || !entry.visible;
4507
+ }
4508
+ }
4509
+ function blockedReason(entry, spec, observed) {
4510
+ if (!entry.visible)
4511
+ return "is not visible";
4512
+ if (spec.enabled && entry.states.disabled)
4513
+ return "is disabled";
4514
+ if (spec.tapPointOnScreen && !isOnScreen(tapPointOf(entry), observed.snapshot.screen)) {
4515
+ return "has its tap point outside the screen; use scrollTo() to bring it into view";
4516
+ }
4517
+ return;
4518
+ }
4519
+ function isOnScreenEntry(entry, observed) {
4520
+ return entry?.visible === true && isOnScreen(tapPointOf(entry), observed.snapshot.screen);
4521
+ }
4522
+ function scrollGaveUp(target, absent, effort, screen) {
4523
+ if (absent)
4524
+ return new E2eError("LOCATOR_NOT_FOUND", `${target} was not found after ${effort}`, screen);
4525
+ return new E2eError("NOT_ACTIONABLE", `${target} is still not on screen after ${effort}`, screen);
4526
+ }
4527
+ async function scrollUntilVisible(rt, target, options = {}) {
4528
+ assertOptions("scrollUntilVisible", options, ["direction", "momentum", "maxSwipes", "timeout"]);
4529
+ const direction = options.direction ?? "down";
4530
+ const maxSwipes = options.maxSwipes ?? DEFAULT_MAX_SWIPES;
4531
+ if (!Number.isInteger(maxSwipes) || maxSwipes < 0) {
4532
+ throw new E2eError("INVALID_ARGUMENT", `maxSwipes must be a non-negative integer, got ${String(maxSwipes)}`);
4533
+ }
4534
+ return rt.step(`scrollUntilVisible ${target}`, async () => {
4535
+ const budget = rt.budget(options.timeout);
4536
+ for (let swipes = 0;; swipes += 1) {
4537
+ const observed = await rt.observe();
4538
+ const entries = resolve2(target.expr, observed.index);
4539
+ if (entries.length > 1)
4540
+ pickOne(rt, target.expr, entries, observed);
4541
+ const [entry] = entries;
4542
+ if (isOnScreenEntry(entry, observed))
4543
+ return;
4544
+ if (swipes >= maxSwipes || budget.remaining() <= 0) {
4545
+ throw scrollGaveUp(target, entry === undefined, `${swipes} swipes ${direction}`, rt.excerpt(observed.snapshot));
4546
+ }
4547
+ const vector = swipeVector(direction, screenRect(observed.snapshot.screen), options.momentum);
4548
+ rt.drive("scrollUntilVisible", await observed.driver.swipe(vector.from, vector.to, vector.options));
4549
+ await observed.driver.settle(SCROLL_SETTLE);
4550
+ }
4551
+ });
4552
+ }
4553
+
4554
+ // ../../packages/e2e/src/runner/artifacts.ts
4555
+ import { createHash } from "crypto";
4556
+ import { basename, extname, join as join3, relative, sep } from "path";
4557
+ var SLUG_MAX = 60;
4558
+ function slug(text, fallback = "test") {
4559
+ const slugged = text.normalize("NFKD").replace(/[\u0300-\u036F]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, SLUG_MAX).replace(/-+$/g, "");
4560
+ return slugged === "" ? fallback : slugged;
4561
+ }
4562
+ function hash6(...parts) {
4563
+ return createHash("sha1").update(parts.join("\x00")).digest("hex").slice(0, 6);
4564
+ }
4565
+ function attemptDir(out, flowId, title, hash) {
4566
+ return join3(out, flowId, `${slug(title.join(" "))}-${hash.slice(0, 6)}`);
4567
+ }
4568
+ function screenshotPath(dir, n, name) {
4569
+ return join3(dir, "screenshots", `${String(n).padStart(3, "0")}-${slug(name, "screenshot")}.png`);
4570
+ }
4571
+ var SPEC_EXT = /\.e2e$/;
4572
+ function flowIdOf(file, flowsDir) {
4573
+ const strip = (name) => basename(name, extname(name)).replace(SPEC_EXT, "");
4574
+ if (flowsDir !== undefined) {
4575
+ const rel = relative(flowsDir, file);
4576
+ if (rel !== "" && !rel.startsWith("..") && !rel.includes(`..${sep}`)) {
4577
+ const parts = rel.split(sep);
4578
+ const last = parts.pop() ?? rel;
4579
+ return [...parts.map((p) => slug(p, "dir")), slug(strip(last), "flow")].join("/");
4580
+ }
4581
+ }
4582
+ return slug(strip(file), "flow");
4583
+ }
4584
+
4585
+ export { E2eError, noteSpanDetail, addSpanDetail, canObserve, canDetectAlerts, settleDetailOf, DEFAULT_SETTLE_QUIET_MS, DEFAULT_SETTLE_TIMEOUT_MS, settleByHash, snapshotHash, ok, err, swipeVector, screenRect, fieldValue, PINCH_MS, ROTATE_MS, PATH_MS, centreOf, pinchRadius, rotateRadius, requireGesture, assertScale, assertDegrees, isInteractiveRole, resolveRole, normalizeText, matchesText, indexSnapshot, testIdQuery, textQuery, roleQuery, selectorExpression, renderSnapshot, renderHeader, resolve2 as resolve, locateByTestIdViaReact, isLocator, Locator, pickOne, textOf2 as textOf, scrollUntilVisible, ENV_NAMES, DEFAULT_ASSERTION_TIMEOUT_MS, readEnv, runStep, setDefaultDriverFactory, hasDefaultDriverFactory, setDefaultFixtureFactory, hasDefaultFixtureFactory, attemptSession, getSession, hash6, attemptDir, screenshotPath, flowIdOf, callerFile, BUN_TIMEOUT_MARGIN_MS, currentScope, createScope, runInScope, hooksFor, resolveUse, registrationDefaults, markOnly, onlyWasSeen, WORKSPACE_VERSION, WARDEN_E2E_VERSION };