@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/{chunk-qtkmfjbq.mjs → chunk-7ns30pbp.mjs} +5482 -4235
- package/index.mjs +119 -70
- package/package.json +2 -2
- package/preload.mjs +215 -939
- package/types/core/src/providers/android.d.ts +8 -0
- package/types/core/src/store.d.ts +15 -1
- package/types/core/src/types.d.ts +20 -0
- package/types/e2e/src/app.d.ts +1 -1
- package/types/e2e/src/device.d.ts +9 -1
- package/types/e2e/src/devtools/native-log.d.ts +50 -0
- package/types/e2e/src/driver/android/uiad-backend.d.ts +1 -1
- package/types/e2e/src/driver/driver.types.d.ts +1 -1
- package/types/e2e/src/driver/ios/hid-recovery.d.ts +36 -0
- package/types/e2e/src/driver/ios/simctl.d.ts +15 -0
- package/types/e2e/src/driver/observation-cache.d.ts +1 -1
- package/types/e2e/src/env.d.ts +26 -9
- package/types/e2e/src/errors.d.ts +1 -1
- package/types/e2e/src/fixture-session.d.ts +5 -1
- package/types/e2e/src/index.d.ts +3 -0
- package/types/e2e/src/perf/budgets.d.ts +33 -0
- package/types/e2e/src/perf/device-perf.d.ts +45 -0
- package/types/e2e/src/perf/perf.types.d.ts +59 -0
- package/types/e2e/src/preload.d.ts +2 -2
- package/types/e2e/src/runner/artifacts.d.ts +5 -5
- package/types/e2e/src/runner/failure.d.ts +12 -0
- package/types/e2e/src/runner/fixtures.types.d.ts +1 -1
- package/types/e2e/src/runner/scope.d.ts +5 -3
- package/types/e2e/src/runner/test.d.ts +7 -0
- package/types/e2e/src/screen/actions.d.ts +1 -1
- package/types/e2e/src/session.d.ts +19 -0
package/preload.mjs
CHANGED
|
@@ -1,93 +1,78 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
settleByHash,
|
|
12
|
+
snapshotHash,
|
|
13
|
+
ok,
|
|
14
|
+
err,
|
|
15
|
+
swipeVector,
|
|
16
|
+
screenRect,
|
|
11
17
|
PINCH_MS,
|
|
12
18
|
ROTATE_MS,
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
335
|
-
if (
|
|
299
|
+
const start = l.buffer.indexOf(current.prefix);
|
|
300
|
+
if (start < 0)
|
|
336
301
|
return;
|
|
337
|
-
const afterPrefix =
|
|
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,
|
|
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
|
|
1235
|
-
return { text:
|
|
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
|
|
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 =
|
|
1790
|
-
width:
|
|
1791
|
-
height:
|
|
1792
|
-
scale:
|
|
1793
|
-
rotation:
|
|
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 =
|
|
1796
|
-
version:
|
|
1797
|
-
protocol:
|
|
1798
|
-
sdk:
|
|
1799
|
-
release:
|
|
1800
|
-
model:
|
|
1801
|
-
uid:
|
|
1802
|
-
pid:
|
|
1803
|
-
uiAutomation:
|
|
1804
|
-
events:
|
|
1805
|
-
uptimeMs:
|
|
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:
|
|
1328
|
+
displayError: z2.string().optional()
|
|
1808
1329
|
});
|
|
1809
|
-
var
|
|
1810
|
-
quiet:
|
|
1811
|
-
waitedMs:
|
|
1812
|
-
via:
|
|
1813
|
-
events:
|
|
1814
|
-
firstEventMs:
|
|
1815
|
-
reason:
|
|
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 =
|
|
1818
|
-
text:
|
|
1819
|
-
caret:
|
|
1820
|
-
caretPlaced:
|
|
1338
|
+
var textDataSchema = z2.looseObject({
|
|
1339
|
+
text: z2.string(),
|
|
1340
|
+
caret: z2.number().optional(),
|
|
1341
|
+
caretPlaced: z2.boolean().optional()
|
|
1821
1342
|
});
|
|
1822
|
-
var snapshotEnvelope =
|
|
1823
|
-
backend:
|
|
1343
|
+
var snapshotEnvelope = z2.looseObject({
|
|
1344
|
+
backend: z2.literal("uiad"),
|
|
1824
1345
|
screen: screenSchema,
|
|
1825
|
-
windows:
|
|
1826
|
-
elements:
|
|
1827
|
-
truncated:
|
|
1828
|
-
nodes:
|
|
1829
|
-
readMs:
|
|
1830
|
-
ms:
|
|
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
|
|
1848
|
-
event:
|
|
1849
|
-
protocol:
|
|
1850
|
-
version:
|
|
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
|
|
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 =
|
|
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
|
|
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 ??
|
|
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(
|
|
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,
|
|
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}${
|
|
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
|
|
3066
|
-
if (!
|
|
3067
|
-
return
|
|
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
|
|
3115
|
-
return
|
|
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,
|
|
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(
|
|
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: (
|
|
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
|
|
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
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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
|
|
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
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4026
|
+
const connect = async () => {
|
|
4766
4027
|
const attached = await source.devtools();
|
|
4767
4028
|
if (attached.success)
|
|
4768
4029
|
return attached.data;
|
|
4769
|
-
throw new
|
|
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
|
|
4050
|
+
return (await connect()).network.entries(query);
|
|
4790
4051
|
},
|
|
4791
4052
|
async clear() {
|
|
4792
|
-
await (await
|
|
4053
|
+
await (await connect()).network.clear();
|
|
4793
4054
|
}
|
|
4794
4055
|
},
|
|
4795
4056
|
console: {
|
|
4796
4057
|
async lines() {
|
|
4797
|
-
return (await
|
|
4058
|
+
return (await connect()).console.lines();
|
|
4798
4059
|
},
|
|
4799
4060
|
async clear() {
|
|
4800
|
-
(await
|
|
4061
|
+
(await connect()).console.clear();
|
|
4801
4062
|
},
|
|
4802
4063
|
async mark(label) {
|
|
4803
|
-
(await
|
|
4064
|
+
(await connect()).console.mark(label);
|
|
4804
4065
|
}
|
|
4805
4066
|
},
|
|
4806
|
-
connect
|
|
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
|
|
4868
|
-
if (!
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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((
|
|
4375
|
+
return new Promise((resolve) => {
|
|
5106
4376
|
if (signal?.aborted)
|
|
5107
|
-
return
|
|
4377
|
+
return resolve();
|
|
5108
4378
|
const onAbort = () => {
|
|
5109
4379
|
clearTimeout(timer);
|
|
5110
|
-
|
|
4380
|
+
resolve();
|
|
5111
4381
|
};
|
|
5112
4382
|
const timer = setTimeout(() => {
|
|
5113
4383
|
signal?.removeEventListener("abort", onAbort);
|
|
5114
|
-
|
|
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
|
|
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
|
|
4562
|
+
if (reason instanceof E2eError2)
|
|
5293
4563
|
return reason;
|
|
5294
|
-
return new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 +
|
|
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
|
|
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:
|
|
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
|
-
|
|
5509
|
-
isCi,
|
|
4784
|
+
BUN_TIMEOUT_MARGIN_MS2 as BUN_TIMEOUT_MARGIN_MS,
|
|
5510
4785
|
installPreload,
|
|
5511
|
-
|
|
4786
|
+
isCi,
|
|
4787
|
+
versionMismatch
|
|
5512
4788
|
};
|