@delacour/warden-e2e 0.7.0 → 0.9.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.
Files changed (94) hide show
  1. package/{chunk-empqr9n0.mjs → chunk-y0y7frfz.mjs} +2010 -169
  2. package/index.mjs +214 -13
  3. package/package.json +2 -2
  4. package/preload.mjs +3007 -800
  5. package/types/core/src/a11y/a11y.config.d.ts +47 -0
  6. package/types/core/src/affected/affected.schema.d.ts +49 -1
  7. package/types/core/src/android-keyguard.d.ts +2 -0
  8. package/types/core/src/batch/preset.schema.d.ts +12 -1
  9. package/types/core/src/builds/app-option.schema.d.ts +1 -1
  10. package/types/core/src/builds/config.schema.d.ts +93 -2
  11. package/types/core/src/builds/define-config.d.ts +55 -0
  12. package/types/core/src/builds/kinds.d.ts +6 -0
  13. package/types/core/src/camera-mode.d.ts +17 -0
  14. package/types/core/src/camera-placeholder.d.ts +9 -0
  15. package/types/core/src/camera-profile.d.ts +12 -0
  16. package/types/core/src/claim-errors.d.ts +34 -0
  17. package/types/core/src/conditions/a11y-keys.d.ts +45 -0
  18. package/types/core/src/conditions/camera-media.d.ts +30 -0
  19. package/types/core/src/conditions/conditions.config.d.ts +24 -0
  20. package/types/core/src/conditions/conditions.types.d.ts +30 -0
  21. package/types/core/src/conditions/inject-state.d.ts +1 -1
  22. package/types/core/src/ios-runner/build.d.ts +28 -0
  23. package/types/core/src/ios-runner/cache.d.ts +44 -0
  24. package/types/core/src/ios-runner/pack.d.ts +17 -0
  25. package/types/core/src/ios-runner/signing.d.ts +40 -0
  26. package/types/core/src/ios-runner/sim-build.d.ts +36 -0
  27. package/types/core/src/ios-runner/stage.d.ts +26 -0
  28. package/types/core/src/lease-events.d.ts +126 -0
  29. package/types/core/src/ownership.d.ts +44 -0
  30. package/types/core/src/providers/android.d.ts +10 -1
  31. package/types/core/src/providers/provider.types.d.ts +9 -0
  32. package/types/core/src/queue.d.ts +43 -0
  33. package/types/core/src/store.d.ts +61 -5
  34. package/types/core/src/types.d.ts +10 -0
  35. package/types/core/src/xcode.d.ts +22 -0
  36. package/types/e2e/src/a11y/a11y-target.d.ts +23 -0
  37. package/types/e2e/src/a11y/a11y.types.d.ts +81 -0
  38. package/types/e2e/src/a11y/announcements.d.ts +24 -0
  39. package/types/e2e/src/a11y/audit.d.ts +22 -0
  40. package/types/e2e/src/a11y/native/atf.d.ts +51 -0
  41. package/types/e2e/src/a11y/native/xcuitest.d.ts +60 -0
  42. package/types/e2e/src/a11y/order.d.ts +5 -0
  43. package/types/e2e/src/a11y/rules/duplicate-name.d.ts +6 -0
  44. package/types/e2e/src/a11y/rules/hidden-focusable.d.ts +3 -0
  45. package/types/e2e/src/a11y/rules/image-no-name.d.ts +6 -0
  46. package/types/e2e/src/a11y/rules/label-in-name.d.ts +7 -0
  47. package/types/e2e/src/a11y/rules/missing-name.d.ts +3 -0
  48. package/types/e2e/src/a11y/rules/role-in-name.d.ts +3 -0
  49. package/types/e2e/src/a11y/rules/rule.context.d.ts +22 -0
  50. package/types/e2e/src/a11y/rules/target-size.d.ts +21 -0
  51. package/types/e2e/src/a11y/rules/text-clipped.d.ts +9 -0
  52. package/types/e2e/src/a11y/speech.d.ts +26 -0
  53. package/types/e2e/src/a11y/talkback-log.d.ts +13 -0
  54. package/types/e2e/src/app.d.ts +0 -1
  55. package/types/e2e/src/conditions/condition-events.d.ts +6 -1
  56. package/types/e2e/src/conditions/device-conditions.d.ts +30 -2
  57. package/types/e2e/src/device-a11y.d.ts +97 -0
  58. package/types/e2e/src/device.d.ts +11 -2
  59. package/types/e2e/src/driver/a11y-events.d.ts +30 -0
  60. package/types/e2e/src/driver/a11y-read.d.ts +12 -0
  61. package/types/e2e/src/driver/android/android-animations.d.ts +7 -0
  62. package/types/e2e/src/driver/android/android-argv.d.ts +2 -0
  63. package/types/e2e/src/driver/android/android-camera-host.d.ts +23 -0
  64. package/types/e2e/src/driver/android/android-camera.d.ts +28 -0
  65. package/types/e2e/src/driver/android/android-driver.d.ts +5 -0
  66. package/types/e2e/src/driver/android/android-screen-reader.d.ts +23 -0
  67. package/types/e2e/src/driver/android/android-settings.d.ts +9 -2
  68. package/types/e2e/src/driver/android/android-spoken.d.ts +18 -0
  69. package/types/e2e/src/driver/android/android-talkback.d.ts +57 -0
  70. package/types/e2e/src/driver/android/atf-device.d.ts +23 -0
  71. package/types/e2e/src/driver/android/atf-dex.generated.d.ts +7 -0
  72. package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +5 -5
  73. package/types/e2e/src/driver/android/uiad-ops.d.ts +27 -1
  74. package/types/e2e/src/driver/android/uiad-protocol.d.ts +8 -0
  75. package/types/e2e/src/driver/android/uiad-screen-reader.d.ts +33 -0
  76. package/types/e2e/src/driver/driver.types.d.ts +71 -5
  77. package/types/e2e/src/driver/element-actions.d.ts +16 -0
  78. package/types/e2e/src/driver/ios/inject.d.ts +16 -2
  79. package/types/e2e/src/driver/ios/ios-a11y.d.ts +73 -0
  80. package/types/e2e/src/driver/ios/ios-audit.d.ts +36 -0
  81. package/types/e2e/src/driver/ios/ios-driver.d.ts +21 -1
  82. package/types/e2e/src/driver/ios/ios-network.d.ts +7 -2
  83. package/types/e2e/src/driver/ios/sim-runner.d.ts +41 -0
  84. package/types/e2e/src/driver/ios/simd-transport.d.ts +11 -2
  85. package/types/e2e/src/driver/native-audit.d.ts +26 -0
  86. package/types/e2e/src/driver/physical-conditions.d.ts +1 -1
  87. package/types/e2e/src/driver/screen-reader.d.ts +26 -0
  88. package/types/e2e/src/env.d.ts +16 -1
  89. package/types/e2e/src/expect/a11y-matchers.d.ts +33 -0
  90. package/types/e2e/src/expect/expect.d.ts +7 -2
  91. package/types/e2e/src/expect/matchers.d.ts +14 -0
  92. package/types/e2e/src/index.d.ts +5 -1
  93. package/types/e2e/src/runner/callsite.d.ts +3 -1
  94. package/types/e2e/src/screen/render.d.ts +8 -0
