@delacour/warden-e2e 0.13.0 → 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.
- package/{chunk-tnr47n9c.mjs → chunk-zk3wxa70.mjs} +552 -229
- package/index.mjs +113 -12
- package/package.json +2 -2
- package/preload.mjs +72 -34
- package/types/e2e/src/driver/bridge-stall.d.ts +37 -0
- package/types/e2e/src/driver/driver.types.d.ts +5 -1
- package/types/e2e/src/driver/ios/ios-driver.d.ts +10 -0
- package/types/e2e/src/driver/ios/simd-client.d.ts +2 -0
- package/types/e2e/src/driver/ios/simd-protocol.d.ts +30 -0
- package/types/e2e/src/driver/settle.d.ts +8 -0
- package/types/e2e/src/errors.d.ts +3 -1
- package/types/e2e/src/expect/matchers.d.ts +5 -0
- package/types/e2e/src/runner/bridge-evidence.d.ts +12 -0
- package/types/e2e/src/runner/results.d.ts +29 -0
- package/types/e2e/src/screen/context.d.ts +8 -11
- package/types/e2e/src/screen/degraded-read.d.ts +23 -0
- package/types/e2e/src/screen/locator.d.ts +46 -1
- package/types/e2e/src/screen/react-locator.d.ts +17 -2
- package/types/e2e/src/screen/react-merge.d.ts +10 -0
|
@@ -33,6 +33,14 @@ export declare function settleDetailOf(outcome: SettleOutcome): SettleDetail;
|
|
|
33
33
|
export declare const DEFAULT_SETTLE_QUIET_MS = 250;
|
|
34
34
|
/** How long a settle waits for quiet before it gives up. */
|
|
35
35
|
export declare const DEFAULT_SETTLE_TIMEOUT_MS = 3000;
|
|
36
|
+
/**
|
|
37
|
+
* The shortest window a snapshot-hash settle fallback waits (iOS, and `device.settle` on a driver without one). A navigation push exposes both screens at their final
|
|
38
|
+
* frames for 300-460 ms before the old one leaves the tree, so two equal reads 250 ms apart can settle on that mixed
|
|
39
|
+
* tree. The helper's own snapshot fallback (`FrameStability.minQuietMs`) uses the same floor.
|
|
40
|
+
*/
|
|
41
|
+
export declare const HASH_FALLBACK_MIN_QUIET_MS = 500;
|
|
42
|
+
/** The quiet window a hash fallback uses for a caller's `quietMs`. */
|
|
43
|
+
export declare function hashFallbackQuietMs(quietMs: number): number;
|
|
36
44
|
/**
|
|
37
45
|
* Snapshot-hash settle: poll a UI fingerprint until it stays unchanged for `quietMs`
|
|
38
46
|
* (two consecutive equal hashes and ≥ quietMs since the last change), or give up at `timeoutMs`.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ErrorResult } from "../../types/src/result";
|
|
2
|
-
export type E2eErrorCode = "LOCATOR_NOT_FOUND" | "LOCATOR_AMBIGUOUS" | "INVALID_LOCATOR" | "ACTION_FAILED" | "TIMEOUT" | "NOT_ACTIONABLE" | "INVALID_ARGUMENT" | "SESSION" | "DRIVER" | "ASSERTION" | "PERF_BUDGET" | "RUN_FAILED"
|
|
2
|
+
export type E2eErrorCode = "LOCATOR_NOT_FOUND" | "LOCATOR_AMBIGUOUS" | "INVALID_LOCATOR" | "ACTION_FAILED" | "TIMEOUT" | "NOT_ACTIONABLE" | "INVALID_ARGUMENT" | "SESSION" | "DRIVER" | "ASSERTION" | "PERF_BUDGET" | "RUN_FAILED"
|
|
3
|
+
/** a read that fell back from its first reader (or came back an empty root) could not confirm the locator either way */
|
|
4
|
+
| "READ_DEGRADED";
|
|
3
5
|
/** Plain shape of a spec error: what `e2eErr()` accepts and what a `Result` carries. */
|
|
4
6
|
export type E2eErrorShape = {
|
|
5
7
|
code: E2eErrorCode;
|
|
@@ -81,6 +81,11 @@ export declare class LocatorAssertions {
|
|
|
81
81
|
toHaveCount(expected: number, options?: AssertionOptions): Promise<void>;
|
|
82
82
|
private assert;
|
|
83
83
|
private poll;
|
|
84
|
+
/**
|
|
85
|
+
* What one look says about `spec`: a judgement (from the accessibility tree, or from the React tree for a degraded
|
|
86
|
+
* read it answered), or `unsettled` when the read was degraded and nothing could say whether the locator is there.
|
|
87
|
+
*/
|
|
88
|
+
private judgeLook;
|
|
84
89
|
/** The screen is rendered here, from the last snapshot read, because only a failure needs the text. */
|
|
85
90
|
private failure;
|
|
86
91
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TestResult } from "./results";
|
|
2
|
+
import type { TraceEvent } from "./trace";
|
|
3
|
+
export type BridgeEvidence = NonNullable<TestResult["bridge"]>;
|
|
4
|
+
/**
|
|
5
|
+
* Collects an attempt's iOS bridge fallbacks from its spans for `results.jsonl` (`bridge`): reads answered by `ax`
|
|
6
|
+
* in place of `axbridge`, and failed reads that carried a stall report. A read the bridge answered by pid (frontmost
|
|
7
|
+
* unresolved) is not one.
|
|
8
|
+
*/
|
|
9
|
+
export declare function bridgeEvidence(): {
|
|
10
|
+
observe(event: TraceEvent): void;
|
|
11
|
+
summary(): BridgeEvidence | undefined;
|
|
12
|
+
};
|
|
@@ -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
|
|
@@ -78,8 +78,6 @@ export declare class Runtime {
|
|
|
78
78
|
private reactCache;
|
|
79
79
|
/** the index of each snapshot, so a snapshot that several reads share is indexed once */
|
|
80
80
|
private readonly indexed;
|
|
81
|
-
/** the time before which a degraded look is not retried on its first reader, after that reader failed to answer */
|
|
82
|
-
private firstReaderRetryAt;
|
|
83
81
|
constructor(host: ScreenHost);
|
|
84
82
|
get clock(): Clock;
|
|
85
83
|
actionTimeout(): number;
|
|
@@ -97,14 +95,6 @@ export declare class Runtime {
|
|
|
97
95
|
* `systemAlerts` on, an alert the screen shows is answered and the screen looked at again.
|
|
98
96
|
*/
|
|
99
97
|
tryObserve(options?: ObserveOptions): Promise<Observation>;
|
|
100
|
-
/**
|
|
101
|
-
* A snapshot the driver answered from a fallback reader (`Snapshot.fellBackFrom`, iOS `ax` after `axbridge` stalled)
|
|
102
|
-
* can lack what the first reader has: React Native testIDs on static text. Such a look is read once more on the
|
|
103
|
-
* first reader, and that read replaces it when it answers. When it does not (the guest is still silent) the
|
|
104
|
-
* fallback snapshot stands and the first reader is left alone for `FIRST_READER_RETRY_MS`, so a dead guest costs a
|
|
105
|
-
* poll one deadline, not one per attempt. Every locator and matcher looks through here, so none needs its own rule.
|
|
106
|
-
*/
|
|
107
|
-
private restoreDegraded;
|
|
108
98
|
/** What came before `look`, given the cache's answer from just before it was taken. */
|
|
109
99
|
private earlierThan;
|
|
110
100
|
/** Whether `snapshot` showed an alert that has now been answered. A screen without one costs no extra read. */
|
|
@@ -124,6 +114,13 @@ export declare class Runtime {
|
|
|
124
114
|
* Empty without devtools. Reads are throttled: inside the window the previous answer for the same query is reused.
|
|
125
115
|
*/
|
|
126
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>;
|
|
127
124
|
/**
|
|
128
125
|
* The interactive part of the tree as text, for failure messages. It indexes the snapshot again and renders up to
|
|
129
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
|
-
|
|
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)
|
|
23
|
-
*
|
|
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;
|