@delacour/warden-e2e 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/chunk-7ns30pbp.mjs +5832 -0
  3. package/index.d.ts +1 -0
  4. package/index.mjs +1047 -0
  5. package/package.json +49 -4
  6. package/preload.d.ts +1 -0
  7. package/preload.mjs +4788 -0
  8. package/types/core/src/allocate.d.ts +23 -0
  9. package/types/core/src/builds/app-option.schema.d.ts +37 -0
  10. package/types/core/src/builds/install.d.ts +95 -0
  11. package/types/core/src/builds/resolve.d.ts +74 -0
  12. package/types/core/src/config.defaults.d.ts +25 -0
  13. package/types/core/src/exec.d.ts +18 -0
  14. package/types/core/src/liveness.d.ts +12 -0
  15. package/types/core/src/providers/android.d.ts +132 -0
  16. package/types/core/src/providers/health.d.ts +45 -0
  17. package/types/core/src/providers/provider.types.d.ts +56 -0
  18. package/types/core/src/store.d.ts +105 -0
  19. package/types/core/src/types.d.ts +126 -0
  20. package/types/e2e/src/alert-buttons.d.ts +41 -0
  21. package/types/e2e/src/alert-reader.d.ts +25 -0
  22. package/types/e2e/src/app.d.ts +62 -0
  23. package/types/e2e/src/device-devtools.d.ts +26 -0
  24. package/types/e2e/src/device-link.d.ts +43 -0
  25. package/types/e2e/src/device.d.ts +90 -0
  26. package/types/e2e/src/devtools/cdp.d.ts +65 -0
  27. package/types/e2e/src/devtools/console.d.ts +59 -0
  28. package/types/e2e/src/devtools/devtools.types.d.ts +169 -0
  29. package/types/e2e/src/devtools/facet.d.ts +28 -0
  30. package/types/e2e/src/devtools/inspector.d.ts +48 -0
  31. package/types/e2e/src/devtools/jsonl.d.ts +4 -0
  32. package/types/e2e/src/devtools/native-log.d.ts +50 -0
  33. package/types/e2e/src/devtools/network.d.ts +33 -0
  34. package/types/e2e/src/devtools/network.script.d.ts +24 -0
  35. package/types/e2e/src/devtools/session.d.ts +82 -0
  36. package/types/e2e/src/devtools/tree.d.ts +23 -0
  37. package/types/e2e/src/devtools/tree.script.d.ts +33 -0
  38. package/types/e2e/src/driver/alerts.d.ts +29 -0
  39. package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
  40. package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
  41. package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
  42. package/types/e2e/src/driver/android/android-app.d.ts +32 -0
  43. package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
  44. package/types/e2e/src/driver/android/android-device.d.ts +44 -0
  45. package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
  46. package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
  47. package/types/e2e/src/driver/android/android-state.d.ts +69 -0
  48. package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
  49. package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
  50. package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
  51. package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
  52. package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
  53. package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
  54. package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
  55. package/types/e2e/src/driver/driver.types.d.ts +231 -0
  56. package/types/e2e/src/driver/errors.d.ts +13 -0
  57. package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
  58. package/types/e2e/src/driver/ios/hid-recovery.d.ts +36 -0
  59. package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
  60. package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
  61. package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
  62. package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
  63. package/types/e2e/src/driver/ios/simctl.d.ts +102 -0
  64. package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
  65. package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
  66. package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
  67. package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
  68. package/types/e2e/src/driver/observation-cache.d.ts +66 -0
  69. package/types/e2e/src/driver/open.d.ts +29 -0
  70. package/types/e2e/src/driver/settle.d.ts +43 -0
  71. package/types/e2e/src/driver/span-detail.d.ts +12 -0
  72. package/types/e2e/src/driver/traced-driver.d.ts +14 -0
  73. package/types/e2e/src/driver/typing.d.ts +48 -0
  74. package/types/e2e/src/driver/unwrap.d.ts +13 -0
  75. package/types/e2e/src/env.d.ts +78 -0
  76. package/types/e2e/src/errors.d.ts +26 -0
  77. package/types/e2e/src/expect/expect.d.ts +27 -0
  78. package/types/e2e/src/expect/matchers.d.ts +72 -0
  79. package/types/e2e/src/fixture-session.d.ts +38 -0
  80. package/types/e2e/src/fixtures.d.ts +14 -0
  81. package/types/e2e/src/index.d.ts +11 -0
  82. package/types/e2e/src/perf/budgets.d.ts +33 -0
  83. package/types/e2e/src/perf/device-perf.d.ts +45 -0
  84. package/types/e2e/src/perf/perf.types.d.ts +59 -0
  85. package/types/e2e/src/preload.d.ts +29 -0
  86. package/types/e2e/src/runner/artifacts.d.ts +13 -0
  87. package/types/e2e/src/runner/attempt.d.ts +63 -0
  88. package/types/e2e/src/runner/callsite.d.ts +2 -0
  89. package/types/e2e/src/runner/failure.d.ts +40 -0
  90. package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
  91. package/types/e2e/src/runner/results.d.ts +48 -0
  92. package/types/e2e/src/runner/scope.d.ts +64 -0
  93. package/types/e2e/src/runner/span.d.ts +40 -0
  94. package/types/e2e/src/runner/step.d.ts +7 -0
  95. package/types/e2e/src/runner/test.d.ts +59 -0
  96. package/types/e2e/src/runner/trace.d.ts +87 -0
  97. package/types/e2e/src/screen/actions.d.ts +93 -0
  98. package/types/e2e/src/screen/context.d.ts +134 -0
  99. package/types/e2e/src/screen/field.d.ts +121 -0
  100. package/types/e2e/src/screen/locator.d.ts +233 -0
  101. package/types/e2e/src/screen/nodes.d.ts +53 -0
  102. package/types/e2e/src/screen/poll.d.ts +57 -0
  103. package/types/e2e/src/screen/queries.d.ts +34 -0
  104. package/types/e2e/src/screen/query.d.ts +60 -0
  105. package/types/e2e/src/screen/react-locator.d.ts +30 -0
  106. package/types/e2e/src/screen/render.d.ts +42 -0
  107. package/types/e2e/src/screen/roles.d.ts +24 -0
  108. package/types/e2e/src/screen/screen.d.ts +61 -0
  109. package/types/e2e/src/screen/selector.d.ts +38 -0
  110. package/types/e2e/src/screen/text.d.ts +13 -0
  111. package/types/e2e/src/session.d.ts +124 -0
  112. package/types/e2e/src/traced-device.d.ts +9 -0
  113. package/types/e2e/src/version.d.ts +7 -0
  114. package/types/types/src/result.d.ts +12 -0
  115. package/README.md +0 -3
