@delacour/warden-e2e 0.0.0-stage → 0.1.1

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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/chunk-qtkmfjbq.mjs +4585 -0
  3. package/index.d.ts +1 -0
  4. package/index.mjs +998 -0
  5. package/package.json +49 -4
  6. package/preload.d.ts +1 -0
  7. package/preload.mjs +5512 -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 +124 -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 +91 -0
  19. package/types/core/src/types.d.ts +106 -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 +82 -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/network.d.ts +33 -0
  33. package/types/e2e/src/devtools/network.script.d.ts +24 -0
  34. package/types/e2e/src/devtools/session.d.ts +82 -0
  35. package/types/e2e/src/devtools/tree.d.ts +23 -0
  36. package/types/e2e/src/devtools/tree.script.d.ts +33 -0
  37. package/types/e2e/src/driver/alerts.d.ts +29 -0
  38. package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
  39. package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
  40. package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
  41. package/types/e2e/src/driver/android/android-app.d.ts +32 -0
  42. package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
  43. package/types/e2e/src/driver/android/android-device.d.ts +44 -0
  44. package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
  45. package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
  46. package/types/e2e/src/driver/android/android-state.d.ts +69 -0
  47. package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
  48. package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
  49. package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
  50. package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
  51. package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
  52. package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
  53. package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
  54. package/types/e2e/src/driver/driver.types.d.ts +231 -0
  55. package/types/e2e/src/driver/errors.d.ts +13 -0
  56. package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
  57. package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
  58. package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
  59. package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
  60. package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
  61. package/types/e2e/src/driver/ios/simctl.d.ts +87 -0
  62. package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
  63. package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
  64. package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
  65. package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
  66. package/types/e2e/src/driver/observation-cache.d.ts +66 -0
  67. package/types/e2e/src/driver/open.d.ts +29 -0
  68. package/types/e2e/src/driver/settle.d.ts +43 -0
  69. package/types/e2e/src/driver/span-detail.d.ts +12 -0
  70. package/types/e2e/src/driver/traced-driver.d.ts +14 -0
  71. package/types/e2e/src/driver/typing.d.ts +48 -0
  72. package/types/e2e/src/driver/unwrap.d.ts +13 -0
  73. package/types/e2e/src/env.d.ts +61 -0
  74. package/types/e2e/src/errors.d.ts +26 -0
  75. package/types/e2e/src/expect/expect.d.ts +27 -0
  76. package/types/e2e/src/expect/matchers.d.ts +72 -0
  77. package/types/e2e/src/fixture-session.d.ts +34 -0
  78. package/types/e2e/src/fixtures.d.ts +14 -0
  79. package/types/e2e/src/index.d.ts +8 -0
  80. package/types/e2e/src/preload.d.ts +29 -0
  81. package/types/e2e/src/runner/artifacts.d.ts +13 -0
  82. package/types/e2e/src/runner/attempt.d.ts +63 -0
  83. package/types/e2e/src/runner/callsite.d.ts +2 -0
  84. package/types/e2e/src/runner/failure.d.ts +28 -0
  85. package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
  86. package/types/e2e/src/runner/results.d.ts +48 -0
  87. package/types/e2e/src/runner/scope.d.ts +62 -0
  88. package/types/e2e/src/runner/span.d.ts +40 -0
  89. package/types/e2e/src/runner/step.d.ts +7 -0
  90. package/types/e2e/src/runner/test.d.ts +52 -0
  91. package/types/e2e/src/runner/trace.d.ts +87 -0
  92. package/types/e2e/src/screen/actions.d.ts +93 -0
  93. package/types/e2e/src/screen/context.d.ts +134 -0
  94. package/types/e2e/src/screen/field.d.ts +121 -0
  95. package/types/e2e/src/screen/locator.d.ts +233 -0
  96. package/types/e2e/src/screen/nodes.d.ts +53 -0
  97. package/types/e2e/src/screen/poll.d.ts +57 -0
  98. package/types/e2e/src/screen/queries.d.ts +34 -0
  99. package/types/e2e/src/screen/query.d.ts +60 -0
  100. package/types/e2e/src/screen/react-locator.d.ts +30 -0
  101. package/types/e2e/src/screen/render.d.ts +42 -0
  102. package/types/e2e/src/screen/roles.d.ts +24 -0
  103. package/types/e2e/src/screen/screen.d.ts +61 -0
  104. package/types/e2e/src/screen/selector.d.ts +38 -0
  105. package/types/e2e/src/screen/text.d.ts +13 -0
  106. package/types/e2e/src/session.d.ts +105 -0
  107. package/types/e2e/src/traced-device.d.ts +9 -0
  108. package/types/e2e/src/version.d.ts +7 -0
  109. package/types/types/src/result.d.ts +12 -0
  110. package/README.md +0 -3
