e2e 0.15.1 → 0.15.2

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 (87) hide show
  1. package/README.md +51 -24
  2. package/dist/.build.json +1 -1
  3. package/dist/agent/act.js +3 -3
  4. package/dist/agent/act.js.map +1 -1
  5. package/dist/agent/error.d.ts +16 -3
  6. package/dist/agent/error.d.ts.map +1 -1
  7. package/dist/agent/error.js +25 -6
  8. package/dist/agent/error.js.map +1 -1
  9. package/dist/agent/model/tool-choice.d.ts +24 -1
  10. package/dist/agent/model/tool-choice.d.ts.map +1 -1
  11. package/dist/agent/model/tool-choice.js +27 -1
  12. package/dist/agent/model/tool-choice.js.map +1 -1
  13. package/dist/agent/step-cache.d.ts +14 -3
  14. package/dist/agent/step-cache.d.ts.map +1 -1
  15. package/dist/agent/step-cache.js +13 -3
  16. package/dist/agent/step-cache.js.map +1 -1
  17. package/dist/agent/tool-loop.d.ts.map +1 -1
  18. package/dist/agent/tool-loop.js +62 -9
  19. package/dist/agent/tool-loop.js.map +1 -1
  20. package/dist/cache/context.d.ts +19 -6
  21. package/dist/cache/context.d.ts.map +1 -1
  22. package/dist/cache/context.js +15 -21
  23. package/dist/cache/context.js.map +1 -1
  24. package/dist/cli/init/scaffold.d.ts +1 -0
  25. package/dist/cli/init/scaffold.d.ts.map +1 -1
  26. package/dist/cli/init/scaffold.js +2 -1
  27. package/dist/cli/init/scaffold.js.map +1 -1
  28. package/dist/cli/init/sibling-versions.json +1 -1
  29. package/dist/expect/values.js +1 -1
  30. package/dist/expect/values.js.map +1 -1
  31. package/dist/run/execute.d.ts.map +1 -1
  32. package/dist/run/execute.js +21 -8
  33. package/dist/run/execute.js.map +1 -1
  34. package/dist/types.d.ts +1 -1
  35. package/dist/types.d.ts.map +1 -1
  36. package/docs/agent-steps.mdx +126 -0
  37. package/docs/agents.mdx +217 -0
  38. package/docs/api-testing.mdx +65 -0
  39. package/docs/authentication.mdx +345 -0
  40. package/docs/browser.mdx +440 -0
  41. package/docs/bug-bash.mdx +90 -0
  42. package/docs/cache.mdx +306 -0
  43. package/docs/ci.mdx +210 -0
  44. package/docs/coding-agents.mdx +183 -0
  45. package/docs/debugging.mdx +291 -0
  46. package/docs/executors.mdx +243 -0
  47. package/docs/explore.mdx +174 -0
  48. package/docs/github.mdx +184 -0
  49. package/docs/index.mdx +82 -0
  50. package/docs/integrations/eas.mdx +216 -0
  51. package/docs/integrations/index.mdx +24 -0
  52. package/docs/integrations/kernel.mdx +163 -0
  53. package/docs/migrate/cypress.mdx +124 -0
  54. package/docs/migrate/detox.mdx +140 -0
  55. package/docs/migrate/maestro.mdx +229 -0
  56. package/docs/migrate/playwright.mdx +427 -0
  57. package/docs/migrate/selenium.mdx +110 -0
  58. package/docs/mobile-ci.mdx +567 -0
  59. package/docs/mobile.mdx +555 -0
  60. package/docs/models.mdx +241 -0
  61. package/docs/quickstart.mdx +260 -0
  62. package/docs/reference/agent.mdx +524 -0
  63. package/docs/reference/app.mdx +72 -0
  64. package/docs/reference/cli.mdx +817 -0
  65. package/docs/reference/config.mdx +986 -0
  66. package/docs/reference/engine.mdx +1362 -0
  67. package/docs/reference/environment.mdx +113 -0
  68. package/docs/reference/errors.mdx +143 -0
  69. package/docs/reference/expect.mdx +513 -0
  70. package/docs/reference/mcp.mdx +202 -0
  71. package/docs/reference/mobile.mdx +522 -0
  72. package/docs/reference/reporters.mdx +216 -0
  73. package/docs/reference/screen.mdx +688 -0
  74. package/docs/reference/test.mdx +449 -0
  75. package/docs/reference/web.mdx +611 -0
  76. package/docs/security.mdx +292 -0
  77. package/docs/starting-your-app.mdx +231 -0
  78. package/docs/subscriptions.mdx +157 -0
  79. package/docs/telemetry.mdx +185 -0
  80. package/docs/tools.mdx +150 -0
  81. package/docs/viewports.mdx +146 -0
  82. package/docs/writing-an-engine.mdx +316 -0
  83. package/docs/writing-tests.mdx +256 -0
  84. package/package.json +2 -1
  85. package/skills/e2e/SKILL.md +4 -1
  86. package/skills/e2e/references/agent.md +3 -1
  87. package/skills/e2e/references/setup.md +13 -4