@@ -0,0 +1,36 @@
1
+ import type { Exec } from "../../../../core/src/exec";
2
+ import type { RunnerSourceFiles } from "../../../../core/src/ios-runner/runner-build.types";
3
+ import { type AsyncResult } from "../../../../types/src/result";
4
+ import type { Logger } from "../driver.types";
5
+ import type { NativeAuditOptions, NativeAuditResult } from "../native-audit";
6
+ import { type SimRunnerDeps } from "./sim-runner";
7
+ /**
8
+ * The iOS Simulator's XCUITest accessibility audit: the runner build (cached, unsigned), the runner process and the
9
+ * `audit` op behind `AuditsNatively`. Without runner sources the audit is skipped with the reason.
10
+ */
11
+ export type IosNativeAudit = {
12
+ audit(options: NativeAuditOptions): Promise<NativeAuditResult>;
13
+ close(): Promise<void>;
14
+ };
15
+ export type IosNativeAuditOptions = {
16
+ udid: string;
17
+ exec: Exec;
18
+ /** the app the audit checks when the caller names none */
19
+ bundleId: () => string | undefined;
20
+ env?: Record<string, string | undefined>;
21
+ wardenHome?: string;
22
+ version?: string;
23
+ log?: Logger;
24
+ /** the runner's Xcode project as text files; default: this checkout's `packages/e2e/native` when running from source */
25
+ runnerSources?: () => AsyncResult<RunnerSourceFiles>;
26
+ /** test seams */
27
+ ensureBuild?: SimRunnerDeps["ensureBuild"];
28
+ spawn?: SimRunnerDeps["spawn"];
29
+ connect?: SimRunnerDeps["connect"];
30
+ };
31
+ /**
32
+ * `packages/e2e/native` of the checkout this file runs from; in a bundled install (no checkout beside it) the blob
33
+ * embedded in the compiled `warden` binary, which a spec process (the same binary) sees too.
34
+ */
35
+ export declare function checkoutRunnerSources(importDir?: string, embedded?: () => AsyncResult<RunnerSourceFiles>): AsyncResult<RunnerSourceFiles>;
36
+ export declare function createIosNativeAudit(o: IosNativeAuditOptions): IosNativeAudit;
@@ -1,11 +1,16 @@
1
1
  import type { Exec } from "../../../../core/src/exec";
