@delacour/warden-e2e 0.7.0 → 0.8.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-empqr9n0.mjs → chunk-vs5npy59.mjs} +2010 -169
- package/index.mjs +214 -13
- package/package.json +2 -2
- package/preload.mjs +3002 -798
- package/types/core/src/a11y/a11y.config.d.ts +47 -0
- package/types/core/src/affected/affected.schema.d.ts +49 -1
- package/types/core/src/android-keyguard.d.ts +2 -0
- package/types/core/src/batch/preset.schema.d.ts +12 -1
- package/types/core/src/builds/app-option.schema.d.ts +1 -1
- package/types/core/src/builds/config.schema.d.ts +93 -2
- package/types/core/src/builds/define-config.d.ts +55 -0
- package/types/core/src/camera-mode.d.ts +17 -0
- package/types/core/src/camera-placeholder.d.ts +9 -0
- package/types/core/src/camera-profile.d.ts +12 -0
- package/types/core/src/claim-errors.d.ts +34 -0
- package/types/core/src/conditions/a11y-keys.d.ts +45 -0
- package/types/core/src/conditions/camera-media.d.ts +30 -0
- package/types/core/src/conditions/conditions.config.d.ts +24 -0
- package/types/core/src/conditions/conditions.types.d.ts +30 -0
- package/types/core/src/conditions/inject-state.d.ts +1 -1
- package/types/core/src/ios-runner/build.d.ts +28 -0
- package/types/core/src/ios-runner/cache.d.ts +44 -0
- package/types/core/src/ios-runner/pack.d.ts +17 -0
- package/types/core/src/ios-runner/signing.d.ts +40 -0
- package/types/core/src/ios-runner/sim-build.d.ts +36 -0
- package/types/core/src/ios-runner/stage.d.ts +26 -0
- package/types/core/src/lease-events.d.ts +126 -0
- package/types/core/src/ownership.d.ts +44 -0
- package/types/core/src/providers/android.d.ts +10 -1
- package/types/core/src/providers/provider.types.d.ts +9 -0
- package/types/core/src/queue.d.ts +43 -0
- package/types/core/src/store.d.ts +61 -5
- package/types/core/src/types.d.ts +10 -0
- package/types/core/src/xcode.d.ts +22 -0
- package/types/e2e/src/a11y/a11y-target.d.ts +23 -0
- package/types/e2e/src/a11y/a11y.types.d.ts +81 -0
- package/types/e2e/src/a11y/announcements.d.ts +24 -0
- package/types/e2e/src/a11y/audit.d.ts +22 -0
- package/types/e2e/src/a11y/native/atf.d.ts +51 -0
- package/types/e2e/src/a11y/native/xcuitest.d.ts +60 -0
- package/types/e2e/src/a11y/order.d.ts +5 -0
- package/types/e2e/src/a11y/rules/duplicate-name.d.ts +6 -0
- package/types/e2e/src/a11y/rules/hidden-focusable.d.ts +3 -0
- package/types/e2e/src/a11y/rules/image-no-name.d.ts +6 -0
- package/types/e2e/src/a11y/rules/label-in-name.d.ts +7 -0
- package/types/e2e/src/a11y/rules/missing-name.d.ts +3 -0
- package/types/e2e/src/a11y/rules/role-in-name.d.ts +3 -0
- package/types/e2e/src/a11y/rules/rule.context.d.ts +22 -0
- package/types/e2e/src/a11y/rules/target-size.d.ts +21 -0
- package/types/e2e/src/a11y/rules/text-clipped.d.ts +9 -0
- package/types/e2e/src/a11y/speech.d.ts +26 -0
- package/types/e2e/src/a11y/talkback-log.d.ts +13 -0
- package/types/e2e/src/app.d.ts +0 -1
- package/types/e2e/src/conditions/condition-events.d.ts +6 -1
- package/types/e2e/src/conditions/device-conditions.d.ts +30 -2
- package/types/e2e/src/device-a11y.d.ts +97 -0
- package/types/e2e/src/device.d.ts +11 -2
- package/types/e2e/src/driver/a11y-events.d.ts +30 -0
- package/types/e2e/src/driver/a11y-read.d.ts +12 -0
- package/types/e2e/src/driver/android/android-animations.d.ts +7 -0
- package/types/e2e/src/driver/android/android-argv.d.ts +2 -0
- package/types/e2e/src/driver/android/android-camera-host.d.ts +23 -0
- package/types/e2e/src/driver/android/android-camera.d.ts +28 -0
- package/types/e2e/src/driver/android/android-driver.d.ts +5 -0
- package/types/e2e/src/driver/android/android-screen-reader.d.ts +23 -0
- package/types/e2e/src/driver/android/android-settings.d.ts +9 -2
- package/types/e2e/src/driver/android/android-spoken.d.ts +18 -0
- package/types/e2e/src/driver/android/android-talkback.d.ts +57 -0
- package/types/e2e/src/driver/android/atf-device.d.ts +23 -0
- package/types/e2e/src/driver/android/atf-dex.generated.d.ts +7 -0
- package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +5 -5
- package/types/e2e/src/driver/android/uiad-ops.d.ts +27 -1
- package/types/e2e/src/driver/android/uiad-protocol.d.ts +8 -0
- package/types/e2e/src/driver/android/uiad-screen-reader.d.ts +33 -0
- package/types/e2e/src/driver/driver.types.d.ts +71 -5
- package/types/e2e/src/driver/element-actions.d.ts +16 -0
- package/types/e2e/src/driver/ios/inject.d.ts +16 -2
- package/types/e2e/src/driver/ios/ios-a11y.d.ts +73 -0
- package/types/e2e/src/driver/ios/ios-audit.d.ts +36 -0
- package/types/e2e/src/driver/ios/ios-driver.d.ts +21 -1
- package/types/e2e/src/driver/ios/ios-network.d.ts +7 -2
- package/types/e2e/src/driver/ios/sim-runner.d.ts +41 -0
- package/types/e2e/src/driver/ios/simd-transport.d.ts +11 -2
- package/types/e2e/src/driver/native-audit.d.ts +26 -0
- package/types/e2e/src/driver/physical-conditions.d.ts +1 -1
- package/types/e2e/src/driver/screen-reader.d.ts +26 -0
- package/types/e2e/src/env.d.ts +16 -1
- package/types/e2e/src/expect/a11y-matchers.d.ts +33 -0
- package/types/e2e/src/expect/expect.d.ts +7 -2
- package/types/e2e/src/expect/matchers.d.ts +14 -0
- package/types/e2e/src/index.d.ts +5 -1
- package/types/e2e/src/runner/callsite.d.ts +3 -1
- package/types/e2e/src/screen/render.d.ts +8 -0
|
@@ -21,6 +21,20 @@ function e2eErr(error) {
|
|
|
21
21
|
return { success: false, error: E2eError2.from(error) };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// ../../packages/e2e/src/a11y/a11y-target.ts
|
|
25
|
+
var registry = new WeakMap;
|
|
26
|
+
function registerA11yTarget(target, hooks) {
|
|
27
|
+
registry.set(target, hooks);
|
|
28
|
+
}
|
|
29
|
+
function a11yTargetOf(target) {
|
|
30
|
+
return typeof target === "object" && target !== null ? registry.get(target) : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ../../packages/e2e/src/driver/element-actions.ts
|
|
34
|
+
function canActOnElements(driver) {
|
|
35
|
+
return typeof driver.pressElement === "function";
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
// ../../packages/e2e/src/driver/span-detail.ts
|
|
25
39
|
import { AsyncLocalStorage } from "async_hooks";
|
|
26
40
|
var collectors = new AsyncLocalStorage;
|
|
@@ -1272,6 +1286,24 @@ function head(entry) {
|
|
|
1272
1286
|
function describeEntry(entry) {
|
|
1273
1287
|
return head(entry).join(" ");
|
|
1274
1288
|
}
|
|
1289
|
+
function a11yWords(entry) {
|
|
1290
|
+
const a11y = entry.element.a11y;
|
|
1291
|
+
if (!a11y)
|
|
1292
|
+
return [];
|
|
1293
|
+
const words = [];
|
|
1294
|
+
if (a11y.hint)
|
|
1295
|
+
words.push(`hint=${quote(a11y.hint, MAX_NAME)}`);
|
|
1296
|
+
if (a11y.actions && a11y.actions.length > 0) {
|
|
1297
|
+
words.push(`actions=[${a11y.actions.map((action) => quote(action, MAX_VALUE)).join(",")}]`);
|
|
1298
|
+
}
|
|
1299
|
+
if (a11y.a11yFocused)
|
|
1300
|
+
words.push("a11y-focused");
|
|
1301
|
+
if (a11y.important === false)
|
|
1302
|
+
words.push("not-important");
|
|
1303
|
+
if (a11y.liveRegion)
|
|
1304
|
+
words.push(`live=${a11y.liveRegion}`);
|
|
1305
|
+
return words;
|
|
1306
|
+
}
|
|
1275
1307
|
function renderEntryLine(entry, options = {}) {
|
|
1276
1308
|
const parts = [];
|
|
1277
1309
|
if (options.ref !== undefined)
|
|
@@ -1282,6 +1314,8 @@ function renderEntryLine(entry, options = {}) {
|
|
|
1282
1314
|
parts.push(...stateWords(entry));
|
|
1283
1315
|
const { x, y, width, height } = entry.element.frame;
|
|
1284
1316
|
parts.push(`(${Math.round(x)},${Math.round(y)} ${Math.round(width)}\xD7${Math.round(height)})`);
|
|
1317
|
+
if (options.a11y)
|
|
1318
|
+
parts.push(...a11yWords(entry));
|
|
1285
1319
|
return `${" ".repeat(options.indent ?? 0)}${parts.join(" ")}`;
|
|
1286
1320
|
}
|
|
1287
1321
|
function isListed(entry) {
|
|
@@ -1297,6 +1331,7 @@ function renderSnapshot(snapshot, options) {
|
|
|
1297
1331
|
const visible = listed.slice(0, limit);
|
|
1298
1332
|
const lines = visible.map((entry) => renderEntryLine(entry, {
|
|
1299
1333
|
indent: options.interactiveOnly ? 0 : entry.depth,
|
|
1334
|
+
...options.a11y ? { a11y: true } : {},
|
|
1300
1335
|
...options.refs.has(entry.element.id) ? { ref: options.refs.get(entry.element.id) } : {}
|
|
1301
1336
|
}));
|
|
1302
1337
|
const omitted = listed.length - visible.length;
|
|
@@ -1842,7 +1877,12 @@ class Locator {
|
|
|
1842
1877
|
const spec = { enabled: !withPosition, tapPointOnScreen: !withPosition };
|
|
1843
1878
|
const found = (options.until === undefined ? await this.aimFromKept(spec, options.position) : undefined) ?? await this.actionable(spec, options.timeout ?? this.rt.actionTimeout(), true);
|
|
1844
1879
|
const point = "react" in found ? reactTapPoint(found.react, options.position) : tapPointOf(found.entry, options.position);
|
|
1845
|
-
|
|
1880
|
+
const { driver } = found.observed;
|
|
1881
|
+
if (!withPosition && "entry" in found && canActOnElements(driver)) {
|
|
1882
|
+
if (this.rt.drive("tap", await driver.pressElement(found.entry.element)))
|
|
1883
|
+
return found.observed.snapshot;
|
|
1884
|
+
}
|
|
1885
|
+
this.rt.drive("tap", await driver.tap(point));
|
|
1846
1886
|
return found.observed.snapshot;
|
|
1847
1887
|
}
|
|
1848
1888
|
async arrivedWithin(plan, pressedOn) {
|
|
@@ -1953,6 +1993,10 @@ class Locator {
|
|
|
1953
1993
|
}
|
|
1954
1994
|
return this.act("swipe", async () => {
|
|
1955
1995
|
const { entry, observed } = await this.actionable({ enabled: false, tapPointOnScreen: false }, options.timeout ?? this.rt.actionTimeout());
|
|
1996
|
+
if (canActOnElements(observed.driver)) {
|
|
1997
|
+
if (this.rt.drive("swipe", await observed.driver.scrollElement(options.direction, entry.element)))
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
1956
2000
|
const vector = swipeVector(options.direction, visibleRect(entry.element.frame, observed.snapshot.screen), options.momentum);
|
|
1957
2001
|
this.rt.drive("swipe", await observed.driver.swipe(vector.from, vector.to, vector.options));
|
|
1958
2002
|
});
|
|
@@ -2170,7 +2214,9 @@ async function scrollUntilVisible(rt, target, options = {}) {
|
|
|
2170
2214
|
throw scrollGaveUp(target, entry === undefined, `${swipes} swipes ${direction}`, rt.excerpt(observed.snapshot));
|
|
2171
2215
|
}
|
|
2172
2216
|
const vector = swipeVector(direction, screenRect(observed.snapshot.screen), options.momentum);
|
|
2173
|
-
rt.drive("scrollUntilVisible", await observed.driver.
|
|
2217
|
+
const scrolled = canActOnElements(observed.driver) && rt.drive("scrollUntilVisible", await observed.driver.scrollElement(direction));
|
|
2218
|
+
if (!scrolled)
|
|
2219
|
+
rt.drive("scrollUntilVisible", await observed.driver.swipe(vector.from, vector.to, vector.options));
|
|
2174
2220
|
await observed.driver.settle(SCROLL_SETTLE);
|
|
2175
2221
|
}
|
|
2176
2222
|
});
|
|
@@ -4524,6 +4570,7 @@ function stdioTransport(opts) {
|
|
|
4524
4570
|
};
|
|
4525
4571
|
}
|
|
4526
4572
|
function socketTransport(opts) {
|
|
4573
|
+
const label = opts.tcp ? `${opts.tcp.hostname}:${opts.tcp.port}` : opts.socketPath ?? "";
|
|
4527
4574
|
return async (handlers) => {
|
|
4528
4575
|
const splitter = createLineSplitter();
|
|
4529
4576
|
const encoder = new TextEncoder;
|
|
@@ -4543,37 +4590,35 @@ function socketTransport(opts) {
|
|
|
4543
4590
|
queued = written >= queued.length ? new Uint8Array(0) : queued.slice(Math.max(written, 0));
|
|
4544
4591
|
};
|
|
4545
4592
|
try {
|
|
4546
|
-
const
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
end() {
|
|
4566
|
-
notifyClosed();
|
|
4567
|
-
}
|
|
4593
|
+
const socketHandlers = {
|
|
4594
|
+
open(s) {
|
|
4595
|
+
socket = s;
|
|
4596
|
+
},
|
|
4597
|
+
data(_s, chunk) {
|
|
4598
|
+
for (const line of splitter.push(chunk))
|
|
4599
|
+
handlers.onLine(line);
|
|
4600
|
+
},
|
|
4601
|
+
drain() {
|
|
4602
|
+
flush();
|
|
4603
|
+
},
|
|
4604
|
+
close() {
|
|
4605
|
+
notifyClosed();
|
|
4606
|
+
},
|
|
4607
|
+
error() {
|
|
4608
|
+
notifyClosed();
|
|
4609
|
+
},
|
|
4610
|
+
end() {
|
|
4611
|
+
notifyClosed();
|
|
4568
4612
|
}
|
|
4569
|
-
}
|
|
4613
|
+
};
|
|
4614
|
+
const connected = opts.tcp ? await Bun.connect({ hostname: opts.tcp.hostname, port: opts.tcp.port, socket: socketHandlers }) : await Bun.connect({ unix: opts.socketPath, socket: socketHandlers });
|
|
4570
4615
|
socket = connected;
|
|
4571
4616
|
} catch (error) {
|
|
4572
4617
|
const reason = error instanceof Error ? error.message : String(error);
|
|
4573
4618
|
return {
|
|
4574
4619
|
success: false,
|
|
4575
|
-
error: driverError("E_HELPER_CRASHED", `cannot connect to warden-simd socket ${
|
|
4576
|
-
detail: { socketPath:
|
|
4620
|
+
error: driverError("E_HELPER_CRASHED", `cannot connect to warden-simd socket ${label}: ${reason}`, {
|
|
4621
|
+
detail: { socketPath: label }
|
|
4577
4622
|
})
|
|
4578
4623
|
};
|
|
4579
4624
|
}
|
|
@@ -5067,8 +5112,41 @@ function traceDriver(driver, options) {
|
|
|
5067
5112
|
return wrap(driver, "driver.");
|
|
5068
5113
|
}
|
|
5069
5114
|
|
|
5070
|
-
// ../../packages/core/src/
|
|
5115
|
+
// ../../packages/core/src/a11y/a11y.config.ts
|
|
5071
5116
|
import { z as z7 } from "zod";
|
|
5117
|
+
var A11Y_RULE_IDS = [
|
|
5118
|
+
"missing-name",
|
|
5119
|
+
"image-no-name",
|
|
5120
|
+
"target-size",
|
|
5121
|
+
"target-size-platform",
|
|
5122
|
+
"duplicate-name",
|
|
5123
|
+
"role-in-name",
|
|
5124
|
+
"label-in-name",
|
|
5125
|
+
"hidden-focusable",
|
|
5126
|
+
"text-clipped",
|
|
5127
|
+
"native"
|
|
5128
|
+
];
|
|
5129
|
+
var ruleId = z7.enum(A11Y_RULE_IDS);
|
|
5130
|
+
var a11yAuditConfigSchema = z7.object({
|
|
5131
|
+
ignore: z7.array(z7.object({ rule: ruleId, identifier: z7.string().min(1).optional() }).strict()).optional(),
|
|
5132
|
+
minSeverity: z7.enum(["error", "warning"]).optional(),
|
|
5133
|
+
rules: z7.array(ruleId).min(1).optional()
|
|
5134
|
+
}).strict();
|
|
5135
|
+
function parseA11yAuditEnv(raw) {
|
|
5136
|
+
let json;
|
|
5137
|
+
try {
|
|
5138
|
+
json = JSON.parse(raw);
|
|
5139
|
+
} catch {
|
|
5140
|
+
return err("must be JSON");
|
|
5141
|
+
}
|
|
5142
|
+
const parsed = a11yAuditConfigSchema.safeParse(json);
|
|
5143
|
+
if (parsed.success)
|
|
5144
|
+
return ok(parsed.data);
|
|
5145
|
+
return err(parsed.error.issues.map((i) => `${i.path.join(".") || "a11y"}: ${i.message}`).join("; "));
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5148
|
+
// ../../packages/core/src/conditions/conditions.config.ts
|
|
5149
|
+
import { z as z8 } from "zod";
|
|
5072
5150
|
|
|
5073
5151
|
// ../../packages/core/src/duration.ts
|
|
5074
5152
|
var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
@@ -5110,6 +5188,7 @@ var TEXT_SIZE_NAMES = [
|
|
|
5110
5188
|
"accessibility-extra-extra-large",
|
|
5111
5189
|
"accessibility-extra-extra-extra-large"
|
|
5112
5190
|
];
|
|
5191
|
+
var CAMERA_FACINGS = ["back", "front"];
|
|
5113
5192
|
var CONDITION_AREAS = [
|
|
5114
5193
|
"network",
|
|
5115
5194
|
"location",
|
|
@@ -5117,7 +5196,8 @@ var CONDITION_AREAS = [
|
|
|
5117
5196
|
"permission",
|
|
5118
5197
|
"locale",
|
|
5119
5198
|
"a11y",
|
|
5120
|
-
"battery"
|
|
5199
|
+
"battery",
|
|
5200
|
+
"camera"
|
|
5121
5201
|
];
|
|
5122
5202
|
|
|
5123
5203
|
// ../../packages/core/src/conditions/network.presets.ts
|
|
@@ -5227,43 +5307,62 @@ function appliedA11y(op, applied) {
|
|
|
5227
5307
|
|
|
5228
5308
|
// ../../packages/core/src/conditions/conditions.config.ts
|
|
5229
5309
|
var NETWORK_WORDS = ["online", "offline", ...NETWORK_PRESET_NAMES];
|
|
5230
|
-
var nonNegative2 =
|
|
5231
|
-
var customNetworkSchema =
|
|
5310
|
+
var nonNegative2 = z8.number().nonnegative().finite();
|
|
5311
|
+
var customNetworkSchema = z8.object({
|
|
5232
5312
|
latencyMs: nonNegative2.optional(),
|
|
5233
5313
|
jitterMs: nonNegative2.optional(),
|
|
5234
5314
|
downKbps: nonNegative2.optional(),
|
|
5235
5315
|
upKbps: nonNegative2.optional(),
|
|
5236
|
-
lossPct:
|
|
5316
|
+
lossPct: z8.number().min(0).max(100).optional()
|
|
5237
5317
|
}).strict();
|
|
5238
|
-
var isoDate =
|
|
5239
|
-
var offsetDuration =
|
|
5240
|
-
var textSize =
|
|
5241
|
-
var a11yConfigSchema =
|
|
5318
|
+
var isoDate = z8.string().refine((v) => !Number.isNaN(Date.parse(v)), { message: "invalid date (use ISO 8601)" });
|
|
5319
|
+
var offsetDuration = z8.string().refine((v) => parseDuration(v).success, { message: "invalid duration (use e.g. 2d)" });
|
|
5320
|
+
var textSize = z8.union([z8.enum(TEXT_SIZE_NAMES), z8.number().positive().finite()]);
|
|
5321
|
+
var a11yConfigSchema = z8.object({
|
|
5242
5322
|
textSize: textSize.optional(),
|
|
5243
|
-
boldText:
|
|
5244
|
-
contrast:
|
|
5245
|
-
reduceMotion:
|
|
5323
|
+
boldText: z8.boolean().optional(),
|
|
5324
|
+
contrast: z8.enum(["increased", "normal"]).optional(),
|
|
5325
|
+
reduceMotion: z8.boolean().optional()
|
|
5246
5326
|
}).strict();
|
|
5247
|
-
var conditionsConfigSchema =
|
|
5248
|
-
network:
|
|
5249
|
-
location:
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
lat:
|
|
5253
|
-
lon:
|
|
5254
|
-
altM:
|
|
5327
|
+
var conditionsConfigSchema = z8.object({
|
|
5328
|
+
network: z8.union([z8.enum(NETWORK_WORDS), customNetworkSchema]).optional(),
|
|
5329
|
+
location: z8.union([
|
|
5330
|
+
z8.enum(["city-run", "city-bike", "freeway"]),
|
|
5331
|
+
z8.object({
|
|
5332
|
+
lat: z8.number().min(-90).max(90),
|
|
5333
|
+
lon: z8.number().min(-180).max(180),
|
|
5334
|
+
altM: z8.number().finite().optional()
|
|
5255
5335
|
}).strict()
|
|
5256
5336
|
]).optional(),
|
|
5257
|
-
clock:
|
|
5337
|
+
clock: z8.union([
|
|
5258
5338
|
isoDate,
|
|
5259
|
-
|
|
5260
|
-
|
|
5339
|
+
z8.object({ at: isoDate, frozen: z8.boolean().optional() }).strict(),
|
|
5340
|
+
z8.object({ offset: offsetDuration }).strict()
|
|
5261
5341
|
]).optional(),
|
|
5262
|
-
permissions:
|
|
5263
|
-
locale:
|
|
5264
|
-
timezone:
|
|
5342
|
+
permissions: z8.partialRecord(z8.enum(PERMISSION_NAMES), z8.enum(["granted", "denied", "unset"])).optional(),
|
|
5343
|
+
locale: z8.string().min(1).optional(),
|
|
5344
|
+
timezone: z8.string().min(1).optional(),
|
|
5265
5345
|
a11y: a11yConfigSchema.optional()
|
|
5266
5346
|
}).strict();
|
|
5347
|
+
var cameraFeedInput = z8.union([
|
|
5348
|
+
z8.string().min(1),
|
|
5349
|
+
z8.object({ path: z8.string().min(1), loop: z8.boolean().optional() }).strict()
|
|
5350
|
+
]);
|
|
5351
|
+
var cameraConfigSchema = z8.object({ back: cameraFeedInput.optional(), front: cameraFeedInput.optional() }).strict().refine((v) => v.back !== undefined || v.front !== undefined, { message: "camera needs a back or a front asset" });
|
|
5352
|
+
var cameraFeedsSchema = z8.object({
|
|
5353
|
+
back: z8.object({ path: z8.string().min(1), loop: z8.boolean() }).strict().optional(),
|
|
5354
|
+
front: z8.object({ path: z8.string().min(1), loop: z8.boolean() }).strict().optional()
|
|
5355
|
+
}).strict();
|
|
5356
|
+
function parseCameraFeedsEnv(raw) {
|
|
5357
|
+
let json;
|
|
5358
|
+
try {
|
|
5359
|
+
json = JSON.parse(raw);
|
|
5360
|
+
} catch {
|
|
5361
|
+
return err("must be JSON");
|
|
5362
|
+
}
|
|
5363
|
+
const parsed = cameraFeedsSchema.safeParse(json);
|
|
5364
|
+
return parsed.success ? ok(parsed.data) : err(parsed.error.issues.map((i) => i.message).join("; "));
|
|
5365
|
+
}
|
|
5267
5366
|
var isWord = (v) => NETWORK_WORDS.includes(v);
|
|
5268
5367
|
function toNetworkCondition(input) {
|
|
5269
5368
|
if (typeof input === "string") {
|
|
@@ -5334,7 +5433,7 @@ function parseConditionsEnv(raw) {
|
|
|
5334
5433
|
}
|
|
5335
5434
|
|
|
5336
5435
|
// ../../packages/e2e/src/env.ts
|
|
5337
|
-
import { z as
|
|
5436
|
+
import { z as z9 } from "zod";
|
|
5338
5437
|
var ENV_NAMES = {
|
|
5339
5438
|
udid: "WARDEN_UDID",
|
|
5340
5439
|
deviceName: "WARDEN_DEVICE_NAME",
|
|
@@ -5343,6 +5442,7 @@ var ENV_NAMES = {
|
|
|
5343
5442
|
appPath: "WARDEN_APP_PATH",
|
|
5344
5443
|
metroUrl: "WARDEN_METRO_URL",
|
|
5345
5444
|
launchArgs: "WARDEN_LAUNCH_ARGS",
|
|
5445
|
+
devClientScheme: "WARDEN_DEV_CLIENT_SCHEME",
|
|
5346
5446
|
out: "WARDEN_E2E_OUT",
|
|
5347
5447
|
timeout: "WARDEN_E2E_TIMEOUT",
|
|
5348
5448
|
actionTimeout: "WARDEN_E2E_ACTION_TIMEOUT",
|
|
@@ -5363,66 +5463,86 @@ var ENV_NAMES = {
|
|
|
5363
5463
|
conditions: "WARDEN_CONDITIONS",
|
|
5364
5464
|
leaseId: "WARDEN_LEASE_ID",
|
|
5365
5465
|
netproxyCommand: "WARDEN_NETPROXY_COMMAND",
|
|
5366
|
-
biometrics: "WARDEN_BIOMETRICS"
|
|
5466
|
+
biometrics: "WARDEN_BIOMETRICS",
|
|
5467
|
+
a11yAudit: "WARDEN_A11Y_AUDIT",
|
|
5468
|
+
a11y: "WARDEN_A11Y",
|
|
5469
|
+
cameraFeeds: "WARDEN_CAMERA_FEEDS"
|
|
5367
5470
|
};
|
|
5368
5471
|
var DEFAULT_TIMEOUT_MS4 = 120000;
|
|
5369
5472
|
var DEFAULT_ACTION_TIMEOUT_MS = 30000;
|
|
5370
5473
|
var DEFAULT_ASSERTION_TIMEOUT_MS = 5000;
|
|
5371
|
-
var toggle =
|
|
5372
|
-
var millis =
|
|
5373
|
-
var launchArgs =
|
|
5474
|
+
var toggle = z9.enum(["1", "true", "0", "false"]).transform((v) => v === "1" || v === "true");
|
|
5475
|
+
var millis = z9.string().regex(/^\d+$/, "must be a whole number of milliseconds").transform(Number).pipe(z9.number().int().positive());
|
|
5476
|
+
var launchArgs = z9.string().transform((raw, ctx) => {
|
|
5374
5477
|
try {
|
|
5375
|
-
const parsed =
|
|
5478
|
+
const parsed = z9.array(z9.string()).safeParse(JSON.parse(raw));
|
|
5376
5479
|
if (parsed.success)
|
|
5377
5480
|
return parsed.data;
|
|
5378
5481
|
} catch {}
|
|
5379
5482
|
ctx.addIssue({ code: "custom", message: "must be a JSON array of strings" });
|
|
5380
|
-
return
|
|
5483
|
+
return z9.NEVER;
|
|
5381
5484
|
});
|
|
5382
|
-
var conditions =
|
|
5485
|
+
var conditions = z9.string().transform((raw, ctx) => {
|
|
5383
5486
|
const parsed = parseConditionsEnv(raw);
|
|
5384
5487
|
if (parsed.success)
|
|
5385
5488
|
return parsed.data;
|
|
5386
5489
|
ctx.addIssue({ code: "custom", message: parsed.error });
|
|
5387
|
-
return
|
|
5490
|
+
return z9.NEVER;
|
|
5491
|
+
});
|
|
5492
|
+
var a11yAudit = z9.string().transform((raw, ctx) => {
|
|
5493
|
+
const parsed = parseA11yAuditEnv(raw);
|
|
5494
|
+
if (parsed.success)
|
|
5495
|
+
return parsed.data;
|
|
5496
|
+
ctx.addIssue({ code: "custom", message: parsed.error });
|
|
5497
|
+
return z9.NEVER;
|
|
5498
|
+
});
|
|
5499
|
+
var cameraFeeds = z9.string().transform((raw, ctx) => {
|
|
5500
|
+
const parsed = parseCameraFeedsEnv(raw);
|
|
5501
|
+
if (parsed.success)
|
|
5502
|
+
return parsed.data;
|
|
5503
|
+
ctx.addIssue({ code: "custom", message: parsed.error });
|
|
5504
|
+
return z9.NEVER;
|
|
5388
5505
|
});
|
|
5389
5506
|
var REQUIRED = "required when WARDEN_UDID is set";
|
|
5390
|
-
var schema =
|
|
5391
|
-
udid:
|
|
5392
|
-
deviceName:
|
|
5393
|
-
platform:
|
|
5394
|
-
bundleId:
|
|
5395
|
-
appPath:
|
|
5396
|
-
metroUrl:
|
|
5507
|
+
var schema = z9.object({
|
|
5508
|
+
udid: z9.string().min(1),
|
|
5509
|
+
deviceName: z9.string().optional(),
|
|
5510
|
+
platform: z9.enum(["ios", "android"]).default("ios"),
|
|
5511
|
+
bundleId: z9.string({ error: REQUIRED }).min(1, REQUIRED),
|
|
5512
|
+
appPath: z9.string().optional(),
|
|
5513
|
+
metroUrl: z9.string().optional(),
|
|
5514
|
+
devClientScheme: z9.string().min(1).optional(),
|
|
5397
5515
|
launchArgs: launchArgs.default([]),
|
|
5398
|
-
out:
|
|
5516
|
+
out: z9.string().optional(),
|
|
5399
5517
|
timeout: millis.default(DEFAULT_TIMEOUT_MS4),
|
|
5400
5518
|
actionTimeout: millis.default(DEFAULT_ACTION_TIMEOUT_MS),
|
|
5401
5519
|
assertionTimeout: millis.default(DEFAULT_ASSERTION_TIMEOUT_MS),
|
|
5402
|
-
specsDir:
|
|
5403
|
-
helperDir:
|
|
5404
|
-
simd:
|
|
5405
|
-
simdSocket:
|
|
5406
|
-
iosRunnerSocket:
|
|
5520
|
+
specsDir: z9.string().optional(),
|
|
5521
|
+
helperDir: z9.string().optional(),
|
|
5522
|
+
simd: z9.string().optional(),
|
|
5523
|
+
simdSocket: z9.string().optional(),
|
|
5524
|
+
iosRunnerSocket: z9.string().optional(),
|
|
5407
5525
|
logTimestamps: toggle.default(false),
|
|
5408
|
-
deviceKind:
|
|
5526
|
+
deviceKind: z9.enum(["physical"]).optional(),
|
|
5409
5527
|
iosRunnerRestart: launchArgs.optional(),
|
|
5410
5528
|
afterReboot: launchArgs.optional(),
|
|
5411
|
-
version:
|
|
5412
|
-
bin:
|
|
5413
|
-
perf:
|
|
5414
|
-
perfBudgets:
|
|
5529
|
+
version: z9.string().optional(),
|
|
5530
|
+
bin: z9.string().optional(),
|
|
5531
|
+
perf: z9.string().optional(),
|
|
5532
|
+
perfBudgets: z9.string().optional(),
|
|
5415
5533
|
conditions: conditions.optional(),
|
|
5416
|
-
leaseId:
|
|
5417
|
-
netproxyCommand: launchArgs.pipe(
|
|
5418
|
-
biometrics: toggle.optional()
|
|
5534
|
+
leaseId: z9.string().min(1).optional(),
|
|
5535
|
+
netproxyCommand: launchArgs.pipe(z9.array(z9.string()).min(1)).optional(),
|
|
5536
|
+
biometrics: toggle.optional(),
|
|
5537
|
+
a11yAudit: a11yAudit.optional(),
|
|
5538
|
+
cameraFeeds: cameraFeeds.optional()
|
|
5419
5539
|
});
|
|
5420
5540
|
function defined2(values) {
|
|
5421
5541
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
5422
5542
|
}
|
|
5423
5543
|
function parseArgv(raw) {
|
|
5424
5544
|
try {
|
|
5425
|
-
const parsed =
|
|
5545
|
+
const parsed = z9.array(z9.string()).min(1).safeParse(JSON.parse(raw));
|
|
5426
5546
|
if (parsed.success)
|
|
5427
5547
|
return parsed.data;
|
|
5428
5548
|
} catch {}
|
|
@@ -5443,6 +5563,7 @@ function toEnv(v, now) {
|
|
|
5443
5563
|
deviceName: v.deviceName,
|
|
5444
5564
|
appPath: v.appPath,
|
|
5445
5565
|
metroUrl: v.metroUrl,
|
|
5566
|
+
devClientScheme: v.devClientScheme,
|
|
5446
5567
|
specsDir: v.specsDir,
|
|
5447
5568
|
helperDir: v.helperDir,
|
|
5448
5569
|
simd: v.simd,
|
|
@@ -5457,6 +5578,8 @@ function toEnv(v, now) {
|
|
|
5457
5578
|
...v.leaseId === undefined ? {} : { leaseId: v.leaseId },
|
|
5458
5579
|
...v.netproxyCommand === undefined ? {} : { netproxyCommand: v.netproxyCommand },
|
|
5459
5580
|
...v.biometrics === true ? { biometrics: true } : {},
|
|
5581
|
+
...v.a11yAudit === undefined ? {} : { a11yAudit: v.a11yAudit },
|
|
5582
|
+
...v.cameraFeeds === undefined ? {} : { cameraFeeds: v.cameraFeeds },
|
|
5460
5583
|
...v.afterReboot === undefined ? {} : { afterReboot: v.afterReboot },
|
|
5461
5584
|
...v.bin === undefined ? {} : { wardenBin: parseArgv(v.bin) },
|
|
5462
5585
|
...v.perf === undefined ? {} : {
|
|
@@ -5489,25 +5612,25 @@ function readEnv(env, opts = {}) {
|
|
|
5489
5612
|
// ../../packages/e2e/src/runner/results.ts
|
|
5490
5613
|
import { appendFileSync, mkdirSync } from "fs";
|
|
5491
5614
|
import { dirname as dirname2 } from "path";
|
|
5492
|
-
import { z as
|
|
5493
|
-
var testStatusSchema =
|
|
5494
|
-
var testResultSchema =
|
|
5495
|
-
file:
|
|
5496
|
-
title:
|
|
5615
|
+
import { z as z10 } from "zod";
|
|
5616
|
+
var testStatusSchema = z10.enum(["passed", "failed", "skipped", "timed-out"]);
|
|
5617
|
+
var testResultSchema = z10.object({
|
|
5618
|
+
file: z10.string(),
|
|
5619
|
+
title: z10.array(z10.string()),
|
|
5497
5620
|
status: testStatusSchema,
|
|
5498
|
-
durationMs:
|
|
5499
|
-
startedAt:
|
|
5500
|
-
only:
|
|
5501
|
-
error:
|
|
5502
|
-
message:
|
|
5503
|
-
code:
|
|
5504
|
-
stack:
|
|
5621
|
+
durationMs: z10.number(),
|
|
5622
|
+
startedAt: z10.number(),
|
|
5623
|
+
only: z10.boolean().optional(),
|
|
5624
|
+
error: z10.object({
|
|
5625
|
+
message: z10.string(),
|
|
5626
|
+
code: z10.string().optional(),
|
|
5627
|
+
stack: z10.string().optional()
|
|
5505
5628
|
}).optional(),
|
|
5506
|
-
artifacts:
|
|
5507
|
-
device:
|
|
5508
|
-
udid:
|
|
5509
|
-
platform:
|
|
5510
|
-
name:
|
|
5629
|
+
artifacts: z10.object({ dir: z10.string(), files: z10.array(z10.string()) }).optional(),
|
|
5630
|
+
device: z10.object({
|
|
5631
|
+
udid: z10.string(),
|
|
5632
|
+
platform: z10.enum(["ios", "android"]),
|
|
5633
|
+
name: z10.string().optional()
|
|
5511
5634
|
})
|
|
5512
5635
|
});
|
|
5513
5636
|
function openResults(path) {
|
|
@@ -5935,19 +6058,624 @@ function getSession(opts = {}) {
|
|
|
5935
6058
|
return session;
|
|
5936
6059
|
}
|
|
5937
6060
|
|
|
5938
|
-
// ../../packages/e2e/src/
|
|
5939
|
-
var
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
6061
|
+
// ../../packages/e2e/src/a11y/speech.ts
|
|
6062
|
+
var ACTIONABLE_ROLES = new Set([
|
|
6063
|
+
"button",
|
|
6064
|
+
"switch",
|
|
6065
|
+
"checkbox",
|
|
6066
|
+
"slider",
|
|
6067
|
+
"link",
|
|
6068
|
+
"tab",
|
|
6069
|
+
"textfield",
|
|
6070
|
+
"securefield",
|
|
6071
|
+
"searchfield"
|
|
6072
|
+
]);
|
|
6073
|
+
var INPUT_ROLES = new Set(["textfield", "securefield", "searchfield"]);
|
|
6074
|
+
var ON_VALUES = new Set(["1", "on", "true", "checked"]);
|
|
6075
|
+
var OFF_VALUES = new Set(["0", "off", "false", "unchecked"]);
|
|
6076
|
+
function clean(s) {
|
|
6077
|
+
const t = s?.trim();
|
|
6078
|
+
return t ? t : undefined;
|
|
6079
|
+
}
|
|
6080
|
+
function has(el, trait) {
|
|
6081
|
+
return el.traits.includes(trait);
|
|
6082
|
+
}
|
|
6083
|
+
function isActionable(el, platform) {
|
|
6084
|
+
if (ACTIONABLE_ROLES.has(el.role))
|
|
6085
|
+
return true;
|
|
6086
|
+
if (platform === "ios")
|
|
6087
|
+
return has(el, "button") || has(el, "link") || has(el, "adjustable");
|
|
6088
|
+
return has(el, "clickable") || has(el, "long-clickable") || has(el, "checkable");
|
|
6089
|
+
}
|
|
6090
|
+
function isFocusTarget(el, platform) {
|
|
6091
|
+
return el.a11y?.focusable === true || isActionable(el, platform);
|
|
6092
|
+
}
|
|
6093
|
+
function isInput(el) {
|
|
6094
|
+
return INPUT_ROLES.has(el.role);
|
|
6095
|
+
}
|
|
6096
|
+
function ownName(el, platform) {
|
|
6097
|
+
const label = clean(el.label);
|
|
6098
|
+
if (label)
|
|
6099
|
+
return label;
|
|
6100
|
+
const text = clean(el.text);
|
|
6101
|
+
if (text)
|
|
6102
|
+
return text;
|
|
6103
|
+
if (isInput(el)) {
|
|
6104
|
+
return platform === "android" ? clean(el.value) ?? clean(el.placeholder) : undefined;
|
|
6105
|
+
}
|
|
6106
|
+
if (el.role === "switch" || el.role === "checkbox")
|
|
6107
|
+
return;
|
|
6108
|
+
return platform === "android" ? clean(el.value) : undefined;
|
|
6109
|
+
}
|
|
6110
|
+
function descendantTexts(el, platform, out) {
|
|
6111
|
+
for (const child of el.children) {
|
|
6112
|
+
if (child.hidden)
|
|
6113
|
+
continue;
|
|
6114
|
+
if (child.a11y?.important === false && child.children.length === 0)
|
|
6115
|
+
continue;
|
|
6116
|
+
const name = clean(child.label) ?? clean(child.text) ?? (child.role === "text" ? clean(child.value) : undefined);
|
|
6117
|
+
if (name)
|
|
6118
|
+
out.push(name);
|
|
6119
|
+
else
|
|
6120
|
+
descendantTexts(child, platform, out);
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
function hasActionableDescendant(el, platform) {
|
|
6124
|
+
return el.children.some((c) => !c.hidden && (isActionable(c, platform) || hasActionableDescendant(c, platform)));
|
|
6125
|
+
}
|
|
6126
|
+
function accessibleName(el, platform) {
|
|
6127
|
+
const own = ownName(el, platform);
|
|
6128
|
+
if (own)
|
|
6129
|
+
return own;
|
|
6130
|
+
if (!isFocusTarget(el, platform) && el.role !== "text")
|
|
6131
|
+
return;
|
|
6132
|
+
if (el.children.length === 0 || hasActionableDescendant(el, platform))
|
|
6133
|
+
return;
|
|
6134
|
+
const parts = [];
|
|
6135
|
+
descendantTexts(el, platform, parts);
|
|
6136
|
+
return parts.length ? parts.join(platform === "ios" ? " " : ", ") : undefined;
|
|
6137
|
+
}
|
|
6138
|
+
function switchState(el) {
|
|
6139
|
+
const v = clean(el.value)?.toLowerCase();
|
|
6140
|
+
if (v && ON_VALUES.has(v))
|
|
6141
|
+
return "on";
|
|
6142
|
+
if (v && OFF_VALUES.has(v))
|
|
6143
|
+
return "off";
|
|
6144
|
+
if (el.checked !== undefined)
|
|
6145
|
+
return el.checked ? "on" : "off";
|
|
6146
|
+
if (has(el, "checked"))
|
|
6147
|
+
return "on";
|
|
6148
|
+
return;
|
|
6149
|
+
}
|
|
6150
|
+
function iosRoleWord(el) {
|
|
6151
|
+
if (has(el, "header"))
|
|
6152
|
+
return "heading";
|
|
6153
|
+
if (has(el, "adjustable") || el.role === "slider")
|
|
6154
|
+
return "adjustable";
|
|
6155
|
+
if (has(el, "link") || el.role === "link")
|
|
6156
|
+
return "link";
|
|
6157
|
+
switch (el.role) {
|
|
6158
|
+
case "switch":
|
|
6159
|
+
return "switch button";
|
|
6160
|
+
case "checkbox":
|
|
6161
|
+
return "checkbox";
|
|
6162
|
+
case "tab":
|
|
6163
|
+
return "tab";
|
|
6164
|
+
case "textfield":
|
|
6165
|
+
return "text field";
|
|
6166
|
+
case "securefield":
|
|
6167
|
+
return "secure text field";
|
|
6168
|
+
case "searchfield":
|
|
6169
|
+
return "search field";
|
|
6170
|
+
case "image":
|
|
6171
|
+
return "image";
|
|
6172
|
+
case "button":
|
|
6173
|
+
return "button";
|
|
6174
|
+
default:
|
|
6175
|
+
}
|
|
6176
|
+
if (has(el, "button"))
|
|
6177
|
+
return "button";
|
|
6178
|
+
if (has(el, "image"))
|
|
6179
|
+
return "image";
|
|
6180
|
+
return;
|
|
6181
|
+
}
|
|
6182
|
+
function androidRoleWord(el) {
|
|
6183
|
+
if (has(el, "heading") || has(el, "header"))
|
|
6184
|
+
return "heading";
|
|
6185
|
+
if (has(el, "link") || el.role === "link")
|
|
6186
|
+
return "link";
|
|
6187
|
+
switch (el.role) {
|
|
6188
|
+
case "switch":
|
|
6189
|
+
return "switch";
|
|
6190
|
+
case "checkbox":
|
|
6191
|
+
return "checkbox";
|
|
6192
|
+
case "textfield":
|
|
6193
|
+
case "securefield":
|
|
6194
|
+
case "searchfield":
|
|
6195
|
+
return "edit box";
|
|
6196
|
+
case "image":
|
|
6197
|
+
return "image";
|
|
6198
|
+
case "button":
|
|
6199
|
+
return "button";
|
|
6200
|
+
case "tab":
|
|
6201
|
+
return "tab";
|
|
6202
|
+
case "slider":
|
|
6203
|
+
return "seek control";
|
|
6204
|
+
default:
|
|
6205
|
+
}
|
|
6206
|
+
if (has(el, "checkable"))
|
|
6207
|
+
return "checkbox";
|
|
6208
|
+
return;
|
|
6209
|
+
}
|
|
6210
|
+
function expandedState(el) {
|
|
6211
|
+
if (has(el, "expanded"))
|
|
6212
|
+
return "expanded";
|
|
6213
|
+
if (has(el, "collapsed"))
|
|
6214
|
+
return "collapsed";
|
|
6215
|
+
return;
|
|
6216
|
+
}
|
|
6217
|
+
function join2(parts) {
|
|
6218
|
+
return parts.filter((p) => !!p).join(", ");
|
|
6219
|
+
}
|
|
6220
|
+
function announceIos(el, opts) {
|
|
6221
|
+
const label = accessibleName(el, "ios");
|
|
6222
|
+
const toggle = el.role === "switch" || el.role === "checkbox" ? switchState(el) : undefined;
|
|
6223
|
+
const rawValue = clean(el.value);
|
|
6224
|
+
let value;
|
|
6225
|
+
if (el.role === "securefield")
|
|
6226
|
+
value = undefined;
|
|
6227
|
+
else if (isInput(el))
|
|
6228
|
+
value = rawValue ?? clean(el.placeholder);
|
|
6229
|
+
else if (!toggle && el.role !== "text" && rawValue && rawValue !== label)
|
|
6230
|
+
value = rawValue;
|
|
6231
|
+
const role = iosRoleWord(el);
|
|
6232
|
+
const state = [];
|
|
6233
|
+
if (toggle)
|
|
6234
|
+
state.push(toggle);
|
|
6235
|
+
if (el.selected || has(el, "selected"))
|
|
6236
|
+
state.push("selected");
|
|
6237
|
+
const expanded = expandedState(el);
|
|
6238
|
+
if (expanded)
|
|
6239
|
+
state.push(expanded);
|
|
6240
|
+
if (!el.enabled || has(el, "not-enabled"))
|
|
6241
|
+
state.push("dimmed");
|
|
6242
|
+
const hint = opts.hints === false ? undefined : clean(el.a11y?.hint);
|
|
6243
|
+
return build(el, "ios", { label, value, role, state, hint }, join2([label, value, role, ...state, hint]));
|
|
6244
|
+
}
|
|
6245
|
+
function announceAndroid(el, opts) {
|
|
6246
|
+
const name = accessibleName(el, "android");
|
|
6247
|
+
const state = [];
|
|
6248
|
+
const toggle = switchState(el);
|
|
6249
|
+
if (el.role === "switch") {
|
|
6250
|
+
if (toggle)
|
|
6251
|
+
state.push(toggle);
|
|
6252
|
+
} else if (el.role === "checkbox" || has(el, "checkable")) {
|
|
6253
|
+
const checked = el.checked ?? has(el, "checked");
|
|
6254
|
+
state.push(checked ? "checked" : "not checked");
|
|
6255
|
+
}
|
|
6256
|
+
if (el.selected || has(el, "selected"))
|
|
6257
|
+
state.push("selected");
|
|
6258
|
+
const expanded = expandedState(el);
|
|
6259
|
+
if (expanded)
|
|
6260
|
+
state.push(expanded);
|
|
6261
|
+
const role = androidRoleWord(el);
|
|
6262
|
+
const disabled = !el.enabled;
|
|
6263
|
+
const hint = opts.hints === false ? undefined : clean(el.a11y?.hint);
|
|
6264
|
+
const text = join2([...state, name, role, disabled ? "disabled" : undefined, hint]);
|
|
6265
|
+
return build(el, "android", { label: name, role, state: disabled ? [...state, "disabled"] : state, hint }, text);
|
|
6266
|
+
}
|
|
6267
|
+
function build(el, platform, parts, text) {
|
|
6268
|
+
const { label, value, role, state, hint } = parts;
|
|
6269
|
+
return {
|
|
6270
|
+
id: el.id,
|
|
6271
|
+
...el.identifier ? { identifier: el.identifier } : {},
|
|
6272
|
+
text,
|
|
6273
|
+
role: el.role,
|
|
6274
|
+
platform,
|
|
6275
|
+
source: "simulated",
|
|
6276
|
+
parts: {
|
|
6277
|
+
...label ? { label } : {},
|
|
6278
|
+
...value ? { value } : {},
|
|
6279
|
+
...role ? { role } : {},
|
|
6280
|
+
state,
|
|
6281
|
+
...hint ? { hint } : {}
|
|
6282
|
+
}
|
|
6283
|
+
};
|
|
6284
|
+
}
|
|
6285
|
+
function announce(el, platform, opts = {}) {
|
|
6286
|
+
return platform === "ios" ? announceIos(el, opts) : announceAndroid(el, opts);
|
|
6287
|
+
}
|
|
6288
|
+
|
|
6289
|
+
// ../../packages/e2e/src/a11y/order.ts
|
|
6290
|
+
function onScreen(el, screen) {
|
|
6291
|
+
const f = el.frame;
|
|
6292
|
+
if (f.width <= 0 || f.height <= 0)
|
|
6293
|
+
return false;
|
|
6294
|
+
return f.x < screen.width && f.y < screen.height && f.x + f.width > 0 && f.y + f.height > 0;
|
|
6295
|
+
}
|
|
6296
|
+
function verticalOverlap(a, b) {
|
|
6297
|
+
const top = Math.max(a.frame.y, b.frame.y);
|
|
6298
|
+
const bottom = Math.min(a.frame.y + a.frame.height, b.frame.y + b.frame.height);
|
|
6299
|
+
return bottom - top;
|
|
6300
|
+
}
|
|
6301
|
+
function rowMajor(items) {
|
|
6302
|
+
const byTop = [...items].sort((a, b) => a.frame.y - b.frame.y || a.frame.x - b.frame.x);
|
|
6303
|
+
const rows = [];
|
|
6304
|
+
for (const el of byTop) {
|
|
6305
|
+
const row = rows.find((r) => {
|
|
6306
|
+
const anchor = r[0];
|
|
6307
|
+
return anchor !== undefined && verticalOverlap(anchor, el) > 0.5 * Math.min(anchor.frame.height, el.frame.height);
|
|
6308
|
+
});
|
|
6309
|
+
if (row)
|
|
6310
|
+
row.push(el);
|
|
6311
|
+
else
|
|
6312
|
+
rows.push([el]);
|
|
6313
|
+
}
|
|
6314
|
+
return rows.flatMap((r) => r.sort((a, b) => a.frame.x - b.frame.x));
|
|
6315
|
+
}
|
|
6316
|
+
function orderSiblings(children, platform) {
|
|
6317
|
+
if (platform === "android")
|
|
6318
|
+
return children;
|
|
6319
|
+
const explicit = children.filter((c) => c.a11y?.order !== undefined);
|
|
6320
|
+
if (explicit.length > 0) {
|
|
6321
|
+
const rest = children.filter((c) => c.a11y?.order === undefined);
|
|
6322
|
+
return [...explicit.sort((a, b) => (a.a11y?.order ?? 0) - (b.a11y?.order ?? 0)), ...rowMajor(rest)];
|
|
6323
|
+
}
|
|
6324
|
+
return rowMajor(children);
|
|
6325
|
+
}
|
|
6326
|
+
function hasActionableDescendant2(el, platform) {
|
|
6327
|
+
return el.children.some((c) => !c.hidden && (isActionable(c, platform) || hasActionableDescendant2(c, platform)));
|
|
6328
|
+
}
|
|
6329
|
+
function hasSpokenDescendant(el, platform) {
|
|
6330
|
+
return el.children.some((c) => !c.hidden && (isActionable(c, platform) || accessibleName(c, platform) !== undefined || hasSpokenDescendant(c, platform)));
|
|
6331
|
+
}
|
|
6332
|
+
var CONTAINERS = new Set([
|
|
6333
|
+
"application",
|
|
6334
|
+
"window",
|
|
6335
|
+
"toolbar",
|
|
6336
|
+
"navbar",
|
|
6337
|
+
"tabbar",
|
|
6338
|
+
"list",
|
|
6339
|
+
"scrollview"
|
|
6340
|
+
]);
|
|
6341
|
+
function swallows(el, platform, actionable) {
|
|
6342
|
+
if (actionable)
|
|
6343
|
+
return !hasActionableDescendant2(el, platform);
|
|
6344
|
+
return el.a11y?.focusable === true || !hasSpokenDescendant(el, platform);
|
|
6345
|
+
}
|
|
6346
|
+
function walk(el, platform, screen, opts, out) {
|
|
6347
|
+
if (el.hidden)
|
|
6348
|
+
return;
|
|
6349
|
+
const visible = el.a11y?.important !== false && onScreen(el, screen);
|
|
6350
|
+
if (visible) {
|
|
6351
|
+
const actionable = isActionable(el, platform);
|
|
6352
|
+
const container = CONTAINERS.has(el.role) && !actionable;
|
|
6353
|
+
const named = accessibleName(el, platform) !== undefined;
|
|
6354
|
+
if (!container && (actionable || named)) {
|
|
6355
|
+
if (swallows(el, platform, actionable)) {
|
|
6356
|
+
out.push({ el, announcement: announce(el, platform, opts) });
|
|
6357
|
+
return;
|
|
6358
|
+
}
|
|
6359
|
+
if (named)
|
|
6360
|
+
out.push({ el, announcement: announce(el, platform, opts) });
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6363
|
+
for (const child of orderSiblings(el.children, platform))
|
|
6364
|
+
walk(child, platform, screen, opts, out);
|
|
6365
|
+
}
|
|
6366
|
+
function readingOrder(snapshot, platform, opts = {}) {
|
|
6367
|
+
const out = [];
|
|
6368
|
+
for (const el of orderSiblings(snapshot.elements, platform))
|
|
6369
|
+
walk(el, platform, snapshot.screen, opts, out);
|
|
6370
|
+
return out.map((e) => e.announcement);
|
|
6371
|
+
}
|
|
6372
|
+
|
|
6373
|
+
// ../../packages/e2e/src/a11y/rules/rule.context.ts
|
|
6374
|
+
function unitsPerPoint(snapshot) {
|
|
6375
|
+
return snapshot.platform === "android" ? snapshot.screen.scale || 1 : 1;
|
|
6376
|
+
}
|
|
6377
|
+
function isAncestor(a, b, parents) {
|
|
6378
|
+
for (let p = parents.get(b);p; p = parents.get(p))
|
|
6379
|
+
if (p === a)
|
|
6380
|
+
return true;
|
|
6381
|
+
return false;
|
|
6382
|
+
}
|
|
6383
|
+
function related(a, b, parents) {
|
|
6384
|
+
return a === b || isAncestor(a, b, parents) || isAncestor(b, a, parents);
|
|
6385
|
+
}
|
|
6386
|
+
function issue(rule, severity, el, message, wcag) {
|
|
6387
|
+
return {
|
|
6388
|
+
rule,
|
|
6389
|
+
severity,
|
|
6390
|
+
source: "warden",
|
|
6391
|
+
elementId: el.id,
|
|
6392
|
+
message,
|
|
6393
|
+
...wcag === undefined ? {} : { wcag },
|
|
6394
|
+
...el.identifier === undefined ? {} : { identifier: el.identifier }
|
|
6395
|
+
};
|
|
6396
|
+
}
|
|
6397
|
+
function describe(el) {
|
|
6398
|
+
const what = el.identifier ?? el.label ?? el.text ?? el.rawType;
|
|
6399
|
+
return `${el.role} "${what}"`;
|
|
6400
|
+
}
|
|
6401
|
+
|
|
6402
|
+
// ../../packages/e2e/src/a11y/rules/duplicate-name.ts
|
|
6403
|
+
var key = (s) => s.trim().toLowerCase().replace(/\s+/g, " ");
|
|
6404
|
+
var sameFrame2 = (a, b) => a.frame.x === b.frame.x && a.frame.y === b.frame.y && a.frame.width === b.frame.width && a.frame.height === b.frame.height;
|
|
6405
|
+
var duplicateName = (_snapshot, ctx) => {
|
|
6406
|
+
const named = ctx.reachable.flatMap((el) => {
|
|
6407
|
+
if (!isActionable(el, ctx.platform))
|
|
6408
|
+
return [];
|
|
6409
|
+
const name = accessibleName(el, ctx.platform);
|
|
6410
|
+
return name === undefined ? [] : [{ el, name: key(name), raw: name }];
|
|
6411
|
+
});
|
|
6412
|
+
const out = [];
|
|
6413
|
+
const kept = [];
|
|
6414
|
+
for (const n of named) {
|
|
6415
|
+
const first = kept.find((k) => k.name === n.name && !sameFrame2(k.el, n.el) && !related(k.el, n.el, ctx.parents) && !(k.el.identifier !== undefined && k.el.identifier === n.el.identifier));
|
|
6416
|
+
if (first) {
|
|
6417
|
+
out.push(issue("duplicate-name", "warning", n.el, `${describe(n.el)} is announced "${n.raw}", the same as ${describe(first.el)}`, "2.4.6"));
|
|
6418
|
+
} else
|
|
6419
|
+
kept.push(n);
|
|
6420
|
+
}
|
|
6421
|
+
return out;
|
|
5947
6422
|
};
|
|
5948
6423
|
|
|
5949
|
-
// ../../packages/e2e/src/
|
|
5950
|
-
|
|
6424
|
+
// ../../packages/e2e/src/a11y/rules/hidden-focusable.ts
|
|
6425
|
+
function offScreen(el, screen) {
|
|
6426
|
+
const f = el.frame;
|
|
6427
|
+
return f.x >= screen.width || f.y >= screen.height || f.x + f.width <= 0 || f.y + f.height <= 0;
|
|
6428
|
+
}
|
|
6429
|
+
var hiddenFocusable = (snapshot) => snapshot.flat.filter((el) => el.a11y?.focusable === true && el.a11y.important !== false).flatMap((el) => {
|
|
6430
|
+
const why = el.hidden ? "hidden" : el.frame.width <= 0 || el.frame.height <= 0 ? "zero-size" : offScreen(el, snapshot.screen) ? "off screen" : undefined;
|
|
6431
|
+
return why ? [issue("hidden-focusable", "warning", el, `${describe(el)} is ${why} but still focusable`)] : [];
|
|
6432
|
+
});
|
|
6433
|
+
|
|
6434
|
+
// ../../packages/e2e/src/a11y/rules/image-no-name.ts
|
|
6435
|
+
var imageNoName = (_snapshot, ctx) => ctx.reachable.filter((el) => el.role === "image" && accessibleName(el, ctx.platform) === undefined).map((el) => issue("image-no-name", "error", el, `${describe(el)} has no name (add a label, or mark it decorative)`, "1.1.1"));
|
|
6436
|
+
|
|
6437
|
+
// ../../packages/e2e/src/a11y/rules/label-in-name.ts
|
|
6438
|
+
var norm = (s) => s.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
|
|
6439
|
+
function visibleText(el, platform) {
|
|
6440
|
+
const own = platform === "android" ? el.text?.trim() : undefined;
|
|
6441
|
+
if (own)
|
|
6442
|
+
return own;
|
|
6443
|
+
const parts = [];
|
|
6444
|
+
const walk = (e) => {
|
|
6445
|
+
for (const c of e.children) {
|
|
6446
|
+
if (c.hidden)
|
|
6447
|
+
continue;
|
|
6448
|
+
if (c.role === "text") {
|
|
6449
|
+
const t = (c.text ?? c.label ?? c.value)?.trim();
|
|
6450
|
+
if (t)
|
|
6451
|
+
parts.push(t);
|
|
6452
|
+
} else
|
|
6453
|
+
walk(c);
|
|
6454
|
+
}
|
|
6455
|
+
};
|
|
6456
|
+
walk(el);
|
|
6457
|
+
return parts.join(" ");
|
|
6458
|
+
}
|
|
6459
|
+
var labelInName = (_snapshot, ctx) => ctx.reachable.flatMap((el) => {
|
|
6460
|
+
if (!isActionable(el, ctx.platform) || !el.label?.trim())
|
|
6461
|
+
return [];
|
|
6462
|
+
const visible = visibleText(el, ctx.platform);
|
|
6463
|
+
const name = accessibleName(el, ctx.platform);
|
|
6464
|
+
if (!visible || !name || norm(name).includes(norm(visible)))
|
|
6465
|
+
return [];
|
|
6466
|
+
return [issue("label-in-name", "error", el, `${describe(el)} shows "${visible}" but is named "${name}"`, "2.5.3")];
|
|
6467
|
+
});
|
|
6468
|
+
|
|
6469
|
+
// ../../packages/e2e/src/a11y/rules/missing-name.ts
|
|
6470
|
+
function isScrollContainer(el, platform) {
|
|
6471
|
+
const scrolls = el.role === "scrollview" || el.role === "list" || el.traits.includes("scrollable");
|
|
6472
|
+
return scrolls && !isActionable(el, platform);
|
|
6473
|
+
}
|
|
6474
|
+
var missingName = (_snapshot, ctx) => ctx.reachable.filter((el) => isFocusTarget(el, ctx.platform) && !isScrollContainer(el, ctx.platform) && accessibleName(el, ctx.platform) === undefined).map((el) => issue("missing-name", "error", el, `${describe(el)} has no accessible name`, "4.1.2"));
|
|
6475
|
+
|
|
6476
|
+
// ../../packages/e2e/src/a11y/rules/role-in-name.ts
|
|
6477
|
+
var WORDS = {
|
|
6478
|
+
button: /\b(button|btn)\b/i,
|
|
6479
|
+
image: /\b(image|icon)\b/i
|
|
6480
|
+
};
|
|
6481
|
+
var roleInName = (_snapshot, ctx) => ctx.reachable.flatMap((el) => {
|
|
6482
|
+
const pattern = WORDS[el.role];
|
|
6483
|
+
const name = pattern ? accessibleName(el, ctx.platform) : undefined;
|
|
6484
|
+
const hit = name ? pattern?.exec(name)?.[0] : undefined;
|
|
6485
|
+
return hit ? [
|
|
6486
|
+
issue("role-in-name", "warning", el, `${describe(el)} name "${name}" contains its role word "${hit}", announced twice`)
|
|
6487
|
+
] : [];
|
|
6488
|
+
});
|
|
6489
|
+
|
|
6490
|
+
// ../../packages/e2e/src/a11y/rules/target-size.ts
|
|
6491
|
+
var WCAG_MIN = 24;
|
|
6492
|
+
var IOS_MIN = 44;
|
|
6493
|
+
var ANDROID_MIN = 48;
|
|
6494
|
+
var RADIUS = WCAG_MIN / 2;
|
|
6495
|
+
function targets(snapshot, ctx) {
|
|
6496
|
+
const k = unitsPerPoint(snapshot);
|
|
6497
|
+
return ctx.reachable.filter((el) => el.enabled && isActionable(el, ctx.platform) && el.frame.width > 0 && el.frame.height > 0).map((el) => ({ el, x: el.frame.x / k, y: el.frame.y / k, w: el.frame.width / k, h: el.frame.height / k }));
|
|
6498
|
+
}
|
|
6499
|
+
function centre(b) {
|
|
6500
|
+
return { x: b.x + b.w / 2, y: b.y + b.h / 2 };
|
|
6501
|
+
}
|
|
6502
|
+
function distanceToBox(p, b) {
|
|
6503
|
+
const dx = Math.max(b.x - p.x, 0, p.x - (b.x + b.w));
|
|
6504
|
+
const dy = Math.max(b.y - p.y, 0, p.y - (b.y + b.h));
|
|
6505
|
+
return Math.hypot(dx, dy);
|
|
6506
|
+
}
|
|
6507
|
+
var undersized = (b) => b.w < WCAG_MIN || b.h < WCAG_MIN;
|
|
6508
|
+
function wcagFailures(snapshot, ctx) {
|
|
6509
|
+
const all = targets(snapshot, ctx);
|
|
6510
|
+
return all.filter((b) => {
|
|
6511
|
+
if (!undersized(b))
|
|
6512
|
+
return false;
|
|
6513
|
+
const c = centre(b);
|
|
6514
|
+
return all.some((o) => {
|
|
6515
|
+
if (related(b.el, o.el, ctx.parents))
|
|
6516
|
+
return false;
|
|
6517
|
+
if (undersized(o))
|
|
6518
|
+
return Math.hypot(centre(o).x - c.x, centre(o).y - c.y) < RADIUS * 2;
|
|
6519
|
+
return distanceToBox(c, o) < RADIUS;
|
|
6520
|
+
});
|
|
6521
|
+
});
|
|
6522
|
+
}
|
|
6523
|
+
var fmt = (n) => String(Math.round(n * 10) / 10);
|
|
6524
|
+
var targetSize = (snapshot, ctx) => {
|
|
6525
|
+
const unit = ctx.platform === "ios" ? "pt" : "dp";
|
|
6526
|
+
return wcagFailures(snapshot, ctx).map((b) => issue("target-size", "error", b.el, `${describe(b.el)} is ${fmt(b.w)}x${fmt(b.h)}${unit}, under ${WCAG_MIN}x${WCAG_MIN}${unit} with another target within ${WCAG_MIN}${unit}`, "2.5.8"));
|
|
6527
|
+
};
|
|
6528
|
+
var targetSizePlatform = (snapshot, ctx) => {
|
|
6529
|
+
const min = ctx.platform === "ios" ? IOS_MIN : ANDROID_MIN;
|
|
6530
|
+
const unit = ctx.platform === "ios" ? "pt" : "dp";
|
|
6531
|
+
const failing = new Set(wcagFailures(snapshot, ctx).map((b) => b.el));
|
|
6532
|
+
return targets(snapshot, ctx).filter((b) => (b.w < min || b.h < min) && !failing.has(b.el)).map((b) => issue("target-size-platform", "warning", b.el, `${describe(b.el)} is ${fmt(b.w)}x${fmt(b.h)}${unit}, under the platform minimum ${min}${unit}`));
|
|
6533
|
+
};
|
|
6534
|
+
|
|
6535
|
+
// ../../packages/e2e/src/a11y/rules/text-clipped.ts
|
|
6536
|
+
var CLIPPING_MIN_SCALE = 1.6;
|
|
6537
|
+
var ELLIPSIS = /(\u2026|\.\.\.)$/;
|
|
6538
|
+
var TOLERANCE = 1;
|
|
6539
|
+
function overflows(el, parent) {
|
|
6540
|
+
const f = el.frame;
|
|
6541
|
+
const p = parent.frame;
|
|
6542
|
+
return f.x < p.x - TOLERANCE || f.y < p.y - TOLERANCE || f.x + f.width > p.x + p.width + TOLERANCE || f.y + f.height > p.y + p.height + TOLERANCE;
|
|
6543
|
+
}
|
|
6544
|
+
var textClipped = (snapshot, ctx) => {
|
|
6545
|
+
if (ctx.textScale === undefined || ctx.textScale < CLIPPING_MIN_SCALE)
|
|
6546
|
+
return [];
|
|
6547
|
+
return ctx.reachable.flatMap((el) => {
|
|
6548
|
+
if (el.role !== "text")
|
|
6549
|
+
return [];
|
|
6550
|
+
const content = (el.text ?? el.label ?? el.value)?.trim();
|
|
6551
|
+
if (content && ELLIPSIS.test(content))
|
|
6552
|
+
return [issue("text-clipped", "warning", el, `${describe(el)} is truncated ("${content}")`, "1.4.4")];
|
|
6553
|
+
const parent = ctx.parents.get(el);
|
|
6554
|
+
if (!parent || parent.role === "scrollview" || parent.role === "list" || parent.traits.includes("scrollable"))
|
|
6555
|
+
return [];
|
|
6556
|
+
if (parent.frame.width >= snapshot.screen.width && parent.frame.height >= snapshot.screen.height)
|
|
6557
|
+
return [];
|
|
6558
|
+
return overflows(el, parent) ? [issue("text-clipped", "warning", el, `${describe(el)} overflows its container`, "1.4.4")] : [];
|
|
6559
|
+
});
|
|
6560
|
+
};
|
|
6561
|
+
|
|
6562
|
+
// ../../packages/e2e/src/a11y/audit.ts
|
|
6563
|
+
var WARDEN_RULES = [
|
|
6564
|
+
{ id: "missing-name", run: missingName },
|
|
6565
|
+
{ id: "image-no-name", run: imageNoName },
|
|
6566
|
+
{ id: "label-in-name", run: labelInName },
|
|
6567
|
+
{ id: "target-size", run: targetSize },
|
|
6568
|
+
{ id: "target-size-platform", run: targetSizePlatform },
|
|
6569
|
+
{ id: "duplicate-name", run: duplicateName },
|
|
6570
|
+
{ id: "role-in-name", run: roleInName },
|
|
6571
|
+
{ id: "hidden-focusable", run: hiddenFocusable },
|
|
6572
|
+
{ id: "text-clipped", run: textClipped }
|
|
6573
|
+
];
|
|
6574
|
+
function reachableNodes(snapshot) {
|
|
6575
|
+
const reachable = [];
|
|
6576
|
+
const parents = new Map;
|
|
6577
|
+
const walk = (el, parent) => {
|
|
6578
|
+
if (parent)
|
|
6579
|
+
parents.set(el, parent);
|
|
6580
|
+
if (el.hidden)
|
|
6581
|
+
return;
|
|
6582
|
+
if (el.a11y?.important !== false)
|
|
6583
|
+
reachable.push(el);
|
|
6584
|
+
for (const c of el.children)
|
|
6585
|
+
walk(c, el);
|
|
6586
|
+
};
|
|
6587
|
+
for (const el of snapshot.elements)
|
|
6588
|
+
walk(el, undefined);
|
|
6589
|
+
return { reachable, parents };
|
|
6590
|
+
}
|
|
6591
|
+
var SEVERITY_RANK = { warning: 0, error: 1 };
|
|
6592
|
+
var ruleRank = (rule) => {
|
|
6593
|
+
const i = WARDEN_RULES.findIndex((r) => r.id === rule);
|
|
6594
|
+
return i === -1 ? WARDEN_RULES.length : i;
|
|
6595
|
+
};
|
|
6596
|
+
function positions(snapshot, platform) {
|
|
6597
|
+
const listed = new Map;
|
|
6598
|
+
readingOrder(snapshot, platform).forEach((a, i) => {
|
|
6599
|
+
listed.set(a.id, i);
|
|
6600
|
+
});
|
|
6601
|
+
const pos = new Map;
|
|
6602
|
+
let last = -1;
|
|
6603
|
+
snapshot.flat.forEach((el, i) => {
|
|
6604
|
+
const at = listed.get(el.id);
|
|
6605
|
+
if (at !== undefined) {
|
|
6606
|
+
pos.set(el.id, at);
|
|
6607
|
+
last = at;
|
|
6608
|
+
} else
|
|
6609
|
+
pos.set(el.id, last + 0.5 + i / 1e6);
|
|
6610
|
+
});
|
|
6611
|
+
return pos;
|
|
6612
|
+
}
|
|
6613
|
+
function runAudit(snapshot, platform, opts = {}) {
|
|
6614
|
+
const { reachable, parents } = reachableNodes(snapshot);
|
|
6615
|
+
const resolved = opts.textSize === undefined ? undefined : resolveTextSize(opts.textSize);
|
|
6616
|
+
const ctx = {
|
|
6617
|
+
platform,
|
|
6618
|
+
reachable,
|
|
6619
|
+
parents,
|
|
6620
|
+
...resolved?.success ? { textScale: resolved.data.scale } : {}
|
|
6621
|
+
};
|
|
6622
|
+
const wanted = opts.rules === undefined ? undefined : new Set(opts.rules);
|
|
6623
|
+
const found = WARDEN_RULES.filter((r) => wanted === undefined || wanted.has(r.id)).flatMap((r) => r.run(snapshot, ctx));
|
|
6624
|
+
const ignore = opts.ignore ?? [];
|
|
6625
|
+
const kept = found.filter((i) => !ignore.some((g) => g.rule === i.rule && (g.identifier === undefined || g.identifier === i.identifier)));
|
|
6626
|
+
const seen = new Set;
|
|
6627
|
+
const unique = kept.filter((i) => {
|
|
6628
|
+
const k = `${i.rule}\x00${i.elementId ?? ""}\x00${i.message}`;
|
|
6629
|
+
if (seen.has(k))
|
|
6630
|
+
return false;
|
|
6631
|
+
seen.add(k);
|
|
6632
|
+
return true;
|
|
6633
|
+
});
|
|
6634
|
+
const pos = positions(snapshot, platform);
|
|
6635
|
+
const at = (i) => i.elementId === undefined ? Number.MAX_SAFE_INTEGER : pos.get(i.elementId) ?? 0;
|
|
6636
|
+
const issues = unique.sort((a, b) => at(a) - at(b) || ruleRank(a.rule) - ruleRank(b.rule));
|
|
6637
|
+
return { issues, checked: reachable.length, sources: ["warden"], skipped: [] };
|
|
6638
|
+
}
|
|
6639
|
+
function failing(result, minSeverity = "error") {
|
|
6640
|
+
return result.issues.filter((i) => SEVERITY_RANK[i.severity] >= SEVERITY_RANK[minSeverity]);
|
|
6641
|
+
}
|
|
6642
|
+
function elementKey(i) {
|
|
6643
|
+
return i.identifier ?? i.elementId;
|
|
6644
|
+
}
|
|
6645
|
+
function mergeIssues(base, extra, skipped = []) {
|
|
6646
|
+
const issues = [...base.issues];
|
|
6647
|
+
const added = [];
|
|
6648
|
+
for (const e of extra) {
|
|
6649
|
+
const key = elementKey(e);
|
|
6650
|
+
const idx = issues.findIndex((i) => key !== undefined && elementKey(i) === key && (e.wcag !== undefined ? i.wcag === e.wcag : i.rule === e.rule && i.message === e.message));
|
|
6651
|
+
const hit = idx === -1 ? undefined : issues[idx];
|
|
6652
|
+
if (hit === undefined) {
|
|
6653
|
+
added.push(e);
|
|
6654
|
+
} else if (hit.source !== e.source && !hit.message.includes(`also reported by ${e.source}`)) {
|
|
6655
|
+
issues[idx] = { ...hit, message: `${hit.message} (also reported by ${e.source})` };
|
|
6656
|
+
}
|
|
6657
|
+
}
|
|
6658
|
+
const sources = [...new Set([...base.sources, ...extra.map((e) => e.source)])];
|
|
6659
|
+
return {
|
|
6660
|
+
issues: [...issues, ...added],
|
|
6661
|
+
checked: base.checked,
|
|
6662
|
+
sources,
|
|
6663
|
+
skipped: [...base.skipped, ...skipped]
|
|
6664
|
+
};
|
|
6665
|
+
}
|
|
6666
|
+
function formatIssues(result, minSeverity = "warning") {
|
|
6667
|
+
const shown = failing(result, minSeverity);
|
|
6668
|
+
const head = `a11y audit: ${shown.length} issue${shown.length === 1 ? "" : "s"} in ${result.checked} elements (${result.sources.join(", ")})`;
|
|
6669
|
+
const skipped = result.skipped.map((s) => `skipped ${s.source}: ${s.reason}`);
|
|
6670
|
+
if (shown.length === 0)
|
|
6671
|
+
return [head, ...skipped].join(`
|
|
6672
|
+
`);
|
|
6673
|
+
const rows = shown.map((i) => [i.severity, i.rule, i.wcag ?? "-", i.identifier ?? i.elementId ?? "-", i.message]);
|
|
6674
|
+
const widths = [0, 1, 2, 3].map((c) => Math.max(...rows.map((r) => r[c]?.length ?? 0)));
|
|
6675
|
+
const lines = rows.map((r) => `${r.map((cell, c) => c < 4 ? cell.padEnd(widths[c] ?? 0) : cell).join(" ")}`);
|
|
6676
|
+
return [head, ...lines, ...skipped].join(`
|
|
6677
|
+
`);
|
|
6678
|
+
}
|
|
5951
6679
|
|
|
5952
6680
|
// ../../packages/e2e/src/driver/unwrap.ts
|
|
5953
6681
|
var TIMEOUTS = new Set(["E_TIMEOUT", "E_APP_NOT_RESPONDING"]);
|
|
@@ -6000,14 +6728,1048 @@ async function unwrap(call, action, signal) {
|
|
|
6000
6728
|
return result.data;
|
|
6001
6729
|
}
|
|
6002
6730
|
|
|
6731
|
+
// ../../packages/e2e/src/driver/a11y-events.ts
|
|
6732
|
+
function canReadA11yEvents(driver) {
|
|
6733
|
+
return typeof driver.a11yEvents === "function";
|
|
6734
|
+
}
|
|
6735
|
+
var KINDS = new Set([
|
|
6736
|
+
"announcement",
|
|
6737
|
+
"live-region",
|
|
6738
|
+
"screen-changed",
|
|
6739
|
+
"layout-changed"
|
|
6740
|
+
]);
|
|
6741
|
+
function isRecord(value) {
|
|
6742
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6743
|
+
}
|
|
6744
|
+
var SYSTEM_ANNOUNCEMENT = /^AXAnnouncementType/;
|
|
6745
|
+
function parseA11yEvent(value, source) {
|
|
6746
|
+
if (!isRecord(value) || typeof value.kind !== "string" || !KINDS.has(value.kind))
|
|
6747
|
+
return;
|
|
6748
|
+
if (typeof value.at !== "number" || !Number.isFinite(value.at))
|
|
6749
|
+
return;
|
|
6750
|
+
if (value.text !== undefined && typeof value.text !== "string")
|
|
6751
|
+
return;
|
|
6752
|
+
return {
|
|
6753
|
+
kind: value.kind,
|
|
6754
|
+
...typeof value.text === "string" && value.text !== "" ? { text: value.text } : {},
|
|
6755
|
+
at: value.at,
|
|
6756
|
+
source
|
|
6757
|
+
};
|
|
6758
|
+
}
|
|
6759
|
+
function parseUiadA11yEvents(data) {
|
|
6760
|
+
if (!isRecord(data) || !Array.isArray(data.events) || typeof data.cursor !== "number") {
|
|
6761
|
+
return { success: false, error: "warden-uiad answered a11yEvents with an unexpected shape" };
|
|
6762
|
+
}
|
|
6763
|
+
const events = [];
|
|
6764
|
+
for (const [index, raw] of data.events.entries()) {
|
|
6765
|
+
const event = parseA11yEvent(raw, "uiad");
|
|
6766
|
+
if (!event)
|
|
6767
|
+
return { success: false, error: `warden-uiad sent a malformed a11y event at ${index}` };
|
|
6768
|
+
events.push(event);
|
|
6769
|
+
}
|
|
6770
|
+
return { success: true, data: { events, cursor: data.cursor, ...data.lost === true ? { lost: true } : {} } };
|
|
6771
|
+
}
|
|
6772
|
+
function parseInjectEvents(text, since = 0) {
|
|
6773
|
+
const bytes = Buffer.from(text, "utf8");
|
|
6774
|
+
const start = since > bytes.length || since < 0 ? 0 : since;
|
|
6775
|
+
const end = bytes.lastIndexOf(10) + 1;
|
|
6776
|
+
if (end <= start)
|
|
6777
|
+
return { events: [], cursor: start };
|
|
6778
|
+
const events = [];
|
|
6779
|
+
for (const line of bytes.subarray(start, end).toString("utf8").split(`
|
|
6780
|
+
`)) {
|
|
6781
|
+
if (line.trim() === "")
|
|
6782
|
+
continue;
|
|
6783
|
+
try {
|
|
6784
|
+
const event = parseA11yEvent(JSON.parse(line), "inject");
|
|
6785
|
+
if (event && !(event.text !== undefined && SYSTEM_ANNOUNCEMENT.test(event.text)))
|
|
6786
|
+
events.push(event);
|
|
6787
|
+
} catch {}
|
|
6788
|
+
}
|
|
6789
|
+
return { events, cursor: end };
|
|
6790
|
+
}
|
|
6791
|
+
|
|
6792
|
+
// ../../packages/e2e/src/a11y/announcements.ts
|
|
6793
|
+
var ANNOUNCEMENT_TIMEOUT_MS = 5000;
|
|
6794
|
+
var ANNOUNCEMENT_POLL_MS = 100;
|
|
6795
|
+
var ANNOUNCING = new Set(["announcement", "live-region"]);
|
|
6796
|
+
function matches(event, match) {
|
|
6797
|
+
if (!ANNOUNCING.has(event.kind) || event.text === undefined)
|
|
6798
|
+
return false;
|
|
6799
|
+
if (typeof match === "string")
|
|
6800
|
+
return event.text.includes(match);
|
|
6801
|
+
match.lastIndex = 0;
|
|
6802
|
+
return match.test(event.text);
|
|
6803
|
+
}
|
|
6804
|
+
function withDefaults(opts) {
|
|
6805
|
+
return {
|
|
6806
|
+
now: opts.now ?? Date.now,
|
|
6807
|
+
sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
6808
|
+
timeoutMs: opts.timeoutMs ?? ANNOUNCEMENT_TIMEOUT_MS,
|
|
6809
|
+
pollMs: opts.pollMs ?? ANNOUNCEMENT_POLL_MS
|
|
6810
|
+
};
|
|
6811
|
+
}
|
|
6812
|
+
async function waitForAnnouncement(driver, match, opts = {}) {
|
|
6813
|
+
if (!canReadA11yEvents(driver)) {
|
|
6814
|
+
return {
|
|
6815
|
+
success: false,
|
|
6816
|
+
error: driverError("E_UNSUPPORTED", "this driver cannot read announcements (no a11yEvents capability)")
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
const { now, sleep, timeoutMs, pollMs } = withDefaults(opts);
|
|
6820
|
+
const deadline = now() + timeoutMs;
|
|
6821
|
+
let cursor = opts.since;
|
|
6822
|
+
const seen = [];
|
|
6823
|
+
for (;; ) {
|
|
6824
|
+
const read = await driver.a11yEvents(cursor);
|
|
6825
|
+
if (!read.success)
|
|
6826
|
+
return read;
|
|
6827
|
+
cursor = read.data.cursor;
|
|
6828
|
+
const hit = read.data.events.find((event) => matches(event, match));
|
|
6829
|
+
if (hit)
|
|
6830
|
+
return { success: true, data: { event: hit, cursor: read.data.cursor } };
|
|
6831
|
+
for (const event of read.data.events)
|
|
6832
|
+
if (event.text !== undefined)
|
|
6833
|
+
seen.push(event.text);
|
|
6834
|
+
if (now() >= deadline) {
|
|
6835
|
+
return {
|
|
6836
|
+
success: false,
|
|
6837
|
+
error: driverError("E_TIMEOUT", `no announcement matched ${String(match)} within ${timeoutMs} ms`, {
|
|
6838
|
+
detail: { seen: seen.slice(-10), cursor }
|
|
6839
|
+
})
|
|
6840
|
+
};
|
|
6841
|
+
}
|
|
6842
|
+
await sleep(pollMs);
|
|
6843
|
+
}
|
|
6844
|
+
}
|
|
6845
|
+
|
|
6846
|
+
// ../../packages/e2e/src/driver/a11y-read.ts
|
|
6847
|
+
function canReadA11y(driver) {
|
|
6848
|
+
return typeof driver.readA11y === "function";
|
|
6849
|
+
}
|
|
6850
|
+
|
|
6851
|
+
// ../../packages/e2e/src/driver/native-audit.ts
|
|
6852
|
+
function canAuditNatively(driver) {
|
|
6853
|
+
return typeof driver.auditNatively === "function";
|
|
6854
|
+
}
|
|
6855
|
+
|
|
6856
|
+
// ../../packages/e2e/src/driver/screen-reader.ts
|
|
6857
|
+
function canDriveScreenReader(driver) {
|
|
6858
|
+
return typeof driver.screenReaderNext === "function";
|
|
6859
|
+
}
|
|
6860
|
+
|
|
6861
|
+
// ../../packages/e2e/src/device-a11y.ts
|
|
6862
|
+
var TALKBACK_HINT = "turn TalkBack on with device.a11y({ screenReader: true }) on an Android emulator";
|
|
6863
|
+
function unsupported(action, why) {
|
|
6864
|
+
return toE2eError(driverError("E_UNSUPPORTED", why), action);
|
|
6865
|
+
}
|
|
6866
|
+
async function withA11yRead(driver, snapshot) {
|
|
6867
|
+
if (!canReadA11y(driver))
|
|
6868
|
+
return { success: true, data: snapshot };
|
|
6869
|
+
const read = await driver.readA11y(snapshot);
|
|
6870
|
+
if (read.success)
|
|
6871
|
+
return read;
|
|
6872
|
+
if (read.error.code === "E_UNSUPPORTED")
|
|
6873
|
+
return { success: true, data: snapshot };
|
|
6874
|
+
return read;
|
|
6875
|
+
}
|
|
6876
|
+
var A11Y_EXCERPT_LINES = 40;
|
|
6877
|
+
function renderA11yScreen(snapshot, maxLines = A11Y_EXCERPT_LINES) {
|
|
6878
|
+
const rendered = renderSnapshot(snapshot, { interactiveOnly: true, refs: new Map, a11y: true, maxLines });
|
|
6879
|
+
return [renderHeader({ snapshot, rendered, interactiveOnly: true }), ...rendered.lines].join(`
|
|
6880
|
+
`);
|
|
6881
|
+
}
|
|
6882
|
+
function ignored(issue, ignore) {
|
|
6883
|
+
return ignore.some((g) => g.rule === issue.rule && (g.identifier === undefined || g.identifier === issue.identifier));
|
|
6884
|
+
}
|
|
6885
|
+
async function auditSnapshot(driver, snapshot, options = {}) {
|
|
6886
|
+
const { platform } = driver;
|
|
6887
|
+
const { rules, minSeverity } = options;
|
|
6888
|
+
const ignore = options.ignore ?? [];
|
|
6889
|
+
const own = runAudit(snapshot, platform, {
|
|
6890
|
+
...rules === undefined ? {} : { rules },
|
|
6891
|
+
ignore,
|
|
6892
|
+
...minSeverity === undefined ? {} : { minSeverity },
|
|
6893
|
+
...options.textSize === undefined ? {} : { textSize: options.textSize }
|
|
6894
|
+
});
|
|
6895
|
+
if (options.native === false || rules !== undefined && !rules.includes("native"))
|
|
6896
|
+
return own;
|
|
6897
|
+
const source = platform === "ios" ? "xcuitest" : "atf";
|
|
6898
|
+
if (!canAuditNatively(driver)) {
|
|
6899
|
+
return mergeIssues(own, [], [{ source, reason: "this driver has no native accessibility audit" }]);
|
|
6900
|
+
}
|
|
6901
|
+
const native = await driver.auditNatively(options.bundleId === undefined ? {} : { bundleId: options.bundleId });
|
|
6902
|
+
return mergeIssues(own, native.issues.filter((issue) => !ignored(issue, ignore)), native.skipped === undefined ? [] : [native.skipped]);
|
|
6903
|
+
}
|
|
6904
|
+
function createDeviceA11y(deps) {
|
|
6905
|
+
const { driver, env } = deps;
|
|
6906
|
+
const signal = () => deps.signal();
|
|
6907
|
+
const { platform } = driver;
|
|
6908
|
+
const readSnapshot = async () => {
|
|
6909
|
+
const snapshot = await unwrap(() => driver.snapshot(), "snapshot", signal());
|
|
6910
|
+
const merged = await withA11yRead(driver, snapshot);
|
|
6911
|
+
if (!merged.success)
|
|
6912
|
+
throw toE2eError(merged.error, "read accessibility facts");
|
|
6913
|
+
return merged.data;
|
|
6914
|
+
};
|
|
6915
|
+
const audit = async (options = {}) => {
|
|
6916
|
+
const config = env.a11yAudit ?? {};
|
|
6917
|
+
const rules = options.rules ?? config.rules;
|
|
6918
|
+
const ignore = options.ignore ?? config.ignore ?? [];
|
|
6919
|
+
const minSeverity = options.minSeverity ?? config.minSeverity;
|
|
6920
|
+
return auditSnapshot(driver, await readSnapshot(), {
|
|
6921
|
+
...rules === undefined ? {} : { rules },
|
|
6922
|
+
ignore,
|
|
6923
|
+
...minSeverity === undefined ? {} : { minSeverity },
|
|
6924
|
+
...options.textSize === undefined ? {} : { textSize: options.textSize },
|
|
6925
|
+
...options.native === undefined ? {} : { native: options.native },
|
|
6926
|
+
...env.bundleId === undefined ? {} : { bundleId: env.bundleId }
|
|
6927
|
+
});
|
|
6928
|
+
};
|
|
6929
|
+
const announcements = async (options = {}) => {
|
|
6930
|
+
if (!canReadA11yEvents(driver)) {
|
|
6931
|
+
throw unsupported("device.announcements", "this driver cannot read announcements (no a11yEvents capability)");
|
|
6932
|
+
}
|
|
6933
|
+
const { since } = options;
|
|
6934
|
+
const read = await unwrap(() => driver.a11yEvents(since), "read announcements", signal());
|
|
6935
|
+
const announced = read.events.filter((e) => (e.kind === "announcement" || e.kind === "live-region") && e.text !== undefined);
|
|
6936
|
+
return {
|
|
6937
|
+
announcements: announced.map((e) => ({ kind: e.kind, text: e.text, at: e.at, source: e.source })),
|
|
6938
|
+
cursor: read.cursor,
|
|
6939
|
+
...read.lost === true ? { lost: true } : {}
|
|
6940
|
+
};
|
|
6941
|
+
};
|
|
6942
|
+
const reader = (action) => {
|
|
6943
|
+
if (!canDriveScreenReader(driver))
|
|
6944
|
+
throw unsupported(action, TALKBACK_HINT);
|
|
6945
|
+
return driver;
|
|
6946
|
+
};
|
|
6947
|
+
const spoken = (el) => el === undefined ? undefined : announce(el, platform);
|
|
6948
|
+
const screenReader = {
|
|
6949
|
+
async order() {
|
|
6950
|
+
return readingOrder(await readSnapshot(), platform);
|
|
6951
|
+
},
|
|
6952
|
+
async state() {
|
|
6953
|
+
const d = reader("device.screenReader.state");
|
|
6954
|
+
return unwrap(() => d.screenReaderState(), "screen reader state", signal());
|
|
6955
|
+
},
|
|
6956
|
+
async next() {
|
|
6957
|
+
const d = reader("device.screenReader.next");
|
|
6958
|
+
return spoken(await unwrap(() => d.screenReaderNext(), "screen reader next", signal()));
|
|
6959
|
+
},
|
|
6960
|
+
async prev() {
|
|
6961
|
+
const d = reader("device.screenReader.prev");
|
|
6962
|
+
return spoken(await unwrap(() => d.screenReaderPrev(), "screen reader prev", signal()));
|
|
6963
|
+
},
|
|
6964
|
+
async activate() {
|
|
6965
|
+
const d = reader("device.screenReader.activate");
|
|
6966
|
+
await unwrap(() => d.screenReaderActivate(), "screen reader activate", signal());
|
|
6967
|
+
},
|
|
6968
|
+
async focused() {
|
|
6969
|
+
const d = reader("device.screenReader.focused");
|
|
6970
|
+
return spoken(await unwrap(() => d.screenReaderFocused(), "screen reader focused", signal()));
|
|
6971
|
+
},
|
|
6972
|
+
async spoken(options = {}) {
|
|
6973
|
+
const d = reader("device.screenReader.spoken");
|
|
6974
|
+
const read = await unwrap(() => d.screenReaderSpoken(options.since), "screen reader spoken", signal());
|
|
6975
|
+
return { lines: read.lines, cursor: read.cursor };
|
|
6976
|
+
}
|
|
6977
|
+
};
|
|
6978
|
+
const target = {
|
|
6979
|
+
audit: (options) => audit(options),
|
|
6980
|
+
waitForAnnouncement(match, options) {
|
|
6981
|
+
return waitForAnnouncement(driver, match, {
|
|
6982
|
+
timeoutMs: options.timeoutMs,
|
|
6983
|
+
...options.since === undefined ? {} : { since: options.since }
|
|
6984
|
+
});
|
|
6985
|
+
},
|
|
6986
|
+
async a11yScreen() {
|
|
6987
|
+
const taken = await settled(() => driver.snapshot(), "snapshot", signal());
|
|
6988
|
+
if (!taken.success)
|
|
6989
|
+
return;
|
|
6990
|
+
const merged = await withA11yRead(driver, taken.data);
|
|
6991
|
+
return renderA11yScreen(merged.success ? merged.data : taken.data);
|
|
6992
|
+
},
|
|
6993
|
+
assertionTimeout: () => env.assertionTimeoutMs,
|
|
6994
|
+
step: (label, fn) => deps.step === undefined ? fn() : deps.step(label, fn)
|
|
6995
|
+
};
|
|
6996
|
+
return { a11yAudit: audit, screenReader, announcements, target };
|
|
6997
|
+
}
|
|
6998
|
+
|
|
6999
|
+
// ../../packages/e2e/src/driver/ios/device/capabilities.ts
|
|
7000
|
+
var IOS_DEVICE_CAPABILITIES = {
|
|
7001
|
+
pinch: false,
|
|
7002
|
+
maxPointers: 0,
|
|
7003
|
+
unicodeText: true,
|
|
7004
|
+
nativeSettle: false,
|
|
7005
|
+
alerts: true,
|
|
7006
|
+
reachability: true,
|
|
7007
|
+
cheapHitTest: false
|
|
7008
|
+
};
|
|
7009
|
+
|
|
7010
|
+
// ../../packages/e2e/src/conditions/device-conditions.ts
|
|
7011
|
+
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
7012
|
+
|
|
7013
|
+
// ../../packages/core/src/conditions/camera-media.ts
|
|
7014
|
+
import { createHash } from "crypto";
|
|
7015
|
+
import { closeSync, copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, openSync, readFileSync, readSync, rmSync } from "fs";
|
|
7016
|
+
import { join as join3 } from "path";
|
|
7017
|
+
var SUPPORTED = "png, jpg, heic, mp4, mov";
|
|
7018
|
+
var FTYP_BRANDS = {
|
|
7019
|
+
heic: { kind: "image", ext: "heic" },
|
|
7020
|
+
heix: { kind: "image", ext: "heic" },
|
|
7021
|
+
mif1: { kind: "image", ext: "heic" },
|
|
7022
|
+
"qt ": { kind: "video", ext: "mov" },
|
|
7023
|
+
isom: { kind: "video", ext: "mp4" },
|
|
7024
|
+
iso2: { kind: "video", ext: "mp4" },
|
|
7025
|
+
mp41: { kind: "video", ext: "mp4" },
|
|
7026
|
+
mp42: { kind: "video", ext: "mp4" },
|
|
7027
|
+
avc1: { kind: "video", ext: "mp4" },
|
|
7028
|
+
M4V: { kind: "video", ext: "mp4" },
|
|
7029
|
+
"M4V ": { kind: "video", ext: "mp4" }
|
|
7030
|
+
};
|
|
7031
|
+
function head2(path, n) {
|
|
7032
|
+
const fd = openSync(path, "r");
|
|
7033
|
+
try {
|
|
7034
|
+
const buf = Buffer.alloc(n);
|
|
7035
|
+
const read = readSync(fd, buf, 0, n, 0);
|
|
7036
|
+
return buf.subarray(0, read);
|
|
7037
|
+
} finally {
|
|
7038
|
+
closeSync(fd);
|
|
7039
|
+
}
|
|
7040
|
+
}
|
|
7041
|
+
function sniffCameraMedia(path) {
|
|
7042
|
+
if (!existsSync3(path)) {
|
|
7043
|
+
return { success: false, error: { code: "E_CAMERA_NOT_FOUND", message: `camera media not found: ${path}` } };
|
|
7044
|
+
}
|
|
7045
|
+
const b = head2(path, 12);
|
|
7046
|
+
if (b.length >= 8 && b.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
7047
|
+
return ok({ kind: "image", ext: "png" });
|
|
7048
|
+
}
|
|
7049
|
+
if (b.length >= 3 && b[0] === 255 && b[1] === 216 && b[2] === 255)
|
|
7050
|
+
return ok({ kind: "image", ext: "jpg" });
|
|
7051
|
+
if (b.length >= 12 && b.subarray(4, 8).toString("latin1") === "ftyp") {
|
|
7052
|
+
const brand = FTYP_BRANDS[b.subarray(8, 12).toString("latin1")];
|
|
7053
|
+
if (brand)
|
|
7054
|
+
return ok(brand);
|
|
7055
|
+
}
|
|
7056
|
+
return {
|
|
7057
|
+
success: false,
|
|
7058
|
+
error: { code: "E_CAMERA_MEDIA", message: `unsupported camera media ${path}: use ${SUPPORTED}` }
|
|
7059
|
+
};
|
|
7060
|
+
}
|
|
7061
|
+
function cameraFeedDir(wardenHome, udid) {
|
|
7062
|
+
return join3(wardenHome, "camera", udid);
|
|
7063
|
+
}
|
|
7064
|
+
function stageCameraFeed(input) {
|
|
7065
|
+
const sniffed = sniffCameraMedia(input.source);
|
|
7066
|
+
if (!sniffed.success)
|
|
7067
|
+
return sniffed;
|
|
7068
|
+
const hash = createHash("sha256").update(readFileSync(input.source)).digest("hex").slice(0, 12);
|
|
7069
|
+
const dir = cameraFeedDir(input.wardenHome, input.udid);
|
|
7070
|
+
mkdirSync3(dir, { recursive: true });
|
|
7071
|
+
const path = join3(dir, `${input.facing}-${hash}.${sniffed.data.ext}`);
|
|
7072
|
+
if (!existsSync3(path))
|
|
7073
|
+
copyFileSync(input.source, path);
|
|
7074
|
+
return ok(sniffed.data.kind === "image" ? { kind: "image", path } : { kind: "video", path, loop: input.loop });
|
|
7075
|
+
}
|
|
7076
|
+
function clearCameraFeeds(wardenHome, udid) {
|
|
7077
|
+
rmSync(cameraFeedDir(wardenHome, udid), { recursive: true, force: true });
|
|
7078
|
+
}
|
|
7079
|
+
|
|
7080
|
+
// ../../packages/core/src/store.ts
|
|
7081
|
+
import { Database } from "bun:sqlite";
|
|
7082
|
+
import { randomUUID } from "crypto";
|
|
7083
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
7084
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
7085
|
+
|
|
7086
|
+
// ../../packages/core/src/lease-events.ts
|
|
7087
|
+
var LEASE_EVENT_TYPES = [
|
|
7088
|
+
"requested",
|
|
7089
|
+
"queued",
|
|
7090
|
+
"granted",
|
|
7091
|
+
"heartbeat_lost",
|
|
7092
|
+
"released",
|
|
7093
|
+
"expired",
|
|
7094
|
+
"evicted",
|
|
7095
|
+
"quarantined",
|
|
7096
|
+
"unquarantined",
|
|
7097
|
+
"shutdown",
|
|
7098
|
+
"device_unhealthy",
|
|
7099
|
+
"device_recovered"
|
|
7100
|
+
];
|
|
7101
|
+
function insertLeaseEvent(db, event) {
|
|
7102
|
+
db.query(`INSERT INTO lease_events (at, type, lease_id, resource_key, owner_key, label, claim_id, data)
|
|
7103
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(event.at, event.type, event.leaseId ?? null, event.resourceKey ?? null, event.ownerKey ?? null, event.label ?? null, event.claimId ?? null, JSON.stringify(event.data));
|
|
7104
|
+
}
|
|
7105
|
+
function isEventType(value) {
|
|
7106
|
+
return LEASE_EVENT_TYPES.includes(value);
|
|
7107
|
+
}
|
|
7108
|
+
function toEvent(row) {
|
|
7109
|
+
if (!isEventType(row.type))
|
|
7110
|
+
return;
|
|
7111
|
+
const base = {
|
|
7112
|
+
id: row.id,
|
|
7113
|
+
at: row.at,
|
|
7114
|
+
type: row.type,
|
|
7115
|
+
data: JSON.parse(row.data),
|
|
7116
|
+
...row.lease_id !== null ? { leaseId: row.lease_id } : {},
|
|
7117
|
+
...row.resource_key !== null ? { resourceKey: row.resource_key } : {},
|
|
7118
|
+
...row.owner_key !== null ? { ownerKey: row.owner_key } : {},
|
|
7119
|
+
...row.label !== null ? { label: row.label } : {},
|
|
7120
|
+
...row.claim_id !== null ? { claimId: row.claim_id } : {}
|
|
7121
|
+
};
|
|
7122
|
+
return base;
|
|
7123
|
+
}
|
|
7124
|
+
function queryLeaseEvents(db, filter = {}) {
|
|
7125
|
+
const where = [];
|
|
7126
|
+
const params = [];
|
|
7127
|
+
if (filter.since !== undefined) {
|
|
7128
|
+
where.push("at >= ?");
|
|
7129
|
+
params.push(filter.since);
|
|
7130
|
+
}
|
|
7131
|
+
if (filter.leaseId !== undefined) {
|
|
7132
|
+
where.push("lease_id = ?");
|
|
7133
|
+
params.push(filter.leaseId);
|
|
7134
|
+
}
|
|
7135
|
+
if (filter.owner !== undefined) {
|
|
7136
|
+
where.push("instr(owner_key, ?) > 0");
|
|
7137
|
+
params.push(filter.owner);
|
|
7138
|
+
}
|
|
7139
|
+
if (filter.afterId !== undefined) {
|
|
7140
|
+
where.push("id > ?");
|
|
7141
|
+
params.push(filter.afterId);
|
|
7142
|
+
}
|
|
7143
|
+
const sql = `SELECT * FROM lease_events${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY id`;
|
|
7144
|
+
return db.query(sql).all(...params).flatMap((row) => toEvent(row) ?? []);
|
|
7145
|
+
}
|
|
7146
|
+
function pruneLeaseEvents(db, before) {
|
|
7147
|
+
return db.query("DELETE FROM lease_events WHERE at < ?").run(before).changes;
|
|
7148
|
+
}
|
|
7149
|
+
var INTERNAL_LEASE_LABELS = ["capacity-evict", "gc-shutdown", "reclaiming"];
|
|
7150
|
+
|
|
7151
|
+
// ../../packages/core/src/liveness.ts
|
|
7152
|
+
var LOST_HEARTBEATS = 3;
|
|
7153
|
+
function isLeaseAlive(lease, now, pidAlive) {
|
|
7154
|
+
const supervised = lease.pid !== undefined && lease.heartbeatMs !== undefined;
|
|
7155
|
+
const fresh = supervised ? Math.min(lease.ttlMs, LOST_HEARTBEATS * (lease.heartbeatMs ?? 0)) : lease.ttlMs;
|
|
7156
|
+
if (now - lease.heartbeatAt <= fresh)
|
|
7157
|
+
return true;
|
|
7158
|
+
return lease.pid !== undefined && pidAlive(lease.pid);
|
|
7159
|
+
}
|
|
7160
|
+
function processAlive(pid) {
|
|
7161
|
+
try {
|
|
7162
|
+
process.kill(pid, 0);
|
|
7163
|
+
return true;
|
|
7164
|
+
} catch (error) {
|
|
7165
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
7168
|
+
|
|
7169
|
+
// ../../packages/core/src/queue.ts
|
|
7170
|
+
var AGING_MS = 10 * 60000;
|
|
7171
|
+
var DEVICE_WAIT_REASONS = ["device-limit", "requester-limit", "waiting-for-device"];
|
|
7172
|
+
var WAIT_REASONS = new Set([
|
|
7173
|
+
"behind-earlier",
|
|
7174
|
+
"ram-budget",
|
|
7175
|
+
"cpu-budget",
|
|
7176
|
+
"memory-pressure",
|
|
7177
|
+
"host-load",
|
|
7178
|
+
...DEVICE_WAIT_REASONS
|
|
7179
|
+
]);
|
|
7180
|
+
var isWaitReason = (s) => WAIT_REASONS.has(s);
|
|
7181
|
+
|
|
7182
|
+
// ../../packages/core/src/types.ts
|
|
7183
|
+
function resourceKey(resource) {
|
|
7184
|
+
switch (resource.kind) {
|
|
7185
|
+
case "device":
|
|
7186
|
+
return `device:${resource.platform}:${resource.id}`;
|
|
7187
|
+
case "port":
|
|
7188
|
+
return `port:${resource.port}`;
|
|
7189
|
+
case "build":
|
|
7190
|
+
return `build:${resource.key}`;
|
|
7191
|
+
}
|
|
7192
|
+
}
|
|
7193
|
+
function describeOwner(owner) {
|
|
7194
|
+
switch (owner.kind) {
|
|
7195
|
+
case "agent":
|
|
7196
|
+
return `agent ${owner.sessionId}`;
|
|
7197
|
+
case "user":
|
|
7198
|
+
return `user pid ${owner.pid}`;
|
|
7199
|
+
case "ci":
|
|
7200
|
+
return `ci ${owner.runId}`;
|
|
7201
|
+
}
|
|
7202
|
+
}
|
|
7203
|
+
var PHYSICAL_IOS_ID = /^(?:[0-9A-F]{8}-[0-9A-F]{16}|[0-9a-f]{40})$/i;
|
|
7204
|
+
function isPhysicalIosId(id) {
|
|
7205
|
+
return PHYSICAL_IOS_ID.test(id);
|
|
7206
|
+
}
|
|
7207
|
+
|
|
7208
|
+
// ../../packages/core/src/store.ts
|
|
7209
|
+
function wardenHome(env = process.env) {
|
|
7210
|
+
if (env.WARDEN_HOME)
|
|
7211
|
+
return env.WARDEN_HOME;
|
|
7212
|
+
return join4(env.HOME ?? "~", ".warden");
|
|
7213
|
+
}
|
|
7214
|
+
function defaultDbPath(env = process.env) {
|
|
7215
|
+
return join4(wardenHome(env), "warden.db");
|
|
7216
|
+
}
|
|
7217
|
+
var MIGRATIONS = [
|
|
7218
|
+
`CREATE TABLE leases (
|
|
7219
|
+
id TEXT PRIMARY KEY,
|
|
7220
|
+
resource_key TEXT NOT NULL UNIQUE,
|
|
7221
|
+
resource TEXT NOT NULL,
|
|
7222
|
+
owner_key TEXT NOT NULL,
|
|
7223
|
+
owner TEXT NOT NULL,
|
|
7224
|
+
label TEXT,
|
|
7225
|
+
acquired_at INTEGER NOT NULL,
|
|
7226
|
+
heartbeat_at INTEGER NOT NULL,
|
|
7227
|
+
ttl_ms INTEGER NOT NULL,
|
|
7228
|
+
pid INTEGER
|
|
7229
|
+
);
|
|
7230
|
+
CREATE INDEX leases_owner ON leases(owner_key);
|
|
7231
|
+
CREATE TABLE devices (
|
|
7232
|
+
platform TEXT NOT NULL,
|
|
7233
|
+
id TEXT NOT NULL,
|
|
7234
|
+
name TEXT NOT NULL,
|
|
7235
|
+
profile TEXT,
|
|
7236
|
+
runtime TEXT,
|
|
7237
|
+
created_at INTEGER NOT NULL,
|
|
7238
|
+
last_used_at INTEGER NOT NULL,
|
|
7239
|
+
PRIMARY KEY (platform, id)
|
|
7240
|
+
);`,
|
|
7241
|
+
`CREATE TABLE builds (
|
|
7242
|
+
project_key TEXT NOT NULL,
|
|
7243
|
+
platform TEXT NOT NULL,
|
|
7244
|
+
profile TEXT NOT NULL,
|
|
7245
|
+
hash TEXT NOT NULL,
|
|
7246
|
+
path TEXT NOT NULL,
|
|
7247
|
+
source TEXT NOT NULL,
|
|
7248
|
+
size INTEGER NOT NULL,
|
|
7249
|
+
created_at INTEGER NOT NULL,
|
|
7250
|
+
last_used_at INTEGER NOT NULL,
|
|
7251
|
+
PRIMARY KEY (project_key, platform, hash)
|
|
7252
|
+
);
|
|
7253
|
+
CREATE TABLE installs (
|
|
7254
|
+
platform TEXT NOT NULL,
|
|
7255
|
+
device_id TEXT NOT NULL,
|
|
7256
|
+
bundle_id TEXT NOT NULL,
|
|
7257
|
+
hash TEXT NOT NULL,
|
|
7258
|
+
installed_at INTEGER NOT NULL,
|
|
7259
|
+
PRIMARY KEY (platform, device_id, bundle_id)
|
|
7260
|
+
);`,
|
|
7261
|
+
`ALTER TABLE leases ADD COLUMN booted_by_owner INTEGER;
|
|
7262
|
+
CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);`,
|
|
7263
|
+
`ALTER TABLE devices ADD COLUMN quarantined_at INTEGER;
|
|
7264
|
+
ALTER TABLE devices ADD COLUMN quarantine_reason TEXT;
|
|
7265
|
+
ALTER TABLE devices ADD COLUMN slimmed_at INTEGER;`,
|
|
7266
|
+
`ALTER TABLE leases ADD COLUMN heartbeat_ms INTEGER;`,
|
|
7267
|
+
`CREATE TABLE fingerprints (
|
|
7268
|
+
root TEXT NOT NULL,
|
|
7269
|
+
platform TEXT NOT NULL,
|
|
7270
|
+
variant TEXT NOT NULL,
|
|
7271
|
+
command TEXT NOT NULL,
|
|
7272
|
+
signature TEXT NOT NULL,
|
|
7273
|
+
native TEXT NOT NULL,
|
|
7274
|
+
computed_at INTEGER NOT NULL,
|
|
7275
|
+
PRIMARY KEY (root, platform, variant, command)
|
|
7276
|
+
);`,
|
|
7277
|
+
`ALTER TABLE installs ADD COLUMN container TEXT;
|
|
7278
|
+
ALTER TABLE installs ADD COLUMN exe_stamp TEXT;`,
|
|
7279
|
+
`CREATE TABLE boot_queue (
|
|
7280
|
+
id TEXT PRIMARY KEY,
|
|
7281
|
+
platform TEXT NOT NULL,
|
|
7282
|
+
profile TEXT NOT NULL,
|
|
7283
|
+
units REAL NOT NULL,
|
|
7284
|
+
priority INTEGER NOT NULL,
|
|
7285
|
+
enqueued_at INTEGER NOT NULL,
|
|
7286
|
+
heartbeat_at INTEGER NOT NULL,
|
|
7287
|
+
owner_key TEXT NOT NULL,
|
|
7288
|
+
owner TEXT NOT NULL,
|
|
7289
|
+
label TEXT,
|
|
7290
|
+
pid INTEGER
|
|
7291
|
+
);
|
|
7292
|
+
CREATE INDEX boot_queue_order ON boot_queue(priority, enqueued_at);`,
|
|
7293
|
+
`ALTER TABLE leases ADD COLUMN shared INTEGER;`,
|
|
7294
|
+
`ALTER TABLE builds ADD COLUMN kind TEXT NOT NULL DEFAULT 'legacy';
|
|
7295
|
+
ALTER TABLE builds ADD COLUMN base_hash TEXT;
|
|
7296
|
+
ALTER TABLE builds ADD COLUMN js_key TEXT;
|
|
7297
|
+
ALTER TABLE builds ADD COLUMN worktree TEXT;
|
|
7298
|
+
ALTER TABLE builds ADD COLUMN branch TEXT;`,
|
|
7299
|
+
`CREATE TABLE lease_conditions (
|
|
7300
|
+
lease_id TEXT NOT NULL REFERENCES leases(id) ON DELETE CASCADE,
|
|
7301
|
+
area TEXT NOT NULL,
|
|
7302
|
+
json TEXT NOT NULL,
|
|
7303
|
+
owned TEXT NOT NULL DEFAULT '[]',
|
|
7304
|
+
set_at INTEGER NOT NULL,
|
|
7305
|
+
PRIMARY KEY (lease_id, area)
|
|
7306
|
+
);`,
|
|
7307
|
+
`CREATE TABLE orphan_conditions (
|
|
7308
|
+
platform TEXT NOT NULL,
|
|
7309
|
+
device_id TEXT NOT NULL,
|
|
7310
|
+
area TEXT NOT NULL,
|
|
7311
|
+
lease_id TEXT NOT NULL,
|
|
7312
|
+
json TEXT NOT NULL,
|
|
7313
|
+
owned TEXT NOT NULL DEFAULT '[]',
|
|
7314
|
+
set_at INTEGER NOT NULL,
|
|
7315
|
+
orphaned_at INTEGER NOT NULL,
|
|
7316
|
+
PRIMARY KEY (platform, device_id, area)
|
|
7317
|
+
);
|
|
7318
|
+
CREATE TRIGGER leases_orphan_conditions BEFORE DELETE ON leases
|
|
7319
|
+
WHEN json_extract(OLD.resource, '$.kind') = 'device' AND json_extract(OLD.resource, '$.id') NOT LIKE 'pending:%'
|
|
7320
|
+
BEGIN
|
|
7321
|
+
INSERT OR REPLACE INTO orphan_conditions (platform, device_id, area, lease_id, json, owned, set_at, orphaned_at)
|
|
7322
|
+
SELECT json_extract(OLD.resource, '$.platform'), json_extract(OLD.resource, '$.id'), c.area, OLD.id, c.json, c.owned, c.set_at,
|
|
7323
|
+
CAST(strftime('%s', 'now') AS INTEGER) * 1000
|
|
7324
|
+
FROM lease_conditions c WHERE c.lease_id = OLD.id;
|
|
7325
|
+
END;`,
|
|
7326
|
+
`ALTER TABLE devices ADD COLUMN persistent_at INTEGER;`,
|
|
7327
|
+
`ALTER TABLE boot_queue ADD COLUMN reason TEXT;`,
|
|
7328
|
+
`ALTER TABLE leases ADD COLUMN claim_id TEXT;
|
|
7329
|
+
ALTER TABLE boot_queue ADD COLUMN claim_id TEXT;`,
|
|
7330
|
+
`CREATE TABLE lease_events (
|
|
7331
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
7332
|
+
at INTEGER NOT NULL,
|
|
7333
|
+
type TEXT NOT NULL,
|
|
7334
|
+
lease_id TEXT,
|
|
7335
|
+
resource_key TEXT,
|
|
7336
|
+
owner_key TEXT,
|
|
7337
|
+
label TEXT,
|
|
7338
|
+
claim_id TEXT,
|
|
7339
|
+
data TEXT NOT NULL DEFAULT '{}'
|
|
7340
|
+
);
|
|
7341
|
+
CREATE INDEX lease_events_at ON lease_events(at);
|
|
7342
|
+
CREATE INDEX lease_events_lease ON lease_events(lease_id);
|
|
7343
|
+
CREATE INDEX lease_events_owner ON lease_events(owner_key, at);
|
|
7344
|
+
CREATE INDEX lease_events_claim ON lease_events(claim_id);`,
|
|
7345
|
+
`CREATE TABLE claim_keys (
|
|
7346
|
+
owner_key TEXT NOT NULL,
|
|
7347
|
+
key TEXT NOT NULL,
|
|
7348
|
+
claim_id TEXT NOT NULL,
|
|
7349
|
+
platform TEXT NOT NULL,
|
|
7350
|
+
profile TEXT NOT NULL,
|
|
7351
|
+
count INTEGER NOT NULL,
|
|
7352
|
+
ready INTEGER NOT NULL DEFAULT 0,
|
|
7353
|
+
created_at INTEGER NOT NULL,
|
|
7354
|
+
PRIMARY KEY (owner_key, key)
|
|
7355
|
+
);
|
|
7356
|
+
CREATE INDEX claim_keys_claim ON claim_keys(claim_id);`
|
|
7357
|
+
];
|
|
7358
|
+
var QUEUE_TTL_MS = 60000;
|
|
7359
|
+
function ownerKey(owner) {
|
|
7360
|
+
switch (owner.kind) {
|
|
7361
|
+
case "agent":
|
|
7362
|
+
return `agent:${owner.sessionId}`;
|
|
7363
|
+
case "user":
|
|
7364
|
+
return `user:${owner.pid}`;
|
|
7365
|
+
case "ci":
|
|
7366
|
+
return `ci:${owner.runId}`;
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
function toQueueEntry(row) {
|
|
7370
|
+
const entry = {
|
|
7371
|
+
id: row.id,
|
|
7372
|
+
platform: row.platform,
|
|
7373
|
+
profile: row.profile,
|
|
7374
|
+
units: row.units,
|
|
7375
|
+
priority: row.priority,
|
|
7376
|
+
enqueuedAt: row.enqueued_at,
|
|
7377
|
+
heartbeatAt: row.heartbeat_at,
|
|
7378
|
+
owner: JSON.parse(row.owner)
|
|
7379
|
+
};
|
|
7380
|
+
if (row.label !== null)
|
|
7381
|
+
entry.label = row.label;
|
|
7382
|
+
if (row.pid !== null)
|
|
7383
|
+
entry.pid = row.pid;
|
|
7384
|
+
if (row.reason !== null && isWaitReason(row.reason))
|
|
7385
|
+
entry.reason = row.reason;
|
|
7386
|
+
if (row.claim_id !== null)
|
|
7387
|
+
entry.claimId = row.claim_id;
|
|
7388
|
+
return entry;
|
|
7389
|
+
}
|
|
7390
|
+
function toLease(row) {
|
|
7391
|
+
const lease = {
|
|
7392
|
+
id: row.id,
|
|
7393
|
+
resource: JSON.parse(row.resource),
|
|
7394
|
+
owner: JSON.parse(row.owner),
|
|
7395
|
+
acquiredAt: row.acquired_at,
|
|
7396
|
+
heartbeatAt: row.heartbeat_at,
|
|
7397
|
+
ttlMs: row.ttl_ms
|
|
7398
|
+
};
|
|
7399
|
+
if (row.label !== null)
|
|
7400
|
+
lease.label = row.label;
|
|
7401
|
+
if (row.pid !== null)
|
|
7402
|
+
lease.pid = row.pid;
|
|
7403
|
+
if (row.booted_by_owner)
|
|
7404
|
+
lease.bootedByOwner = true;
|
|
7405
|
+
if (row.heartbeat_ms !== null)
|
|
7406
|
+
lease.heartbeatMs = row.heartbeat_ms;
|
|
7407
|
+
if (row.shared)
|
|
7408
|
+
lease.shared = true;
|
|
7409
|
+
if (row.claim_id !== null)
|
|
7410
|
+
lease.claimId = row.claim_id;
|
|
7411
|
+
return lease;
|
|
7412
|
+
}
|
|
7413
|
+
function toDevice(row) {
|
|
7414
|
+
const device = {
|
|
7415
|
+
platform: row.platform,
|
|
7416
|
+
id: row.id,
|
|
7417
|
+
name: row.name,
|
|
7418
|
+
createdAt: row.created_at,
|
|
7419
|
+
lastUsedAt: row.last_used_at
|
|
7420
|
+
};
|
|
7421
|
+
if (row.profile !== null)
|
|
7422
|
+
device.profile = row.profile;
|
|
7423
|
+
if (row.runtime !== null)
|
|
7424
|
+
device.runtime = row.runtime;
|
|
7425
|
+
if (row.quarantined_at !== null)
|
|
7426
|
+
device.quarantinedAt = row.quarantined_at;
|
|
7427
|
+
if (row.quarantine_reason !== null)
|
|
7428
|
+
device.quarantineReason = row.quarantine_reason;
|
|
7429
|
+
if (row.slimmed_at !== null)
|
|
7430
|
+
device.slimmedAt = row.slimmed_at;
|
|
7431
|
+
if (row.persistent_at !== null)
|
|
7432
|
+
device.persistentAt = row.persistent_at;
|
|
7433
|
+
return device;
|
|
7434
|
+
}
|
|
7435
|
+
function newLeaseId() {
|
|
7436
|
+
return `l_${randomUUID().replaceAll("-", "").slice(0, 12)}`;
|
|
7437
|
+
}
|
|
7438
|
+
|
|
7439
|
+
class Store {
|
|
7440
|
+
db;
|
|
7441
|
+
constructor(db) {
|
|
7442
|
+
this.db = db;
|
|
7443
|
+
}
|
|
7444
|
+
close() {
|
|
7445
|
+
this.db.close();
|
|
7446
|
+
}
|
|
7447
|
+
transaction(fn) {
|
|
7448
|
+
if (this.db.inTransaction)
|
|
7449
|
+
return fn();
|
|
7450
|
+
return this.db.transaction(fn).immediate();
|
|
7451
|
+
}
|
|
7452
|
+
insertLease(input, now) {
|
|
7453
|
+
const lease = {
|
|
7454
|
+
id: newLeaseId(),
|
|
7455
|
+
resource: input.resource,
|
|
7456
|
+
owner: input.owner,
|
|
7457
|
+
acquiredAt: now,
|
|
7458
|
+
heartbeatAt: now,
|
|
7459
|
+
ttlMs: input.ttlMs
|
|
7460
|
+
};
|
|
7461
|
+
if (input.label !== undefined)
|
|
7462
|
+
lease.label = input.label;
|
|
7463
|
+
if (input.pid !== undefined)
|
|
7464
|
+
lease.pid = input.pid;
|
|
7465
|
+
if (input.bootedByOwner)
|
|
7466
|
+
lease.bootedByOwner = true;
|
|
7467
|
+
if (input.shared)
|
|
7468
|
+
lease.shared = true;
|
|
7469
|
+
if (input.claimId !== undefined)
|
|
7470
|
+
lease.claimId = input.claimId;
|
|
7471
|
+
this.db.query(`INSERT INTO leases (id, resource_key, resource, owner_key, owner, label, acquired_at, heartbeat_at, ttl_ms, pid, booted_by_owner, shared, claim_id)
|
|
7472
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(lease.id, resourceKey(lease.resource), JSON.stringify(lease.resource), ownerKey(lease.owner), JSON.stringify(lease.owner), lease.label ?? null, now, now, lease.ttlMs, lease.pid ?? null, lease.bootedByOwner ? 1 : null, lease.shared ? 1 : null, lease.claimId ?? null);
|
|
7473
|
+
return lease;
|
|
7474
|
+
}
|
|
7475
|
+
markShared(ids) {
|
|
7476
|
+
if (ids.length === 0)
|
|
7477
|
+
return;
|
|
7478
|
+
const stmt = this.db.query("UPDATE leases SET shared = 1 WHERE id = ?");
|
|
7479
|
+
this.transaction(() => {
|
|
7480
|
+
for (const id of ids)
|
|
7481
|
+
stmt.run(id);
|
|
7482
|
+
});
|
|
7483
|
+
}
|
|
7484
|
+
markBootedByOwner(id) {
|
|
7485
|
+
this.db.query("UPDATE leases SET booted_by_owner = 1 WHERE id = ?").run(id);
|
|
7486
|
+
}
|
|
7487
|
+
setLeasePid(id, pid) {
|
|
7488
|
+
this.db.query("UPDATE leases SET pid = ? WHERE id = ?").run(pid, id);
|
|
7489
|
+
}
|
|
7490
|
+
getMeta(key) {
|
|
7491
|
+
return this.db.query("SELECT value FROM meta WHERE key = ?").get(key)?.value;
|
|
7492
|
+
}
|
|
7493
|
+
setMeta(key, value) {
|
|
7494
|
+
this.db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value").run(key, value);
|
|
7495
|
+
}
|
|
7496
|
+
deleteMeta(key) {
|
|
7497
|
+
this.db.query("DELETE FROM meta WHERE key = ?").run(key);
|
|
7498
|
+
}
|
|
7499
|
+
getLease(id) {
|
|
7500
|
+
const row = this.db.query("SELECT * FROM leases WHERE id = ?").get(id);
|
|
7501
|
+
return row ? toLease(row) : undefined;
|
|
7502
|
+
}
|
|
7503
|
+
listLeases() {
|
|
7504
|
+
return this.db.query("SELECT * FROM leases ORDER BY acquired_at, id").all().map(toLease);
|
|
7505
|
+
}
|
|
7506
|
+
listLeasesByOwner(owner) {
|
|
7507
|
+
return this.db.query("SELECT * FROM leases WHERE owner_key = ? ORDER BY acquired_at, id").all(ownerKey(owner)).map(toLease);
|
|
7508
|
+
}
|
|
7509
|
+
findLeaseByResource(resource) {
|
|
7510
|
+
const row = this.db.query("SELECT * FROM leases WHERE resource_key = ?").get(resourceKey(resource));
|
|
7511
|
+
return row ? toLease(row) : undefined;
|
|
7512
|
+
}
|
|
7513
|
+
deleteLeases(ids, end) {
|
|
7514
|
+
if (ids.length === 0)
|
|
7515
|
+
return 0;
|
|
7516
|
+
const stmt = this.db.query("DELETE FROM leases WHERE id = ?");
|
|
7517
|
+
return this.transaction(() => ids.reduce((n, id) => {
|
|
7518
|
+
const lease = end ? this.getLease(id) : undefined;
|
|
7519
|
+
const changes = stmt.run(id).changes;
|
|
7520
|
+
if (changes > 0 && lease && end)
|
|
7521
|
+
this.recordLeaseEnd(lease, end);
|
|
7522
|
+
return n + changes;
|
|
7523
|
+
}, 0));
|
|
7524
|
+
}
|
|
7525
|
+
recordLeaseEnd(lease, end) {
|
|
7526
|
+
if (lease.resource.kind !== "device" || lease.resource.id.startsWith("pending:"))
|
|
7527
|
+
return;
|
|
7528
|
+
if (lease.label !== undefined && INTERNAL_LEASE_LABELS.includes(lease.label))
|
|
7529
|
+
return;
|
|
7530
|
+
const granted = this.db.query("SELECT at FROM lease_events WHERE lease_id = ? AND type = 'granted' LIMIT 1").get(lease.id);
|
|
7531
|
+
if (!granted)
|
|
7532
|
+
return;
|
|
7533
|
+
this.recordEvent({
|
|
7534
|
+
at: end.now,
|
|
7535
|
+
type: end.type,
|
|
7536
|
+
data: { heldMs: Math.max(0, end.now - granted.at) },
|
|
7537
|
+
leaseId: lease.id,
|
|
7538
|
+
resourceKey: resourceKey(lease.resource),
|
|
7539
|
+
ownerKey: ownerKey(lease.owner),
|
|
7540
|
+
...lease.label !== undefined ? { label: lease.label } : {},
|
|
7541
|
+
...lease.claimId !== undefined ? { claimId: lease.claimId } : {}
|
|
7542
|
+
});
|
|
7543
|
+
}
|
|
7544
|
+
recordEvent(event) {
|
|
7545
|
+
insertLeaseEvent(this.db, event);
|
|
7546
|
+
}
|
|
7547
|
+
recordQueuedOnce(event) {
|
|
7548
|
+
return this.transaction(() => {
|
|
7549
|
+
const seen = this.db.query("SELECT 1 AS n FROM lease_events WHERE type = 'queued' AND claim_id IS ?1 AND json_extract(data, '$.queueId') = ?2 LIMIT 1").get(event.claimId ?? null, event.data.queueId);
|
|
7550
|
+
if (seen)
|
|
7551
|
+
return false;
|
|
7552
|
+
this.recordEvent(event);
|
|
7553
|
+
return true;
|
|
7554
|
+
});
|
|
7555
|
+
}
|
|
7556
|
+
recordHeartbeatLost(ids, now) {
|
|
7557
|
+
return this.transaction(() => {
|
|
7558
|
+
let logged = 0;
|
|
7559
|
+
for (const id of ids) {
|
|
7560
|
+
const event = this.getLease(id) ? undefined : this.heartbeatLostEvent(id, now);
|
|
7561
|
+
if (!event)
|
|
7562
|
+
continue;
|
|
7563
|
+
this.recordEvent(event);
|
|
7564
|
+
logged++;
|
|
7565
|
+
}
|
|
7566
|
+
return logged;
|
|
7567
|
+
});
|
|
7568
|
+
}
|
|
7569
|
+
heartbeatLostEvent(leaseId, at) {
|
|
7570
|
+
const granted = this.db.query("SELECT resource_key, owner_key, label, claim_id FROM lease_events WHERE lease_id = ? AND type = 'granted' LIMIT 1").get(leaseId);
|
|
7571
|
+
if (!granted)
|
|
7572
|
+
return;
|
|
7573
|
+
return {
|
|
7574
|
+
at,
|
|
7575
|
+
type: "heartbeat_lost",
|
|
7576
|
+
data: {},
|
|
7577
|
+
leaseId,
|
|
7578
|
+
...granted.resource_key ? { resourceKey: granted.resource_key } : {},
|
|
7579
|
+
...granted.owner_key ? { ownerKey: granted.owner_key } : {},
|
|
7580
|
+
...granted.label ? { label: granted.label } : {},
|
|
7581
|
+
...granted.claim_id ? { claimId: granted.claim_id } : {}
|
|
7582
|
+
};
|
|
7583
|
+
}
|
|
7584
|
+
listEvents(filter = {}) {
|
|
7585
|
+
return queryLeaseEvents(this.db, filter);
|
|
7586
|
+
}
|
|
7587
|
+
pruneEvents(before) {
|
|
7588
|
+
return pruneLeaseEvents(this.db, before);
|
|
7589
|
+
}
|
|
7590
|
+
heartbeat(ids, now, periodMs) {
|
|
7591
|
+
if (ids.length === 0)
|
|
7592
|
+
return 0;
|
|
7593
|
+
const stmt = this.db.query("UPDATE leases SET heartbeat_at = ?, heartbeat_ms = COALESCE(?, heartbeat_ms) WHERE id = ?");
|
|
7594
|
+
return this.transaction(() => ids.reduce((n, id) => n + stmt.run(now, periodMs ?? null, id).changes, 0));
|
|
7595
|
+
}
|
|
7596
|
+
reclaimStale(now, pidAlive) {
|
|
7597
|
+
return this.transaction(() => {
|
|
7598
|
+
const stale = this.listLeases().filter((l) => !isLeaseAlive(l, now, pidAlive)).map((l) => l.id);
|
|
7599
|
+
this.deleteLeases(stale, { type: "expired", now });
|
|
7600
|
+
return stale;
|
|
7601
|
+
});
|
|
7602
|
+
}
|
|
7603
|
+
enqueue(entry) {
|
|
7604
|
+
return this.transaction(() => {
|
|
7605
|
+
this.db.query(`INSERT INTO boot_queue (id, platform, profile, units, priority, enqueued_at, heartbeat_at, owner_key, owner, label, pid, reason, claim_id)
|
|
7606
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7607
|
+
ON CONFLICT (id) DO UPDATE SET platform = excluded.platform, profile = excluded.profile, units = excluded.units,
|
|
7608
|
+
heartbeat_at = excluded.heartbeat_at, label = excluded.label, pid = excluded.pid, reason = excluded.reason, claim_id = excluded.claim_id`).run(entry.id, entry.platform, entry.profile, entry.units, entry.priority, entry.enqueuedAt, entry.heartbeatAt, ownerKey(entry.owner), JSON.stringify(entry.owner), entry.label ?? null, entry.pid ?? null, entry.reason ?? null, entry.claimId ?? null);
|
|
7609
|
+
const row = this.db.query("SELECT * FROM boot_queue WHERE id = ?").get(entry.id);
|
|
7610
|
+
if (!row)
|
|
7611
|
+
throw new Error(`boot_queue row ${entry.id} vanished`);
|
|
7612
|
+
return toQueueEntry(row);
|
|
7613
|
+
});
|
|
7614
|
+
}
|
|
7615
|
+
refreshQueue(id, now, reason) {
|
|
7616
|
+
return this.db.query("UPDATE boot_queue SET heartbeat_at = ?, reason = COALESCE(?, reason) WHERE id = ?").run(now, reason ?? null, id).changes > 0;
|
|
7617
|
+
}
|
|
7618
|
+
getClaimKey(ownerKey, key) {
|
|
7619
|
+
const row = this.db.query("SELECT * FROM claim_keys WHERE owner_key = ? AND key = ?").get(ownerKey, key);
|
|
7620
|
+
if (!row)
|
|
7621
|
+
return;
|
|
7622
|
+
return {
|
|
7623
|
+
ownerKey: row.owner_key,
|
|
7624
|
+
key: row.key,
|
|
7625
|
+
claimId: row.claim_id,
|
|
7626
|
+
platform: row.platform,
|
|
7627
|
+
profile: row.profile,
|
|
7628
|
+
count: row.count,
|
|
7629
|
+
ready: row.ready === 1
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7632
|
+
setClaimKey(entry, now) {
|
|
7633
|
+
this.db.query("INSERT OR REPLACE INTO claim_keys (owner_key, key, claim_id, platform, profile, count, ready, created_at) VALUES (?, ?, ?, ?, ?, ?, 0, ?)").run(entry.ownerKey, entry.key, entry.claimId, entry.platform, entry.profile, entry.count, now);
|
|
7634
|
+
}
|
|
7635
|
+
markClaimKeyReady(claimId) {
|
|
7636
|
+
this.db.query("UPDATE claim_keys SET ready = 1 WHERE claim_id = ?").run(claimId);
|
|
7637
|
+
}
|
|
7638
|
+
deleteClaimKey(ownerKey, key) {
|
|
7639
|
+
this.db.query("DELETE FROM claim_keys WHERE owner_key = ? AND key = ?").run(ownerKey, key);
|
|
7640
|
+
}
|
|
7641
|
+
deleteClaimKeysOf(claimId) {
|
|
7642
|
+
this.db.query("DELETE FROM claim_keys WHERE claim_id = ?").run(claimId);
|
|
7643
|
+
}
|
|
7644
|
+
dequeue(id) {
|
|
7645
|
+
return this.db.query("DELETE FROM boot_queue WHERE id = ?").run(id).changes > 0;
|
|
7646
|
+
}
|
|
7647
|
+
listQueue() {
|
|
7648
|
+
return this.db.query("SELECT * FROM boot_queue ORDER BY enqueued_at, id").all().map(toQueueEntry);
|
|
7649
|
+
}
|
|
7650
|
+
reclaimStaleQueue(now, pidAlive) {
|
|
7651
|
+
return this.transaction(() => {
|
|
7652
|
+
const stale = this.listQueue().filter((e) => now - e.heartbeatAt > QUEUE_TTL_MS || e.pid !== undefined && !pidAlive(e.pid)).map((e) => e.id);
|
|
7653
|
+
const stmt = this.db.query("DELETE FROM boot_queue WHERE id = ?");
|
|
7654
|
+
for (const id of stale)
|
|
7655
|
+
stmt.run(id);
|
|
7656
|
+
return stale;
|
|
7657
|
+
});
|
|
7658
|
+
}
|
|
7659
|
+
recordDevice(device, now) {
|
|
7660
|
+
this.db.query(`INSERT INTO devices (platform, id, name, profile, runtime, created_at, last_used_at) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
7661
|
+
ON CONFLICT (platform, id) DO UPDATE SET name = excluded.name, profile = excluded.profile, runtime = excluded.runtime, last_used_at = excluded.last_used_at`).run(device.platform, device.id, device.name, device.profile ?? null, device.runtime ?? null, now, now);
|
|
7662
|
+
}
|
|
7663
|
+
listDevices(platform) {
|
|
7664
|
+
const rows = platform === undefined ? this.db.query("SELECT * FROM devices ORDER BY platform, name").all() : this.db.query("SELECT * FROM devices WHERE platform = ? ORDER BY name").all(platform);
|
|
7665
|
+
return rows.map(toDevice);
|
|
7666
|
+
}
|
|
7667
|
+
touchDevice(platform, id, now) {
|
|
7668
|
+
this.db.query("UPDATE devices SET last_used_at = ? WHERE platform = ? AND id = ?").run(now, platform, id);
|
|
7669
|
+
}
|
|
7670
|
+
quarantineDevice(device, reason, now) {
|
|
7671
|
+
this.transaction(() => {
|
|
7672
|
+
this.db.query(`INSERT INTO devices (platform, id, name, profile, runtime, created_at, last_used_at, quarantined_at, quarantine_reason)
|
|
7673
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7674
|
+
ON CONFLICT (platform, id) DO UPDATE SET quarantined_at = excluded.quarantined_at, quarantine_reason = excluded.quarantine_reason`).run(device.platform, device.id, device.name, device.profile ?? null, device.runtime ?? null, now, now, now, reason ?? null);
|
|
7675
|
+
this.recordEvent({
|
|
7676
|
+
at: now,
|
|
7677
|
+
type: "quarantined",
|
|
7678
|
+
data: {
|
|
7679
|
+
platform: device.platform,
|
|
7680
|
+
deviceId: device.id,
|
|
7681
|
+
deviceName: device.name,
|
|
7682
|
+
...reason !== undefined ? { reason } : {}
|
|
7683
|
+
},
|
|
7684
|
+
resourceKey: `device:${device.platform}:${device.id}`
|
|
7685
|
+
});
|
|
7686
|
+
});
|
|
7687
|
+
}
|
|
7688
|
+
unquarantineDevice(id, now) {
|
|
7689
|
+
return this.transaction(() => {
|
|
7690
|
+
const cleared = this.db.query("SELECT platform FROM devices WHERE id = ? AND quarantined_at IS NOT NULL").all(id);
|
|
7691
|
+
this.db.query("UPDATE devices SET quarantined_at = NULL, quarantine_reason = NULL WHERE id = ? AND quarantined_at IS NOT NULL").run(id);
|
|
7692
|
+
for (const { platform } of cleared) {
|
|
7693
|
+
this.recordEvent({
|
|
7694
|
+
at: now,
|
|
7695
|
+
type: "unquarantined",
|
|
7696
|
+
data: { platform, deviceId: id },
|
|
7697
|
+
resourceKey: `device:${platform}:${id}`
|
|
7698
|
+
});
|
|
7699
|
+
}
|
|
7700
|
+
return cleared.length;
|
|
7701
|
+
});
|
|
7702
|
+
}
|
|
7703
|
+
setSlimmed(platform, id, at) {
|
|
7704
|
+
this.db.query("UPDATE devices SET slimmed_at = ? WHERE platform = ? AND id = ?").run(at ?? null, platform, id);
|
|
7705
|
+
}
|
|
7706
|
+
markPersistent(platform, id, now) {
|
|
7707
|
+
this.db.query("UPDATE devices SET persistent_at = ? WHERE platform = ? AND id = ?").run(now, platform, id);
|
|
7708
|
+
}
|
|
7709
|
+
forgetDevice(platform, id) {
|
|
7710
|
+
this.db.query("DELETE FROM devices WHERE platform = ? AND id = ?").run(platform, id);
|
|
7711
|
+
}
|
|
7712
|
+
gcWork(now, idleMs, pidAlive) {
|
|
7713
|
+
const staleLeases = this.listLeases().filter((l) => !isLeaseAlive(l, now, pidAlive)).length;
|
|
7714
|
+
const lastGc = Number(this.getMeta("last_gc_at") ?? 0);
|
|
7715
|
+
const row = this.db.query(`SELECT COUNT(*) AS n FROM devices d
|
|
7716
|
+
WHERE d.last_used_at <= ?1 - ?2 AND d.last_used_at + ?2 > ?3
|
|
7717
|
+
AND NOT EXISTS (SELECT 1 FROM leases l WHERE l.resource_key = 'device:' || d.platform || ':' || d.id)`).get(now, idleMs, lastGc);
|
|
7718
|
+
return { staleLeases, idleDevices: row?.n ?? 0 };
|
|
7719
|
+
}
|
|
7720
|
+
}
|
|
7721
|
+
function migrate(db) {
|
|
7722
|
+
const row = db.query("PRAGMA user_version").get();
|
|
7723
|
+
const current = row?.user_version ?? 0;
|
|
7724
|
+
if (current >= MIGRATIONS.length)
|
|
7725
|
+
return;
|
|
7726
|
+
db.transaction(() => {
|
|
7727
|
+
const again = db.query("PRAGMA user_version").get()?.user_version ?? 0;
|
|
7728
|
+
for (let v = again;v < MIGRATIONS.length; v++) {
|
|
7729
|
+
const sql = MIGRATIONS[v];
|
|
7730
|
+
if (sql !== undefined)
|
|
7731
|
+
db.exec(sql);
|
|
7732
|
+
}
|
|
7733
|
+
db.exec(`PRAGMA user_version = ${MIGRATIONS.length}`);
|
|
7734
|
+
}).immediate();
|
|
7735
|
+
}
|
|
7736
|
+
function openStore(path = defaultDbPath()) {
|
|
7737
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
7738
|
+
const db = new Database(path, { create: true, strict: true });
|
|
7739
|
+
db.exec("PRAGMA busy_timeout = 15000");
|
|
7740
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
7741
|
+
db.exec("PRAGMA synchronous = NORMAL");
|
|
7742
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
7743
|
+
migrate(db);
|
|
7744
|
+
return new Store(db);
|
|
7745
|
+
}
|
|
7746
|
+
|
|
7747
|
+
// ../../packages/e2e/src/runner/callsite.ts
|
|
7748
|
+
import { join as join5 } from "path";
|
|
7749
|
+
var OWN_FILES = new Set(["callsite.ts", "scope.ts", "test.ts", "attempt.ts"].map((name) => join5(import.meta.dir, name)).concat(join5(import.meta.dir, "..", "index.ts")));
|
|
7750
|
+
var FRAME = /(?:\(|\s)((?:file:\/\/)?\/[^\s()]+?):\d+(?::\d+)?\)?\s*$/;
|
|
7751
|
+
function callerFile(stack = new Error().stack, extraOwn = () => false) {
|
|
7752
|
+
for (const line of (stack ?? "").split(`
|
|
7753
|
+
`)) {
|
|
7754
|
+
const raw = FRAME.exec(line)?.[1];
|
|
7755
|
+
if (raw === undefined)
|
|
7756
|
+
continue;
|
|
7757
|
+
const path = raw.startsWith("file://") ? raw.slice("file://".length) : raw;
|
|
7758
|
+
if (OWN_FILES.has(path) || extraOwn(path) || path.includes("/node_modules/"))
|
|
7759
|
+
continue;
|
|
7760
|
+
return path;
|
|
7761
|
+
}
|
|
7762
|
+
return "unknown";
|
|
7763
|
+
}
|
|
7764
|
+
|
|
6003
7765
|
// ../../packages/e2e/src/conditions/condition-events.ts
|
|
6004
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
6005
|
-
import { dirname as
|
|
7766
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5 } from "fs";
|
|
7767
|
+
import { dirname as dirname4 } from "path";
|
|
6006
7768
|
function openConditionSink(path) {
|
|
6007
7769
|
let ready = false;
|
|
6008
7770
|
return (event) => {
|
|
6009
7771
|
if (!ready) {
|
|
6010
|
-
|
|
7772
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
6011
7773
|
ready = true;
|
|
6012
7774
|
}
|
|
6013
7775
|
appendFileSync2(path, `${JSON.stringify(event)}
|
|
@@ -6022,7 +7784,8 @@ var KNOWN_AREAS = new Set([
|
|
|
6022
7784
|
"permission",
|
|
6023
7785
|
"locale",
|
|
6024
7786
|
"a11y",
|
|
6025
|
-
"battery"
|
|
7787
|
+
"battery",
|
|
7788
|
+
"camera"
|
|
6026
7789
|
]);
|
|
6027
7790
|
|
|
6028
7791
|
// ../../packages/e2e/src/conditions/geo.ts
|
|
@@ -6350,12 +8113,28 @@ function locationOf(input) {
|
|
|
6350
8113
|
validatePoint(input);
|
|
6351
8114
|
return { kind: "point", lat: input.lat, lon: input.lon };
|
|
6352
8115
|
}
|
|
6353
|
-
var
|
|
8116
|
+
var PACKAGE_SRC = `${resolve3(import.meta.dir, "..")}/`;
|
|
8117
|
+
function callingSpecDir() {
|
|
8118
|
+
const file = callerFile(new Error().stack, (path) => path.startsWith(PACKAGE_SRC) && !path.endsWith(".test.ts"));
|
|
8119
|
+
return file === "unknown" ? undefined : dirname5(file);
|
|
8120
|
+
}
|
|
8121
|
+
var FACINGS = {
|
|
8122
|
+
back: ["back"],
|
|
8123
|
+
front: ["front"],
|
|
8124
|
+
both: ["back", "front"]
|
|
8125
|
+
};
|
|
8126
|
+
function facingsOf(choice) {
|
|
8127
|
+
const facings = FACINGS[choice ?? "back"];
|
|
8128
|
+
if (facings === undefined)
|
|
8129
|
+
throw invalid2(`unknown camera facing "${String(choice)}" (use back, front or both)`);
|
|
8130
|
+
return facings;
|
|
8131
|
+
}
|
|
8132
|
+
var registry2 = new WeakMap;
|
|
6354
8133
|
function registerConditions(device, internals) {
|
|
6355
|
-
|
|
8134
|
+
registry2.set(device, internals);
|
|
6356
8135
|
}
|
|
6357
8136
|
function conditionsOf(device) {
|
|
6358
|
-
return
|
|
8137
|
+
return registry2.get(device);
|
|
6359
8138
|
}
|
|
6360
8139
|
function conditionSinkFor(session) {
|
|
6361
8140
|
return session.results === undefined ? undefined : openConditionSink(session.results.path);
|
|
@@ -6379,6 +8158,8 @@ function createDeviceConditions(deps) {
|
|
|
6379
8158
|
let localeDirty = false;
|
|
6380
8159
|
let a11yDirty = false;
|
|
6381
8160
|
let batteryDirty = false;
|
|
8161
|
+
let cameraDirty = false;
|
|
8162
|
+
let currentCamera = {};
|
|
6382
8163
|
const permissionStates = new Map;
|
|
6383
8164
|
let permissionBundle;
|
|
6384
8165
|
let currentLocale = {};
|
|
@@ -6728,6 +8509,12 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
6728
8509
|
...appliedA11y(op, state.applied),
|
|
6729
8510
|
...resolved?.success ? { fontScale: resolved.data.scale } : {}
|
|
6730
8511
|
};
|
|
8512
|
+
if (op?.screenReader === false && currentA11y.screenReaderPrevious !== undefined) {
|
|
8513
|
+
const { screenReader: _off, screenReaderPrevious: _prior, ...rest } = currentA11y;
|
|
8514
|
+
currentA11y = rest;
|
|
8515
|
+
} else if (state.screenReaderPrevious !== undefined) {
|
|
8516
|
+
currentA11y = { ...currentA11y, screenReaderPrevious: state.screenReaderPrevious };
|
|
8517
|
+
}
|
|
6731
8518
|
if (Object.keys(currentA11y).length === 0)
|
|
6732
8519
|
recorder?.settle("a11y", currentA11y, true);
|
|
6733
8520
|
else
|
|
@@ -6747,6 +8534,68 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
6747
8534
|
emit({ area: "battery", value });
|
|
6748
8535
|
return state;
|
|
6749
8536
|
}
|
|
8537
|
+
const home = deps.wardenHome ?? wardenHome(process.env);
|
|
8538
|
+
async function applyCamera(next) {
|
|
8539
|
+
cameraDirty = true;
|
|
8540
|
+
const run = settingOp("camera");
|
|
8541
|
+
const empty = Object.keys(next).length === 0;
|
|
8542
|
+
const state = await call("camera", () => run(empty ? null : next));
|
|
8543
|
+
currentCamera = state.feeds;
|
|
8544
|
+
if (Object.keys(currentCamera).length === 0)
|
|
8545
|
+
recorder?.settle("camera", currentCamera, true);
|
|
8546
|
+
else
|
|
8547
|
+
recorder?.record("camera", currentCamera);
|
|
8548
|
+
emit({ area: "camera", value: currentCamera });
|
|
8549
|
+
return state;
|
|
8550
|
+
}
|
|
8551
|
+
function stage(facing, source, loop) {
|
|
8552
|
+
const staged = stageCameraFeed({ wardenHome: home, udid: env.udid, facing, source, loop });
|
|
8553
|
+
if (!staged.success) {
|
|
8554
|
+
throw invalid2(`camera failed: ${staged.error.code}: ${staged.error.message}`);
|
|
8555
|
+
}
|
|
8556
|
+
return staged.data;
|
|
8557
|
+
}
|
|
8558
|
+
function specPath(path, dir) {
|
|
8559
|
+
if (typeof path !== "string" || path === "")
|
|
8560
|
+
throw invalid2("camera.set needs the path of an image or video");
|
|
8561
|
+
return resolve3(dir ?? (env.specsDir !== undefined ? resolve3(env.specsDir) : process.cwd()), path);
|
|
8562
|
+
}
|
|
8563
|
+
const cameraApi = {
|
|
8564
|
+
async set(path, options = {}) {
|
|
8565
|
+
const dir = (deps.specDir ?? callingSpecDir)();
|
|
8566
|
+
const source = specPath(path, dir);
|
|
8567
|
+
const facings = facingsOf(options.facing);
|
|
8568
|
+
const loop = options.loop ?? true;
|
|
8569
|
+
const next = { ...currentCamera };
|
|
8570
|
+
for (const facing of facings) {
|
|
8571
|
+
const feed = stage(facing, source, loop);
|
|
8572
|
+
if (feed !== undefined)
|
|
8573
|
+
next[facing] = feed;
|
|
8574
|
+
}
|
|
8575
|
+
return applyCamera(next);
|
|
8576
|
+
},
|
|
8577
|
+
async clear(facing = "both") {
|
|
8578
|
+
const next = { ...currentCamera };
|
|
8579
|
+
for (const f of facingsOf(facing))
|
|
8580
|
+
delete next[f];
|
|
8581
|
+
return applyCamera(next);
|
|
8582
|
+
},
|
|
8583
|
+
async status() {
|
|
8584
|
+
return { feeds: currentCamera };
|
|
8585
|
+
}
|
|
8586
|
+
};
|
|
8587
|
+
function baselineCamera() {
|
|
8588
|
+
const feeds = {};
|
|
8589
|
+
for (const facing of ["back", "front"]) {
|
|
8590
|
+
const configured = env.cameraFeeds?.[facing];
|
|
8591
|
+
if (configured === undefined)
|
|
8592
|
+
continue;
|
|
8593
|
+
const feed = stage(facing, configured.path, configured.loop);
|
|
8594
|
+
if (feed !== undefined)
|
|
8595
|
+
feeds[facing] = feed;
|
|
8596
|
+
}
|
|
8597
|
+
return feeds;
|
|
8598
|
+
}
|
|
6750
8599
|
async function startTimelineRun(steps) {
|
|
6751
8600
|
const converted = steps.map((step, index) => {
|
|
6752
8601
|
const atMs = durationMs(step.at, `timeline step ${index} at`);
|
|
@@ -6787,10 +8636,10 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
6787
8636
|
throw new E2eError2("ACTION_FAILED", `a network flap or timeline stopped on its own after failing: ${failures[0]}`);
|
|
6788
8637
|
}
|
|
6789
8638
|
}
|
|
6790
|
-
const declared = baseline.network !== undefined || baseline.location !== undefined || baseline.clock !== undefined || baseline.permissions !== undefined || baseline.locale !== undefined || baseline.timezone !== undefined || baseline.a11y !== undefined;
|
|
8639
|
+
const declared = baseline.network !== undefined || baseline.location !== undefined || baseline.clock !== undefined || baseline.permissions !== undefined || baseline.locale !== undefined || baseline.timezone !== undefined || baseline.a11y !== undefined || env.cameraFeeds !== undefined;
|
|
6791
8640
|
const internals = {
|
|
6792
8641
|
declared,
|
|
6793
|
-
dirty: () => networkDirty || locationDirty || clockDirty || permissionsDirty || localeDirty || a11yDirty || batteryDirty || runs.size > 0,
|
|
8642
|
+
dirty: () => networkDirty || locationDirty || clockDirty || permissionsDirty || localeDirty || a11yDirty || batteryDirty || cameraDirty || runs.size > 0,
|
|
6794
8643
|
wantsInject: () => baseline.network !== undefined || baseline.clock !== undefined || currentNetwork.kind !== "online" || currentClock.kind !== "real",
|
|
6795
8644
|
async applyBaseline() {
|
|
6796
8645
|
if (baseline.permissions !== undefined) {
|
|
@@ -6810,6 +8659,9 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
6810
8659
|
await applyLocation(baseline.location);
|
|
6811
8660
|
if (baseline.network !== undefined)
|
|
6812
8661
|
await applyNetwork(baseline.network, { relaunch: true });
|
|
8662
|
+
if (env.cameraFeeds !== undefined)
|
|
8663
|
+
await applyCamera(baselineCamera());
|
|
8664
|
+
cameraDirty = false;
|
|
6813
8665
|
networkDirty = false;
|
|
6814
8666
|
locationDirty = false;
|
|
6815
8667
|
clockDirty = false;
|
|
@@ -6863,6 +8715,12 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
6863
8715
|
batteryDirty = false;
|
|
6864
8716
|
});
|
|
6865
8717
|
}
|
|
8718
|
+
if (cameraDirty) {
|
|
8719
|
+
await attempt(async () => {
|
|
8720
|
+
await applyCamera(baselineCamera());
|
|
8721
|
+
cameraDirty = false;
|
|
8722
|
+
});
|
|
8723
|
+
}
|
|
6866
8724
|
if (errors.length > 0)
|
|
6867
8725
|
throw errors[0];
|
|
6868
8726
|
}
|
|
@@ -6876,6 +8734,7 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
6876
8734
|
locale: applyLocale,
|
|
6877
8735
|
a11y: applyA11y,
|
|
6878
8736
|
battery: applyBattery,
|
|
8737
|
+
camera: cameraApi,
|
|
6879
8738
|
memoryWarning: async (options) => {
|
|
6880
8739
|
const op = settingOp("memoryWarning");
|
|
6881
8740
|
await call("memoryWarning", () => op(options ?? { bundleId: env.bundleId }));
|
|
@@ -6890,8 +8749,8 @@ the app runs without the warden network dylib: declare \`conditions.network\` fo
|
|
|
6890
8749
|
}
|
|
6891
8750
|
|
|
6892
8751
|
// ../../packages/e2e/src/perf/device-perf.ts
|
|
6893
|
-
import { copyFileSync, existsSync as
|
|
6894
|
-
import { join as
|
|
8752
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync6, writeFileSync } from "fs";
|
|
8753
|
+
import { join as join6 } from "path";
|
|
6895
8754
|
|
|
6896
8755
|
// ../../packages/e2e/src/perf/budgets.ts
|
|
6897
8756
|
var BUDGET_KEYS = ["startupColdMs", "jankPct", "maxCommitMs", "p95CpuPct", "peakMemoryMb"];
|
|
@@ -6980,30 +8839,30 @@ var flagsOf = (o) => [
|
|
|
6980
8839
|
...o.reset ? ["--reset"] : [],
|
|
6981
8840
|
...o.maxDurationMs === undefined ? [] : ["--max-duration", `${Math.ceil(o.maxDurationMs / 1000)}s`]
|
|
6982
8841
|
];
|
|
6983
|
-
var
|
|
8842
|
+
var isRecord2 = (v) => typeof v === "object" && v !== null;
|
|
6984
8843
|
function saveReport(deps, result, report, sessionId) {
|
|
6985
8844
|
const sessionReport = typeof result.reportPath === "string" ? result.reportPath : undefined;
|
|
6986
8845
|
const attempt = deps.attemptDir();
|
|
6987
8846
|
if (attempt === undefined)
|
|
6988
8847
|
return sessionReport;
|
|
6989
|
-
const out =
|
|
6990
|
-
|
|
8848
|
+
const out = join6(attempt, PERF_DIR);
|
|
8849
|
+
mkdirSync6(out, { recursive: true });
|
|
6991
8850
|
const sessionDir = typeof result.dir === "string" ? result.dir : undefined;
|
|
6992
8851
|
const write = (name, from, fallback) => {
|
|
6993
|
-
if (from !== undefined &&
|
|
6994
|
-
|
|
8852
|
+
if (from !== undefined && existsSync4(from))
|
|
8853
|
+
copyFileSync2(from, join6(out, name));
|
|
6995
8854
|
else if (fallback !== undefined)
|
|
6996
|
-
writeFileSync(
|
|
8855
|
+
writeFileSync(join6(out, name), fallback);
|
|
6997
8856
|
else
|
|
6998
8857
|
return;
|
|
6999
8858
|
deps.addFile?.(`${PERF_DIR}/${name}`);
|
|
7000
8859
|
};
|
|
7001
8860
|
write("report.json", sessionReport, `${JSON.stringify(report, null, 2)}
|
|
7002
8861
|
`);
|
|
7003
|
-
write("report.md", sessionDir === undefined ? undefined :
|
|
8862
|
+
write("report.md", sessionDir === undefined ? undefined : join6(sessionDir, "report.md"));
|
|
7004
8863
|
write("session.json", undefined, `${JSON.stringify({ sessionId, dir: sessionDir }, null, 2)}
|
|
7005
8864
|
`);
|
|
7006
|
-
return
|
|
8865
|
+
return join6(out, "report.json");
|
|
7007
8866
|
}
|
|
7008
8867
|
function createDevicePerf(deps) {
|
|
7009
8868
|
const run = async (sub, extra) => {
|
|
@@ -7015,7 +8874,7 @@ function createDevicePerf(deps) {
|
|
|
7015
8874
|
const why = env === undefined ? stderr.trim() || `exit ${code}` : `${env.error.code}: ${env.error.message}`;
|
|
7016
8875
|
throw new E2eError2("SESSION", `warden perf ${sub} failed: ${why}`);
|
|
7017
8876
|
}
|
|
7018
|
-
return
|
|
8877
|
+
return isRecord2(env.result) ? env.result : {};
|
|
7019
8878
|
};
|
|
7020
8879
|
let active = false;
|
|
7021
8880
|
return {
|
|
@@ -7037,7 +8896,7 @@ function createDevicePerf(deps) {
|
|
|
7037
8896
|
const result = await run("stop", []);
|
|
7038
8897
|
active = false;
|
|
7039
8898
|
const report = result.report;
|
|
7040
|
-
if (!
|
|
8899
|
+
if (!isRecord2(report))
|
|
7041
8900
|
throw new E2eError2("SESSION", "warden perf stop returned no report");
|
|
7042
8901
|
const sessionId = typeof result.sessionId === "string" ? result.sessionId : "";
|
|
7043
8902
|
const reportPath = saveReport(deps, result, report, sessionId);
|
|
@@ -7074,21 +8933,21 @@ function spawnWarden(bin) {
|
|
|
7074
8933
|
}
|
|
7075
8934
|
|
|
7076
8935
|
// ../../packages/e2e/src/runner/artifacts.ts
|
|
7077
|
-
import { createHash } from "crypto";
|
|
7078
|
-
import { basename, extname, join as
|
|
8936
|
+
import { createHash as createHash2 } from "crypto";
|
|
8937
|
+
import { basename, extname, join as join7, relative, sep } from "path";
|
|
7079
8938
|
var SLUG_MAX = 60;
|
|
7080
8939
|
function slug(text, fallback = "test") {
|
|
7081
8940
|
const slugged = text.normalize("NFKD").replace(/[\u0300-\u036F]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, SLUG_MAX).replace(/-+$/g, "");
|
|
7082
8941
|
return slugged === "" ? fallback : slugged;
|
|
7083
8942
|
}
|
|
7084
8943
|
function hash6(...parts) {
|
|
7085
|
-
return
|
|
8944
|
+
return createHash2("sha1").update(parts.join("\x00")).digest("hex").slice(0, 6);
|
|
7086
8945
|
}
|
|
7087
8946
|
function attemptDir(out, specId, title, hash) {
|
|
7088
|
-
return
|
|
8947
|
+
return join7(out, specId, `${slug(title.join(" "))}-${hash.slice(0, 6)}`);
|
|
7089
8948
|
}
|
|
7090
8949
|
function screenshotPath(dir, n, name) {
|
|
7091
|
-
return
|
|
8950
|
+
return join7(dir, "screenshots", `${String(n).padStart(3, "0")}-${slug(name, "screenshot")}.png`);
|
|
7092
8951
|
}
|
|
7093
8952
|
var SPEC_EXT = /\.e2e$/;
|
|
7094
8953
|
function specIdOf(file, specsDir) {
|
|
@@ -7105,9 +8964,9 @@ function specIdOf(file, specsDir) {
|
|
|
7105
8964
|
}
|
|
7106
8965
|
|
|
7107
8966
|
// ../../packages/e2e/src/devtools/native-log.ts
|
|
7108
|
-
import { copyFileSync as
|
|
8967
|
+
import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync7, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
7109
8968
|
import { homedir } from "os";
|
|
7110
|
-
import { dirname as
|
|
8969
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
7111
8970
|
var NATIVE_LOG = "native.log";
|
|
7112
8971
|
var DEFAULT_TIMEOUT_MS5 = 1e4;
|
|
7113
8972
|
var SLACK_S = 5;
|
|
@@ -7116,23 +8975,23 @@ function realNativeFs() {
|
|
|
7116
8975
|
return {
|
|
7117
8976
|
listReports: (dir) => {
|
|
7118
8977
|
try {
|
|
7119
|
-
return readdirSync(dir).map((name) => ({ name, mtimeMs: statSync(
|
|
8978
|
+
return readdirSync(dir).map((name) => ({ name, mtimeMs: statSync(join8(dir, name)).mtimeMs }));
|
|
7120
8979
|
} catch {
|
|
7121
8980
|
return [];
|
|
7122
8981
|
}
|
|
7123
8982
|
},
|
|
7124
8983
|
copy: (from, to) => {
|
|
7125
|
-
|
|
7126
|
-
|
|
8984
|
+
mkdirSync7(dirname6(to), { recursive: true });
|
|
8985
|
+
copyFileSync3(from, to);
|
|
7127
8986
|
},
|
|
7128
8987
|
write: (path, text) => {
|
|
7129
|
-
|
|
8988
|
+
mkdirSync7(dirname6(path), { recursive: true });
|
|
7130
8989
|
writeFileSync2(path, text);
|
|
7131
8990
|
}
|
|
7132
8991
|
};
|
|
7133
8992
|
}
|
|
7134
8993
|
function realNativeLogDeps(exec, adb) {
|
|
7135
|
-
return { exec, adb, fs: realNativeFs(), reportsDir:
|
|
8994
|
+
return { exec, adb, fs: realNativeFs(), reportsDir: join8(homedir(), "Library", "Logs", "DiagnosticReports") };
|
|
7136
8995
|
}
|
|
7137
8996
|
function tail(text) {
|
|
7138
8997
|
return text.length > MAX_BYTES ? text.slice(text.length - MAX_BYTES) : text;
|
|
@@ -7148,7 +9007,7 @@ async function iosProcessName(target, exec, timeoutMs) {
|
|
|
7148
9007
|
const app = container.stdout.trim();
|
|
7149
9008
|
if (container.exitCode !== 0 || !app)
|
|
7150
9009
|
return { error: failed(`app container of ${target.bundleId}`, container) };
|
|
7151
|
-
const name = await exec(["plutil", "-extract", "CFBundleExecutable", "raw", "-o", "-",
|
|
9010
|
+
const name = await exec(["plutil", "-extract", "CFBundleExecutable", "raw", "-o", "-", join8(app, "Info.plist")], {
|
|
7152
9011
|
timeoutMs
|
|
7153
9012
|
});
|
|
7154
9013
|
const exe = name.stdout.trim();
|
|
@@ -7177,7 +9036,7 @@ async function iosNativeLog(target, deps, win, timeoutMs) {
|
|
|
7177
9036
|
`process == "${proc.exe}"`
|
|
7178
9037
|
], { timeoutMs });
|
|
7179
9038
|
if (show.exitCode === 0) {
|
|
7180
|
-
deps.fs.write(
|
|
9039
|
+
deps.fs.write(join8(win.dir, NATIVE_LOG), tail(show.stdout));
|
|
7181
9040
|
capture.files.push(NATIVE_LOG);
|
|
7182
9041
|
} else {
|
|
7183
9042
|
capture.problems.push(`native log: ${failed("log show", show)}`);
|
|
@@ -7185,7 +9044,7 @@ async function iosNativeLog(target, deps, win, timeoutMs) {
|
|
|
7185
9044
|
for (const report of deps.fs.listReports(deps.reportsDir)) {
|
|
7186
9045
|
if (!report.name.startsWith(proc.exe) || !report.name.endsWith(".ips") || report.mtimeMs < win.sinceMs)
|
|
7187
9046
|
continue;
|
|
7188
|
-
deps.fs.copy(
|
|
9047
|
+
deps.fs.copy(join8(deps.reportsDir, report.name), join8(win.dir, report.name));
|
|
7189
9048
|
capture.files.push(report.name);
|
|
7190
9049
|
}
|
|
7191
9050
|
return capture;
|
|
@@ -7207,7 +9066,7 @@ async function androidNativeLog(target, deps, win, timeoutMs) {
|
|
|
7207
9066
|
crash.exitCode === 0 ? `--- crash buffer ---
|
|
7208
9067
|
${crash.stdout}` : ""
|
|
7209
9068
|
];
|
|
7210
|
-
deps.fs.write(
|
|
9069
|
+
deps.fs.write(join8(win.dir, NATIVE_LOG), tail(sections.filter(Boolean).join(`
|
|
7211
9070
|
`)));
|
|
7212
9071
|
capture.files.push(NATIVE_LOG);
|
|
7213
9072
|
return capture;
|
|
@@ -7221,24 +9080,6 @@ async function captureNativeLog(target, deps, win) {
|
|
|
7221
9080
|
}
|
|
7222
9081
|
}
|
|
7223
9082
|
|
|
7224
|
-
// ../../packages/e2e/src/runner/callsite.ts
|
|
7225
|
-
import { join as join5 } from "path";
|
|
7226
|
-
var OWN_FILES = new Set(["callsite.ts", "scope.ts", "test.ts", "attempt.ts"].map((name) => join5(import.meta.dir, name)).concat(join5(import.meta.dir, "..", "index.ts")));
|
|
7227
|
-
var FRAME = /(?:\(|\s)((?:file:\/\/)?\/[^\s()]+?):\d+(?::\d+)?\)?\s*$/;
|
|
7228
|
-
function callerFile(stack = new Error().stack) {
|
|
7229
|
-
for (const line of (stack ?? "").split(`
|
|
7230
|
-
`)) {
|
|
7231
|
-
const raw = FRAME.exec(line)?.[1];
|
|
7232
|
-
if (raw === undefined)
|
|
7233
|
-
continue;
|
|
7234
|
-
const path = raw.startsWith("file://") ? raw.slice("file://".length) : raw;
|
|
7235
|
-
if (OWN_FILES.has(path) || path.includes("/node_modules/"))
|
|
7236
|
-
continue;
|
|
7237
|
-
return path;
|
|
7238
|
-
}
|
|
7239
|
-
return "unknown";
|
|
7240
|
-
}
|
|
7241
|
-
|
|
7242
9083
|
// ../../packages/e2e/src/runner/scope.ts
|
|
7243
9084
|
var BUN_TIMEOUT_MARGIN_MS2 = 30000;
|
|
7244
9085
|
var hookOwners = new WeakMap;
|
|
@@ -7329,6 +9170,6 @@ function onlyWasSeen() {
|
|
|
7329
9170
|
|
|
7330
9171
|
// ../../packages/e2e/src/version.ts
|
|
7331
9172
|
var WORKSPACE_VERSION = "0.0.0-workspace";
|
|
7332
|
-
var WARDEN_E2E_VERSION2 = "0.
|
|
9173
|
+
var WARDEN_E2E_VERSION2 = "0.8.0";
|
|
7333
9174
|
|
|
7334
|
-
export { E2eError2, noteSpanDetail, addSpanDetail, canObserve, canDetectAlerts, settleDetailOf, DEFAULT_SETTLE_QUIET_MS, DEFAULT_SETTLE_TIMEOUT_MS, settleByHash, snapshotHash, ok, err, swipeVector, screenRect, fieldValue, PINCH_MS, ROTATE_MS, PATH_MS, centreOf, pinchRadius, rotateRadius, requireGesture, assertScale, assertDegrees, isInteractiveRole, resolveRole, normalizeText, matchesText, indexSnapshot, testIdQuery, textQuery, roleQuery, selectorExpression, renderSnapshot, renderHeader, resolve, locateByTestIdViaReact, isLocator, Locator, pickOne, textOf, scrollUntilVisible, bunExec, execError, driverError, driverErr, logEventSchema, fatalEventSchema, responseSchema, mapWireError, encodeRequest, bunSpawn, stdioTransport, spawnSimd, connectSimd, isRecoverableLoss, CONDITION_AREAS, resolveNetwork, resolveTextSize, androidFontScale, ENV_NAMES, DEFAULT_ASSERTION_TIMEOUT_MS, readEnv, runStep, setDefaultHidRecovery, setDefaultNativeLog, setDefaultDriverFactory, hasDefaultDriverFactory, setDefaultFixtureFactory, hasDefaultFixtureFactory, attemptSession, getSession,
|
|
9175
|
+
export { E2eError2, registerA11yTarget, a11yTargetOf, canActOnElements, noteSpanDetail, addSpanDetail, canObserve, canDetectAlerts, settleDetailOf, DEFAULT_SETTLE_QUIET_MS, DEFAULT_SETTLE_TIMEOUT_MS, settleByHash, snapshotHash, ok, err, swipeVector, screenRect, fieldValue, PINCH_MS, ROTATE_MS, PATH_MS, centreOf, pinchRadius, rotateRadius, requireGesture, assertScale, assertDegrees, isInteractiveRole, resolveRole, normalizeText, matchesText, indexSnapshot, testIdQuery, textQuery, roleQuery, selectorExpression, renderSnapshot, renderHeader, resolve, locateByTestIdViaReact, isLocator, Locator, pickOne, textOf, scrollUntilVisible, bunExec, execError, driverError, driverErr, logEventSchema, fatalEventSchema, responseSchema, mapWireError, createLineSplitter, encodeRequest, bunSpawn, stdioTransport, socketTransport, createSimdClient, spawnSimd, connectSimd, isRecoverableLoss, CAMERA_FACINGS, CONDITION_AREAS, resolveNetwork, resolveTextSize, androidFontScale, ENV_NAMES, DEFAULT_ASSERTION_TIMEOUT_MS, readEnv, runStep, setDefaultHidRecovery, setDefaultNativeLog, setDefaultDriverFactory, hasDefaultDriverFactory, setDefaultFixtureFactory, hasDefaultFixtureFactory, attemptSession, getSession, accessibleName, announce, failing, formatIssues, toE2eError, settled, unwrap, parseUiadA11yEvents, parseInjectEvents, withA11yRead, renderA11yScreen, createDeviceA11y, IOS_DEVICE_CAPABILITIES, sniffCameraMedia, cameraFeedDir, clearCameraFeeds, isLeaseAlive, processAlive, describeOwner, isPhysicalIosId, wardenHome, defaultDbPath, ownerKey, openStore, callerFile, formatCoord, DEFAULT_SPEED_MPS, DEFAULT_INTERVAL_S, realTickerClock, startRouteTicker, startFlap, registerConditions, conditionsOf, conditionSinkFor, createDeviceConditions, parseBudgets, createDevicePerf, perfStartFor, spawnWarden, hash6, attemptDir, screenshotPath, specIdOf, realNativeLogDeps, captureNativeLog, BUN_TIMEOUT_MARGIN_MS2, ownHook, currentScope, createScope, runInScope, hooksFor, resolveUse, registrationDefaults, markOnly, onlyWasSeen, WORKSPACE_VERSION, WARDEN_E2E_VERSION2 };
|