@@ -0,0 +1,688 @@
1
+ ---
2
+ title: screen and Locator
3
+ description: Queries, refinement, actions, and reads.
4
+ ---
5
+
6
+ A `Locator` stores a query and resolves it when an action, read, or assertion
7
+ runs. Every `Screen` query method is also available on a locator, scoped to
8
+ its subtree. `tapAt` is not scoped: on a locator it still taps a viewport
9
+ point.
10
+
11
+ ```ts
12
+ export interface Screen {
13
+ /** Creates a lazy role query. An alias such as `img` is rewritten to its role. */
14
+ getByRole(role: Role | RoleAlias, options?: RoleOptions): Locator;
15
+ /** Creates a lazy role query narrowed to an accessible name: `getByRole('button', 'Sign in')`. */
16
+ getByRole(role: Role | RoleAlias, name: TextMatch, options?: Omit<RoleOptions, 'name'>): Locator;
17
+ /** Creates a lazy accessible-label query. */
18
+ getByLabel(text: TextMatch, options?: TextMatchOptions): Locator;
19
+ /** Creates a lazy placeholder query. */
20
+ getByPlaceholder(text: TextMatch, options?: TextMatchOptions): Locator;
21
+ /** Creates a lazy visible-text query. */
22
+ getByText(text: TextMatch, options?: TextMatchOptions): Locator;
23
+ /** Creates a lazy displayed-value query. */
24
+ getByDisplayValue(value: TextMatch, options?: TextMatchOptions): Locator;
25
+ /** Creates a lazy test-id query: a string matches the whole id, case-sensitive; a RegExp tests it. */
26
+ getByTestId(id: TextMatch, options?: { visible?: boolean }): Locator;
27
+ /** Taps a viewport point, with no node behind it. */
28
+ tapAt(point: Point, options?: ActionOptions): Promise<void>;
29
+ /** Performs a viewport-level swipe: in a direction, or along a path from one point to another. */
30
+ swipe(options: SwipeOptions | SwipePathOptions): Promise<void>;
31
+ /** Scrolls until a locator resolves visibly or times out. */
32
+ scrollUntilVisible(
33
+ target: Locator,
34
+ options?: { direction?: ScrollDirection; momentum?: Momentum; timeout?: number },
35
+ ): Promise<void>;
36
+ }
37
+ ```
38
+
39
+ Most operations require exactly one match. Reads return the current value
40
+ without waiting for it to change: a frame that is not in the document answers
41
+ at once, and a node the engine reports stale (a resolve that raced a
42
+ navigation, or a match the app replaced while it was read) is re-resolved
43
+ within the action timeout, which repairs the race without waiting for a
44
+ match. Use [`expect`](/reference/expect) to wait.
45
+
46
+ | Operation | Zero matches | Multiple matches |
47
+ | --- | --- | --- |
48
+ | Actions | Poll until the timeout, then `LOCATOR_NOT_FOUND` | `LOCATOR_AMBIGUOUS` |
49
+ | Reads | `LOCATOR_NOT_FOUND` | `LOCATOR_AMBIGUOUS` |
50
+ | `count()`, `all()`, `allTextContents()` | `0`, `[]`, `[]` | The count, one locator per match, one text per match |
51
+ | `isVisible()`, `isHidden()` | `false`, `true` | `LOCATOR_AMBIGUOUS` |
52
+ | Assertions | Poll until the timeout | `LOCATOR_AMBIGUOUS` |
53
+ | Any of these under a frame that is not in the document | As zero matches for `count()`, `all()`, `allTextContents()`, `isVisible()`, and `isHidden()`; actions, reads, and assertions poll for the frame until the timeout, then `LOCATOR_NOT_FOUND` | `LOCATOR_AMBIGUOUS` when several frames match |
54
+
55
+ ## Queries
56
+
57
+ Every query returns a `Locator` synchronously and performs no work. An
58
+ option key the query does not take (`{ nam: 'Save' }`) throws
59
+ `INVALID_LOCATOR` naming the keys it does.
60
+
61
+ `getByRole` takes the accessible name as its second argument, a string or a
62
+ `RegExp`, with the other options after it:
63
+ `getByRole('button', 'Save', { exact: false })`. The object form,
64
+ `getByRole('button', { name: 'Save', disabled: true })`, is the same query.
65
+
66
+ A string passed to `getByRole` as the name, `getByLabel`, `getByPlaceholder`,
67
+ `getByText`, or `getByDisplayValue` matches the whole text, case-sensitive,
68
+ after whitespace normalization: `getByRole('button', 'Save')` does
69
+ not match `Save changes`. `{ exact: false }` makes it a case-insensitive
70
+ substring match, and a `RegExp` matches as written.
71
+
72
+ `getByTestId` matches the node's `testId`: on the web the value of the
73
+ engine's test-id attribute (`web({ testIdAttribute })`, default
74
+ `data-testid`), on a device the accessibility identifier or resource id. A
75
+ string matches the whole id, case-sensitive; a `RegExp` such as
76
+ `/total-budgeted$/` matches as written.
77
+
78
+ ```ts
79
+ screen.getByRole('button', 'Save');
80
+ screen.getByRole('heading', /welcome/i, { level: 1 });
81
+ screen.getByLabel('Email');
82
+ screen.getByText('Welcome back', { exact: false });
83
+ screen.getByTestId('todo-3');
84
+ ```
85
+
86
+ ### Echoed text
87
+
88
+ `getByText` answers with the innermost matching node, so a container that
89
+ echoes its child's text counts once. Browsers do this natively; on a device
90
+ the engine applies the same rule, since iOS reports a React Native `Text` as
91
+ a host view plus a `StaticText` child with the same label. Two matches that
92
+ do not contain each other remain `LOCATOR_AMBIGUOUS`.
93
+
94
+ ### Hidden duplicates
95
+
96
+ Every query accepts `visible: true`, which drops nodes the platform reports
97
+ as hidden before the exactly-one rule runs. Frameworks often keep a hidden
98
+ copy of content in the document, such as a closed drawer or an inactive tab
99
+ panel, and without `visible` both copies match.
100
+
101
+ ```ts
102
+ await browser.reload();
103
+ // Two "No memories yet" nodes exist for a moment; one is display:none.
104
+ await expect(screen.getByText('No memories yet', { visible: true })).toBeVisible();
105
+ await screen.getByPlaceholder('Search memory...', { visible: true }).fill('launch');
106
+ await screen.getByTestId('memory-list', { visible: true }).getByRole('listitem').first().tap();
107
+ ```
108
+
109
+ `visible` uses the same visibility check as `toBeVisible()`, `isVisible()`,
110
+ and `waitFor()`. It works with scopes, `filter`, `first`, `last`, and `nth`:
111
+ `getByText('Save', { visible: true }).first()` is the first visible match.
112
+ Prefer it over `browser.locator('p:visible')`, which ties the test to CSS.
113
+
114
+ ## Refinement
115
+
116
+ Refinement returns a new locator without querying the app. `hasText` with a
117
+ string is a case-insensitive substring match over the match and its
118
+ descendants, unlike a query text, which matches exactly; pass a RegExp for
119
+ anything stricter. `has` keeps a match whose subtree resolves the inner
120
+ locator.
121
+
122
+ Throws `INVALID_LOCATOR` when `filter` gets neither `hasText` nor `has`, when
123
+ it gets any other key, even beside them (`hasNot`, `hasNotText`, and
124
+ `visible` are not filters here), when `has` is not a runner locator or was
125
+ made by another target's screen, or when `nth` gets a negative or
126
+ non-integer index.
127
+
128
+ ```ts
129
+ const row = screen.getByRole('listitem').filter({ hasText: 'Invoice 42' });
130
+ await row.getByRole('button', 'Void').tap();
131
+ ```
132
+
133
+ <Note>
134
+ In the web engine a `getByDisplayValue` locator cannot be the scope
135
+ of a child query or a `has` filter; both fail with `UNSUPPORTED_CAPABILITY`.
136
+ Playwright has no selector for a control's current value, so the engine
137
+ filters candidates after the fact.
138
+ </Note>
139
+
140
+ ## screen.tapAt
141
+
142
+ ```ts
143
+ tapAt(point: Point, options?: ActionOptions): Promise<void>;
144
+ ```
145
+
146
+ Taps a viewport point in CSS pixels, the space `boundingBox()` reports in,
147
+ with no node behind it: a pin on a canvas, a control the tree does not
148
+ list. Nothing is resolved or waited for; the page decides what the tap
149
+ lands on. Uses `config.actionTimeout`. Throws `INVALID_ARGUMENT` for a
150
+ negative or non-finite coordinate and `UNSUPPORTED_CAPABILITY` when the
151
+ engine does not list `tap` in its pointer actions.
152
+
153
+ ```ts
154
+ const map = await screen.getByRole('image', 'Map').boundingBox();
155
+ await screen.tapAt({ x: map.x + 360, y: map.y + 70 });
156
+ ```
157
+
158
+ Prefer a locator when the tree lists the control; a point moves with the
159
+ layout. For a point inside a node, `tap({ position })` reads the node's box
160
+ for you.
161
+
162
+ ## screen.swipe
163
+
164
+ ```ts
165
+ swipe(options: SwipeOptions | SwipePathOptions): Promise<void>;
166
+ ```
167
+
168
+ Viewport-level gesture, in one of two shapes. `{ direction, momentum? }` is
169
+ a scroll gesture on the screen. `direction` is the way the content scrolls,
170
+ not the finger: `'down'` reveals what is below. `momentum` is its fling
171
+ strength, `'none'` by default: `'none'` a plain drag of half the surface,
172
+ `'slow'` a short fling, `'fast'` a long one. How far each carries is the
173
+ engine's.
174
+ `{ from, to }` is a swipe along a path between two viewport points: a touch
175
+ swipe on a device, a pointer drag on a document platform, which is what
176
+ reveals a row action, dismisses a sheet, or drags a card between columns.
177
+ Uses `config.actionTimeout`. Throws
178
+ `ACTION_FAILED` when the gesture is rejected, `INVALID_ARGUMENT` for a mix
179
+ of the two shapes or a coordinate off the plane, and `UNSUPPORTED_CAPABILITY`
180
+ when the engine does not list `swipe` in its actions (a direction) or
181
+ `swipeTo` in its pointer actions (a path).
182
+
183
+ ```ts
184
+ await screen.swipe({ direction: 'up', momentum: 'fast' });
185
+
186
+ const row = await screen.getByText('Design review').boundingBox();
187
+ await screen.swipe({
188
+ from: { x: row.x + row.width - 10, y: row.y + row.height / 2 },
189
+ to: { x: row.x + 10, y: row.y + row.height / 2 },
190
+ });
191
+ ```
192
+
193
+ ## screen.scrollUntilVisible
194
+
195
+ ```ts
196
+ scrollUntilVisible(
197
+ target: Locator,
198
+ options?: { direction?: ScrollDirection; momentum?: Momentum; timeout?: number },
199
+ ): Promise<void>;
200
+ ```
201
+
202
+ | Name | Type | Default |
203
+ | --- | --- | --- |
204
+ | `target` | `Locator` | - |
205
+ | `options.direction` | `ScrollDirection` | `'down'` |
206
+ | `options.momentum` | `Momentum` | `'slow'`; the gesture of each step, as [`screen.swipe`](#screen-swipe) defines it. A longer step is faster but must not carry the target past the surface unseen, which is what a virtualized list does |
207
+ | `options.timeout` | `number` | `30000`, its own default, not `config.actionTimeout`; it bounds the whole scroll, each swipe included |
208
+
209
+ On `screen` each step is a viewport swipe. On a locator the step is a swipe
210
+ of that node, so a feed or a table that is its own scroll container pages
211
+ without the pointer having to hover it first:
212
+ `screen.getByRole('feed').scrollUntilVisible(row)`.
213
+
214
+ Throws `INVALID_LOCATOR` for a non-runner locator or one made by another
215
+ target's screen, `INVALID_ARGUMENT` for an option it does not take,
216
+ `LOCATOR_NOT_FOUND` when the deadline expires first, a swipe it cut short
217
+ included.
218
+
219
+ ```ts
220
+ await screen.scrollUntilVisible(screen.getByRole('button', 'Accept'));
221
+ ```
222
+
223
+ ## Locator actions
224
+
225
+ Each action resolves one node, waits for actionability, and performs one
226
+ operation. Default timeout `config.actionTimeout`, 30000 ms, overridable
227
+ with `{ timeout }`.
228
+
229
+ ```ts
230
+ export interface Locator extends Screen {
231
+ /** Taps exactly one matching actionable node; with `position`, taps that point of its box once it is in view, with no other actionability check. */
232
+ tap(options?: TapOptions): Promise<void>;
233
+ /** Alias of tap. */
234
+ click(options?: TapOptions): Promise<void>;
235
+ /** Double-taps exactly one matching actionable node. */
236
+ doubleTap(options?: ClickOptions): Promise<void>;
237
+ /** Performs a secondary tap, such as a right click or two-finger tap. */
238
+ secondaryTap(options?: ClickOptions): Promise<void>;
239
+ /** Long-presses exactly one matching actionable node. */
240
+ longPress(options?: LongPressOptions): Promise<void>;
241
+ /** Fills exactly one input. Secret values are never logged. */
242
+ fill(value: string | Secret, options?: ActionOptions): Promise<void>;
243
+ /**
244
+ * Focuses exactly one input and types `text` through the keyboard, one
245
+ * character at a time, so the app receives key events. A `Secret` is
246
+ * refused; it goes through `fill`.
247
+ */
248
+ pressSequentially(text: string, options?: PressSequentiallyOptions): Promise<void>;
249
+ /** Clears exactly one input. */
250
+ clear(options?: ActionOptions): Promise<void>;
251
+ /** Sends one key to exactly one node. */
252
+ press(key: string, options?: ActionOptions): Promise<void>;
253
+ /** Checks exactly one node. */
254
+ check(options?: ActionOptions): Promise<void>;
255
+ /** Unchecks exactly one node. */
256
+ uncheck(options?: ActionOptions): Promise<void>;
257
+ /** Selects one option from exactly one control. */
258
+ selectOption(value: SelectOption, options?: ActionOptions): Promise<void>;
259
+ /** Focuses exactly one node. */
260
+ focus(options?: ActionOptions): Promise<void>;
261
+ /** Hovers over exactly one matching actionable node. */
262
+ hover(options?: ActionOptions): Promise<void>;
263
+ /** Sets the files of exactly one file input; paths resolve from the project root. */
264
+ setInputFiles(paths: string | readonly string[], options?: ActionOptions): Promise<void>;
265
+ /** Drags exactly one node to exactly one target. */
266
+ dragTo(target: Locator, options?: ActionOptions): Promise<void>;
267
+ /** Scrolls exactly one node into view. */
268
+ scrollIntoView(options?: ActionOptions): Promise<void>;
269
+ /** Performs a swipe scoped to exactly one node. */
270
+ swipe(options: SwipeOptions & ActionOptions): Promise<void>;
271
+ /** Reads normalized text without retrying for a particular value. */
272
+ textContent(): Promise<string | null>;
273
+ /** Reads the current input value. */
274
+ inputValue(): Promise<string>;
275
+ /** Reads one attribute. */
276
+ getAttribute(name: string): Promise<string | null>;
277
+ /** Reads current visibility. */
278
+ isVisible(): Promise<boolean>;
279
+ /** Reads current hidden or absent state, the negation of `isVisible`. */
280
+ isHidden(): Promise<boolean>;
281
+ /** Reads current enabled state. */
282
+ isEnabled(): Promise<boolean>;
283
+ /** Reads current disabled state, the negation of `isEnabled`. */
284
+ isDisabled(): Promise<boolean>;
285
+ /** Reads current checked state. */
286
+ isChecked(): Promise<boolean>;
287
+ /** Reads the current viewport-relative rectangle. */
288
+ boundingBox(): Promise<{ x: number; y: number; width: number; height: number } | null>;
289
+ /** Counts current matches without auto-waiting. */
290
+ count(): Promise<number>;
291
+ /** One `nth(i)` locator per current match, without auto-waiting; empty when nothing matches. */
292
+ all(): Promise<Locator[]>;
293
+ /** Reads the normalized text of every current match, without auto-waiting; empty when nothing matches. */
294
+ allTextContents(): Promise<string[]>;
295
+ /** Waits for the requested locator state. */
296
+ waitFor(options?: { state?: 'visible' | 'hidden'; timeout?: number }): Promise<void>;
297
+ /** Adds deterministic locator filters. */
298
+ filter(options: { hasText?: TextMatch; has?: Locator }): Locator;
299
+ /** Selects the first current match. */
300
+ first(): Locator;
301
+ /** Selects the last current match. */
302
+ last(): Locator;
303
+ /** Selects one zero-based current match. */
304
+ nth(index: number): Locator;
305
+ }
306
+ ```
307
+
308
+ - An action whose kind the engine does not list in `actions` throws
309
+ `UNSUPPORTED_CAPABILITY` before reaching the engine.
310
+ - An action takes only the options its signature names. Any other key,
311
+ Playwright's `trial`, `force`, or `noWaitAfter` included, throws
312
+ `INVALID_ARGUMENT` before the node is resolved, so the control is never
313
+ touched.
314
+ - `click` is an alias of `tap`.
315
+ - `tap`, `doubleTap`, and `secondaryTap` take `modifiers`, the keys held for
316
+ the click, as a Shift-click extends a selection:
317
+ `row.click({ modifiers: ['Shift'] })`. `ControlOrMeta` is Control on Windows
318
+ and Linux, Meta on macOS. An unknown or repeated modifier is
319
+ `INVALID_ARGUMENT`, and so is `modifiers` beside `position`. The browser
320
+ engine holds them; an engine that does not declare `tapModifiers`, the
321
+ mobile engine among them, fails with `UNSUPPORTED_CAPABILITY` before it
322
+ acts.
323
+ - `tap({ position })` taps at an offset of the node's top-left corner, in
324
+ CSS pixels. The node is scrolled into view when the engine lists
325
+ `scrollIntoView`, then its box is read and the pointer dispatched at the
326
+ point, with no node behind the gesture, so the platform's other
327
+ actionability checks (enabled, not covered) do not run. A node the engine
328
+ cannot bring into view fails as the engine reports it (`ACTION_FAILED` on
329
+ the browser engine, whose scroll waits for visibility); one that resolves
330
+ but never shows a visible box within the timeout is `LOCATOR_NOT_FOUND`.
331
+ An engine without the `tap` pointer action is `UNSUPPORTED_CAPABILITY`.
332
+ - `press` takes zero or more modifiers (`Shift`, `Control`, `Alt`, `Meta`,
333
+ `ControlOrMeta`) and one key, joined with `+`: a named key (`Enter`,
334
+ `Escape`, `Tab`, `Backspace`, `Delete`, `Insert`, `Space`, the arrows,
335
+ `Home`, `End`, `PageUp`, `PageDown`, `F1` to `F12`) or one printable
336
+ character. `Shift++` presses `+`. A key the platform lacks throws
337
+ `UNSUPPORTED_CAPABILITY`. See the [key grammar](/reference/engine#keys).
338
+ - `longPress` holds for `{ duration }` milliseconds, 100 through 10000, else
339
+ for the engine's default: 500 ms on the browser engine, one second on the
340
+ mobile engine, past a React Native `Pressable`'s `delayLongPress`.
341
+ - `fill` replaces the current content. A `Secret` is resolved on the host
342
+ right before the call and never logged, reported, or sent to a model.
343
+ - `pressSequentially` focuses the input and sends the text as keystrokes
344
+ through the engine's keyboard, appending to the current content. `fill`
345
+ sets the value directly and fires no key events, so an autocomplete, a
346
+ search-as-you-type box, or a masked input wired to `keydown` or `keyup`
347
+ never reacts to it; this is the action for those. `{ delay }` waits that
348
+ many milliseconds between characters, each sent in its own keyboard call;
349
+ without it the whole text goes in one call. Focus, pauses, and keystrokes
350
+ share the one action deadline: a pause is cut short at it and no character
351
+ is sent past it, and the action fails with `ACTION_FAILED` naming how many
352
+ characters landed. It needs the engine's `focus` action and `keyboard`
353
+ capability, else `UNSUPPORTED_CAPABILITY` before any node resolves, and it
354
+ relies on the [keyboard contract](/reference/engine#engine): `type`
355
+ must deliver text as a person typing would, so key events reach the app
356
+ where the platform has them. A `Secret` is a type error and
357
+ `INVALID_ARGUMENT` at runtime: a secret is filled, never typed as
358
+ keystrokes. The step records the locator, not the text, like `fill`.
359
+ - `setInputFiles` paths resolve from the project root. An empty list or blank
360
+ entry throws `INVALID_ARGUMENT`.
361
+ - `dragTo` with a non-runner target, or one made by another target's screen,
362
+ throws `INVALID_LOCATOR`.
363
+ - `swipe` on a locator is a gesture on that one node: `{ direction,
364
+ momentum? }` as [`screen.swipe`](#screen-swipe) defines them, plus
365
+ `timeout`. It has no `{ from, to }` form. `screen.swipe` takes no
366
+ `timeout`: a `timeout` key there is `INVALID_ARGUMENT`.
367
+ - An action that may already have committed surfaces as `ACTION_FAILED` and
368
+ is never repeated.
369
+
370
+ ```ts
371
+ await screen.getByLabel('Email').fill('ada@example.test');
372
+ await screen.getByRole('checkbox', 'Remember me').check();
373
+ await screen.getByLabel('Plan').selectOption({ label: 'Pro' });
374
+ await screen.getByLabel('Plan').selectOption({ value: 'pro' });
375
+ await screen.getByRole('button', 'Save').tap({ timeout: 10_000 });
376
+ await screen.getByRole('image', 'Seat map').tap({ position: { x: 120, y: 48 } });
377
+ await screen.getByRole('textbox', 'Search').press('Enter');
378
+ await screen.getByRole('textbox', 'Notes').press('ControlOrMeta+a');
379
+ await screen.getByLabel('City').pressSequentially('War', { delay: 50 });
380
+ ```
381
+
382
+ ## Locator reads
383
+
384
+ `waitFor` defaults `state` to `'visible'` and `timeout` to
385
+ `config.actionTimeout`, and throws `LOCATOR_NOT_FOUND` on timeout.
386
+
387
+ `textContent` and `allTextContents` return the node's rendered text with
388
+ whitespace collapsed to single spaces. On the web that is what `innerText`
389
+ reads, not the DOM's `textContent`: CSS `text-transform` applies,
390
+ `display: none` descendants are left out, and a `<br>` reads as a space. A
391
+ node that is not rendered itself, such as a `display: none` one, reads its
392
+ DOM text, as `innerText` does.
393
+
394
+ `isChecked` is `false` on a node with no checked state, such as a button or
395
+ a heading; it does not throw.
396
+
397
+ `textContent`, `inputValue`, `getAttribute`, and `allTextContents` throw
398
+ `POLICY_DENIED` on a secure field, and so do `expect(locator).toHaveValue`,
399
+ `toHaveText`, `toContainText`, and `toHaveAttribute`.
400
+ `isEnabled`, `isDisabled`, `isChecked`, and `boundingBox` are allowed there.
401
+
402
+ `isHidden` is `!isVisible()` and `isDisabled` is `!isEnabled()`, with the
403
+ same strictness: `isHidden` is `true` for zero matches, `isDisabled` is
404
+ `LOCATOR_NOT_FOUND` there, and both are `LOCATOR_AMBIGUOUS` on several.
405
+
406
+ ```ts
407
+ if (await screen.getByRole('alert').isVisible()) {
408
+ const message = await screen.getByRole('alert').textContent();
409
+ }
410
+ await screen.getByTestId('spinner').waitFor({ state: 'hidden' });
411
+ ```
412
+
413
+ ### Every match at once
414
+
415
+ `all()` resolves the query once and hands back one `nth(i)` locator per
416
+ match, so a loop can act on each row; `allTextContents()` reads every match's
417
+ normalized text in one call. Neither waits or enforces exactly-one: zero
418
+ matches is an empty array, the same snapshot rule as `count()`. A
419
+ `frameLocator` whose frame is not in the document counts as zero matches here
420
+ and in `isVisible()`, where an action or assertion polls for it. Each locator
421
+ `all()` returns re-resolves when used, so a list that changes under the loop
422
+ can make a later `nth(i)` point at a different row, or at nothing. Follow the
423
+ loop with an assertion on the list, and use
424
+ [`toHaveText([...])`](/reference/expect#locator-matchers) instead of a loop
425
+ when the point is to check the texts.
426
+
427
+ ```ts
428
+ for (const row of await screen.getByRole('row').all()) {
429
+ await row.getByRole('checkbox').check();
430
+ }
431
+ expect(await screen.getByRole('listitem').allTextContents()).toEqual(['Alpha', 'Beta']);
432
+ ```
433
+
434
+ ## Types
435
+
436
+ ```ts
437
+ /**
438
+ * The closed role vocabulary; an unsupported role is a type error. Every
439
+ * engine maps its platform's element types onto these names, so a role query
440
+ * reads the same against a browser and a device. A role the platform has no
441
+ * widget for matches nothing there; it is never a type error.
442
+ */
443
+ export type Role =
444
+ | 'button'
445
+ | 'link'
446
+ | 'textbox'
447
+ | 'searchbox'
448
+ | 'combobox'
449
+ | 'listbox'
450
+ | 'option'
451
+ | 'checkbox'
452
+ | 'radio'
453
+ | 'radiogroup'
454
+ | 'switch'
455
+ | 'slider'
456
+ | 'spinbutton'
457
+ | 'progressbar'
458
+ | 'meter'
459
+ | 'image'
460
+ | 'heading'
461
+ | 'tab'
462
+ | 'tablist'
463
+ | 'tabpanel'
464
+ | 'menu'
465
+ | 'menubar'
466
+ | 'menuitem'
467
+ | 'menuitemcheckbox'
468
+ | 'menuitemradio'
469
+ | 'toolbar'
470
+ | 'tooltip'
471
+ | 'tree'
472
+ | 'treeitem'
473
+ | 'list'
474
+ | 'listitem'
475
+ | 'table'
476
+ | 'grid'
477
+ | 'row'
478
+ | 'rowgroup'
479
+ | 'rowheader'
480
+ | 'cell'
481
+ | 'gridcell'
482
+ | 'columnheader'
483
+ | 'separator'
484
+ | 'group'
485
+ | 'article'
486
+ | 'figure'
487
+ | 'form'
488
+ | 'status'
489
+ | 'alert'
490
+ | 'dialog'
491
+ | 'alertdialog'
492
+ | 'main'
493
+ | 'navigation'
494
+ | 'banner'
495
+ | 'contentinfo'
496
+ | 'complementary'
497
+ | 'region';
498
+
499
+ /**
500
+ * Spellings `getByRole` accepts beside the vocabulary and rewrites to a `Role`
501
+ * before the query is built: `img` is the ARIA name of `image`. An alias never
502
+ * reaches an engine, the replay cache, or a report; a `Role` is what they see.
503
+ */
504
+ export type RoleAlias = 'img';
505
+
506
+ /** A string matches exactly; a RegExp uses its own source and flags. */
507
+ export type TextMatch = string | RegExp;
508
+
509
+ export interface TextMatchOptions {
510
+ /** Exact match, the default. `false` is case-insensitive substring matching; a RegExp ignores it. */
511
+ exact?: boolean;
512
+ /**
513
+ * When true, nodes the platform reports as hidden are excluded before the
514
+ * exactly-one rule is applied, so a visible node with a hidden twin (a
515
+ * prerendered copy kept after navigation, a closed drawer) still resolves.
516
+ * Omitted or false keeps every match, hidden ones included.
517
+ */
518
+ visible?: boolean;
519
+ }
520
+
521
+ /**
522
+ * Role query options. A role query never matches a node hidden from the
523
+ * accessibility tree, on every engine; `visible` (inherited) is the one knob
524
+ * that narrows the other query kinds the same way.
525
+ */
526
+ export interface RoleOptions extends TextMatchOptions {
527
+ /** Accessible name filter. */
528
+ name?: TextMatch;
529
+ /** Requires the checked state. */
530
+ checked?: boolean;
531
+ /** Requires the disabled state. */
532
+ disabled?: boolean;
533
+ /** Requires the selected state. */
534
+ selected?: boolean;
535
+ /** Requires the expanded state. */
536
+ expanded?: boolean;
537
+ /** Requires the pressed state of a toggle button. */
538
+ pressed?: boolean;
539
+ /** Requires a heading level, 1 through 6. */
540
+ level?: number;
541
+ }
542
+
543
+ export interface ActionOptions {
544
+ /** Deadline in milliseconds; defaults to `config.actionTimeout`. */
545
+ timeout?: number;
546
+ }
547
+
548
+ /** `longPress` options: the hold time in milliseconds, 100 through 10000, default the engine's own. */
549
+ export interface LongPressOptions extends ActionOptions {
550
+ /** Hold time in milliseconds, 100 through 10000; unset, the engine's default. */
551
+ duration?: number;
552
+ }
553
+
554
+ /** `pressSequentially` options: an optional pause between characters. */
555
+ export interface PressSequentiallyOptions extends ActionOptions {
556
+ /** Milliseconds to wait between characters; omitted types the whole text in one call. */
557
+ delay?: number;
558
+ }
559
+
560
+ export interface SwipeOptions {
561
+ /** Swipe direction. */
562
+ direction: ScrollDirection;
563
+ /** Fling strength; default `none`. The three values are defined under screen.swipe. */
564
+ momentum?: Momentum;
565
+ from?: never;
566
+ to?: never;
567
+ }
568
+
569
+ /** A swipe along a path between two viewport points: a touch swipe on a device, a pointer drag on a document platform. */
570
+ export interface SwipePathOptions {
571
+ /** Where the finger goes down. */
572
+ from: Point;
573
+ /** Where it lifts. */
574
+ to: Point;
575
+ direction?: never;
576
+ momentum?: never;
577
+ }
578
+
579
+ /**
580
+ * A point in CSS pixels. For `screen.tapAt` and `screen.swipe` the origin is
581
+ * the viewport's top-left corner, the space `boundingBox()` reports in; for
582
+ * `tap({ position })` it is the node's own top-left corner.
583
+ */
584
+ export type Point = ViewportPoint; // { x: number; y: number }
585
+
586
+ /** `tap`, `click`, `doubleTap`, and `secondaryTap` options: the action timeout and the keys held for the click. */
587
+ export interface ClickOptions extends ActionOptions {
588
+ /**
589
+ * Keys held while the pointer clicks, as a Shift-click extends a selection:
590
+ * `Shift`, `Control`, `Alt`, `Meta`, or `ControlOrMeta` (Control on Windows
591
+ * and Linux, Meta on macOS). Needs an engine that declares `tapModifiers`,
592
+ * `UNSUPPORTED_CAPABILITY` otherwise.
593
+ */
594
+ modifiers?: readonly KeyModifier[];
595
+ }
596
+
597
+ /** A modifier key, for `modifiers` and the `press` key grammar. */
598
+ export type KeyModifier = 'Shift' | 'Control' | 'Alt' | 'Meta' | 'ControlOrMeta';
599
+
600
+ /** `tap` and `click` options. */
601
+ export interface TapOptions extends ClickOptions {
602
+ /**
603
+ * Where to tap, relative to the node's top-left corner. Without it the
604
+ * platform picks a point of the node, usually its center, behind its own
605
+ * actionability checks; with it the pointer is dispatched at that point
606
+ * once the node is in view and has a box, which needs the engine's `tap`
607
+ * pointer action.
608
+ */
609
+ position?: Point;
610
+ }
611
+
612
+ /** One option of a select: its label (a bare string too), its `value` attribute, or its zero-based index. */
613
+ export type SelectOption =
614
+ | string
615
+ | { label: string; value?: never; index?: never }
616
+ | { value: string; label?: never; index?: never }
617
+ | { index: number; label?: never; value?: never };
618
+
619
+ /** A swipe or scroll direction. */
620
+ export type ScrollDirection = 'up' | 'down' | 'left' | 'right';
621
+
622
+ /** Fling strength of a swipe; defined under screen.swipe. */
623
+ export type Momentum = 'none' | 'slow' | 'fast';
624
+ ```
625
+
626
+ A role query never matches a node hidden from the accessibility tree.
627
+ `visible` applies the same rule to the other query kinds. See
628
+ [Hidden duplicates](#hidden-duplicates).
629
+
630
+ `img` is an alias, not a role: `getByRole('img', { name })` builds the same
631
+ query as `getByRole('image', { name })`, so a test ported from Playwright keeps
632
+ its spelling while engines, the replay cache, and reports see `image`. On the
633
+ web, `image` covers `<img>` with alt text and any element with `role="img"`;
634
+ an `<img alt="">` is decoration and matches nothing.
635
+
636
+ The web engine derives the roles from HTML the way HTML-AAM does: `<progress>`
637
+ is `progressbar`, `<meter>` is `meter`, `<hr>` is `separator`, `<fieldset>`
638
+ and `<details>` are `group`, `<input type="number">` is `spinbutton`,
639
+ `<input type="file">` is `button`, as Chrome and Playwright read it,
640
+ `<select size="3">` is a `listbox` like a `multiple` one, an `<input list>`
641
+ naming a `<datalist>` is a `combobox`, a `<td>` of a `role="grid"` table is a
642
+ `gridcell`,
643
+ `<thead>` and `<tbody>` are `rowgroup`, `<th scope="row">` is `rowheader`,
644
+ `<figure>` is `figure`, `<article>` is `article`, `<header>` and `<footer>` are
645
+ `banner` and `contentinfo` only outside sectioning content or an element with
646
+ a sectioning role (`article`, `complementary`, `main`, `navigation`, `region`),
647
+ and `<form>` and `<section>`
648
+ are `form` and `region` only when they carry an `aria-label`,
649
+ `aria-labelledby`, or `title`. A `<legend>`, `<figcaption>`, or `<caption>`
650
+ names its fieldset, figure, or table, so `toHaveAccessibleName` reads what
651
+ `getByRole('group', { name })` matched. A role accname names from content (a
652
+ button, link, heading, tab, menu item, option, checkbox, radio, switch, cell,
653
+ row or column header, row, tree item, tooltip) is named by its descendants, a
654
+ custom element's shadow tree included, so `getByRole('checkbox', { name })`
655
+ and `toHaveAccessibleName(name)` agree on a `<div role="checkbox">`. An
656
+ `<input type="submit">` or `type="reset"` with no `value` reads `Submit` or
657
+ `Reset`. `<menu>` is a `list`; only `role="menu"` is a
658
+ `menu`. A device engine maps the platform types it has (a tab bar or segmented
659
+ control is a `tablist`, a progress indicator a `progressbar`, a stepper a
660
+ `spinbutton`) and keeps any other type verbatim, so a role with no native
661
+ widget, such as `tooltip` or `figure`, matches nothing on a phone.
662
+
663
+ A `contenteditable` editing host, the root a TipTap, ProseMirror, Lexical, or
664
+ Slate editor renders into, is a `textbox` whose value is its text as rendered
665
+ (and, while it has focus, whose `selection` is the text selected inside it).
666
+ The value keeps spaces and newlines, so `toHaveValue` and `inputValue()` read
667
+ a `white-space: pre-wrap` editor like a textarea. An empty editor, holding only
668
+ the `<p><br></p>` or the zero-width no-break space its editor pads an empty
669
+ line with, reads as `''`, and that padding is never part of a value. It is named by its
670
+ `aria-label`, `aria-placeholder`, or the `data-placeholder` its editor paints;
671
+ the blocks inside it are content, not textboxes. The tag does not matter (an
672
+ `<article contenteditable>` is the editor, not an article), while a native
673
+ control or a drawn surface made editable keeps its own role. `getByRole` resolves through
674
+ Playwright's role selector, which has no such rule, so a test addresses an
675
+ editor by role only when the host carries `role="textbox"`; `getByLabel` and
676
+ `getByTestId` reach it either way. A name taken from a block's
677
+ `data-placeholder` (TipTap, ProseMirror) goes once the editor has content, as
678
+ the painted hint does, so the host reads as an unnamed `textbox` from then on;
679
+ a name from the host's own `aria-label`, `aria-labelledby`, `aria-placeholder`,
680
+ or `data-placeholder` (Quill) stays. Anchor a test on a label or a test id, not
681
+ on the placeholder.
682
+
683
+
684
+ For `selectOption`, pass a label string or exactly one of `label`, `value`,
685
+ or `index`. `value` matches the option's value attribute, which is what the
686
+ form submits. One option per call: an array, an object with more than one of
687
+ those keys, or a negative or fractional `index` fails with `INVALID_ARGUMENT`
688
+ before the engine acts.