2
2
  import type { E2eEnv } from "../../env";
3
+ import type { ReadsA11yEvents } from "../a11y-events";
4
+ import type { ReadsA11y } from "../a11y-read";
3
5
  import type { ProbesFrontmost, ReadsAlertOn } from "../alerts";
4
6
  import type { ClockRecorder } from "../clock-recorder";
5
7
  import type { Driver, DriverResult, KeyName, Logger } from "../driver.types";
8
+ import type { AuditsNatively } from "../native-audit";
6
9
  import type { NetworkProxyPort } from "../network-proxy";
7
10
  import type { PastesText, SelectsAll } from "../typing";
8
11
  import { type InjectFs } from "./inject";
12
+ import { type IosA11y } from "./ios-a11y";
13
+ import { type IosNativeAudit, type IosNativeAuditOptions } from "./ios-audit";
9
14
  import { type IosNetwork } from "./ios-network";
10
15
  import { type SwappableClient } from "./ios-session";
11
16
  import { type IosSettings, type StagedSettings } from "./ios-settings";
@@ -21,7 +26,7 @@ import type { SimdSpawn } from "./simd-transport";
21
26
  */
22
27
  export type IosDriverEnv = Pick<E2eEnv, "simd" | "simdSocket" | "helperDir">;
23
28
  /** The iOS driver is a `Driver` plus the optional capabilities of `driver/alerts.ts`. */
