e2e 0.15.1 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +51 -24
  2. package/dist/.build.json +1 -1
  3. package/dist/agent/act.js +3 -3
  4. package/dist/agent/act.js.map +1 -1
  5. package/dist/agent/error.d.ts +16 -3
  6. package/dist/agent/error.d.ts.map +1 -1
  7. package/dist/agent/error.js +25 -6
  8. package/dist/agent/error.js.map +1 -1
  9. package/dist/agent/model/tool-choice.d.ts +24 -1
  10. package/dist/agent/model/tool-choice.d.ts.map +1 -1
  11. package/dist/agent/model/tool-choice.js +27 -1
  12. package/dist/agent/model/tool-choice.js.map +1 -1
  13. package/dist/agent/step-cache.d.ts +14 -3
  14. package/dist/agent/step-cache.d.ts.map +1 -1
  15. package/dist/agent/step-cache.js +13 -3
  16. package/dist/agent/step-cache.js.map +1 -1
  17. package/dist/agent/tool-loop.d.ts.map +1 -1
  18. package/dist/agent/tool-loop.js +62 -9
  19. package/dist/agent/tool-loop.js.map +1 -1
  20. package/dist/cache/context.d.ts +19 -6
  21. package/dist/cache/context.d.ts.map +1 -1
  22. package/dist/cache/context.js +15 -21
  23. package/dist/cache/context.js.map +1 -1
  24. package/dist/cli/init/scaffold.d.ts +1 -0
  25. package/dist/cli/init/scaffold.d.ts.map +1 -1
  26. package/dist/cli/init/scaffold.js +2 -1
  27. package/dist/cli/init/scaffold.js.map +1 -1
  28. package/dist/cli/init/sibling-versions.json +1 -1
  29. package/dist/expect/values.js +1 -1
  30. package/dist/expect/values.js.map +1 -1
  31. package/dist/run/execute.d.ts.map +1 -1
  32. package/dist/run/execute.js +21 -8
  33. package/dist/run/execute.js.map +1 -1
  34. package/dist/types.d.ts +1 -1
  35. package/dist/types.d.ts.map +1 -1
  36. package/docs/agent-steps.mdx +126 -0
  37. package/docs/agents.mdx +217 -0
  38. package/docs/api-testing.mdx +65 -0
  39. package/docs/authentication.mdx +345 -0
  40. package/docs/browser.mdx +440 -0
  41. package/docs/bug-bash.mdx +90 -0
  42. package/docs/cache.mdx +306 -0
  43. package/docs/ci.mdx +210 -0
  44. package/docs/coding-agents.mdx +183 -0
  45. package/docs/debugging.mdx +291 -0
  46. package/docs/executors.mdx +243 -0
  47. package/docs/explore.mdx +174 -0
  48. package/docs/github.mdx +184 -0
  49. package/docs/index.mdx +82 -0
  50. package/docs/integrations/eas.mdx +216 -0
  51. package/docs/integrations/index.mdx +24 -0
  52. package/docs/integrations/kernel.mdx +163 -0
  53. package/docs/migrate/cypress.mdx +124 -0
  54. package/docs/migrate/detox.mdx +140 -0
  55. package/docs/migrate/maestro.mdx +229 -0
  56. package/docs/migrate/playwright.mdx +427 -0
  57. package/docs/migrate/selenium.mdx +110 -0
  58. package/docs/mobile-ci.mdx +567 -0
  59. package/docs/mobile.mdx +555 -0
  60. package/docs/models.mdx +241 -0
  61. package/docs/quickstart.mdx +260 -0
  62. package/docs/reference/agent.mdx +524 -0
  63. package/docs/reference/app.mdx +72 -0
  64. package/docs/reference/cli.mdx +817 -0
  65. package/docs/reference/config.mdx +986 -0
  66. package/docs/reference/engine.mdx +1362 -0
  67. package/docs/reference/environment.mdx +113 -0
  68. package/docs/reference/errors.mdx +143 -0
  69. package/docs/reference/expect.mdx +513 -0
  70. package/docs/reference/mcp.mdx +202 -0
  71. package/docs/reference/mobile.mdx +522 -0
  72. package/docs/reference/reporters.mdx +216 -0
  73. package/docs/reference/screen.mdx +688 -0
  74. package/docs/reference/test.mdx +449 -0
  75. package/docs/reference/web.mdx +611 -0
  76. package/docs/security.mdx +292 -0
  77. package/docs/starting-your-app.mdx +231 -0
  78. package/docs/subscriptions.mdx +157 -0
  79. package/docs/telemetry.mdx +185 -0
  80. package/docs/tools.mdx +150 -0
  81. package/docs/viewports.mdx +146 -0
  82. package/docs/writing-an-engine.mdx +316 -0
  83. package/docs/writing-tests.mdx +256 -0
  84. package/package.json +2 -1
  85. package/skills/e2e/SKILL.md +4 -1
  86. package/skills/e2e/references/agent.md +3 -1
  87. package/skills/e2e/references/setup.md +13 -4
