@delacour/warden-e2e 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/preload.mjs CHANGED
@@ -1,93 +1,78 @@
1
1
  // @bun
2
2
  import {
3
- BUN_TIMEOUT_MARGIN_MS,
4
- DEFAULT_ASSERTION_TIMEOUT_MS,
3
+ E2eError2,
4
+ noteSpanDetail,
5
+ addSpanDetail,
6
+ canObserve,
7
+ canDetectAlerts,
8
+ settleDetailOf,
5
9
  DEFAULT_SETTLE_QUIET_MS,
6
10
  DEFAULT_SETTLE_TIMEOUT_MS,
7
- E2eError,
8
- ENV_NAMES,
9
- Locator,
10
- PATH_MS,
11
+ settleByHash,
12
+ snapshotHash,
13
+ ok,
14
+ err,
15
+ swipeVector,
16
+ screenRect,
11
17
  PINCH_MS,
12
18
  ROTATE_MS,
13
- WARDEN_E2E_VERSION,
14
- WORKSPACE_VERSION,
15
- addSpanDetail,
16
- assertDegrees,
17
- assertScale,
18
- canDetectAlerts,
19
- canObserve,
19
+ PATH_MS,
20
20
  centreOf,
21
- err,
22
- getSession,
23
- hasDefaultDriverFactory,
24
- hasDefaultFixtureFactory,
25
- indexSnapshot,
26
- isInteractiveRole,
27
- isLocator,
28
- locateByTestIdViaReact,
29
- noteSpanDetail,
30
- ok,
31
- onlyWasSeen,
32
21
  pinchRadius,
33
- readEnv,
34
- renderHeader,
35
- renderSnapshot,
22
+ rotateRadius,
36
23
  requireGesture,
24
+ assertScale,
25
+ assertDegrees,
26
+ isInteractiveRole,
37
27
  resolveRole,
28
+ indexSnapshot,
29
+ testIdQuery,
30
+ textQuery,
38
31
  roleQuery,
39
- rotateRadius,
40
- screenRect,
41
- screenshotPath,
42
- scrollUntilVisible,
43
32
  selectorExpression,
33
+ renderSnapshot,
34
+ renderHeader,
35
+ locateByTestIdViaReact,
36
+ isLocator,
37
+ Locator,
38
+ scrollUntilVisible,
39
+ bunExec,
40
+ execError,
41
+ driverError,
42
+ driverErr,
43
+ logEventSchema,
44
+ fatalEventSchema,
45
+ responseSchema,
46
+ mapWireError,
47
+ encodeRequest,
48
+ bunSpawn,
49
+ stdioTransport,
50
+ spawnSimd,
51
+ connectSimd,
52
+ ENV_NAMES,
53
+ DEFAULT_ASSERTION_TIMEOUT_MS,
54
+ readEnv,
55
+ setDefaultHidRecovery,
56
+ setDefaultNativeLog,
44
57
  setDefaultDriverFactory,
58
+ hasDefaultDriverFactory,
45
59
  setDefaultFixtureFactory,
46
- settleByHash,
47
- settleDetailOf,
48
- snapshotHash,
49
- swipeVector,
50
- testIdQuery,
51
- textQuery
52
- } from "./chunk-qtkmfjbq.mjs";
60
+ hasDefaultFixtureFactory,
61
+ getSession,
62
+ parseBudgets,
63
+ createDevicePerf,
64
+ spawnWarden,
65
+ screenshotPath,
66
+ realNativeLogDeps,
67
+ BUN_TIMEOUT_MARGIN_MS2,
68
+ onlyWasSeen,
69
+ WORKSPACE_VERSION,
70
+ WARDEN_E2E_VERSION2
71
+ } from "./chunk-7ns30pbp.mjs";
53
72
 
54
73
  // ../../packages/e2e/src/preload.ts
55
74
  import { afterAll, setDefaultTimeout } from "bun:test";
56
75
 
57
- // ../../packages/core/src/exec.ts
58
- import { existsSync } from "fs";
59
- function spawnFailure(cmd, cwd, error) {
60
- if (cwd !== undefined && !existsSync(cwd))
61
- return `cannot run \`${cmd.join(" ")}\`: no such directory ${cwd}`;
62
- return `cannot run \`${cmd.join(" ")}\`: ${error instanceof Error ? error.message : String(error)}`;
63
- }
64
- var bunExec = async (cmd, opts = {}) => {
65
- try {
66
- const proc = Bun.spawn([...cmd], {
67
- cwd: opts.cwd,
68
- env: opts.env ? { ...process.env, ...opts.env } : process.env,
69
- stdout: "pipe",
70
- stderr: "pipe",
71
- stdin: "ignore",
72
- timeout: opts.timeoutMs,
73
- ...opts.signal !== undefined ? { signal: opts.signal } : {}
74
- });
75
- const [stdout, stderr, exitCode] = await Promise.all([
76
- new Response(proc.stdout).text(),
77
- new Response(proc.stderr).text(),
78
- proc.exited
79
- ]);
80
- return { exitCode, stdout, stderr };
81
- } catch (error) {
82
- return { exitCode: 127, stdout: "", stderr: spawnFailure(cmd, opts.cwd, error) };
83
- }
84
- };
85
- function execError(cmd, result) {
86
- const detail = (result.stderr || result.stdout).trim().split(`
87
- `).slice(-3).join(" | ");
88
- return `\`${cmd.join(" ")}\` exited ${result.exitCode}${detail ? `: ${detail}` : ""}`;
89
- }
90
-
91
76
  // ../../packages/core/src/providers/android.ts
92
77
  import { join as join2 } from "path";
93
78
 
@@ -119,26 +104,6 @@ var avdLists = new WeakMap;
119
104
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
120
105
  import { dirname as dirname2 } from "path";
121
106
 
122
- // ../../packages/e2e/src/driver/errors.ts
123
- var RETRYABLE = new Set([
124
- "E_TIMEOUT",
125
- "E_HELPER_CRASHED",
126
- "E_AX_UNAVAILABLE",
127
- "E_APP_NOT_RESPONDING",
128
- "E_STALE_REF"
129
- ]);
130
- function driverError(code, message, extra = {}) {
131
- return {
132
- code,
133
- message,
134
- retryable: extra.retryable ?? RETRYABLE.has(code),
135
- ...extra.detail !== undefined ? { detail: extra.detail } : {}
136
- };
137
- }
138
- function driverErr(code, message, extra) {
139
- return { success: false, error: driverError(code, message, extra) };
140
- }
141
-
142
107
  // ../../packages/e2e/src/driver/gesture-geometry.ts
143
108
  var GESTURE_FRAME_MS = 16;
144
109
  var GESTURE_MAX_FRAMES = 1000;