24
- export type IosDriver = Driver & SelectsAll & PastesText & ReadsAlertOn & ProbesFrontmost;
29
+ export type IosDriver = Driver & SelectsAll & PastesText & ReadsAlertOn & ProbesFrontmost & ReadsA11y & ReadsA11yEvents & AuditsNatively;
25
30
  export type OpenIosDriverOptions = {
26
31
  udid: string;
27
32
  env?: IosDriverEnv;
@@ -64,8 +69,17 @@ export type OpenIosDriverOptions = {
64
69
  stagedSettings?: StagedSettings;
65
70
  /** state-file operations of the injected dylib's control channel, injected for tests */
66
71
  injectFs?: InjectFs;
72
+ /**
73
+ * Launch apps with the injected library so `readA11y` works (hint, custom actions, order). Default: `WARDEN_A11Y=1`.
74
+ * Off, a launch pays nothing for it.
75
+ */
76
+ a11y?: boolean;
67
77
  /** CoreSimulator devices directory for the host-side `AutomationEnabled` check (default `~/Library/Developer/CoreSimulator/Devices`) */
68
78
  devicesRoot?: string;
79
+ /** the runner's Xcode project as text files for the XCUITest audit (default: this checkout when run from source) */
80
+ runnerSources?: IosNativeAuditOptions["runnerSources"];
81
+ /** test seams for the XCUITest audit's runner */
82
+ nativeAudit?: Pick<IosNativeAuditOptions, "ensureBuild" | "spawn" | "connect">;
69
83
  };
70
84
  /** USB HID keyboard-page usage ids for the keys `Driver.key` offers. */
71
85
  export declare const KEY_USAGE: Readonly<Record<KeyName, number>>;
@@ -80,7 +94,13 @@ export declare function createIosDriver(swap: SwappableClient, simctl: Simctl, o
80
94
  settings?: (ctx: {
81
95
  lastBundle: () => string | undefined;
82
96
  }) => IosSettings;
97
+ /** screen-reader facts from the injected dylib (`ios-a11y.ts`); absent: `readA11y` answers `E_UNSUPPORTED` */
98
+ a11y?: () => IosA11y;
83
99
  /** per-app network conditions (`ios-network.ts`); absent: `network` answers `E_UNSUPPORTED` */
100
+ /** the XCUITest audit (`ios-audit.ts`); absent: `auditNatively` skips with a reason */
101
+ nativeAudit?: (ctx: {
102
+ lastBundle: () => string | undefined;
103
+ }) => IosNativeAudit;
84
104
  network?: (deps: {
85
105
  lastBundle: () => string | undefined;
86
106
  relaunch: (bundleId: string) => DriverResult<void>;
@@ -1,6 +1,6 @@
1
- import type { ClockCondition, HostFault, NetworkCondition } from "../../../../core/src/conditions/conditions.types";
1
+ import type { CameraCondition, ClockCondition, HostFault, NetworkCondition } from "../../../../core/src/conditions/conditions.types";
2
2
  import type { Result } from "../../../../types/src/result";
3
- import type { ClockState, DriverError, DriverResult, LaunchOptions, NetworkOptions, NetworkState } from "../driver.types";
3
+ import type { CameraState, ClockState, DriverError, DriverResult, LaunchOptions, NetworkOptions, NetworkState } from "../driver.types";
4
4
  import { type NetworkProxyPort } from "../network-proxy";
5
5
  import { type InjectChannel } from "./inject";
6
6
  /**
@@ -42,5 +42,10 @@ export type IosNetwork = {
42
42
  setClockOffset(ms: number): DriverResult<void>;
43
43
  /** Mock the injected app's wall clock (`Driver.clock`). Live when the app is injected, else staged for the next launch. */
44
44
  clock(op: ClockCondition, opts?: NetworkOptions): DriverResult<ClockState>;
45
+ /**
46
+ * The simulated camera (`Driver.camera`): feeds are staged copies. Live on an app launched with the camera on
47
+ * (`WARDEN_CAMERA=1`), else from the next launch, which loads the dylib for it (`appliesOnLaunch`).
48
+ */
49
+ camera(op: CameraCondition | null): DriverResult<CameraState>;
45
50
  };
46
51
  export declare function createIosNetwork(deps: IosNetworkDeps): IosNetwork;
@@ -0,0 +1,41 @@
1
+ import type { Result } from "../../../../types/src/result";
2
+ import type { DriverError, DriverResult, Logger } from "../driver.types";
3
+ import { type ClientOptions, type RequestOptions, type SimdClient } from "./simd-client";
4
+ import { type SimdSpawn } from "./simd-transport";
5
+ /**
6
+ * `warden-runner` (the XCUITest server of `native/ios-runner`) on an iOS Simulator, for the ops XCUITest has and the
7
+ * simulator helper does not: today the accessibility audit (A11Y-4). Started lazily on the first request:
8
+ *
9
+ * build (cached, no signing) -> `xcodebuild test-without-building -xctestrun … -destination id=<udid>
10
+ * -only-testing:…/testServe` -> wait for `WARDEN_RUNNER_READY {json}` (the port) -> NDJSON over TCP on this Mac
11
+ *
12
+ * The runner outlives the request (the next audit costs the audit only, about 2 s) and is shut down by `close()`.
13
+ * Anything that stops it from starting is a {@link DriverError} the caller turns into a skipped audit, never a failure.
14
+ */
15
+ export declare const READY_PREFIX = "WARDEN_RUNNER_READY";
16
+ export declare const TEST_FILTER = "-only-testing:WardenRunnerUITests/RunnerTests/testServe";
17
+ /** The port in a `WARDEN_RUNNER_READY {json}` line, or undefined for any other line. */
18
+ export declare function parseRunnerReady(line: string): number | undefined;
19
+ /** The argv that serves the runner on `udid`; `env` carries the `TEST_RUNNER_*` variables `xcodebuild` forwards. */
20
+ export declare function simRunnerArgv(o: {
21
+ xctestrun: string;
22
+ udid: string;
23
+ idleSeconds?: number;
24
+ }): string[];
25
+ export type SimRunnerDeps = {
26
+ udid: string;
27
+ /** a cached simulator runner build (`ensureSimRunnerBuild`), or why there is none */
28
+ ensureBuild(): Promise<Result<{
29
+ xctestrun: string;
30
+ }, string>>;
31
+ spawn?: SimdSpawn;
32
+ /** connect to the runner's port (tests inject a fake) */
33
+ connect?: (port: number, options: ClientOptions) => Promise<Result<SimdClient, DriverError>>;
34
+ readyTimeoutMs?: number;
35
+ log?: Logger;
36
+ };
37
+ export type SimRunner = {
38
+ request(op: string, params?: Record<string, unknown>, opts?: RequestOptions): DriverResult<unknown>;
39
+ close(): Promise<void>;
40
+ };
41
+ export declare function createSimRunner(deps: SimRunnerDeps): SimRunner;
@@ -38,6 +38,15 @@ export declare function stdioTransport(opts: {
38
38
  /** how long `close()` waits for the helper to exit after closing stdin before killing it */
39
39
  graceMs?: number;
40
40
  }): TransportFactory;
41
- export declare function socketTransport(opts: {
41
+ /** Where a socket transport connects: the daemon's unix socket, or a TCP port on this Mac (the Simulator runner). */
42
+ export type SocketTarget = {
42
43
  socketPath: string;
43
- }): TransportFactory;
44
+ tcp?: undefined;
45
+ } | {
46
+ tcp: {
47
+ hostname: string;
48
+ port: number;
49
+ };
50
+ socketPath?: undefined;
51
+ };
52
+ export declare function socketTransport(opts: SocketTarget): TransportFactory;
@@ -0,0 +1,26 @@
1
+ import type { A11yIssue } from "../a11y/a11y.types";
2
+ import type { Driver } from "./driver.types";
3
+ /** What a platform's own audit engine found, or why it did not run (never an error: a missing engine only skips it). */
4
+ export type NativeAuditResult = {
5
+ issues: A11yIssue[];
6
+ skipped?: {
7
+ source: A11yIssue["source"];
8
+ reason: string;
9
+ };
10
+ };
11
+ export type NativeAuditOptions = {
12
+ /** iOS: the app whose accessibility the XCUITest audit checks (required there; the audit needs it running) */
13
+ bundleId?: string;
14
+ /** Android: let ATF's contrast checks read a screenshot (default true) */
15
+ contrast?: boolean;
16
+ };
17
+ /**
18
+ * Optional capability next to the frozen `Driver` interface (pattern: `ReadsA11y` in `a11y-read.ts`): run the
19
+ * platform's own accessibility audit on the screen now. iOS simulator: XCUITest `performAccessibilityAudit` through
20
+ * the runner (`a11y/native/xcuitest.ts`). Android: the Accessibility Test Framework inside `warden-uiad`
21
+ * (`a11y/native/atf.ts`). Resolves with `skipped` and a reason when the engine cannot run.
22
+ */
23
+ export type AuditsNatively = {
24
+ auditNatively(options?: NativeAuditOptions): Promise<NativeAuditResult>;
25
+ };
26
+ export declare function canAuditNatively(driver: Driver): driver is Driver & AuditsNatively;
@@ -1,6 +1,6 @@
1
1
  import type { Driver } from "./driver.types";
2
2
  /** The condition and setting ops of a `Driver`: each changes the device (or an app's grants) beyond driving its UI. */
3
- export declare const PHYSICAL_CONDITION_OPS: readonly ["network", "networkFaults", "location", "clock", "permissions", "timezone", "locale", "a11y", "battery", "memoryWarning", "push"];
3
+ export declare const PHYSICAL_CONDITION_OPS: readonly ["network", "networkFaults", "location", "clock", "permissions", "timezone", "locale", "a11y", "battery", "memoryWarning", "push", "camera"];
4
4
  type PhysicalConditionOps = Pick<Driver, (typeof PHYSICAL_CONDITION_OPS)[number]>;
5
5
  /**
6
6
  * The condition ops for a physical phone: all `E_UNSUPPORTED`. On a phone they would flip airplane mode (cutting
@@ -0,0 +1,26 @@
1
+ import type { ScreenReaderState, SpokenLine } from "../a11y/a11y.types";
2
+ import type { Driver, DriverResult, Element } from "./driver.types";
3
+ /**
4
+ * Optional capability next to the frozen `Driver` interface (pattern: `ReadsA11yEvents`): drive a real screen reader
5
+ * the way a user's swipes and double-taps would. Only Android offers it, and only while TalkBack is on through
6
+ * `a11y({ screenReader: true })`; any call with TalkBack off answers `E_UNSUPPORTED`.
7
+ *
8
+ * `next` / `prev` move TalkBack's accessibility focus one step in its traversal order and answer the node that holds
9
+ * it afterwards (`undefined` at either end). `activate` clicks the focused node (`ACTION_CLICK` on it or its clickable
10
+ * ancestor). `focused` reads the node holding accessibility focus. `spoken` answers what TalkBack said since `since`
11
+ * (opaque cursor, as for `a11yEvents`): from its verbose log when that is on (`source: "talkback"`), else composed
12
+ * from the focus events by the speech model (`source: "simulated"`).
13
+ */
14
+ export type SpokenRead = {
15
+ lines: SpokenLine[];
16
+ cursor: number;
17
+ };
18
+ export type DrivesScreenReader = {
19
+ screenReaderState(): DriverResult<ScreenReaderState>;
20
+ screenReaderNext(): DriverResult<Element | undefined>;
21
+ screenReaderPrev(): DriverResult<Element | undefined>;
22
+ screenReaderActivate(): DriverResult<void>;
23
+ screenReaderFocused(): DriverResult<Element | undefined>;
24
+ screenReaderSpoken(since?: number): DriverResult<SpokenRead>;
25
+ };
26
+ export declare function canDriveScreenReader(driver: Driver): driver is Driver & DrivesScreenReader;
@@ -1,4 +1,5 @@
1
- import { type ConditionsConfig } from "../../core/src/conditions/conditions.config";
1
+ import { type A11yAuditConfig } from "../../core/src/a11y/a11y.config";
2
+ import { type CameraSuiteFeeds, type ConditionsConfig } from "../../core/src/conditions/conditions.config";
2
3
  import type { Result } from "../../types/src/result";
3
4
  /**
4
5
  * The env names `warden e2e` writes and the spec process reads (`apps/cli` imports these). `WARDEN_HEADLESS` and
@@ -13,6 +14,8 @@ export declare const ENV_NAMES: {
13
14
  readonly appPath: "WARDEN_APP_PATH";
14
15
  readonly metroUrl: "WARDEN_METRO_URL";
15
16
  readonly launchArgs: "WARDEN_LAUNCH_ARGS";
17
+ /** the dev client's url scheme (`exp+<slug>`): an Android launch has no args, so Metro's `--initialUrl` becomes a deep link on this scheme */
18
+ readonly devClientScheme: "WARDEN_DEV_CLIENT_SCHEME";
16
19
  readonly out: "WARDEN_E2E_OUT";
17
20
  readonly timeout: "WARDEN_E2E_TIMEOUT";
18
21
  readonly actionTimeout: "WARDEN_E2E_ACTION_TIMEOUT";
@@ -52,6 +55,12 @@ export declare const ENV_NAMES: {
52
55
  readonly netproxyCommand: "WARDEN_NETPROXY_COMMAND";
53
56
  /** `1` when the suite's devices are biometrics devices: the session enrolls an iOS sim again before the app is installed */
54
57
  readonly biometrics: "WARDEN_BIOMETRICS";
58
+ /** JSON of the suite's `a11y` (`ignore` / `minSeverity` / `rules`): the defaults `device.a11yAudit()` and `toPassA11yAudit()` use */
59
+ readonly a11yAudit: "WARDEN_A11Y_AUDIT";
60
+ /** `1`: iOS apps launch with the injected library, so hint, custom actions, reading order and announcements can be read */
61
+ readonly a11y: "WARDEN_A11Y";
62
+ /** JSON of the suite's `camera` with absolute paths (`{ back?: { path, loop }, front? }`): staged and fed before the first `app.open()` */
63
+ readonly cameraFeeds: "WARDEN_CAMERA_FEEDS";
55
64
  };
56
65
  export declare const DEFAULT_TIMEOUT_MS = 120000;
57
66
  export declare const DEFAULT_ACTION_TIMEOUT_MS = 30000;
@@ -64,6 +73,8 @@ export type E2eEnv = {
64
73
  bundleId: string;
65
74
  appPath?: string;
66
75
  metroUrl?: string;
76
+ /** `WARDEN_DEV_CLIENT_SCHEME` */
77
+ devClientScheme?: string;
67
78
  launchArgs: string[];
68
79
  /** where `results.jsonl` and per-attempt artifacts go (may be relative to cwd) */
69
80
  out: string;
@@ -100,6 +111,10 @@ export type E2eEnv = {
100
111
  netproxyCommand?: string[];
101
112
  /** `WARDEN_BIOMETRICS`: this is a biometrics device (profile `<p>+bio`); iOS enrollment does not survive a reboot or a spec's `unenroll` */
102
113
  biometrics?: boolean;
114
+ /** `WARDEN_A11Y_AUDIT`: the suite's accessibility audit settings; absent = rules all, ignore none, minSeverity `error` */
115
+ a11yAudit?: A11yAuditConfig;
116
+ /** `WARDEN_CAMERA_FEEDS`: the suite's `camera` (absolute paths), applied before the first `app.open()` */
117
+ cameraFeeds?: CameraSuiteFeeds;
103
118
  };
104
119
  /**
105
120
  * Reads the spec env contract. `undefined` data = not running under warden (no `WARDEN_UDID`); an
@@ -0,0 +1,33 @@
1
+ import type { A11yTarget } from "../a11y/a11y-target";
2
+ import type { DeviceA11yAuditOptions } from "../device-a11y";
3
+ import type { TextMatch } from "../screen/text";
4
+ import type { AssertionContext } from "./matchers";
5
+ export type AnnouncedOptions = {
6
+ /** how long to wait for the announcement (default: the assertion timeout); `.not` waits this long for none */
7
+ timeoutMs?: number;
8
+ /** same as `timeoutMs`, spelled as the other matchers do */
9
+ timeout?: number;
10
+ /** cursor from `device.announcements()`: only what came after it counts (default: everything the device kept) */
11
+ since?: number;
12
+ };
13
+ export type PassA11yAuditOptions = DeviceA11yAuditOptions;
14
+ /**
15
+ * The matchers of `expect(device)` / `expect(screen)`: `toPassA11yAudit` and `toHaveAnnounced`. A failure carries the
16
+ * issue table or the announcements seen, and the screen as text with the `--a11y` columns (`screen-at-failure.txt`).
17
+ */
18
+ export declare class DeviceAssertions {
19
+ private readonly target;
20
+ private readonly context;
21
+ constructor(target: A11yTarget, context: AssertionContext);
22
+ get not(): DeviceAssertions;
23
+ /**
24
+ * The screen as it is now has no issue at `minSeverity` (default `error`) or worse, from warden's rules and the
25
+ * platform's own audit. Takes the options of `device.a11yAudit` (suite `a11y` config applies); runs once, so settle
26
+ * first. Warnings (target size, platform guidelines) are reported only with `{ minSeverity: "warning" }`.
27
+ */
28
+ toPassA11yAudit(options?: PassA11yAuditOptions): Promise<void>;
29
+ /** An announcement or live-region change whose text contains the string or matches the RegExp arrived (`.not`: none did within the timeout). */
30
+ toHaveAnnounced(expected: TextMatch, options?: AnnouncedOptions): Promise<void>;
31
+ private failure;
32
+ private run;
33
+ }
@@ -1,20 +1,25 @@
1
1
  import { type Expect as BunExpect, type Matchers } from "bun:test";
2
+ import type { Device } from "../device";
2
3
  import { E2eError } from "../errors";
3
4
  import { type Locator } from "../screen/locator";
5
+ import type { Screen } from "../screen/screen";
4
6
  import { type Session } from "../session";
7
+ import { DeviceAssertions } from "./a11y-matchers";
5
8
  import { LocatorAssertions } from "./matchers";
6
9
  /** Receives a failure `expect.soft` kept instead of throwing. */
7
10
  export type SoftSink = (error: E2eError) => void;
8
11
  /** `expect.soft`: `expect` whose failures are kept on the running attempt, which fails once its body has settled. */
9
12
  export type SoftExpect = {
10
13
  (actual: Locator): LocatorAssertions;
14
+ (actual: Device | Screen): DeviceAssertions;
11
15
  <T = unknown>(actual: T, message?: string): Matchers<T>;
12
16
  };
13
17
  /**
14
- * The spec `expect`: `expect(locator)` gives the polling locator matchers, anything else is bun's own `expect`
18
+ * The spec `expect`: `expect(locator)` gives the polling locator matchers, `expect(device)` / `expect(screen)` the
19
+ * accessibility ones (`toPassA11yAudit`, `toHaveAnnounced`), anything else is bun's own `expect`
15
20
  * (all of its statics — `any`, `arrayContaining`, `extend`, `not`, … — are on it too).
16
21
  */
17
- export type E2eExpect = ((actual: Locator) => LocatorAssertions) & BunExpect & {
22
+ export type E2eExpect = ((actual: Locator) => LocatorAssertions) & ((actual: Device | Screen) => DeviceAssertions) & BunExpect & {
18
23
  readonly soft: SoftExpect;
19
24
  };
20
25
  export type ExpectDeps = {
@@ -1,5 +1,6 @@
1
1
  import { E2eError } from "../errors";
2
2
  import { type Locator } from "../screen/locator";
3
+ import type { QueryRole } from "../screen/roles";
3
4
  import { type TextMatch } from "../screen/text";
4
5
  /**
5
6
  * A negated matcher passes only after its condition held this long without a break (or the whole timeout, if shorter).
@@ -63,6 +64,19 @@ export declare class LocatorAssertions {
63
64
  toBeChecked(options?: CheckedOptions): Promise<void>;
64
65
  toBeEnabled(options?: AssertionOptions): Promise<void>;
65
66
  toBeDisabled(options?: AssertionOptions): Promise<void>;
67
+ /** The computed accessible name (label, else text, else merged child text) equals the string (whitespace normalised) or matches the RegExp. */
68
+ toHaveAccessibleName(expected: TextMatch, options?: AssertionOptions): Promise<void>;
69
+ /** The element's role in the `getByRole` vocabulary (`button`, `switch`, `heading`, …). */
70
+ toHaveRole(expected: QueryRole, options?: AssertionOptions): Promise<void>;
71
+ /** The accessibility hint equals the string or matches the RegExp. iOS needs the app launched with the injected library to see hints. */
72
+ toHaveHint(expected: TextMatch, options?: AssertionOptions): Promise<void>;
73
+ /**
74
+ * The announcement warden's speech model composes for the element ("Save, button, double tap to save") equals the
75
+ * string (`exact: false`: contains it, ignoring case) or matches the RegExp. Simulated: never what the real reader says.
76
+ */
77
+ toAnnounce(expected: TextMatch, options?: TextAssertionOptions): Promise<void>;
78
+ /** The element holds screen-reader focus (Android TalkBack's accessibility focus; iOS does not report it). */
79
+ toBeA11yFocused(options?: AssertionOptions): Promise<void>;
66
80
  /** Exactly `expected` current matches (zero included). */
67
81
  toHaveCount(expected: number, options?: AssertionOptions): Promise<void>;
68
82
  private assert;
@@ -1,7 +1,11 @@
1
+ export type { A11yAuditOptions, A11yAuditResult, A11yEvent, A11yEventKind, A11yEventSource, A11yIgnore, A11yIssue, A11yRuleId, A11ySeverity, A11ySource, Announcement, ScreenReaderState, SpokenLine, } from "./a11y/a11y.types";
1
2
  export type { OpenOptions, OpenWaitUntil } from "./app";
2
- export type { ClockInput, ConditionRun, DeviceClock, DeviceLocation, DeviceNetwork, DevicePermissions, Duration, FlapOptions, LocationPoint, NetworkSetOptions, NetworkStatus, RouteOptions, TimelineStepInput, } from "./conditions/device-conditions";
3
+ export type { CameraFacingChoice, CameraSetOptions, ClockInput, ConditionRun, DeviceCamera, DeviceClock, DeviceLocation, DeviceNetwork, DevicePermissions, Duration, FlapOptions, LocationPoint, NetworkSetOptions, NetworkStatus, RouteOptions, TimelineStepInput, } from "./conditions/device-conditions";
3
4
  export type { BiometricState, DeviceBiometrics } from "./device";
5
+ export type { A11yAnnouncement, A11yAnnouncements, A11yAnnouncementsOptions, DeviceA11y, DeviceA11yAuditOptions, DeviceScreenReader, ScreenReaderSpokenOptions, } from "./device-a11y";
6
+ export type { A11yOp, A11yState, CameraState } from "./driver/driver.types";
4
7
  export { E2eError } from "./errors";
8
+ export type { AnnouncedOptions, PassA11yAuditOptions } from "./expect/a11y-matchers";
5
9
  export { expect } from "./expect/expect";
6
10
  export type { PerfBreach, PerfBudgets } from "./perf/budgets";
7
11
  export type { DevicePerf } from "./perf/device-perf";
@@ -1,2 +1,4 @@
1
1
  /** The absolute path of the first stack frame outside the e2e runner: the spec file calling `test()`. */
2
- export declare function callerFile(stack?: string | undefined): string;
2
+ export declare function callerFile(stack?: string | undefined,
3
+ /** more files that never count as the caller (an API that resolves paths against the spec) */
4
+ extraOwn?: (path: string) => boolean): string;
@@ -7,6 +7,8 @@ export type RenderOptions = {
7
7
  refs: ReadonlyMap<string, string>;
8
8
  /** cap on element lines; a `… N more (use --all)` line follows when cut */
9
9
  maxLines?: number;
10
+ /** append the screen-reader facts of `Element.a11y` (see {@link a11yWords}) to each line */
11
+ a11y?: boolean;
10
12
  };
11
13
  export type RenderedSnapshot = {
12
14
  lines: string[];
@@ -19,10 +21,16 @@ export type RenderedSnapshot = {
19
21
  };
20
22
  /** `[button] "Sign in" id=sign-in-submit`: enough to tell matches apart in an error message. */
21
23
  export declare function describeEntry(entry: NodeEntry): string;
24
+ /**
25
+ * The `--a11y` tail of an entry line: `hint="…"`, `actions=["…"]`, `a11y-focused`, `not-important`, `live=polite|assertive`.
26
+ * Only facts the backend read are printed; an element without `a11y` adds nothing.
27
+ */
28
+ export declare function a11yWords(entry: NodeEntry): string[];
22
29
  /** `@e3 [button] "Sign in" id=sign-in-submit value="…" enabled hittable (24,332 345×48)`; `indent` is in levels. */
23
30
  export declare function renderEntryLine(entry: NodeEntry, options?: {
24
31
  ref?: string;
25
32
  indent?: number;
33
+ a11y?: boolean;
26
34
  }): string;
27
35
  /** The entries `interactiveOnly` lists: visible ones that are interactive or carry a testID. */
28
36
  export declare function isListed(entry: NodeEntry): boolean;