@@ -0,0 +1,449 @@
1
+ ---
2
+ title: test
3
+ description: Tests, setup tests, groups, hooks, options, and fixtures.
4
+ ---
5
+
6
+ Declare tests, groups, and hooks synchronously while the test file is
7
+ imported. Use `test.extend` to add fixtures and `test.skip` to skip a test at
8
+ registration or during its body.
9
+
10
+ ```ts
11
+ export interface TestAPI<Fixtures = TestFixtures> {
12
+ /** Registers one test synchronously during module evaluation. */
13
+ (title: string, fn: TestFn<Fixtures>): TestCase;
14
+ /** Registers one configured test synchronously during module evaluation. */
15
+ (title: string, options: TestOptions, fn: TestFn<Fixtures>): TestCase;
16
+ /** Registers one skipped test. */
17
+ skip(title: string, fn: TestFn<Fixtures>): TestCase;
18
+ /**
19
+ * Inside a running test: skips it when `condition` is true (or always,
20
+ * with no condition), ending the body there. The attempt is reported
21
+ * `skipped` with `reason`; steps that already ran stay in the report, and
22
+ * teardown still runs. A setup test cannot skip: its sessions are owed.
23
+ * Outside a test body this form is `COLLECTION_ERROR`; use the `skip`
24
+ * option to skip at collection.
25
+ */
26
+ skip(condition: boolean, reason?: string): void;
27
+ skip(reason?: string): void;
28
+ /** Registers one focused local test. CI rejects focused tests. */
29
+ only(title: string, fn: TestFn<Fixtures>): TestCase;
30
+ /** Registers one setup test with statically declared session outputs. */
31
+ setup(title: string, options: SetupOptions, fn: SetupFn<Fixtures>): TestCase;
32
+ /**
33
+ * Returns the same runtime `test`, typed with an engine's contributed
34
+ * fixtures. A pure type refinement, the fixtures still resolve from the
35
+ * target's engine at runtime, so a project types its device/desktop/web
36
+ * surface without a global `declare module` augmentation:
37
+ *
38
+ * export const test = base.extend<{ device: Device }>();
39
+ */
40
+ extend<Extra>(): TestAPI<Fixtures & Extra>;
41
+ /**
42
+ * Returns a new `test` whose registrations carry these fixture
43
+ * definitions on top of the ones it already had. Each attempt sets them up
44
+ * in declaration order after the engine's fixtures, runs the hooks and the
45
+ * body with them, and tears them down in reverse order after `afterEach`:
46
+ *
47
+ * export const test = base.extend<{ workspace: Workspace }>({
48
+ * workspace: async ({ browser }, use) => {
49
+ * const workspace = await createWorkspace();
50
+ * await use(workspace);
51
+ * await workspace.cleanup();
52
+ * },
53
+ * });
54
+ */
55
+ extend<Extra>(fixtures: FixtureDefinitions<Fixtures, Extra>): TestAPI<Fixtures & Extra>;
56
+ /** Declares a group synchronously. */
57
+ describe<Result>(title: string, body: SynchronousBody<Result>): void;
58
+ /** Declares a configured group synchronously. */
59
+ describe<Result>(
60
+ title: string,
61
+ options: DescribeOptions,
62
+ body: SynchronousBody<Result>,
63
+ ): void;
64
+ /** Registers a test-attempt setup hook. */
65
+ beforeEach(fn: TestHookFn<Fixtures>): void;
66
+ /** Registers a test-attempt teardown hook. */
67
+ afterEach(fn: TestHookFn<Fixtures>): void;
68
+ /** Registers a suite-instance setup hook. */
69
+ beforeAll(fn: SuiteHookFn): void;
70
+ /** Registers a suite-instance teardown hook. */
71
+ afterAll(fn: SuiteHookFn): void;
72
+ }
73
+
74
+ export const test: TestAPI;
75
+ export const describe: TestAPI['describe'];
76
+ export const beforeEach: TestAPI['beforeEach'];
77
+ export const afterEach: TestAPI['afterEach'];
78
+ export const beforeAll: TestAPI['beforeAll'];
79
+ export const afterAll: TestAPI['afterAll'];
80
+ ```
81
+
82
+ `describe` and the four hooks are also top-level exports, the same functions
83
+ as `test.describe` and `test.beforeEach` and the rest:
84
+ `import { describe, beforeEach, test } from 'e2e'`. A per-test hook imported
85
+ from `e2e` is typed with the core fixtures. `@e2e-dev/web` and
86
+ `@e2e-dev/mobile` export `test`, `describe`, and all four hooks typed with
87
+ their `browser` or `device` fixture, so a test file registers from one
88
+ import, and a `test` you extended with your own fixtures registers hooks
89
+ that see them through `test.beforeEach`.
90
+
91
+ Registering a test, group, or hook outside module evaluation throws
92
+ `COLLECTION_ERROR`.
93
+
94
+ A test or group title is 1 through 512 UTF-8 bytes after NFC normalization,
95
+ with no NUL character. Two tests in one file with the same title path, the
96
+ group titles and then the test's own, are a `COLLECTION_ERROR`.
97
+
98
+ ## Members
99
+
100
+ A skipped test never affects the exit code. With `test.only`, other tests
101
+ are reported as `filtered`, and a CI run fails with `ONLY_IN_CI`.
102
+
103
+ Call `test.skip(condition, reason)` inside a test for conditions you learn at
104
+ runtime, such as a disabled feature flag. When the condition is true, the call
105
+ throws: the body stops at that line, as Playwright's does, and the test is
106
+ reported `skipped` with `reason`, with no retry. Nothing after the call runs.
107
+ Steps before it stay in the report as they ran, but they do not decide the
108
+ status: a failed `expect.soft` before the call is dropped. Hooks and fixtures still run their teardown. When the condition is
109
+ a precondition, call it first, before any step.
110
+
111
+ A setup test cannot skip because other tests depend on its sessions; the
112
+ call fails with `INVALID_ARGUMENT`. Outside a test body, the conditional
113
+ form is `COLLECTION_ERROR`. Use the `skip` option when declaring the test.
114
+
115
+ ```ts
116
+ test('switches organizations', { session: 'admin' }, async ({ app, agent }) => {
117
+ const organizations = await listOrganizations();
118
+ test.skip(organizations.length < 2, 'the demo tenant has a single organization');
119
+ await app.open('/backend');
120
+ await agent.act(`switch to the organization "${organizations[1].name}"`);
121
+ });
122
+ ```
123
+ `test.setup` must be top-level, not inside `describe`. An `async`
124
+ `describe` body is a type error and a `COLLECTION_ERROR`.
125
+
126
+ ```ts
127
+ import { describe, test } from '@e2e-dev/web';
128
+ import { expect } from 'e2e';
129
+
130
+ test('app opens', async ({ app, browser }) => {
131
+ await app.open();
132
+ await expect(browser).toHaveURL('/');
133
+ });
134
+
135
+ test('checks out', { tags: ['billing'], retries: 2, timeout: 60_000 }, async ({ app }) => {
136
+ await app.open('/checkout');
137
+ });
138
+
139
+ describe('checkout', { tags: ['smoke'], serial: true }, () => {
140
+ test('adds an item', async ({ screen }) => {});
141
+ test('pays', async ({ screen }) => {});
142
+ });
143
+ ```
144
+
145
+ `test.setup` declares the sessions it saves, and must save each exactly once:
146
+
147
+ ```ts
148
+ import { credentials } from 'e2e';
149
+
150
+ test.setup('authenticate', { sessions: ['member'] }, async ({ app, screen, session }) => {
151
+ await app.open('/login');
152
+ await screen.getByLabel('Email').fill(credentials.user('member').username);
153
+ await screen.getByLabel('Password').fill(credentials.user('member').password);
154
+ await screen.getByRole('button', 'Sign in').tap();
155
+ await session.save('member');
156
+ });
157
+ ```
158
+
159
+ Session names must match `/^[A-Za-z0-9_.-]{1,128}$/` and be unique.
160
+ Not saving a declared session, or saving one twice, throws `SESSION_CONTRACT`
161
+ at runtime.
162
+
163
+ ```ts
164
+ export interface SetupSession {
165
+ /** Saves state under a setup-declared name. */
166
+ save(name: string): Promise<void>;
167
+ }
168
+ ```
169
+
170
+ ## Hooks
171
+
172
+ `beforeEach` hooks run outermost group first, then in declaration order
173
+ within a group. `afterEach` hooks run innermost first, in reverse declaration
174
+ order, after a failure or timeout too. `beforeAll` runs once per suite
175
+ instance on first entry into the scope; `afterAll` when the scope's last test
176
+ in the realm finishes. Hook order follows scope nesting, not position in the
177
+ file, so a hook declared after a test still applies to it.
178
+
179
+ The module is re-imported for every retry, serial group, and setup test, so
180
+ `beforeAll` runs again in each. A scope's `afterAll` runs as soon as its last
181
+ test in that realm is done. A realm that a failure discards runs `afterAll`
182
+ for every scope whose `beforeAll` started.
183
+
184
+ `beforeEach` and the body share the test timeout. Each `afterEach` hook gets
185
+ its own `cleanupTimeout` budget with working fixtures, so teardown can still
186
+ drive the app after the body timed out.
187
+
188
+ A throw inside `beforeAll` or `afterAll` is `HOOK_FAILED` and skips the
189
+ suite's tests with cause `hook-failed`. Inside a serial group it skips every
190
+ remaining member and fails the group without a retry.
191
+
192
+ ## Options
193
+
194
+ ```ts
195
+ export interface TestOptions {
196
+ /** Attempt deadline in milliseconds; defaults to `config.timeout`. Innermost wins. */
197
+ timeout?: number;
198
+ /** Retries, 0 through 10; defaults to `config.retries`. Inside a serial group the group's value applies. */
199
+ retries?: number;
200
+ /** Tags for `--tag`: distinct names, none blank, with no comma and no leading or trailing whitespace; every layer's tags are unioned. */
201
+ tags?: readonly string[];
202
+ /** Skips the test; a string is the reason. Any truthy layer skips. */
203
+ skip?: boolean | string;
204
+ /** Focuses the test locally. CI rejects it with `ONLY_IN_CI`. */
205
+ only?: boolean;
206
+ /** Platforms the test runs on; other targets skip it. Innermost wins. */
207
+ platforms?: readonly string[];
208
+ /** Capabilities the target must have; otherwise the test is skipped at selection. Innermost wins. */
209
+ requires?: readonly Capability[];
210
+ /** Session a setup test saved, restored before the body runs. */
211
+ session?: string;
212
+ /** Extra context for agent steps; layers are concatenated outermost first. */
213
+ agentContext?: string;
214
+ /**
215
+ * The configured agent (`agents.<name>`) this test or group runs with, in
216
+ * place of the run's agents. A list runs the test once per agent named, as
217
+ * one result each. Innermost wins; a call's own `agent` option wins over
218
+ * it. `--agent` narrows a list to the names both name and never overrides
219
+ * a pin the flag does not name.
220
+ */
221
+ agent?: string | readonly string[];
222
+ /**
223
+ * Which of the test's attempts record a trace, in place of the run's: the
224
+ * same modes as the config's `trace`. Innermost wins, over `--trace` too;
225
+ * inside a serial group the group's value applies, since the group
226
+ * records as one unit. A mode set here is required of the target's engine.
227
+ */
228
+ trace?: RecordingMode;
229
+ /**
230
+ * Which of the test's attempts record a video, in place of the run's:
231
+ * the same modes as the config's `video`. Innermost wins, over `--video`
232
+ * too; inside a serial group the group's value applies, since the group
233
+ * records as one unit. A mode set here is required of the target's engine.
234
+ */
235
+ video?: RecordingMode;
236
+ }
237
+
238
+ export interface DescribeOptions extends Omit<TestOptions, 'only'> {
239
+ /** Runs the group as one ordered retry unit with shared app state. Nesting one serial group in another is a `COLLECTION_ERROR`. */
240
+ serial?: boolean;
241
+ }
242
+
243
+ export interface SetupOptions extends Omit<TestOptions, 'session' | 'only' | 'skip' | 'agent'> {
244
+ /** Session names this setup test MUST save. */
245
+ sessions: readonly string[];
246
+ /** A setup test runs once per target, so it pins at most one agent. */
247
+ agent?: string;
248
+ }
249
+ ```
250
+
251
+ `timeout` must be a positive safe integer; `retries` must be an integer from
252
+ 0 through 10. Groups do not accept `only`. An option key the call does not
253
+ take (`{ timout: 1000 }`) is `COLLECTION_ERROR`, naming the nearest known key
254
+ when it is a likely typo and every key the call takes otherwise. `test.setup` requires `sessions`
255
+ to be a static, non-empty array of unique names.
256
+
257
+ Resolution order: test, nearest group, outer groups, config, built-in default.
258
+
259
+ `requires` names capabilities of the target's engine, the harness ones
260
+ (`observation`, `actions`, `location`, `pointer`, `keyboard`, `state`,
261
+ `artifacts`) and one per contributed fixture (`browser`, `device`; see
262
+ [the engine contract](/reference/engine#engine)), or the kind of app the
263
+ target declares: `browser` for an `app.url`, `native-app` for an
264
+ `app.bundleId` or `app.appPath`. A target that lacks one skips the test at
265
+ selection:
266
+
267
+ ```ts
268
+ test('onboarding asks for the camera', { requires: ['native-app'] }, async ({ app, agent }) => {
269
+ await app.open();
270
+ await agent.assert('The app asks for camera access');
271
+ });
272
+ ```
273
+
274
+ An `agent` name that `agents` does not define is a `COLLECTION_ERROR` at
275
+ selection, before any test runs.
276
+
277
+ `trace` and `video` take a [recording mode](/reference/config#recording):
278
+ `off`, `on`, `retain-on-failure`, `on-first-retry`, or `on-all-retries`. A
279
+ test's or group's own value wins over `--trace` / `--video`, the target's,
280
+ and the config's, so one test can record on a run that records nothing, or
281
+ opt out of one that records everything. A mode set here is required: when it
282
+ records on some attempt and the target's engine cannot record that kind, the
283
+ run fails with `UNSUPPORTED_ARTIFACT` before any test starts, naming the
284
+ test:
285
+
286
+ ```ts
287
+ test('checkout', { video: 'on' }, async ({ agent }) => {
288
+ await agent.act('Buy the first product in the catalog');
289
+ });
290
+ ```
291
+
292
+ `serial: true` makes the group one ordered retry unit with shared app state
293
+ and shared secret state: a secret fill withholds pixels for the rest of the
294
+ group's attempt. A retry starts fresh. Inside it, `retries`, `trace`,
295
+ `video`, `session`, `platforms`, `requires`, `skip`, `only`, and a nested
296
+ `serial` on a member are collection errors. Set them on the group. The group
297
+ records one trace and one video per group attempt, from the `trace` and
298
+ `video` on the serial `describe` or a group around it.
299
+
300
+ ## Fixtures
301
+
302
+ ```ts
303
+ export interface TestFixtures {
304
+ /** Agent steps. Acquiring the built-in agent without a configured model is `MODEL_UNAVAILABLE`. */
305
+ readonly agent: Agent;
306
+ /** App lifecycle: open, restart, clear state, back, screenshot. */
307
+ readonly app: App;
308
+ /** Semantic queries and the actions on their matches. */
309
+ readonly screen: Screen;
310
+ /** The target's platform label: `web`, `ios`, `android`, or an engine's own string. */
311
+ readonly platform: string;
312
+ }
313
+
314
+ export interface SetupFixtures extends TestFixtures {
315
+ /** Saves the sessions the setup test declared. */
316
+ readonly session: SetupSession;
317
+ }
318
+
319
+ export interface SuiteFixtures {
320
+ /** The target's platform label: `web`, `ios`, `android`, or an engine's own string. */
321
+ readonly platform: string;
322
+ }
323
+ ```
324
+
325
+ Everything else is a fixture the target's engine contributes, or one of
326
+ [your own](#your-own-fixtures). `@e2e-dev/web` contributes
327
+ [`browser`](/reference/web) and exports a `test` typed with it; a device
328
+ engine contributes `device`. Reaching for a fixture the engine does not
329
+ contribute fails with `UNSUPPORTED_CAPABILITY`; declare `requires: ['browser']`
330
+ to skip such tests at selection instead.
331
+
332
+ The fixtures above are lazy. Destructuring in the parameter list acquires
333
+ them before the body's first statement. Your own fixtures are not: every
334
+ fixture in the `extend` chain a test was registered through sets up for that
335
+ test, even when its parameters omit it.
336
+
337
+ ### Your own fixtures
338
+
339
+ `test.extend(fixtures)` defines resources set up before each test and cleaned
340
+ up afterward, including after a failure. In each fixture function, code before
341
+ `await use(value)` sets up the resource. The test receives `value`, and code
342
+ after `use` tears it down.
343
+
344
+ ```ts
345
+ import { test as base } from '@e2e-dev/web';
346
+
347
+ export const test = base.extend<{ workspace: Workspace }>({
348
+ workspace: async ({ browser }, use) => {
349
+ const workspace = await createWorkspace();
350
+ await signIn(browser, workspace);
351
+ await use(workspace); // the hooks and the body run here
352
+ await workspace.cleanup(); // teardown, after the body even when it failed
353
+ },
354
+ });
355
+
356
+ test('renames the workspace', async ({ workspace, screen }) => {
357
+ await screen.getByLabel('Name').fill(`${workspace.name} (renamed)`);
358
+ });
359
+ ```
360
+
361
+ ```ts
362
+ /**
363
+ * Defines one fixture: everything before `await use(value)` is its setup,
364
+ * everything after is its teardown. The body, its hooks, and later fixtures
365
+ * run while `use` is pending; it resolves once they are done, whether or not
366
+ * the body failed.
367
+ */
368
+ export type FixtureFn<Fixtures, Value> = (
369
+ fixtures: Fixtures,
370
+ use: (value: Value) => Promise<void>,
371
+ ) => Promise<void>;
372
+
373
+ /**
374
+ * One definition per fixture `test.extend()` adds. A definition sees the
375
+ * fixtures of the `test` it extends, not its siblings: a fixture that needs
376
+ * another one goes in a chained `extend()`. A name the base already has is a
377
+ * type error (and a `COLLECTION_ERROR` at runtime).
378
+ */
379
+ export type FixtureDefinitions<Base, Extra> = {
380
+ readonly [K in keyof Extra]: K extends keyof Base ? never : FixtureFn<Base, Extra[K]>;
381
+ };
382
+ ```
383
+
384
+ `extend` returns a new `test`; the one it was called on is unchanged. A
385
+ definition is typed against the fixtures of the `test` it extends, not its
386
+ siblings in the same call. A fixture that needs another goes in a chained
387
+ second `extend`: `base.extend({ a }).extend({ b })` gives `b` a fixture
388
+ object that already has `a`.
389
+
390
+ Per attempt:
391
+
392
+ 1. Each definition runs in declaration order until it calls `use`, whether
393
+ or not the test names it: a fixture that opens a page or changes app
394
+ state does so for every test of that `test`, so a file whose tests need
395
+ different setups registers them through different `extend` chains. Setup
396
+ counts against the test `timeout`; a failure is reported in phase
397
+ `beforeEach`, before any `beforeEach` hook has run.
398
+ 2. `beforeEach` hooks, the body, then `afterEach` hooks run with the same
399
+ fixture object. The fixture set is the test's own chain: a hook that reads
400
+ `workspace` around a test registered through `base` fails with
401
+ `UNSUPPORTED_CAPABILITY`.
402
+ 3. Teardowns run in reverse order after the last `afterEach`, whether or not
403
+ the body passed, each with its own `cleanupTimeout` budget. An error there
404
+ fails a passing attempt in phase `afterEach` and joins `secondaryErrors`
405
+ after a body failure.
406
+
407
+ What fails, and where:
408
+
409
+ | Mistake | Fails |
410
+ | --- | --- |
411
+ | 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 |
412
+ | A name the target's engine contributes (`browser` on a Playwright target) | The attempt, in `beforeEach`, `TEST_SETUP_FAILED` |
413
+ | A definition that returns without calling `use` | The attempt, in `beforeEach`, `TEST_SETUP_FAILED` |
414
+ | A definition that calls `use` twice | The attempt, at teardown, `TEST_SETUP_FAILED` |
415
+ | A definition that throws before `use` | The attempt, in `beforeEach`, with that error |
416
+ | A definition that throws after `use` | The attempt, in `afterEach`, with that error |
417
+
418
+ A body or hook that hits the test `timeout` is abandoned, not cancelled, so
419
+ teardown starts while it may still be running. A setup abandoned this way
420
+ that reaches `use` later gets an immediate resolve, and its teardown runs
421
+ detached from the run, so what it allocated is still released.
422
+
423
+ ## Function types
424
+
425
+ ```ts
426
+ /** A test body. */
427
+ export type TestFn<Fixtures = TestFixtures> = (fixtures: Fixtures) => void | Promise<void>;
428
+
429
+ /** A setup test body; `session.save` is the extra fixture. */
430
+ export type SetupFn<Fixtures = TestFixtures> = (
431
+ fixtures: Fixtures & SetupFixtures,
432
+ ) => void | Promise<void>;
433
+
434
+ /** A `beforeEach` or `afterEach` body. */
435
+ export type TestHookFn<Fixtures = TestFixtures> = (fixtures: Fixtures) => void | Promise<void>;
436
+
437
+ /** A `beforeAll` or `afterAll` body. */
438
+ export type SuiteHookFn = (fixtures: SuiteFixtures) => void | Promise<void>;
439
+
440
+ /** A `describe` body. An async body is a type error. */
441
+ export type SynchronousBody<Result> = Extract<Result, PromiseLike<unknown>> extends never
442
+ ? () => Result
443
+ : never;
444
+
445
+ /** Opaque handle of a registered test. */
446
+ export interface TestCase {
447
+ readonly [testCaseBrand]: true;
448
+ }
449
+ ```