@@ -331,14 +296,14 @@ function openAdbSession(options) {
331
296
  function check(l) {
332
297
  if (!current)
333
298
  return;
334
- const start2 = l.buffer.indexOf(current.prefix);
335
- if (start2 < 0)
299
+ const start = l.buffer.indexOf(current.prefix);
300
+ if (start < 0)
336
301
  return;
337
- const afterPrefix = start2 + current.prefix.length;
302
+ const afterPrefix = start + current.prefix.length;
338
303
  const m = /^(\d+)__(\r?\n)?/.exec(l.buffer.slice(afterPrefix));
339
304
  if (!m)
340
305
  return;
341
- const stdout = l.buffer.slice(0, start2);
306
+ const stdout = l.buffer.slice(0, start);
342
307
  l.buffer = l.buffer.slice(afterPrefix + m[0].length);
343
308
  const settle = current.settle;
344
309
  current = undefined;
@@ -1231,8 +1196,8 @@ function createDumper(deps) {
1231
1196
  async function fetchRoute(route) {
1232
1197
  if (route === "tty") {
1233
1198
  try {
1234
- const res2 = await deps.exec(dumpTtyHostArgv(deps.adb, deps.serial), { timeoutMs: DUMP_TIMEOUT_MS });
1235
- return { text: res2.stdout };
1199
+ const res = await deps.exec(dumpTtyHostArgv(deps.adb, deps.serial), { timeoutMs: DUMP_TIMEOUT_MS });
1200
+ return { text: res.stdout };
1236
1201
  } catch (error) {
1237
1202
  return { error: { code: "E_IO", message: String(error), retryable: false } };
1238
1203
  }
@@ -1283,452 +1248,8 @@ function createDumper(deps) {
1283
1248
  };
1284
1249
  }
1285
1250
 
1286
- // ../../packages/e2e/src/driver/ios/simd-protocol.ts
1287
- import { z as z2 } from "zod";
1288
- var protocolVersion = 1;
1289
- var rectSchema = z2.object({
1290
- x: z2.number(),
1291
- y: z2.number(),
1292
- width: z2.number(),
1293
- height: z2.number()
1294
- });
1295
- var screenInfoSchema = z2.object({
1296
- width: z2.number(),
1297
- height: z2.number(),
1298
- scale: z2.number(),
1299
- rotation: z2.number().optional()
1300
- });
1301
- var ROLES = [
1302
- "button",
1303
- "text",
1304
- "textfield",
1305
- "securefield",
1306
- "searchfield",
1307
- "image",
1308
- "switch",
1309
- "checkbox",
1310
- "slider",
1311
- "link",
1312
- "cell",
1313
- "list",
1314
- "scrollview",
1315
- "tab",
1316
- "tabbar",
1317
- "alert",
1318
- "sheet",
1319
- "keyboard",
1320
- "navbar",
1321
- "toolbar",
1322
- "window",
1323
- "application",
1324
- "other"
1325
- ];
1326
- var pointSchema = z2.object({ x: z2.number(), y: z2.number() });
1327
- var wireElementSchema = z2.lazy(() => z2.object({
1328
- id: z2.string().nullish(),
1329
- role: z2.enum(ROLES),
1330
- rawType: z2.string(),
1331
- label: z2.string().nullish(),
1332
- identifier: z2.string().nullish(),
1333
- value: z2.string().nullish(),
1334
- text: z2.string().nullish(),
1335
- placeholder: z2.string().nullish(),
1336
- frame: rectSchema,
1337
- normFrame: rectSchema.nullish(),
1338
- traits: z2.array(z2.string()).nullish(),
1339
- enabled: z2.boolean().nullish(),
1340
- focused: z2.boolean().nullish(),
1341
- hidden: z2.boolean().nullish(),
1342
- selected: z2.boolean().nullish(),
1343
- checked: z2.boolean().nullish(),
1344
- hittable: z2.boolean().nullish(),
1345
- tapPoint: pointSchema.nullish(),
1346
- pid: z2.number().nullish(),
1347
- depth: z2.number().nullish(),
1348
- children: z2.array(wireElementSchema).nullish()
1349
- }));
1350
- var ROLE_SET = new Set(ROLES);
1351
- var isBag = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1352
- var isNum = (value) => typeof value === "number" && Number.isFinite(value);
1353
- var isNullish = (value) => value === null || value === undefined;
1354
- function isRect(value) {
1355
- return isBag(value) && isNum(value.x) && isNum(value.y) && isNum(value.width) && isNum(value.height);
1356
- }
1357
- var OPTIONAL_STRINGS = ["id", "label", "identifier", "value", "text", "placeholder"];
1358
- var OPTIONAL_FLAGS = ["enabled", "focused", "hidden", "selected", "checked", "hittable"];
1359
- var isString = (value) => typeof value === "string";
1360
- var isBool = (value) => typeof value === "boolean";
1361
- var isPoint = (value) => isBag(value) && isNum(value.x) && isNum(value.y);
1362
- var isStringArray = (value) => Array.isArray(value) && value.every(isString);
1363
- var orNullish = (value, check) => isNullish(value) || check(value);
1364
- function hasValidOptionals(node) {
1365
- return OPTIONAL_STRINGS.every((key) => orNullish(node[key], isString)) && OPTIONAL_FLAGS.every((key) => orNullish(node[key], isBool)) && orNullish(node.normFrame, isRect) && orNullish(node.traits, isStringArray) && orNullish(node.tapPoint, isPoint) && orNullish(node.pid, isNum) && orNullish(node.depth, isNum);
1366
- }
1367
- function isWireElement(value) {
1368
- if (!isBag(value))
1369
- return false;
1370
- if (!isString(value.role) || !ROLE_SET.has(value.role))
1371
- return false;
1372
- if (!isString(value.rawType) || !isRect(value.frame) || !hasValidOptionals(value))
1373
- return false;
1374
- const { children } = value;
1375
- if (isNullish(children))
1376
- return true;
1377
- return Array.isArray(children) && children.every(isWireElement);
1378
- }
1379
- function wireElementProblem(value) {
1380
- const parsed = wireElementSchema.safeParse(value);
1381
- if (parsed.success)
1382
- return "invalid element";
1383
- const issue = parsed.error.issues[0];
1384
- return issue ? `${issue.path.join(".") || "(element)"}: ${issue.message}` : "invalid element";
1385
- }
1386
- var wireErrorSchema = z2.object({
1387
- code: z2.string(),
1388
- message: z2.string(),
1389
- retryable: z2.boolean().optional(),
1390
- detail: z2.unknown().optional()
1391
- });
1392
- var readyEventSchema = z2.object({
1393
- event: z2.literal("ready"),
1394
- protocol: z2.number(),
1395
- version: z2.string(),
1396
- udid: z2.string(),
1397
- hid: z2.object({ transport: z2.string() }),
1398
- ax: z2.object({ default: z2.string(), bridge: z2.string() }),
1399
- screen: screenInfoSchema
1400
- });
1401
- var logEventSchema = z2.object({
1402
- event: z2.literal("log"),
1403
- level: z2.enum(["debug", "info", "warn", "error"]),
1404
- message: z2.string(),
1405
- op: z2.string().optional(),
1406
- id: z2.string().optional()
1407
- });
1408
- var fatalEventSchema = z2.object({ event: z2.literal("fatal"), error: wireErrorSchema });
1409
- var eventSchema = z2.discriminatedUnion("event", [readyEventSchema, logEventSchema, fatalEventSchema]);
1410
- var responseSchema = z2.discriminatedUnion("ok", [
1411
- z2.object({ id: z2.string(), ok: z2.literal(true), data: z2.unknown().optional() }),
1412
- z2.object({ id: z2.string(), ok: z2.literal(false), error: wireErrorSchema })
1413
- ]);
1414
- function parseLine(line) {
1415
- let json;
1416
- try {
1417
- json = JSON.parse(line);
1418
- } catch {
1419
- return { success: false, reason: "not JSON" };
1420
- }
1421
- if (typeof json === "object" && json !== null && "event" in json) {
1422
- const event = eventSchema.safeParse(json);
1423
- return event.success ? { success: true, message: { kind: "event", event: event.data } } : { success: false, reason: `bad event: ${event.error.message}` };
1424
- }
1425
- const response = responseSchema.safeParse(json);
1426
- return response.success ? { success: true, message: { kind: "response", response: response.data } } : { success: false, reason: `bad response: ${response.error.message}` };
1427
- }
1428
- var healthDataSchema = z2.looseObject({
1429
- version: z2.string(),
1430
- udid: z2.string(),
1431
- hid: z2.object({ transport: z2.string() }),
1432
- ax: z2.object({
1433
- default: z2.string(),
1434
- bridge: z2.enum(["available", "degraded", "missing", "failed"]),
1435
- error: z2.string().optional(),
1436
- lastError: z2.object({ message: z2.string(), agoMs: z2.number() }).optional()
1437
- }),
1438
- screen: screenInfoSchema,
1439
- socket: z2.object({ path: z2.string(), clients: z2.number() }).optional(),
1440
- idb: z2.string().optional(),
1441
- xcode: z2.string().optional(),
1442
- state: z2.string().optional(),
1443
- uptimeMs: z2.number().optional()
1444
- });
1445
- var snapshotDataSchema = z2.looseObject({
1446
- backend: z2.enum(["axbridge", "ax", "uiautomator"]),
1447
- screen: screenInfoSchema,
1448
- modal: z2.object({ pid: z2.number().optional(), description: z2.string().optional() }).nullish(),
1449
- truncated: z2.boolean(),
1450
- elements: z2.array(z2.custom(isWireElement, { error: (issue) => wireElementProblem(issue.input) })),
1451
- ms: z2.number(),
1452
- fallback: z2.object({ from: z2.string(), to: z2.string(), reason: z2.string() }).optional(),
1453
- readMs: z2.number().optional(),
1454
- nodes: z2.number().optional()
1455
- });
1456
- var elementAtPointDataSchema = wireElementSchema.nullable();
1457
- var screenshotDataSchema = z2.looseObject({
1458
- path: z2.string().optional(),
1459
- png: z2.string().optional(),
1460
- width: z2.number().optional(),
1461
- height: z2.number().optional()
1462
- });
1463
- var settleDataSchema = z2.looseObject({
1464
- quiet: z2.boolean(),
1465
- waitedMs: z2.number(),
1466
- via: z2.enum(["stream", "snapshot"]).optional().catch(undefined),
1467
- reason: z2.string().optional(),
1468
- frontmostPid: z2.number().optional()
1469
- });
1470
- var rebindDataSchema = z2.looseObject({
1471
- bound: z2.boolean(),
1472
- waitedMs: z2.number(),
1473
- pid: z2.number().optional(),
1474
- restarts: z2.number().optional(),
1475
- recycled: z2.boolean().optional(),
1476
- reason: z2.string().optional()
1477
- });
1478
- var alertButtonsDataSchema = z2.object({
1479
- title: z2.string().optional(),
1480
- message: z2.string().optional(),
1481
- buttons: z2.array(wireElementSchema)
1482
- }).nullable();
1483
- var frontmostDataSchema = z2.looseObject({
1484
- frontmost: z2.boolean(),
1485
- pid: z2.number().optional(),
1486
- frontmostPid: z2.number().optional()
1487
- });
1488
- var orientationDataSchema = z2.object({
1489
- orientation: z2.enum([
1490
- "portrait",
1491
- "portraitUpsideDown",
1492
- "landscapeLeft",
1493
- "landscapeRight",
1494
- "faceUp",
1495
- "faceDown",
1496
- "unknown"
1497
- ]),
1498
- source: z2.enum(["device", "written", "assumed"]).optional()
1499
- });
1500
- var opSchemas = {
1501
- health: healthDataSchema,
1502
- snapshot: snapshotDataSchema,
1503
- elementAtPoint: elementAtPointDataSchema,
1504
- screenshot: screenshotDataSchema,
1505
- settle: settleDataSchema,
1506
- rebind: rebindDataSchema,
1507
- alertButtons: alertButtonsDataSchema,
1508
- frontmost: frontmostDataSchema,
1509
- orientation: orientationDataSchema
1510
- };
1511
- function hasOpSchema(op) {
1512
- return Object.hasOwn(opSchemas, op);
1513
- }
1514
- var DRIVER_CODES = new Set([
1515
- "E_HELPER_MISSING",
1516
- "E_HELPER_CRASHED",
1517
- "E_TIMEOUT",
1518
- "E_NOT_BOOTED",
1519
- "E_UNKNOWN_DEVICE",
1520
- "E_AX_UNAVAILABLE",
1521
- "E_APP_UNAVAILABLE",
1522
- "E_APP_NOT_RESPONDING",
1523
- "E_NO_ELEMENT",
1524
- "E_STALE_REF",
1525
- "E_UNSUPPORTED",
1526
- "E_UNSUPPORTED_TEXT",
1527
- "E_HID",
1528
- "E_IO",
1529
- "E_BAD_REQUEST",
1530
- "E_INTERNAL"
1531
- ]);
1532
- function isDriverCode(code) {
1533
- return DRIVER_CODES.has(code);
1534
- }
1535
- function mapWireError(wire) {
1536
- const retryable = wire.retryable === undefined ? {} : { retryable: wire.retryable };
1537
- if (wire.code === "E_UNKNOWN_UDID") {
1538
- return driverError("E_UNKNOWN_DEVICE", wire.message, { ...retryable, detail: wire.detail });
1539
- }
1540
- if (isDriverCode(wire.code))
1541
- return driverError(wire.code, wire.message, { ...retryable, detail: wire.detail });
1542
- return driverError("E_INTERNAL", wire.message, {
1543
- ...retryable,
1544
- detail: { wireCode: wire.code, ...wire.detail !== undefined ? { wireDetail: wire.detail } : {} }
1545
- });
1546
- }
1547
- function createLineSplitter() {
1548
- const decoder = new TextDecoder("utf-8");
1549
- let buffer = "";
1550
- return {
1551
- push(chunk) {
1552
- buffer += decoder.decode(chunk, { stream: true });
1553
- const lines = buffer.split(`
1554
- `);
1555
- buffer = lines.pop() ?? "";
1556
- return lines.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line).filter((line) => line.length > 0);
1557
- },
1558
- rest: () => buffer
1559
- };
1560
- }
1561
- function encodeRequest(request) {
1562
- return `${JSON.stringify(request)}
1563
- `;
1564
- }
1565
-
1566
- // ../../packages/e2e/src/driver/ios/simd-transport.ts
1567
- var bunSpawn = (cmd) => {
1568
- const proc = Bun.spawn([...cmd], { stdin: "pipe", stdout: "pipe", stderr: "pipe" });
1569
- return {
1570
- write(data) {
1571
- proc.stdin.write(data);
1572
- proc.stdin.flush();
1573
- },
1574
- end() {
1575
- proc.stdin.end();
1576
- },
1577
- kill() {
1578
- proc.kill();
1579
- },
1580
- stdout: proc.stdout,
1581
- stderr: proc.stderr,
1582
- exited: proc.exited
1583
- };
1584
- };
1585
- async function pump(stream, onLine) {
1586
- const splitter = createLineSplitter();
1587
- const reader = stream.getReader();
1588
- try {
1589
- for (;; ) {
1590
- const { done, value } = await reader.read();
1591
- if (done)
1592
- break;
1593
- for (const line of splitter.push(value))
1594
- onLine(line);
1595
- }
1596
- } catch {}
1597
- const rest = splitter.rest().trim();
1598
- if (rest)
1599
- onLine(rest);
1600
- }
1601
- var STDERR_TAIL = 20;
1602
- var DEFAULT_GRACE_MS = 1500;
1603
- function stdioTransport(opts) {
1604
- const spawn = opts.spawn ?? bunSpawn;
1605
- const graceMs = opts.graceMs ?? DEFAULT_GRACE_MS;
1606
- return async (handlers) => {
1607
- let proc;
1608
- try {
1609
- proc = spawn(opts.cmd);
1610
- } catch (error) {
1611
- const reason = error instanceof Error ? error.message : String(error);
1612
- return {
1613
- success: false,
1614
- error: driverError("E_HELPER_MISSING", `cannot start ${opts.cmd[0]}: ${reason}`, {
1615
- detail: { cmd: opts.cmd }
1616
- })
1617
- };
1618
- }
1619
- const stderrTail = [];
1620
- const stdout = pump(proc.stdout, handlers.onLine);
1621
- const stderr = proc.stderr ? pump(proc.stderr, (line) => {
1622
- stderrTail.push(line);
1623
- if (stderrTail.length > STDERR_TAIL)
1624
- stderrTail.shift();
1625
- handlers.onDiagnostic(line);
1626
- }) : Promise.resolve();
1627
- const exited = proc.exited.catch(() => -1);
1628
- Promise.all([stdout, stderr, exited]).then(([, , exitCode]) => {
1629
- handlers.onClose({ exitCode, stderrTail: [...stderrTail] });
1630
- });
1631
- return {
1632
- success: true,
1633
- data: {
1634
- send: (data) => proc.write(data),
1635
- async close() {
1636
- try {
1637
- proc.end();
1638
- } catch {}
1639
- let timer;
1640
- const timedOut = new Promise((resolve) => {
1641
- timer = setTimeout(() => resolve("timeout"), graceMs);
1642
- });
1643
- const outcome = await Promise.race([exited.then(() => "exited"), timedOut]);
1644
- clearTimeout(timer);
1645
- if (outcome === "timeout") {
1646
- proc.kill();
1647
- await exited;
1648
- }
1649
- }
1650
- }
1651
- };
1652
- };
1653
- }
1654
- function socketTransport(opts) {
1655
- return async (handlers) => {
1656
- const splitter = createLineSplitter();
1657
- const encoder = new TextEncoder;
1658
- let socket;
1659
- let queued = new Uint8Array(0);
1660
- let closedNotified = false;
1661
- const notifyClosed = () => {
1662
- if (closedNotified)
1663
- return;
1664
- closedNotified = true;
1665
- handlers.onClose({ exitCode: null, stderrTail: [] });
1666
- };
1667
- const flush = () => {
1668
- if (!socket || queued.length === 0)
1669
- return;
1670
- const written = socket.write(queued);
1671
- queued = written >= queued.length ? new Uint8Array(0) : queued.slice(Math.max(written, 0));
1672
- };
1673
- try {
1674
- const connected = await Bun.connect({
1675
- unix: opts.socketPath,
1676
- socket: {
1677
- open(s) {
1678
- socket = s;
1679
- },
1680
- data(_s, chunk) {
1681
- for (const line of splitter.push(chunk))
1682
- handlers.onLine(line);
1683
- },
1684
- drain() {
1685
- flush();
1686
- },
1687
- close() {
1688
- notifyClosed();
1689
- },
1690
- error() {
1691
- notifyClosed();
1692
- },
1693
- end() {
1694
- notifyClosed();
1695
- }
1696
- }
1697
- });
1698
- socket = connected;
1699
- } catch (error) {
1700
- const reason = error instanceof Error ? error.message : String(error);
1701
- return {
1702
- success: false,
1703
- error: driverError("E_HELPER_CRASHED", `cannot connect to warden-simd socket ${opts.socketPath}: ${reason}`, {
1704
- detail: { socketPath: opts.socketPath }
1705
- })
1706
- };
1707
- }
1708
- return {
1709
- success: true,
1710
- data: {
1711
- send(data) {
1712
- const bytes = encoder.encode(data);
1713
- const merged = new Uint8Array(queued.length + bytes.length);
1714
- merged.set(queued, 0);
1715
- merged.set(bytes, queued.length);
1716
- queued = merged;
1717
- flush();
1718
- },
1719
- async close() {
1720
- try {
1721
- socket?.end();
1722
- } catch {}
1723
- notifyClosed();
1724
- }
1725
- }
1726
- };
1727
- };
1728
- }
1729
-
1730
1251
  // ../../packages/e2e/src/driver/android/uiad-protocol.ts
1731
- import { z as z3 } from "zod";
1252
+ import { z as z2 } from "zod";
1732
1253
  var UIAD_PROTOCOL = 1;
1733
1254
  var UIAD_MAIN_CLASS = "nz.co.delacour.warden.uiad.Main";
1734
1255
  var STRING_FIELDS = ["class", "pkg", "id", "text", "desc", "hint"];
@@ -1786,48 +1307,48 @@ function readNode(value, path = "elements") {
1786
1307
  node.children = children;
1787
1308
  return node;
1788
1309
  }
1789
- var screenSchema = z3.object({
1790
- width: z3.number(),
1791
- height: z3.number(),
1792
- scale: z3.number().optional(),
1793
- rotation: z3.number().optional()
1310
+ var screenSchema = z2.object({
1311
+ width: z2.number(),
1312
+ height: z2.number(),
1313
+ scale: z2.number().optional(),
1314
+ rotation: z2.number().optional()
1794
1315
  });
1795
- var healthSchema = z3.looseObject({
1796
- version: z3.string(),
1797
- protocol: z3.number(),
1798
- sdk: z3.number(),
1799
- release: z3.string().optional(),
1800
- model: z3.string().optional(),
1801
- uid: z3.number().optional(),
1802
- pid: z3.number().optional(),
1803
- uiAutomation: z3.looseObject({ state: z3.string(), connect: z3.string().optional(), connectMs: z3.number().optional() }),
1804
- events: z3.looseObject({ count: z3.number(), lastAgoMs: z3.number() }).optional(),
1805
- uptimeMs: z3.number().optional(),
1316
+ var healthSchema = z2.looseObject({
1317
+ version: z2.string(),
1318
+ protocol: z2.number(),
1319
+ sdk: z2.number(),
1320
+ release: z2.string().optional(),
1321
+ model: z2.string().optional(),
1322
+ uid: z2.number().optional(),
1323
+ pid: z2.number().optional(),
1324
+ uiAutomation: z2.looseObject({ state: z2.string(), connect: z2.string().optional(), connectMs: z2.number().optional() }),
1325
+ events: z2.looseObject({ count: z2.number(), lastAgoMs: z2.number() }).optional(),
1326
+ uptimeMs: z2.number().optional(),
1806
1327
  screen: screenSchema.optional(),
1807
- displayError: z3.string().optional()
1328
+ displayError: z2.string().optional()
1808
1329
  });
1809
- var settleDataSchema2 = z3.looseObject({
1810
- quiet: z3.boolean(),
1811
- waitedMs: z3.number(),
1812
- via: z3.literal("events"),
1813
- events: z3.number(),
1814
- firstEventMs: z3.number().optional(),
1815
- reason: z3.string().optional()
1330
+ var settleDataSchema = z2.looseObject({
1331
+ quiet: z2.boolean(),
1332
+ waitedMs: z2.number(),
1333
+ via: z2.literal("events"),
1334
+ events: z2.number(),
1335
+ firstEventMs: z2.number().optional(),
1336
+ reason: z2.string().optional()
1816
1337
  });
1817
- var textDataSchema = z3.looseObject({
1818
- text: z3.string(),
1819
- caret: z3.number().optional(),
1820
- caretPlaced: z3.boolean().optional()
1338
+ var textDataSchema = z2.looseObject({
1339
+ text: z2.string(),
1340
+ caret: z2.number().optional(),
1341
+ caretPlaced: z2.boolean().optional()
1821
1342
  });
1822
- var snapshotEnvelope = z3.looseObject({
1823
- backend: z3.literal("uiad"),
1343
+ var snapshotEnvelope = z2.looseObject({
1344
+ backend: z2.literal("uiad"),
1824
1345
  screen: screenSchema,
1825
- windows: z3.array(z3.unknown()).optional(),
1826
- elements: z3.array(z3.unknown()),
1827
- truncated: z3.boolean(),
1828
- nodes: z3.number(),
1829
- readMs: z3.number(),
1830
- ms: z3.number()
1346
+ windows: z2.array(z2.unknown()).optional(),
1347
+ elements: z2.array(z2.unknown()),
1348
+ truncated: z2.boolean(),
1349
+ nodes: z2.number(),
1350
+ readMs: z2.number(),
1351
+ ms: z2.number()
1831
1352
  });
1832
1353
  function readSnapshotData(data) {
1833
1354
  const envelope = snapshotEnvelope.safeParse(data);
@@ -1844,13 +1365,13 @@ function readSnapshotData(data) {
1844
1365
  }
1845
1366
  return { ...envelope.data, elements };
1846
1367
  }
1847
- var readyEventSchema2 = z3.object({
1848
- event: z3.literal("ready"),
1849
- protocol: z3.number(),
1850
- version: z3.string(),
1368
+ var readyEventSchema = z2.object({
1369
+ event: z2.literal("ready"),
1370
+ protocol: z2.number(),
1371
+ version: z2.string(),
1851
1372
  data: healthSchema
1852
1373
  });
1853
- var eventSchema2 = z3.discriminatedUnion("event", [readyEventSchema2, logEventSchema, fatalEventSchema]);
1374
+ var eventSchema = z2.discriminatedUnion("event", [readyEventSchema, logEventSchema, fatalEventSchema]);
1854
1375
  function parseUiadLine(line) {
1855
1376
  let json;
1856
1377
  try {
@@ -1859,7 +1380,7 @@ function parseUiadLine(line) {
1859
1380
  return { success: false, reason: "not JSON" };
1860
1381
  }
1861
1382
  if (isRecord(json) && "event" in json) {
1862
- const event = eventSchema2.safeParse(json);
1383
+ const event = eventSchema.safeParse(json);
1863
1384
  return event.success ? { success: true, message: { kind: "event", event: event.data } } : { success: false, reason: `bad event: ${event.error.message}` };
1864
1385
  }
1865
1386
  const response = responseSchema.safeParse(json);
@@ -1869,7 +1390,7 @@ function parseUiadLine(line) {
1869
1390
  // ../../packages/e2e/src/driver/android/uiad-client.ts
1870
1391
  var DEFAULT_READY_TIMEOUT_MS2 = 5000;
1871
1392
  var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
1872
- var DEFAULT_GRACE_MS2 = 1000;
1393
+ var DEFAULT_GRACE_MS = 1000;
1873
1394
  var SHUTDOWN_WAIT_MS = 300;
1874
1395
  var NOT_SENT = { sent: false };
1875
1396
  function schemaReader(schema) {
@@ -1966,7 +1487,7 @@ async function startUiad(options) {
1966
1487
  const factory = stdioTransport({
1967
1488
  cmd: options.cmd,
1968
1489
  ...options.spawn ? { spawn: options.spawn } : { spawn: bunSpawn },
1969
- graceMs: options.graceMs ?? DEFAULT_GRACE_MS2
1490
+ graceMs: options.graceMs ?? DEFAULT_GRACE_MS
1970
1491
  });
1971
1492
  const waiting = new Promise((resolve) => {
1972
1493
  settleHandshake = (result) => {
@@ -2044,7 +1565,7 @@ async function startUiad(options) {
2044
1565
  return through(await request("snapshot", { ...params }, opts), readSnapshot, "snapshot");
2045
1566
  },
2046
1567
  async settle(params) {
2047
- return through(await request("settle", params, { timeoutMs: params.timeoutMs + requestTimeoutMs }), schemaReader(settleDataSchema2), "settle");
1568
+ return through(await request("settle", params, { timeoutMs: params.timeoutMs + requestTimeoutMs }), schemaReader(settleDataSchema), "settle");
2048
1569
  },
2049
1570
  async typeText(text) {
2050
1571
  return through(await request("typeText", { text }), schemaReader(textDataSchema), "typeText");
@@ -2126,12 +1647,12 @@ function uiadPresentCommand(devicePath, bytes) {
2126
1647
  var bunPushDex = async (argv, bytes) => {
2127
1648
  try {
2128
1649
  const proc = Bun.spawn([...argv], { stdin: bytes, stdout: "pipe", stderr: "pipe", timeout: 15000 });
2129
- const [out, err2, exitCode] = await Promise.all([
1650
+ const [out, err, exitCode] = await Promise.all([
2130
1651
  new Response(proc.stdout).text(),
2131
1652
  new Response(proc.stderr).text(),
2132
1653
  proc.exited
2133
1654
  ]);
2134
- return { exitCode, output: `${out}${err2}`.trim() };
1655
+ return { exitCode, output: `${out}${err}`.trim() };
2135
1656
  } catch (error) {
2136
1657
  return { exitCode: 127, output: error instanceof Error ? error.message : String(error) };
2137
1658
  }
@@ -3062,9 +2583,9 @@ function buildDriver(options, session, baseScreen, clock, execBinary, facts) {
3062
2583
  const orientation = async (set) => {
3063
2584
  if (set) {
3064
2585
  for (const argv of rotationArgvs(orientationToRotation(set))) {
3065
- const res2 = await input(shellCommand(argv));
3066
- if (!res2.success)
3067
- return res2;
2586
+ const res = await input(shellCommand(argv));
2587
+ if (!res.success)
2588
+ return res;
3068
2589
  }
3069
2590
  return { success: true, data: set };
3070
2591
  }
@@ -3111,8 +2632,8 @@ function buildDriver(options, session, baseScreen, clock, execBinary, facts) {
3111
2632
  };
3112
2633
  const appearance = async (set) => {
3113
2634
  if (set) {
3114
- const res2 = await runChecked(session, shellCommand(uimodeSetArgv(set === "dark")), "E_UNSUPPORTED");
3115
- return res2.success ? { success: true, data: set } : res2;
2635
+ const res = await runChecked(session, shellCommand(uimodeSetArgv(set === "dark")), "E_UNSUPPORTED");
2636
+ return res.success ? { success: true, data: set } : res;
3116
2637
  }
3117
2638
  const res = await runChecked(session, shellCommand(uimodeGetArgv()), "E_UNSUPPORTED");
3118
2639
  if (!res.success)
@@ -3121,12 +2642,12 @@ function buildDriver(options, session, baseScreen, clock, execBinary, facts) {
3121
2642
  };
3122
2643
  const clipboard = async () => driverErr("E_UNSUPPORTED", "Android has no public shell clipboard access; type ASCII with typeText");
3123
2644
  const health = async () => {
3124
- const [release, sdk2, model] = await Promise.all(["ro.build.version.release", "ro.build.version.sdk", "ro.product.model"].map((k) => session.run(shellCommand(getpropArgv(k)))));
2645
+ const [release, sdk, model] = await Promise.all(["ro.build.version.release", "ro.build.version.sdk", "ro.product.model"].map((k) => session.run(shellCommand(getpropArgv(k)))));
3125
2646
  const text = (r) => r?.success ? r.data.stdout.trim() : "";
3126
2647
  const info = {
3127
2648
  platform: "android",
3128
2649
  id: serial,
3129
- version: `Android ${text(release)} (sdk ${text(sdk2)})`,
2650
+ version: `Android ${text(release)} (sdk ${text(sdk)})`,
3130
2651
  transport: "adb",
3131
2652
  screen: baseScreen,
3132
2653
  detail: { model: text(model), dumpRoute: dumper.route() ?? null, uiad: uiad?.status() ?? "off" }
@@ -3159,7 +2680,7 @@ function buildDriver(options, session, baseScreen, clock, execBinary, facts) {
3159
2680
  orientation,
3160
2681
  settle,
3161
2682
  alert,
3162
- alertIn: (snapshot2) => detectAlert(snapshot2.flat),
2683
+ alertIn: (snapshot) => detectAlert(snapshot.flat),
3163
2684
  appearance,
3164
2685
  clipboard,
3165
2686
  app: createAppCommands({
@@ -3180,7 +2701,7 @@ function buildDriver(options, session, baseScreen, clock, execBinary, facts) {
3180
2701
  }
3181
2702
 
3182
2703
  // ../../packages/e2e/src/driver/ios/ios-driver.ts
3183
- import { existsSync as existsSync3 } from "fs";
2704
+ import { existsSync as existsSync2 } from "fs";
3184
2705
  import { dirname as dirname4, join as join6 } from "path";
3185
2706
 
3186
2707
  // ../../packages/e2e/src/driver/ios/ios-session.ts
@@ -3235,7 +2756,7 @@ function swappableClient(initial, o) {
3235
2756
  }
3236
2757
 
3237
2758
  // ../../packages/e2e/src/driver/ios/locate-helper.ts
3238
- import { existsSync as existsSync2, realpathSync } from "fs";
2759
+ import { existsSync, realpathSync } from "fs";
3239
2760
  import { dirname as dirname3, join as join4, resolve } from "path";
3240
2761
  var HELPER_NAME = "warden-simd";
3241
2762
  var DOCTOR_HINT = "run `warden doctor` to check the iOS driver helper, or set WARDEN_SIMD=/path/to/warden-simd";
@@ -3286,7 +2807,7 @@ function resolveHelper(o) {
3286
2807
  execPath: o.execPath ?? process.execPath,
3287
2808
  importDir: o.importDir ?? import.meta.dir,
3288
2809
  wardenHome: o.wardenHome ?? wardenHome(),
3289
- exists: o.exists ?? existsSync2,
2810
+ exists: o.exists ?? existsSync,
3290
2811
  realpath: o.realpath ?? realpathSync,
3291
2812
  ...o.version ? { version: o.version } : {}
3292
2813
  });
@@ -3554,6 +3075,20 @@ var statusBarClearArgv = (udid) => [...XCRUN, "status_bar", udid, "clear"];
3554
3075
  function pbcopyArgv(udid, text) {
3555
3076
  return ["sh", "-c", 'printf %s "$1" | xcrun simctl pbcopy "$2"', "sh", text, udid];
3556
3077
  }
3078
+ function devicectlPbcopyArgv(udid, text) {
3079
+ return [
3080
+ "sh",
3081
+ "-c",
3082
+ 'printf %s "$1" | xcrun devicectl device pasteboard copy --quiet --device "$2"',
3083
+ "sh",
3084
+ text,
3085
+ udid
3086
+ ];
3087
+ }
3088
+ var PBCOPY_CHANNELS = [
3089
+ { name: "devicectl pasteboard copy", argv: devicectlPbcopyArgv },
3090
+ { name: "simctl pbcopy", argv: pbcopyArgv }
3091
+ ];
3557
3092
  function escapeRegExp(text) {
3558
3093
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3559
3094
  }
@@ -3645,7 +3180,26 @@ function createSimctl(deps) {
3645
3180
  const code = /is not installed/.test(reset.error) ? "E_APP_UNAVAILABLE" : "E_IO";
3646
3181
  return { success: false, error: driverError(code, reset.error) };
3647
3182
  },
3648
- pbcopy: (text) => runVoid(pbcopyArgv(udid, text)),
3183
+ async pbcopy(text) {
3184
+ const outcomes = [];
3185
+ for (const channel of PBCOPY_CHANNELS) {
3186
+ const copied = await exec(channel.argv(udid, text), { timeoutMs: SHORT_MS });
3187
+ if (copied.exitCode !== 0) {
3188
+ outcomes.push(`${channel.name} exit ${copied.exitCode}`);
3189
+ continue;
3190
+ }
3191
+ const read = await run(pbpasteArgv(udid), SHORT_MS);
3192
+ if (!read.ok)
3193
+ return { success: false, error: read.error };
3194
+ if (read.res.stdout === text)
3195
+ return ok4(undefined);
3196
+ outcomes.push(`${channel.name} exit 0 but pbpaste reads other text`);
3197
+ }
3198
+ return {
3199
+ success: false,
3200
+ error: driverError("E_IO", `simulator pasteboard did not take the text (${outcomes.join("; ")})`)
3201
+ };
3202
+ },
3649
3203
  async pbpaste() {
3650
3204
  const result = await run(pbpasteArgv(udid), SHORT_MS);
3651
3205
  return result.ok ? ok4(result.res.stdout) : { success: false, error: result.error };
@@ -3682,299 +3236,6 @@ function createSimctl(deps) {
3682
3236
  };
3683
3237
  }
3684
3238
 
3685
- // ../../packages/e2e/src/driver/ios/simd-client.ts
3686
- var DEFAULT_TIMEOUT_MS = 1e4;
3687
- var DEFAULT_READY_TIMEOUT_MS3 = 15000;
3688
- var SHUTDOWN_TIMEOUT_MS2 = 500;
3689
- var STDERR_TAIL2 = 20;
3690
- function crashed(info, what) {
3691
- const exit = info?.exitCode === null || info?.exitCode === undefined ? "" : ` (exit ${info.exitCode})`;
3692
- const tail = info?.stderrTail.length ? `: ${info.stderrTail.slice(-3).join(" | ")}` : "";
3693
- return driverError("E_HELPER_CRASHED", `warden-simd ${what}${exit}${tail}`, {
3694
- detail: info ? { exitCode: info.exitCode, stderrTail: info.stderrTail } : undefined
3695
- });
3696
- }
3697
- async function createSimdClient(factory, options = {}) {
3698
- const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
3699
- const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS3;
3700
- const maxRespawns = options.maxRespawns ?? 0;
3701
- const shutdownOnClose = options.shutdownOnClose ?? false;
3702
- const log = options.log;
3703
- const pending = new Map;
3704
- const listeners = new Map;
3705
- let seq = 0;
3706
- let respawns = 0;
3707
- let closed = false;
3708
- let closing = false;
3709
- let shutdownAcked = false;
3710
- let current;
3711
- let opening;
3712
- let lastCrash;
3713
- let fatalError;
3714
- function emit(event, payload) {
3715
- for (const listener of listeners.get(event) ?? []) {
3716
- try {
3717
- listener(payload);
3718
- } catch (error) {
3719
- log?.(`[simd] ${event} listener threw: ${error instanceof Error ? error.message : String(error)}`);
3720
- }
3721
- }
3722
- }
3723
- function failAll(error) {
3724
- for (const [id, entry] of pending) {
3725
- clearTimeout(entry.timer);
3726
- pending.delete(id);
3727
- entry.resolve({ success: false, error });
3728
- }
3729
- }
3730
- function handleResponse(response) {
3731
- respawns = 0;
3732
- const entry = pending.get(response.id);
3733
- if (!entry) {
3734
- log?.(`[simd] response for unknown id ${response.id} (timed out?)`);
3735
- return;
3736
- }
3737
- clearTimeout(entry.timer);
3738
- pending.delete(response.id);
3739
- entry.resolve(response.ok ? { success: true, data: response.data ?? null } : { success: false, error: mapWireError(response.error) });
3740
- }
3741
- function handleFatal(session, event) {
3742
- const error = mapWireError(event.error);
3743
- fatalError = error;
3744
- log?.(`[simd] fatal: ${error.code}: ${error.message}`);
3745
- emit("fatal", error);
3746
- if (session.handshaking) {
3747
- session.handshaking = false;
3748
- session.handshake({ success: false, error });
3749
- }
3750
- failAll(error);
3751
- }
3752
- function handleEvent(session, event) {
3753
- if (event.event === "log") {
3754
- log?.(`[simd] ${event.level}: ${event.message}`);
3755
- emit("log", event);
3756
- } else if (event.event === "fatal") {
3757
- handleFatal(session, event);
3758
- } else if (session.handshaking) {
3759
- session.handshaking = false;
3760
- session.ready = event;
3761
- emit("ready", event);
3762
- session.handshake({ success: true, data: event });
3763
- }
3764
- }
3765
- function handleMessage(session, message) {
3766
- if (message.kind === "response")
3767
- handleResponse(message.response);
3768
- else
3769
- handleEvent(session, message.event);
3770
- }
3771
- function handleLine(session, line) {
3772
- const parsed = parseLine(line);
3773
- if (!parsed.success) {
3774
- log?.(`[simd] ignoring line (${parsed.reason}): ${line.slice(0, 200)}`);
3775
- return;
3776
- }
3777
- handleMessage(session, parsed.message);
3778
- }
3779
- function handleClose(session, info) {
3780
- if (!session.alive)
3781
- return;
3782
- session.alive = false;
3783
- const expected = closed || closing;
3784
- emit("exit", { exitCode: info.exitCode, expected });
3785
- if (session.handshaking) {
3786
- session.handshaking = false;
3787
- session.handshake({ success: false, error: fatalError ?? crashed(info, "exited before it was ready") });
3788
- return;
3789
- }
3790
- if (fatalError)
3791
- return;
3792
- const error = crashed(info, expected ? "stopped" : "exited unexpectedly");
3793
- if (!expected)
3794
- lastCrash = error;
3795
- failAll(expected ? driverError("E_INTERNAL", "client closed", { retryable: false }) : error);
3796
- }
3797
- async function openSession() {
3798
- let resolveHandshake = () => {
3799
- return;
3800
- };
3801
- const handshake = new Promise((resolve2) => {
3802
- resolveHandshake = resolve2;
3803
- });
3804
- const session = { alive: true, handshake: resolveHandshake, handshaking: true };
3805
- const stderrTail = [];
3806
- const opened = await factory({
3807
- onLine: (line) => handleLine(session, line),
3808
- onDiagnostic: (line) => {
3809
- stderrTail.push(line);
3810
- if (stderrTail.length > STDERR_TAIL2)
3811
- stderrTail.shift();
3812
- log?.(`[simd:stderr] ${line}`);
3813
- },
3814
- onClose: (info) => handleClose(session, { ...info, stderrTail: info.stderrTail.length ? info.stderrTail : [...stderrTail] })
3815
- });
3816
- if (!opened.success)
3817
- return opened;
3818
- session.transport = opened.data;
3819
- let timer;
3820
- const timeout = new Promise((resolve2) => {
3821
- timer = setTimeout(() => resolve2({
3822
- success: false,
3823
- error: driverError("E_TIMEOUT", `warden-simd sent no ready event within ${readyTimeoutMs} ms`)
3824
- }), readyTimeoutMs);
3825
- });
3826
- const ready = await Promise.race([handshake, timeout]);
3827
- clearTimeout(timer);
3828
- if (!ready.success) {
3829
- session.handshaking = false;
3830
- session.alive = false;
3831
- await session.transport.close().catch(() => {
3832
- return;
3833
- });
3834
- return ready;
3835
- }
3836
- if (ready.data.protocol !== protocolVersion) {
3837
- session.alive = false;
3838
- await session.transport.close().catch(() => {
3839
- return;
3840
- });
3841
- return {
3842
- success: false,
3843
- error: driverError("E_INTERNAL", `warden-simd speaks protocol ${ready.data.protocol}, this client expects ${protocolVersion}; rebuild or reinstall the helper`, { retryable: false })
3844
- };
3845
- }
3846
- current = session;
3847
- return { success: true, data: session };
3848
- }
3849
- async function ensureSession() {
3850
- if (current?.alive)
3851
- return { success: true, data: current };
3852
- if (opening)
3853
- return opening;
3854
- if (respawns >= maxRespawns) {
3855
- return {
3856
- success: false,
3857
- error: lastCrash ?? driverError("E_HELPER_CRASHED", "warden-simd connection is closed")
3858
- };
3859
- }
3860
- respawns += 1;
3861
- log?.(`[simd] respawning helper (${respawns}/${maxRespawns})`);
3862
- opening = openSession().finally(() => {
3863
- opening = undefined;
3864
- });
3865
- return opening;
3866
- }
3867
- function validate(op, data) {
3868
- if (!hasOpSchema(op))
3869
- return { success: true, data };
3870
- const parsed = opSchemas[op].safeParse(data);
3871
- if (parsed.success)
3872
- return { success: true, data: parsed.data };
3873
- return {
3874
- success: false,
3875
- error: driverError("E_INTERNAL", `warden-simd returned an unexpected \`${op}\` payload: ${parsed.error.message}`, {
3876
- retryable: false
3877
- })
3878
- };
3879
- }
3880
- async function request(op, params, opts) {
3881
- if (closed)
3882
- return { success: false, error: driverError("E_INTERNAL", "warden-simd client is closed", { retryable: false }) };
3883
- if (fatalError)
3884
- return { success: false, error: fatalError };
3885
- const session = await ensureSession();
3886
- if (!session.success)
3887
- return session;
3888
- const transport = session.data.transport;
3889
- if (!transport)
3890
- return { success: false, error: driverError("E_INTERNAL", "warden-simd transport missing") };
3891
- const timeoutMs = opts.timeoutMs ?? defaultTimeoutMs;
3892
- const id = String(++seq);
3893
- const result = await new Promise((resolve2) => {
3894
- const timer = setTimeout(() => {
3895
- pending.delete(id);
3896
- resolve2({
3897
- success: false,
3898
- error: driverError("E_TIMEOUT", `warden-simd \`${op}\` timed out after ${timeoutMs} ms`, {
3899
- detail: { op, timeoutMs }
3900
- })
3901
- });
3902
- }, timeoutMs);
3903
- pending.set(id, { op, resolve: resolve2, timer });
3904
- try {
3905
- transport.send(encodeRequest({ timeoutMs, ...params, id, op }));
3906
- } catch (error) {
3907
- clearTimeout(timer);
3908
- pending.delete(id);
3909
- resolve2({
3910
- success: false,
3911
- error: crashed(undefined, `rejected the \`${op}\` request: ${error instanceof Error ? error.message : String(error)}`)
3912
- });
3913
- }
3914
- });
3915
- if (result.success && op === "shutdown")
3916
- shutdownAcked = true;
3917
- return result.success ? validate(op, result.data) : result;
3918
- }
3919
- const first = await openSession();
3920
- if (!first.success)
3921
- return first;
3922
- const client = {
3923
- get ready() {
3924
- const ready = current?.ready ?? first.data.ready;
3925
- if (!ready)
3926
- throw new Error("warden-simd client has no handshake");
3927
- return ready;
3928
- },
3929
- request: (op, params = {}, opts = {}) => request(op, params, opts),
3930
- on(event, handler) {
3931
- const stored = handler;
3932
- const set = listeners.get(event) ?? new Set;
3933
- set.add(stored);
3934
- listeners.set(event, set);
3935
- return () => {
3936
- set.delete(stored);
3937
- };
3938
- },
3939
- async close() {
3940
- if (closed)
3941
- return;
3942
- closing = true;
3943
- const session = current;
3944
- if (shutdownOnClose && session?.alive && !fatalError && !shutdownAcked) {
3945
- await request("shutdown", {}, { timeoutMs: SHUTDOWN_TIMEOUT_MS2 });
3946
- }
3947
- closed = true;
3948
- failAll(driverError("E_INTERNAL", "warden-simd client closed", { retryable: false }));
3949
- await session?.transport?.close().catch(() => {
3950
- return;
3951
- });
3952
- }
3953
- };
3954
- return { success: true, data: client };
3955
- }
3956
- function spawnSimd(options) {
3957
- const cmd = [
3958
- options.helperPath,
3959
- "--udid",
3960
- options.udid,
3961
- ...options.resources ? ["--resources", options.resources] : [],
3962
- ...options.args ?? []
3963
- ];
3964
- return createSimdClient(stdioTransport({ cmd, spawn: options.spawn, graceMs: options.graceMs }), {
3965
- ...options,
3966
- maxRespawns: options.maxRespawns ?? 1,
3967
- shutdownOnClose: options.shutdownOnClose ?? true
3968
- });
3969
- }
3970
- function connectSimd(options) {
3971
- return createSimdClient(socketTransport({ socketPath: options.socketPath }), {
3972
- ...options,
3973
- maxRespawns: options.maxRespawns ?? 0,
3974
- shutdownOnClose: options.shutdownOnClose ?? false
3975
- });
3976
- }
3977
-
3978
3239
  // ../../packages/e2e/src/driver/ios/type-budget.ts
3979
3240
  var TYPE_BASE_MS = 1e4;
3980
3241
  var TYPE_PER_CHAR_MS = 70;
@@ -4431,7 +3692,7 @@ async function connect(o, log) {
4431
3692
  helperPath: helper.data,
4432
3693
  udid: o.udid,
4433
3694
  log,
4434
- ...(o.exists ?? existsSync3)(resources) ? { resources } : {},
3695
+ ...(o.exists ?? existsSync2)(resources) ? { resources } : {},
4435
3696
  ...o.spawn ? { spawn: o.spawn } : {},
4436
3697
  ...timeoutOption(o)
4437
3698
  });
@@ -4535,12 +3796,12 @@ function codeFor(code) {
4535
3796
  return "ACTION_FAILED";
4536
3797
  }
4537
3798
  function toE2eError(error, action) {
4538
- return new E2eError(codeFor(error.code), `${action} failed: ${error.code}: ${error.message}`);
3799
+ return new E2eError2(codeFor(error.code), `${action} failed: ${error.code}: ${error.message}`);
4539
3800
  }
4540
3801
  function aborted(action, signal) {
4541
3802
  const reason = signal.reason;
4542
3803
  const detail = reason instanceof Error ? `: ${reason.message}` : "";
4543
- return new E2eError("TIMEOUT", `${action} aborted (test deadline reached or cancelled)${detail}`);
3804
+ return new E2eError2("TIMEOUT", `${action} aborted (test deadline reached or cancelled)${detail}`);
4544
3805
  }
4545
3806
  async function settled(call, action, signal) {
4546
3807
  if (signal === undefined)
@@ -4569,7 +3830,7 @@ async function unwrap(call, action, signal) {
4569
3830
  // ../../packages/e2e/src/fixture-session.ts
4570
3831
  var systemClock = {
4571
3832
  now: () => Date.now(),
4572
- sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms))
3833
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
4573
3834
  };
4574
3835
 
4575
3836
  // ../../packages/e2e/src/app.ts
@@ -4606,11 +3867,11 @@ function parseWait(wait) {
4606
3867
  if (typeof wait === "object" && wait !== null && isLocator(wait.until)) {
4607
3868
  const { timeout } = wait;
4608
3869
  if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) {
4609
- throw new E2eError("INVALID_ARGUMENT", `app.open wait timeout must be a non-negative number, got ${timeout}`);
3870
+ throw new E2eError2("INVALID_ARGUMENT", `app.open wait timeout must be a non-negative number, got ${timeout}`);
4610
3871
  }
4611
3872
  return timeout === undefined ? { kind: "until", until: wait.until } : { kind: "until", until: wait.until, timeout };
4612
3873
  }
4613
- throw new E2eError("INVALID_ARGUMENT", `app.open wait must be "quiet", "none" or { until: <locator made by screen> }, got ${describeWait(wait)}`);
3874
+ throw new E2eError2("INVALID_ARGUMENT", `app.open wait must be "quiet", "none" or { until: <locator made by screen> }, got ${describeWait(wait)}`);
4614
3875
  }
4615
3876
  function describeWait(wait) {
4616
3877
  if (typeof wait === "string")
@@ -4689,7 +3950,7 @@ function createApp({ session, driver, env, log = stderrLog2, clock = systemClock
4689
3950
  async reinstall() {
4690
3951
  const path = env.appPath;
4691
3952
  if (path === undefined) {
4692
- throw new E2eError("INVALID_ARGUMENT", "app.reinstall() needs WARDEN_APP_PATH (the build to install) to be set");
3953
+ throw new E2eError2("INVALID_ARGUMENT", "app.reinstall() needs WARDEN_APP_PATH (the build to install) to be set");
4693
3954
  }
4694
3955
  const installed = await unwrap(() => driver.app.isInstalled(bundleId), `check ${bundleId}`, session.signal);
4695
3956
  if (installed)
@@ -4762,11 +4023,11 @@ function createAlertReader({ driver, clock, signal }) {
4762
4023
 
4763
4024
  // ../../packages/e2e/src/device-devtools.ts
4764
4025
  function createDeviceDevtools(source) {
4765
- const connect2 = async () => {
4026
+ const connect = async () => {
4766
4027
  const attached = await source.devtools();
4767
4028
  if (attached.success)
4768
4029
  return attached.data;
4769
- throw new E2eError("SESSION", `devtools unavailable: ${attached.error.message}`);
4030
+ throw new E2eError2("SESSION", `devtools unavailable: ${attached.error.message}`);
4770
4031
  };
4771
4032
  const attach = () => source.devtools();
4772
4033
  return {
@@ -4786,24 +4047,24 @@ function createDeviceDevtools(source) {
4786
4047
  },
4787
4048
  network: {
4788
4049
  async entries(query) {
4789
- return (await connect2()).network.entries(query);
4050
+ return (await connect()).network.entries(query);
4790
4051
  },
4791
4052
  async clear() {
4792
- await (await connect2()).network.clear();
4053
+ await (await connect()).network.clear();
4793
4054
  }
4794
4055
  },
4795
4056
  console: {
4796
4057
  async lines() {
4797
- return (await connect2()).console.lines();
4058
+ return (await connect()).console.lines();
4798
4059
  },
4799
4060
  async clear() {
4800
- (await connect2()).console.clear();
4061
+ (await connect()).console.clear();
4801
4062
  },
4802
4063
  async mark(label) {
4803
- (await connect2()).console.mark(label);
4064
+ (await connect()).console.mark(label);
4804
4065
  }
4805
4066
  },
4806
- connect: connect2
4067
+ connect
4807
4068
  };
4808
4069
  }
4809
4070
 
@@ -4864,8 +4125,8 @@ async function lookForSheet(deps, plan, watch, started, remembered) {
4864
4125
  watch.lastSheetAt = now;
4865
4126
  watch.sheetSeenAt ??= now;
4866
4127
  const since = (at) => at === undefined ? Number.POSITIVE_INFINITY : now - at;
4867
- const settled2 = since(watch.sheetSeenAt) >= SHEET_SETTLE_MS && since(watch.lastPressAt) >= PRESS_GAP_MS;
4868
- if (!settled2)
4128
+ const settled = since(watch.sheetSeenAt) >= SHEET_SETTLE_MS && since(watch.lastPressAt) >= PRESS_GAP_MS;
4129
+ if (!settled)
4869
4130
  return { sheetUp: true, confirmed: false, finished: false };
4870
4131
  if (watch.presses >= MAX_PRESSES)
4871
4132
  return { sheetUp: true, confirmed: false, finished: plan.until === undefined };
@@ -4954,11 +4215,11 @@ function alertTarget(info, action, platform) {
4954
4215
  const target = findButton(buttons, action.button);
4955
4216
  if (target === undefined) {
4956
4217
  const names = buttons.map((b) => nameOf(b) ?? "?").join(", ");
4957
- throw new E2eError("ACTION_FAILED", `alert has no "${action.button}" button (buttons: ${names})`);
4218
+ throw new E2eError2("ACTION_FAILED", `alert has no "${action.button}" button (buttons: ${names})`);
4958
4219
  }
4959
4220
  return target;
4960
4221
  }
4961
- function createDevice({ session, driver, env, clock = systemClock }) {
4222
+ function createDevice({ session, driver, env, clock = systemClock, perfExec }) {
4962
4223
  const issued = new Map;
4963
4224
  const signal = () => session.signal;
4964
4225
  const pressAlertButton = async (button, action) => {
@@ -4980,7 +4241,7 @@ function createDevice({ session, driver, env, clock = systemClock }) {
4980
4241
  return false;
4981
4242
  }
4982
4243
  };
4983
- const readSnapshot2 = (opts) => unwrap(() => driver.snapshot(opts), "snapshot", signal());
4244
+ const readSnapshot = (opts) => unwrap(() => driver.snapshot(opts), "snapshot", signal());
4984
4245
  const { devtools: attach, peekDevtools } = session;
4985
4246
  let devtools;
4986
4247
  const lazyDevtools = () => {
@@ -4995,10 +4256,19 @@ function createDevice({ session, driver, env, clock = systemClock }) {
4995
4256
  }
4996
4257
  return devtools;
4997
4258
  };
4259
+ const perf = createDevicePerf({
4260
+ exec: perfExec ?? spawnWarden(env.wardenBin),
4261
+ udid: env.udid,
4262
+ budgets: parseBudgets(env.perfBudgets),
4263
+ attemptDir: () => session.attemptDir,
4264
+ addFile: (relative) => session.attempt?.files.add(relative),
4265
+ ...env.wardenBin === undefined && perfExec === undefined ? { unavailable: "WARDEN_BIN is not set (run the spec with `warden e2e`)" } : {}
4266
+ });
4998
4267
  const device = {
4999
4268
  get devtools() {
5000
4269
  return lazyDevtools();
5001
4270
  },
4271
+ perf,
5002
4272
  async alert(action) {
5003
4273
  const info = await readAlert("axbridge");
5004
4274
  if (info === null || info.buttons.length === 0)
@@ -5014,7 +4284,7 @@ function createDevice({ session, driver, env, clock = systemClock }) {
5014
4284
  async openLink(url, options = {}) {
5015
4285
  const waitMs = options.waitMs ?? OPEN_LINK_WAIT_MS;
5016
4286
  if (!Number.isFinite(waitMs) || waitMs < 0) {
5017
- throw new E2eError("INVALID_ARGUMENT", `openLink waitMs must be a non-negative number, got ${String(waitMs)}`);
4287
+ throw new E2eError2("INVALID_ARGUMENT", `openLink waitMs must be a non-negative number, got ${String(waitMs)}`);
5018
4288
  }
5019
4289
  await unwrap(() => driver.app.openUrl(url), `open ${url}`, signal());
5020
4290
  const answersSheet = options.accept !== false && driver.platform === "ios" && driver.capabilities.alerts;
@@ -5034,7 +4304,7 @@ function createDevice({ session, driver, env, clock = systemClock }) {
5034
4304
  },
5035
4305
  async back() {
5036
4306
  if (driver.platform === "ios") {
5037
- throw new E2eError("INVALID_ARGUMENT", "device.back() is Android only: iOS has no back key");
4307
+ throw new E2eError2("INVALID_ARGUMENT", "device.back() is Android only: iOS has no back key");
5038
4308
  }
5039
4309
  await unwrap(() => driver.pressButton("back"), "press back", signal());
5040
4310
  },
@@ -5051,7 +4321,7 @@ function createDevice({ session, driver, env, clock = systemClock }) {
5051
4321
  await unwrap(() => driver.clipboard(text), "set clipboard", signal());
5052
4322
  },
5053
4323
  async hideKeyboard() {
5054
- const snapshot = await readSnapshot2();
4324
+ const snapshot = await readSnapshot();
5055
4325
  if (!snapshot.flat.some((element) => element.role === "keyboard"))
5056
4326
  return;
5057
4327
  if (driver.platform === "ios")
@@ -5073,7 +4343,7 @@ function createDevice({ session, driver, env, clock = systemClock }) {
5073
4343
  },
5074
4344
  async snapshot(options = {}) {
5075
4345
  const interactiveOnly = options.interactiveOnly ?? false;
5076
- const snapshot = await readSnapshot2({ interactiveOnly });
4346
+ const snapshot = await readSnapshot({ interactiveOnly });
5077
4347
  const rendered = renderSnapshot(snapshot, { interactiveOnly, refs: new Map });
5078
4348
  return [renderHeader({ snapshot, rendered, interactiveOnly }), ...rendered.lines].join(`
5079
4349
  `);
@@ -5086,7 +4356,7 @@ function createDevice({ session, driver, env, clock = systemClock }) {
5086
4356
  return settleDetailOf(native.data);
5087
4357
  if (native.error.code !== "E_UNSUPPORTED")
5088
4358
  throw toE2eError(native.error, "settle");
5089
- const polled = await settleByHash(async () => snapshotHash(await readSnapshot2()), {
4359
+ const polled = await settleByHash(async () => snapshotHash(await readSnapshot()), {
5090
4360
  quietMs,
5091
4361
  timeoutMs,
5092
4362
  now: clock.now,
@@ -5102,16 +4372,16 @@ function createDevice({ session, driver, env, clock = systemClock }) {
5102
4372
  var systemClock2 = {
5103
4373
  now: () => Date.now(),
5104
4374
  sleep(ms, signal) {
5105
- return new Promise((resolve2) => {
4375
+ return new Promise((resolve) => {
5106
4376
  if (signal?.aborted)
5107
- return resolve2();
4377
+ return resolve();
5108
4378
  const onAbort = () => {
5109
4379
  clearTimeout(timer);
5110
- resolve2();
4380
+ resolve();
5111
4381
  };
5112
4382
  const timer = setTimeout(() => {
5113
4383
  signal?.removeEventListener("abort", onAbort);
5114
- resolve2();
4384
+ resolve();
5115
4385
  }, ms);
5116
4386
  signal?.addEventListener("abort", onAbort, { once: true });
5117
4387
  });
@@ -5285,13 +4555,13 @@ class Runtime {
5285
4555
  }
5286
4556
  }
5287
4557
  function driverFailure(verb, error, code = "DRIVER") {
5288
- return new E2eError(code, `${verb} failed: ${error.code}: ${error.message}`);
4558
+ return new E2eError2(code, `${verb} failed: ${error.code}: ${error.message}`);
5289
4559
  }
5290
4560
  function abortError(signal) {
5291
4561
  const reason = signal?.reason;
5292
- if (reason instanceof E2eError)
4562
+ if (reason instanceof E2eError2)
5293
4563
  return reason;
5294
- return new E2eError("TIMEOUT", "the test was aborted before the action finished");
4564
+ return new E2eError2("TIMEOUT", "the test was aborted before the action finished");
5295
4565
  }
5296
4566
 
5297
4567
  // ../../packages/e2e/src/screen/screen.ts
@@ -5306,7 +4576,7 @@ function createScreen(host) {
5306
4576
  const allowed = "direction" in options ? ["direction", "momentum"] : ["from", "to", "durationMs"];
5307
4577
  const unknown = keys.filter((key) => !allowed.includes(key));
5308
4578
  if (unknown.length > 0 || keys.length === 0 || "direction" in options && "from" in options) {
5309
- throw new E2eError("INVALID_ARGUMENT", "swipe takes either { direction, momentum? } or { from, to, durationMs? }, not a mix");
4579
+ throw new E2eError2("INVALID_ARGUMENT", "swipe takes either { direction, momentum? } or { from, to, durationMs? }, not a mix");
5310
4580
  }
5311
4581
  return rt.step("swipe", async () => {
5312
4582
  if ("direction" in options) {
@@ -5316,7 +4586,7 @@ function createScreen(host) {
5316
4586
  return;
5317
4587
  }
5318
4588
  if (!isFinitePoint(options.from) || !isFinitePoint(options.to)) {
5319
- throw new E2eError("INVALID_ARGUMENT", "swipe needs finite coordinates for from and to");
4589
+ throw new E2eError2("INVALID_ARGUMENT", "swipe needs finite coordinates for from and to");
5320
4590
  }
5321
4591
  const driver = await host.driver();
5322
4592
  const opts = options.durationMs === undefined ? undefined : { durationMs: options.durationMs };
@@ -5333,7 +4603,7 @@ function createScreen(host) {
5333
4603
  locator: (selector) => new Locator(selectorExpression(selector), rt),
5334
4604
  async tapAt(point) {
5335
4605
  if (!isFinitePoint(point))
5336
- throw new E2eError("INVALID_ARGUMENT", `tapAt needs finite x and y, got ${JSON.stringify(point)}`);
4606
+ throw new E2eError2("INVALID_ARGUMENT", `tapAt needs finite x and y, got ${JSON.stringify(point)}`);
5337
4607
  return rt.step(`tapAt (${point.x}, ${point.y})`, async () => {
5338
4608
  const driver = await host.driver();
5339
4609
  rt.drive("tapAt", await driver.tap(point));
@@ -5368,10 +4638,10 @@ function createScreen(host) {
5368
4638
  async path(options) {
5369
4639
  const { paths } = options;
5370
4640
  if (paths.length < 1 || paths.length > 2) {
5371
- throw new E2eError("INVALID_ARGUMENT", `gesture.path takes 1 or 2 paths, got ${paths.length}`);
4641
+ throw new E2eError2("INVALID_ARGUMENT", `gesture.path takes 1 or 2 paths, got ${paths.length}`);
5372
4642
  }
5373
4643
  if (paths.some((points) => points.length < 1 || !points.every(isFinitePoint))) {
5374
- throw new E2eError("INVALID_ARGUMENT", "gesture.path needs at least one point per path, all with finite x and y");
4644
+ throw new E2eError2("INVALID_ARGUMENT", "gesture.path needs at least one point per path, all with finite x and y");
5375
4645
  }
5376
4646
  return rt.step(`gesture path (${paths.length} ${paths.length === 1 ? "finger" : "fingers"})`, async () => {
5377
4647
  const driver = await host.driver();
@@ -5393,6 +4663,9 @@ function traceDevice(device, span) {
5393
4663
  get devtools() {
5394
4664
  return device.devtools;
5395
4665
  },
4666
+ get perf() {
4667
+ return device.perf;
4668
+ },
5396
4669
  alert: (action) => span("device.alert", () => device.alert(action), { detail: (answered) => ({ answered }) }),
5397
4670
  openLink: (url, options) => span("device.openLink", () => device.openLink(url, options)),
5398
4671
  home: () => span("device.home", () => device.home()),
@@ -5479,17 +4752,20 @@ ${read.error}
5479
4752
  deps.exit(2);
5480
4753
  return;
5481
4754
  }
5482
- deps.setDefaultTimeout(read.data.timeoutMs + BUN_TIMEOUT_MARGIN_MS);
4755
+ deps.setDefaultTimeout(read.data.timeoutMs + BUN_TIMEOUT_MARGIN_MS2);
5483
4756
  const session = deps.createSession({ env: read.data });
5484
4757
  deps.afterAll(async () => {
5485
4758
  await session.close();
5486
4759
  if (isCi(deps.env) && deps.onlySeen()) {
5487
- throw new E2eError("INVALID_ARGUMENT", "test.only / test(..., { only: true }) is not allowed in CI");
4760
+ throw new E2eError2("INVALID_ARGUMENT", "test.only / test(..., { only: true }) is not allowed in CI");
5488
4761
  }
5489
4762
  });
5490
4763
  }
5491
- if (!hasDefaultDriverFactory())
4764
+ if (!hasDefaultDriverFactory()) {
5492
4765
  setDefaultDriverFactory(defaultDriverFactory);
4766
+ setDefaultNativeLog(realNativeLogDeps(bunExec, androidTools(process.env).adb));
4767
+ setDefaultHidRecovery({ exec: bunExec });
4768
+ }
5493
4769
  if (!hasDefaultFixtureFactory())
5494
4770
  setDefaultFixtureFactory(defaultFixtureFactory);
5495
4771
  installPreload({
@@ -5497,7 +4773,7 @@ installPreload({
5497
4773
  setDefaultTimeout,
5498
4774
  afterAll,
5499
4775
  createSession: getSession,
5500
- version: WARDEN_E2E_VERSION,
4776
+ version: WARDEN_E2E_VERSION2,
5501
4777
  onlySeen: onlyWasSeen,
5502
4778
  stderr: (text) => {
5503
4779
  process.stderr.write(text);
@@ -5505,8 +4781,8 @@ installPreload({
5505
4781
  exit: (code) => process.exit(code)
5506
4782
  });
5507
4783
  export {
5508
- versionMismatch,
5509
- isCi,
4784
+ BUN_TIMEOUT_MARGIN_MS2 as BUN_TIMEOUT_MARGIN_MS,
5510
4785
  installPreload,
5511
- BUN_TIMEOUT_MARGIN_MS
4786
+ isCi,
4787
+ versionMismatch
5512
4788
  };