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
package/docs/reference/test.mdx
CHANGED
|
@@ -6,9 +6,620 @@ icon: "flask-vial"
|
|
|
6
6
|
"twitter:image": "https://e2e.tester.army/docs/images/og/reference/test.png?v=d1ad3b01c3"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
registration or during its body.
|
|
9
|
+
`test` declares tests, setup tests, groups, and hooks. Declarations run
|
|
10
|
+
synchronously while the test file is imported. Use `test.extend` to add
|
|
11
|
+
fixtures and `test.skip` to skip a test at registration or during its body.
|
|
12
|
+
|
|
13
|
+
```ts title="tests/checkout.e2e.ts"
|
|
14
|
+
import { beforeEach, describe, expect, test } from 'e2e';
|
|
15
|
+
|
|
16
|
+
describe('checkout', { tags: ['billing'] }, () => {
|
|
17
|
+
beforeEach(async ({ app }) => {
|
|
18
|
+
await app.open('/checkout');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('pays with a saved card', { retries: 2 }, async ({ screen }) => {
|
|
22
|
+
await screen.getByRole('button', 'Pay').tap();
|
|
23
|
+
await expect(screen.getByText('Payment received')).toBeVisible();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Reference
|
|
29
|
+
|
|
30
|
+
### Members
|
|
31
|
+
|
|
32
|
+
| Member | Returns |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| [`test`](#test) | `TestCase` |
|
|
35
|
+
| [`test.skip`](#test-skip) | `TestCase \| void` |
|
|
36
|
+
| [`test.only`](#test-only) | `TestCase` |
|
|
37
|
+
| [`test.setup`](#test-setup) | `TestCase` |
|
|
38
|
+
| [`test.extend`](#test-extend) | `TestAPI` |
|
|
39
|
+
| [`describe`](#describe) | `void` |
|
|
40
|
+
| [Hooks](#hooks) | `void` |
|
|
41
|
+
|
|
42
|
+
`describe` and the four hooks are also top-level exports, the same functions
|
|
43
|
+
as `test.describe`, `test.beforeEach`, and the rest:
|
|
44
|
+
`import { describe, beforeEach, test } from 'e2e'`.
|
|
45
|
+
|
|
46
|
+
- A per-test hook imported from `e2e` is typed with the core fixtures.
|
|
47
|
+
- `@e2e-dev/web` and `@e2e-dev/mobile` export `test`, `describe`, and all
|
|
48
|
+
four hooks typed with their `browser` or `device` fixture, so a test file
|
|
49
|
+
registers from one import.
|
|
50
|
+
- A `test` you extended with your own fixtures registers hooks that see them
|
|
51
|
+
through `test.beforeEach`.
|
|
52
|
+
|
|
53
|
+
Registering a test, group, or hook outside module evaluation throws
|
|
54
|
+
`COLLECTION_ERROR`. The full declarations are under [Types](#types).
|
|
55
|
+
|
|
56
|
+
#### `test`
|
|
57
|
+
|
|
58
|
+
Registers one test, with or without [options](#options).
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { describe, test } from '@e2e-dev/web';
|
|
62
|
+
import { expect } from 'e2e';
|
|
63
|
+
|
|
64
|
+
test('app opens', async ({ app, browser }) => {
|
|
65
|
+
await app.open();
|
|
66
|
+
await expect(browser).toHaveURL('/');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('checks out', { tags: ['billing'], retries: 2, timeout: 60_000 }, async ({ app }) => {
|
|
70
|
+
await app.open('/checkout');
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A test or group title is 1 through 512 UTF-8 bytes after NFC normalization,
|
|
75
|
+
with no NUL character.
|
|
76
|
+
|
|
77
|
+
| Throws | When |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `COLLECTION_ERROR` | Two tests in one file share a title path: the group titles, then the test's own. |
|
|
80
|
+
|
|
81
|
+
#### `test.skip`
|
|
82
|
+
|
|
83
|
+
Registers a skipped test, or skips the running test.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
test.skip('exports a PDF', async ({ app }) => {}); // at registration
|
|
87
|
+
test.skip(organizations.length < 2, 'a single organization'); // inside a body
|
|
88
|
+
test.skip('not on this tenant'); // inside a body, always
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- A skipped test does not affect the exit code by default.
|
|
92
|
+
- To skip at collection by option, use the [`skip`](#skip) option.
|
|
93
|
+
- At registration it returns a `TestCase`; inside a body, `void`.
|
|
94
|
+
|
|
95
|
+
Call `test.skip(condition, reason)` inside a test for a condition you learn
|
|
96
|
+
at runtime, such as a disabled feature flag:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
test('switches organizations', { session: 'admin' }, async ({ app, agent }) => {
|
|
100
|
+
const organizations = await listOrganizations();
|
|
101
|
+
test.skip(organizations.length < 2, 'the demo tenant has a single organization');
|
|
102
|
+
await app.open('/backend');
|
|
103
|
+
await agent.act(`switch to the organization "${organizations[1].name}"`);
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- When the condition is true, the call throws, as Playwright's does. Nothing
|
|
108
|
+
after the call runs.
|
|
109
|
+
- With no failure before it, the test is reported `skipped` with `reason`,
|
|
110
|
+
with no retry.
|
|
111
|
+
- Steps before it stay in the report as they ran.
|
|
112
|
+
- A failed `expect.soft` before the call still fails the attempt, as in
|
|
113
|
+
Playwright. The attempt is `failed`, with the soft failures as its error and
|
|
114
|
+
the skip reason in its `skip`. It retries like any failure.
|
|
115
|
+
- Hooks and fixtures still run their teardown.
|
|
116
|
+
- When the condition is a precondition, call it first, before any step.
|
|
117
|
+
- A failed attempt followed by a skip on retry leaves the test `skipped`. The
|
|
118
|
+
CLI lists it under `Skipped After Failure` with its original error. Set
|
|
119
|
+
[`failOnSkippedFailure: true`](/reference/config) to fail the run with exit
|
|
120
|
+
code 1 in that case. The default is `false`.
|
|
121
|
+
|
|
122
|
+
| Throws | When |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| `INVALID_ARGUMENT` | Called in a setup test. A setup test cannot skip because other tests depend on its sessions. |
|
|
125
|
+
| `COLLECTION_ERROR` | The conditional form, outside a test body. |
|
|
126
|
+
|
|
127
|
+
#### `test.only`
|
|
128
|
+
|
|
129
|
+
Registers one focused local test.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
test.only('pays', async ({ app }) => {
|
|
133
|
+
await app.open('/checkout');
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Other tests are reported as `filtered`. A CI run with a focused test fails
|
|
138
|
+
with `ONLY_IN_CI`.
|
|
139
|
+
|
|
140
|
+
#### `test.setup`
|
|
141
|
+
|
|
142
|
+
Registers a setup test that declares the sessions it saves, and must save
|
|
143
|
+
each exactly once.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { credentials } from 'e2e';
|
|
147
|
+
|
|
148
|
+
test.setup('authenticate', { sessions: ['member'] }, async ({ app, screen, session }) => {
|
|
149
|
+
await app.open('/login');
|
|
150
|
+
await screen.getByLabel('Email').fill(credentials.user('member').username);
|
|
151
|
+
await screen.getByLabel('Password').fill(credentials.user('member').password);
|
|
152
|
+
await screen.getByRole('button', 'Sign in').tap();
|
|
153
|
+
await session.save('member');
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- `test.setup` must be top-level, not inside `describe`.
|
|
158
|
+
- The [`sessions`](#sessions) option declares the names.
|
|
159
|
+
- A test consumes a saved session with the [`session`](#session) option.
|
|
160
|
+
- The setup body gets one extra fixture, `session`:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
export interface SetupSession {
|
|
164
|
+
/** Saves state under a setup-declared name. */
|
|
165
|
+
save(name: string): Promise<void>;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| Throws | When |
|
|
170
|
+
| --- | --- |
|
|
171
|
+
| `SESSION_CONTRACT` | At runtime, a declared session was not saved, or was saved twice. |
|
|
172
|
+
|
|
173
|
+
#### `test.extend`
|
|
174
|
+
|
|
175
|
+
Returns a new `test` typed with more fixtures. The one it was called on is
|
|
176
|
+
unchanged.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { test as base } from 'e2e';
|
|
180
|
+
import type { Device } from '@e2e-dev/mobile';
|
|
181
|
+
|
|
182
|
+
export const test = base.extend<{ device: Device }>(); // types only
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- With no argument, `extend` refines only the types: it returns the same
|
|
186
|
+
runtime `test`, typed with an engine's contributed fixtures. They still
|
|
187
|
+
resolve from the target's engine at runtime. This types a device, desktop,
|
|
188
|
+
or web surface without a global `declare module` augmentation.
|
|
189
|
+
- With definitions, it adds fixtures of your own. See
|
|
190
|
+
[Your own fixtures](#your-own-fixtures).
|
|
191
|
+
|
|
192
|
+
#### `describe`
|
|
193
|
+
|
|
194
|
+
Declares a group of tests, with or without options.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { describe, test } from 'e2e';
|
|
198
|
+
|
|
199
|
+
describe('checkout', { tags: ['smoke'], serial: true }, () => {
|
|
200
|
+
test('adds an item', async ({ screen }) => {});
|
|
201
|
+
test('pays', async ({ screen }) => {});
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- A group takes the [options](#options) a test takes, except `only`, plus
|
|
206
|
+
[`serial`](#serial).
|
|
207
|
+
- The body is synchronous.
|
|
208
|
+
|
|
209
|
+
| Throws | When |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `COLLECTION_ERROR` | An `async` body. It is also a type error. |
|
|
212
|
+
| `COLLECTION_ERROR` | `test.setup` inside the body. |
|
|
213
|
+
|
|
214
|
+
### Hooks
|
|
215
|
+
|
|
216
|
+
| Hook | Runs |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| [`beforeEach`](#beforeeach) | Before each test attempt |
|
|
219
|
+
| [`afterEach`](#aftereach) | After each test attempt |
|
|
220
|
+
| [`beforeAll`](#beforeall) | Once per suite instance |
|
|
221
|
+
| [`afterAll`](#afterall) | After the scope's last test |
|
|
222
|
+
|
|
223
|
+
Hook order follows scope nesting, not position in the file, so a hook
|
|
224
|
+
declared after a test still applies to it.
|
|
225
|
+
|
|
226
|
+
#### `beforeEach`
|
|
227
|
+
|
|
228
|
+
Registers a test-attempt setup hook.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
test.beforeEach(async ({ app }) => {
|
|
232
|
+
await app.open('/');
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- Runs outermost group first, then in declaration order within a group.
|
|
237
|
+
- `beforeEach` and the body share the test timeout.
|
|
238
|
+
- Receives the test fixtures.
|
|
239
|
+
|
|
240
|
+
#### `afterEach`
|
|
241
|
+
|
|
242
|
+
Registers a test-attempt teardown hook.
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
test.afterEach(async ({ app }) => {
|
|
246
|
+
await app.screenshot('after');
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- Runs innermost group first, in reverse declaration order, after a failure
|
|
251
|
+
or timeout too.
|
|
252
|
+
- Each `afterEach` hook gets its own `cleanupTimeout` budget with working
|
|
253
|
+
fixtures, so teardown can still drive the app after the body timed out.
|
|
254
|
+
- Receives the test fixtures.
|
|
255
|
+
|
|
256
|
+
#### `beforeAll`
|
|
257
|
+
|
|
258
|
+
Registers a suite-instance setup hook. It runs once per suite instance, on
|
|
259
|
+
first entry into the scope.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
beforeAll(async ({ platform }) => {
|
|
263
|
+
await seedDatabase(platform);
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The module is re-imported for every retry, serial group, and setup test, so
|
|
268
|
+
`beforeAll` runs again in each. Its only fixture is `platform`.
|
|
269
|
+
|
|
270
|
+
| Throws | When |
|
|
271
|
+
| --- | --- |
|
|
272
|
+
| `HOOK_FAILED` | The hook throws. The suite's tests are skipped with cause `hook-failed`. Inside a serial group it skips every remaining member and fails the group without a retry. |
|
|
273
|
+
|
|
274
|
+
#### `afterAll`
|
|
275
|
+
|
|
276
|
+
Registers a suite-instance teardown hook. It runs as soon as the scope's last
|
|
277
|
+
test in the realm is done.
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
afterAll(async () => {
|
|
281
|
+
await resetDatabase();
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
A realm that a failure discards runs `afterAll` for every scope whose
|
|
286
|
+
`beforeAll` started. Its only fixture is `platform`. A throw is `HOOK_FAILED`,
|
|
287
|
+
as for [`beforeAll`](#beforeall).
|
|
288
|
+
|
|
289
|
+
#### Blocked event loop
|
|
290
|
+
|
|
291
|
+
A test that blocks its worker's event loop (`while (true) {}` in a fixture,
|
|
292
|
+
`beforeEach`, the body, or `afterEach`) stops every timer in that worker, so
|
|
293
|
+
the worker cannot time it out.
|
|
294
|
+
|
|
295
|
+
- When the worker has not responded `cleanupTimeout` (at least 5 s) after the
|
|
296
|
+
test timeout, the runner kills it.
|
|
297
|
+
- That attempt is reported `timed-out` with `TEST_TIMEOUT` and no artifacts,
|
|
298
|
+
after the attempts before it, and is not retried.
|
|
299
|
+
- The rest of its file runs on a new worker. In a serial group, the members
|
|
300
|
+
after it are skipped.
|
|
301
|
+
- A `beforeAll` or `afterAll` that blocks still hangs the run.
|
|
302
|
+
|
|
303
|
+
<Note>
|
|
304
|
+
A worker with a debugger attached when the test starts (an editor's
|
|
305
|
+
auto-attach, or `NODE_OPTIONS=--inspect=0`) is never killed this way, so a
|
|
306
|
+
breakpoint can stay paused. A programmatic run with an inline config or
|
|
307
|
+
in-memory tests runs in the runner's own process and has no worker to kill: a
|
|
308
|
+
blocked event loop hangs it.
|
|
309
|
+
</Note>
|
|
310
|
+
|
|
311
|
+
### Options
|
|
312
|
+
|
|
313
|
+
| Option | Type | Default |
|
|
314
|
+
| --- | --- | --- |
|
|
315
|
+
| [`timeout`](#timeout) | `number` | `config.timeout` |
|
|
316
|
+
| [`retries`](#retries) | `number` | `config.retries` |
|
|
317
|
+
| [`tags`](#tags) | `readonly string[]` | - |
|
|
318
|
+
| [`skip`](#skip) | `boolean \| string` | - |
|
|
319
|
+
| [`only`](#only) | `boolean` | - |
|
|
320
|
+
| [`platforms`](#platforms) | `readonly string[]` | - |
|
|
321
|
+
| [`requires`](#requires) | `readonly Capability[]` | - |
|
|
322
|
+
| [`session`](#session) | `string` | - |
|
|
323
|
+
| [`agentContext`](#agentcontext) | `string` | - |
|
|
324
|
+
| [`agent`](#agent) | `string \| readonly string[]` | the run's agents |
|
|
325
|
+
| [`trace`](#trace) | `RecordingMode` | the run's |
|
|
326
|
+
| [`video`](#video) | `RecordingMode` | the run's |
|
|
327
|
+
| [`serial`](#serial) | `boolean` | - |
|
|
328
|
+
| [`sessions`](#sessions) | `readonly string[]` | required |
|
|
329
|
+
|
|
330
|
+
`test` takes every option but `serial` and `sessions`. `describe` takes
|
|
331
|
+
every option but `only` and `sessions`. `test.setup` requires `sessions` and
|
|
332
|
+
takes no `session`, `only`, or `skip`.
|
|
333
|
+
|
|
334
|
+
Resolution order: test, nearest group, outer groups, config, built-in
|
|
335
|
+
default. Inside a [serial group](#serial), the group's `retries`, `trace`, and
|
|
336
|
+
`video` apply.
|
|
337
|
+
|
|
338
|
+
| Throws | When |
|
|
339
|
+
| --- | --- |
|
|
340
|
+
| `COLLECTION_ERROR` | An option key the call does not take (`{ timout: 1000 }`). The error names the nearest known key when it is a likely typo, and every key the call takes otherwise. |
|
|
341
|
+
|
|
342
|
+
#### `timeout`
|
|
343
|
+
|
|
344
|
+
The attempt deadline in milliseconds. Innermost wins.
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
test('imports a large file', { timeout: 300_000 }, async ({ app }) => {}); // default: config.timeout
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`timeout` must be a positive safe integer.
|
|
351
|
+
|
|
352
|
+
#### `retries`
|
|
353
|
+
|
|
354
|
+
How many times a failed test is retried.
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
test('checks out', { retries: 2 }, async ({ app }) => {}); // default: config.retries
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
An integer from 0 through 10.
|
|
361
|
+
|
|
362
|
+
#### `tags`
|
|
363
|
+
|
|
364
|
+
Tags that `--tag` selects on. Every layer's tags are unioned.
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
describe('checkout', { tags: ['billing', 'smoke'] }, () => {});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Tags are distinct names, none blank, with no comma and no leading or
|
|
371
|
+
trailing whitespace.
|
|
372
|
+
|
|
373
|
+
#### `skip`
|
|
374
|
+
|
|
375
|
+
Skips the test at collection. A string is the reason. Any truthy layer skips.
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
test('exports a PDF', { skip: 'PDF export is off on staging' }, async ({ app }) => {});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### `only`
|
|
382
|
+
|
|
383
|
+
Focuses the test locally. CI rejects it with `ONLY_IN_CI`.
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
test('pays', { only: true }, async ({ app }) => {});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### `platforms`
|
|
390
|
+
|
|
391
|
+
Platforms the test runs on. Other targets skip it. Innermost wins.
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
test('uses Face ID', { platforms: ['ios'] }, async ({ app }) => {});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### `requires`
|
|
398
|
+
|
|
399
|
+
Capabilities the target must have. A target that lacks one skips the test at
|
|
400
|
+
selection. Innermost wins.
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
test('onboarding asks for the camera', { requires: ['native-app'] }, async ({ app, agent }) => {
|
|
404
|
+
await app.open();
|
|
405
|
+
await agent.assert('The app asks for camera access');
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Accepted values:
|
|
410
|
+
|
|
411
|
+
- The harness capabilities of the target's engine: `observation`, `actions`,
|
|
412
|
+
`location`, `pointer`, `keyboard`, `state`, `artifacts`.
|
|
413
|
+
- One per contributed fixture: `browser`, `device`. See
|
|
414
|
+
[the engine contract](/reference/engine#engine).
|
|
415
|
+
- The kind of app the target declares: `browser` for an `app.url`,
|
|
416
|
+
`native-app` for an `app.bundleId` or `app.appPath`.
|
|
417
|
+
|
|
418
|
+
#### `session`
|
|
419
|
+
|
|
420
|
+
The session a setup test saved, restored before the body runs.
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
test('sees the dashboard', { session: 'member' }, async ({ app }) => {});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
See [`test.setup`](#test-setup).
|
|
427
|
+
|
|
428
|
+
#### `agentContext`
|
|
429
|
+
|
|
430
|
+
Extra context for agent steps. Layers are concatenated outermost first.
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
describe('pricing', { agentContext: 'Prices are shown in EUR.' }, () => {});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
#### `agent`
|
|
437
|
+
|
|
438
|
+
The configured agent (`agents.<name>`) this test or group runs with, in
|
|
439
|
+
place of the run's agents.
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
test('checks out', { agent: ['fast', 'smart'] }, async ({ agent }) => {}); // one result per agent
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
- A list runs the test once per agent named, as one result each.
|
|
446
|
+
- Innermost wins. A call's own `agent` option wins over it.
|
|
447
|
+
- `--agent` narrows a list to the names both name, and never overrides a pin
|
|
448
|
+
the flag does not name.
|
|
449
|
+
- A setup test runs once per target, so it pins at most one agent.
|
|
450
|
+
|
|
451
|
+
| Throws | When |
|
|
452
|
+
| --- | --- |
|
|
453
|
+
| `COLLECTION_ERROR` | At selection, before any test runs: a name that `agents` does not define. |
|
|
454
|
+
|
|
455
|
+
#### `trace`
|
|
456
|
+
|
|
457
|
+
Whether the test keeps a trace page, in place of the run's.
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
test('checkout', { trace: 'retain-on-failure' }, async ({ agent }) => {});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
- Takes a [recording mode](/reference/config#recording), the same as the
|
|
464
|
+
config's `trace`: `off`, `on`, `retain-on-failure`, `on-first-retry`, or
|
|
465
|
+
`on-all-retries`.
|
|
466
|
+
- A test's or group's own value wins over `--trace`, the target's, and the
|
|
467
|
+
config's. One test can record on a run that records nothing, or opt out of
|
|
468
|
+
one that records everything.
|
|
469
|
+
|
|
470
|
+
#### `video`
|
|
471
|
+
|
|
472
|
+
Which of the test's attempts record a video, in place of the run's.
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
test('checkout', { video: 'on' }, async ({ agent }) => {
|
|
476
|
+
await agent.act('Buy the first product in the catalog');
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
- Takes the same [recording modes](/reference/config#recording) as `trace`
|
|
481
|
+
and the config's `video`.
|
|
482
|
+
- A test's or group's own value wins over `--video`, the target's, and the
|
|
483
|
+
config's.
|
|
484
|
+
|
|
485
|
+
| Throws | When |
|
|
486
|
+
| --- | --- |
|
|
487
|
+
| `UNSUPPORTED_ARTIFACT` | Before any test starts, naming the test: the mode records on some attempt and the target's engine cannot record video. |
|
|
488
|
+
|
|
489
|
+
#### `serial`
|
|
490
|
+
|
|
491
|
+
`describe` only. Runs the group as one ordered retry unit with shared app
|
|
492
|
+
state.
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
describe('checkout', { serial: true }, () => {
|
|
496
|
+
test('adds an item', async ({ screen }) => {});
|
|
497
|
+
test('pays', async ({ screen }) => {});
|
|
498
|
+
});
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
- The group also shares secret state: a secret fill withholds pixels for the
|
|
502
|
+
rest of the group's attempt. A retry starts fresh.
|
|
503
|
+
- Members take the `trace` and `video` on the serial `describe` or a group
|
|
504
|
+
around it. The group records one video per group attempt.
|
|
505
|
+
- A group attempt may not start because its file fails to load or its session
|
|
506
|
+
fails to launch. Then the first member fails with that error, and the rest
|
|
507
|
+
skip with cause `serial-predecessor-failed`. When the run is interrupted
|
|
508
|
+
during the launch, every member skips instead.
|
|
509
|
+
- On a retry, the members keep the verdicts of the attempt before it.
|
|
510
|
+
- The exit code follows the error's category, as for any other test.
|
|
511
|
+
|
|
512
|
+
| Throws | When |
|
|
513
|
+
| --- | --- |
|
|
514
|
+
| `COLLECTION_ERROR` | A member sets `retries`, `trace`, `video`, `session`, `platforms`, `requires`, `skip`, `only`, or a nested `serial`. |
|
|
515
|
+
|
|
516
|
+
#### `sessions`
|
|
517
|
+
|
|
518
|
+
`test.setup` only. The session names the setup test must save.
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
test.setup('authenticate', { sessions: ['member', 'admin'] }, async ({ session }) => {});
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
A static, non-empty array of unique names. Each name matches
|
|
525
|
+
`/^[A-Za-z0-9_.-]{1,128}$/`. See [`test.setup`](#test-setup).
|
|
526
|
+
|
|
527
|
+
### Fixtures
|
|
528
|
+
|
|
529
|
+
| Fixture | Type |
|
|
530
|
+
| --- | --- |
|
|
531
|
+
| [`agent`](/reference/agent) | `Agent` |
|
|
532
|
+
| [`app`](/reference/app) | `App` |
|
|
533
|
+
| [`screen`](/reference/screen) | `Screen` |
|
|
534
|
+
| `platform` | `string` |
|
|
535
|
+
| [`session`](#test-setup) | `SetupSession` |
|
|
536
|
+
|
|
537
|
+
`agent`, `app`, and `screen` reach test bodies, `beforeEach`, and
|
|
538
|
+
`afterEach`. `platform` reaches every hook too, and `session` only
|
|
539
|
+
`test.setup` bodies. [`TestFixtures`](#testfixtures) describes each one.
|
|
540
|
+
|
|
541
|
+
Everything else is a fixture the target's engine contributes, or one of
|
|
542
|
+
[your own](#your-own-fixtures). `@e2e-dev/web` contributes
|
|
543
|
+
[`browser`](/reference/web); a device engine contributes `device`.
|
|
544
|
+
|
|
545
|
+
| Throws | When |
|
|
546
|
+
| --- | --- |
|
|
547
|
+
| `UNSUPPORTED_CAPABILITY` | A test reaches for a fixture the engine does not contribute. Declare `requires: ['browser']` to skip such tests at selection instead. |
|
|
548
|
+
|
|
549
|
+
<Note>
|
|
550
|
+
The fixtures above are lazy. Destructuring in the parameter list acquires
|
|
551
|
+
them before the body's first statement. [Your own
|
|
552
|
+
fixtures](#your-own-fixtures) are not lazy.
|
|
553
|
+
</Note>
|
|
554
|
+
|
|
555
|
+
#### Your own fixtures
|
|
556
|
+
|
|
557
|
+
`test.extend(fixtures)` defines resources set up before each test and cleaned
|
|
558
|
+
up afterward, including after a failure. In each fixture function, code before
|
|
559
|
+
`await use(value)` sets up the resource. The test receives `value`, and code
|
|
560
|
+
after `use` tears it down.
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
import { test as base } from '@e2e-dev/web';
|
|
564
|
+
|
|
565
|
+
export const test = base.extend<{ workspace: Workspace }>({
|
|
566
|
+
workspace: async ({ browser }, use) => {
|
|
567
|
+
const workspace = await createWorkspace();
|
|
568
|
+
await signIn(browser, workspace);
|
|
569
|
+
await use(workspace); // the hooks and the body run here
|
|
570
|
+
await workspace.cleanup(); // teardown, after the body even when it failed
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test('renames the workspace', async ({ workspace, screen }) => {
|
|
575
|
+
await screen.getByLabel('Name').fill(`${workspace.name} (renamed)`);
|
|
576
|
+
});
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
A definition is typed against the fixtures of the `test` it extends, not its siblings in
|
|
580
|
+
the same call. A fixture that needs another goes in a chained second
|
|
581
|
+
`extend`: `base.extend({ a }).extend({ b })` gives `b` a fixture object that
|
|
582
|
+
already has `a`.
|
|
583
|
+
|
|
584
|
+
Per attempt:
|
|
585
|
+
|
|
586
|
+
1. Each definition in the `extend` chain the test was registered through
|
|
587
|
+
runs in declaration order, after the engine's fixtures, until it calls
|
|
588
|
+
`use`. It runs whether or not the test names it. A fixture that opens a
|
|
589
|
+
page or changes app state does so for every test of that `test`. So a
|
|
590
|
+
file whose tests need different setups registers them through different
|
|
591
|
+
`extend` chains. Setup counts against the test `timeout`. A failure is
|
|
592
|
+
reported in phase `beforeEach`, before any `beforeEach` hook has run.
|
|
593
|
+
2. `beforeEach` hooks, the body, then `afterEach` hooks run with the same
|
|
594
|
+
fixture object. The fixture set is the test's own chain: a hook that reads
|
|
595
|
+
`workspace` around a test registered through `base` fails with
|
|
596
|
+
`UNSUPPORTED_CAPABILITY`.
|
|
597
|
+
3. Teardowns run in reverse order after the last `afterEach`, whether or not
|
|
598
|
+
the body passed, each with its own `cleanupTimeout` budget. An error there
|
|
599
|
+
fails a passing attempt in phase `afterEach` and joins `secondaryErrors`
|
|
600
|
+
after a body failure.
|
|
601
|
+
|
|
602
|
+
| Mistake | Fails |
|
|
603
|
+
| --- | --- |
|
|
604
|
+
| A value that is not a plain object of functions, an empty name, a core name (`agent`, `app`, `screen`, `platform`, `session`), or a name an earlier `extend` in the chain defined | At import, `COLLECTION_ERROR` naming the fixture |
|
|
605
|
+
| A name the target's engine contributes (`browser` on a Playwright target) | The attempt, in `beforeEach`, `TEST_SETUP_FAILED` |
|
|
606
|
+
| A definition that returns without calling `use` | The attempt, in `beforeEach`, `TEST_SETUP_FAILED` |
|
|
607
|
+
| A definition that calls `use` twice | The attempt, at teardown, `TEST_SETUP_FAILED` |
|
|
608
|
+
| A definition that throws before `use` | The attempt, in `beforeEach`, with that error |
|
|
609
|
+
| A definition that throws after `use` | The attempt, in `afterEach`, with that error |
|
|
610
|
+
|
|
611
|
+
<Note>
|
|
612
|
+
A body or hook that hits the test `timeout` is abandoned, not cancelled, so
|
|
613
|
+
teardown starts while it may still be running. A setup abandoned this way
|
|
614
|
+
that reaches `use` later gets an immediate resolve. Its teardown runs
|
|
615
|
+
detached from the run, so what it allocated is still released. Later
|
|
616
|
+
fixtures, hooks, and the test body do not start after an abandoned setup or
|
|
617
|
+
hook finishes.
|
|
618
|
+
</Note>
|
|
619
|
+
|
|
620
|
+
## Types
|
|
621
|
+
|
|
622
|
+
### `TestAPI`
|
|
12
623
|
|
|
13
624
|
```ts
|
|
14
625
|
export interface TestAPI<Fixtures = TestFixtures> {
|
|
@@ -20,9 +631,10 @@ export interface TestAPI<Fixtures = TestFixtures> {
|
|
|
20
631
|
skip(title: string, fn: TestFn<Fixtures>): TestCase;
|
|
21
632
|
/**
|
|
22
633
|
* Inside a running test: skips it when `condition` is true (or always,
|
|
23
|
-
* with no condition),
|
|
24
|
-
* `skipped` with `reason`; steps that already ran stay
|
|
25
|
-
*
|
|
634
|
+
* with no condition) by throwing, so nothing after the call runs. The
|
|
635
|
+
* attempt is reported `skipped` with `reason`; steps that already ran stay
|
|
636
|
+
* in the report without deciding the status, and teardown still runs. A
|
|
637
|
+
* setup test cannot skip: its sessions are owed.
|
|
26
638
|
* Outside a test body this form is `COLLECTION_ERROR`; use the `skip`
|
|
27
639
|
* option to skip at collection.
|
|
28
640
|
*/
|
|
@@ -82,138 +694,7 @@ export const beforeAll: TestAPI['beforeAll'];
|
|
|
82
694
|
export const afterAll: TestAPI['afterAll'];
|
|
83
695
|
```
|
|
84
696
|
|
|
85
|
-
`
|
|
86
|
-
as `test.describe` and `test.beforeEach` and the rest:
|
|
87
|
-
`import { describe, beforeEach, test } from 'e2e'`. A per-test hook imported
|
|
88
|
-
from `e2e` is typed with the core fixtures. `@e2e-dev/web` and
|
|
89
|
-
`@e2e-dev/mobile` export `test`, `describe`, and all four hooks typed with
|
|
90
|
-
their `browser` or `device` fixture, so a test file registers from one
|
|
91
|
-
import, and a `test` you extended with your own fixtures registers hooks
|
|
92
|
-
that see them through `test.beforeEach`.
|
|
93
|
-
|
|
94
|
-
Registering a test, group, or hook outside module evaluation throws
|
|
95
|
-
`COLLECTION_ERROR`.
|
|
96
|
-
|
|
97
|
-
A test or group title is 1 through 512 UTF-8 bytes after NFC normalization,
|
|
98
|
-
with no NUL character. Two tests in one file with the same title path, the
|
|
99
|
-
group titles and then the test's own, are a `COLLECTION_ERROR`.
|
|
100
|
-
|
|
101
|
-
## Members
|
|
102
|
-
|
|
103
|
-
A skipped test does not affect the exit code by default. With `test.only`, other tests
|
|
104
|
-
are reported as `filtered`, and a CI run fails with `ONLY_IN_CI`.
|
|
105
|
-
|
|
106
|
-
Call `test.skip(condition, reason)` inside a test for conditions you learn at
|
|
107
|
-
runtime, such as a disabled feature flag. When the condition is true, the call
|
|
108
|
-
throws: the body stops at that line, as Playwright's does. Nothing after the
|
|
109
|
-
call runs. With no failure before it, the test is reported `skipped` with
|
|
110
|
-
`reason`, with no retry.
|
|
111
|
-
Steps before it stay in the report as they ran. A failed `expect.soft` before
|
|
112
|
-
the call still fails the attempt, as in Playwright: the attempt is `failed`
|
|
113
|
-
with the soft failures as its error and the skip reason in its `skip`, and it
|
|
114
|
-
retries like any failure. Hooks and fixtures still run their teardown. When
|
|
115
|
-
the condition is a precondition, call it first, before any step.
|
|
116
|
-
|
|
117
|
-
A failed attempt followed by a skip on retry leaves the test `skipped`, and
|
|
118
|
-
the CLI lists it under `Skipped After Failure` with its original error. Set
|
|
119
|
-
[`failOnSkippedFailure: true`](/reference/config) to fail the run with exit
|
|
120
|
-
code 1 in that case. The default is `false`.
|
|
121
|
-
|
|
122
|
-
A setup test cannot skip because other tests depend on its sessions; the
|
|
123
|
-
call fails with `INVALID_ARGUMENT`. Outside a test body, the conditional
|
|
124
|
-
form is `COLLECTION_ERROR`. Use the `skip` option when declaring the test.
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
test('switches organizations', { session: 'admin' }, async ({ app, agent }) => {
|
|
128
|
-
const organizations = await listOrganizations();
|
|
129
|
-
test.skip(organizations.length < 2, 'the demo tenant has a single organization');
|
|
130
|
-
await app.open('/backend');
|
|
131
|
-
await agent.act(`switch to the organization "${organizations[1].name}"`);
|
|
132
|
-
});
|
|
133
|
-
```
|
|
134
|
-
`test.setup` must be top-level, not inside `describe`. An `async`
|
|
135
|
-
`describe` body is a type error and a `COLLECTION_ERROR`.
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
import { describe, test } from '@e2e-dev/web';
|
|
139
|
-
import { expect } from 'e2e';
|
|
140
|
-
|
|
141
|
-
test('app opens', async ({ app, browser }) => {
|
|
142
|
-
await app.open();
|
|
143
|
-
await expect(browser).toHaveURL('/');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('checks out', { tags: ['billing'], retries: 2, timeout: 60_000 }, async ({ app }) => {
|
|
147
|
-
await app.open('/checkout');
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
describe('checkout', { tags: ['smoke'], serial: true }, () => {
|
|
151
|
-
test('adds an item', async ({ screen }) => {});
|
|
152
|
-
test('pays', async ({ screen }) => {});
|
|
153
|
-
});
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
`test.setup` declares the sessions it saves, and must save each exactly once:
|
|
157
|
-
|
|
158
|
-
```ts
|
|
159
|
-
import { credentials } from 'e2e';
|
|
160
|
-
|
|
161
|
-
test.setup('authenticate', { sessions: ['member'] }, async ({ app, screen, session }) => {
|
|
162
|
-
await app.open('/login');
|
|
163
|
-
await screen.getByLabel('Email').fill(credentials.user('member').username);
|
|
164
|
-
await screen.getByLabel('Password').fill(credentials.user('member').password);
|
|
165
|
-
await screen.getByRole('button', 'Sign in').tap();
|
|
166
|
-
await session.save('member');
|
|
167
|
-
});
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Session names must match `/^[A-Za-z0-9_.-]{1,128}$/` and be unique.
|
|
171
|
-
Not saving a declared session, or saving one twice, throws `SESSION_CONTRACT`
|
|
172
|
-
at runtime.
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
export interface SetupSession {
|
|
176
|
-
/** Saves state under a setup-declared name. */
|
|
177
|
-
save(name: string): Promise<void>;
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## Hooks
|
|
182
|
-
|
|
183
|
-
`beforeEach` hooks run outermost group first, then in declaration order
|
|
184
|
-
within a group. `afterEach` hooks run innermost first, in reverse declaration
|
|
185
|
-
order, after a failure or timeout too. `beforeAll` runs once per suite
|
|
186
|
-
instance on first entry into the scope; `afterAll` when the scope's last test
|
|
187
|
-
in the realm finishes. Hook order follows scope nesting, not position in the
|
|
188
|
-
file, so a hook declared after a test still applies to it.
|
|
189
|
-
|
|
190
|
-
The module is re-imported for every retry, serial group, and setup test, so
|
|
191
|
-
`beforeAll` runs again in each. A scope's `afterAll` runs as soon as its last
|
|
192
|
-
test in that realm is done. A realm that a failure discards runs `afterAll`
|
|
193
|
-
for every scope whose `beforeAll` started.
|
|
194
|
-
|
|
195
|
-
`beforeEach` and the body share the test timeout. Each `afterEach` hook gets
|
|
196
|
-
its own `cleanupTimeout` budget with working fixtures, so teardown can still
|
|
197
|
-
drive the app after the body timed out.
|
|
198
|
-
|
|
199
|
-
A test that blocks its worker's event loop (`while (true) {}` in a fixture,
|
|
200
|
-
`beforeEach`, the body, or `afterEach`) stops every timer in that worker, so
|
|
201
|
-
the worker cannot time it out. A `beforeAll` or `afterAll` that blocks still
|
|
202
|
-
hangs the run. When the worker has not responded `cleanupTimeout` (at least 5 s) after
|
|
203
|
-
the test timeout, the runner kills it. That attempt is reported `timed-out`
|
|
204
|
-
with `TEST_TIMEOUT` and no artifacts, after the attempts before it, and is
|
|
205
|
-
not retried. The rest of its file runs on a new worker. In a serial group,
|
|
206
|
-
the members after it are skipped. A worker with a debugger attached when the
|
|
207
|
-
test starts (an editor's auto-attach, or `NODE_OPTIONS=--inspect=0`) is
|
|
208
|
-
never killed this way, so a breakpoint can stay paused. A programmatic run with an inline
|
|
209
|
-
config or in-memory tests runs in the runner's own process and has no worker
|
|
210
|
-
to kill: a blocked event loop hangs it.
|
|
211
|
-
|
|
212
|
-
A throw inside `beforeAll` or `afterAll` is `HOOK_FAILED` and skips the
|
|
213
|
-
suite's tests with cause `hook-failed`. Inside a serial group it skips every
|
|
214
|
-
remaining member and fails the group without a retry.
|
|
215
|
-
|
|
216
|
-
## Options
|
|
697
|
+
### `TestOptions`
|
|
217
698
|
|
|
218
699
|
```ts
|
|
219
700
|
export interface TestOptions {
|
|
@@ -244,9 +725,10 @@ export interface TestOptions {
|
|
|
244
725
|
*/
|
|
245
726
|
agent?: string | readonly string[];
|
|
246
727
|
/**
|
|
247
|
-
*
|
|
248
|
-
* modes as the config's `trace`. Innermost wins, over `--trace` too;
|
|
249
|
-
* inside a serial group the group's value applies
|
|
728
|
+
* Which of the test's attempts keep a trace, in place of the run's: the
|
|
729
|
+
* same modes as the config's `trace`. Innermost wins, over `--trace` too;
|
|
730
|
+
* inside a serial group the group's value applies, since the group runs as
|
|
731
|
+
* one unit.
|
|
250
732
|
*/
|
|
251
733
|
trace?: RecordingMode;
|
|
252
734
|
/**
|
|
@@ -271,62 +753,7 @@ export interface SetupOptions extends Omit<TestOptions, 'session' | 'only' | 'sk
|
|
|
271
753
|
}
|
|
272
754
|
```
|
|
273
755
|
|
|
274
|
-
|
|
275
|
-
0 through 10. Groups do not accept `only`. An option key the call does not
|
|
276
|
-
take (`{ timout: 1000 }`) is `COLLECTION_ERROR`, naming the nearest known key
|
|
277
|
-
when it is a likely typo and every key the call takes otherwise. `test.setup` requires `sessions`
|
|
278
|
-
to be a static, non-empty array of unique names.
|
|
279
|
-
|
|
280
|
-
Resolution order: test, nearest group, outer groups, config, built-in default.
|
|
281
|
-
|
|
282
|
-
`requires` names capabilities of the target's engine, the harness ones
|
|
283
|
-
(`observation`, `actions`, `location`, `pointer`, `keyboard`, `state`,
|
|
284
|
-
`artifacts`) and one per contributed fixture (`browser`, `device`; see
|
|
285
|
-
[the engine contract](/reference/engine#engine)), or the kind of app the
|
|
286
|
-
target declares: `browser` for an `app.url`, `native-app` for an
|
|
287
|
-
`app.bundleId` or `app.appPath`. A target that lacks one skips the test at
|
|
288
|
-
selection:
|
|
289
|
-
|
|
290
|
-
```ts
|
|
291
|
-
test('onboarding asks for the camera', { requires: ['native-app'] }, async ({ app, agent }) => {
|
|
292
|
-
await app.open();
|
|
293
|
-
await agent.assert('The app asks for camera access');
|
|
294
|
-
});
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
An `agent` name that `agents` does not define is a `COLLECTION_ERROR` at
|
|
298
|
-
selection, before any test runs.
|
|
299
|
-
|
|
300
|
-
`trace` and `video` take a [recording mode](/reference/config#recording):
|
|
301
|
-
`off`, `on`, `retain-on-failure`, `on-first-retry`, or `on-all-retries`. A
|
|
302
|
-
test's or group's own value wins over `--trace` / `--video`, the target's,
|
|
303
|
-
and the config's, so one test can record on a run that records nothing, or
|
|
304
|
-
opt out of one that records everything. A `video` mode set here is required:
|
|
305
|
-
when it records on some attempt and the target's engine cannot record video,
|
|
306
|
-
the run fails with `UNSUPPORTED_ARTIFACT` before any test starts, naming the
|
|
307
|
-
test:
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
test('checkout', { video: 'on' }, async ({ agent }) => {
|
|
311
|
-
await agent.act('Buy the first product in the catalog');
|
|
312
|
-
});
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
`serial: true` makes the group one ordered retry unit with shared app state
|
|
316
|
-
and shared secret state: a secret fill withholds pixels for the rest of the
|
|
317
|
-
group's attempt. A retry starts fresh. Inside it, `retries`, `trace`,
|
|
318
|
-
`video`, `session`, `platforms`, `requires`, `skip`, `only`, and a nested
|
|
319
|
-
`serial` on a member are collection errors. Set them on the group. Members
|
|
320
|
-
take the `trace` and `video` on the serial `describe` or a group around it,
|
|
321
|
-
and the group records one video per group attempt. A group attempt that
|
|
322
|
-
cannot start, because its file fails to load or its session fails to launch,
|
|
323
|
-
fails the first member with that error and skips the rest with cause
|
|
324
|
-
`serial-predecessor-failed`. When the run is interrupted during the launch,
|
|
325
|
-
every member skips instead. On a retry, the members keep the verdicts of
|
|
326
|
-
the attempt before it. The exit code follows the error's category, as for any
|
|
327
|
-
other test.
|
|
328
|
-
|
|
329
|
-
## Fixtures
|
|
756
|
+
### `TestFixtures`
|
|
330
757
|
|
|
331
758
|
```ts
|
|
332
759
|
export interface TestFixtures {
|
|
@@ -351,41 +778,7 @@ export interface SuiteFixtures {
|
|
|
351
778
|
}
|
|
352
779
|
```
|
|
353
780
|
|
|
354
|
-
|
|
355
|
-
[your own](#your-own-fixtures). `@e2e-dev/web` contributes
|
|
356
|
-
[`browser`](/reference/web) and exports a `test` typed with it; a device
|
|
357
|
-
engine contributes `device`. Reaching for a fixture the engine does not
|
|
358
|
-
contribute fails with `UNSUPPORTED_CAPABILITY`; declare `requires: ['browser']`
|
|
359
|
-
to skip such tests at selection instead.
|
|
360
|
-
|
|
361
|
-
The fixtures above are lazy. Destructuring in the parameter list acquires
|
|
362
|
-
them before the body's first statement. Your own fixtures are not: every
|
|
363
|
-
fixture in the `extend` chain a test was registered through sets up for that
|
|
364
|
-
test, even when its parameters omit it.
|
|
365
|
-
|
|
366
|
-
### Your own fixtures
|
|
367
|
-
|
|
368
|
-
`test.extend(fixtures)` defines resources set up before each test and cleaned
|
|
369
|
-
up afterward, including after a failure. In each fixture function, code before
|
|
370
|
-
`await use(value)` sets up the resource. The test receives `value`, and code
|
|
371
|
-
after `use` tears it down.
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
import { test as base } from '@e2e-dev/web';
|
|
375
|
-
|
|
376
|
-
export const test = base.extend<{ workspace: Workspace }>({
|
|
377
|
-
workspace: async ({ browser }, use) => {
|
|
378
|
-
const workspace = await createWorkspace();
|
|
379
|
-
await signIn(browser, workspace);
|
|
380
|
-
await use(workspace); // the hooks and the body run here
|
|
381
|
-
await workspace.cleanup(); // teardown, after the body even when it failed
|
|
382
|
-
},
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
test('renames the workspace', async ({ workspace, screen }) => {
|
|
386
|
-
await screen.getByLabel('Name').fill(`${workspace.name} (renamed)`);
|
|
387
|
-
});
|
|
388
|
-
```
|
|
781
|
+
### `FixtureDefinitions`
|
|
389
782
|
|
|
390
783
|
```ts
|
|
391
784
|
/**
|
|
@@ -410,47 +803,7 @@ export type FixtureDefinitions<Base, Extra> = {
|
|
|
410
803
|
};
|
|
411
804
|
```
|
|
412
805
|
|
|
413
|
-
|
|
414
|
-
definition is typed against the fixtures of the `test` it extends, not its
|
|
415
|
-
siblings in the same call. A fixture that needs another goes in a chained
|
|
416
|
-
second `extend`: `base.extend({ a }).extend({ b })` gives `b` a fixture
|
|
417
|
-
object that already has `a`.
|
|
418
|
-
|
|
419
|
-
Per attempt:
|
|
420
|
-
|
|
421
|
-
1. Each definition runs in declaration order until it calls `use`, whether
|
|
422
|
-
or not the test names it: a fixture that opens a page or changes app
|
|
423
|
-
state does so for every test of that `test`, so a file whose tests need
|
|
424
|
-
different setups registers them through different `extend` chains. Setup
|
|
425
|
-
counts against the test `timeout`; a failure is reported in phase
|
|
426
|
-
`beforeEach`, before any `beforeEach` hook has run.
|
|
427
|
-
2. `beforeEach` hooks, the body, then `afterEach` hooks run with the same
|
|
428
|
-
fixture object. The fixture set is the test's own chain: a hook that reads
|
|
429
|
-
`workspace` around a test registered through `base` fails with
|
|
430
|
-
`UNSUPPORTED_CAPABILITY`.
|
|
431
|
-
3. Teardowns run in reverse order after the last `afterEach`, whether or not
|
|
432
|
-
the body passed, each with its own `cleanupTimeout` budget. An error there
|
|
433
|
-
fails a passing attempt in phase `afterEach` and joins `secondaryErrors`
|
|
434
|
-
after a body failure.
|
|
435
|
-
|
|
436
|
-
What fails, and where:
|
|
437
|
-
|
|
438
|
-
| Mistake | Fails |
|
|
439
|
-
| --- | --- |
|
|
440
|
-
| A value that is not a plain object of functions, an empty name, a core name (`agent`, `app`, `screen`, `platform`, `session`), or a name an earlier `extend` in the chain defined | At import, `COLLECTION_ERROR` naming the fixture |
|
|
441
|
-
| A name the target's engine contributes (`browser` on a Playwright target) | The attempt, in `beforeEach`, `TEST_SETUP_FAILED` |
|
|
442
|
-
| A definition that returns without calling `use` | The attempt, in `beforeEach`, `TEST_SETUP_FAILED` |
|
|
443
|
-
| A definition that calls `use` twice | The attempt, at teardown, `TEST_SETUP_FAILED` |
|
|
444
|
-
| A definition that throws before `use` | The attempt, in `beforeEach`, with that error |
|
|
445
|
-
| A definition that throws after `use` | The attempt, in `afterEach`, with that error |
|
|
446
|
-
|
|
447
|
-
A body or hook that hits the test `timeout` is abandoned, not cancelled, so
|
|
448
|
-
teardown starts while it may still be running. A setup abandoned this way
|
|
449
|
-
that reaches `use` later gets an immediate resolve, and its teardown runs
|
|
450
|
-
detached from the run, so what it allocated is still released.
|
|
451
|
-
Later fixtures, hooks, and the test body do not start after an abandoned setup or hook finishes.
|
|
452
|
-
|
|
453
|
-
## Function types
|
|
806
|
+
### Function types
|
|
454
807
|
|
|
455
808
|
```ts
|
|
456
809
|
/** A test body. */
|