@delacour/warden-e2e 0.12.1 → 0.14.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.
@@ -26,6 +26,35 @@ export declare const testResultSchema: z.ZodObject<{
26
26
  dir: z.ZodString;
27
27
  files: z.ZodArray<z.ZodString>;
28
28
  }, z.core.$strip>>;
29
+ bridge: z.ZodOptional<z.ZodObject<{
30
+ fallbacks: z.ZodNumber;
31
+ first: z.ZodObject<{
32
+ reason: z.ZodOptional<z.ZodString>;
33
+ code: z.ZodOptional<z.ZodString>;
34
+ stage: z.ZodOptional<z.ZodString>;
35
+ readMs: z.ZodOptional<z.ZodNumber>;
36
+ deadlineMs: z.ZodOptional<z.ZodNumber>;
37
+ guestAgeMs: z.ZodOptional<z.ZodNumber>;
38
+ cold: z.ZodOptional<z.ZodBoolean>;
39
+ cooldownRemainingMs: z.ZodOptional<z.ZodNumber>;
40
+ probes: z.ZodOptional<z.ZodNumber>;
41
+ recycles: z.ZodOptional<z.ZodNumber>;
42
+ recyclesExhausted: z.ZodOptional<z.ZodBoolean>;
43
+ }, z.core.$strip>;
44
+ last: z.ZodObject<{
45
+ reason: z.ZodOptional<z.ZodString>;
46
+ code: z.ZodOptional<z.ZodString>;
47
+ stage: z.ZodOptional<z.ZodString>;
48
+ readMs: z.ZodOptional<z.ZodNumber>;
49
+ deadlineMs: z.ZodOptional<z.ZodNumber>;
50
+ guestAgeMs: z.ZodOptional<z.ZodNumber>;
51
+ cold: z.ZodOptional<z.ZodBoolean>;
52
+ cooldownRemainingMs: z.ZodOptional<z.ZodNumber>;
53
+ probes: z.ZodOptional<z.ZodNumber>;
54
+ recycles: z.ZodOptional<z.ZodNumber>;
55
+ recyclesExhausted: z.ZodOptional<z.ZodBoolean>;
56
+ }, z.core.$strip>;
57
+ }, z.core.$strip>>;
29
58
  device: z.ZodObject<{
30
59
  udid: z.ZodString;
31
60
  platform: z.ZodEnum<{
@@ -4,7 +4,7 @@ import { E2eError } from "../errors";
4
4
  import type { SpanOptions, SpanRunner } from "../runner/span";
5
5
  import { type NodeIndex } from "./nodes";
6
6
  import { type Clock, type PollResult, type PollStep } from "./poll";
7
- import { type ReactMatch } from "./react-locator";
7
+ import { type ReactLookup, type ReactMatch } from "./react-locator";
8
8
  import type { TextMatch } from "./text";
9
9
  /**
10
10
  * What `screen` and `Locator` need from the test session. `Session` satisfies it as is; the optional members are
@@ -114,6 +114,13 @@ export declare class Runtime {
114
114
  * Empty without devtools. Reads are throttled: inside the window the previous answer for the same query is reused.
115
115
  */
116
116
  reactMatches(testId: TextMatch, observed: Observed): Promise<ReactMatch[]>;
117
+ /** Whether the host has devtools to ask the React tree through. */
118
+ hasReactTree(): boolean;
119
+ /**
120
+ * `reactMatches`, keeping "the React tree could not be read" (`answered: false`, also without devtools) apart from
121
+ * "it has no such component", for a degraded read that must not take the first for the second.
122
+ */
123
+ reactLookup(testId: TextMatch, observed: Observed): Promise<ReactLookup>;
117
124
  /**
118
125
  * The interactive part of the tree as text, for failure messages. It indexes the snapshot again and renders up to
119
126
  * `EXCERPT_LINES` lines, so call it only once an error is certain; keep the snapshot, not this text, while polling.
@@ -0,0 +1,23 @@
1
+ import type { Snapshot, SnapshotBackend } from "../driver/driver.types";
2
+ import { E2eError } from "../errors";
3
+ /**
4
+ * Why a snapshot cannot say a node is absent: it was answered by a fallback reader (`Snapshot.fellBackFrom`, iOS `ax`
5
+ * after `axbridge` stalled, which drops React Native testIDs on static text), or it is one childless root with no
6
+ * area (the reader could not see into the app). A match found on such a snapshot is real; a missing one is unknown.
7
+ */
8
+ export type DegradedRead = {
9
+ kind: "fallback";
10
+ from: SnapshotBackend;
11
+ backend: SnapshotBackend;
12
+ } | {
13
+ kind: "emptyRoot";
14
+ backend: SnapshotBackend;
15
+ };
16
+ /** What makes `snapshot` inconclusive about absence; undefined for a whole read. */
17
+ export declare function degradedRead(snapshot: Snapshot): DegradedRead | undefined;
18
+ /** One phrase naming what was degraded, with the fallback and read backend. */
19
+ export declare function describeDegraded(read: DegradedRead): string;
20
+ /** Why the React tree did not settle it. */
21
+ export type ReactNote = "unanswered" | "no-devtools" | "not-testid";
22
+ /** `READ_DEGRADED`: the bridge is degraded and `locator` could be confirmed neither present nor absent in `waitedMs`. */
23
+ export declare function degradedFailure(locator: string, read: DegradedRead, waitedMs: number, react: ReactNote, screen?: string): E2eError;
@@ -1,10 +1,13 @@
1
1
  import type { KeyName, Modifier, Point, Rect } from "../driver/driver.types";
2
+ import { E2eError } from "../errors";
2
3
  import { type Momentum, type SwipeDirection } from "./actions";
3
4
  import type { Observed, Runtime } from "./context";
5
+ import { type DegradedRead, type ReactNote } from "./degraded-read";
4
6
  import { type FillVia } from "./field";
5
7
  import type { NodeEntry } from "./nodes";
6
8
  import { type RoleOptions, type TestIdOptions, type TextQueryOptions } from "./queries";
7
9
  import { type LocatorExpression } from "./query";
10
+ import { type ReactMatch } from "./react-locator";
8
11
  import type { QueryRole } from "./roles";
9
12
  import { type TextMatch } from "./text";
10
13
  export type ActionOptions = {
@@ -69,6 +72,21 @@ export type WaitState = "attached" | "detached" | "visible" | "hidden";
69
72
  export type WaitForOptions = ActionOptions & {
70
73
  state?: WaitState;
71
74
  };
75
+ /**
76
+ * What one look says about a locator. `nodes`: the accessibility tree's answer (none included, when the read was whole
77
+ * or the React tree confirmed the absence). `react`: the read was degraded and the React tree has the component(s).
78
+ * `unknown`: the read was degraded (`screen/degraded-read.ts`) and nothing could confirm the locator either way.
79
+ */
80
+ export type Resolved = {
81
+ kind: "nodes";
82
+ entries: NodeEntry[];
83
+ } | {
84
+ kind: "react";
85
+ matches: ReactMatch[];
86
+ } | {
87
+ kind: "unknown";
88
+ read: DegradedRead;
89
+ };
72
90
  declare const LOCATOR_BRAND: unique symbol;
73
91
  /** Whether `value` is a `Locator` (also across duplicated copies of this module, e.g. the bundled entry and the preload). */
74
92
  export declare function isLocator(value: unknown): value is Locator;
@@ -102,7 +120,28 @@ export declare class Locator {
102
120
  entries: NodeEntry[];
103
121
  observed: Observed;
104
122
  }>;
123
+ /**
124
+ * What `observed` says about this locator. A whole read, or one with matches, is the accessibility tree's answer. A
125
+ * degraded read with no match is asked of the React tree (plain `getByTestId` with devtools): it answers presence or
126
+ * absence; otherwise the look is `unknown`, and no caller may take it as "absent". A component whose rect is an
127
+ * accessibility node's (the fallback reader kept the node, dropped the testID) resolves to that node, so text,
128
+ * visibility and actions read it like any match; one with no such node stays a React-only match.
129
+ */
130
+ resolveIn(observed: Observed): Promise<Resolved>;
131
+ /** Why the React tree did not settle a degraded look, for the `READ_DEGRADED` message. */
132
+ reactNote(): ReactNote;
133
+ /** `READ_DEGRADED` for this locator after `waitedMs`, with the screen of the last degraded look. */
134
+ degradedFailure(read: DegradedRead, waitedMs: number, snapshot: Observed["snapshot"] | undefined): E2eError;
135
+ /**
136
+ * One look that settles this locator. A whole read answers at once (never waits); a degraded one that cannot say
137
+ * whether the locator is there is looked at again until one can, within the action timeout, then `READ_DEGRADED`.
138
+ */
139
+ private conclusive;
140
+ /** The conclusive answer of `observed`, or undefined while it is `unknown`. */
141
+ private settled;
105
142
  private matches;
143
+ /** The one React match a read of a component the accessibility tree lacks stands for; several are ambiguous. */
144
+ private reactOnly;
106
145
  private single;
107
146
  /** Components of the React tree that this locator names, when it is a plain `getByTestId` and the app has devtools. */
108
147
  private reactCandidates;
@@ -113,6 +152,8 @@ export declare class Locator {
113
152
  all(): Promise<Locator[]>;
114
153
  text(): Promise<string>;
115
154
  allTexts(): Promise<string[]>;
155
+ /** The text the React tree reported for a component of a degraded read that no accessibility node stands for. */
156
+ private reactText;
116
157
  value(): Promise<string>;
117
158
  isVisible(): Promise<boolean>;
118
159
  /**
@@ -124,7 +165,11 @@ export declare class Locator {
124
165
  */
125
166
  read(): Promise<LocatorReading>;
126
167
  /** Whether this locator's one match is visible in `observed`; no match is not visible, several matches throw. */
127
- visibleIn(observed: Observed): boolean;
168
+ /**
169
+ * Whether `observed` shows this locator, without waiting: a degraded read is resolved like `resolveIn` (the React
170
+ * tree's node or rect), and one nothing could settle counts as not shown yet.
171
+ */
172
+ shownIn(observed: Observed): Promise<boolean>;
128
173
  isHidden(): Promise<boolean>;
129
174
  isEnabled(): Promise<boolean>;
130
175
  isChecked(): Promise<boolean>;
@@ -11,17 +11,32 @@ export type ReactMatch = {
11
11
  testId: string;
12
12
  /** the component's name (`View`, `Button`, ...) */
13
13
  name: string;
14
+ /** the strings rendered inside it, when the React tree reported any */
15
+ text?: string;
14
16
  };
15
17
  export type ReactView = {
16
18
  platform: Platform;
17
19
  screen: ScreenInfo;
18
20
  };
21
+ /** What the React tree said: `answered: false` when it could not be read (no devtools, a failed or timed-out read). */
22
+ export type ReactLookup = {
23
+ answered: true;
24
+ matches: ReactMatch[];
25
+ } | {
26
+ answered: false;
27
+ };
19
28
  /**
20
29
  * Components of the running app whose `testID` matches, with rects in the driver's pixels. This is the fallback for
21
30
  * `getByTestId` when the platform hides the view from accessibility (React Native only exposes `testID` to
22
- * accessibility on some views). Any devtools failure means "nothing found", so callers keep their own not-found
23
- * error. Android measures in dp but the driver taps in px: `view.screen.scale` converts.
31
+ * accessibility on some views), and the second opinion on a degraded read. A devtools failure is `answered: false`,
32
+ * kept apart from an empty answer so a caller can tell "absent" from "cannot tell". Android measures in dp but the
33
+ * driver taps in px: `view.screen.scale` converts.
24
34
  */
35
+ export declare function lookUpTestIdViaReact(facet: Pick<DevtoolsFacet, "tree">, testId: TextMatch, view: ReactView, options?: {
36
+ screenName?: string;
37
+ timeoutMs?: number;
38
+ }): Promise<ReactLookup>;
39
+ /** `lookUpTestIdViaReact` where any failure means "nothing found", so callers keep their own not-found error. */
25
40
  export declare function locateByTestIdViaReact(facet: Pick<DevtoolsFacet, "tree">, testId: TextMatch, view: ReactView, options?: {
26
41
  screenName?: string;
27
42
  timeoutMs?: number;
@@ -0,0 +1,10 @@
1
+ import type { NodeEntry, NodeIndex } from "./nodes";
2
+ import type { ReactMatch } from "./react-locator";
3
+ /**
4
+ * The accessibility node a React component stands for on a degraded read: a fallback reader (iOS host `ax`) keeps the
5
+ * node but drops its testID, so the component's rect is what ties the two. Never a node that carries another testID,
6
+ * never an area-less rect. Several nodes with the rect: the one whose own text is the component's, else the deepest.
7
+ */
8
+ export declare function nodeOfReactMatch(match: ReactMatch, index: NodeIndex): NodeEntry | undefined;
9
+ /** The node of every match, each a different node; undefined when one of them has none (the React tree answers alone). */
10
+ export declare function mergeReactMatches(matches: readonly ReactMatch[], index: NodeIndex): NodeEntry[] | undefined;
@@ -123,5 +123,7 @@ export type AttemptBinding = {
123
123
  export declare function attemptSession(session: Session, binding: () => AttemptBinding): Session;
124
124
  /** The process-wide session (`globalThis[Symbol.for("./session")]`), created on first call. */
125
125
  export declare function getSession(opts?: SessionOptions): Session;
126
+ /** The process-wide session if one exists; never creates one (so a caller outside a test opens no device). */
127
+ export declare function peekSession(): Session | undefined;
126
128
  /** Closes and forgets the process-wide session, if any. */
127
129
  export declare function resetSession(): Promise<void>;