e2e 0.21.1 → 0.21.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.
- package/README.md +2 -0
- package/dist/.build.json +1 -1
- package/dist/agent/act-validation.js +1 -0
- package/dist/agent/act.js +7 -3
- package/dist/agent/action-dispatcher.js +30 -8
- package/dist/agent/actions.d.ts +2 -0
- package/dist/agent/actions.js +5 -2
- package/dist/agent/error.js +1 -0
- package/dist/agent/executor.d.ts +14 -6
- package/dist/agent/index.js +5 -1
- package/dist/agent/invocation.d.ts +3 -0
- package/dist/agent/model/sdk.d.ts +5 -0
- package/dist/agent/model/sdk.js +18 -1
- package/dist/agent/observation.js +1 -0
- package/dist/agent/primitives.js +14 -3
- package/dist/agent/replay.js +9 -3
- package/dist/agent/step-cache.d.ts +3 -2
- package/dist/agent/step-cache.js +7 -4
- package/dist/agent/tool-loop.js +4 -1
- package/dist/cache/context.js +1 -1
- package/dist/cache/recorder.d.ts +2 -1
- package/dist/cache/recorder.js +11 -2
- package/dist/cache/trace-fields.d.ts +10 -0
- package/dist/cache/trace-fields.js +17 -0
- package/dist/cache/trace.d.ts +9 -1
- package/dist/cache/trace.js +26 -7
- package/dist/cli/feedback.js +2 -1
- package/dist/cli/init/sibling-versions.json +1 -1
- package/dist/collect/select.js +12 -2
- package/dist/config/agent.d.ts +1 -1
- package/dist/config/diagnose.js +9 -3
- package/dist/config/esm-hooks.js +3 -2
- package/dist/config/resolve.js +3 -2
- package/dist/config/typescript.js +52 -2
- package/dist/engine/contract.d.ts +3 -2
- package/dist/engine/manifest.js +2 -2
- package/dist/internal/errors.js +5 -2
- package/dist/internal/source.js +2 -0
- package/dist/internal/unserved-model.d.ts +5 -0
- package/dist/internal/unserved-model.js +22 -0
- package/dist/locator/screen.js +1 -2
- package/dist/oauth/copilot.js +23 -5
- package/dist/oauth/device-code.js +35 -19
- package/dist/oauth/errors.d.ts +3 -0
- package/dist/oauth/errors.js +26 -11
- package/dist/oauth/providers/github-copilot.d.ts +4 -0
- package/dist/oauth/providers/github-copilot.js +64 -16
- package/dist/oauth/providers/openai.d.ts +2 -2
- package/dist/oauth/providers/openai.js +47 -19
- package/dist/oauth/providers/opencode-console.js +36 -15
- package/dist/oauth/providers/xai.js +18 -9
- package/dist/oauth/token-endpoint.d.ts +15 -6
- package/dist/oauth/token-endpoint.js +18 -6
- package/dist/report/code-frame.js +9 -3
- package/dist/report/failure-text.d.ts +10 -2
- package/dist/report/failure-text.js +24 -8
- package/dist/report/list-model.d.ts +13 -0
- package/dist/report/list-steps.d.ts +6 -1
- package/dist/report/list-steps.js +29 -0
- package/dist/report/list.d.ts +1 -0
- package/dist/report/list.js +16 -7
- package/dist/report/live-window.js +2 -1
- package/dist/report/markdown.js +1 -1
- package/dist/run/fixtures.js +2 -0
- package/dist/run/host-trace.d.ts +3 -2
- package/dist/run/host-trace.js +2 -1
- package/dist/run/last-run.d.ts +9 -0
- package/dist/run/last-run.js +28 -0
- package/dist/run/open-session.d.ts +3 -1
- package/dist/run/open-session.js +1 -1
- package/dist/run/runner.js +6 -2
- package/dist/run/sessions.js +3 -3
- package/dist/run/standalone-attempt.d.ts +2 -1
- package/dist/run/standalone-attempt.js +3 -1
- package/dist/run/steps.d.ts +3 -1
- package/dist/run/units.d.ts +8 -0
- package/dist/run/units.js +14 -0
- package/dist/run/worker-models.d.ts +2 -0
- package/dist/run/worker-models.js +7 -0
- package/dist/types.d.ts +2 -2
- package/docs/authentication.mdx +26 -23
- package/docs/browser.mdx +7 -4
- package/docs/bug-bash.mdx +2 -2
- package/docs/cache.mdx +6 -1
- package/docs/ci/bitrise.mdx +1 -1
- package/docs/ci/codemagic.mdx +2 -2
- package/docs/ci/eas-workflows.mdx +1 -1
- package/docs/coding-agents.mdx +7 -35
- package/docs/debugging.mdx +24 -36
- package/docs/executors.mdx +1 -1
- package/docs/github.mdx +2 -1
- package/docs/integrations/eas.mdx +1 -1
- package/docs/integrations/index.mdx +6 -0
- package/docs/integrations/lightpanda.mdx +192 -0
- package/docs/integrations/testmu.mdx +152 -0
- package/docs/mcp.mdx +123 -0
- package/docs/migrate/detox.mdx +1 -1
- package/docs/migrate/maestro.mdx +101 -105
- package/docs/migrate/playwright.mdx +65 -70
- package/docs/mobile.mdx +18 -3
- package/docs/models.mdx +4 -2
- package/docs/quickstart.mdx +7 -0
- package/docs/reference/agent.mdx +369 -277
- package/docs/reference/app.mdx +141 -32
- package/docs/reference/cli.mdx +674 -558
- package/docs/reference/config.mdx +1406 -752
- package/docs/reference/engine.mdx +1062 -256
- package/docs/reference/environment.mdx +156 -84
- package/docs/reference/errors.mdx +106 -72
- package/docs/reference/expect.mdx +752 -372
- package/docs/reference/mcp.mdx +366 -141
- package/docs/reference/mobile.mdx +844 -403
- package/docs/reference/reporters.mdx +312 -106
- package/docs/reference/runner.mdx +373 -174
- package/docs/reference/screen.mdx +931 -398
- package/docs/reference/security.mdx +106 -40
- package/docs/reference/test.mdx +626 -273
- package/docs/reference/web.mdx +956 -533
- package/docs/security.mdx +6 -5
- package/docs/snippets/model-provider.jsx +763 -0
- package/docs/snippets/setup-prompt-card.jsx +22 -0
- package/docs/snippets/show-more.jsx +22 -0
- package/docs/snippets/video-card.jsx +34 -0
- package/docs/snippets/video.jsx +25 -0
- package/docs/subscriptions.mdx +10 -3
- package/docs/telemetry.mdx +39 -39
- package/package.json +3 -2
- package/schema/report-v1.schema.json +6 -0
- package/skills/e2e/references/agent.md +8 -3
- package/skills/e2e/references/debugging.md +1 -1
- package/skills/e2e/references/mcp.md +1 -1
- package/skills/e2e/references/setup.md +12 -3
- package/skills/e2e/references/writing-tests.md +1 -1
|
@@ -6,95 +6,242 @@ icon: "display"
|
|
|
6
6
|
"twitter:image": "https://e2e.tester.army/docs/images/og/reference/screen.png?v=1b21c45d37"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
A `Locator` stores a query and resolves
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
getByRole(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/** Creates a lazy placeholder query. */
|
|
23
|
-
getByPlaceholder(text: TextMatch, options?: TextMatchOptions): Locator;
|
|
24
|
-
/** Creates a lazy visible-text query. */
|
|
25
|
-
getByText(text: TextMatch, options?: TextMatchOptions): Locator;
|
|
26
|
-
/** Creates a lazy displayed-value query. */
|
|
27
|
-
getByDisplayValue(value: TextMatch, options?: TextMatchOptions): Locator;
|
|
28
|
-
/** Creates a lazy test-id query: a string matches the whole id, case-sensitive; a RegExp tests it. */
|
|
29
|
-
getByTestId(id: TextMatch, options?: { visible?: boolean }): Locator;
|
|
30
|
-
/** Taps a viewport point, with no node behind it. */
|
|
31
|
-
tapAt(point: Point, options?: ActionOptions): Promise<void>;
|
|
32
|
-
/** Performs a viewport-level swipe: in a direction, or along a path from one point to another. */
|
|
33
|
-
swipe(options: SwipeOptions | SwipePathOptions): Promise<void>;
|
|
34
|
-
/** Scrolls until a locator resolves visibly or times out. */
|
|
35
|
-
scrollUntilVisible(
|
|
36
|
-
target: Locator,
|
|
37
|
-
options?: { direction?: ScrollDirection; momentum?: Momentum; timeout?: number },
|
|
38
|
-
): Promise<void>;
|
|
39
|
-
}
|
|
9
|
+
The `screen` fixture builds locators. A `Locator` stores a query and resolves
|
|
10
|
+
it when an action, read, or assertion runs. `Locator` extends `Screen`, so
|
|
11
|
+
every query also runs on a locator, scoped to its subtree.
|
|
12
|
+
|
|
13
|
+
```ts title="tests/invoices.e2e.ts"
|
|
14
|
+
import { expect, test } from 'e2e';
|
|
15
|
+
|
|
16
|
+
test('void an invoice', async ({ app, screen }) => {
|
|
17
|
+
await app.open('/invoices');
|
|
18
|
+
const row = screen.getByRole('listitem').filter({ hasText: 'Invoice 42' });
|
|
19
|
+
await row.getByRole('button', 'Void').tap();
|
|
20
|
+
await expect(row.getByText('Voided')).toBeVisible();
|
|
21
|
+
});
|
|
40
22
|
```
|
|
41
23
|
|
|
42
|
-
|
|
43
|
-
until the action timeout, as an action does, then returns the current value
|
|
44
|
-
without waiting for it to change. A node the engine reports stale (a resolve
|
|
45
|
-
that raced a navigation, or a match the app replaced while it was read) is
|
|
46
|
-
re-resolved within the same timeout. Use [`expect`](/reference/expect) to
|
|
47
|
-
wait for a value.
|
|
24
|
+
## Reference
|
|
48
25
|
|
|
49
|
-
|
|
50
|
-
| --- | --- | --- |
|
|
51
|
-
| Actions | Poll until the timeout, then `LOCATOR_NOT_FOUND` | `LOCATOR_AMBIGUOUS` |
|
|
52
|
-
| Reads | Poll until the timeout, then `LOCATOR_NOT_FOUND` | `LOCATOR_AMBIGUOUS` |
|
|
53
|
-
| `count()`, `all()`, `allTextContents()` | `0`, `[]`, `[]` | The count, one locator per match, one text per match |
|
|
54
|
-
| `isVisible()`, `isHidden()` | `false`, `true` | `LOCATOR_AMBIGUOUS` |
|
|
55
|
-
| Assertions | Poll until the timeout | `LOCATOR_AMBIGUOUS` |
|
|
56
|
-
| 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 |
|
|
26
|
+
### Queries
|
|
57
27
|
|
|
58
|
-
|
|
28
|
+
| Method | Returns |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| [`getByRole`](#getbyrole) | `Locator` |
|
|
31
|
+
| [`getByLabel`](#getbylabel) | `Locator` |
|
|
32
|
+
| [`getByPlaceholder`](#getbyplaceholder) | `Locator` |
|
|
33
|
+
| [`getByText`](#getbytext) | `Locator` |
|
|
34
|
+
| [`getByDisplayValue`](#getbydisplayvalue) | `Locator` |
|
|
35
|
+
| [`getByTestId`](#getbytestid) | `Locator` |
|
|
59
36
|
|
|
60
37
|
Every query returns a `Locator` synchronously and performs no work. An
|
|
61
38
|
option key the query does not take (`{ nam: 'Save' }`) throws
|
|
62
39
|
`INVALID_LOCATOR` naming the keys it does.
|
|
63
40
|
|
|
64
|
-
`getByRole` takes the accessible name as its second argument, a string or a
|
|
65
|
-
`RegExp`, with the other options after it:
|
|
66
|
-
`getByRole('button', 'Save', { exact: false })`. The object form,
|
|
67
|
-
`getByRole('button', { name: 'Save', disabled: true })`, is the same query.
|
|
68
|
-
|
|
69
41
|
A string passed to `getByRole` as the name, `getByLabel`, `getByPlaceholder`,
|
|
70
42
|
`getByText`, or `getByDisplayValue` matches the whole text, case-sensitive,
|
|
71
|
-
after whitespace normalization: `getByRole('button', 'Save')` does
|
|
72
|
-
|
|
73
|
-
|
|
43
|
+
after whitespace normalization: `getByRole('button', 'Save')` does not match
|
|
44
|
+
`Save changes`. `{ exact: false }` makes it a case-insensitive substring
|
|
45
|
+
match, and a `RegExp` matches as written.
|
|
46
|
+
|
|
47
|
+
Every query accepts `visible: true`; see
|
|
48
|
+
[Hidden duplicates](#hidden-duplicates).
|
|
74
49
|
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
50
|
+
#### `getByRole`
|
|
51
|
+
|
|
52
|
+
Matches nodes by [role](#types), optionally narrowed to an accessible name
|
|
53
|
+
and to states.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
/** Creates a lazy role query. An alias such as `img` is rewritten to its role. */
|
|
57
|
+
getByRole(role: Role | RoleAlias, options?: RoleOptions): Locator;
|
|
58
|
+
/** Creates a lazy role query narrowed to an accessible name: `getByRole('button', 'Sign in')`. */
|
|
59
|
+
getByRole(role: Role | RoleAlias, name: TextMatch, options?: Omit<RoleOptions, 'name'>): Locator;
|
|
60
|
+
```
|
|
80
61
|
|
|
81
62
|
```ts
|
|
82
63
|
screen.getByRole('button', 'Save');
|
|
64
|
+
screen.getByRole('button', 'Save', { exact: false });
|
|
65
|
+
screen.getByRole('button', { name: 'Save', disabled: true }); // the object form
|
|
83
66
|
screen.getByRole('heading', /welcome/i, { level: 1 });
|
|
84
|
-
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The accessible name goes second, a string or a `RegExp`, with the other
|
|
70
|
+
options after it. The object form, `{ name: 'Save' }`, is the same query.
|
|
71
|
+
The options are [`RoleOptions`](#types).
|
|
72
|
+
|
|
73
|
+
<Note>
|
|
74
|
+
A role query never matches a node hidden from the accessibility tree: one
|
|
75
|
+
that does not render, or one under `aria-hidden`. On the other query kinds,
|
|
76
|
+
`visible: true` keeps only nodes that render, the same check `toBeVisible()`
|
|
77
|
+
makes, so an `aria-hidden` node that still paints counts as visible. See
|
|
78
|
+
[Hidden duplicates](#hidden-duplicates).
|
|
79
|
+
</Note>
|
|
80
|
+
|
|
81
|
+
##### `img` alias
|
|
82
|
+
|
|
83
|
+
`img` is an alias, not a role ([`RoleAlias`](#types)).
|
|
84
|
+
`getByRole('img', { name })` builds the same query as
|
|
85
|
+
`getByRole('image', { name })`, so a test ported from Playwright keeps its
|
|
86
|
+
spelling. On the web, `image` covers `<img>` with alt text and any element
|
|
87
|
+
with `role="img"`. An `<img alt="">` is decoration and matches nothing.
|
|
88
|
+
|
|
89
|
+
##### Roles on the web
|
|
90
|
+
|
|
91
|
+
The web engine derives the roles from HTML the way HTML-AAM does:
|
|
92
|
+
|
|
93
|
+
- `<progress>` is `progressbar`, `<meter>` is `meter`, `<hr>` is `separator`.
|
|
94
|
+
- `<fieldset>` and `<details>` are `group`.
|
|
95
|
+
- `<input type="number">` is `spinbutton`. `<input type="file">` is `button`,
|
|
96
|
+
as Chrome and Playwright read it.
|
|
97
|
+
- `<select size="3">` is a `listbox`, like a `multiple` one. An
|
|
98
|
+
`<input list>` naming a `<datalist>` is a `combobox`.
|
|
99
|
+
- A `<td>` of a `role="grid"` table is a `gridcell`.
|
|
100
|
+
- `<thead>` and `<tbody>` are `rowgroup`.
|
|
101
|
+
- `<th scope="row">` is `rowheader`. A scope-less `<th>` beside a data cell
|
|
102
|
+
with text or children is also a `rowheader`; header-only rows keep
|
|
103
|
+
`columnheader`. A lone scope-less header in a one-row table has no header
|
|
104
|
+
role, but an explicit `scope="col"` or `scope="colgroup"` keeps
|
|
105
|
+
`columnheader`.
|
|
106
|
+
- `<figure>` is `figure`, `<article>` is `article`.
|
|
107
|
+
- `<header>` and `<footer>` are `banner` and `contentinfo` only outside
|
|
108
|
+
sectioning content or an element with a sectioning role (`article`,
|
|
109
|
+
`complementary`, `main`, `navigation`, `region`).
|
|
110
|
+
- `<form>` and `<section>` are `form` and `region` only when they carry an
|
|
111
|
+
`aria-label`, `aria-labelledby`, or `title`.
|
|
112
|
+
- `<menu>` is a `list`; only `role="menu"` is a `menu`.
|
|
113
|
+
|
|
114
|
+
Names follow the same rules:
|
|
115
|
+
|
|
116
|
+
- A `<legend>`, `<figcaption>`, or `<caption>` names its fieldset, figure, or
|
|
117
|
+
table, so `toHaveAccessibleName` reads what `getByRole('group', { name })`
|
|
118
|
+
matched.
|
|
119
|
+
- A role that accname names from content (a button, link, heading, tab, menu
|
|
120
|
+
item, option, checkbox, radio, switch, cell, row or column header, row, tree
|
|
121
|
+
item, tooltip) takes its name from its descendants, a custom element's
|
|
122
|
+
shadow tree included. So `getByRole('checkbox', { name })` and
|
|
123
|
+
`toHaveAccessibleName(name)` agree on a `<div role="checkbox">`.
|
|
124
|
+
- An `<input type="submit">` or `type="reset"` with no `value` reads `Submit`
|
|
125
|
+
or `Reset`.
|
|
126
|
+
|
|
127
|
+
##### Roles on a device
|
|
128
|
+
|
|
129
|
+
A device engine maps the platform types it has: a tab bar or segmented
|
|
130
|
+
control is a `tablist`, a progress indicator a `progressbar`, a stepper a
|
|
131
|
+
`spinbutton`. It keeps any other type verbatim, so a role with no native
|
|
132
|
+
widget, such as `tooltip` or `figure`, matches nothing on a phone.
|
|
133
|
+
|
|
134
|
+
##### Editing hosts
|
|
135
|
+
|
|
136
|
+
A `contenteditable` editing host, the root a TipTap, ProseMirror, Lexical, or
|
|
137
|
+
Slate editor renders into, is a `textbox`.
|
|
138
|
+
|
|
139
|
+
- Its value is its text as rendered. While it has focus, its `selection` is
|
|
140
|
+
the text selected inside it. The value keeps spaces and newlines, so
|
|
141
|
+
`toHaveValue` and `inputValue()` read a `white-space: pre-wrap` editor like
|
|
142
|
+
a textarea.
|
|
143
|
+
- An empty editor reads as `''`: one holding only the `<p><br></p>` or the
|
|
144
|
+
zero-width no-break space its editor pads an empty line with. That padding
|
|
145
|
+
is never part of a value.
|
|
146
|
+
- It is named by its `aria-label`, `aria-placeholder`, or the
|
|
147
|
+
`data-placeholder` its editor paints. The blocks inside it are content, not
|
|
148
|
+
textboxes.
|
|
149
|
+
- The tag does not matter: an `<article contenteditable>` is the editor, not
|
|
150
|
+
an article. A native control or a drawn surface made editable keeps its own
|
|
151
|
+
role.
|
|
152
|
+
- A name from a block's `data-placeholder` (TipTap, ProseMirror) goes once
|
|
153
|
+
the editor has content, as the painted hint does. From then on the host is
|
|
154
|
+
an unnamed `textbox`. A name from the host's own `aria-label`,
|
|
155
|
+
`aria-labelledby`, `aria-placeholder`, or `data-placeholder` (Quill) stays.
|
|
156
|
+
|
|
157
|
+
`getByRole` resolves through Playwright's role selector, which has no such
|
|
158
|
+
rule. So `getByRole` finds an editor only when the host carries
|
|
159
|
+
`role="textbox"`; `getByLabel` and `getByTestId` reach it either way. Anchor
|
|
160
|
+
a test on a label or a test id, not on the placeholder.
|
|
161
|
+
|
|
162
|
+
#### `getByLabel`
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
/** Creates a lazy accessible-label query. */
|
|
166
|
+
getByLabel(text: TextMatch, options?: TextMatchOptions): Locator;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
await screen.getByLabel('Email').fill('ada@example.test');
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### `getByPlaceholder`
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
/** Creates a lazy placeholder query. */
|
|
177
|
+
getByPlaceholder(text: TextMatch, options?: TextMatchOptions): Locator;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
await screen.getByPlaceholder('Search memory...').fill('launch');
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `getByText`
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
/** Creates a lazy visible-text query. */
|
|
188
|
+
getByText(text: TextMatch, options?: TextMatchOptions): Locator;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```ts
|
|
85
192
|
screen.getByText('Welcome back', { exact: false });
|
|
86
|
-
screen.getByTestId('todo-3');
|
|
87
193
|
```
|
|
88
194
|
|
|
89
|
-
|
|
195
|
+
##### Echoed text
|
|
90
196
|
|
|
91
197
|
`getByText` answers with the innermost matching node, so a container that
|
|
92
|
-
echoes its child's text counts once. Browsers do this natively
|
|
198
|
+
echoes its child's text counts once. Browsers do this natively. On a device
|
|
93
199
|
the engine applies the same rule, since iOS reports a React Native `Text` as
|
|
94
200
|
a host view plus a `StaticText` child with the same label. Two matches that
|
|
95
201
|
do not contain each other remain `LOCATOR_AMBIGUOUS`.
|
|
96
202
|
|
|
97
|
-
|
|
203
|
+
#### `getByDisplayValue`
|
|
204
|
+
|
|
205
|
+
Matches controls by their displayed value.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
/** Creates a lazy displayed-value query. */
|
|
209
|
+
getByDisplayValue(value: TextMatch, options?: TextMatchOptions): Locator;
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
await screen.getByDisplayValue('ada@example.test').clear();
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
<Note>
|
|
217
|
+
In the web engine a `getByDisplayValue` locator cannot be the scope of a
|
|
218
|
+
child query or a `has` filter; both fail with `UNSUPPORTED_CAPABILITY`.
|
|
219
|
+
Playwright has no selector for a control's current value, so the engine
|
|
220
|
+
filters candidates after the fact.
|
|
221
|
+
</Note>
|
|
222
|
+
|
|
223
|
+
#### `getByTestId`
|
|
224
|
+
|
|
225
|
+
Matches the node's `testId`.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
/** Creates a lazy test-id query: a string matches the whole id, case-sensitive; a RegExp tests it. An engine may also match an id without its platform's namespacing. */
|
|
229
|
+
getByTestId(id: TextMatch, options?: { visible?: boolean }): Locator;
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
screen.getByTestId('todo-3');
|
|
234
|
+
screen.getByTestId(/total-budgeted$/);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
- On the web, `testId` is the value of the engine's test-id attribute
|
|
238
|
+
(`web({ testIdAttribute })`, default `data-testid`).
|
|
239
|
+
- On a device, it is the accessibility identifier or resource id. For
|
|
240
|
+
namespacing, see the mobile engine's
|
|
241
|
+
[Android resource ids](/reference/mobile#capabilities).
|
|
242
|
+
- `visible` is the only option.
|
|
243
|
+
|
|
244
|
+
#### Hidden duplicates
|
|
98
245
|
|
|
99
246
|
Every query accepts `visible: true`, which drops nodes the platform reports
|
|
100
247
|
as hidden before the exactly-one rule runs. Frameworks often keep a hidden
|
|
@@ -110,91 +257,142 @@ await screen.getByTestId('memory-list', { visible: true }).getByRole('listitem')
|
|
|
110
257
|
```
|
|
111
258
|
|
|
112
259
|
`visible` uses the same visibility check as `toBeVisible()`, `isVisible()`,
|
|
113
|
-
and `waitFor()`: what renders, as Playwright reads it.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`
|
|
260
|
+
and `waitFor()`: what renders, as Playwright reads it.
|
|
261
|
+
|
|
262
|
+
- Hidden: `display: none`, `visibility: hidden`, a box with no size, a closed
|
|
263
|
+
`<details>` body, and content under `content-visibility: hidden`.
|
|
264
|
+
- Not hidden: `aria-hidden`, since a decorative spinner marked with it still
|
|
265
|
+
paints, so `toBeHidden()` on one waits until it leaves.
|
|
266
|
+
- A child that sets `visibility: visible` under a hidden parent is visible.
|
|
267
|
+
- On the web, text directly under a `display: contents` element with
|
|
268
|
+
`visibility: hidden` is hidden, where Playwright calls it visible.
|
|
269
|
+
- It works with scopes, `filter`, `first`, `last`, and `nth`:
|
|
270
|
+
`getByText('Save', { visible: true }).first()` is the first visible match.
|
|
271
|
+
|
|
122
272
|
Prefer it over `browser.locator('p:visible')`, which ties the test to CSS.
|
|
123
273
|
|
|
124
|
-
|
|
274
|
+
### Refinement
|
|
125
275
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
276
|
+
| Method | Returns |
|
|
277
|
+
| --- | --- |
|
|
278
|
+
| [`filter`](#filter) | `Locator` |
|
|
279
|
+
| [`first`](#first) | `Locator` |
|
|
280
|
+
| [`last`](#last) | `Locator` |
|
|
281
|
+
| [`nth`](#nth) | `Locator` |
|
|
131
282
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
283
|
+
Refinement returns a new locator without querying the app.
|
|
284
|
+
|
|
285
|
+
#### `filter`
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
/** Adds deterministic locator filters. */
|
|
289
|
+
filter(options: { hasText?: TextMatch; has?: Locator }): Locator;
|
|
290
|
+
```
|
|
137
291
|
|
|
138
292
|
```ts
|
|
139
293
|
const row = screen.getByRole('listitem').filter({ hasText: 'Invoice 42' });
|
|
140
294
|
await row.getByRole('button', 'Void').tap();
|
|
141
295
|
```
|
|
142
296
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
297
|
+
- `hasText` with a string is a case-insensitive substring match over the
|
|
298
|
+
match and its descendants, unlike a query text, which matches exactly. Pass
|
|
299
|
+
a `RegExp` for anything stricter.
|
|
300
|
+
- `has` keeps a match whose subtree resolves the inner locator.
|
|
301
|
+
|
|
302
|
+
| Throws | When |
|
|
303
|
+
| --- | --- |
|
|
304
|
+
| `INVALID_LOCATOR` | Neither `hasText` nor `has` is given. |
|
|
305
|
+
| `INVALID_LOCATOR` | Any other key, even beside them: `hasNot`, `hasNotText`, and `visible` are not filters here. |
|
|
306
|
+
| `INVALID_LOCATOR` | `has` is not a runner locator, or was made by another target's screen. |
|
|
149
307
|
|
|
150
|
-
|
|
308
|
+
#### `first`
|
|
151
309
|
|
|
152
310
|
```ts
|
|
153
|
-
|
|
311
|
+
/** Selects the first current match. */
|
|
312
|
+
first(): Locator;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
await screen.getByRole('listitem').first().tap();
|
|
154
317
|
```
|
|
155
318
|
|
|
156
|
-
|
|
157
|
-
with no node behind it: a pin on a canvas, a control the tree does not
|
|
158
|
-
list. Nothing is resolved or waited for; the page decides what the tap
|
|
159
|
-
lands on. Uses `config.actionTimeout`. Throws `INVALID_ARGUMENT` for a
|
|
160
|
-
negative or non-finite coordinate and `UNSUPPORTED_CAPABILITY` when the
|
|
161
|
-
engine does not list `tap` in its pointer actions.
|
|
319
|
+
#### `last`
|
|
162
320
|
|
|
163
321
|
```ts
|
|
164
|
-
|
|
322
|
+
/** Selects the last current match. */
|
|
323
|
+
last(): Locator;
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
await screen.getByRole('listitem').last().tap();
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### `nth`
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
/** Selects one zero-based current match. */
|
|
334
|
+
nth(index: number): Locator;
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
await screen.getByRole('row').nth(2).getByRole('checkbox').check();
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
| Throws | When |
|
|
342
|
+
| --- | --- |
|
|
343
|
+
| `INVALID_LOCATOR` | A negative or non-integer index. |
|
|
344
|
+
|
|
345
|
+
### Screen actions
|
|
346
|
+
|
|
347
|
+
| Method | Returns |
|
|
348
|
+
| --- | --- |
|
|
349
|
+
| [`screen.tapAt`](#screen-tapat) | `Promise<void>` |
|
|
350
|
+
| [`screen.swipe`](#screen-swipe) | `Promise<void>` |
|
|
351
|
+
| [`screen.scrollUntilVisible`](#screen-scrolluntilvisible) | `Promise<void>` |
|
|
352
|
+
|
|
353
|
+
`tapAt` and `swipe` act on the viewport.
|
|
354
|
+
|
|
355
|
+
#### `screen.tapAt`
|
|
356
|
+
|
|
357
|
+
Use it for a pin on a canvas or a control the tree does not list.
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
/** Taps a viewport point, with no node behind it. */
|
|
361
|
+
tapAt(point: Point, options?: ActionOptions): Promise<void>;
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
const map = (await screen.getByRole('image', 'Map').boundingBox())!;
|
|
165
366
|
await screen.tapAt({ x: map.x + 360, y: map.y + 70 });
|
|
166
367
|
```
|
|
167
368
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
369
|
+
- The point is in CSS pixels, the space `boundingBox()` reports in.
|
|
370
|
+
- Nothing is resolved or waited for; the page decides what the tap lands on.
|
|
371
|
+
- Uses `config.actionTimeout`.
|
|
372
|
+
- Prefer a locator when the tree lists the control, since a point moves with
|
|
373
|
+
the layout. For a point inside a node, `tap({ position })` reads the node's
|
|
374
|
+
box for you.
|
|
171
375
|
|
|
172
|
-
|
|
376
|
+
| Throws | When |
|
|
377
|
+
| --- | --- |
|
|
378
|
+
| `INVALID_ARGUMENT` | A negative or non-finite coordinate. |
|
|
379
|
+
| `UNSUPPORTED_CAPABILITY` | The engine does not list `tap` in its pointer actions. |
|
|
380
|
+
|
|
381
|
+
<Note>
|
|
382
|
+
`tapAt` is not scoped: on a locator it still taps a viewport point.
|
|
383
|
+
</Note>
|
|
384
|
+
|
|
385
|
+
#### `screen.swipe`
|
|
173
386
|
|
|
174
387
|
```ts
|
|
388
|
+
/** Performs a viewport-level swipe: in a direction, or along a path from one point to another. */
|
|
175
389
|
swipe(options: SwipeOptions | SwipePathOptions): Promise<void>;
|
|
176
390
|
```
|
|
177
391
|
|
|
178
|
-
Viewport-level gesture, in one of two shapes. `{ direction, momentum? }` is
|
|
179
|
-
a scroll gesture on the screen. `direction` is the way the content scrolls,
|
|
180
|
-
not the finger: `'down'` reveals what is below. `momentum` is its fling
|
|
181
|
-
strength, `'none'` by default: `'none'` a plain drag of half the surface,
|
|
182
|
-
`'slow'` a short fling, `'fast'` a long one. How far each carries is the
|
|
183
|
-
engine's.
|
|
184
|
-
`{ from, to, duration? }` is a swipe along a path between two viewport points:
|
|
185
|
-
a touch swipe on a device, a pointer drag on a document platform, which is
|
|
186
|
-
what reveals a row action, dismisses a sheet, or drags a card between
|
|
187
|
-
columns. `duration` sets how long the gesture takes in milliseconds; omitted
|
|
188
|
-
leaves the duration to the engine's default. Uses `config.actionTimeout`.
|
|
189
|
-
Throws `ACTION_FAILED` when the gesture is rejected, `INVALID_ARGUMENT` for
|
|
190
|
-
a mix of the two shapes, an out-of-range duration, or a coordinate off the
|
|
191
|
-
plane, and `UNSUPPORTED_CAPABILITY` when the engine does not list `swipe` in
|
|
192
|
-
its actions (a direction) or `swipeTo` in its pointer actions (a path).
|
|
193
|
-
|
|
194
392
|
```ts
|
|
195
393
|
await screen.swipe({ direction: 'up', momentum: 'fast' });
|
|
196
394
|
|
|
197
|
-
const row = await screen.getByText('Design review').boundingBox()
|
|
395
|
+
const row = (await screen.getByText('Design review').boundingBox())!;
|
|
198
396
|
await screen.swipe({
|
|
199
397
|
from: { x: row.x + row.width - 10, y: row.y + row.height / 2 },
|
|
200
398
|
to: { x: row.x + 10, y: row.y + row.height / 2 },
|
|
@@ -202,263 +400,663 @@ await screen.swipe({
|
|
|
202
400
|
});
|
|
203
401
|
```
|
|
204
402
|
|
|
205
|
-
|
|
403
|
+
`{ direction, momentum? }` is a scroll gesture on the screen:
|
|
404
|
+
|
|
405
|
+
- `direction` is the way the content scrolls, not the finger: `'down'`
|
|
406
|
+
reveals what is below.
|
|
407
|
+
- `momentum` is its fling strength, `'none'` by default. `'none'` is a
|
|
408
|
+
plain drag of half the surface, `'slow'` a short fling, `'fast'` a long
|
|
409
|
+
one. The engine decides how far each carries.
|
|
410
|
+
|
|
411
|
+
`{ from, to, duration? }` is a swipe along a path between two viewport
|
|
412
|
+
points: a touch swipe on a device, a pointer drag on a document platform.
|
|
413
|
+
Use it to reveal a row action, dismiss a sheet, or drag a card between
|
|
414
|
+
columns.
|
|
415
|
+
|
|
416
|
+
- `duration` is how long the gesture takes, an integer from 16 through
|
|
417
|
+
10000 ms. Omitted, it is the engine's default.
|
|
418
|
+
|
|
419
|
+
Uses `config.actionTimeout` and takes no `timeout` option.
|
|
420
|
+
|
|
421
|
+
| Throws | When |
|
|
422
|
+
| --- | --- |
|
|
423
|
+
| `ACTION_FAILED` | The gesture is rejected. |
|
|
424
|
+
| `INVALID_ARGUMENT` | A mix of the two shapes, an out-of-range duration, a coordinate off the plane, or a `timeout` key. |
|
|
425
|
+
| `UNSUPPORTED_CAPABILITY` | The engine does not list `swipe` in its actions (a direction) or `swipeTo` in its pointer actions (a path). |
|
|
426
|
+
|
|
427
|
+
#### `screen.scrollUntilVisible`
|
|
206
428
|
|
|
207
429
|
```ts
|
|
430
|
+
/**
|
|
431
|
+
* Scrolls until a locator resolves visibly or times out: the viewport on
|
|
432
|
+
* `screen`, the node itself on a locator. `momentum` is the stride of each
|
|
433
|
+
* step, `slow` unless told otherwise.
|
|
434
|
+
*/
|
|
208
435
|
scrollUntilVisible(
|
|
209
436
|
target: Locator,
|
|
210
437
|
options?: { direction?: ScrollDirection; momentum?: Momentum; timeout?: number },
|
|
211
438
|
): Promise<void>;
|
|
212
439
|
```
|
|
213
440
|
|
|
214
|
-
|
|
441
|
+
```ts
|
|
442
|
+
await screen.scrollUntilVisible(screen.getByRole('button', 'Accept'));
|
|
443
|
+
await screen.getByTestId('feed').scrollUntilVisible(row);
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
| Option | Type | Default |
|
|
215
447
|
| --- | --- | --- |
|
|
216
448
|
| `target` | `Locator` | - |
|
|
217
449
|
| `options.direction` | `ScrollDirection` | `'down'` |
|
|
218
|
-
| `options.momentum` | `Momentum` | `'slow'
|
|
219
|
-
| `options.timeout` | `number` | `30000
|
|
450
|
+
| `options.momentum` | `Momentum` | `'slow'` |
|
|
451
|
+
| `options.timeout` | `number` | `30000` |
|
|
452
|
+
|
|
453
|
+
- `momentum` is as [`screen.swipe`](#screen-swipe) defines it. A longer step
|
|
454
|
+
is faster, but it must not carry the target past the surface unseen, as a
|
|
455
|
+
virtualized list does.
|
|
456
|
+
- `timeout` is its own default, not `config.actionTimeout`. It bounds the
|
|
457
|
+
whole scroll, each swipe included.
|
|
458
|
+
- On a locator each step swipes that node. So a feed or a table that is its
|
|
459
|
+
own scroll container pages without the pointer hovering it first.
|
|
460
|
+
|
|
461
|
+
| Throws | When |
|
|
462
|
+
| --- | --- |
|
|
463
|
+
| `INVALID_LOCATOR` | `target` is not a runner locator, or was made by another target's screen. |
|
|
464
|
+
| `INVALID_ARGUMENT` | An option it does not take. |
|
|
465
|
+
| `LOCATOR_NOT_FOUND` | The deadline expires first, a swipe it cut short included. |
|
|
466
|
+
|
|
467
|
+
### Locator actions
|
|
468
|
+
|
|
469
|
+
| Method | Returns |
|
|
470
|
+
| --- | --- |
|
|
471
|
+
| [`tap`](#tap) | `Promise<void>` |
|
|
472
|
+
| [`click`](#tap) | `Promise<void>` |
|
|
473
|
+
| [`doubleTap`](#doubletap) | `Promise<void>` |
|
|
474
|
+
| [`dblclick`](#doubletap) | `Promise<void>` |
|
|
475
|
+
| [`secondaryTap`](#secondarytap) | `Promise<void>` |
|
|
476
|
+
| [`longPress`](#longpress) | `Promise<void>` |
|
|
477
|
+
| [`fill`](#fill) | `Promise<void>` |
|
|
478
|
+
| [`pressSequentially`](#presssequentially) | `Promise<void>` |
|
|
479
|
+
| [`clear`](#clear) | `Promise<void>` |
|
|
480
|
+
| [`press`](#press) | `Promise<void>` |
|
|
481
|
+
| [`check`](#check) | `Promise<void>` |
|
|
482
|
+
| [`uncheck`](#uncheck) | `Promise<void>` |
|
|
483
|
+
| [`selectOption`](#selectoption) | `Promise<void>` |
|
|
484
|
+
| [`focus`](#focus) | `Promise<void>` |
|
|
485
|
+
| [`hover`](#hover) | `Promise<void>` |
|
|
486
|
+
| [`setInputFiles`](#setinputfiles) | `Promise<void>` |
|
|
487
|
+
| [`dragTo`](#dragto) | `Promise<void>` |
|
|
488
|
+
| [`scrollIntoView`](#scrollintoview) | `Promise<void>` |
|
|
489
|
+
| [`swipe`](#swipe) | `Promise<void>` |
|
|
220
490
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
`
|
|
491
|
+
Each action resolves one node, waits for actionability, and performs one
|
|
492
|
+
operation. The timeout is
|
|
493
|
+
[`config.actionTimeout`](/reference/config#actiontimeout), and every action
|
|
494
|
+
takes `{ timeout }` to override it. An action takes only the options its
|
|
495
|
+
signature names.
|
|
225
496
|
|
|
226
|
-
Throws
|
|
227
|
-
|
|
228
|
-
`
|
|
229
|
-
included.
|
|
497
|
+
| Throws | When |
|
|
498
|
+
| --- | --- |
|
|
499
|
+
| `UNSUPPORTED_CAPABILITY` | The engine does not list the action's kind in `actions`; thrown before reaching the engine. |
|
|
500
|
+
| `INVALID_ARGUMENT` | An option key the action does not take, Playwright's `trial`, `force`, or `noWaitAfter` included. Thrown before the node is resolved, so the control is never touched. |
|
|
501
|
+
| `LOCATOR_NOT_FOUND` | No match before the timeout. |
|
|
502
|
+
| `LOCATOR_AMBIGUOUS` | More than one match. |
|
|
503
|
+
| `ACTION_FAILED` | The action may already have committed; it is never repeated. |
|
|
504
|
+
|
|
505
|
+
#### `tap`
|
|
230
506
|
|
|
231
507
|
```ts
|
|
232
|
-
|
|
508
|
+
/** Taps exactly one matching actionable node; with `position`, taps that point of its box once it is in view, with no other actionability check. */
|
|
509
|
+
tap(options?: TapOptions): Promise<void>;
|
|
510
|
+
/** Alias of tap. */
|
|
511
|
+
click(options?: TapOptions): Promise<void>;
|
|
233
512
|
```
|
|
234
513
|
|
|
235
|
-
|
|
514
|
+
```ts
|
|
515
|
+
await screen.getByRole('button', 'Save').tap({ timeout: 10_000 });
|
|
516
|
+
await screen.getByRole('image', 'Seat map').tap({ position: { x: 120, y: 48 } });
|
|
517
|
+
await row.click({ modifiers: ['Shift'] });
|
|
518
|
+
```
|
|
236
519
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
/** Sends one key to exactly one node. */
|
|
266
|
-
press(key: string, options?: ActionOptions): Promise<void>;
|
|
267
|
-
/** Checks exactly one node. */
|
|
268
|
-
check(options?: ActionOptions): Promise<void>;
|
|
269
|
-
/** Unchecks exactly one node. */
|
|
270
|
-
uncheck(options?: ActionOptions): Promise<void>;
|
|
271
|
-
/** Selects one option from exactly one control. */
|
|
272
|
-
selectOption(value: SelectOption, options?: ActionOptions): Promise<void>;
|
|
273
|
-
/** Focuses exactly one node. */
|
|
274
|
-
focus(options?: ActionOptions): Promise<void>;
|
|
275
|
-
/** Hovers over exactly one matching actionable node. */
|
|
276
|
-
hover(options?: ActionOptions): Promise<void>;
|
|
277
|
-
/** Sets the files of exactly one file input; paths resolve from the project root. */
|
|
278
|
-
setInputFiles(paths: string | readonly string[], options?: ActionOptions): Promise<void>;
|
|
279
|
-
/** Drags exactly one node to exactly one target. */
|
|
280
|
-
dragTo(target: Locator, options?: ActionOptions): Promise<void>;
|
|
281
|
-
/** Scrolls exactly one node into view. */
|
|
282
|
-
scrollIntoView(options?: ActionOptions): Promise<void>;
|
|
283
|
-
/** Performs a swipe scoped to exactly one node. */
|
|
284
|
-
swipe(options: SwipeOptions & ActionOptions): Promise<void>;
|
|
285
|
-
/** Reads normalized text once a match exists, without retrying for a particular value. */
|
|
286
|
-
textContent(): Promise<string | null>;
|
|
287
|
-
/** Reads the current input value. */
|
|
288
|
-
inputValue(): Promise<string>;
|
|
289
|
-
/** Reads one attribute. */
|
|
290
|
-
getAttribute(name: string): Promise<string | null>;
|
|
291
|
-
/** Reads current visibility. */
|
|
292
|
-
isVisible(): Promise<boolean>;
|
|
293
|
-
/** Reads current hidden or absent state, the negation of `isVisible`. */
|
|
294
|
-
isHidden(): Promise<boolean>;
|
|
295
|
-
/** Reads current enabled state. */
|
|
296
|
-
isEnabled(): Promise<boolean>;
|
|
297
|
-
/** Reads current disabled state, the negation of `isEnabled`. */
|
|
298
|
-
isDisabled(): Promise<boolean>;
|
|
299
|
-
/** Reads current checked state. */
|
|
300
|
-
isChecked(): Promise<boolean>;
|
|
301
|
-
/** Reads the current viewport-relative rectangle. */
|
|
302
|
-
boundingBox(): Promise<{ x: number; y: number; width: number; height: number } | null>;
|
|
303
|
-
/** Counts current matches without auto-waiting. */
|
|
304
|
-
count(): Promise<number>;
|
|
305
|
-
/** One `nth(i)` locator per current match, without auto-waiting; empty when nothing matches. */
|
|
306
|
-
all(): Promise<Locator[]>;
|
|
307
|
-
/** Reads the normalized text of every current match, without auto-waiting; empty when nothing matches. */
|
|
308
|
-
allTextContents(): Promise<string[]>;
|
|
309
|
-
/**
|
|
310
|
-
* Waits for the requested locator state, `visible` by default: `attached`
|
|
311
|
-
* for one match, visible or not; `detached` for none; `hidden` for none or
|
|
312
|
-
* a hidden one.
|
|
313
|
-
*/
|
|
314
|
-
waitFor(options?: { state?: 'attached' | 'detached' | 'visible' | 'hidden'; timeout?: number }): Promise<void>;
|
|
315
|
-
/** Adds deterministic locator filters. */
|
|
316
|
-
filter(options: { hasText?: TextMatch; has?: Locator }): Locator;
|
|
317
|
-
/** Selects the first current match. */
|
|
318
|
-
first(): Locator;
|
|
319
|
-
/** Selects the last current match. */
|
|
320
|
-
last(): Locator;
|
|
321
|
-
/** Selects one zero-based current match. */
|
|
322
|
-
nth(index: number): Locator;
|
|
323
|
-
}
|
|
520
|
+
The options are [`TapOptions`](#types):
|
|
521
|
+
|
|
522
|
+
- `modifiers`: the browser engine holds them.
|
|
523
|
+
- `position`: an offset from the node's top-left corner, in CSS pixels. The
|
|
524
|
+
engine scrolls the node into view when it lists `scrollIntoView`. Then it
|
|
525
|
+
reads the node's box and dispatches the pointer at the point. No node is
|
|
526
|
+
behind the gesture, so the platform's other actionability checks (enabled,
|
|
527
|
+
not covered) do not run.
|
|
528
|
+
|
|
529
|
+
| Throws | When |
|
|
530
|
+
| --- | --- |
|
|
531
|
+
| `INVALID_ARGUMENT` | An unknown or repeated modifier, or `modifiers` beside `position`. |
|
|
532
|
+
| `UNSUPPORTED_CAPABILITY` | `modifiers` on an engine that does not declare `tapModifiers`, the mobile engine among them; thrown before it acts. |
|
|
533
|
+
| `UNSUPPORTED_CAPABILITY` | `position` on an engine without the `tap` pointer action. |
|
|
534
|
+
| `ACTION_FAILED` | `position` on a node the browser engine cannot bring into view; its scroll waits for visibility. Other engines fail as they report it. |
|
|
535
|
+
| `LOCATOR_NOT_FOUND` | `position` on a node that resolves but never shows a visible box within the timeout. |
|
|
536
|
+
|
|
537
|
+
#### `doubleTap`
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
/** Double-taps exactly one matching actionable node. */
|
|
541
|
+
doubleTap(options?: ClickOptions): Promise<void>;
|
|
542
|
+
/** Alias of doubleTap. */
|
|
543
|
+
dblclick(options?: ClickOptions): Promise<void>;
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
await screen.getByRole('cell', 'Q3 budget').doubleTap();
|
|
324
548
|
```
|
|
325
549
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
share the one action deadline: a pause is cut short at it and no character
|
|
369
|
-
is sent past it, and the action fails with `ACTION_FAILED` naming how many
|
|
370
|
-
characters landed. It needs the engine's `focus` action and `keyboard`
|
|
371
|
-
capability, else `UNSUPPORTED_CAPABILITY` before any node resolves, and it
|
|
372
|
-
relies on the [keyboard contract](/reference/engine#engine): `type`
|
|
373
|
-
must deliver text as a person typing would, so key events reach the app
|
|
374
|
-
where the platform has them. A `Secret` is a type error and
|
|
375
|
-
`INVALID_ARGUMENT` at runtime: a secret is filled, never typed as
|
|
376
|
-
keystrokes. The step records the locator, not the text, like `fill`.
|
|
377
|
-
- `setInputFiles` paths resolve from the project root. An empty list or blank
|
|
378
|
-
entry throws `INVALID_ARGUMENT`.
|
|
379
|
-
- `dragTo` with a non-runner target, or one made by another target's screen,
|
|
380
|
-
throws `INVALID_LOCATOR`.
|
|
381
|
-
- `swipe` on a locator is a gesture on that one node: `{ direction,
|
|
382
|
-
momentum? }` as [`screen.swipe`](#screen-swipe) defines them, plus
|
|
383
|
-
`timeout`. It has no `{ from, to }` form. `screen.swipe` takes no
|
|
384
|
-
`timeout`: a `timeout` key there is `INVALID_ARGUMENT`.
|
|
385
|
-
- An action that may already have committed surfaces as `ACTION_FAILED` and
|
|
386
|
-
is never repeated.
|
|
550
|
+
Takes `timeout` and `modifiers`, with the same rules and errors as
|
|
551
|
+
[`tap`](#tap).
|
|
552
|
+
|
|
553
|
+
#### `secondaryTap`
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
/** Secondary-taps exactly one matching actionable node: a right click, a two-finger tap. */
|
|
557
|
+
secondaryTap(options?: ClickOptions): Promise<void>;
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
```ts
|
|
561
|
+
await screen.getByRole('row', /Invoice 42/).secondaryTap();
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Takes `timeout` and `modifiers`, with the same rules and errors as
|
|
565
|
+
[`tap`](#tap).
|
|
566
|
+
|
|
567
|
+
#### `longPress`
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
/** Long-presses exactly one matching actionable node. */
|
|
571
|
+
longPress(options?: LongPressOptions): Promise<void>;
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
await screen.getByText('Design review').longPress({ duration: 800 });
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
- `duration` is the hold time, an integer from 100 through 10000 ms.
|
|
579
|
+
Anything else is `INVALID_ARGUMENT`.
|
|
580
|
+
- Without it the engine's default applies: 500 ms on the browser engine, one
|
|
581
|
+
second on the mobile engine, past a React Native `Pressable`'s
|
|
582
|
+
`delayLongPress`.
|
|
583
|
+
|
|
584
|
+
#### `fill`
|
|
585
|
+
|
|
586
|
+
Replaces the input's current content.
|
|
587
|
+
|
|
588
|
+
```ts
|
|
589
|
+
/** Fills exactly one input. Secret values are never logged. */
|
|
590
|
+
fill(value: string | Secret, options?: ActionOptions): Promise<void>;
|
|
591
|
+
```
|
|
387
592
|
|
|
388
593
|
```ts
|
|
389
594
|
await screen.getByLabel('Email').fill('ada@example.test');
|
|
595
|
+
await screen.getByLabel('Password').fill(credentials.user('member').password);
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
A `Secret` is resolved on the host right before the call and never logged,
|
|
599
|
+
reported, or sent to a model.
|
|
600
|
+
|
|
601
|
+
<Note>
|
|
602
|
+
`fill` sets the value directly and fires no key events. An autocomplete, a
|
|
603
|
+
search-as-you-type box, or a masked input wired to `keydown` or `keyup`
|
|
604
|
+
never reacts to it. Use [`pressSequentially`](#presssequentially) for those.
|
|
605
|
+
</Note>
|
|
606
|
+
|
|
607
|
+
#### `pressSequentially`
|
|
608
|
+
|
|
609
|
+
Types through the engine's keyboard and appends to the current content.
|
|
610
|
+
|
|
611
|
+
```ts
|
|
612
|
+
/**
|
|
613
|
+
* Focuses exactly one input and types `text` through the keyboard, one
|
|
614
|
+
* character at a time, so the app receives key events. A `Secret` is
|
|
615
|
+
* refused; it goes through `fill`.
|
|
616
|
+
*/
|
|
617
|
+
pressSequentially(text: string, options?: PressSequentiallyOptions): Promise<void>;
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
await screen.getByLabel('City').pressSequentially('War', { delay: 50 });
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
- `delay` waits that many milliseconds between characters, each sent in its
|
|
625
|
+
own keyboard call. Without it the whole text goes in one call.
|
|
626
|
+
- Focus, pauses, and keystrokes share the one action deadline: a pause is cut
|
|
627
|
+
short at it and no character is sent past it.
|
|
628
|
+
- It relies on the [keyboard contract](/reference/engine#engine): `type` must
|
|
629
|
+
deliver text as a person typing would, so key events reach the app where
|
|
630
|
+
the platform has them.
|
|
631
|
+
- The step records the locator, not the text, like `fill`.
|
|
632
|
+
|
|
633
|
+
| Throws | When |
|
|
634
|
+
| --- | --- |
|
|
635
|
+
| `ACTION_FAILED` | The deadline passed mid-text; the error names how many characters landed. |
|
|
636
|
+
| `UNSUPPORTED_CAPABILITY` | The engine lacks the `focus` action or the `keyboard` capability; thrown before any node resolves. |
|
|
637
|
+
| `INVALID_ARGUMENT` | A `Secret` at runtime (a type error at compile time): a secret is filled, never typed as keystrokes. |
|
|
638
|
+
|
|
639
|
+
#### `clear`
|
|
640
|
+
|
|
641
|
+
```ts
|
|
642
|
+
/** Clears exactly one input. */
|
|
643
|
+
clear(options?: ActionOptions): Promise<void>;
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
await screen.getByLabel('Search').clear();
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
#### `press`
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
/** Sends one key to exactly one node. */
|
|
654
|
+
press(key: string, options?: ActionOptions): Promise<void>;
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
```ts
|
|
658
|
+
await screen.getByRole('textbox', 'Search').press('Enter');
|
|
659
|
+
await screen.getByRole('textbox', 'Notes').press('ControlOrMeta+a');
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
A key is zero or more modifiers and one key, joined with `+`, as the
|
|
663
|
+
[key grammar](/reference/engine#keys) lists them. `Shift++` presses `+`.
|
|
664
|
+
|
|
665
|
+
| Throws | When |
|
|
666
|
+
| --- | --- |
|
|
667
|
+
| `UNSUPPORTED_CAPABILITY` | A key the platform lacks. |
|
|
668
|
+
|
|
669
|
+
#### `check`
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
/** Checks exactly one node. */
|
|
673
|
+
check(options?: ActionOptions): Promise<void>;
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
```ts
|
|
390
677
|
await screen.getByRole('checkbox', 'Remember me').check();
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
#### `uncheck`
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
/** Unchecks exactly one node. */
|
|
684
|
+
uncheck(options?: ActionOptions): Promise<void>;
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
```ts
|
|
688
|
+
await screen.getByRole('checkbox', 'Remember me').uncheck();
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
#### `selectOption`
|
|
692
|
+
|
|
693
|
+
```ts
|
|
694
|
+
/** Selects one option from exactly one control. */
|
|
695
|
+
selectOption(value: SelectOption, options?: ActionOptions): Promise<void>;
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
```ts
|
|
699
|
+
await screen.getByLabel('Plan').selectOption('Pro');
|
|
391
700
|
await screen.getByLabel('Plan').selectOption({ label: 'Pro' });
|
|
392
701
|
await screen.getByLabel('Plan').selectOption({ value: 'pro' });
|
|
393
|
-
await screen.getByRole('button', 'Save').tap({ timeout: 10_000 });
|
|
394
|
-
await screen.getByRole('image', 'Seat map').tap({ position: { x: 120, y: 48 } });
|
|
395
|
-
await screen.getByRole('textbox', 'Search').press('Enter');
|
|
396
|
-
await screen.getByRole('textbox', 'Notes').press('ControlOrMeta+a');
|
|
397
|
-
await screen.getByLabel('City').pressSequentially('War', { delay: 50 });
|
|
398
702
|
```
|
|
399
703
|
|
|
400
|
-
|
|
704
|
+
Pass a label string, or an object with exactly one of `label`, `value` (the
|
|
705
|
+
value attribute, which the form submits), or `index` (zero-based).
|
|
401
706
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
a hidden match or none. Several matches are `LOCATOR_AMBIGUOUS` in every
|
|
406
|
-
state. Any other state, or an option key besides `state` and `timeout`, is
|
|
407
|
-
`INVALID_ARGUMENT` before anything is resolved.
|
|
707
|
+
| Throws | When |
|
|
708
|
+
| --- | --- |
|
|
709
|
+
| `INVALID_ARGUMENT` | More than one option per call: an array, or an object with more than one of those keys. Also a negative or fractional `index`. Thrown before the engine acts. |
|
|
408
710
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
711
|
+
#### `focus`
|
|
712
|
+
|
|
713
|
+
```ts
|
|
714
|
+
/** Focuses exactly one node. */
|
|
715
|
+
focus(options?: ActionOptions): Promise<void>;
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
```ts
|
|
719
|
+
await screen.getByLabel('Email').focus();
|
|
720
|
+
```
|
|
415
721
|
|
|
416
|
-
|
|
417
|
-
a heading; it does not throw.
|
|
722
|
+
#### `hover`
|
|
418
723
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
724
|
+
```ts
|
|
725
|
+
/** Hovers over exactly one matching actionable node. */
|
|
726
|
+
hover(options?: ActionOptions): Promise<void>;
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
```ts
|
|
730
|
+
await screen.getByRole('button', 'Account').hover();
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
#### `setInputFiles`
|
|
734
|
+
|
|
735
|
+
```ts
|
|
736
|
+
/** Sets the files of exactly one file input; paths resolve from the project root. */
|
|
737
|
+
setInputFiles(paths: string | readonly string[], options?: ActionOptions): Promise<void>;
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
```ts
|
|
741
|
+
await screen.getByLabel('Receipt').setInputFiles('fixtures/receipt.pdf');
|
|
742
|
+
```
|
|
423
743
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
`
|
|
427
|
-
|
|
744
|
+
| Throws | When |
|
|
745
|
+
| --- | --- |
|
|
746
|
+
| `INVALID_ARGUMENT` | An empty list or a blank entry. |
|
|
747
|
+
|
|
748
|
+
#### `dragTo`
|
|
749
|
+
|
|
750
|
+
```ts
|
|
751
|
+
/** Drags exactly one node to exactly one target. */
|
|
752
|
+
dragTo(target: Locator, options?: ActionOptions): Promise<void>;
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
```ts
|
|
756
|
+
await screen.getByText('Design review').dragTo(screen.getByRole('region', 'Done'));
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
| Throws | When |
|
|
760
|
+
| --- | --- |
|
|
761
|
+
| `INVALID_LOCATOR` | `target` is not a runner locator, or was made by another target's screen. |
|
|
762
|
+
|
|
763
|
+
#### `scrollIntoView`
|
|
764
|
+
|
|
765
|
+
```ts
|
|
766
|
+
/** Scrolls exactly one node into view. */
|
|
767
|
+
scrollIntoView(options?: ActionOptions): Promise<void>;
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
```ts
|
|
771
|
+
await screen.getByRole('contentinfo').scrollIntoView();
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
#### `swipe`
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
/** Performs a swipe scoped to exactly one node. */
|
|
778
|
+
swipe(options: SwipeOptions & ActionOptions): Promise<void>;
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
```ts
|
|
782
|
+
await screen.getByTestId('feed').swipe({ direction: 'down', momentum: 'slow' });
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
It takes `{ direction, momentum? }` as [`screen.swipe`](#screen-swipe)
|
|
786
|
+
defines them, plus `timeout`, which `screen.swipe` refuses. It has no
|
|
787
|
+
`{ from, to }` form.
|
|
788
|
+
|
|
789
|
+
### Locator reads
|
|
790
|
+
|
|
791
|
+
| Method | Returns |
|
|
792
|
+
| --- | --- |
|
|
793
|
+
| [`textContent`](#textcontent) | `Promise<string \| null>` |
|
|
794
|
+
| [`inputValue`](#inputvalue) | `Promise<string>` |
|
|
795
|
+
| [`getAttribute`](#getattribute) | `Promise<string \| null>` |
|
|
796
|
+
| [`isVisible`](#isvisible) | `Promise<boolean>` |
|
|
797
|
+
| [`isHidden`](#ishidden) | `Promise<boolean>` |
|
|
798
|
+
| [`isEnabled`](#isenabled) | `Promise<boolean>` |
|
|
799
|
+
| [`isDisabled`](#isdisabled) | `Promise<boolean>` |
|
|
800
|
+
| [`isChecked`](#ischecked) | `Promise<boolean>` |
|
|
801
|
+
| [`boundingBox`](#boundingbox) | `Promise<{ x, y, width, height } \| null>` |
|
|
802
|
+
| [`count`](#count) | `Promise<number>` |
|
|
803
|
+
| [`all`](#all) | `Promise<Locator[]>` |
|
|
804
|
+
| [`allTextContents`](#alltextcontents) | `Promise<string[]>` |
|
|
805
|
+
| [`waitFor`](#waitfor) | `Promise<void>` |
|
|
806
|
+
|
|
807
|
+
Reads follow the [Match rules](#match-rules). `count`, `all`, and
|
|
808
|
+
`allTextContents` take a snapshot and do not wait.
|
|
809
|
+
|
|
810
|
+
On a secure field, `textContent`, `inputValue`, `getAttribute`, and
|
|
811
|
+
`allTextContents` throw `POLICY_DENIED`, as the matching
|
|
812
|
+
[`expect` matchers](/reference/expect#secure-fields) do. `isEnabled`,
|
|
813
|
+
`isDisabled`, `isChecked`, and `boundingBox` are allowed there.
|
|
814
|
+
|
|
815
|
+
#### `textContent`
|
|
816
|
+
|
|
817
|
+
Reads the node's rendered text with whitespace collapsed to single spaces.
|
|
818
|
+
|
|
819
|
+
```ts
|
|
820
|
+
/** Reads normalized text once a match exists, without retrying for a particular value. */
|
|
821
|
+
textContent(): Promise<string | null>;
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
```ts
|
|
825
|
+
const message = await screen.getByRole('alert').textContent();
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
- On the web that is what `innerText` reads, not the DOM's `textContent`:
|
|
829
|
+
CSS `text-transform` applies, `display: none` descendants are left out, and
|
|
830
|
+
a `<br>` reads as a space.
|
|
831
|
+
- A node that is not rendered itself, such as a `display: none` one, reads
|
|
832
|
+
its DOM text, as `innerText` does.
|
|
833
|
+
|
|
834
|
+
#### `inputValue`
|
|
835
|
+
|
|
836
|
+
```ts
|
|
837
|
+
/** Reads the current input value. */
|
|
838
|
+
inputValue(): Promise<string>;
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
```ts
|
|
842
|
+
const email = await screen.getByLabel('Email').inputValue();
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
On a checkbox or radio, `inputValue()` and `toHaveValue` read its value
|
|
846
|
+
attribute, `on` when it has none, whatever its checked state. `isChecked()`
|
|
428
847
|
reads the state.
|
|
429
848
|
|
|
430
|
-
|
|
431
|
-
same strictness: `isHidden` is `true` for zero matches, `isDisabled` is
|
|
432
|
-
`LOCATOR_NOT_FOUND` there, and both are `LOCATOR_AMBIGUOUS` on several.
|
|
849
|
+
#### `getAttribute`
|
|
433
850
|
|
|
434
851
|
```ts
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
852
|
+
/** Reads one attribute. */
|
|
853
|
+
getAttribute(name: string): Promise<string | null>;
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
```ts
|
|
857
|
+
const href = await screen.getByRole('link', 'Docs').getAttribute('href');
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
#### `isVisible`
|
|
861
|
+
|
|
862
|
+
```ts
|
|
863
|
+
/** Reads current visibility. */
|
|
864
|
+
isVisible(): Promise<boolean>;
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
```ts
|
|
868
|
+
const open = await screen.getByRole('dialog').isVisible();
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
- `false` for zero matches, and for a `frameLocator` whose frame is not in
|
|
872
|
+
the document.
|
|
873
|
+
- Uses the same visibility check as `visible: true` (see
|
|
874
|
+
[Hidden duplicates](#hidden-duplicates)).
|
|
875
|
+
|
|
876
|
+
| Throws | When |
|
|
877
|
+
| --- | --- |
|
|
878
|
+
| `LOCATOR_AMBIGUOUS` | Several matches. |
|
|
879
|
+
|
|
880
|
+
#### `isHidden`
|
|
881
|
+
|
|
882
|
+
```ts
|
|
883
|
+
/** Reads current hidden or absent state, the negation of `isVisible`. */
|
|
884
|
+
isHidden(): Promise<boolean>;
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
```ts
|
|
888
|
+
const gone = await screen.getByTestId('spinner').isHidden();
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
It has the same strictness as `isVisible`: `true` for zero matches.
|
|
892
|
+
|
|
893
|
+
| Throws | When |
|
|
894
|
+
| --- | --- |
|
|
895
|
+
| `LOCATOR_AMBIGUOUS` | Several matches. |
|
|
896
|
+
|
|
897
|
+
#### `isEnabled`
|
|
898
|
+
|
|
899
|
+
```ts
|
|
900
|
+
/** Reads current enabled state. */
|
|
901
|
+
isEnabled(): Promise<boolean>;
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
```ts
|
|
905
|
+
const ready = await screen.getByRole('button', 'Pay').isEnabled();
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
#### `isDisabled`
|
|
909
|
+
|
|
910
|
+
```ts
|
|
911
|
+
/** Reads current disabled state, the negation of `isEnabled`. */
|
|
912
|
+
isDisabled(): Promise<boolean>;
|
|
439
913
|
```
|
|
440
914
|
|
|
441
|
-
|
|
915
|
+
```ts
|
|
916
|
+
const blocked = await screen.getByRole('button', 'Pay').isDisabled();
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
It has the same strictness as `isEnabled`.
|
|
920
|
+
|
|
921
|
+
| Throws | When |
|
|
922
|
+
| --- | --- |
|
|
923
|
+
| `LOCATOR_NOT_FOUND` | Zero matches. |
|
|
924
|
+
| `LOCATOR_AMBIGUOUS` | Several matches. |
|
|
442
925
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
926
|
+
#### `isChecked`
|
|
927
|
+
|
|
928
|
+
```ts
|
|
929
|
+
/** Reads current checked state. */
|
|
930
|
+
isChecked(): Promise<boolean>;
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
```ts
|
|
934
|
+
const remembered = await screen.getByRole('checkbox', 'Remember me').isChecked();
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
`false` on a node with no checked state, such as a button or a heading. It
|
|
938
|
+
does not throw.
|
|
939
|
+
|
|
940
|
+
#### `boundingBox`
|
|
941
|
+
|
|
942
|
+
```ts
|
|
943
|
+
/** Reads the current viewport-relative rectangle. */
|
|
944
|
+
boundingBox(): Promise<{ x: number; y: number; width: number; height: number } | null>;
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
```ts
|
|
948
|
+
const map = (await screen.getByRole('image', 'Map').boundingBox())!;
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
The box is in CSS pixels. It is `null` for a node that lays out no box
|
|
952
|
+
(`display: none` on it or an ancestor). A `visibility: hidden` node keeps
|
|
953
|
+
its box.
|
|
954
|
+
|
|
955
|
+
#### `count`
|
|
956
|
+
|
|
957
|
+
```ts
|
|
958
|
+
/** Counts current matches without auto-waiting. */
|
|
959
|
+
count(): Promise<number>;
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
```ts
|
|
963
|
+
const rows = await screen.getByRole('row').count();
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
`0` for zero matches, and for a `frameLocator` whose frame is not in the
|
|
967
|
+
document.
|
|
968
|
+
|
|
969
|
+
#### `all`
|
|
970
|
+
|
|
971
|
+
Resolves the query one time, so a loop can act on each row.
|
|
972
|
+
|
|
973
|
+
```ts
|
|
974
|
+
/** One `nth(i)` locator per current match, without auto-waiting; empty when nothing matches. */
|
|
975
|
+
all(): Promise<Locator[]>;
|
|
976
|
+
```
|
|
454
977
|
|
|
455
978
|
```ts
|
|
456
979
|
for (const row of await screen.getByRole('row').all()) {
|
|
457
980
|
await row.getByRole('checkbox').check();
|
|
458
981
|
}
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
- It neither waits nor enforces exactly-one: zero matches is an empty array,
|
|
985
|
+
the same snapshot rule as `count()`. A `frameLocator` whose frame is not in
|
|
986
|
+
the document counts as zero matches here, where an action or assertion
|
|
987
|
+
polls for it.
|
|
988
|
+
- Each locator that `all()` returns re-resolves when used. A list that changes
|
|
989
|
+
under the loop can make a later `nth(i)` point at a different row, or at
|
|
990
|
+
nothing. Follow the loop with an assertion on the list.
|
|
991
|
+
|
|
992
|
+
#### `allTextContents`
|
|
993
|
+
|
|
994
|
+
```ts
|
|
995
|
+
/** Reads the normalized text of every current match, without auto-waiting; empty when nothing matches. */
|
|
996
|
+
allTextContents(): Promise<string[]>;
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
```ts
|
|
459
1000
|
expect(await screen.getByRole('listitem').allTextContents()).toEqual(['Alpha', 'Beta']);
|
|
460
1001
|
```
|
|
461
1002
|
|
|
1003
|
+
- Text reads as in [`textContent`](#textcontent).
|
|
1004
|
+
- It neither waits nor enforces exactly-one: zero matches is an empty array,
|
|
1005
|
+
as for [`all`](#all).
|
|
1006
|
+
- To check the texts, use
|
|
1007
|
+
[`toHaveText([...])`](/reference/expect#locator-matchers) instead of a
|
|
1008
|
+
loop.
|
|
1009
|
+
|
|
1010
|
+
#### `waitFor`
|
|
1011
|
+
|
|
1012
|
+
```ts
|
|
1013
|
+
/**
|
|
1014
|
+
* Waits for the requested locator state, `visible` by default: `attached`
|
|
1015
|
+
* for one match, visible or not; `detached` for none; `hidden` for none or
|
|
1016
|
+
* a hidden one.
|
|
1017
|
+
*/
|
|
1018
|
+
waitFor(options?: { state?: 'attached' | 'detached' | 'visible' | 'hidden'; timeout?: number }): Promise<void>;
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
```ts
|
|
1022
|
+
await screen.getByTestId('spinner').waitFor({ state: 'hidden' }); // default state: 'visible'
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
| State | Waits for |
|
|
1026
|
+
| --- | --- |
|
|
1027
|
+
| `'visible'` (default) | One visible match. |
|
|
1028
|
+
| `'attached'` | One match, hidden or shown. |
|
|
1029
|
+
| `'detached'` | No match. |
|
|
1030
|
+
| `'hidden'` | A hidden match, or none. |
|
|
1031
|
+
|
|
1032
|
+
`timeout` defaults to `config.actionTimeout`.
|
|
1033
|
+
|
|
1034
|
+
| Throws | When |
|
|
1035
|
+
| --- | --- |
|
|
1036
|
+
| `LOCATOR_NOT_FOUND` | The timeout expires. |
|
|
1037
|
+
| `LOCATOR_AMBIGUOUS` | Several matches, in every state. |
|
|
1038
|
+
| `INVALID_ARGUMENT` | Any other state, or an option key besides `state` and `timeout`; thrown before anything is resolved. |
|
|
1039
|
+
|
|
1040
|
+
## Match rules
|
|
1041
|
+
|
|
1042
|
+
Most operations require exactly one match. A read polls for its one match
|
|
1043
|
+
until the action timeout, as an action does. Then it returns the current
|
|
1044
|
+
value without waiting for it to change. Use [`expect`](/reference/expect) to
|
|
1045
|
+
wait for a value.
|
|
1046
|
+
|
|
1047
|
+
A node the engine reports stale is re-resolved within the same timeout:
|
|
1048
|
+
a resolve that raced a navigation, or a match the app replaced while it was
|
|
1049
|
+
read.
|
|
1050
|
+
|
|
1051
|
+
| Operation | Zero matches | Multiple matches |
|
|
1052
|
+
| --- | --- | --- |
|
|
1053
|
+
| Actions | Poll until the timeout, then `LOCATOR_NOT_FOUND` | `LOCATOR_AMBIGUOUS` |
|
|
1054
|
+
| Reads | Poll until the timeout, then `LOCATOR_NOT_FOUND` | `LOCATOR_AMBIGUOUS` |
|
|
1055
|
+
| `count()`, `all()`, `allTextContents()` | `0`, `[]`, `[]` | The count, one locator per match, one text per match |
|
|
1056
|
+
| `isVisible()`, `isHidden()` | `false`, `true` | `LOCATOR_AMBIGUOUS` |
|
|
1057
|
+
| Assertions | Poll until the timeout, then `ASSERTION_FAILED` | `LOCATOR_AMBIGUOUS` |
|
|
1058
|
+
| Any of these under a frame that is not in the document | As zero matches for `count()`, `all()`, `allTextContents()`, `isVisible()`, and `isHidden()`; actions and reads poll for the frame until the timeout, then `LOCATOR_NOT_FOUND`; assertions, then `ASSERTION_FAILED` | `LOCATOR_AMBIGUOUS` when several frames match |
|
|
1059
|
+
|
|
462
1060
|
## Types
|
|
463
1061
|
|
|
464
1062
|
```ts
|
|
@@ -601,7 +1199,7 @@ export interface SwipePathOptions {
|
|
|
601
1199
|
from: Point;
|
|
602
1200
|
/** Where it lifts. */
|
|
603
1201
|
to: Point;
|
|
604
|
-
/** Gesture duration
|
|
1202
|
+
/** Gesture duration, an integer from 16 through 10000 ms; omitted = the engine's default. */
|
|
605
1203
|
duration?: number;
|
|
606
1204
|
direction?: never;
|
|
607
1205
|
momentum?: never;
|
|
@@ -654,72 +1252,7 @@ export type ScrollDirection = 'up' | 'down' | 'left' | 'right';
|
|
|
654
1252
|
export type Momentum = 'none' | 'slow' | 'fast';
|
|
655
1253
|
```
|
|
656
1254
|
|
|
657
|
-
|
|
658
|
-
that does not render, or one under `aria-hidden`. `visible` narrows the
|
|
659
|
-
other query kinds to the nodes that render, the check `toBeVisible()` makes,
|
|
660
|
-
so an `aria-hidden` node that still paints counts as visible there. See
|
|
661
|
-
[Hidden duplicates](#hidden-duplicates).
|
|
662
|
-
|
|
663
|
-
`img` is an alias, not a role: `getByRole('img', { name })` builds the same
|
|
664
|
-
query as `getByRole('image', { name })`, so a test ported from Playwright keeps
|
|
665
|
-
its spelling while engines, the replay cache, and reports see `image`. On the
|
|
666
|
-
web, `image` covers `<img>` with alt text and any element with `role="img"`;
|
|
667
|
-
an `<img alt="">` is decoration and matches nothing.
|
|
668
|
-
|
|
669
|
-
The web engine derives the roles from HTML the way HTML-AAM does: `<progress>`
|
|
670
|
-
is `progressbar`, `<meter>` is `meter`, `<hr>` is `separator`, `<fieldset>`
|
|
671
|
-
and `<details>` are `group`, `<input type="number">` is `spinbutton`,
|
|
672
|
-
`<input type="file">` is `button`, as Chrome and Playwright read it,
|
|
673
|
-
`<select size="3">` is a `listbox` like a `multiple` one, an `<input list>`
|
|
674
|
-
naming a `<datalist>` is a `combobox`, a `<td>` of a `role="grid"` table is a
|
|
675
|
-
`gridcell`,
|
|
676
|
-
`<thead>` and `<tbody>` are `rowgroup`, `<th scope="row">` is `rowheader`.
|
|
677
|
-
A scope-less `<th>` beside a data cell with text or children is also a
|
|
678
|
-
`rowheader`; header-only rows keep `columnheader`. A lone scope-less header in a one-row
|
|
679
|
-
table has no header role, while `scope="col"` and `scope="colgroup"` explicitly
|
|
680
|
-
keep `columnheader`.
|
|
681
|
-
`<figure>` is `figure`, `<article>` is `article`, `<header>` and `<footer>` are
|
|
682
|
-
`banner` and `contentinfo` only outside sectioning content or an element with
|
|
683
|
-
a sectioning role (`article`, `complementary`, `main`, `navigation`, `region`),
|
|
684
|
-
and `<form>` and `<section>`
|
|
685
|
-
are `form` and `region` only when they carry an `aria-label`,
|
|
686
|
-
`aria-labelledby`, or `title`. A `<legend>`, `<figcaption>`, or `<caption>`
|
|
687
|
-
names its fieldset, figure, or table, so `toHaveAccessibleName` reads what
|
|
688
|
-
`getByRole('group', { name })` matched. A role accname names from content (a
|
|
689
|
-
button, link, heading, tab, menu item, option, checkbox, radio, switch, cell,
|
|
690
|
-
row or column header, row, tree item, tooltip) is named by its descendants, a
|
|
691
|
-
custom element's shadow tree included, so `getByRole('checkbox', { name })`
|
|
692
|
-
and `toHaveAccessibleName(name)` agree on a `<div role="checkbox">`. An
|
|
693
|
-
`<input type="submit">` or `type="reset"` with no `value` reads `Submit` or
|
|
694
|
-
`Reset`. `<menu>` is a `list`; only `role="menu"` is a
|
|
695
|
-
`menu`. A device engine maps the platform types it has (a tab bar or segmented
|
|
696
|
-
control is a `tablist`, a progress indicator a `progressbar`, a stepper a
|
|
697
|
-
`spinbutton`) and keeps any other type verbatim, so a role with no native
|
|
698
|
-
widget, such as `tooltip` or `figure`, matches nothing on a phone.
|
|
1255
|
+
## Related
|
|
699
1256
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
(and, while it has focus, whose `selection` is the text selected inside it).
|
|
703
|
-
The value keeps spaces and newlines, so `toHaveValue` and `inputValue()` read
|
|
704
|
-
a `white-space: pre-wrap` editor like a textarea. An empty editor, holding only
|
|
705
|
-
the `<p><br></p>` or the zero-width no-break space its editor pads an empty
|
|
706
|
-
line with, reads as `''`, and that padding is never part of a value. It is named by its
|
|
707
|
-
`aria-label`, `aria-placeholder`, or the `data-placeholder` its editor paints;
|
|
708
|
-
the blocks inside it are content, not textboxes. The tag does not matter (an
|
|
709
|
-
`<article contenteditable>` is the editor, not an article), while a native
|
|
710
|
-
control or a drawn surface made editable keeps its own role. `getByRole` resolves through
|
|
711
|
-
Playwright's role selector, which has no such rule, so a test addresses an
|
|
712
|
-
editor by role only when the host carries `role="textbox"`; `getByLabel` and
|
|
713
|
-
`getByTestId` reach it either way. A name taken from a block's
|
|
714
|
-
`data-placeholder` (TipTap, ProseMirror) goes once the editor has content, as
|
|
715
|
-
the painted hint does, so the host reads as an unnamed `textbox` from then on;
|
|
716
|
-
a name from the host's own `aria-label`, `aria-labelledby`, `aria-placeholder`,
|
|
717
|
-
or `data-placeholder` (Quill) stays. Anchor a test on a label or a test id, not
|
|
718
|
-
on the placeholder.
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
For `selectOption`, pass a label string or exactly one of `label`, `value`,
|
|
722
|
-
or `index`. `value` matches the option's value attribute, which is what the
|
|
723
|
-
form submits. One option per call: an array, an object with more than one of
|
|
724
|
-
those keys, or a negative or fractional `index` fails with `INVALID_ARGUMENT`
|
|
725
|
-
before the engine acts.
|
|
1257
|
+
[`expect`](/reference/expect) waits for a locator's state. The
|
|
1258
|
+
[`browser`](/reference/web) fixture adds CSS locators and frames.
|