@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.
- package/LICENSE +21 -0
- package/chunk-qtkmfjbq.mjs +4585 -0
- package/index.d.ts +1 -0
- package/index.mjs +998 -0
- package/package.json +49 -4
- package/preload.d.ts +1 -0
- package/preload.mjs +5512 -0
- package/types/core/src/allocate.d.ts +23 -0
- package/types/core/src/builds/app-option.schema.d.ts +37 -0
- package/types/core/src/builds/install.d.ts +95 -0
- package/types/core/src/builds/resolve.d.ts +74 -0
- package/types/core/src/config.defaults.d.ts +25 -0
- package/types/core/src/exec.d.ts +18 -0
- package/types/core/src/liveness.d.ts +12 -0
- package/types/core/src/providers/android.d.ts +124 -0
- package/types/core/src/providers/health.d.ts +45 -0
- package/types/core/src/providers/provider.types.d.ts +56 -0
- package/types/core/src/store.d.ts +91 -0
- package/types/core/src/types.d.ts +106 -0
- package/types/e2e/src/alert-buttons.d.ts +41 -0
- package/types/e2e/src/alert-reader.d.ts +25 -0
- package/types/e2e/src/app.d.ts +62 -0
- package/types/e2e/src/device-devtools.d.ts +26 -0
- package/types/e2e/src/device-link.d.ts +43 -0
- package/types/e2e/src/device.d.ts +82 -0
- package/types/e2e/src/devtools/cdp.d.ts +65 -0
- package/types/e2e/src/devtools/console.d.ts +59 -0
- package/types/e2e/src/devtools/devtools.types.d.ts +169 -0
- package/types/e2e/src/devtools/facet.d.ts +28 -0
- package/types/e2e/src/devtools/inspector.d.ts +48 -0
- package/types/e2e/src/devtools/jsonl.d.ts +4 -0
- package/types/e2e/src/devtools/network.d.ts +33 -0
- package/types/e2e/src/devtools/network.script.d.ts +24 -0
- package/types/e2e/src/devtools/session.d.ts +82 -0
- package/types/e2e/src/devtools/tree.d.ts +23 -0
- package/types/e2e/src/devtools/tree.script.d.ts +33 -0
- package/types/e2e/src/driver/alerts.d.ts +29 -0
- package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
- package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
- package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
- package/types/e2e/src/driver/android/android-app.d.ts +32 -0
- package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
- package/types/e2e/src/driver/android/android-device.d.ts +44 -0
- package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
- package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
- package/types/e2e/src/driver/android/android-state.d.ts +69 -0
- package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
- package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
- package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
- package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
- package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
- package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
- package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
- package/types/e2e/src/driver/driver.types.d.ts +231 -0
- package/types/e2e/src/driver/errors.d.ts +13 -0
- package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
- package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
- package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
- package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
- package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
- package/types/e2e/src/driver/ios/simctl.d.ts +87 -0
- package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
- package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
- package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
- package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
- package/types/e2e/src/driver/observation-cache.d.ts +66 -0
- package/types/e2e/src/driver/open.d.ts +29 -0
- package/types/e2e/src/driver/settle.d.ts +43 -0
- package/types/e2e/src/driver/span-detail.d.ts +12 -0
- package/types/e2e/src/driver/traced-driver.d.ts +14 -0
- package/types/e2e/src/driver/typing.d.ts +48 -0
- package/types/e2e/src/driver/unwrap.d.ts +13 -0
- package/types/e2e/src/env.d.ts +61 -0
- package/types/e2e/src/errors.d.ts +26 -0
- package/types/e2e/src/expect/expect.d.ts +27 -0
- package/types/e2e/src/expect/matchers.d.ts +72 -0
- package/types/e2e/src/fixture-session.d.ts +34 -0
- package/types/e2e/src/fixtures.d.ts +14 -0
- package/types/e2e/src/index.d.ts +8 -0
- package/types/e2e/src/preload.d.ts +29 -0
- package/types/e2e/src/runner/artifacts.d.ts +13 -0
- package/types/e2e/src/runner/attempt.d.ts +63 -0
- package/types/e2e/src/runner/callsite.d.ts +2 -0
- package/types/e2e/src/runner/failure.d.ts +28 -0
- package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
- package/types/e2e/src/runner/results.d.ts +48 -0
- package/types/e2e/src/runner/scope.d.ts +62 -0
- package/types/e2e/src/runner/span.d.ts +40 -0
- package/types/e2e/src/runner/step.d.ts +7 -0
- package/types/e2e/src/runner/test.d.ts +52 -0
- package/types/e2e/src/runner/trace.d.ts +87 -0
- package/types/e2e/src/screen/actions.d.ts +93 -0
- package/types/e2e/src/screen/context.d.ts +134 -0
- package/types/e2e/src/screen/field.d.ts +121 -0
- package/types/e2e/src/screen/locator.d.ts +233 -0
- package/types/e2e/src/screen/nodes.d.ts +53 -0
- package/types/e2e/src/screen/poll.d.ts +57 -0
- package/types/e2e/src/screen/queries.d.ts +34 -0
- package/types/e2e/src/screen/query.d.ts +60 -0
- package/types/e2e/src/screen/react-locator.d.ts +30 -0
- package/types/e2e/src/screen/render.d.ts +42 -0
- package/types/e2e/src/screen/roles.d.ts +24 -0
- package/types/e2e/src/screen/screen.d.ts +61 -0
- package/types/e2e/src/screen/selector.d.ts +38 -0
- package/types/e2e/src/screen/text.d.ts +13 -0
- package/types/e2e/src/session.d.ts +105 -0
- package/types/e2e/src/traced-device.d.ts +9 -0
- package/types/e2e/src/version.d.ts +7 -0
- package/types/types/src/result.d.ts +12 -0
- package/README.md +0 -3
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { KeyName, Modifier, Point, Rect } from "../driver/driver.types";
|
|
2
|
+
import { type Momentum, type SwipeDirection } from "./actions";
|
|
3
|
+
import type { Observed, Runtime } from "./context";
|
|
4
|
+
import type { NodeEntry } from "./nodes";
|
|
5
|
+
import { type RoleOptions, type TestIdOptions, type TextQueryOptions } from "./queries";
|
|
6
|
+
import { type LocatorExpression } from "./query";
|
|
7
|
+
import type { QueryRole } from "./roles";
|
|
8
|
+
import { type TextMatch } from "./text";
|
|
9
|
+
export type ActionOptions = {
|
|
10
|
+
timeout?: number;
|
|
11
|
+
};
|
|
12
|
+
/** What `locator.read()` saw in one look: no match is just `{ visible: false }`. */
|
|
13
|
+
export type LocatorReading = {
|
|
14
|
+
visible: boolean;
|
|
15
|
+
/** what `text()` returns; absent when nothing matched */
|
|
16
|
+
text?: string;
|
|
17
|
+
/** what `value()` returns; absent when nothing matched */
|
|
18
|
+
value?: string;
|
|
19
|
+
/** only for a node that can be checked (a switch, a checkbox) */
|
|
20
|
+
checked?: boolean;
|
|
21
|
+
};
|
|
22
|
+
/** What shows a tap landed: a locator that becomes visible, or a check that resolves true. */
|
|
23
|
+
export type TapUntil = Locator | (() => Promise<boolean>);
|
|
24
|
+
export type TapOptions = ActionOptions & {
|
|
25
|
+
position?: Point;
|
|
26
|
+
/**
|
|
27
|
+
* Tap again when this does not show up. A locator counts once it is visible; a function once it resolves true (its
|
|
28
|
+
* reads share one snapshot per check). After the tap and after each re-tap the wait is `windowMs`, except that the
|
|
29
|
+
* last re-tap is not waited on: like a hand-rolled tap-and-check helper, `tap` returns and the next assertion says
|
|
30
|
+
* whether it landed. With a locator, the screen is also compared with the one the press aimed from: once it
|
|
31
|
+
* changed (a spinner, a disabled button, the target gone) the press landed and is never sent again, however late
|
|
32
|
+
* `until` is; a screen that has not changed at all is pressed again from 750 ms on, without waiting out `windowMs`.
|
|
33
|
+
*/
|
|
34
|
+
until?: TapUntil;
|
|
35
|
+
/** extra taps when `until` has not shown within `windowMs` of the previous tap. Default 2. Needs `until`. */
|
|
36
|
+
retries?: number;
|
|
37
|
+
/** how long each tap gets to show `until`. Default 4000 ms. Needs `until`. */
|
|
38
|
+
windowMs?: number;
|
|
39
|
+
/** gap between checks of `until`. Default 250 ms. Needs `until`. */
|
|
40
|
+
pollMs?: number;
|
|
41
|
+
};
|
|
42
|
+
export type LongPressOptions = ActionOptions & {
|
|
43
|
+
durationMs?: number;
|
|
44
|
+
};
|
|
45
|
+
export type PressOptions = ActionOptions & {
|
|
46
|
+
modifiers?: readonly Modifier[];
|
|
47
|
+
};
|
|
48
|
+
export type LocatorSwipeOptions = ActionOptions & {
|
|
49
|
+
direction: SwipeDirection;
|
|
50
|
+
momentum?: Momentum;
|
|
51
|
+
};
|
|
52
|
+
/** A pinch or rotate: `radius` is each finger's distance from the centre at the start, in points. */
|
|
53
|
+
export type PinchOptions = ActionOptions & {
|
|
54
|
+
durationMs?: number;
|
|
55
|
+
radius?: number;
|
|
56
|
+
};
|
|
57
|
+
export type RotateOptions = PinchOptions;
|
|
58
|
+
export type ScrollOptions = ActionOptions & {
|
|
59
|
+
direction?: SwipeDirection;
|
|
60
|
+
momentum?: Momentum;
|
|
61
|
+
maxSwipes?: number;
|
|
62
|
+
};
|
|
63
|
+
export type WaitState = "attached" | "detached" | "visible" | "hidden";
|
|
64
|
+
export type WaitForOptions = ActionOptions & {
|
|
65
|
+
state?: WaitState;
|
|
66
|
+
};
|
|
67
|
+
declare const LOCATOR_BRAND: unique symbol;
|
|
68
|
+
/** Whether `value` is a `Locator` (also across duplicated copies of this module, e.g. the bundled entry and the preload). */
|
|
69
|
+
export declare function isLocator(value: unknown): value is Locator;
|
|
70
|
+
export declare class Locator {
|
|
71
|
+
readonly expr: LocatorExpression;
|
|
72
|
+
private readonly rt;
|
|
73
|
+
readonly [LOCATOR_BRAND] = true;
|
|
74
|
+
constructor(expr: LocatorExpression, rt: Runtime);
|
|
75
|
+
toString(): string;
|
|
76
|
+
private scoped;
|
|
77
|
+
getByTestId(id: TextMatch, options?: TestIdOptions): Locator;
|
|
78
|
+
getByText(text: TextMatch, options?: TextQueryOptions): Locator;
|
|
79
|
+
getByLabel(text: TextMatch, options?: TextQueryOptions): Locator;
|
|
80
|
+
getByPlaceholder(text: TextMatch, options?: TextQueryOptions): Locator;
|
|
81
|
+
getByValue(value: TextMatch, options?: TextQueryOptions): Locator;
|
|
82
|
+
getByRole(role: QueryRole, nameOrOptions?: TextMatch | RoleOptions, options?: Omit<RoleOptions, "name">): Locator;
|
|
83
|
+
locator(selector: string): Locator;
|
|
84
|
+
filter(options: {
|
|
85
|
+
hasText?: TextMatch;
|
|
86
|
+
has?: Locator;
|
|
87
|
+
}): Locator;
|
|
88
|
+
first(): Locator;
|
|
89
|
+
last(): Locator;
|
|
90
|
+
nth(index: number): Locator;
|
|
91
|
+
/** The runtime (host, timeouts, clock) this locator runs on; the `expect` matchers poll through it. */
|
|
92
|
+
get runtime(): Runtime;
|
|
93
|
+
/** One look at the screen: every current match and the snapshot they came from. Never waits. */
|
|
94
|
+
look(options?: {
|
|
95
|
+
reuse?: boolean;
|
|
96
|
+
}): Promise<{
|
|
97
|
+
entries: NodeEntry[];
|
|
98
|
+
observed: Observed;
|
|
99
|
+
}>;
|
|
100
|
+
private matches;
|
|
101
|
+
private single;
|
|
102
|
+
/** Components of the React tree that this locator names, when it is a plain `getByTestId` and the app has devtools. */
|
|
103
|
+
private reactCandidates;
|
|
104
|
+
private exactlyOne;
|
|
105
|
+
/** One read as a trace-only `read.<name>` span naming this locator; reads that call another read are not wrapped twice. */
|
|
106
|
+
private traced;
|
|
107
|
+
count(): Promise<number>;
|
|
108
|
+
all(): Promise<Locator[]>;
|
|
109
|
+
text(): Promise<string>;
|
|
110
|
+
allTexts(): Promise<string[]>;
|
|
111
|
+
value(): Promise<string>;
|
|
112
|
+
isVisible(): Promise<boolean>;
|
|
113
|
+
/**
|
|
114
|
+
* Visibility, text, value and checked state from ONE look at the screen: where `isVisible()` then `text()` read the
|
|
115
|
+
* screen for each (sharing a snapshot only when nothing was pressed in between and it is still young), this is one
|
|
116
|
+
* read by construction. No match is `{ visible: false }` and never waits; several matches fail like `text()`.
|
|
117
|
+
* `text` is what `text()` returns, `value` what `value()` returns, `checked` is there only for a node that can be
|
|
118
|
+
* checked (a switch, a checkbox).
|
|
119
|
+
*/
|
|
120
|
+
read(): Promise<LocatorReading>;
|
|
121
|
+
/** Whether this locator's one match is visible in `observed`; no match is not visible, several matches throw. */
|
|
122
|
+
visibleIn(observed: Observed): boolean;
|
|
123
|
+
isHidden(): Promise<boolean>;
|
|
124
|
+
isEnabled(): Promise<boolean>;
|
|
125
|
+
isChecked(): Promise<boolean>;
|
|
126
|
+
/** Always a new snapshot: a frame is for aiming at, and one from a screen that was still moving is wrong. */
|
|
127
|
+
boundingBox(): Promise<Rect>;
|
|
128
|
+
/** Polls until the locator is `visible` (default), `hidden`, `attached` or `detached`; else `TIMEOUT`. */
|
|
129
|
+
waitFor(options?: WaitForOptions): Promise<void>;
|
|
130
|
+
private waitForState;
|
|
131
|
+
private act;
|
|
132
|
+
/**
|
|
133
|
+
* Waits for exactly one visible (and, per `spec`, enabled, on-screen) match. Several matches fail at once.
|
|
134
|
+
* With `allowReact` a component that only the React tree knows (a plain `getByTestId`) counts as a match.
|
|
135
|
+
* Every look here is a new snapshot, never the one a read or an `expect` kept: the action goes to where the element
|
|
136
|
+
* is now, and a snapshot taken while the screen was still sliding in has it somewhere else. (A plain `tap` on a
|
|
137
|
+
* driver with a cheap hit-test first tries `aimFromKept`, which checks the kept snapshot with a hit-test instead.)
|
|
138
|
+
* A fresh snapshot can also be of a screen still moving, so a match is taken only once its frame held still across
|
|
139
|
+
* two looks with no input between them (Playwright's "stable"): the look a read or an `expect` kept, when there is
|
|
140
|
+
* one, costs nothing; with none, a look less than `STABLE_INPUT_GAP_MS` after an input is checked by one more. An
|
|
141
|
+
* element that never stops moving is taken after `MAX_STABILITY_LOOKS` extra looks.
|
|
142
|
+
*/
|
|
143
|
+
private actionable;
|
|
144
|
+
/** One look of `actionable`: done, not yet, or `moving` (the match has not held still yet; look again at once). */
|
|
145
|
+
private actionableLook;
|
|
146
|
+
/**
|
|
147
|
+
* Whether `entry`'s frame, to 1 pt, is where the look before this one had this locator's one match. Without that look:
|
|
148
|
+
* still unless an input ended less than `STABLE_INPUT_GAP_MS` before this one. No observation cache = still (no history).
|
|
149
|
+
*/
|
|
150
|
+
private heldStill;
|
|
151
|
+
/**
|
|
152
|
+
* Where a hit-test is cheap (`capabilities.cheapHitTest`, iOS), the tap after an assertion need not take a second
|
|
153
|
+
* snapshot: when the observation cache holds one it would still reuse (younger than `OBSERVATION_MAX_AGE_MS` for the
|
|
154
|
+
* platform) with no input since, its one
|
|
155
|
+
* actionable match is hit-tested at the tap point, and the match is taken as is when the hit is that element (same
|
|
156
|
+
* role, identifier, label, enabled state and frame to 1 pt). That hit is also the second look of the stable-frame
|
|
157
|
+
* rule: the element is where the snapshot had it. Anything else (no such snapshot, an alert on it, several or no
|
|
158
|
+
* matches, a blocked match, a hit on a child or a neighbour, a failed hit-test) is undefined: `actionable` looks again.
|
|
159
|
+
* Never with system alerts answered (`answerSystemAlert`, `test.use({ systemAlerts })`): an alert that came up after
|
|
160
|
+
* the kept snapshot sits outside the app, where the app-scoped hit-test may not see it, and the fresh look answers it.
|
|
161
|
+
*/
|
|
162
|
+
private aimFromKept;
|
|
163
|
+
/**
|
|
164
|
+
* Taps the one visible, enabled match. With `until` it is also the retrying tap: the press on a tab bar or a list row
|
|
165
|
+
* right after a navigation is sometimes dropped with nothing on screen to tell, so it checks that `until` shows
|
|
166
|
+
* within `windowMs` and presses again, up to `retries` more times.
|
|
167
|
+
*/
|
|
168
|
+
tap(options?: TapOptions): Promise<void>;
|
|
169
|
+
/**
|
|
170
|
+
* One press; resolves to the snapshot it aimed from (the screen just before the press). A plain tap may aim from the
|
|
171
|
+
* kept snapshot (`aimFromKept`); a tap with `until` takes a fresh look, since the screen it aimed from is what the
|
|
172
|
+
* checks compare against to tell the press landed, and an older one could differ only because it was older.
|
|
173
|
+
*/
|
|
174
|
+
private tapOnce;
|
|
175
|
+
/**
|
|
176
|
+
* Polls `plan.until` for `plan.windowMs`, one look at the screen per check. A locator is checked on the snapshot of
|
|
177
|
+
* the poll itself, which is also compared with `pressedOn` (the hash of the screen the press aimed from): once the
|
|
178
|
+
* screen changed the press landed, so it is never sent again (`landed`, even when `until` is late); a screen still
|
|
179
|
+
* unchanged on `UNCHANGED_CHECKS` checks in a row from `UNCHANGED_AFTER_MS` on is pressed again at once. A function
|
|
180
|
+
* is called with the kept snapshot forgotten (after the first check), so its own reads take one new snapshot
|
|
181
|
+
* between them rather than the previous check's; it gets the whole window.
|
|
182
|
+
*/
|
|
183
|
+
private arrivedWithin;
|
|
184
|
+
/** One check of a locator `until`: shown, the press looks dropped (`press-again`), or not yet (noting whether the screen moved). */
|
|
185
|
+
private checkLocatorUntil;
|
|
186
|
+
longPress(options?: LongPressOptions): Promise<void>;
|
|
187
|
+
/**
|
|
188
|
+
* Replaces the content of a text field: focus with the caret at the end, empty it (select all and type over the selection when
|
|
189
|
+
* the driver can, nothing for a field that is empty or shows a placeholder), type, then poll until the field holds
|
|
190
|
+
* the text on consecutive reads (the accessibility value lags the keys). A mismatch is retried once with one Backspace
|
|
191
|
+
* per character and a retype (a field that refuses select-all). Secure fields cannot be read back, so they are not
|
|
192
|
+
* verified.
|
|
193
|
+
*/
|
|
194
|
+
fill(text: string, options?: ActionOptions): Promise<void>;
|
|
195
|
+
/** Empties a text field (select all and one Backspace when the driver can), then checks it is empty (one more pass of counted Backspaces when it is not). */
|
|
196
|
+
clear(options?: ActionOptions): Promise<void>;
|
|
197
|
+
/** Types at the caret; the field is tapped (caret to the end) first unless it already has focus. */
|
|
198
|
+
type(text: string, options?: ActionOptions): Promise<void>;
|
|
199
|
+
press(key: KeyName, options?: PressOptions): Promise<void>;
|
|
200
|
+
/** A scroll gesture on this one node, in the direction the content moves. */
|
|
201
|
+
swipe(options: LocatorSwipeOptions): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* A two-finger pinch centred on this node's visible part: `scale` above 1 spreads the fingers (zoom in), below 1
|
|
204
|
+
* brings them together. `INVALID_ARGUMENT` for a scale outside 0.1-10 or exactly 1, or on a driver without pinch.
|
|
205
|
+
*/
|
|
206
|
+
pinch(scale: number, options?: PinchOptions): Promise<void>;
|
|
207
|
+
/** A two-finger rotation about this node's visible centre; positive `degrees` turn clockwise. */
|
|
208
|
+
rotate(degrees: number, options?: RotateOptions): Promise<void>;
|
|
209
|
+
/** Scrolls the screen until this locator is on screen. */
|
|
210
|
+
scrollTo(options?: ScrollOptions): Promise<void>;
|
|
211
|
+
/** One poll step when the accessibility tree has no match: ask the React tree (plain `getByTestId` only). */
|
|
212
|
+
private reactStep;
|
|
213
|
+
private focus;
|
|
214
|
+
private focusAndClear;
|
|
215
|
+
/** Sleeps on the runtime's clock (a fake one in tests), ending early when the attempt aborts. */
|
|
216
|
+
private readonly pause;
|
|
217
|
+
/**
|
|
218
|
+
* Polls until the field holds `text` on consecutive reads. Each read is a hit-test at the field's centre (a full
|
|
219
|
+
* snapshot with the keyboard up takes 300-1000 ms and slows the app that is digesting the keys), falling back to a
|
|
220
|
+
* scoped re-query when the hit-test cannot tell. The deadline is `VERIFY_MS`, extended while the value changes.
|
|
221
|
+
*/
|
|
222
|
+
private confirm;
|
|
223
|
+
}
|
|
224
|
+
/** The strict one-match rule; the error carries the rendered screen, which is only built when there is an error. */
|
|
225
|
+
export declare function pickOne(rt: Runtime, expr: LocatorExpression, entries: readonly NodeEntry[], observed: Observed): NodeEntry;
|
|
226
|
+
/** Display text: the accessible name, else the text of everything inside. */
|
|
227
|
+
export declare function textOf(entry: NodeEntry): string;
|
|
228
|
+
/**
|
|
229
|
+
* Swipes the screen in `direction` (the way the content moves) until `target` has exactly one match that is on screen.
|
|
230
|
+
* Gives up after `maxSwipes` swipes or the action timeout.
|
|
231
|
+
*/
|
|
232
|
+
export declare function scrollUntilVisible(rt: Runtime, target: Locator, options?: ScrollOptions): Promise<void>;
|
|
233
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Element, ScreenInfo, Snapshot } from "../driver/driver.types";
|
|
2
|
+
import { type QueryRole } from "./roles";
|
|
3
|
+
export type NodeStates = {
|
|
4
|
+
/** undefined when the node is not checkable */
|
|
5
|
+
checked: boolean | undefined;
|
|
6
|
+
expanded: boolean | undefined;
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
selected: boolean;
|
|
9
|
+
focused: boolean;
|
|
10
|
+
/** the driver's `hidden` flag, inherited from ancestors; queries use this, `visible` adds geometry */
|
|
11
|
+
hidden: boolean;
|
|
12
|
+
};
|
|
13
|
+
/** One accessibility node with everything queries need derived once per snapshot. */
|
|
14
|
+
export type NodeEntry = {
|
|
15
|
+
/** index into `NodeIndex.entries` (document order) */
|
|
16
|
+
position: number;
|
|
17
|
+
element: Element;
|
|
18
|
+
depth: number;
|
|
19
|
+
parent: NodeEntry | undefined;
|
|
20
|
+
children: NodeEntry[];
|
|
21
|
+
role: QueryRole;
|
|
22
|
+
/** accessible name: label, else text */
|
|
23
|
+
name: string | undefined;
|
|
24
|
+
/** displayed value with React Native role prefixes removed; never set for secure fields */
|
|
25
|
+
value: string | undefined;
|
|
26
|
+
states: NodeStates;
|
|
27
|
+
secure: boolean;
|
|
28
|
+
/** not hidden, non-empty frame, intersecting the screen */
|
|
29
|
+
visible: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Normalised label/text/value of this node and its descendants, echoes removed, in document order. Worked out on the
|
|
32
|
+
* first read (and kept), because indexing every node's subtree costs nodes x depth and most reads never ask.
|
|
33
|
+
*/
|
|
34
|
+
readonly fragments: readonly string[];
|
|
35
|
+
/** `fragments` joined with a space; also worked out on first read */
|
|
36
|
+
readonly subtreeText: string;
|
|
37
|
+
};
|
|
38
|
+
export type NodeIndex = {
|
|
39
|
+
entries: readonly NodeEntry[];
|
|
40
|
+
byId: ReadonlyMap<string, NodeEntry>;
|
|
41
|
+
screen: ScreenInfo;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Flattens a snapshot into document-order entries with parent/child links and derived state. Subtree text is not built
|
|
45
|
+
* here: `fragments` and `subtreeText` compute on first read.
|
|
46
|
+
*/
|
|
47
|
+
export declare function indexSnapshot(snapshot: Snapshot): NodeIndex;
|
|
48
|
+
/** True when `entry` is a strict descendant of `ancestor`. */
|
|
49
|
+
export declare function isWithin(entry: NodeEntry, ancestor: NodeEntry): boolean;
|
|
50
|
+
/** Strict descendants of any of `ancestors` among `universe`, in the universe's order. */
|
|
51
|
+
export declare function descendantsOf(universe: readonly NodeEntry[], ancestors: readonly NodeEntry[]): NodeEntry[];
|
|
52
|
+
/** The snapshot entry of one element (its role, name, value and secure flag), for rules that need more than the `Element`. */
|
|
53
|
+
export declare function entryOfElement(element: Element, template: Pick<Snapshot, "platform">): NodeEntry | undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Time source for polling; tests inject `fakeClock()` so nothing really waits. */
|
|
2
|
+
export type Clock = {
|
|
3
|
+
now(): number;
|
|
4
|
+
/** resolves after `ms`, or early when `signal` aborts */
|
|
5
|
+
sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
export declare const systemClock: Clock;
|
|
8
|
+
export declare const DEFAULT_POLL_INTERVAL_MS = 100;
|
|
9
|
+
/**
|
|
10
|
+
* Gap between attempts: `fastMs` while the poll is younger than `fastForMs`, `slowMs` after. Every attempt is a read of
|
|
11
|
+
* the device (a full accessibility walk inside the app, or a 1.9 s `uiautomator dump` on Android), so a wait that has
|
|
12
|
+
* not been satisfied for a second is not worth 10 reads a second.
|
|
13
|
+
*/
|
|
14
|
+
export type PollSchedule = {
|
|
15
|
+
fastMs: number;
|
|
16
|
+
fastForMs: number;
|
|
17
|
+
slowMs: number;
|
|
18
|
+
};
|
|
19
|
+
export declare const DEFAULT_POLL_SCHEDULE: PollSchedule;
|
|
20
|
+
/** One attempt: finished with a value, or not yet (with whatever the caller wants to report if time runs out). */
|
|
21
|
+
export type PollStep<T, S> = {
|
|
22
|
+
done: true;
|
|
23
|
+
value: T;
|
|
24
|
+
} | {
|
|
25
|
+
done: false;
|
|
26
|
+
state: S;
|
|
27
|
+
/** wake no later than this many ms from now, even when the schedule says to wait longer (a hold window ending) */
|
|
28
|
+
maxWaitMs?: number;
|
|
29
|
+
};
|
|
30
|
+
export type PollResult<T, S> = {
|
|
31
|
+
status: "done";
|
|
32
|
+
value: T;
|
|
33
|
+
attempts: number;
|
|
34
|
+
} | {
|
|
35
|
+
status: "timeout";
|
|
36
|
+
last: S | undefined;
|
|
37
|
+
attempts: number;
|
|
38
|
+
} | {
|
|
39
|
+
status: "aborted";
|
|
40
|
+
attempts: number;
|
|
41
|
+
};
|
|
42
|
+
export type PollOptions = {
|
|
43
|
+
/** a fixed gap between attempts, with no back-off; wins over `schedule` */
|
|
44
|
+
intervalMs?: number;
|
|
45
|
+
/** default `DEFAULT_POLL_SCHEDULE`: 100 ms for the first second, then 250 ms */
|
|
46
|
+
schedule?: PollSchedule;
|
|
47
|
+
timeoutMs: number;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
now?: Clock["now"];
|
|
50
|
+
sleep?: Clock["sleep"];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Runs `step` until it is done, `timeoutMs` pass, or `signal` aborts. Always tries at least once (unless already
|
|
54
|
+
* aborted); never sleeps past the deadline, so the last attempt is at the deadline. The gap follows `intervalMs`, else
|
|
55
|
+
* `schedule` measured on the clock (a slow step reaches the slow gap sooner). Errors thrown by `step` end the poll.
|
|
56
|
+
*/
|
|
57
|
+
export declare function poll<T, S = undefined>(step: (attempt: number) => Promise<PollStep<T, S>> | PollStep<T, S>, options: PollOptions): Promise<PollResult<T, S>>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LocatorExpression, Query, RoleQuery } from "./query";
|
|
2
|
+
import { type QueryRole } from "./roles";
|
|
3
|
+
import type { TextMatch } from "./text";
|
|
4
|
+
/** Pure constructors for the `getBy*` family, shared by `screen` and `Locator` (which scopes them to its subtree). */
|
|
5
|
+
export type TextQueryOptions = {
|
|
6
|
+
exact?: boolean;
|
|
7
|
+
visible?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type TestIdOptions = {
|
|
10
|
+
visible?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type RoleOptions = {
|
|
13
|
+
name?: TextMatch;
|
|
14
|
+
exact?: boolean;
|
|
15
|
+
checked?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
selected?: boolean;
|
|
18
|
+
visible?: boolean;
|
|
19
|
+
};
|
|
20
|
+
export declare function testIdQuery(id: TextMatch, options?: TestIdOptions): Query;
|
|
21
|
+
declare const METHOD: {
|
|
22
|
+
readonly text: "getByText";
|
|
23
|
+
readonly label: "getByLabel";
|
|
24
|
+
readonly placeholder: "getByPlaceholder";
|
|
25
|
+
readonly value: "getByValue";
|
|
26
|
+
};
|
|
27
|
+
export declare function textQuery(kind: keyof typeof METHOD, value: TextMatch, options?: TextQueryOptions): Query;
|
|
28
|
+
/** `getByRole("button", "Save", { checked })` and `getByRole("button", { name: "Save", checked })` are the same query. */
|
|
29
|
+
export declare function roleQuery(role: QueryRole, nameOrOptions?: TextMatch | RoleOptions, options?: Omit<RoleOptions, "name">): RoleQuery;
|
|
30
|
+
/** `locator("id=x role=button")`: the agent-CLI selector grammar as a locator expression. */
|
|
31
|
+
export declare function selectorExpression(text: string): LocatorExpression;
|
|
32
|
+
/** Runs `expr` inside the subtree(s) of `scope` (below any scope `expr` already has). */
|
|
33
|
+
export declare function withScope(expr: LocatorExpression, scope: LocatorExpression): LocatorExpression;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type Result } from "../../../types/src/result";
|
|
2
|
+
import { type E2eError } from "../errors";
|
|
3
|
+
import { type NodeEntry, type NodeIndex } from "./nodes";
|
|
4
|
+
import type { QueryRole } from "./roles";
|
|
5
|
+
import { type ParsedSelector } from "./selector";
|
|
6
|
+
import { type TextMatch } from "./text";
|
|
7
|
+
/** Every query accepts `visible: true`, which drops hidden nodes before the exactly-one rule runs. */
|
|
8
|
+
type Visibility = {
|
|
9
|
+
visible?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type RoleQuery = {
|
|
12
|
+
kind: "role";
|
|
13
|
+
role: QueryRole;
|
|
14
|
+
name?: TextMatch;
|
|
15
|
+
exact?: boolean;
|
|
16
|
+
checked?: boolean;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
selected?: boolean;
|
|
19
|
+
} & Visibility;
|
|
20
|
+
export type Query = ({
|
|
21
|
+
kind: "testId";
|
|
22
|
+
value: TextMatch;
|
|
23
|
+
} & Visibility) | ({
|
|
24
|
+
kind: "text" | "label" | "placeholder" | "value";
|
|
25
|
+
value: TextMatch;
|
|
26
|
+
exact?: boolean;
|
|
27
|
+
} & Visibility) | RoleQuery | ({
|
|
28
|
+
kind: "selector";
|
|
29
|
+
selector: ParsedSelector;
|
|
30
|
+
} & Visibility);
|
|
31
|
+
/**
|
|
32
|
+
* A lazy locator: what to look for, never where it is. Evaluated against one `NodeIndex` by `resolve`.
|
|
33
|
+
* `query` optionally runs inside the subtree(s) of `scope`; `filter` refines; `index` picks one.
|
|
34
|
+
*/
|
|
35
|
+
export type LocatorExpression = {
|
|
36
|
+
kind: "query";
|
|
37
|
+
query: Query;
|
|
38
|
+
scope?: LocatorExpression;
|
|
39
|
+
} | {
|
|
40
|
+
kind: "filter";
|
|
41
|
+
source: LocatorExpression;
|
|
42
|
+
hasText?: TextMatch;
|
|
43
|
+
has?: LocatorExpression;
|
|
44
|
+
} | {
|
|
45
|
+
kind: "index";
|
|
46
|
+
source: LocatorExpression;
|
|
47
|
+
index: "first" | "last" | number;
|
|
48
|
+
};
|
|
49
|
+
/** Every node the expression currently matches, in document order. One immediate pass: no waiting, no strictness. */
|
|
50
|
+
export declare function resolve(expr: LocatorExpression, index: NodeIndex): NodeEntry[];
|
|
51
|
+
/**
|
|
52
|
+
* The strict one-match rule for actions and reads: zero matches is `LOCATOR_NOT_FOUND`, several is `LOCATOR_AMBIGUOUS`.
|
|
53
|
+
* `options.screen` is attached to the error (the rendered tree helps a failure report).
|
|
54
|
+
*/
|
|
55
|
+
export declare function exactlyOne(expr: LocatorExpression, matches: readonly NodeEntry[], options?: {
|
|
56
|
+
screen?: string;
|
|
57
|
+
}): Result<NodeEntry, E2eError>;
|
|
58
|
+
/** A spec-API style description of the expression (`getByRole("button", "Save").first()`), for error messages. */
|
|
59
|
+
export declare function describeExpression(expr: LocatorExpression): string;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DevtoolsFacet } from "../devtools/devtools.types";
|
|
2
|
+
import type { Platform, Point, Rect, ScreenInfo } from "../driver/driver.types";
|
|
3
|
+
import { type TextMatch } from "./text";
|
|
4
|
+
/** A component found by `testID` in the React tree, where the accessibility tree had nothing. */
|
|
5
|
+
export type ReactMatch = {
|
|
6
|
+
/** window rect in the driver's pixels */
|
|
7
|
+
frame: Rect;
|
|
8
|
+
/** centre of `frame` */
|
|
9
|
+
tapPoint: Point;
|
|
10
|
+
source: "react";
|
|
11
|
+
testId: string;
|
|
12
|
+
/** the component's name (`View`, `Button`, ...) */
|
|
13
|
+
name: string;
|
|
14
|
+
};
|
|
15
|
+
export type ReactView = {
|
|
16
|
+
platform: Platform;
|
|
17
|
+
screen: ScreenInfo;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Components of the running app whose `testID` matches, with rects in the driver's pixels. This is the fallback for
|
|
21
|
+
* `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.
|
|
24
|
+
*/
|
|
25
|
+
export declare function locateByTestIdViaReact(facet: Pick<DevtoolsFacet, "tree">, testId: TextMatch, view: ReactView, options?: {
|
|
26
|
+
screenName?: string;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
}): Promise<ReactMatch[]>;
|
|
29
|
+
/** Where a tap on `match` lands: `position` is an offset from the frame's top-left corner, else the centre. */
|
|
30
|
+
export declare function reactTapPoint(match: ReactMatch, position?: Point): Point;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Snapshot } from "../driver/driver.types";
|
|
2
|
+
import { type NodeEntry } from "./nodes";
|
|
3
|
+
export type RenderOptions = {
|
|
4
|
+
/** keep interactive roles plus any element with an identifier; hidden elements are dropped, no indentation */
|
|
5
|
+
interactiveOnly: boolean;
|
|
6
|
+
/** element id -> `@eN`; elements without an entry print without a ref */
|
|
7
|
+
refs: ReadonlyMap<string, string>;
|
|
8
|
+
/** cap on element lines; a `… N more (use --all)` line follows when cut */
|
|
9
|
+
maxLines?: number;
|
|
10
|
+
};
|
|
11
|
+
export type RenderedSnapshot = {
|
|
12
|
+
lines: string[];
|
|
13
|
+
/** element lines printed */
|
|
14
|
+
shown: number;
|
|
15
|
+
/** elements in the snapshot */
|
|
16
|
+
total: number;
|
|
17
|
+
/** elements cut by `maxLines` */
|
|
18
|
+
omitted: number;
|
|
19
|
+
};
|
|
20
|
+
/** `[button] "Sign in" id=sign-in-submit`: enough to tell matches apart in an error message. */
|
|
21
|
+
export declare function describeEntry(entry: NodeEntry): string;
|
|
22
|
+
/** `@e3 [button] "Sign in" id=sign-in-submit value="…" enabled hittable (24,332 345×48)`; `indent` is in levels. */
|
|
23
|
+
export declare function renderEntryLine(entry: NodeEntry, options?: {
|
|
24
|
+
ref?: string;
|
|
25
|
+
indent?: number;
|
|
26
|
+
}): string;
|
|
27
|
+
/** The entries `interactiveOnly` lists: visible ones that are interactive or carry a testID. */
|
|
28
|
+
export declare function isListed(entry: NodeEntry): boolean;
|
|
29
|
+
/** The `warden ui snapshot` body: one line per element in document order. */
|
|
30
|
+
export declare function renderSnapshot(snapshot: Snapshot, options: RenderOptions): RenderedSnapshot;
|
|
31
|
+
/**
|
|
32
|
+
* `# ios axbridge 390×844@3x gen 12 · 5 of 8 elements (interactive)` for spec failure artifacts; with `style: "cli"`
|
|
33
|
+
* the `warden ui snapshot` line, `generation 4 · axbridge · 393×852@3x · 5 of 41 elements` (`, truncated` at the end,
|
|
34
|
+
* the count is `41 elements` when nothing was cut).
|
|
35
|
+
*/
|
|
36
|
+
export declare function renderHeader(input: {
|
|
37
|
+
snapshot: Snapshot;
|
|
38
|
+
rendered: RenderedSnapshot;
|
|
39
|
+
interactiveOnly: boolean;
|
|
40
|
+
generation?: number;
|
|
41
|
+
style?: "spec" | "cli";
|
|
42
|
+
}): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Element } from "../driver/driver.types";
|
|
2
|
+
/** The role vocabulary `getByRole` and `role=` selectors speak. */
|
|
3
|
+
export declare const QUERY_ROLES: readonly ["button", "link", "textbox", "searchbox", "checkbox", "radio", "switch", "slider", "image", "heading", "text", "tab", "tablist", "menu", "menuitem", "list", "listitem", "row", "alert", "dialog", "navigation", "group", "other"];
|
|
4
|
+
export type QueryRole = (typeof QUERY_ROLES)[number];
|
|
5
|
+
export declare function isQueryRole(value: string): value is QueryRole;
|
|
6
|
+
/** Roles `snapshot -i` keeps: things a user can act on. */
|
|
7
|
+
export declare function isInteractiveRole(role: QueryRole): boolean;
|
|
8
|
+
export type SpelledValue = {
|
|
9
|
+
checked?: boolean;
|
|
10
|
+
expanded?: boolean;
|
|
11
|
+
role: QueryRole;
|
|
12
|
+
value: string | undefined;
|
|
13
|
+
};
|
|
14
|
+
/** Reads `<role description>[, <state>…][, <value>]` back; undefined for any value that does not start with a role. */
|
|
15
|
+
export declare function parseSpelledValue(value: string | undefined): SpelledValue | undefined;
|
|
16
|
+
export type ResolvedRole = {
|
|
17
|
+
role: QueryRole;
|
|
18
|
+
spelled?: SpelledValue;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The query role for one driver element. `parent` lets iOS tab bar items (reported as plain buttons) answer `tab`.
|
|
22
|
+
* Precedence: React Native spelled value on an `Other`, Android class, driver role, then trait upgrades.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveRole(element: Element, parent?: Element): ResolvedRole;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Point } from "../driver/driver.types";
|
|
2
|
+
import { type Momentum, type SwipeDirection } from "./actions";
|
|
3
|
+
import { type ScreenHost } from "./context";
|
|
4
|
+
import { type ActionOptions, Locator, type ScrollOptions } from "./locator";
|
|
5
|
+
import { type RoleOptions, type TestIdOptions, type TextQueryOptions } from "./queries";
|
|
6
|
+
import type { QueryRole } from "./roles";
|
|
7
|
+
import type { TextMatch } from "./text";
|
|
8
|
+
export type ScreenSwipeOptions = {
|
|
9
|
+
direction: SwipeDirection;
|
|
10
|
+
momentum?: Momentum;
|
|
11
|
+
} | {
|
|
12
|
+
from: Point;
|
|
13
|
+
to: Point;
|
|
14
|
+
durationMs?: number;
|
|
15
|
+
};
|
|
16
|
+
export type ScreenPinchOptions = {
|
|
17
|
+
scale: number;
|
|
18
|
+
center?: Point;
|
|
19
|
+
radius?: number;
|
|
20
|
+
durationMs?: number;
|
|
21
|
+
};
|
|
22
|
+
export type ScreenRotateOptions = {
|
|
23
|
+
degrees: number;
|
|
24
|
+
center?: Point;
|
|
25
|
+
radius?: number;
|
|
26
|
+
durationMs?: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* `paths` are 1-2 fingers' routes in screen points, each with at least one finite point (give two or more for a
|
|
30
|
+
* real path; a single point is a held touch). More fingers than the driver's `capabilities.maxPointers` is `INVALID_ARGUMENT`.
|
|
31
|
+
*/
|
|
32
|
+
export type ScreenPathOptions = {
|
|
33
|
+
paths: readonly (readonly Point[])[];
|
|
34
|
+
durationMs?: number;
|
|
35
|
+
};
|
|
36
|
+
/** Viewport-level gestures; `screen.swipe` is the same as `screen.gesture.swipe`. */
|
|
37
|
+
export type ScreenGestures = {
|
|
38
|
+
/** pinch about the screen centre (or `center`); `scale` above 1 zooms in */
|
|
39
|
+
pinch(options: ScreenPinchOptions): Promise<void>;
|
|
40
|
+
/** rotate about the screen centre (or `center`); positive `degrees` turn clockwise */
|
|
41
|
+
rotate(options: ScreenRotateOptions): Promise<void>;
|
|
42
|
+
/** replay 1-2 finger paths on one timeline; no observation, coordinates are used as given */
|
|
43
|
+
path(options: ScreenPathOptions): Promise<void>;
|
|
44
|
+
swipe(options: ScreenSwipeOptions): Promise<void>;
|
|
45
|
+
};
|
|
46
|
+
/** The `screen` of the spec API: lazy queries over the device, plus viewport-level gestures. */
|
|
47
|
+
export type Screen = {
|
|
48
|
+
getByTestId(id: TextMatch, options?: TestIdOptions): Locator;
|
|
49
|
+
getByText(text: TextMatch, options?: TextQueryOptions): Locator;
|
|
50
|
+
getByLabel(text: TextMatch, options?: TextQueryOptions): Locator;
|
|
51
|
+
getByPlaceholder(text: TextMatch, options?: TextQueryOptions): Locator;
|
|
52
|
+
getByValue(value: TextMatch, options?: TextQueryOptions): Locator;
|
|
53
|
+
getByRole(role: QueryRole, nameOrOptions?: TextMatch | RoleOptions, options?: Omit<RoleOptions, "name">): Locator;
|
|
54
|
+
/** the agent-CLI selector grammar: `locator("id=sign-in role=button nth=1")` */
|
|
55
|
+
locator(selector: string): Locator;
|
|
56
|
+
tapAt(point: Point, options?: ActionOptions): Promise<void>;
|
|
57
|
+
swipe(options: ScreenSwipeOptions): Promise<void>;
|
|
58
|
+
gesture: ScreenGestures;
|
|
59
|
+
scrollUntilVisible(target: Locator, options?: ScrollOptions): Promise<void>;
|
|
60
|
+
};
|
|
61
|
+
export declare function createScreen(host: ScreenHost): Screen;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Result } from "../../../types/src/result";
|
|
2
|
+
import type { NodeEntry } from "./nodes";
|
|
3
|
+
import type { LocatorExpression } from "./query";
|
|
4
|
+
/** A selector value: literal text (bare or quoted on the wire) or a regular expression. */
|
|
5
|
+
export type SelectorValue = {
|
|
6
|
+
kind: "text";
|
|
7
|
+
value: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: "regex";
|
|
10
|
+
source: string;
|
|
11
|
+
flags: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The agent-CLI selector grammar: `id=<v> label=<v> text=<v> role=<v> value=<v> editable=true|false nth=<i>`.
|
|
15
|
+
* `id` is exact; `label`, `text` and `value` are case-insensitive substrings; a regex is applied as written.
|
|
16
|
+
*/
|
|
17
|
+
export type ParsedSelector = {
|
|
18
|
+
id?: SelectorValue;
|
|
19
|
+
label?: SelectorValue;
|
|
20
|
+
text?: SelectorValue;
|
|
21
|
+
role?: SelectorValue;
|
|
22
|
+
value?: SelectorValue;
|
|
23
|
+
editable?: boolean;
|
|
24
|
+
nth?: number;
|
|
25
|
+
};
|
|
26
|
+
/** Parses `id=x label="Sign in" text=/regex/i role=button editable=true nth=1`. */
|
|
27
|
+
export declare function parseSelector(input: string): Result<ParsedSelector>;
|
|
28
|
+
/** The canonical text for a selector; `parseSelector(formatSelector(s))` equals `s`. */
|
|
29
|
+
export declare function formatSelector(selector: ParsedSelector): string;
|
|
30
|
+
/** The expression a selector stands for: one selector query, wrapped in `nth` when given. */
|
|
31
|
+
export declare function selectorToQuery(selector: ParsedSelector): LocatorExpression;
|
|
32
|
+
/** One node against one parsed selector (`nth` is not part of matching; see `selectorToQuery`). */
|
|
33
|
+
export declare function matchesSelector(entry: NodeEntry, selector: ParsedSelector): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Picks the target of an action among several matches: visible first, then the most exact text terms, then the smallest
|
|
36
|
+
* frame, then reading order.
|
|
37
|
+
*/
|
|
38
|
+
export declare function pickMostSpecific(matches: readonly NodeEntry[], selector: ParsedSelector): NodeEntry | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** A string (whole-text, or substring with `exact:false`) or a RegExp applied as written. */
|
|
2
|
+
export type TextMatch = string | RegExp;
|
|
3
|
+
export type TextMatchOptions = {
|
|
4
|
+
exact?: boolean;
|
|
5
|
+
};
|
|
6
|
+
/** Collapses every whitespace run (including no-break spaces) to one space and trims. */
|
|
7
|
+
export declare function normalizeText(text: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Whether `value` satisfies `match`. A string must equal the whole text after whitespace normalisation (case-sensitive);
|
|
10
|
+
* `exact:false` turns it into a case-insensitive substring. A RegExp is tested as written against the normalised value
|
|
11
|
+
* (its `g`/`y` flags are dropped so repeated calls are not stateful).
|
|
12
|
+
*/
|
|
13
|
+
export declare function matchesText(value: string, match: TextMatch, options?: TextMatchOptions): boolean;
|