@delacour/warden-e2e 0.6.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-yb1hcjqm.mjs → chunk-vs5npy59.mjs} +3370 -218
- package/index.mjs +247 -16
- package/package.json +2 -2
- package/preload.mjs +8264 -2387
- package/types/core/src/a11y/a11y.config.d.ts +47 -0
- package/types/core/src/affected/affected.schema.d.ts +142 -1
- package/types/core/src/allocate.d.ts +2 -1
- package/types/core/src/android-keyguard.d.ts +36 -0
- package/types/core/src/batch/preset.schema.d.ts +13 -1
- package/types/core/src/biometrics.d.ts +15 -0
- package/types/core/src/builds/app-option.schema.d.ts +1 -1
- package/types/core/src/builds/builds.defaults.d.ts +15 -0
- package/types/core/src/builds/config.d.ts +5 -1
- package/types/core/src/builds/config.schema.d.ts +275 -2
- package/types/core/src/builds/define-config.d.ts +159 -0
- package/types/core/src/builds/install-device.d.ts +37 -0
- package/types/core/src/builds/install.d.ts +17 -3
- package/types/core/src/builds/kinds.d.ts +21 -3
- package/types/core/src/builds/lock.d.ts +38 -0
- package/types/core/src/builds/resolve.d.ts +12 -2
- 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 +141 -0
- package/types/core/src/conditions/conditions.d.ts +22 -0
- package/types/core/src/conditions/conditions.types.d.ts +171 -0
- package/types/core/src/conditions/inject-state.d.ts +13 -0
- package/types/core/src/conditions/launch-settings.d.ts +14 -0
- package/types/core/src/conditions/network.presets.d.ts +42 -0
- package/types/core/src/conditions/text-size.d.ts +19 -0
- package/types/core/src/golden/android-biometrics.d.ts +35 -0
- 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/classify.d.ts +7 -0
- package/types/core/src/ios-runner/pack.d.ts +17 -0
- package/types/core/src/ios-runner/runner-build.types.d.ts +76 -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/netproxy/control.d.ts +75 -0
- package/types/core/src/netproxy/decide.d.ts +22 -0
- package/types/core/src/netproxy/handle-file.d.ts +12 -0
- package/types/core/src/netproxy/lease-proxy.d.ts +53 -0
- package/types/core/src/netproxy/netproxy.main.d.ts +40 -0
- package/types/core/src/netproxy/pipe.d.ts +37 -0
- package/types/core/src/netproxy/proxy.d.ts +48 -0
- package/types/core/src/netproxy/shaping.d.ts +43 -0
- package/types/core/src/ownership.d.ts +44 -0
- package/types/core/src/providers/android.d.ts +46 -2
- 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 +57 -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/alert-buttons.d.ts +9 -0
- package/types/e2e/src/app.d.ts +3 -2
- package/types/e2e/src/conditions/condition-events.d.ts +68 -0
- package/types/e2e/src/conditions/device-conditions.d.ts +198 -0
- package/types/e2e/src/conditions/geo.d.ts +15 -0
- package/types/e2e/src/conditions/gpx.d.ts +8 -0
- package/types/e2e/src/conditions/lease-recorder.d.ts +27 -0
- package/types/e2e/src/conditions/lease-store.d.ts +9 -0
- package/types/e2e/src/conditions/location-plan.d.ts +17 -0
- package/types/e2e/src/conditions/location-scenarios.data.d.ts +17 -0
- package/types/e2e/src/conditions/route-ticker.d.ts +53 -0
- package/types/e2e/src/conditions/scheduler.d.ts +83 -0
- package/types/e2e/src/device-a11y.d.ts +97 -0
- package/types/e2e/src/device.d.ts +67 -4
- 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-app.d.ts +4 -0
- package/types/e2e/src/driver/android/android-argv.d.ts +21 -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-clock.d.ts +28 -0
- package/types/e2e/src/driver/android/android-device.d.ts +8 -0
- package/types/e2e/src/driver/android/android-driver.d.ts +22 -0
- package/types/e2e/src/driver/android/android-network.d.ts +66 -0
- package/types/e2e/src/driver/android/android-phone.d.ts +47 -0
- package/types/e2e/src/driver/android/android-screen-reader.d.ts +23 -0
- package/types/e2e/src/driver/android/android-settings.d.ts +41 -0
- package/types/e2e/src/driver/android/android-spoken.d.ts +18 -0
- package/types/e2e/src/driver/android/android-state.d.ts +10 -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/clock-recorder.d.ts +9 -0
- package/types/e2e/src/driver/device-settings.d.ts +10 -0
- package/types/e2e/src/driver/driver.types.d.ts +242 -1
- package/types/e2e/src/driver/element-actions.d.ts +16 -0
- package/types/e2e/src/driver/ios/device/capabilities.d.ts +3 -0
- package/types/e2e/src/driver/ios/device/device-driver.d.ts +49 -0
- package/types/e2e/src/driver/ios/device/devicectl.d.ts +38 -0
- package/types/e2e/src/driver/ios/device/runner-protocol.d.ts +21 -0
- package/types/e2e/src/driver/ios/hid-recovery.d.ts +15 -0
- package/types/e2e/src/driver/ios/inject.d.ts +124 -0
- 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 +50 -1
- package/types/e2e/src/driver/ios/ios-network.d.ts +51 -0
- package/types/e2e/src/driver/ios/ios-settings.d.ts +44 -0
- package/types/e2e/src/driver/ios/sim-runner.d.ts +41 -0
- package/types/e2e/src/driver/ios/simctl.d.ts +41 -1
- package/types/e2e/src/driver/ios/simd-transport.d.ts +11 -2
- package/types/e2e/src/driver/ios/type-budget.d.ts +6 -1
- package/types/e2e/src/driver/native-audit.d.ts +26 -0
- package/types/e2e/src/driver/network-proxy.d.ts +52 -0
- package/types/e2e/src/driver/open.d.ts +20 -0
- package/types/e2e/src/driver/physical-conditions.d.ts +11 -0
- package/types/e2e/src/driver/requirements.d.ts +14 -0
- package/types/e2e/src/driver/screen-reader.d.ts +26 -0
- package/types/e2e/src/driver/typing.d.ts +1 -1
- package/types/e2e/src/env.d.ts +47 -0
- 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/fixture-session.d.ts +4 -0
- package/types/e2e/src/fixtures.d.ts +6 -1
- package/types/e2e/src/index.d.ts +6 -0
- package/types/e2e/src/runner/callsite.d.ts +3 -1
- package/types/e2e/src/runner/scope.d.ts +1 -0
- package/types/e2e/src/runner/test.d.ts +6 -0
- package/types/e2e/src/screen/render.d.ts +8 -0
- package/types/e2e/src/traced-device.d.ts +2 -1
package/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
E2eError2,
|
|
4
|
+
a11yTargetOf,
|
|
4
5
|
canObserve,
|
|
5
6
|
fieldValue,
|
|
6
7
|
normalizeText,
|
|
@@ -11,16 +12,25 @@ import {
|
|
|
11
12
|
isLocator,
|
|
12
13
|
pickOne,
|
|
13
14
|
textOf,
|
|
14
|
-
|
|
15
|
+
isRecoverableLoss,
|
|
15
16
|
runStep,
|
|
16
17
|
attemptSession,
|
|
17
18
|
getSession,
|
|
19
|
+
accessibleName,
|
|
20
|
+
announce,
|
|
21
|
+
failing,
|
|
22
|
+
formatIssues,
|
|
23
|
+
toE2eError,
|
|
24
|
+
withA11yRead,
|
|
25
|
+
renderA11yScreen,
|
|
26
|
+
IOS_DEVICE_CAPABILITIES,
|
|
27
|
+
callerFile,
|
|
28
|
+
conditionsOf,
|
|
18
29
|
perfStartFor,
|
|
19
30
|
hash6,
|
|
20
31
|
attemptDir,
|
|
21
32
|
specIdOf,
|
|
22
33
|
captureNativeLog,
|
|
23
|
-
callerFile,
|
|
24
34
|
BUN_TIMEOUT_MARGIN_MS2,
|
|
25
35
|
ownHook,
|
|
26
36
|
currentScope,
|
|
@@ -31,10 +41,106 @@ import {
|
|
|
31
41
|
registrationDefaults,
|
|
32
42
|
markOnly,
|
|
33
43
|
WARDEN_E2E_VERSION2
|
|
34
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-vs5npy59.mjs";
|
|
35
45
|
// ../../packages/e2e/src/expect/expect.ts
|
|
36
46
|
import { expect as bunExpect } from "bun:test";
|
|
37
47
|
|
|
48
|
+
// ../../packages/e2e/src/expect/a11y-matchers.ts
|
|
49
|
+
function show(match) {
|
|
50
|
+
return typeof match === "string" ? JSON.stringify(match) : String(match);
|
|
51
|
+
}
|
|
52
|
+
function checkMillis(matcher, key, value) {
|
|
53
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value) || value < 0)) {
|
|
54
|
+
throw new E2eError2("INVALID_ARGUMENT", `${matcher}: "${key}" must be a non-negative number of milliseconds`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class DeviceAssertions {
|
|
59
|
+
target;
|
|
60
|
+
context;
|
|
61
|
+
constructor(target, context) {
|
|
62
|
+
this.target = target;
|
|
63
|
+
this.context = context;
|
|
64
|
+
}
|
|
65
|
+
get not() {
|
|
66
|
+
return new DeviceAssertions(this.target, { ...this.context, negate: !this.context.negate });
|
|
67
|
+
}
|
|
68
|
+
async toPassA11yAudit(options = {}) {
|
|
69
|
+
const call = `toPassA11yAudit(${Object.keys(options).length === 0 ? "" : JSON.stringify(options)})`;
|
|
70
|
+
return this.run(call, async () => {
|
|
71
|
+
const result = await this.target.audit(options);
|
|
72
|
+
const min = options.minSeverity ?? "error";
|
|
73
|
+
const bad = failing(result, min);
|
|
74
|
+
const passes = bad.length === 0;
|
|
75
|
+
if (passes !== this.context.negate)
|
|
76
|
+
return;
|
|
77
|
+
const table = formatIssues(result, min);
|
|
78
|
+
const lines = [
|
|
79
|
+
`expect(device)${this.context.negate ? ".not" : ""}.${call}`,
|
|
80
|
+
`Expected: ${this.context.negate ? "at least one" : "no"} ${min}${min === "error" ? "" : " or worse"} accessibility issue`,
|
|
81
|
+
`Received: ${table}`
|
|
82
|
+
];
|
|
83
|
+
throw await this.failure(lines);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async toHaveAnnounced(expected, options = {}) {
|
|
87
|
+
for (const key of Object.keys(options)) {
|
|
88
|
+
if (key !== "timeoutMs" && key !== "timeout" && key !== "since") {
|
|
89
|
+
throw new E2eError2("INVALID_ARGUMENT", `toHaveAnnounced does not take "${key}"; it takes timeoutMs, since`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
checkMillis("toHaveAnnounced", "timeoutMs", options.timeoutMs);
|
|
93
|
+
checkMillis("toHaveAnnounced", "timeout", options.timeout);
|
|
94
|
+
const timeoutMs = options.timeoutMs ?? options.timeout ?? this.target.assertionTimeout();
|
|
95
|
+
const call = `toHaveAnnounced(${show(expected)})`;
|
|
96
|
+
return this.run(call, async () => {
|
|
97
|
+
const waited = await this.target.waitForAnnouncement(expected, {
|
|
98
|
+
timeoutMs,
|
|
99
|
+
...options.since === undefined ? {} : { since: options.since }
|
|
100
|
+
});
|
|
101
|
+
const { negate } = this.context;
|
|
102
|
+
if (waited.success) {
|
|
103
|
+
if (!negate)
|
|
104
|
+
return;
|
|
105
|
+
throw await this.failure([
|
|
106
|
+
`expect(device).not.${call}`,
|
|
107
|
+
`Expected: no announcement matching ${show(expected)}`,
|
|
108
|
+
`Received: ${JSON.stringify(waited.data.event.text ?? "")}`
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
if (waited.error.code !== "E_TIMEOUT")
|
|
112
|
+
throw toE2eError(waited.error, "read announcements");
|
|
113
|
+
if (negate)
|
|
114
|
+
return;
|
|
115
|
+
const seen = waited.error.detail?.seen ?? [];
|
|
116
|
+
throw await this.failure([
|
|
117
|
+
`expect(device).${call}`,
|
|
118
|
+
`Expected: an announcement matching ${show(expected)} within ${timeoutMs} ms`,
|
|
119
|
+
`Received: ${seen.length === 0 ? "(no announcements)" : seen.map((text) => JSON.stringify(text)).join(", ")}`
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async failure(lines) {
|
|
124
|
+
const screen = await this.target.a11yScreen();
|
|
125
|
+
const head = lines.join(`
|
|
126
|
+
`);
|
|
127
|
+
return new E2eError2("ASSERTION", screen === undefined ? head : `${head}
|
|
128
|
+
|
|
129
|
+
${screen}`, screen);
|
|
130
|
+
}
|
|
131
|
+
async run(call, body) {
|
|
132
|
+
try {
|
|
133
|
+
await this.target.step(`expect device.${call}`, body);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (this.context.soft !== undefined && error instanceof E2eError2 && error.code !== "TIMEOUT") {
|
|
136
|
+
this.context.soft(error);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
38
144
|
// ../../packages/e2e/src/expect/matchers.ts
|
|
39
145
|
var NEGATION_GRACE_MS = 1000;
|
|
40
146
|
var NEGATION_QUIET_MS = 250;
|
|
@@ -45,7 +151,7 @@ function missing() {
|
|
|
45
151
|
function judged(matches, received) {
|
|
46
152
|
return { kind: "judged", matches, received };
|
|
47
153
|
}
|
|
48
|
-
function
|
|
154
|
+
function show2(match) {
|
|
49
155
|
return typeof match === "string" ? JSON.stringify(match) : String(match);
|
|
50
156
|
}
|
|
51
157
|
function assertOptions(matcher, options, allowed) {
|
|
@@ -112,8 +218,8 @@ class LocatorAssertions {
|
|
|
112
218
|
assertOptions("toHaveText", options, ["exact", "timeout"]);
|
|
113
219
|
const exact = options.exact ?? true;
|
|
114
220
|
return this.assert({
|
|
115
|
-
call: `toHaveText(${
|
|
116
|
-
expected: exact ?
|
|
221
|
+
call: `toHaveText(${show2(expected)})`,
|
|
222
|
+
expected: exact ? show2(expected) : `${show2(expected)} (substring, ignoring case)`,
|
|
117
223
|
strict: true,
|
|
118
224
|
judge: (entry) => {
|
|
119
225
|
if (entry === undefined)
|
|
@@ -127,8 +233,8 @@ class LocatorAssertions {
|
|
|
127
233
|
assertOptions("toContainText", options, ["ignoreCase", "timeout"]);
|
|
128
234
|
const ignoreCase = options.ignoreCase ?? false;
|
|
129
235
|
return this.assert({
|
|
130
|
-
call: `toContainText(${
|
|
131
|
-
expected: `text containing ${
|
|
236
|
+
call: `toContainText(${show2(expected)})`,
|
|
237
|
+
expected: `text containing ${show2(expected)}`,
|
|
132
238
|
strict: true,
|
|
133
239
|
judge: (entry) => {
|
|
134
240
|
if (entry === undefined)
|
|
@@ -141,8 +247,8 @@ class LocatorAssertions {
|
|
|
141
247
|
async toHaveValue(expected, options = {}) {
|
|
142
248
|
assertOptions("toHaveValue", options, ["timeout"]);
|
|
143
249
|
return this.assert({
|
|
144
|
-
call: `toHaveValue(${
|
|
145
|
-
expected:
|
|
250
|
+
call: `toHaveValue(${show2(expected)})`,
|
|
251
|
+
expected: show2(expected),
|
|
146
252
|
strict: true,
|
|
147
253
|
judge: (entry) => {
|
|
148
254
|
if (entry === undefined)
|
|
@@ -189,6 +295,78 @@ class LocatorAssertions {
|
|
|
189
295
|
judge: (entry) => entry === undefined ? missing() : judged(entry.states.disabled, entry.states.disabled ? "disabled" : "enabled")
|
|
190
296
|
}, options);
|
|
191
297
|
}
|
|
298
|
+
async toHaveAccessibleName(expected, options = {}) {
|
|
299
|
+
assertOptions("toHaveAccessibleName", options, ["timeout"]);
|
|
300
|
+
return this.assert({
|
|
301
|
+
call: `toHaveAccessibleName(${show2(expected)})`,
|
|
302
|
+
expected: show2(expected),
|
|
303
|
+
strict: true,
|
|
304
|
+
a11y: true,
|
|
305
|
+
judge: (entry, _count, ctx) => {
|
|
306
|
+
if (entry === undefined)
|
|
307
|
+
return missing();
|
|
308
|
+
const name = accessibleName(ctx.element(entry), ctx.platform) ?? "";
|
|
309
|
+
return judged(matchesText(name, expected, { exact: true }), JSON.stringify(name));
|
|
310
|
+
}
|
|
311
|
+
}, options);
|
|
312
|
+
}
|
|
313
|
+
async toHaveRole(expected, options = {}) {
|
|
314
|
+
assertOptions("toHaveRole", options, ["timeout"]);
|
|
315
|
+
return this.assert({
|
|
316
|
+
call: `toHaveRole(${JSON.stringify(expected)})`,
|
|
317
|
+
expected: JSON.stringify(expected),
|
|
318
|
+
strict: true,
|
|
319
|
+
judge: (entry) => entry === undefined ? missing() : judged(entry.role === expected, JSON.stringify(entry.role))
|
|
320
|
+
}, options);
|
|
321
|
+
}
|
|
322
|
+
async toHaveHint(expected, options = {}) {
|
|
323
|
+
assertOptions("toHaveHint", options, ["timeout"]);
|
|
324
|
+
return this.assert({
|
|
325
|
+
call: `toHaveHint(${show2(expected)})`,
|
|
326
|
+
expected: show2(expected),
|
|
327
|
+
strict: true,
|
|
328
|
+
a11y: true,
|
|
329
|
+
judge: (entry, _count, ctx) => {
|
|
330
|
+
if (entry === undefined)
|
|
331
|
+
return missing();
|
|
332
|
+
const hint = ctx.element(entry).a11y?.hint;
|
|
333
|
+
if (hint === undefined || hint === "")
|
|
334
|
+
return judged(false, "(no hint)");
|
|
335
|
+
return judged(matchesText(hint, expected, { exact: true }), JSON.stringify(hint));
|
|
336
|
+
}
|
|
337
|
+
}, options);
|
|
338
|
+
}
|
|
339
|
+
async toAnnounce(expected, options = {}) {
|
|
340
|
+
assertOptions("toAnnounce", options, ["exact", "timeout"]);
|
|
341
|
+
const exact = options.exact ?? true;
|
|
342
|
+
return this.assert({
|
|
343
|
+
call: `toAnnounce(${show2(expected)})`,
|
|
344
|
+
expected: exact ? show2(expected) : `${show2(expected)} (substring, ignoring case)`,
|
|
345
|
+
strict: true,
|
|
346
|
+
a11y: true,
|
|
347
|
+
judge: (entry, _count, ctx) => {
|
|
348
|
+
if (entry === undefined)
|
|
349
|
+
return missing();
|
|
350
|
+
const text = announce(ctx.element(entry), ctx.platform).text;
|
|
351
|
+
return judged(matchesText(text, expected, { exact }), JSON.stringify(text));
|
|
352
|
+
}
|
|
353
|
+
}, options);
|
|
354
|
+
}
|
|
355
|
+
async toBeA11yFocused(options = {}) {
|
|
356
|
+
assertOptions("toBeA11yFocused", options, ["timeout"]);
|
|
357
|
+
return this.assert({
|
|
358
|
+
call: "toBeA11yFocused()",
|
|
359
|
+
expected: "a11y-focused",
|
|
360
|
+
strict: true,
|
|
361
|
+
a11y: true,
|
|
362
|
+
judge: (entry, _count, ctx) => {
|
|
363
|
+
if (entry === undefined)
|
|
364
|
+
return missing();
|
|
365
|
+
const focused = ctx.element(entry).a11y?.a11yFocused === true;
|
|
366
|
+
return judged(focused, focused ? "a11y-focused" : "not a11y-focused");
|
|
367
|
+
}
|
|
368
|
+
}, options);
|
|
369
|
+
}
|
|
192
370
|
async toHaveCount(expected, options = {}) {
|
|
193
371
|
assertOptions("toHaveCount", options, ["timeout"]);
|
|
194
372
|
if (!Number.isInteger(expected) || expected < 0) {
|
|
@@ -229,7 +407,8 @@ class LocatorAssertions {
|
|
|
229
407
|
const entries = resolve(this.locator.expr, observed.index);
|
|
230
408
|
if (spec.strict && entries.length > 1)
|
|
231
409
|
pickOne(rt, this.locator.expr, entries, observed);
|
|
232
|
-
const
|
|
410
|
+
const { seen, context } = await judgementContext(spec, observed);
|
|
411
|
+
const judgement = spec.judge(entries[0], entries.length, context);
|
|
233
412
|
const verdict = await hold.next(judgement.kind === "judged" && judgement.matches !== negate, observed.driver);
|
|
234
413
|
if (verdict === "pass")
|
|
235
414
|
return { done: true, value: true };
|
|
@@ -237,7 +416,7 @@ class LocatorAssertions {
|
|
|
237
416
|
done: false,
|
|
238
417
|
state: {
|
|
239
418
|
received: judgement.kind === "missing" ? NOT_FOUND : judgement.received,
|
|
240
|
-
snapshot:
|
|
419
|
+
snapshot: seen
|
|
241
420
|
},
|
|
242
421
|
...verdict.wakeMs === undefined ? {} : { maxWaitMs: verdict.wakeMs }
|
|
243
422
|
};
|
|
@@ -253,7 +432,7 @@ class LocatorAssertions {
|
|
|
253
432
|
`Expected: ${negate ? "not " : ""}${spec.expected}`,
|
|
254
433
|
`Received: ${last?.received ?? NOT_FOUND}`
|
|
255
434
|
];
|
|
256
|
-
const screen = last?.snapshot === undefined ? undefined : this.locator.runtime.excerpt(last.snapshot);
|
|
435
|
+
const screen = last?.snapshot === undefined ? undefined : spec.a11y === true ? renderA11yScreen(last.snapshot) : this.locator.runtime.excerpt(last.snapshot);
|
|
257
436
|
const message = screen === undefined ? lines.join(`
|
|
258
437
|
`) : `${lines.join(`
|
|
259
438
|
`)}
|
|
@@ -262,6 +441,19 @@ ${screen}`;
|
|
|
262
441
|
return new E2eError2("ASSERTION", message, screen);
|
|
263
442
|
}
|
|
264
443
|
}
|
|
444
|
+
async function judgementContext(spec, observed) {
|
|
445
|
+
let seen = observed.snapshot;
|
|
446
|
+
if (spec.a11y === true) {
|
|
447
|
+
const merged = await withA11yRead(observed.driver, observed.snapshot);
|
|
448
|
+
if (merged.success)
|
|
449
|
+
seen = merged.data;
|
|
450
|
+
}
|
|
451
|
+
const byId = new Map(seen.flat.map((element) => [element.id, element]));
|
|
452
|
+
return {
|
|
453
|
+
seen,
|
|
454
|
+
context: { platform: seen.platform, element: (entry) => byId.get(entry.element.id) ?? entry.element }
|
|
455
|
+
};
|
|
456
|
+
}
|
|
265
457
|
function conditionHold(negate, holdMs, now) {
|
|
266
458
|
let since;
|
|
267
459
|
let asked = false;
|
|
@@ -345,11 +537,22 @@ function softMatchers(target, sink) {
|
|
|
345
537
|
}
|
|
346
538
|
var NOT_STATIC = new Set(["length", "name", "prototype"]);
|
|
347
539
|
function createExpect(deps) {
|
|
348
|
-
const call = (actual, message) =>
|
|
540
|
+
const call = (actual, message) => {
|
|
541
|
+
if (isLocator(actual))
|
|
542
|
+
return new LocatorAssertions(actual, { negate: false });
|
|
543
|
+
const target = a11yTargetOf(actual);
|
|
544
|
+
if (target !== undefined)
|
|
545
|
+
return new DeviceAssertions(target, { negate: false });
|
|
546
|
+
return valueExpect(actual, message);
|
|
547
|
+
};
|
|
349
548
|
const soft = (actual, message) => {
|
|
350
549
|
const sink = deps.softSink();
|
|
351
550
|
if (isLocator(actual))
|
|
352
551
|
return new LocatorAssertions(actual, { negate: false, ...sink === undefined ? {} : { soft: sink } });
|
|
552
|
+
const target = a11yTargetOf(actual);
|
|
553
|
+
if (target !== undefined) {
|
|
554
|
+
return new DeviceAssertions(target, { negate: false, ...sink === undefined ? {} : { soft: sink } });
|
|
555
|
+
}
|
|
353
556
|
const matchers = valueExpect(actual, message);
|
|
354
557
|
return sink === undefined ? matchers : softMatchers(matchers, sink);
|
|
355
558
|
};
|
|
@@ -371,6 +574,25 @@ var expect = createExpect({ softSink: currentSoftSink });
|
|
|
371
574
|
// ../../packages/e2e/src/runner/test.ts
|
|
372
575
|
import { afterAll as bunAfterAll, beforeAll as bunBeforeAll, describe as bunDescribe, test as bunTest } from "bun:test";
|
|
373
576
|
|
|
577
|
+
// ../../packages/e2e/src/driver/requirements.ts
|
|
578
|
+
function staticCapabilities(target) {
|
|
579
|
+
return target.platform === "ios" && target.deviceKind === "physical" ? IOS_DEVICE_CAPABILITIES : undefined;
|
|
580
|
+
}
|
|
581
|
+
var met = (capabilities, requirement) => {
|
|
582
|
+
switch (requirement) {
|
|
583
|
+
case "pinch":
|
|
584
|
+
return capabilities.pinch;
|
|
585
|
+
case "multiTouch":
|
|
586
|
+
return capabilities.maxPointers >= 2;
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
function missingRequirements(target, requires) {
|
|
590
|
+
const capabilities = staticCapabilities(target);
|
|
591
|
+
if (capabilities === undefined || requires === undefined)
|
|
592
|
+
return [];
|
|
593
|
+
return requires.filter((requirement) => !met(capabilities, requirement));
|
|
594
|
+
}
|
|
595
|
+
|
|
374
596
|
// ../../packages/e2e/src/runner/attempt.ts
|
|
375
597
|
import { readdirSync } from "fs";
|
|
376
598
|
import { join as join2 } from "path";
|
|
@@ -789,7 +1011,7 @@ async function concludeAttempt(session, spec, ctx, run, outcome, print, clock) {
|
|
|
789
1011
|
ctx.files.add(shot);
|
|
790
1012
|
const result = buildResult(spec, ctx, run, outcome.failed ? { failed: true, error: outcome.failure, timedOut: outcome.deadlineHit } : { failed: false });
|
|
791
1013
|
print(formatConsoleLine({ type: "test-end", result }, { clock }));
|
|
792
|
-
if (outcome.failed && session.recoverHid !== undefined &&
|
|
1014
|
+
if (outcome.failed && session.recoverHid !== undefined && isRecoverableLoss(outcome.failure, session.env.deviceKind) && await session.recoverHid()) {
|
|
793
1015
|
return "again";
|
|
794
1016
|
}
|
|
795
1017
|
if (!outcome.failed || spec.final)
|
|
@@ -838,6 +1060,12 @@ async function setUpAndRun(session, spec, use, signal, state, teardowns) {
|
|
|
838
1060
|
}
|
|
839
1061
|
});
|
|
840
1062
|
}
|
|
1063
|
+
const conditions = conditionsOf(base.device);
|
|
1064
|
+
if (conditions !== undefined) {
|
|
1065
|
+
teardowns.push({ name: "conditions", run: () => conditions.teardown(), when: () => conditions.dirty() });
|
|
1066
|
+
if (conditions.declared)
|
|
1067
|
+
await session.step("conditions", "conditions.baseline", () => conditions.applyBaseline());
|
|
1068
|
+
}
|
|
841
1069
|
if (use.launch === "fresh")
|
|
842
1070
|
await session.step("app", "app.open", () => base.app.open());
|
|
843
1071
|
for (const entry of spec.fixtures) {
|
|
@@ -892,6 +1120,8 @@ async function runCleanup(session, spec, ctx, built, teardowns, budget, bind) {
|
|
|
892
1120
|
}
|
|
893
1121
|
}
|
|
894
1122
|
for (const fixture of [...teardowns].reverse()) {
|
|
1123
|
+
if (fixture.when?.() === false)
|
|
1124
|
+
continue;
|
|
895
1125
|
await attempt(`fixture ${fixture.name} teardown`, () => step("fixture", `${fixture.name} teardown`, fixture.run));
|
|
896
1126
|
}
|
|
897
1127
|
cleanup.abort(new E2eError2("SESSION", "attempt finished"));
|
|
@@ -921,6 +1151,7 @@ function registerTest(fixtures, mode, title, second, third) {
|
|
|
921
1151
|
const use = resolveUse(scope, defaults, options.timeout);
|
|
922
1152
|
const retry = options.retry ?? 0;
|
|
923
1153
|
const wrongPlatform = options.platforms !== undefined && defaults.platform !== undefined && !options.platforms.includes(defaults.platform);
|
|
1154
|
+
const lacking = missingRequirements({ platform: defaults.platform, deviceKind: defaults.deviceKind }, options.requires).length > 0;
|
|
924
1155
|
const only = mode === "only" || options.only === true;
|
|
925
1156
|
if (only)
|
|
926
1157
|
markOnly();
|
|
@@ -942,7 +1173,7 @@ function registerTest(fixtures, mode, title, second, third) {
|
|
|
942
1173
|
});
|
|
943
1174
|
};
|
|
944
1175
|
const bunOptions = { timeout: bunTimeoutMs, retry };
|
|
945
|
-
if (mode === "skip" || options.skip === true || wrongPlatform)
|
|
1176
|
+
if (mode === "skip" || options.skip === true || wrongPlatform || lacking)
|
|
946
1177
|
bunTest.skip(title, run, bunOptions);
|
|
947
1178
|
else if (only)
|
|
948
1179
|
bunTest.only(title, run, bunOptions);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delacour/warden-e2e",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "TypeScript spec API for `warden e2e`: test, expect, screen, device, app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"LICENSE"
|
|
38
38
|
],
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@delacour/warden": "0.
|
|
40
|
+
"@delacour/warden": "0.8.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"zod": "^4.4.3"
|