@@ -0,0 +1,60 @@
1
+ import type { App } from "../app";
2
+ import type { Device } from "../device";
3
+ import type { Platform } from "../driver/driver.types";
4
+ import type { E2eError } from "../errors";
5
+ import type { Screen } from "../screen/screen";
6
+ import type { Session } from "../session";
7
+ import type { TraceWriter } from "./trace";
8
+ export type { App, Device, Screen };
9
+ /** What every test receives from the runner; `test.extend` adds to it. */
10
+ export type TestFixtures = BaseFixtures;
11
+ /** What every test receives; `test.extend` adds to it. */
12
+ export type BaseFixtures = {
13
+ app: App;
14
+ device: Device;
15
+ screen: Screen;
16
+ platform: Platform;
17
+ };
18
+ export type LaunchMode = "fresh" | "none";
19
+ /**
20
+ * `test.use({ systemAlerts })`: what to do when a read or wait sees an alert or sheet over the app (a permission
21
+ * prompt, "Open in …?", the Keychain "Save Password?" sheet, an app alert). `accept` / `dismiss` answer it with the
22
+ * buttons `device.alert("accept")` / `("dismiss")` press; `off` (the default) leaves it for the test.
23
+ */
24
+ export type SystemAlerts = "accept" | "dismiss" | "off";
25
+ export declare const SYSTEM_ALERTS: readonly SystemAlerts[];
26
+ /** What a fixture factory gets for one attempt (or, for `beforeAll`/`afterAll`, the idle session). */
27
+ export type FixtureContext = {
28
+ /**
29
+ * Inside an attempt, a view of the session bound to that attempt (`attemptSession`): its `signal`, `step`, `span`
30
+ * and `trace` stay that attempt's, so a fixture never acts in a later test.
31
+ */
32
+ session: Session;
33
+ /** aborts at the attempt deadline; every driver call and poll must honour it */
34
+ signal: AbortSignal;
35
+ launch: LaunchMode;
36
+ actionTimeoutMs: number;
37
+ assertionTimeoutMs: number;
38
+ /** absent = `off` */
39
+ systemAlerts?: SystemAlerts;
40
+ };
41
+ /**
42
+ * Builds `app`/`device`/`screen`. Registered on `session.fixtures` or via `setDefaultFixtureFactory`.
43
+ * The runner itself calls `app.open()` before the body when `launch` is `"fresh"`.
44
+ */
45
+ export type FixtureFactory = (ctx: FixtureContext) => BaseFixtures | Promise<BaseFixtures>;
46
+ /** The running attempt: where steps, screenshots and failure files go. Read it through the session. */
47
+ export type AttemptContext = {
48
+ readonly signal: AbortSignal;
49
+ /** `<out>/<specId>/<title-slug>-<hash6>/` */
50
+ readonly dir: string;
51
+ /** spec name tagging step lines */
52
+ readonly short: string;
53
+ readonly trace: TraceWriter;
54
+ /** nesting depth of the step in progress (indents console lines) */
55
+ depth: number;
56
+ /** files written into `dir`, relative to it (reported in `TestResult.artifacts`) */
57
+ readonly files: Set<string>;
58
+ /** failures kept by `expect.soft`; the attempt fails with all of them once the body has settled */
59
+ readonly softFailures: E2eError[];
60
+ };
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ export declare const testStatusSchema: z.ZodEnum<{
3
+ failed: "failed";
4
+ passed: "passed";
5
+ skipped: "skipped";
6
+ "timed-out": "timed-out";
7
+ }>;
8
+ export declare const testResultSchema: z.ZodObject<{
9
+ file: z.ZodString;
10
+ title: z.ZodArray<z.ZodString>;
11
+ status: z.ZodEnum<{
12
+ failed: "failed";
13
+ passed: "passed";
14
+ skipped: "skipped";
15
+ "timed-out": "timed-out";
16
+ }>;
17
+ durationMs: z.ZodNumber;
18
+ startedAt: z.ZodNumber;
19
+ only: z.ZodOptional<z.ZodBoolean>;
20
+ error: z.ZodOptional<z.ZodObject<{
21
+ message: z.ZodString;
22
+ code: z.ZodOptional<z.ZodString>;
23
+ stack: z.ZodOptional<z.ZodString>;
24
+ }, z.core.$strip>>;
25
+ artifacts: z.ZodOptional<z.ZodObject<{
26
+ dir: z.ZodString;
27
+ files: z.ZodArray<z.ZodString>;
28
+ }, z.core.$strip>>;
29
+ device: z.ZodObject<{
30
+ udid: z.ZodString;
31
+ platform: z.ZodEnum<{
32
+ ios: "ios";
33
+ android: "android";
34
+ }>;
35
+ name: z.ZodOptional<z.ZodString>;
36
+ }, z.core.$strip>;
37
+ }, z.core.$strip>;
38
+ export type TestResult = z.infer<typeof testResultSchema>;
39
+ export type TestStatus = z.infer<typeof testStatusSchema>;
40
+ export type ResultsWriter = {
41
+ readonly path: string;
42
+ /** one JSON line; `appendFileSync` so a crash right after never loses an earlier line */
43
+ append(result: TestResult): void;
44
+ };
45
+ /** Append-only `results.jsonl` writer; the directory is created on first use. */
46
+ export declare function openResults(path: string): ResultsWriter;
47
+ /** Valid results in file order. Blank, truncated (killed mid-write) and invalid lines are skipped. */
48
+ export declare function readResults(text: string): TestResult[];
@@ -0,0 +1,64 @@
1
+ import type { BaseFixtures, LaunchMode, SystemAlerts } from "./fixtures.types";
2
+ /** Headroom over the test timeout so warden's `AbortSignal` deadline fires before bun's own. */
3
+ export declare const BUN_TIMEOUT_MARGIN_MS = 30000;
4
+ export type UseOptions = {
5
+ timeout?: number;
6
+ actionTimeout?: number;
7
+ assertionTimeout?: number;
8
+ launch?: LaunchMode;
9
+ /**
10
+ * Answer an alert or sheet over the app whenever a read or wait sees one (it already has the snapshot, so a screen
11
+ * without one costs nothing): `dismiss` presses what `device.alert("dismiss")` would, `accept` what `("accept")`
12
+ * would, `off` (default) leaves it. It also answers the app's own alerts, so a test that taps those buttons
13
+ * itself keeps it `off`.
14
+ */
15
+ systemAlerts?: SystemAlerts;
16
+ };
17
+ export type ResolvedUse = {
18
+ timeoutMs: number;
19
+ actionTimeoutMs: number;
20
+ assertionTimeoutMs: number;
21
+ launch: LaunchMode;
22
+ systemAlerts: SystemAlerts;
23
+ };
24
+ /** Scope hooks are stored against the base fixtures; `test.extend(...).beforeEach` hooks are narrowed back to theirs by the runner (bun's own hooks take no arguments). */
25
+ export type Hook = (fixtures: BaseFixtures) => void | Promise<void>;
26
+ /** Marks `hook` as belonging to tests built from `fixtures` (entries compared by identity, in definition order). */
27
+ export declare function ownHook(hook: Hook, fixtures: readonly object[]): Hook;
28
+ export type Scope = {
29
+ /** describe titles from the root down to this scope */
30
+ readonly title: readonly string[];
31
+ readonly parent: Scope | undefined;
32
+ use: UseOptions;
33
+ readonly beforeEach: Hook[];
34
+ readonly afterEach: Hook[];
35
+ /** accepted for API parity: bun runs a file's tests serially unless `--concurrent` */
36
+ readonly serial: boolean;
37
+ };
38
+ /** The innermost `describe` being collected, else the root scope of the spec file making the call. */
39
+ export declare function currentScope(): Scope;
40
+ /**
41
+ * The scope of a `describe` call. Created when `describe()` is called — its parent is whatever is
42
+ * current then — because bun runs the callback later (nested ones after the parent's callback has
43
+ * returned), when the stack no longer says where it belongs.
44
+ */
45
+ export declare function createScope(title: string, serial: boolean): Scope;
46
+ /** Runs `body` (bun's `describe` callback) with `scope` current. */
47
+ export declare function runInScope(scope: Scope, body: () => void): void;
48
+ /** Root first, `scope` last. */
49
+ export declare function scopeChain(scope: Scope): Scope[];
50
+ /** `beforeEach`: outermost first. `afterEach`: innermost first. Hooks owned by another fixture set are left out. */
51
+ export declare function hooksFor(scope: Scope, kind: "beforeEach" | "afterEach", fixtures?: readonly object[]): Hook[];
52
+ /** Session defaults (env contract) under the nearest `test.use`, under the test's own `timeout`. */
53
+ export declare function resolveUse(scope: Scope, defaults: Pick<ResolvedUse, "timeoutMs" | "actionTimeoutMs" | "assertionTimeoutMs">, testTimeout?: number): ResolvedUse;
54
+ /** Timeouts known at collection time: the env when set, else the contract defaults. */
55
+ export declare function registrationDefaults(env?: Readonly<Record<string, string | undefined>>): {
56
+ timeoutMs: number;
57
+ actionTimeoutMs: number;
58
+ assertionTimeoutMs: number;
59
+ platform: "ios" | "android" | undefined;
60
+ };
61
+ /** Records that a `.only` was collected (kept on `globalThis`: the preload and the specs may be separate bundles). */
62
+ export declare function markOnly(): void;
63
+ export declare function onlyWasSeen(): boolean;
64
+ export declare function resetOnly(): void;
@@ -0,0 +1,40 @@
1
+ import { type SpanDetail } from "../driver/span-detail";
2
+ import type { TraceEvent } from "./trace";
3
+ /** Where spans go: the running attempt's `trace.jsonl` writer. */
4
+ export type SpanSink = {
5
+ write(event: TraceEvent): void;
6
+ };
7
+ export type SpanOutcome = {
8
+ ok: boolean;
9
+ code?: string;
10
+ };
11
+ export type SpanOptions<T> = {
12
+ /** clock for `ts` and `ms`; default `Date.now` */
13
+ now?: () => number;
14
+ /** whether a call that did not throw still failed (a driver `Result`) */
15
+ verdict?: (value: T) => SpanOutcome;
16
+ /** scalars read off the result, merged over what the call noted with `noteSpanDetail` */
17
+ detail?: (value: T) => SpanDetail | undefined;
18
+ };
19
+ /** Times one call as a `span` (`device.*`, `read.*`); see {@link runSpan}. */
20
+ export type SpanRunner = <T>(op: string, fn: () => Promise<T>, options?: SpanOptions<T>) => Promise<T>;
21
+ /** The `code` of a thrown error (`E2eError`, a driver error), else `ERROR`. */
22
+ export declare function codeOf(error: unknown): string;
23
+ /**
24
+ * Writes one `span` event: `ts` is when the call started, `ms` how long it took. Tracing is diagnostic, so a sink
25
+ * that throws, or an `outcome` that does, never fails the call being timed.
26
+ */
27
+ export declare function writeSpan(sink: SpanSink, span: {
28
+ op: string;
29
+ ts: number;
30
+ ms: number;
31
+ notes: SpanDetail;
32
+ }, outcome: () => SpanOutcome & {
33
+ extra?: SpanDetail;
34
+ }): void;
35
+ /**
36
+ * Runs `fn` and writes one `span` event for it to `sink`: `ok`/`code` from the outcome, a throw recorded and
37
+ * rethrown unchanged. Without a sink (no attempt is running) it is just `fn()`. The sink is taken when the call
38
+ * starts, so a call that outlives its attempt still reports to that attempt's trace, never the next test's.
39
+ */
40
+ export declare function runSpan<T>(sink: SpanSink | undefined, op: string, fn: () => Promise<T>, options?: SpanOptions<T>): Promise<T>;
@@ -0,0 +1,7 @@
1
+ import type { AttemptContext } from "./fixtures.types";
2
+ /**
3
+ * One reported step of the running attempt: writes `step-start` / `step-end` to `trace.jsonl` (and
4
+ * so to the console), nests by `depth`, and gives up as soon as the attempt deadline aborts, even
5
+ * if `fn` is a driver call that never answers. Without an attempt it just runs `fn`.
6
+ */
7
+ export declare function runStep<T>(attempt: AttemptContext | undefined, kind: string, label: string, fn: () => Promise<T>, now?: () => number): Promise<T>;
@@ -0,0 +1,59 @@
1
+ import type { Platform } from "../driver/driver.types";
2
+ import { type BaseFixtures } from "./fixtures.types";
3
+ import { type Hook, type UseOptions } from "./scope";
4
+ export type TestOptions = {
5
+ /** ms for the whole attempt (hooks included); beats `test.use({ timeout })` */
6
+ timeout?: number;
7
+ skip?: boolean;
8
+ only?: boolean;
9
+ /** run only on these platforms (decided at collection time from `WARDEN_PLATFORM`) */
10
+ platforms?: readonly Platform[];
11
+ /** extra attempts after a failure; only the last attempt writes to `results.jsonl` */
12
+ retry?: number;
13
+ };
14
+ export type TestBody<F extends object> = (fixtures: F) => void | Promise<void>;
15
+ export type TestCallable<F extends object> = {
16
+ (title: string, body: TestBody<F>): void;
17
+ (title: string, options: TestOptions, body: TestBody<F>): void;
18
+ };
19
+ /**
20
+ * `test.extend` fixtures: set up, call `use(value)`, clean up afterwards (runs even when the test
21
+ * failed). A fixture may read the ones defined before it in the same object (`base`).
22
+ */
23
+ export type FixtureDefs<Base extends object, Extra extends object> = {
24
+ [K in keyof Extra]: (base: Base & Extra, use: (value: Extra[K]) => Promise<void>) => Promise<void>;
25
+ };
26
+ export type TestApi<F extends object> = TestCallable<F> & {
27
+ skip: TestCallable<F>;
28
+ only: TestCallable<F>;
29
+ /** a placeholder that never passes silently: it fails if bun is run with `--todo` */
30
+ todo(title: string): void;
31
+ skipIf(condition: boolean): TestCallable<F>;
32
+ /** applies to every test collected in the current scope (file or `describe`) */
33
+ use(options: UseOptions): void;
34
+ /**
35
+ * Like the top-level `beforeEach`, typed with this `test`'s fixtures (the ones `extend` added too). It runs only
36
+ * for tests made with this `test` or one extended from it, not for the base `test`'s.
37
+ */
38
+ beforeEach(hook: (fixtures: F) => void | Promise<void>): void;
39
+ /** `beforeEach`'s counterpart: runs after each such test, even when it failed. */
40
+ afterEach(hook: (fixtures: F) => void | Promise<void>): void;
41
+ /** a `test` whose bodies also receive `Extra` */
42
+ extend<Extra extends object>(defs: FixtureDefs<F, Extra>): TestApi<F & Extra>;
43
+ };
44
+ /** The spec `test`. Fixtures: `{ app, device, screen, platform }`. */
45
+ export declare const test: TestApi<BaseFixtures>;
46
+ export type DescribeOptions = {
47
+ /** accepted for API parity: bun runs a file's tests one after another unless `--concurrent` */
48
+ serial?: boolean;
49
+ };
50
+ export declare function describe(title: string, body: () => void): void;
51
+ export declare function describe(title: string, options: DescribeOptions, body: () => void): void;
52
+ /** Runs before every test in this scope, outermost scope first; receives the test's `{ app, device, screen, platform }`. */
53
+ export declare function beforeEach(hook: Hook): void;
54
+ /** Runs after every test in this scope, innermost scope first, even when the test failed. */
55
+ export declare function afterEach(hook: Hook): void;
56
+ /** Once per file/`describe`, before its first test; receives the base fixtures (not the `extend` ones). */
57
+ export declare function beforeAll(hook: Hook): void;
58
+ /** Once per file/`describe`, after its last test. */
59
+ export declare function afterAll(hook: Hook): void;
@@ -0,0 +1,87 @@
1
+ import { z } from "zod";
2
+ import type { TestResult } from "./results";
3
+ export declare const traceEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
+ ts: z.ZodNumber;
5
+ kind: z.ZodString;
6
+ label: z.ZodString;
7
+ type: z.ZodLiteral<"step-start">;
8
+ }, z.core.$strip>, z.ZodObject<{
9
+ status: z.ZodEnum<{
10
+ failed: "failed";
11
+ passed: "passed";
12
+ }>;
13
+ ms: z.ZodNumber;
14
+ error: z.ZodOptional<z.ZodString>;
15
+ ts: z.ZodNumber;
16
+ kind: z.ZodString;
17
+ label: z.ZodString;
18
+ type: z.ZodLiteral<"step-end">;
19
+ }, z.core.$strip>, z.ZodObject<{
20
+ path: z.ZodString;
21
+ ts: z.ZodNumber;
22
+ kind: z.ZodString;
23
+ label: z.ZodString;
24
+ type: z.ZodLiteral<"screenshot">;
25
+ }, z.core.$strip>, z.ZodObject<{
26
+ ts: z.ZodNumber;
27
+ kind: z.ZodString;
28
+ label: z.ZodString;
29
+ type: z.ZodLiteral<"note">;
30
+ }, z.core.$strip>, z.ZodObject<{
31
+ type: z.ZodLiteral<"span">;
32
+ ts: z.ZodNumber;
33
+ op: z.ZodString;
34
+ ms: z.ZodNumber;
35
+ ok: z.ZodBoolean;
36
+ code: z.ZodOptional<z.ZodString>;
37
+ detail: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
38
+ }, z.core.$strip>], "type">;
39
+ /** One line of `trace.jsonl`; `ts` is epoch ms, `kind` the step family (`app`, `locator`, `expect`, …). */
40
+ export type TraceEvent = z.infer<typeof traceEventSchema>;
41
+ export type TraceWriter = {
42
+ readonly path: string;
43
+ /**
44
+ * Appends `event`. A `span` is only buffered (a test makes hundreds of driver calls and one `appendFileSync`
45
+ * each would show up in the timings it measures): the buffer is appended, in order, before the next other
46
+ * event, by {@link TraceWriter.flush}, or when it holds {@link SPAN_BUFFER_MAX} spans.
47
+ */
48
+ write(event: TraceEvent): void;
49
+ /** Appends the buffered spans; the runner calls it when an attempt ends. Spans written after it are lost. */
50
+ flush(): void;
51
+ };
52
+ /** Spans held in memory before they are appended without waiting for a step. */
53
+ export declare const SPAN_BUFFER_MAX = 256;
54
+ /** Append-only `trace.jsonl`; `mirror` also receives each event (the console reporter). */
55
+ export declare function openTrace(path: string, mirror?: (event: TraceEvent) => void): TraceWriter;
56
+ /** Valid events in file order; blank, truncated and invalid lines are skipped. */
57
+ export declare function readTrace(text: string): TraceEvent[];
58
+ type TestRef = Pick<TestResult, "file" | "title" | "device">;
59
+ /** What the console reporter prints: a trace event, or a test boundary. */
60
+ export type ConsoleEvent = TraceEvent | {
61
+ type: "test-start";
62
+ test: TestRef & {
63
+ startedAt: number;
64
+ };
65
+ } | {
66
+ type: "test-end";
67
+ result: TestResult;
68
+ };
69
+ export type ConsoleLineOptions = {
70
+ /** prefix `[HH:MM:SS.mmm] `; false under `WARDEN_LOG_TIMESTAMPS=1` (warden stamps the log) */
71
+ clock: boolean;
72
+ /** step lines only: short spec name tagging the line (steps interleave across workers) */
73
+ short?: string;
74
+ /** step lines only: nesting depth, 0 = directly in the test */
75
+ depth?: number;
76
+ };
77
+ /** `ts` as local `HH:MM:SS.mmm` (the clock warden stamps its logs with). */
78
+ export declare function clockOf(ts: number): string;
79
+ /** `e2e/sign-in.e2e.ts` → `sign-in`. */
80
+ export declare function shortName(file: string): string;
81
+ /**
82
+ * The console line for an event, in PR #22's convention: `▶ test |<platform>| <file> › <title>` /
83
+ * `■ … <status> <N.NN>s on <device>` per test, `▸`/`▪` per step. `undefined` for events that print
84
+ * nothing (`screenshot`, `note`).
85
+ */
86
+ export declare function formatConsoleLine(event: ConsoleEvent, opts: ConsoleLineOptions): string | undefined;
87
+ export {};
@@ -0,0 +1,93 @@
1
+ import type { Driver, Point, Rect, ScreenInfo } from "../driver/driver.types";
2
+ import type { NodeEntry } from "./nodes";
3
+ /** Pure helpers behind the locator actions: where to tap, how to swipe, how to empty a field. No driver calls. */
4
+ /** The way the content scrolls: `down` reveals what is below, so the finger moves up. */
5
+ export type SwipeDirection = "up" | "down" | "left" | "right";
6
+ /** Fling strength of a swipe: `none` is a plain drag of half the surface. */
7
+ export type Momentum = "none" | "slow" | "fast";
8
+ export type SwipeVector = {
9
+ from: Point;
10
+ to: Point;
11
+ options: {
12
+ durationMs: number;
13
+ momentum: boolean;
14
+ };
15
+ };
16
+ /** Where a tap on `entry` lands: `position` is an offset from its top-left corner, else the driver's tap point or the centre. */
17
+ export declare function tapPointOf(entry: NodeEntry, position?: Point): Point;
18
+ /** A tap here focuses a text field with the caret after its text (right of where the text ends). */
19
+ export declare function focusPoint(frame: Rect): Point;
20
+ /** The finger path that scrolls `rect`'s content in `direction`, centred on the rect. */
21
+ export declare function swipeVector(direction: SwipeDirection, rect: Rect, momentum?: Momentum): SwipeVector;
22
+ /**
23
+ * The one swipe geometry: the finger path inside `rect`, centred, covering `fraction` (0-1) of it, that scrolls the
24
+ * content `direction` (`down` reveals what is below, so the finger travels up). Spec swipes, spec `swipe`/`scroll` steps
25
+ * and `warden ui scroll` use it as is; `warden ui gesture swipe <dir>` names the finger's direction instead and flips it
26
+ * (`fingerPath` in the CLI).
27
+ */
28
+ export declare function contentPath(direction: SwipeDirection, rect: Rect, fraction: number): {
29
+ from: Point;
30
+ to: Point;
31
+ };
32
+ export declare function screenRect(screen: ScreenInfo): Rect;
33
+ /** The part of `frame` that is on the screen (the whole frame when the screen size is unknown). */
34
+ export declare function visibleRect(frame: Rect, screen: ScreenInfo): Rect;
35
+ /** Whether a point is on the screen (an unknown screen size accepts everything). */
36
+ export declare function isOnScreen(point: Point, screen: ScreenInfo): boolean;
37
+ /**
38
+ * What a text field holds. Secure fields hold nothing readable; iOS reports the placeholder as the value of an empty
39
+ * field; Android keeps an input's text in `text`.
40
+ */
41
+ export declare function fieldValue(entry: NodeEntry): string;
42
+ /** What one read says a field holds: nothing, text, or (secure fields) a number of hidden characters. */
43
+ export type FieldContent = {
44
+ kind: "empty";
45
+ } | {
46
+ kind: "text";
47
+ text: string;
48
+ } | {
49
+ kind: "dots";
50
+ count: number;
51
+ };
52
+ /**
53
+ * What `entry` holds, for deciding how to empty it. A placeholder the element names is `empty` (see `fieldValue`),
54
+ * and so is a secure field showing anything but dots. iOS reports `placeholder: null` for React Native inputs, so a
55
+ * plain field showing its placeholder cannot be told from one holding that text and reads as `text`.
56
+ */
57
+ export declare function fieldContent(entry: NodeEntry): FieldContent;
58
+ /**
59
+ * Backspaces that empty a field once the caret is at its end: one per character plus a spare. Secure fields are
60
+ * counted by their dots. The fallback for a driver without select-all, and the retry when select-all did not empty
61
+ * the field.
62
+ */
63
+ export declare function clearKeyPresses(entry: NodeEntry): number;
64
+ /** Default length of a locator or screen pinch. */
65
+ export declare const PINCH_MS = 500;
66
+ /** Default length of a locator or screen rotate. */
67
+ export declare const ROTATE_MS = 500;
68
+ /** Default length of a `screen.gesture.path`. */
69
+ export declare const PATH_MS = 600;
70
+ /** The scales a pinch accepts (exactly 1 is no gesture and is rejected separately). */
71
+ export declare const SCALE_RANGE: readonly [0.1, 10];
72
+ /** The most degrees a rotate accepts either way: two turns. */
73
+ export declare const MAX_ROTATE_DEGREES = 720;
74
+ /** The middle of `rect`. */
75
+ export declare function centreOf(rect: Rect): Point;
76
+ /**
77
+ * The finger distance from the centre a pinch starts at. Fingers end at `radius * scale`, so the larger of the two
78
+ * stays inside `REACH` of the rect's shorter half-side; the smaller never goes below 4% of the screen's shorter side.
79
+ * When both cannot hold (an extreme scale on a tiny rect) the gap wins, and the fingers may leave the rect.
80
+ */
81
+ export declare function pinchRadius(rect: Rect, scale: number, screen: ScreenInfo): number;
82
+ /** The finger distance a rotate uses: 30% of the rect's shorter side, kept between 4% and 30% of the screen's shorter side. */
83
+ export declare function rotateRadius(rect: Rect, screen: ScreenInfo): number;
84
+ /**
85
+ * Throws `INVALID_ARGUMENT` when the driver cannot do `cap`: `pinch` (pinch and rotate) needs `capabilities.pinch`,
86
+ * `path` needs `fingers` (default 1) or more of `capabilities.maxPointers`. Like `device.back()` on iOS, the error
87
+ * names the fix.
88
+ */
89
+ export declare function requireGesture(driver: Driver, cap: "pinch" | "path", verb: string, fingers?: number): void;
90
+ /** `INVALID_ARGUMENT` unless `scale` is a finite number in `SCALE_RANGE` other than 1. */
91
+ export declare function assertScale(scale: number): void;
92
+ /** `INVALID_ARGUMENT` unless `degrees` is finite, not 0 and within `MAX_ROTATE_DEGREES` either way. */
93
+ export declare function assertDegrees(degrees: number): void;
@@ -0,0 +1,134 @@
1
+ import type { DevtoolsFacet } from "../devtools/devtools.types";
2
+ import type { Driver, DriverError, DriverResult, Snapshot } from "../driver/driver.types";
3
+ import { E2eError } from "../errors";
4
+ import type { SpanOptions, SpanRunner } from "../runner/span";
5
+ import { type NodeIndex } from "./nodes";
6
+ import { type Clock, type PollResult, type PollStep } from "./poll";
7
+ import { type ReactMatch } from "./react-locator";
8
+ import type { TextMatch } from "./text";
9
+ /**
10
+ * What `screen` and `Locator` need from the test session. `Session` satisfies it as is; the optional members are
11
+ * filled in by the test wrapper when it exists (feature-detected, so queries work under a bare session too).
12
+ */
13
+ export type ScreenHost = {
14
+ driver(): Promise<Driver>;
15
+ env: {
16
+ actionTimeoutMs: number;
17
+ assertionTimeoutMs?: number;
18
+ };
19
+ /** live override of the action timeout (`test.use({ actionTimeout })`) */
20
+ actionTimeout?: () => number;
21
+ /** live override of the assertion timeout (`test.use({ assertionTimeout })`) */
22
+ assertionTimeout?: () => number;
23
+ /** the running attempt's deadline: every poll stops when it aborts */
24
+ signal?: AbortSignal;
25
+ /** records one reported step around an action */
26
+ step?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;
27
+ /** times a read (`read.isVisible`, …) as a trace-only `span`; absent = reads are not traced */
28
+ span?: SpanRunner;
29
+ /** polling time source; tests inject a fake */
30
+ clock?: Clock;
31
+ /**
32
+ * The running app's devtools, when there is one (a dev client with Metro). `getByTestId` falls back to the React
33
+ * component tree through it when the accessibility tree has no such node.
34
+ */
35
+ devtools?: () => Pick<DevtoolsFacet, "tree"> | undefined;
36
+ /**
37
+ * `test.use({ systemAlerts })`: answers the alert or sheet on top the way that option says (`device.alert("accept")`
38
+ * or `("dismiss")`) and resolves false when none could be answered. Reads and waits call it when the snapshot they
39
+ * already took shows one, so a screen without an alert costs nothing. Absent = alerts are left alone.
40
+ */
41
+ answerSystemAlert?: () => Promise<boolean>;
42
+ };
43
+ export type Observed = {
44
+ driver: Driver;
45
+ snapshot: Snapshot;
46
+ index: NodeIndex;
47
+ };
48
+ /** The look before this one, when the observation cache can tell (`ObserveOptions.withPrevious`). */
49
+ export type Earlier = {
50
+ /** the kept snapshot from before this look, with no input between the two; undefined when there is none */
51
+ previous: Observed | undefined;
52
+ /** ms from the end of the last input to the start of this look; undefined before any input */
53
+ sinceInputMs: number | undefined;
54
+ };
55
+ export type Observation = {
56
+ ok: true;
57
+ observed: Observed;
58
+ earlier?: Earlier;
59
+ } | {
60
+ ok: false;
61
+ error: DriverError;
62
+ };
63
+ /** What a look at the screen may do beyond reading it. */
64
+ export type ObserveOptions = {
65
+ /**
66
+ * May answer with the snapshot the last read kept (`driver/observation-cache.ts`: nothing was pressed since and it is
67
+ * young). Only the first poll of a wait asks for it; a later one reads the device, or a wait could spin on a tree
68
+ * that stopped being true. Default: no.
69
+ */
70
+ reuse?: boolean;
71
+ /** Answer a system alert the snapshot shows when `test.use({ systemAlerts })` says to. Default: yes. */
72
+ alerts?: boolean;
73
+ /** Also say what the look before this one was (`Observation.earlier`), for an action's stability rule. Default: no. */
74
+ withPrevious?: boolean;
75
+ };
76
+ export declare class Runtime {
77
+ readonly host: ScreenHost;
78
+ private reactCache;
79
+ /** the index of each snapshot, so a snapshot that several reads share is indexed once */
80
+ private readonly indexed;
81
+ constructor(host: ScreenHost);
82
+ get clock(): Clock;
83
+ actionTimeout(): number;
84
+ assertionTimeout(): number;
85
+ /** A deadline `timeoutMs` (default: the action timeout) from now; `remaining()` is what is left of it. */
86
+ budget(timeoutMs?: number): {
87
+ remaining(): number;
88
+ total: number;
89
+ };
90
+ step<T>(label: string, fn: () => Promise<T>): Promise<T>;
91
+ span<T>(op: string, fn: () => Promise<T>, options?: SpanOptions<T>): Promise<T>;
92
+ private indexOf;
93
+ /**
94
+ * One look at the device, indexed. A driver failure comes back as data so callers can retry the retryable. With
95
+ * `systemAlerts` on, an alert the screen shows is answered and the screen looked at again.
96
+ */
97
+ tryObserve(options?: ObserveOptions): Promise<Observation>;
98
+ /** What came before `look`, given the cache's answer from just before it was taken. */
99
+ private earlierThan;
100
+ /** Whether `snapshot` showed an alert that has now been answered. A screen without one costs no extra read. */
101
+ private answeredAlert;
102
+ /** Forgets the snapshot the last read kept: the next look reads the device. For a wait that makes its own reads between polls. */
103
+ forgetObservation(): Promise<void>;
104
+ /**
105
+ * Like `tryObserve`, but a driver failure is thrown. Retryable failures (a busy accessibility service, a stale
106
+ * helper) are retried within the action timeout first. The first look may reuse a kept snapshot (`reuse: false`
107
+ * opts out); the retries never do.
108
+ */
109
+ observe(options?: {
110
+ reuse?: boolean;
111
+ }): Promise<Observed>;
112
+ /**
113
+ * Components of the React tree whose testID matches, for `getByTestId` when the accessibility tree has none.
114
+ * Empty without devtools. Reads are throttled: inside the window the previous answer for the same query is reused.
115
+ */
116
+ reactMatches(testId: TextMatch, observed: Observed): Promise<ReactMatch[]>;
117
+ /**
118
+ * The interactive part of the tree as text, for failure messages. It indexes the snapshot again and renders up to
119
+ * `EXCERPT_LINES` lines, so call it only once an error is certain; keep the snapshot, not this text, while polling.
120
+ */
121
+ excerpt(snapshot: Snapshot): string;
122
+ /**
123
+ * `poll` on this runtime's clock and abort signal; an abort throws instead of returning. `intervalMs` is a fixed gap
124
+ * between attempts instead of the back-off schedule.
125
+ */
126
+ poll<T, S>(step: (attempt: number) => Promise<PollStep<T, S>> | PollStep<T, S>, timeoutMs: number, options?: {
127
+ intervalMs?: number;
128
+ }): Promise<Exclude<PollResult<T, S>, {
129
+ status: "aborted";
130
+ }>>;
131
+ /** Unwraps a driver call made for an action; a failure becomes `ACTION_FAILED` naming the driver's code. */
132
+ drive<T>(verb: string, result: Awaited<DriverResult<T>>): T;
133
+ }
134
+ export declare function driverFailure(verb: string, error: DriverError, code?: "DRIVER" | "ACTION_FAILED"): E2eError;