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,14 +6,755 @@ icon: "circle-check"
|
|
|
6
6
|
"twitter:image": "https://e2e.tester.army/docs/images/og/reference/expect.png?v=118436cc41"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
`expect(locator)` waits for an app condition, `expect(screen)` compares the
|
|
10
|
+
screen against a stored screenshot, and `expect(value)` checks a value
|
|
11
|
+
immediately. `expect.poll` re-reads a value until its matcher passes,
|
|
12
|
+
`expect.soft` keeps a failure for the end of the test body instead of
|
|
13
|
+
throwing it, and `expect.any` and the other
|
|
14
14
|
[asymmetric matchers](#asymmetric-matchers) stand in for values inside a
|
|
15
15
|
structural comparison.
|
|
16
16
|
|
|
17
|
+
```ts title="tests/checkout.e2e.ts"
|
|
18
|
+
import { expect, test } from 'e2e';
|
|
19
|
+
|
|
20
|
+
test('checkout summary', async ({ screen }) => {
|
|
21
|
+
await expect(screen.getByRole('heading', 'Checkout')).toBeVisible();
|
|
22
|
+
await expect(screen.getByTestId('line-item')).toHaveCount(2);
|
|
23
|
+
const total = await screen.getByTestId('total').textContent();
|
|
24
|
+
expect(total).toContain('$');
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Reference
|
|
29
|
+
|
|
30
|
+
| Call | Returns |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| [`expect(locator)`](#locator-matchers) | `AsyncExpectation` |
|
|
33
|
+
| [`expect(screen)`](#screenshot-matchers) | `ScreenExpectation` |
|
|
34
|
+
| [`expect(fixture)`](#browser-matchers) | The engine's matcher set |
|
|
35
|
+
| [`expect(value, message?)`](#value-matchers) | `ValueExpectation<T>` |
|
|
36
|
+
| [`expect.poll(read, options?)`](#polling-a-value) | `PollExpectation<T>` |
|
|
37
|
+
| [`expect.soft(actual)`](#soft-assertions) | The same matchers, failures kept |
|
|
38
|
+
| [`expect.any(Class)` and others](#asymmetric-matchers) | `AsymmetricMatcher` |
|
|
39
|
+
|
|
40
|
+
`expect` tells the argument apart by what it is:
|
|
41
|
+
|
|
42
|
+
- Engine fixtures can provide matchers, such as `expect(browser).toHaveURL()`.
|
|
43
|
+
Such a fixture is an `Expectable<E>`: its engine attached the matcher set
|
|
44
|
+
`E` through [`expectable`](/reference/engine#contributed-fixtures), and
|
|
45
|
+
`expect(fixture)` returns it.
|
|
46
|
+
- Every other argument gets the value matchers, a promise included. Await a
|
|
47
|
+
promise before passing its result to `expect`.
|
|
48
|
+
|
|
49
|
+
The declarations are under [Types](#types).
|
|
50
|
+
|
|
51
|
+
### Locator matchers
|
|
52
|
+
|
|
53
|
+
`expect(locator)` returns an `AsyncExpectation`. Each matcher polls until it
|
|
54
|
+
holds or its timeout passes, under the rules in [Behavior](#behavior):
|
|
55
|
+
[Strictness](#strictness), [Text matching](#text-matching), and
|
|
56
|
+
[Secure fields](#secure-fields) among them. The options are in
|
|
57
|
+
[`AsyncExpectation`](#asyncexpectation-and-textmatcheroptions).
|
|
58
|
+
|
|
59
|
+
| Matcher | Description |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| [`toBeVisible`](#tobevisible-and-tobehidden) | Waits for visibility |
|
|
62
|
+
| [`toBeHidden`](#tobevisible-and-tobehidden) | Waits for hidden or absent state |
|
|
63
|
+
| [`toBeAttached`](#tobeattached) | Waits for one match to exist |
|
|
64
|
+
| [`toBeEnabled`](#tobeenabled-and-tobedisabled) | Waits for enabled state |
|
|
65
|
+
| [`toBeDisabled`](#tobeenabled-and-tobedisabled) | Waits for disabled state |
|
|
66
|
+
| [`toBeChecked`](#tobechecked-tobeselected-and-tobeexpanded) | Waits for checked state |
|
|
67
|
+
| [`toBeSelected`](#tobechecked-tobeselected-and-tobeexpanded) | Waits for selected state |
|
|
68
|
+
| [`toBeExpanded`](#tobechecked-tobeselected-and-tobeexpanded) | Waits for expanded state |
|
|
69
|
+
| [`toBeFocused`](#tobefocused) | Waits for focused state |
|
|
70
|
+
| [`toHaveText`](#tohavetext-and-tocontaintext) | Waits for exact normalized text |
|
|
71
|
+
| [`toContainText`](#tohavetext-and-tocontaintext) | Waits for contained normalized text |
|
|
72
|
+
| [`toHaveValue`](#tohavevalue) | Waits for a form control's value |
|
|
73
|
+
| [`toHaveAttribute`](#tohaveattribute) | Waits for an attribute |
|
|
74
|
+
| [`toHaveCount`](#tohavecount) | Waits for an exact match count |
|
|
75
|
+
| [`toHaveAccessibleName`](#tohaveaccessiblename) | Waits for an accessible name |
|
|
76
|
+
| [`toHaveScreenshot`](#screenshot-matchers) | Compares with a stored screenshot |
|
|
77
|
+
|
|
78
|
+
#### `toBeVisible` and `toBeHidden`
|
|
79
|
+
|
|
80
|
+
`toBeVisible` waits for the one match to be shown. `toBeHidden` waits for a
|
|
81
|
+
hidden or absent state.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
await expect(screen.getByRole('dialog')).toBeVisible();
|
|
85
|
+
await expect(screen.getByRole('dialog')).not.toBeVisible({ timeout: 10_000 });
|
|
86
|
+
await expect(screen.getByRole('dialog')).toBeVisible({ visible: false }); // as toBeHidden()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### `toBeAttached`
|
|
90
|
+
|
|
91
|
+
Waits for one match to exist, hidden or shown, where `toBeVisible` needs a
|
|
92
|
+
shown one.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
await expect(screen.getByText('Loaded')).toBeAttached();
|
|
96
|
+
await expect(screen.getByText('Loading')).toBeAttached({ attached: false });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Negated, or with `attached: false`, it waits for no match and passes once
|
|
100
|
+
nothing matches.
|
|
101
|
+
|
|
102
|
+
#### `toBeEnabled` and `toBeDisabled`
|
|
103
|
+
|
|
104
|
+
`toBeEnabled` waits for an enabled node; `toBeDisabled` waits for a disabled
|
|
105
|
+
one.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
await expect(screen.getByRole('button', 'Pay')).toBeEnabled();
|
|
109
|
+
await expect(screen.getByRole('button', 'Pay')).toBeEnabled({ enabled: false }); // as toBeDisabled()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A node is enabled unless it reports `disabled`. On the web, `disabled` is
|
|
113
|
+
the state the browser applies, not the attribute alone. That is the reading
|
|
114
|
+
Playwright's `toBeDisabled` gives:
|
|
115
|
+
|
|
116
|
+
- A control inside a `<fieldset disabled>` is disabled unless it sits in
|
|
117
|
+
that fieldset's first `<legend>`.
|
|
118
|
+
- `aria-disabled="true"` on an ancestor disables the widgets below it until
|
|
119
|
+
an `aria-disabled="false"` cuts the chain.
|
|
120
|
+
|
|
121
|
+
#### `toBeChecked`, `toBeSelected`, and `toBeExpanded`
|
|
122
|
+
|
|
123
|
+
Each waits for its state: checked, selected, or expanded.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
await expect(screen.getByLabel('Subscribe')).toBeChecked({ checked: false });
|
|
127
|
+
await expect(screen.getByRole('tab', 'Billing')).toBeSelected();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
A missing `checked`, `selected`, or `expanded` state counts as `false`, so
|
|
131
|
+
`.not.toBeChecked()` passes on a button or a heading.
|
|
132
|
+
|
|
133
|
+
#### `toBeFocused`
|
|
134
|
+
|
|
135
|
+
Waits for focused state.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
await expect(screen.getByLabel('Email')).toBeFocused();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### `toHaveText` and `toContainText`
|
|
142
|
+
|
|
143
|
+
`toHaveText` waits for exact normalized text; `toContainText` waits for
|
|
144
|
+
contained normalized text. Both take a single `TextMatch` or a list.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
await expect(screen.getByRole('status')).toHaveText('Saved');
|
|
148
|
+
await expect(screen.getByRole('alert')).not.toContainText('error', { ignoreCase: true });
|
|
149
|
+
await expect(screen.getByTestId('todo')).toHaveText(['Write spec', /^Ship/, 'Release']);
|
|
150
|
+
await expect(screen.getByTestId('todo')).toContainText(['spec', 'runner', 'Release']);
|
|
151
|
+
await expect(screen.getByTestId('todo')).toContainText(['spec', 'Release']);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
A list form's failure message lists every text observed.
|
|
155
|
+
|
|
156
|
+
| Form | Holds when |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `toHaveText([...])` | The match count equals the list length, and the match at each position satisfies the entry there: a string exactly after normalization, a `RegExp` by test. An empty list holds when nothing matches |
|
|
159
|
+
| `toContainText([...])` | Each entry is contained by a distinct match, in list order, as Playwright's does. Extra matches before, between, or after are fine, a repeated entry needs a match of its own, and an empty list always holds |
|
|
160
|
+
|
|
161
|
+
<Note>
|
|
162
|
+
Negated, `toContainText([...])` fails as soon as that ordered subsequence
|
|
163
|
+
exists, so `not.toContainText(['Deleted'])` fails while any match contains
|
|
164
|
+
`Deleted`.
|
|
165
|
+
</Note>
|
|
166
|
+
|
|
167
|
+
#### `toHaveValue`
|
|
168
|
+
|
|
169
|
+
Waits for a form control's value, compared as it is.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
await expect(screen.getByLabel('Email')).toHaveValue('ada@example.test');
|
|
173
|
+
await expect(screen.getByLabel('Search')).toHaveValue('');
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- No normalization: newlines and trailing spaces count, and the failure
|
|
177
|
+
message prints the value that way.
|
|
178
|
+
- A `contenteditable` editor's value is its rendered text, kept the same way.
|
|
179
|
+
An empty editor reads as `''`.
|
|
180
|
+
- A control the platform reports without a value, such as a cleared field on
|
|
181
|
+
a device, reads as `''`.
|
|
182
|
+
- A node whose role carries no value, such as a heading or a link, never
|
|
183
|
+
satisfies `toHaveValue`, negated or not. The message reads
|
|
184
|
+
`no value (not a form control)`.
|
|
185
|
+
|
|
186
|
+
#### `toHaveAttribute`
|
|
187
|
+
|
|
188
|
+
Waits for the attribute to be present; with `value`, for it to match.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
await expect(screen.getByRole('link', 'Help')).toHaveAttribute('target');
|
|
192
|
+
await expect(screen.getByRole('link', 'Help')).toHaveAttribute('href', '/help');
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### `toHaveCount`
|
|
196
|
+
|
|
197
|
+
Waits for an exact match count.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
await expect(screen.getByTestId('todo')).toHaveCount(3);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### `toHaveAccessibleName`
|
|
204
|
+
|
|
205
|
+
Waits for an accessible name.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
await expect(screen.getByRole('button', { name: /close/i })).toHaveAccessibleName('Close');
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Screenshot matchers
|
|
212
|
+
|
|
213
|
+
`expect(screen)` and `expect(locator)` compare against a PNG stored beside
|
|
214
|
+
the test file, on every engine that captures pixels. The
|
|
215
|
+
[Visual comparison](/visual-testing) guide shows the workflow.
|
|
216
|
+
|
|
217
|
+
| Matcher | Description |
|
|
218
|
+
| --- | --- |
|
|
219
|
+
| [`expect(screen).toHaveScreenshot`](#tohavescreenshot) | Compares the whole screen |
|
|
220
|
+
| [`expect(locator).toHaveScreenshot`](#tohavescreenshot) | Compares a locator's one match |
|
|
221
|
+
|
|
222
|
+
#### `toHaveScreenshot`
|
|
223
|
+
|
|
224
|
+
Waits for the screen, or the one match of a locator, to look like its stored
|
|
225
|
+
screenshot: `name`, or one named after the test.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
await expect(screen).toHaveScreenshot('home.png');
|
|
229
|
+
await expect(screen.getByRole('navigation')).toHaveScreenshot();
|
|
230
|
+
await expect(screen).toHaveScreenshot('feed.png', { mask: [screen.getByTestId('timestamp')], maxDiffPixels: 50 });
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Both forms take the [Screenshot options](#screenshot-options).
|
|
234
|
+
|
|
235
|
+
| Property | Value |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| Stored at | `<test file>-snapshots/<name>-<target>-<os>.png`; `.png` is added to a name without it |
|
|
238
|
+
| Unnamed | The test's title path, lowercase, and a count: `checkout-pays-by-card-1` |
|
|
239
|
+
| Missing | Taken from two screenshots in a row that are the same within the tolerance, then `ASSERTION_FAILED`. Written beside the test; in CI without `--update-snapshots` only attached to the results, at `snapshots/<path in the project>` |
|
|
240
|
+
| Written earlier in the run | Without `--update-snapshots`, and always for `.not`, no comparison passes against a screenshot the same run wrote (a retry, a repeat, another test naming it): `ASSERTION_FAILED` until the next run. With `--update-snapshots` a positive matcher compares and may write it again |
|
|
241
|
+
| Different | Retried until one matches or the timeout; then `ASSERTION_FAILED` with `-expected`, `-actual`, and `-diff` images on the step |
|
|
242
|
+
| `--update-snapshots`, `-u` | A missing or different stored screenshot is written from a screen that holds still, and the matcher passes |
|
|
243
|
+
| `.not` | Passes once the screen differs; with nothing stored it is `ASSERTION_FAILED` |
|
|
244
|
+
| Tolerance | Both `maxDiffPixels` and `maxDiffPixelRatio` set: the smaller wins |
|
|
245
|
+
|
|
246
|
+
| Throws | When |
|
|
247
|
+
| --- | --- |
|
|
248
|
+
| `INVALID_ARGUMENT` | Before the screen is read: a name with a path separator, an option the matcher does not take (Playwright's `fullPage`, `animations`, `clip`), a value out of range, or `expect` on a screen scoped to a frame. Compare a locator inside the frame instead |
|
|
249
|
+
| `ASSERTION_FAILED` | A missing, different, or same-run stored screenshot, as in the table above |
|
|
250
|
+
| `POLICY_DENIED` | A `Secret` was filled earlier, as for `app.screenshot()` |
|
|
251
|
+
|
|
252
|
+
#### Screenshot options
|
|
253
|
+
|
|
254
|
+
| Option | Type | Default |
|
|
255
|
+
| --- | --- | --- |
|
|
256
|
+
| `threshold` | `number` | `0.2` |
|
|
257
|
+
| `maxDiffPixels` | `number` | `0` |
|
|
258
|
+
| `maxDiffPixelRatio` | `number` | `0` |
|
|
259
|
+
| `mask` | `readonly Locator[]` | - |
|
|
260
|
+
| `maskColor` | `string` | `#ff00ff` |
|
|
261
|
+
| `timeout` | `number` | `config.assertionTimeout` |
|
|
262
|
+
|
|
263
|
+
[`ScreenshotOptions`](#screenexpectation-and-screenshotoptions) describes
|
|
264
|
+
each option.
|
|
265
|
+
|
|
266
|
+
### browser matchers
|
|
267
|
+
|
|
268
|
+
Contributed by `@e2e-dev/web`; the `BrowserExpectation` type is exported
|
|
269
|
+
from that package. Every matcher call is a recorded `expect.<matcher>`
|
|
270
|
+
assertion step.
|
|
271
|
+
|
|
272
|
+
| Matcher | Description |
|
|
273
|
+
| --- | --- |
|
|
274
|
+
| [`toHaveURL`](#tohaveurl) | Waits for the current URL |
|
|
275
|
+
| [`toHaveTitle`](#tohavetitle) | Waits for the current title |
|
|
276
|
+
| [`toHaveClass`](#tohaveclass) | Waits for a target's `class` |
|
|
277
|
+
|
|
278
|
+
Any other option key is `INVALID_ARGUMENT`, such as `ignoreCase` on
|
|
279
|
+
`toHaveTitle`, which Playwright does not take either.
|
|
280
|
+
|
|
281
|
+
#### `toHaveURL`
|
|
282
|
+
|
|
283
|
+
Waits for the current URL to match.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
await expect(browser).toHaveURL('/dashboard');
|
|
287
|
+
await expect(browser).toHaveURL(/\/orders\/\d+$/);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- A relative string resolves against the base URL and matches exactly after
|
|
291
|
+
normalization.
|
|
292
|
+
- A `RegExp` tests the full URL.
|
|
293
|
+
- Anything else, such as a predicate function, is `INVALID_ARGUMENT`.
|
|
294
|
+
- It takes Playwright's `ignoreCase`, as the locator matchers do
|
|
295
|
+
([Text matching](#text-matching)).
|
|
296
|
+
|
|
297
|
+
#### `toHaveTitle`
|
|
298
|
+
|
|
299
|
+
Waits for the current title to match.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
await expect(browser).toHaveTitle(/Dashboard/);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### `toHaveClass`
|
|
306
|
+
|
|
307
|
+
Waits for the target's `class` attribute to match: a string is the whole
|
|
308
|
+
normalized class list, a `RegExp` is tested against it.
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
await expect(browser).toHaveClass(browser.locator('.card'), 'card active');
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
- It polls for the node too: a target that matches nothing yet is one more
|
|
315
|
+
poll, not a failure, until the assertion timeout runs out.
|
|
316
|
+
- An ambiguous target still fails at once with `LOCATOR_AMBIGUOUS`.
|
|
317
|
+
- It reads the `class` attribute, so `''` matches `class=""` and not an
|
|
318
|
+
element with no `class` attribute at all. Playwright's `toHaveClass('')`
|
|
319
|
+
accepts both.
|
|
320
|
+
|
|
321
|
+
### Value matchers
|
|
322
|
+
|
|
323
|
+
`expect(value)` runs synchronously without retrying. For a value that may
|
|
324
|
+
still change, use [Polling a value](#polling-a-value).
|
|
325
|
+
|
|
326
|
+
| Matcher | Description |
|
|
327
|
+
| --- | --- |
|
|
328
|
+
| [`toBe`](#tobe) | Compares with `Object.is` |
|
|
329
|
+
| [`toEqual`](#toequal) | Recursive structural equality |
|
|
330
|
+
| [`toMatchObject`](#tomatchobject) | Recursive subset match |
|
|
331
|
+
| [`toBeTruthy`, `toBeFalsy`](#tobetruthy-and-tobefalsy) | Truthy or falsy value |
|
|
332
|
+
| [`toBeNull`, `toBeUndefined`, `toBeDefined`](#tobenull-tobeundefined-and-tobedefined) | `null`, `undefined`, or defined |
|
|
333
|
+
| [`toHaveLength`](#tohavelength) | `length` equals `expected` |
|
|
334
|
+
| [`toHaveProperty`](#tohaveproperty) | Property at a path |
|
|
335
|
+
| [`toContain`](#tocontain) | String or collection containment |
|
|
336
|
+
| [`toMatch`](#tomatch) | String or `RegExp` match |
|
|
337
|
+
| [`toBeGreaterThan` and others](#numeric-comparisons) | Numeric bound holds |
|
|
338
|
+
| [`toBeCloseTo`](#tobecloseto) | Within `10 ** -digits / 2` |
|
|
339
|
+
| [`toMatchSchema`](#matching-a-schema) | Standard Schema validation |
|
|
340
|
+
|
|
341
|
+
A matcher that cannot evaluate its value fails as `ASSERTION_FAILED`, like a
|
|
342
|
+
mismatch. Examples are `toContain` on a number, or `toHaveLength` on a value
|
|
343
|
+
without a numeric `length`. So under `expect.poll`, a value that has not
|
|
344
|
+
taken its final shape yet is one more failing sample.
|
|
345
|
+
|
|
346
|
+
#### `toBe`
|
|
347
|
+
|
|
348
|
+
Compares with `Object.is`.
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
expect(status).toBe('paid');
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### `toEqual`
|
|
355
|
+
|
|
356
|
+
Performs recursive structural equality the way Jest's does.
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
expect(payload).toEqual({ ok: true, items: [{ sku: 'a' }] });
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
- Primitives compare with `Object.is`, so `NaN` equals `NaN` and `0` does not
|
|
363
|
+
equal `-0`.
|
|
364
|
+
- Arrays and plain objects compare by their contents, `Date` by time,
|
|
365
|
+
`RegExp` by source and flags, `Map` and `Set` by their entries.
|
|
366
|
+
- A property whose value is `undefined` is treated as absent, and a sparse
|
|
367
|
+
array equals one holding `undefined`.
|
|
368
|
+
- Class types are not compared: a class instance equals a plain object with
|
|
369
|
+
the same properties. There is no `toStrictEqual`; see
|
|
370
|
+
[Not included](#not-included).
|
|
371
|
+
|
|
372
|
+
#### `toMatchObject`
|
|
373
|
+
|
|
374
|
+
Requires every property of `expected` to match recursively.
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
expect(order).toMatchObject({ customer: { name: 'Ada' }, lines: [{ sku: 'a' }, { sku: 'b' }] });
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
- Properties `expected` does not name are ignored.
|
|
381
|
+
- An array in `expected` matches an array of the same length, element by
|
|
382
|
+
element, each element again as a subset.
|
|
383
|
+
- Both sides must be objects; a primitive on either side is refused.
|
|
384
|
+
|
|
385
|
+
#### `toBeTruthy` and `toBeFalsy`
|
|
386
|
+
|
|
387
|
+
Require a truthy or a falsy value.
|
|
388
|
+
|
|
389
|
+
```ts
|
|
390
|
+
expect(created).toBeTruthy();
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
#### `toBeNull`, `toBeUndefined`, and `toBeDefined`
|
|
394
|
+
|
|
395
|
+
`toBeNull` requires `null` and `toBeUndefined` requires `undefined`.
|
|
396
|
+
`toBeDefined` requires any value but `undefined`; `null` passes.
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
expect(row).toBeDefined();
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### `toHaveLength`
|
|
403
|
+
|
|
404
|
+
Requires a `length` property equal to `expected`: a string, an array, or
|
|
405
|
+
anything array-like.
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
expect(items).toHaveLength(3);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### `toHaveProperty`
|
|
412
|
+
|
|
413
|
+
Requires the property at `path` to exist; with `expected`, to equal it by
|
|
414
|
+
`toEqual` rules.
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
expect(order).toHaveProperty('customer.email', 'ada@example.test');
|
|
418
|
+
expect(order).toHaveProperty(['lines', 0, 'sku'], 'a');
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
- `path` is dotted (`'user.address.city'`) or an array of keys
|
|
422
|
+
(`['user', 'address', 'city']`). Use the array form when a key contains a
|
|
423
|
+
dot, or to index an array by number.
|
|
424
|
+
- Inherited properties count, and a property holding `undefined` exists.
|
|
425
|
+
- A string, number, or boolean on the path is read through its wrapper
|
|
426
|
+
object, as in Jest, so `toHaveProperty('label.length', 3)` and
|
|
427
|
+
`toHaveProperty(['label', 0], 'a')` reach into a string.
|
|
428
|
+
- `null` and `undefined` have no properties.
|
|
429
|
+
|
|
430
|
+
#### `toContain`
|
|
431
|
+
|
|
432
|
+
Requires string or collection containment.
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
expect(total).toContain('$');
|
|
436
|
+
expect(rows).toContain(expect.objectContaining({ status: 'paid' }));
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
- On a string, `expected` must be a string, and it is a substring check. An
|
|
440
|
+
asymmetric matcher is refused there:
|
|
441
|
+
`expect('abc').toContain(expect.stringContaining('a'))` fails.
|
|
442
|
+
- On a collection, an element must equal `expected`. This form takes
|
|
443
|
+
[asymmetric matchers](#asymmetric-matchers).
|
|
444
|
+
|
|
445
|
+
#### `toMatch`
|
|
446
|
+
|
|
447
|
+
Requires a string or `RegExp` match.
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
expect(id).toMatch(/^usr_/);
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
A pattern that is neither a string nor a `RegExp` is `INVALID_ARGUMENT`.
|
|
454
|
+
|
|
455
|
+
#### Numeric comparisons
|
|
456
|
+
|
|
457
|
+
`toBeGreaterThan` and `toBeLessThan` require a numeric bound;
|
|
458
|
+
`toBeGreaterThanOrEqual` and `toBeLessThanOrEqual` require it inclusive.
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
expect(count).toBeGreaterThan(1);
|
|
462
|
+
expect(count).toBeLessThanOrEqual(10);
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### `toBeCloseTo`
|
|
466
|
+
|
|
467
|
+
Requires a number within `10 ** -digits / 2` of `expected`.
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
expect(totals.lineTotal).toBeCloseTo(60); // digits default: 2
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
`toBeCloseTo(60)` accepts 59.996 and rejects 59.99. Infinite values pass only
|
|
474
|
+
when they are the same infinity.
|
|
475
|
+
|
|
476
|
+
#### Matching a schema
|
|
477
|
+
|
|
478
|
+
`toMatchSchema` validates a value against a
|
|
479
|
+
[Standard Schema](https://standardschema.dev): a Zod, Valibot, or ArkType
|
|
480
|
+
schema, or any library that implements the spec. Pass the schema your app
|
|
481
|
+
already parses its API with, and the test fails when the API drifts from it.
|
|
482
|
+
It returns the schema's output, typed, so the rest of the test reads
|
|
483
|
+
properties without casting.
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
import { z } from 'zod';
|
|
487
|
+
|
|
488
|
+
const User = z.object({ id: z.number(), name: z.string(), email: z.email() });
|
|
489
|
+
|
|
490
|
+
const users = expect(await response.json()).toMatchSchema(z.array(User));
|
|
491
|
+
expect(users.map((user) => user.name)).toContain('Ada');
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
- A failure lists every issue by its path (`1.email: Invalid email address`).
|
|
495
|
+
- The output is the schema's, so defaults and transforms apply.
|
|
496
|
+
- A matcher returns before anything could await it, so a schema that
|
|
497
|
+
validates asynchronously (an async refinement) is `INVALID_ARGUMENT`.
|
|
498
|
+
- Negated, it passes when the value does not match and returns nothing.
|
|
499
|
+
|
|
500
|
+
#### Failure messages
|
|
501
|
+
|
|
502
|
+
Pass a second argument to describe the check in its failure message:
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
expect(created, 'the company exists through the API').toBeTruthy();
|
|
506
|
+
// ASSERTION_FAILED: the company exists through the API: expected undefined to be truthy
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Only value expectations take a message; a locator or `browser` expectation
|
|
510
|
+
already names its locator and matcher in the failure.
|
|
511
|
+
|
|
512
|
+
#### Not included
|
|
513
|
+
|
|
514
|
+
The unit-runner matchers (`toThrow`, `toBeInstanceOf`, `toBeNaN`,
|
|
515
|
+
`toStrictEqual`, `resolves`, `rejects`, `expect.extend`) are not part of
|
|
516
|
+
`expect`. An end-to-end test checks what the app shows and what its API
|
|
517
|
+
returns; the value matchers cover that, see [API](/api-testing). Code-level
|
|
518
|
+
checks belong in the unit runner.
|
|
519
|
+
|
|
520
|
+
### Asymmetric matchers
|
|
521
|
+
|
|
522
|
+
An asymmetric matcher stands in for a value inside `toEqual`,
|
|
523
|
+
`toMatchObject`, the collection form of `toContain`, and the value of
|
|
524
|
+
`toHaveProperty`. Where the comparison would test equality, it asks the
|
|
525
|
+
matcher instead. They are Jest's matchers, with Jest's rules.
|
|
526
|
+
|
|
527
|
+
| Matcher | Matches |
|
|
528
|
+
| --- | --- |
|
|
529
|
+
| `expect.any(Class)` | An instance of the class. For `String`, `Number`, `Boolean`, `BigInt`, `Symbol`, and `Function`, the primitive too; for `Object`, anything `typeof` calls an object, `null` included |
|
|
530
|
+
| `expect.anything()` | Anything but `null` and `undefined` |
|
|
531
|
+
| `expect.objectContaining(sample)` | An object whose properties include every property of `sample`, each compared by `toEqual` rules |
|
|
532
|
+
| `expect.arrayContaining(sample)` | An array holding every element of `sample`, in any order, each compared by `toEqual` rules |
|
|
533
|
+
| `expect.stringContaining(text)` | A string with `text` as a substring |
|
|
534
|
+
| `expect.stringMatching(text \| RegExp)` | A string the RegExp tests true on; a text sample is compiled to a RegExp, so `'a.b'` matches `'axb'` |
|
|
535
|
+
|
|
536
|
+
```ts
|
|
537
|
+
expect(created).toEqual({
|
|
538
|
+
id: expect.stringMatching(/^usr_/),
|
|
539
|
+
createdAt: expect.any(String),
|
|
540
|
+
roles: expect.arrayContaining(['member']),
|
|
541
|
+
profile: expect.objectContaining({ name: 'Ada' }),
|
|
542
|
+
});
|
|
543
|
+
expect(rows).toContain(expect.objectContaining({ status: 'paid' }));
|
|
544
|
+
expect(response).toHaveProperty('body.total', expect.any(Number));
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
- In a failure message a matcher prints as its description: `Any<Number>`,
|
|
548
|
+
`ObjectContaining {"name":"Ada"}`.
|
|
549
|
+
- Matchers nest: an `objectContaining` sample may hold an `expect.any`.
|
|
550
|
+
- The string form of `toContain` refuses them; see [`toContain`](#tocontain).
|
|
551
|
+
|
|
552
|
+
### Soft assertions
|
|
553
|
+
|
|
554
|
+
`expect.soft(actual)` takes what `expect(actual)` takes and returns the same
|
|
555
|
+
matchers. A soft matcher that fails does not throw: the failure is kept on
|
|
556
|
+
the running attempt and the body continues.
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
test('checkout summary', async ({ agent, screen }) => {
|
|
560
|
+
await agent.act('add the "Pro" plan to the cart and open the summary');
|
|
561
|
+
expect.soft(await screen.getByTestId('subtotal').textContent()).toBe('$40.00');
|
|
562
|
+
expect.soft(await screen.getByTestId('tax').textContent()).toBe('$8.00');
|
|
563
|
+
await expect.soft(screen.getByRole('button', 'Pay')).toBeEnabled();
|
|
564
|
+
await expect(screen.getByTestId('total')).toHaveText('$48.00');
|
|
565
|
+
});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
```text
|
|
569
|
+
ASSERTION_FAILED: 2 soft assertions failed
|
|
570
|
+
1. expected "$41.00" to be "$40.00"
|
|
571
|
+
2. expect.toBeEnabled failed
|
|
572
|
+
locator: getByRole('button', 'Pay')
|
|
573
|
+
expected: enabled
|
|
574
|
+
observed: states: disabled (match count 1)
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
- Once the body has settled, and before `afterEach`, the attempt fails with
|
|
578
|
+
one `ASSERTION_FAILED` that lists every kept failure in order. The error's
|
|
579
|
+
source line is the first soft matcher that failed.
|
|
580
|
+
- A soft locator or `browser` matcher is still a recorded `expect.<matcher>`
|
|
581
|
+
step, marked failed, so the report shows where in the flow it landed.
|
|
582
|
+
- A soft value matcher is not a step, the same as a hard one; its failure
|
|
583
|
+
appears in the attempt's error records.
|
|
584
|
+
- When the body throws on its own, or times out, that error stays the
|
|
585
|
+
attempt's verdict and the soft failures are recorded beside it under
|
|
586
|
+
`secondaryErrors`.
|
|
587
|
+
- A body that skips itself with `test.skip(...)` after a soft failure still
|
|
588
|
+
fails: the soft failures are the attempt's error, and the skip reason is
|
|
589
|
+
kept in the attempt's `skip`.
|
|
590
|
+
- A soft `toMatchSchema` returns the schema's output when the value matches
|
|
591
|
+
and `undefined` after a kept failure, and its type says so.
|
|
592
|
+
|
|
593
|
+
<Note>
|
|
594
|
+
Outside a test body there is nothing to keep a failure for, so `expect.soft`
|
|
595
|
+
throws at once like `expect`: in a standalone script, and in an `afterEach`
|
|
596
|
+
hook, which runs after the soft failures have been settled. There is no
|
|
597
|
+
`expect.soft.poll`; `expect.poll` has its own timeout to fail on.
|
|
598
|
+
</Note>
|
|
599
|
+
|
|
600
|
+
### Polling a value
|
|
601
|
+
|
|
602
|
+
`expect.poll(read)` re-reads a value until a matcher passes. Use it to wait
|
|
603
|
+
for an API response or saved data after an app action.
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
await agent.act('create a test named "AI checkout regression" described as {description}', {
|
|
607
|
+
params: { description },
|
|
608
|
+
});
|
|
609
|
+
await expect
|
|
610
|
+
.poll(() => getTest(workspace).then((row) => row?.title), { timeout: 15_000 })
|
|
611
|
+
.toBe('AI checkout regression');
|
|
612
|
+
await expect.poll(async () => (await listTests(workspace)).length).toBeGreaterThan(1);
|
|
613
|
+
await expect.poll(() => readStatus(), { message: 'the batch never finished' }).not.toBe('running');
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
| Option | Type | Default |
|
|
617
|
+
| --- | --- | --- |
|
|
618
|
+
| `timeout` | `number` | `config.assertionTimeout` during an attempt, 5000 ms outside one |
|
|
619
|
+
| `interval` | `number` | `100` |
|
|
620
|
+
| `message` | `string` | - |
|
|
621
|
+
|
|
622
|
+
- `timeout` is the deadline in milliseconds, a finite number 0 or more. It is
|
|
623
|
+
capped by the test deadline in the body, or the hook's `cleanupTimeout`
|
|
624
|
+
during `afterEach`.
|
|
625
|
+
- `interval` is the pause between reads in milliseconds, a finite number
|
|
626
|
+
above 0.
|
|
627
|
+
- `message` is an extra line in the timeout error, after the matcher line.
|
|
628
|
+
|
|
629
|
+
Every value matcher is available, returning a promise.
|
|
630
|
+
|
|
631
|
+
- Each sample calls `read` and runs the matcher. A matcher failure or a
|
|
632
|
+
`read` that throws is one failing sample; a `read` that hangs is cut at the
|
|
633
|
+
deadline.
|
|
634
|
+
- `toMatchSchema` resolves to the schema's output for the passing read.
|
|
635
|
+
- When the attempt is cancelled, the loop stops with `CANCELLED`. At the
|
|
636
|
+
attempt's deadline it stops with `ASSERTION_FAILED`, naming the deadline.
|
|
637
|
+
A `read` in flight is abandoned, not stopped: work it started can go on.
|
|
638
|
+
- `expect.poll` is not recorded as a report step; a timeout surfaces only as
|
|
639
|
+
the test's failure.
|
|
640
|
+
|
|
641
|
+
At the deadline it throws `ASSERTION_FAILED` naming the matcher, then
|
|
642
|
+
`message` when given, then the last sample: the last matcher failure, the
|
|
643
|
+
last `read` error, or `no read completed`.
|
|
644
|
+
|
|
645
|
+
```text
|
|
646
|
+
expect.poll(...).not.toBe(...) timed out after 5000 ms
|
|
647
|
+
the batch never finished
|
|
648
|
+
last: expected "running" not to be "running"
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
| Throws | When |
|
|
652
|
+
| --- | --- |
|
|
653
|
+
| `ASSERTION_FAILED` | The deadline passed without a passing sample |
|
|
654
|
+
| `INVALID_CONFIG` | `timeout` or `interval` out of range, before the first read |
|
|
655
|
+
| `CANCELLED` | The attempt was cancelled or timed out |
|
|
656
|
+
| `STEP_NOT_AWAITED` | The phase that started the poll returned while it still ran |
|
|
657
|
+
| `HOOK_FAILED` | A `beforeAll` or `afterAll` hook returned while its poll still ran |
|
|
658
|
+
|
|
659
|
+
<Note>
|
|
660
|
+
Await every poll. A poll belongs to the phase that started it: the test body
|
|
661
|
+
(with its fixtures and `beforeEach` hooks), one `afterEach` hook or fixture
|
|
662
|
+
teardown, or one `beforeAll` or `afterAll` hook. When that phase returns
|
|
663
|
+
while the poll still runs, the poll is cancelled and the phase fails with
|
|
664
|
+
`STEP_NOT_AWAITED` at the line of the call, whether or not the poll would
|
|
665
|
+
have passed. A body that threw keeps its own error and records this one
|
|
666
|
+
under `secondaryErrors`, and a suite hook reports it as `HOOK_FAILED`.
|
|
667
|
+
</Note>
|
|
668
|
+
|
|
669
|
+
A poll never fails a later test: a poll started by work its phase left
|
|
670
|
+
running, such as a body past its timeout, is cancelled before its first
|
|
671
|
+
read.
|
|
672
|
+
|
|
673
|
+
## Behavior
|
|
674
|
+
|
|
675
|
+
Rules that apply across the matcher groups.
|
|
676
|
+
|
|
677
|
+
### Timeouts and failures
|
|
678
|
+
|
|
679
|
+
| Property | Value |
|
|
680
|
+
| --- | --- |
|
|
681
|
+
| Default timeout | [`config.assertionTimeout`](/reference/config#assertiontimeout) |
|
|
682
|
+
| Override | `{ timeout }` in milliseconds, on every locator and `browser` matcher |
|
|
683
|
+
| Unknown option | `INVALID_ARGUMENT`; see [Option validation](#option-validation) |
|
|
684
|
+
| Failure code | `ASSERTION_FAILED`, exit code 1 |
|
|
685
|
+
|
|
686
|
+
### Negation
|
|
687
|
+
|
|
688
|
+
`.not` inverts a matcher. A negated matcher passes only once the negation
|
|
689
|
+
has held:
|
|
690
|
+
|
|
691
|
+
- It passes after 1000 ms of continuous truth.
|
|
692
|
+
- When `{ timeout }` is shorter than 1000 ms, it passes at the deadline if the
|
|
693
|
+
negation held for the whole budget.
|
|
694
|
+
- The window counts from the moment the first read that saw the negation
|
|
695
|
+
started, so a slow read counts toward it.
|
|
696
|
+
|
|
697
|
+
### Strictness
|
|
698
|
+
|
|
699
|
+
- A positive locator matcher requires exactly one match. The exceptions are
|
|
700
|
+
`toHaveCount` and the list form of `toHaveText` and `toContainText`.
|
|
701
|
+
- Zero matches keep polling until the timeout.
|
|
702
|
+
- Multiple matches fail immediately with `LOCATOR_AMBIGUOUS`.
|
|
703
|
+
- `toBeHidden`, `toBeVisible({ visible: false })`,
|
|
704
|
+
`toBeAttached({ attached: false })`, negated visibility, and negated
|
|
705
|
+
`toBeAttached` accept zero matches.
|
|
706
|
+
- For those, and for `waitFor({ state: 'detached' })` and `'hidden'`, a
|
|
707
|
+
locator under a frame that is not in the document is zero matches rather
|
|
708
|
+
than a wait for the frame.
|
|
709
|
+
|
|
710
|
+
### Text matching
|
|
711
|
+
|
|
712
|
+
- Text comparison is normalized: leading and trailing whitespace is trimmed
|
|
713
|
+
and each run of whitespace collapses to one space.
|
|
714
|
+
- `toHaveText` and `toContainText` read the same rendered text as
|
|
715
|
+
[`textContent()`](/reference/screen#locator-reads), `innerText` on the web.
|
|
716
|
+
- A `TextMatch` string matches exactly. A `RegExp` uses its own source and
|
|
717
|
+
flags. Anything else, such as a predicate function, is `INVALID_ARGUMENT`.
|
|
718
|
+
- `ignoreCase: true` on `toHaveText`, `toContainText`, `toHaveAccessibleName`,
|
|
719
|
+
and the value form of `toHaveAttribute` compares a string case-insensitively
|
|
720
|
+
and adds the `i` flag to a `RegExp`; `ignoreCase: false` removes it.
|
|
721
|
+
- `toHaveAttribute(name, value)` compares like `toHaveText`, after
|
|
722
|
+
normalization.
|
|
723
|
+
- [`toHaveValue`](#tohavevalue) is the exception: it compares a value as it
|
|
724
|
+
is.
|
|
725
|
+
|
|
726
|
+
### Secure fields
|
|
727
|
+
|
|
728
|
+
A secure field, such as a password input, is never judged: the engine
|
|
729
|
+
withholds its value and text.
|
|
730
|
+
|
|
731
|
+
| Matcher | On a secure field |
|
|
732
|
+
| --- | --- |
|
|
733
|
+
| `toHaveValue`, `toHaveText`, `toContainText` | `POLICY_DENIED`, like `inputValue()` and `textContent()`, negated or in list form too |
|
|
734
|
+
| `toHaveAttribute` | `POLICY_DENIED`, like `getAttribute()`, for every attribute name and negated too |
|
|
735
|
+
| `toHaveAccessibleName` and the state matchers | Answer as usual |
|
|
736
|
+
|
|
737
|
+
Reading the withheld value as `''` would pass a filled password field as
|
|
738
|
+
cleared, and reading the withheld `value` attribute as absent would pass
|
|
739
|
+
`not.toHaveAttribute('value')` on a password field that has one. Assert the
|
|
740
|
+
outcome of the fill, not the field.
|
|
741
|
+
|
|
742
|
+
### Option validation
|
|
743
|
+
|
|
744
|
+
- A matcher refuses any option key it does not take, and a flag that is not a
|
|
745
|
+
boolean, with `INVALID_ARGUMENT` before it reads the screen.
|
|
746
|
+
- Types catch these in TypeScript, but `e2e run` does not type-check, so the
|
|
747
|
+
check runs for a JavaScript test or a computed option object too.
|
|
748
|
+
- Playwright options e2e does not implement, such as `indeterminate` on
|
|
749
|
+
`toBeChecked` or `useInnerText` on `toHaveText`, fail this way instead of
|
|
750
|
+
passing on the default.
|
|
751
|
+
- The boolean options Playwright gives these matchers (`checked`, `enabled`,
|
|
752
|
+
`visible`, `attached`) work the same way here.
|
|
753
|
+
|
|
754
|
+
## Types
|
|
755
|
+
|
|
756
|
+
### `ExpectCall`, `SoftExpectCall`, and `Expect`
|
|
757
|
+
|
|
17
758
|
```ts
|
|
18
759
|
/** The `expect(actual)` call: a locator, `screen`, an engine fixture, or a value, told apart by the argument. */
|
|
19
760
|
export interface ExpectCall {
|
|
@@ -46,52 +787,7 @@ export interface Expect extends ExpectCall {
|
|
|
46
787
|
}
|
|
47
788
|
```
|
|
48
789
|
|
|
49
|
-
|
|
50
|
-
Such a fixture is an `Expectable<E>`: its engine attached the matcher set `E`
|
|
51
|
-
through [`expectable`](/reference/engine#contributed-fixtures), and
|
|
52
|
-
`expect(fixture)` returns it.
|
|
53
|
-
Other arguments use value matchers, including promises, so await a promise
|
|
54
|
-
before passing its result to `expect`.
|
|
55
|
-
|
|
56
|
-
| Property | Value |
|
|
57
|
-
| --- | --- |
|
|
58
|
-
| Default timeout | `config.assertionTimeout`, 5000 ms |
|
|
59
|
-
| Override | `{ timeout }` in milliseconds, on every locator and `browser` matcher |
|
|
60
|
-
| Unknown option | `INVALID_ARGUMENT` before the first read, from JavaScript too |
|
|
61
|
-
| Negation grace window | A negated matcher passes after 1000 ms of continuous truth, or at the deadline when `{ timeout }` is shorter than that and the negation held for the whole budget. The window counts from the moment the first read that saw the negation started, so a slow read counts toward it |
|
|
62
|
-
| Failure code | `ASSERTION_FAILED`, exit code 1 |
|
|
63
|
-
|
|
64
|
-
Locator strictness: except for `toHaveCount` and the list form of
|
|
65
|
-
`toHaveText` and `toContainText`, a positive matcher requires exactly one
|
|
66
|
-
match; zero matches keep polling until the timeout, and multiple matches fail
|
|
67
|
-
immediately with `LOCATOR_AMBIGUOUS`. `toBeHidden`, `toBeVisible({ visible:
|
|
68
|
-
false })`, `toBeAttached({ attached: false })`, negated visibility, and
|
|
69
|
-
negated `toBeAttached` accept zero matches; for them, and for
|
|
70
|
-
`waitFor({ state: 'detached' })` and `'hidden'`, a locator under a frame that
|
|
71
|
-
is not in the document is zero matches rather than a wait for the frame.
|
|
72
|
-
|
|
73
|
-
Text comparison is normalized: leading and trailing whitespace is trimmed and
|
|
74
|
-
each run of whitespace collapses to one space. `toHaveText` and
|
|
75
|
-
`toContainText` read the same rendered text as
|
|
76
|
-
[`textContent()`](/reference/screen#locator-reads), `innerText` on the web.
|
|
77
|
-
A `TextMatch` string matches exactly; a `RegExp` uses its own source and
|
|
78
|
-
flags; anything else, such as a predicate function, is `INVALID_ARGUMENT`.
|
|
79
|
-
`toHaveValue` is the exception: a form control's value is compared as it is,
|
|
80
|
-
newlines and trailing spaces included, and the failure message prints it that
|
|
81
|
-
way; a `contenteditable` editor's value is its rendered text, kept the same
|
|
82
|
-
way, and an empty editor reads as `''`. A control the
|
|
83
|
-
platform reports without a value, such as a cleared field on a device, reads
|
|
84
|
-
as `''`. A node whose role carries no value, such as a heading or a link,
|
|
85
|
-
never satisfies `toHaveValue`, negated or not; the message reads
|
|
86
|
-
`no value (not a form control)`. A secure field, such as a password input,
|
|
87
|
-
is never judged: the engine withholds its value and text, so `toHaveValue`,
|
|
88
|
-
`toHaveText`, and `toContainText` throw `POLICY_DENIED` there like
|
|
89
|
-
`inputValue()` and `textContent()`, negated or in list form too, instead of
|
|
90
|
-
reading the withheld value as `''` and passing a filled password field as
|
|
91
|
-
cleared. `toHaveAccessibleName` and the state matchers still answer there.
|
|
92
|
-
Assert the outcome of the fill, not the field.
|
|
93
|
-
|
|
94
|
-
## Locator matchers
|
|
790
|
+
### `AsyncExpectation` and `TextMatcherOptions`
|
|
95
791
|
|
|
96
792
|
```ts
|
|
97
793
|
/**
|
|
@@ -148,67 +844,7 @@ export interface TextMatcherOptions {
|
|
|
148
844
|
}
|
|
149
845
|
```
|
|
150
846
|
|
|
151
|
-
|
|
152
|
-
`.not.toBeChecked()` passes on a button or a heading; a node is enabled
|
|
153
|
-
unless it reports `disabled`. On the web, `disabled` is the state the
|
|
154
|
-
browser applies, not the attribute alone: a control inside a
|
|
155
|
-
`<fieldset disabled>` is disabled unless it sits in that fieldset's first
|
|
156
|
-
`<legend>`, and `aria-disabled="true"` on an ancestor disables the widgets
|
|
157
|
-
below it until an `aria-disabled="false"` cuts the chain, the same reading
|
|
158
|
-
Playwright's `toBeDisabled` gives. `toHaveAttribute(name, value)`
|
|
159
|
-
compares like `toHaveText`, after normalization. On a secure field, such as a
|
|
160
|
-
password input, `toHaveAttribute` throws `POLICY_DENIED` like
|
|
161
|
-
`getAttribute()`, for every attribute name and negated too: the field's
|
|
162
|
-
`value` attribute is withheld, so reading it as absent would pass
|
|
163
|
-
`not.toHaveAttribute('value')` on a password field that has one.
|
|
164
|
-
|
|
165
|
-
`toBeAttached` passes once one match exists, hidden or shown, where
|
|
166
|
-
`toBeVisible` needs a shown one. Negated, it passes once nothing matches.
|
|
167
|
-
|
|
168
|
-
The boolean options Playwright gives these matchers work the same way:
|
|
169
|
-
`toBeChecked({ checked: false })` waits for an unchecked node,
|
|
170
|
-
`toBeEnabled({ enabled: false })` for a disabled one,
|
|
171
|
-
`toBeVisible({ visible: false })` for a hidden or absent one, as
|
|
172
|
-
`toBeHidden` does, and `toBeAttached({ attached: false })` for no match.
|
|
173
|
-
`ignoreCase: true` on `toHaveText`, `toContainText`, `toHaveAccessibleName`,
|
|
174
|
-
and the value form of `toHaveAttribute` compares a string case-insensitively
|
|
175
|
-
and adds the `i` flag to a `RegExp`; `ignoreCase: false` removes it.
|
|
176
|
-
|
|
177
|
-
A matcher refuses any other option key, and a flag that is not a boolean, with
|
|
178
|
-
`INVALID_ARGUMENT` before it reads the screen. Types catch these in
|
|
179
|
-
TypeScript, but `e2e run` does not type-check, so the check runs for a
|
|
180
|
-
JavaScript test or a computed option object too. Playwright options e2e does
|
|
181
|
-
not implement, such as `indeterminate` on `toBeChecked` or `useInnerText` on
|
|
182
|
-
`toHaveText`, fail this way instead of passing on the default.
|
|
183
|
-
|
|
184
|
-
`toHaveText([...])` and `toContainText([...])` check a list, with no
|
|
185
|
-
exactly-one rule; the failure message lists every text observed.
|
|
186
|
-
`toHaveText([...])` needs the match count to equal the list length and the
|
|
187
|
-
match at each position to satisfy the entry there, a string exactly after
|
|
188
|
-
normalization, a `RegExp` by test; an empty list holds when nothing matches.
|
|
189
|
-
`toContainText([...])` needs each entry contained by a distinct match, in
|
|
190
|
-
list order, as Playwright's does: extra matches before, between, or after
|
|
191
|
-
are fine, a repeated entry needs a match of its own, and an empty list always
|
|
192
|
-
holds. Negated, it fails as soon as that ordered subsequence exists, so
|
|
193
|
-
`not.toContainText(['Deleted'])` fails while any match contains `Deleted`.
|
|
194
|
-
|
|
195
|
-
```ts
|
|
196
|
-
await expect(screen.getByRole('status')).toHaveText('Saved');
|
|
197
|
-
await expect(screen.getByRole('alert')).not.toContainText('error', { ignoreCase: true });
|
|
198
|
-
await expect(screen.getByLabel('Subscribe')).toBeChecked({ checked: false });
|
|
199
|
-
await expect(screen.getByTestId('todo')).toHaveCount(3);
|
|
200
|
-
await expect(screen.getByTestId('todo')).toHaveText(['Write spec', /^Ship/, 'Release']);
|
|
201
|
-
await expect(screen.getByTestId('todo')).toContainText(['spec', 'runner', 'Release']);
|
|
202
|
-
await expect(screen.getByTestId('todo')).toContainText(['spec', 'Release']);
|
|
203
|
-
await expect(screen.getByText('Loaded')).toBeAttached();
|
|
204
|
-
await expect(screen.getByRole('dialog')).not.toBeVisible({ timeout: 10_000 });
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
## Screenshot matchers
|
|
208
|
-
|
|
209
|
-
`expect(screen)` and `expect(locator)` compare against a PNG stored beside
|
|
210
|
-
the test file, on every engine that captures pixels. The
|
|
211
|
-
[Visual comparison](/visual-testing) guide shows the workflow.
|
|
847
|
+
### `ScreenExpectation` and `ScreenshotOptions`
|
|
212
848
|
|
|
213
849
|
```ts
|
|
214
850
|
export interface ScreenExpectation {
|
|
@@ -235,33 +871,7 @@ export interface ScreenshotOptions {
|
|
|
235
871
|
}
|
|
236
872
|
```
|
|
237
873
|
|
|
238
|
-
|
|
239
|
-
| --- | --- |
|
|
240
|
-
| Stored at | `<test file>-snapshots/<name>-<target>-<os>.png`; `.png` is added to a name without it |
|
|
241
|
-
| Unnamed | The test's title path, lowercase, and a count: `checkout-pays-by-card-1` |
|
|
242
|
-
| Missing | Taken from two screenshots in a row that are the same within the tolerance, then `ASSERTION_FAILED`. Written beside the test; in CI without `--update-snapshots` only attached to the results, at `snapshots/<path in the project>` |
|
|
243
|
-
| Written earlier in the run | No comparison, `.not` included, passes against a screenshot the same run wrote (a retry, a repeat, another test naming it): `ASSERTION_FAILED` until the next run |
|
|
244
|
-
| Different | Retried until one matches or the timeout; then `ASSERTION_FAILED` with `-expected`, `-actual`, and `-diff` images on the step |
|
|
245
|
-
| `--update-snapshots`, `-u` | A missing or different stored screenshot is written from a screen that holds still, and the matcher passes |
|
|
246
|
-
| `.not` | Passes once the screen differs; with nothing stored it is `ASSERTION_FAILED` |
|
|
247
|
-
| Tolerance | Both `maxDiffPixels` and `maxDiffPixelRatio` set: the smaller wins |
|
|
248
|
-
| After a secret fill | `POLICY_DENIED`, as for `app.screenshot()` |
|
|
249
|
-
|
|
250
|
-
A name with a path separator, an option the matcher does not take (Playwright's
|
|
251
|
-
`fullPage`, `animations`, `clip`), or a value out of range is
|
|
252
|
-
`INVALID_ARGUMENT` before the screen is read, and so is `expect` on a screen
|
|
253
|
-
scoped to a frame: compare a locator inside it instead.
|
|
254
|
-
|
|
255
|
-
```ts
|
|
256
|
-
await expect(screen).toHaveScreenshot('home.png');
|
|
257
|
-
await expect(screen.getByRole('navigation')).toHaveScreenshot();
|
|
258
|
-
await expect(screen).toHaveScreenshot('feed.png', { mask: [screen.getByTestId('timestamp')], maxDiffPixels: 50 });
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
## browser matchers
|
|
262
|
-
|
|
263
|
-
Contributed by `@e2e-dev/web`; the type is exported from that package.
|
|
264
|
-
Every matcher call is a recorded `expect.<matcher>` assertion step.
|
|
874
|
+
### `BrowserExpectation`
|
|
265
875
|
|
|
266
876
|
```ts
|
|
267
877
|
export interface BrowserExpectation {
|
|
@@ -276,26 +886,7 @@ export interface BrowserExpectation {
|
|
|
276
886
|
}
|
|
277
887
|
```
|
|
278
888
|
|
|
279
|
-
|
|
280
|
-
exactly after normalization; a `RegExp` tests the full URL. Anything else,
|
|
281
|
-
such as a predicate function, is `INVALID_ARGUMENT`. `toHaveURL` also takes
|
|
282
|
-
Playwright's `ignoreCase`, with the locator matchers' semantics. Otherwise each
|
|
283
|
-
`browser` matcher takes `timeout` only; any other key, such as `ignoreCase` on
|
|
284
|
-
`toHaveTitle`, which Playwright does not take either, is `INVALID_ARGUMENT`.
|
|
285
|
-
|
|
286
|
-
`toHaveClass` polls for the node too: a target that matches nothing yet is one
|
|
287
|
-
more poll, not a failure, until the assertion timeout runs out. An ambiguous
|
|
288
|
-
target still fails at once with `LOCATOR_AMBIGUOUS`. It reads the `class`
|
|
289
|
-
attribute, so `''` matches `class=""` and not an element with no `class`
|
|
290
|
-
attribute at all; Playwright's `toHaveClass('')` accepts both.
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
await expect(browser).toHaveURL('/dashboard');
|
|
294
|
-
await expect(browser).toHaveTitle(/Dashboard/);
|
|
295
|
-
await expect(browser).toHaveClass(browser.locator('.card'), 'card active');
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
## Value matchers
|
|
889
|
+
### `ValueExpectation`
|
|
299
890
|
|
|
300
891
|
```ts
|
|
301
892
|
export interface ValueExpectation<T> {
|
|
@@ -364,94 +955,7 @@ export type Class = abstract new (...args: never[]) => unknown;
|
|
|
364
955
|
export type PropertyPath = string | readonly (string | number)[];
|
|
365
956
|
```
|
|
366
957
|
|
|
367
|
-
|
|
368
|
-
change, use [Polling a value](#polling-a-value).
|
|
369
|
-
|
|
370
|
-
```ts
|
|
371
|
-
const total = await screen.getByTestId('total').textContent();
|
|
372
|
-
expect(total).toContain('$');
|
|
373
|
-
expect(items).toHaveLength(3);
|
|
374
|
-
expect(payload).toEqual({ ok: true });
|
|
375
|
-
expect(order).toMatchObject({ customer: { name: 'Ada' }, lines: [{ sku: 'a' }, { sku: 'b' }] });
|
|
376
|
-
expect(order).toHaveProperty('customer.email', 'ada@example.test');
|
|
377
|
-
expect(totals.lineTotal).toBeCloseTo(60);
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
`toEqual` compares structure the way Jest's does: primitives with `Object.is`
|
|
381
|
-
(so `NaN` equals `NaN` and `0` does not equal `-0`), arrays and plain objects
|
|
382
|
-
by their contents, `Date` by time, `RegExp` by source and flags, `Map` and
|
|
383
|
-
`Set` by their entries. A property whose value is `undefined` is treated as
|
|
384
|
-
absent, a sparse array equals one holding `undefined`, and a class instance
|
|
385
|
-
equals a plain object with the same properties. There is no `toStrictEqual`.
|
|
386
|
-
|
|
387
|
-
`toMatchObject` reads `expected` as a subset: every property it names must
|
|
388
|
-
match recursively, properties it does not name are ignored. An array in
|
|
389
|
-
`expected` matches an array of the same length, element by element, each
|
|
390
|
-
element again as a subset. Both sides must be objects; a primitive on either
|
|
391
|
-
side is refused.
|
|
392
|
-
|
|
393
|
-
`toHaveProperty` walks a dotted path or an array of keys. Use the array form
|
|
394
|
-
when a key contains a dot, or to index an array by number
|
|
395
|
-
(`['lines', 0, 'sku']`). Inherited properties count; a property holding
|
|
396
|
-
`undefined` exists. A string, number, or boolean on the path is read through
|
|
397
|
-
its wrapper object, as in Jest, so `toHaveProperty('label.length', 3)` and
|
|
398
|
-
`toHaveProperty(['label', 0], 'a')` reach into a string; `null` and
|
|
399
|
-
`undefined` have no properties. With a second argument the value at the path
|
|
400
|
-
must equal it by `toEqual` rules.
|
|
401
|
-
|
|
402
|
-
### Matching a schema
|
|
403
|
-
|
|
404
|
-
`toMatchSchema` validates a value against a
|
|
405
|
-
[Standard Schema](https://standardschema.dev): a Zod, Valibot, or ArkType
|
|
406
|
-
schema, or any library that implements the spec. Pass the schema your app
|
|
407
|
-
already parses its API with, and the test fails when the API drifts from it.
|
|
408
|
-
It returns the schema's output, typed, so the rest of the test reads
|
|
409
|
-
properties without casting:
|
|
410
|
-
|
|
411
|
-
```ts
|
|
412
|
-
import { z } from 'zod';
|
|
413
|
-
|
|
414
|
-
const User = z.object({ id: z.number(), name: z.string(), email: z.email() });
|
|
415
|
-
|
|
416
|
-
const users = expect(await response.json()).toMatchSchema(z.array(User));
|
|
417
|
-
expect(users.map((user) => user.name)).toContain('Ada');
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
A failure lists every issue by its path (`1.email: Invalid email address`).
|
|
421
|
-
The output is the schema's, so defaults and transforms apply. A matcher
|
|
422
|
-
returns before anything could await it, so a schema that validates
|
|
423
|
-
asynchronously (an async refinement) is `INVALID_ARGUMENT`. Negated, it
|
|
424
|
-
passes when the value does not match and returns nothing.
|
|
425
|
-
|
|
426
|
-
A matcher that cannot evaluate its value (`toContain` on a number,
|
|
427
|
-
`toHaveLength` on an object without one) fails as `ASSERTION_FAILED` like a
|
|
428
|
-
mismatch would, so under `expect.poll` a value that has not taken its final
|
|
429
|
-
shape yet is one more failing sample. A `toMatch` pattern that is neither a
|
|
430
|
-
string nor a `RegExp` is `INVALID_ARGUMENT`.
|
|
431
|
-
|
|
432
|
-
The unit-runner matchers (`toThrow`, `toBeInstanceOf`, `toBeNaN`,
|
|
433
|
-
`toStrictEqual`, `resolves`, `rejects`, `expect.extend`) are not part of
|
|
434
|
-
`expect`. An end-to-end test checks what the app shows and what its API
|
|
435
|
-
returns; the value matchers cover that, see
|
|
436
|
-
[API](/api-testing). Code-level
|
|
437
|
-
checks belong in the unit runner.
|
|
438
|
-
|
|
439
|
-
Pass a second argument to describe the check in its failure message:
|
|
440
|
-
|
|
441
|
-
```ts
|
|
442
|
-
expect(created, 'the company exists through the API').toBeTruthy();
|
|
443
|
-
// ASSERTION_FAILED: the company exists through the API: expected undefined to be truthy
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
Only value expectations take a message; a locator or `browser` expectation
|
|
447
|
-
already names its locator and matcher in the failure.
|
|
448
|
-
|
|
449
|
-
## Asymmetric matchers
|
|
450
|
-
|
|
451
|
-
An asymmetric matcher stands in for a value inside `toEqual`,
|
|
452
|
-
`toMatchObject`, the collection form of `toContain`, and the value of
|
|
453
|
-
`toHaveProperty`. Where the comparison would test equality, it asks the
|
|
454
|
-
matcher instead. They are Jest's matchers, with Jest's rules.
|
|
958
|
+
### `AsymmetricMatcher`
|
|
455
959
|
|
|
456
960
|
```ts
|
|
457
961
|
/**
|
|
@@ -468,83 +972,7 @@ export interface AsymmetricMatcher {
|
|
|
468
972
|
}
|
|
469
973
|
```
|
|
470
974
|
|
|
471
|
-
|
|
472
|
-
| --- | --- |
|
|
473
|
-
| `expect.any(Class)` | An instance of the class. For `String`, `Number`, `Boolean`, `BigInt`, `Symbol`, and `Function`, the primitive too; for `Object`, anything `typeof` calls an object, `null` included |
|
|
474
|
-
| `expect.anything()` | Anything but `null` and `undefined` |
|
|
475
|
-
| `expect.objectContaining(sample)` | An object whose properties include every property of `sample`, each compared by `toEqual` rules |
|
|
476
|
-
| `expect.arrayContaining(sample)` | An array holding every element of `sample`, in any order, each compared by `toEqual` rules |
|
|
477
|
-
| `expect.stringContaining(text)` | A string with `text` as a substring |
|
|
478
|
-
| `expect.stringMatching(text \| RegExp)` | A string the RegExp tests true on; a text sample is compiled to a RegExp, so `'a.b'` matches `'axb'` |
|
|
479
|
-
|
|
480
|
-
```ts
|
|
481
|
-
expect(created).toEqual({
|
|
482
|
-
id: expect.stringMatching(/^usr_/),
|
|
483
|
-
createdAt: expect.any(String),
|
|
484
|
-
roles: expect.arrayContaining(['member']),
|
|
485
|
-
profile: expect.objectContaining({ name: 'Ada' }),
|
|
486
|
-
});
|
|
487
|
-
expect(rows).toContain(expect.objectContaining({ status: 'paid' }));
|
|
488
|
-
expect(response).toHaveProperty('body.total', expect.any(Number));
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
In a failure message a matcher prints as its description: `Any<Number>`,
|
|
492
|
-
`ObjectContaining {"name":"Ada"}`. Matchers nest: an `objectContaining`
|
|
493
|
-
sample may hold an `expect.any`. The string form of `toContain` takes a
|
|
494
|
-
string only, so `expect('abc').toContain(expect.stringContaining('a'))` is
|
|
495
|
-
refused.
|
|
496
|
-
|
|
497
|
-
## Soft assertions
|
|
498
|
-
|
|
499
|
-
`expect.soft(actual)` takes what `expect(actual)` takes and returns the same
|
|
500
|
-
matchers. A soft matcher that fails does not throw: the failure is kept on the
|
|
501
|
-
running attempt and the body continues. Once the body has settled, and before
|
|
502
|
-
`afterEach`, the attempt fails with one `ASSERTION_FAILED` that lists every
|
|
503
|
-
kept failure in order. The error's source line is the first soft matcher that
|
|
504
|
-
failed.
|
|
505
|
-
|
|
506
|
-
```ts
|
|
507
|
-
test('checkout summary', async ({ agent, screen }) => {
|
|
508
|
-
await agent.act('add the "Pro" plan to the cart and open the summary');
|
|
509
|
-
expect.soft(await screen.getByTestId('subtotal').textContent()).toBe('$40.00');
|
|
510
|
-
expect.soft(await screen.getByTestId('tax').textContent()).toBe('$8.00');
|
|
511
|
-
await expect.soft(screen.getByRole('button', 'Pay')).toBeEnabled();
|
|
512
|
-
await expect(screen.getByTestId('total')).toHaveText('$48.00');
|
|
513
|
-
});
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
```text
|
|
517
|
-
ASSERTION_FAILED: 2 soft assertions failed
|
|
518
|
-
1. expected "$40.00" to be "$41.00"
|
|
519
|
-
2. expect.toBeEnabled failed
|
|
520
|
-
locator: getByRole('button', 'Pay')
|
|
521
|
-
expected: enabled
|
|
522
|
-
observed: states: disabled (match count 1)
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
A soft locator or `browser` matcher is still a recorded `expect.<matcher>` step,
|
|
526
|
-
marked failed, so the report shows where in the flow it landed. A soft value
|
|
527
|
-
matcher is not a step, the same as a hard one; its failure appears in the
|
|
528
|
-
attempt's error records.
|
|
529
|
-
|
|
530
|
-
When the body throws on its own, or times out, that error stays the
|
|
531
|
-
attempt's verdict and the soft failures are recorded beside it under
|
|
532
|
-
`secondaryErrors`. A body that skips itself with `test.skip(...)` after a
|
|
533
|
-
soft failure still fails: the soft failures are the attempt's error, and the
|
|
534
|
-
skip reason is kept in the attempt's `skip`.
|
|
535
|
-
|
|
536
|
-
A soft `toMatchSchema` returns the schema's output when the value matches
|
|
537
|
-
and `undefined` after a kept failure, and its type says so.
|
|
538
|
-
|
|
539
|
-
Outside a test body there is nothing to keep a failure for, so `expect.soft`
|
|
540
|
-
throws at once like `expect`: in a standalone script, and in an `afterEach`
|
|
541
|
-
hook, which runs after the soft failures have been settled. There is no
|
|
542
|
-
`expect.soft.poll`; `expect.poll` has its own timeout to fail on.
|
|
543
|
-
|
|
544
|
-
## Polling a value
|
|
545
|
-
|
|
546
|
-
`expect.poll(read)` re-reads a value until a matcher passes. Use it to wait for
|
|
547
|
-
an API response or saved data after an app action.
|
|
975
|
+
### `PollOptions` and `PollExpectation`
|
|
548
976
|
|
|
549
977
|
```ts
|
|
550
978
|
export interface PollOptions {
|
|
@@ -579,51 +1007,3 @@ export type NegatedPollExpectation<T> = {
|
|
|
579
1007
|
readonly [K in Exclude<keyof ValueExpectation<T>, 'not'>]: (...args: Parameters<ValueExpectation<T>[K]>) => Promise<void>;
|
|
580
1008
|
};
|
|
581
1009
|
```
|
|
582
|
-
|
|
583
|
-
Every value matcher is available, returning a promise. Each sample calls
|
|
584
|
-
`read` and runs the matcher. A matcher failure or a `read` that throws is one
|
|
585
|
-
failing sample; a `read` that hangs is cut at the deadline.
|
|
586
|
-
|
|
587
|
-
The timeout defaults to `config.assertionTimeout` during an attempt and
|
|
588
|
-
5000 ms outside one. It is capped by the test deadline in the body, or the
|
|
589
|
-
hook's `cleanupTimeout` during `afterEach`.
|
|
590
|
-
|
|
591
|
-
When the attempt is cancelled or times out, the loop and any read in flight
|
|
592
|
-
stop with `CANCELLED`. `timeout` must be a finite number 0 or more and
|
|
593
|
-
`interval` a finite number above 0; anything else is `INVALID_CONFIG` before
|
|
594
|
-
the first read.
|
|
595
|
-
|
|
596
|
-
At the deadline it throws `ASSERTION_FAILED` naming the matcher, then
|
|
597
|
-
`message` when given, then the last sample: the last matcher failure, the
|
|
598
|
-
last `read` error, or `no read completed`.
|
|
599
|
-
|
|
600
|
-
```text
|
|
601
|
-
expect.poll(...).toBe(...) timed out after 5000 ms
|
|
602
|
-
the batch never finished
|
|
603
|
-
last: expected "running" to be "done"
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
```ts
|
|
607
|
-
await agent.act('create a test named "AI checkout regression" described as {description}', {
|
|
608
|
-
params: { description },
|
|
609
|
-
});
|
|
610
|
-
await expect
|
|
611
|
-
.poll(() => getTest(workspace).then((row) => row?.title), { timeout: 15_000 })
|
|
612
|
-
.toBe('AI checkout regression');
|
|
613
|
-
await expect.poll(async () => (await listTests(workspace)).length).toBeGreaterThan(1);
|
|
614
|
-
await expect.poll(() => readStatus(), { message: 'the batch never finished' }).not.toBe('running');
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
`expect.poll` is not recorded as a report step; a timeout surfaces only as
|
|
618
|
-
the test's failure.
|
|
619
|
-
|
|
620
|
-
Await every poll. A poll belongs to the phase that started it: the test body
|
|
621
|
-
(with its fixtures and `beforeEach` hooks), one `afterEach` hook or fixture
|
|
622
|
-
teardown, or one `beforeAll` or `afterAll` hook. When that phase returns while
|
|
623
|
-
the poll still runs, the poll is cancelled and the phase fails with
|
|
624
|
-
`STEP_NOT_AWAITED` at the line of the call, whether or not the poll would
|
|
625
|
-
have passed. It never fails a later test: a poll started by work its phase
|
|
626
|
-
left running, such as a body past its timeout, is cancelled before its first
|
|
627
|
-
read. A body that threw keeps its own
|
|
628
|
-
error and records this one under `secondaryErrors`, and a suite hook reports
|
|
629
|
-
it as `HOOK_FAILED`.
|