@@ -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,52 @@
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
+ /** a `test` whose bodies also receive `Extra` */
35
+ extend<Extra extends object>(defs: FixtureDefs<F, Extra>): TestApi<F & Extra>;
36
+ };
37
+ /** The spec `test`. Fixtures: `{ app, device, screen, platform }`. */
38
+ export declare const test: TestApi<BaseFixtures>;
39
+ export type DescribeOptions = {
40
+ /** accepted for API parity: bun runs a file's tests one after another unless `--concurrent` */
41
+ serial?: boolean;
42
+ };
43
+ export declare function describe(title: string, body: () => void): void;
44
+ export declare function describe(title: string, options: DescribeOptions, body: () => void): void;
45
+ /** Runs before every test in this scope, outermost scope first; receives the test's `{ app, device, screen, platform }`. */
46
+ export declare function beforeEach(hook: Hook): void;
47
+ /** Runs after every test in this scope, innermost scope first, even when the test failed. */
48
+ export declare function afterEach(hook: Hook): void;
49
+ /** Once per file/`describe`, before its first test; receives the base fixtures (not the `extend` ones). */
50
+ export declare function beforeAll(hook: Hook): void;
51
+ /** Once per file/`describe`, after its last test. */
52
+ 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, flow `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;
@@ -0,0 +1,121 @@
1
+ import type { Driver, DriverError, DriverResult, Point } from "../driver/driver.types";
2
+ import { type Sleep } from "../driver/typing";
3
+ import { type NodeEntry } from "./nodes";
4
+ /**
5
+ * Reading back what a text field holds after typing, shared by the spec `fill` and `warden ui fill`.
6
+ *
7
+ * The accessibility value lags the keys by 100-200 ms (the app digests ~50 ms a character), and a controlled input
8
+ * can lose a key under load. So a read is never trusted on its own: the field must hold the wanted text on
9
+ * `CONFIRM_READS` consecutive reads `FIELD_POLL_MS` apart (a stale value from before the edit can equal the wanted
10
+ * text once), and the wait extends while the value is still changing.
11
+ */
12
+ export declare const FIELD_POLL_MS = 50;
13
+ /** While the value keeps changing, wait at least this long for its next change. */
14
+ export declare const FIELD_PROGRESS_MS = 1000;
15
+ /** Consecutive reads that must equal the wanted text. */
16
+ export declare const CONFIRM_READS = 2;
17
+ /** When the field already held the wanted text before the edit, a stale read can match: trust only a longer run. */
18
+ export declare const STALE_RISK_READS = 4;
19
+ /**
20
+ * Consecutive matching reads to trust for `text` typed into `entry` (the field as seen before the edit): the longer
21
+ * `STALE_RISK_READS` only when it already held that text. A placeholder shown as the value (one the element names, or a
22
+ * secure field's) is not text it held.
23
+ */
24
+ export declare function confirmReadsFor(entry: NodeEntry | undefined, text: string): number;
25
+ export type FieldReading = {
26
+ kind: "value";
27
+ value: string;
28
+ }
29
+ /** the field could not be read this time (gone, ambiguous, driver busy); `note` is what a failure message shows */
30
+ | {
31
+ kind: "unreadable";
32
+ note: string;
33
+ };
34
+ export type FieldOutcome<E> = {
35
+ status: "confirmed";
36
+ } | {
37
+ status: "mismatch";
38
+ held: string;
39
+ } | {
40
+ status: "failed";
41
+ error: E;
42
+ };
43
+ export type ConfirmRequest<E> = {
44
+ /** one look at the field; `{ ok: false }` ends the wait at once */
45
+ read: () => Promise<{
46
+ ok: true;
47
+ reading: FieldReading;
48
+ } | {
49
+ ok: false;
50
+ error: E;
51
+ }>;
52
+ text: string;
53
+ now: () => number;
54
+ sleep: (ms: number) => Promise<void>;
55
+ /** how long to keep looking when nothing changes */
56
+ budgetMs: number;
57
+ /** consecutive matching reads to trust; default `CONFIRM_READS` */
58
+ reads?: number;
59
+ /** hard cap including the extension for progress */
60
+ maxMs?: number;
61
+ };
62
+ /** Polls the field until it holds `text` on consecutive reads, or the budget (extended while it changes) runs out. */
63
+ export declare function confirmFieldValue<E>(request: ConfirmRequest<E>): Promise<FieldOutcome<E>>;
64
+ /** What the field behind `wanted` shows right now by a hit-test at its centre (about 10 ms, unlike a full snapshot), or undefined when that cannot tell. */
65
+ export declare function hitTestField(driver: Driver, wanted: NodeEntry): Promise<{
66
+ ok: true;
67
+ entry: NodeEntry | undefined;
68
+ } | {
69
+ ok: false;
70
+ error: DriverError;
71
+ }>;
72
+ /** Gap between the hit-tests that wait for a tapped field to take focus. */
73
+ export declare const FOCUS_POLL_MS = 20;
74
+ /**
75
+ * Most hit-tests (~10 ms each, `FOCUS_POLL_MS` apart) spent waiting for a tapped field to report focus: about 0.75 s.
76
+ * Measured on a simulator: focus shows 70-250 ms after the tap, the keyboard coming up included.
77
+ */
78
+ export declare const FOCUS_POLLS = 25;
79
+ /**
80
+ * Taps `point` to focus the text field `entry` and, on iOS, returns once the field reports focus (a hit-test at
81
+ * `point`, every `FOCUS_POLL_MS`, at most `FOCUS_POLLS` times). Keys sent while focus is still moving reach a React
82
+ * Native controlled input during its focus re-render and are lost or reordered ("Hllo", "lol, World 123!H"): 5 in 400
83
+ * tap-then-type runs, none in 300 that waited for focus (85-112 ms) or in 200 that re-tapped the field already focused. So a field
84
+ * that already had focus, Android (`warden-uiad` types into the focused node itself), a bare coordinate and a
85
+ * hit-test that cannot tell (failed, or another element there, e.g. content that moved up for the keyboard) are not
86
+ * waited on.
87
+ */
88
+ export declare function tapToFocus(driver: Driver, sleep: Sleep, point: Point, entry: NodeEntry | undefined): DriverResult<void>;
89
+ /**
90
+ * How a field is emptied. `auto` selects everything and presses one Backspace when the driver can select all, else
91
+ * presses one Backspace per character; `counted` always presses one per character (the retry after select-all left
92
+ * text behind, e.g. a field that refuses Cmd+A). `setText` is `auto`, except that `focusClearType` replaces a plain
93
+ * filled field in one `setText` call where the driver has it (Android `warden-uiad`): opt-in, see `SETTEXT_ENV`.
94
+ */
95
+ export type ClearMethod = "auto" | "counted" | "setText";
96
+ /**
97
+ * `WARDEN_ANDROID_SETTEXT=1` makes `auto` behave as `setText` in `focusClearType`. Off by default: the server's
98
+ * `setText` writes to the node its accessibility cache says has focus, which can still be the field filled before
99
+ * (it does not re-read focus the way typing and select-all do), so a quick fill of a second field could overwrite the first.
100
+ */
101
+ export declare const SETTEXT_ENV = "WARDEN_ANDROID_SETTEXT";
102
+ /** Whether `env` opts `auto` fills into `setText` (`SETTEXT_ENV`). */
103
+ export declare function setTextOptedIn(env: Readonly<Record<string, string | undefined>>): boolean;
104
+ /**
105
+ * Taps `point` and empties what `entry` shows: select-all and one Backspace when the driver offers it, else one
106
+ * Backspace per character (see `focusAndSelect`). The clear routine behind the spec `clear` and `warden ui clear`.
107
+ */
108
+ export declare function focusAndClear(driver: Driver, sleep: Sleep, point: Point, entry: NodeEntry | undefined, method?: ClearMethod): DriverResult<void>;
109
+ /**
110
+ * Empties the field as `focusAndClear` does, then types `text` (nothing for the empty string). A selected text is not
111
+ * deleted first: the first typed character replaces the whole selection, which saves a Backspace. If the select-all
112
+ * did not take, the old text stays and the typed text lands after it; the confirm read sees that and the retry uses the
113
+ * counted method.
114
+ *
115
+ * With the `setText` method (or `auto` under `WARDEN_ANDROID_SETTEXT=1`, read from `env`), on a driver with `setText`
116
+ * (Android `warden-uiad`) a plain field that holds text is replaced in one call after the focus tap, instead of
117
+ * select-all then typing; the read-back after a `fill` checks it took, and the counted retry types. An empty field gains
118
+ * nothing from it, and a secure one cannot be read back, so both are typed as before. A `setText` that fails falls back
119
+ * to select-all and typing, without a second tap.
120
+ */
121
+ export declare function focusClearType(driver: Driver, sleep: Sleep, point: Point, entry: NodeEntry | undefined, text: string, method?: ClearMethod, env?: Readonly<Record<string, string | undefined>>): DriverResult<void>;