e2e 0.17.0 → 0.18.0

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 (166) hide show
  1. package/README.md +10 -0
  2. package/dist/.build.json +1 -1
  3. package/dist/agent/ai-sdk.js +5 -4
  4. package/dist/agent/error.js +3 -3
  5. package/dist/agent/invocation.js +3 -4
  6. package/dist/agent/model/tool-choice.d.ts +9 -0
  7. package/dist/agent/model/tool-choice.js +14 -0
  8. package/dist/agent/observation-feed.js +3 -4
  9. package/dist/agent/phases.d.ts +7 -1
  10. package/dist/agent/phases.js +12 -3
  11. package/dist/agent/prompts.js +3 -0
  12. package/dist/agent/replay.js +18 -15
  13. package/dist/agent/step-cache.d.ts +2 -1
  14. package/dist/agent/step-cache.js +9 -5
  15. package/dist/agent/tool-loop.js +92 -5
  16. package/dist/cache/trace.js +6 -2
  17. package/dist/cli/bin.js +2 -2
  18. package/dist/cli/index.js +35 -24
  19. package/dist/cli/init/mcp-config.d.ts +2 -1
  20. package/dist/cli/init/mcp-config.js +8 -3
  21. package/dist/cli/init/sibling-versions.json +1 -1
  22. package/dist/cli/mcp.d.ts +3 -2
  23. package/dist/cli/mcp.js +52 -16
  24. package/dist/cli/run-output.d.ts +17 -0
  25. package/dist/cli/run-output.js +45 -0
  26. package/dist/collect/collect.js +8 -4
  27. package/dist/collect/registry.js +27 -16
  28. package/dist/config/compiled-files.d.ts +36 -0
  29. package/dist/config/compiled-files.js +50 -0
  30. package/dist/config/diagnose.js +21 -19
  31. package/dist/config/esm-hooks.d.ts +39 -44
  32. package/dist/config/esm-hooks.js +238 -63
  33. package/dist/config/load.d.ts +1 -7
  34. package/dist/config/load.js +23 -66
  35. package/dist/config/register.d.ts +8 -0
  36. package/dist/config/register.js +9 -0
  37. package/dist/config/specifiers.d.ts +19 -0
  38. package/dist/config/specifiers.js +33 -0
  39. package/dist/config/tsconfig.d.ts +56 -0
  40. package/dist/config/tsconfig.js +115 -0
  41. package/dist/config/typescript.d.ts +19 -0
  42. package/dist/config/typescript.js +253 -0
  43. package/dist/engine/contract.d.ts +1 -0
  44. package/dist/engine/index.d.ts +2 -0
  45. package/dist/engine/session.js +2 -2
  46. package/dist/expect/async.js +2 -1
  47. package/dist/expect/poll.js +2 -2
  48. package/dist/internal/errors.d.ts +10 -3
  49. package/dist/internal/errors.js +23 -8
  50. package/dist/internal/node-version.d.ts +16 -7
  51. package/dist/internal/node-version.js +50 -15
  52. package/dist/internal/package-manager.js +1 -1
  53. package/dist/internal/realm-slot.d.ts +5 -4
  54. package/dist/internal/redact.d.ts +2 -0
  55. package/dist/internal/redact.js +4 -0
  56. package/dist/internal/time.d.ts +21 -5
  57. package/dist/internal/time.js +35 -3
  58. package/dist/locator/engine.js +47 -11
  59. package/dist/locator/screen.js +31 -5
  60. package/dist/mcp/output.d.ts +59 -0
  61. package/dist/mcp/output.js +129 -0
  62. package/dist/mcp/server.d.ts +4 -0
  63. package/dist/mcp/server.js +9 -12
  64. package/dist/mcp/session.d.ts +8 -1
  65. package/dist/mcp/session.js +55 -14
  66. package/dist/mcp/tools.js +7 -5
  67. package/dist/report/list.d.ts +11 -0
  68. package/dist/report/list.js +23 -6
  69. package/dist/run/artifacts.d.ts +4 -4
  70. package/dist/run/artifacts.js +3 -2
  71. package/dist/run/execute.d.ts +25 -5
  72. package/dist/run/execute.js +44 -20
  73. package/dist/run/failure-evidence.js +6 -2
  74. package/dist/run/in-process.js +51 -10
  75. package/dist/run/process-output.d.ts +73 -0
  76. package/dist/run/process-output.js +130 -0
  77. package/dist/run/provision.d.ts +2 -0
  78. package/dist/run/provision.js +1 -0
  79. package/dist/run/records.d.ts +4 -4
  80. package/dist/run/retry.d.ts +7 -0
  81. package/dist/run/retry.js +27 -11
  82. package/dist/run/runner.d.ts +8 -0
  83. package/dist/run/runner.js +32 -9
  84. package/dist/run/scheduler.js +255 -19
  85. package/dist/run/secrecy.d.ts +22 -19
  86. package/dist/run/secrecy.js +22 -18
  87. package/dist/run/serial.d.ts +73 -2
  88. package/dist/run/serial.js +239 -58
  89. package/dist/run/standalone.js +1 -1
  90. package/dist/run/trace-redaction.d.ts +5 -6
  91. package/dist/run/trace-redaction.js +5 -6
  92. package/dist/run/worker/entry.js +35 -14
  93. package/dist/run/worker/handle.js +10 -5
  94. package/dist/run/worker/protocol.d.ts +83 -3
  95. package/dist/run/worker/session.js +28 -0
  96. package/dist/telemetry/environment.d.ts +2 -1
  97. package/dist/telemetry/environment.js +12 -2
  98. package/dist/telemetry/project.d.ts +5 -6
  99. package/dist/telemetry/project.js +6 -11
  100. package/dist/telemetry/store.d.ts +5 -5
  101. package/dist/telemetry/store.js +5 -7
  102. package/dist/telemetry/telemetry.d.ts +2 -3
  103. package/dist/telemetry/telemetry.js +2 -4
  104. package/dist/types.d.ts +6 -3
  105. package/docs/agent-steps.mdx +1 -0
  106. package/docs/agents.mdx +1 -0
  107. package/docs/api-testing.mdx +2 -1
  108. package/docs/authentication.mdx +3 -2
  109. package/docs/browser.mdx +7 -6
  110. package/docs/bug-bash.mdx +2 -1
  111. package/docs/cache.mdx +2 -1
  112. package/docs/ci.mdx +3 -2
  113. package/docs/coding-agents.mdx +2 -1
  114. package/docs/core-concepts.mdx +208 -0
  115. package/docs/debugging.mdx +19 -9
  116. package/docs/decision-models.mdx +142 -0
  117. package/docs/example-projects.mdx +32 -0
  118. package/docs/executors.mdx +3 -0
  119. package/docs/explore.mdx +1 -0
  120. package/docs/github.mdx +2 -1
  121. package/docs/index.mdx +9 -5
  122. package/docs/integrations/eas.mdx +1 -0
  123. package/docs/integrations/index.mdx +1 -0
  124. package/docs/integrations/kernel.mdx +2 -1
  125. package/docs/migrate/cypress.mdx +26 -3
  126. package/docs/migrate/detox.mdx +24 -1
  127. package/docs/migrate/maestro.mdx +28 -4
  128. package/docs/migrate/playwright.mdx +27 -4
  129. package/docs/migrate/selenium.mdx +27 -3
  130. package/docs/mobile-ci.mdx +6 -5
  131. package/docs/mobile.mdx +79 -8
  132. package/docs/models.mdx +7 -2
  133. package/docs/quickstart.mdx +26 -15
  134. package/docs/reference/agent.mdx +13 -3
  135. package/docs/reference/app.mdx +1 -0
  136. package/docs/reference/cli.mdx +11 -5
  137. package/docs/reference/config.mdx +42 -6
  138. package/docs/reference/engine.mdx +22 -10
  139. package/docs/reference/environment.mdx +2 -1
  140. package/docs/reference/errors.mdx +11 -8
  141. package/docs/reference/expect.mdx +2 -1
  142. package/docs/reference/mcp.mdx +21 -8
  143. package/docs/reference/mobile.mdx +1 -0
  144. package/docs/reference/reporters.mdx +1 -0
  145. package/docs/reference/screen.mdx +19 -9
  146. package/docs/reference/test.mdx +21 -1
  147. package/docs/reference/web.mdx +8 -2
  148. package/docs/security.mdx +33 -27
  149. package/docs/subscriptions.mdx +1 -0
  150. package/docs/telemetry.mdx +8 -5
  151. package/docs/tools.mdx +1 -0
  152. package/docs/{starting-your-app.mdx → web.mdx} +157 -11
  153. package/docs/writing-an-engine.mdx +2 -1
  154. package/package.json +11 -7
  155. package/skills/e2e/SKILL.md +3 -1
  156. package/skills/e2e/references/agent.md +11 -0
  157. package/skills/e2e/references/debugging.md +1 -1
  158. package/skills/e2e/references/mcp.md +8 -3
  159. package/skills/e2e/references/running.md +2 -1
  160. package/skills/e2e/references/setup.md +9 -1
  161. package/skills/e2e/references/writing-tests.md +1 -1
  162. package/docs/assertions.mdx +0 -94
  163. package/docs/goals.mdx +0 -176
  164. package/docs/locators.mdx +0 -64
  165. package/docs/physical-devices.mdx +0 -76
  166. package/docs/viewports.mdx +0 -146
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: E2EConfig
3
3
  description: Every configuration key, its default, its valid range, and the environment variables.
4
+ icon: "sliders"
4
5
  ---
5
6
 
6
7
  Export an object from `e2e.config.ts` and use `satisfies E2EConfig` to check
@@ -69,10 +70,45 @@ It throws when the file is missing; guard it with `existsSync` from
69
70
  `node:fs` when only some machines have one.
70
71
 
71
72
  Config and test files run as ESM whatever the nearest `package.json` `type`
72
- says, and so does the TypeScript they import: a helper by path, or a
73
- workspace package that exports its `.ts` source by name. An installed package
74
- imported by name keeps its own format. A config or test written with `require`
75
- or `module.exports` fails to load.
73
+ says, and so does every `.ts`, `.mts`, `.tsx`, and `.jsx` file they reach: a
74
+ helper by path, a package that exports its `.ts` source by name, or a `.ts`
75
+ file a CommonJS file requires. A `.cts` file is CommonJS, written with
76
+ `require`, `import x = require()`, and `export =`; its `import` statements are
77
+ not turned into `require` calls. A `.ts` file written with `require` or
78
+ `module.exports` fails to load: name it `.cts`. JavaScript keeps the format
79
+ its package declares.
80
+
81
+ e2e compiles TypeScript and JSX itself, and an import written in such a file
82
+ resolves the way TypeScript resolves it: `./helper.js` and `./helper` load
83
+ `helper.ts`, `./view.jsx` loads `view.tsx`, `./lib` loads the file
84
+ `lib/package.json` names as `main`, else `lib/index.ts`, a bare import that
85
+ `tsconfig.json` maps through `paths` or `baseUrl` loads the mapped file, and
86
+ a `#` import or package export that names a missing `./x.js`, `./x.jsx`, or
87
+ `./x` loads its `x.ts` or `x.tsx` source when imported (a `require()` of it fails as Node.js reports
88
+ it). An import written in a JavaScript file resolves
89
+ as Node.js resolves it.
90
+
91
+ The `tsconfig.json` is the nearest one above the file, or, when that one has
92
+ `references` and does not include the file itself (as Vite writes it), the
93
+ directly referenced one that does. e2e reads its `paths`, `baseUrl`, `jsx`, `jsxFactory`,
94
+ `jsxFragmentFactory`, `jsxImportSource`, `experimentalDecorators`,
95
+ `emitDecoratorMetadata`, `useDefineForClassFields` (and `target`, which sets
96
+ its default), and `verbatimModuleSyntax`, once per process and again for each
97
+ session `e2e mcp` opens. A `tsconfig.json` e2e cannot read, such as one that
98
+ `extends` a package that is not installed, is skipped with a warning.
99
+ TypeScript inside an installed package compiles without the project's
100
+ `tsconfig.json`.
101
+
102
+ e2e refuses, naming the file and line, two things it cannot compile:
103
+ auto-accessors (`accessor x = 1`) and decorators without
104
+ `experimentalDecorators`. Each file compiles on its own, without type
105
+ information, so a type imported without `import type` fails to load when the
106
+ import stays in the output (a decorated member's type under
107
+ `emitDecoratorMetadata`, a re-exported type); the error names the import to
108
+ change. A JSON file imported without `with { type: 'json' }` loads with its
109
+ parsed contents as the default export. Stack traces and failure locations
110
+ point at the TypeScript source, through another loader's source map too
111
+ (`NODE_OPTIONS=--import tsx`).
76
112
 
77
113
  ## Top level
78
114
 
@@ -895,7 +931,7 @@ See [Secrets that are not passwords](/authentication#secrets-that-are-not-passwo
895
931
  In `read-write` mode the replay cache records an `agent.act()` only after a
896
932
  later verification passes, such as a locator assertion or `agent.assert()`.
897
933
  `read-only` replays existing entries and writes none; `off` disables both.
898
- See [Caching agent steps](/cache).
934
+ See [Caching](/cache).
899
935
 
900
936
  ```ts
901
937
  /**
@@ -950,7 +986,7 @@ instead of a hand-off to the agent; see
950
986
  `e2e init` adds `.e2e/cache/` to `.gitignore`, so committing entries is
951
987
  opt-in. Remove that line to share replays with CI and teammates; CI stays
952
988
  `read-only` unless `cache: 'read-write'` is set. See
953
- [Caching agent steps](/cache#commit-the-replay-cache).
989
+ [Caching](/cache#commit-the-replay-cache).
954
990
 
955
991
  ## Run limits
956
992
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: e2e/engine
3
3
  description: Build an engine with observations, actions, lifecycle hooks, and fixtures.
4
+ icon: "gear"
4
5
  ---
5
6
 
6
7
  Import engine types and helpers from `e2e/engine`. `defineEngine` validates
@@ -709,7 +710,11 @@ export type PointerAction =
709
710
  readonly direction: ScrollDirection;
710
711
  readonly momentum?: Momentum;
711
712
  }
712
- | { readonly kind: 'swipeTo'; readonly target: ViewportPoint };
713
+ | {
714
+ readonly kind: 'swipeTo';
715
+ readonly target: ViewportPoint;
716
+ readonly durationMs?: number;
717
+ };
713
718
 
714
719
  /** Every pointer action kind; what an engine declares in `pointerActions`. */
715
720
  export type PointerActionKind = PointerAction['kind'];
@@ -923,6 +928,13 @@ export const RETRYABLE_ENGINE_ERROR_CODES: ReadonlySet<EngineErrorCode> = new Se
923
928
  | `INVALID_STATE` | `APP_NOT_OPEN` | no |
924
929
  | `ENGINE_FAILURE`, any non-`EngineError` | infrastructure `ENGINE_FAILURE` | no |
925
930
 
931
+ An `OPERATION_TIMEOUT` on a locate or read that started with under 100 ms of
932
+ a waiting call's deadline left, after one that completed, ends the wait
933
+ instead: an action's or `waitFor`'s `LOCATOR_NOT_FOUND`, or an assertion's
934
+ `ASSERTION_FAILED`, with the timeout as its cause. A negated assertion passes
935
+ there if its negation had held long enough by the time the read was cut off, as
936
+ `pollCondition` below describes.
937
+
926
938
  An `EngineError` a contributed fixture method throws is mapped by the table
927
939
  above, so a fixture over a session that ended reports `APP_NOT_OPEN` the way
928
940
  an observation would. A method can also fail a step with the runner's own
@@ -961,7 +973,7 @@ Use these helpers to keep behavior consistent across engines:
961
973
  | `matchesText(actual, pattern)`, `toTextPattern(match, { exact? })`, `describePattern(pattern)` | `TextPattern` matching for `locate` and text matchers; `toTextPattern` throws `INVALID_ARGUMENT` for a `match` that is neither a string nor a `RegExp` |
962
974
  | `resolveExpression(expression, nodes, { selector? })` | The reference `locate`. Resolves one `LocatorExpression` over a semantic tree in document order, with `text` and `label` answering the innermost match when a container echoes a descendant's text. An engine whose tree is the whole truth (a device) calls it; a browser engine reproduces it. A `selector` needs the hook, a `frame` is `FRAME_NOT_FOUND` |
963
975
  | `urlMatches(current, expected, baseHref, ignoreCase?)` | `toHaveURL`-style comparison, `ignoreCase` as on the locator matchers; an `expected` that is neither a string nor a `RegExp` throws `INVALID_ARGUMENT`, as in `toTextPattern` |
964
- | `pollCondition({ deadline, signal, negated, evaluate, onTimeout })`, `Deadline` | Assertion polling and the negation grace window |
976
+ | `pollCondition({ deadline, signal, negated, evaluate, onTimeout })`, `Deadline` | Assertion polling and the negation grace window. An `OPERATION_TIMEOUT` from an `evaluate` that started with under 100 ms of the deadline left, after one that completed, ends the poll: `onTimeout(cause)` builds the failure with it, and a negation passes if it had held long enough by the time that read was cut off. Bound each read `evaluate` makes by `deadline` (`context.operation(deadline.remaining())`): one on the action timeout can outlast the assertion's own |
965
977
  | `validateJsonValue(value, label, rules?)` | Checks a value is plain JSON: a top-level `undefined` passes; a non-finite number, a cycle, an object that is not a plain object or array, or a function, symbol, bigint, or `undefined` inside is `INVALID_ARGUMENT` naming `label`. A `Secret` is `POLICY_DENIED` unless `rules.onSecret` throws its own; `rules.maxDepth` caps the nesting |
966
978
  | `rejectUnknownKeys(label, value, keys, code?)` | Throws `INVALID_CONFIG` for the first own key of `value` outside `keys`, naming the nearest known key when one is a plausible typo (`web() has unknown key "viewprt"; did you mean "viewport"?`) and the known keys otherwise: the check the runner runs on its own config objects, for an engine factory's options. Pass `'INVALID_ARGUMENT'` as `code` for an object a test passes to a fixture method |
967
979
  | `rejectUnknownOptions(api, options, known, code?)` | `INVALID_ARGUMENT` (or `code`, `INVALID_LOCATOR` for a locator option) for an option key a fixture method or matcher does not take, so a JavaScript caller's unsupported option fails instead of running on the default. `options` must be a plain object or `undefined`; a class instance or array is refused, and non-enumerable own keys count as keys |
@@ -1151,16 +1163,16 @@ startedAt }` for `stopVideo` to hand back. A `stop` that throws or returns
1151
1163
  anything else is `ENGINE_FAILURE`, naming the provider and the lease. The
1152
1164
  target argument's type is exported as `ProviderRecordingTarget`.
1153
1165
 
1154
- After a secret fill, the runner redacts every text entry in each trace
1155
- archive and drops its screencast frames: every `screencast/` entry, each
1156
- `screencast-frame` record in a `.trace` entry, and any image a record names.
1157
- After `resolveSecret` returns, it redacts the trace the same way and keeps
1158
- the frames: a trace records the options the engine opened the attempt with,
1159
- and an engine-held secret is protected as text only.
1166
+ When the session knows a secret value, the runner redacts every text entry in
1167
+ each trace archive, so a value a test spelled into a URL or the options the
1168
+ engine opened the attempt with (`resolveSecret`) never stay in it. After a
1169
+ secret fill it also drops the screencast frames: every `screencast/` entry,
1170
+ each `screencast-frame` record in a `.trace` entry, and any image a record
1171
+ names.
1160
1172
  If redaction fails, it deletes the archives and records
1161
1173
  `TRACE_WITHHELD`. Videos are unmasked and carry `redaction: 'incomplete'`.
1162
- A `download` an engine attaches is the app's bytes: after a secret fill or
1163
- once `resolveSecret` returns, the runner rewrites a text one (`.txt`,
1174
+ A `download` an engine attaches is the app's bytes: when the session knows
1175
+ a secret value, the runner rewrites a text one (`.txt`,
1164
1176
  `.csv`, `.json`, `.html`) through the ledger and labels it `complete`; every
1165
1177
  other download keeps its bytes and carries `redaction: 'incomplete'`.
1166
1178
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Environment variables
3
3
  description: Every variable the runner and the first-party engines read, and what each one does.
4
+ icon: "dollar-sign"
4
5
  ---
5
6
 
6
7
  These variables configure the runner and first-party packages. In the tables,
@@ -57,7 +58,6 @@ short would take ordinary text with it.
57
58
  | --- | --- | --- |
58
59
  | `NO_COLOR` | set | Plain CLI output. Colors are otherwise on for a TTY or in CI and off when piped |
59
60
  | `FORCE_COLOR` | set | Colored CLI output even when piped |
60
- | `NODE_OPTIONS` | value | Inspected for a TypeScript `--import` loader, which decides how the runner registers its own module hooks for `.ts` config and test files |
61
61
  | `TERM` | value | `dumb` draws the `e2e init` wordmark at rest, without animation, as in CI |
62
62
  | `GITHUB_SHA`, `GITHUB_HEAD_REF`, `GITHUB_REF_NAME` | value | The commit and branch the report records under `run.vcs` when `git` cannot read the project root. `GITHUB_HEAD_REF` wins over `GITHUB_REF_NAME` |
63
63
 
@@ -107,6 +107,7 @@ What is sent and how to read it is on the [Telemetry](/telemetry) page.
107
107
  | Variable | Reading | Effect |
108
108
  | --- | --- | --- |
109
109
  | `PLAYWRIGHT_BROWSERS_PATH` | value | Playwright's browser cache, honored by the engine's `prepare` hook |
110
+ | `PLAYWRIGHT_SKIP_BROWSER_GC` | value | Defaults to `1` for the engine's browser install, so it keeps other browsers in the cache. Set `0` to let the install remove them |
110
111
  | `KERNEL_API_KEY` | value | The API key `kernel()` from `@e2e-dev/kernel` creates browsers with; unset, the browser lease fails. See [Kernel](/integrations/kernel) |
111
112
  | `EXPO_TOKEN` | value | The Expo access token `easSimulators()` from `@e2e-dev/eas` starts and stops simulator sessions with; unset, it uses the eas-cli login in `.expo/state.json` under `HOME` (`USERPROFILE` on Windows), and with neither the device lease fails. See [EAS Simulators](/integrations/eas) |
112
113
  | `HOME`, `USERPROFILE` | value | The home `easSimulators()` finds the eas-cli login under when `EXPO_TOKEN` is unset: `USERPROFILE` on Windows, `HOME` elsewhere, the OS user's home when unset |
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Errors
3
3
  description: Every code you can see, its class, and its exit code.
4
+ icon: "triangle-exclamation"
4
5
  ---
5
6
 
6
7
  Every error carries a class. The class determines the exit code; the code
@@ -56,8 +57,8 @@ try {
56
57
  | `TEST_SETUP_FAILED` | Test prerequisites outside the product failed to arrange (blocked: test_setup). Also a `test.extend()` fixture that broke its contract: returned without calling `use()`, called it twice, or redefined a fixture the target's engine contributes | configuration | 2 |
57
58
  | `AUTOMATION_UNSUPPORTED` | The step needs an interaction the available toolset cannot perform, e.g. drag-and-drop (blocked: automation) | test | 1 |
58
59
  | `MODEL_UNAVAILABLE` | No model configured: the built-in agent's `agents.<name>.model` holds no AI SDK model instance; the message names the agent that was selected. Checked once per run when the `agent` fixture is first acquired; recorded as one run-level error that stops the run. `e2e explore` checks before anything starts, a custom executor's entry included, since explore runs the built-in agent. A custom executor without a model is not checked: its `act` and `assert` steps run, and `waitFor` or `extract` fails with this code on its first call. Evicts no [recording](/cache). | configuration | 2 |
59
- | `POLICY_DENIED` | A denied destination (a navigation scheme other than `http:` or `https:`, `about:blank` aside, a malformed URL), a forbidden fill (an undeclared secret, a non-editable sink, a password into a field that is not a password field), or pixel evidence after a secret fill (`vision: 'only'` with pixels withheld). Also reading a value, text, or attribute from a secure field, the getters and the `toHaveValue`, `toHaveText`, `toContainText`, and `toHaveAttribute` matchers alike. An unknown tool or an undeclared field is refused to the model as the call's result and is not this code | configuration | 2 |
60
- | `APP_UNREACHABLE` | The app URL could not be reached, or an app command exited early or exceeded `startupTimeout`. The startup error includes recent log output with `command.env` values redacted, and suggests adding a `log` if none is configured. Under the agent, also any engine fault with no more specific code: on a device, an iOS automation runner that is busy or wedged reports here with a message that names the runner, the session and device, and the recovery; see [Testing iOS and Android](/mobile#troubleshooting) | infrastructure | 3 |
60
+ | `POLICY_DENIED` | A denied destination (a navigation scheme other than `http:` or `https:`, `about:blank` aside, a malformed URL), a forbidden fill (an undeclared secret, a non-editable sink, a password into a field that is not a password field), or pixel evidence after a secret fill (`vision: 'only'` with pixels withheld, or an `act` or `vision: true` step whose semantic capture timed out before the step did). Also reading a value, text, or attribute from a secure field, the getters and the `toHaveValue`, `toHaveText`, `toContainText`, and `toHaveAttribute` matchers alike. An unknown tool or an undeclared field is refused to the model as the call's result and is not this code | configuration | 2 |
61
+ | `APP_UNREACHABLE` | The app URL could not be reached, or an app command exited early or exceeded `startupTimeout`. The startup error includes recent log output with `command.env` values redacted, and suggests adding a `log` if none is configured. Under the agent, also any engine fault with no more specific code: on a device, an iOS automation runner that is busy or wedged reports here with a message that names the runner, the session and device, and the recovery; see [Mobile](/mobile#troubleshooting) | infrastructure | 3 |
61
62
  | `APP_ALREADY_RUNNING` | `readyUrl` already answered before the target's `app.command` was spawned and `reuseExisting` was not in effect; stop that process or set `reuseExisting: true` locally | infrastructure | 3 |
62
63
  | `MODEL_PROVIDER_FAILED` | Provider transport failure (network error, 5xx, rate limit, or a request with no response within 120 s) after the adapter's transport retries (up to 5 per model call, honoring `retry-after`, bounded by the remaining step timeout). A request that stalls is aborted at 120 s and sent again, and an `agent.act` step's `--debug` transcript notes it. The bound covers the whole request, so a model that needs longer than 120 s for one answer fails here too. Evicts no [recording](/cache) | infrastructure | 3 |
63
64
  | `CANCELLED` | The runner cancelled the operation, without a process signal: an `e2e mcp` `open_session` whose request the client cancelled or whose connection closed, or that the server's shutdown cut short, after stopping what it had started | infrastructure | 3 |
@@ -83,8 +84,8 @@ These appear in terminal output and in the report as plain string codes.
83
84
  | --- | --- | --- | ---: |
84
85
  | `CONFIG_NOT_FOUND` | `--config <path>` names a file that does not exist, or no `e2e.config.ts` or `e2e.config.mts` exists from the working directory up to the repository root | configuration | 2 |
85
86
  | `CONFIG_AMBIGUOUS` | Both `e2e.config.ts` and `e2e.config.mts` in one directory | configuration | 2 |
86
- | `CONFIG_LOAD_FAILED` | The config module failed to import: a package that is declared but not installed, an undeclared package, a wrong subpath, a removed export, or any other throw while it evaluates. The message names the fix. A config error raised while it evaluates keeps its own code: an option a factory the config calls (`web()`, `mobile()`, `defineEngine()`) refuses, or a `secrets.get()` handle turned into a string, is `INVALID_CONFIG` | configuration | 2 |
87
- | `INVALID_CONFIG` | A factory the config module calls (`web()`, `mobile()`, `defineEngine()`) refused its options, with the factory's own message; or an unknown key, out-of-range value, invalid enum, missing `targets`, a target key outside `{ name, platform, engine, app, trace, video }`, a target `app` key that is unknown or malformed, `app` on a target without an engine, a `web()` target without `app.url` or with a device field, a `mobile()` target without `app.bundleId` or `app.appPath`, with `app.url` (not supported yet), a link in `bundleId`, or an unknown permission name or state (the message names the nearest name), an unknown key in a `web()` or `mobile()` option, including `web({ connect })` and `web({ basicAuth })` (the message names the nearest one), a `trace` or `video` that is not a recording mode, the removed artifact kinds list (`artifacts: [...]` or `artifacts.kinds`) and `artifacts.trace` block (the message names the `trace` mode they meant), the removed `artifacts.video` block or `video` artifact kind (the message names the `video` option), the removed `specVersion` key, a `secrets.get()` in an engine option that names no `secrets` entry (a credential's password is not one), a `secrets.get()` reference turned into a string while the config evaluates (a template literal, `String()`) or given where a string is needed (an `app.command` `args` entry or `env` value; only an engine option that declares secrets takes one), an `app.command` `args` entry or `env` value that is not a string (an `undefined` `env` value is dropped instead), an unknown key in `app.command` (the message names the nearest one), an `engine` that is not a `defineEngine` handle, `json` reporter combined with `list`, duplicate or malformed target name, a secret or credential password shorter than 6 characters (code points), two credentials or two secrets whose names map to one `E2E_USER_*` or `E2E_SECRET_*` override variable (`api-key` and `api_key`), oversized `agents.<name>.context`, an `expect.poll` `timeout` or `interval` that is not a finite number of milliseconds, a `tests` value that is not a glob or a list of globs, a `tests` list of only `!` exclusions, the removed top-level `limits` key, an agents entry that is a bare `StepExecutor` (the message names `{ executor }`) or a model instance (the message names `{ model }`), an unknown agent key (the message names the nearest one), the removed agent keys `timeout` and `maxTurns` (the message names `judgmentTimeout` and `maxModelCalls`), `system` or `tools` beside `executor`, a `tools` entry `defineTool` did not build or that takes a built-in tool name (a grammar verb, `complete_step`, `locate`, `start_recording`, `stop_recording`, or `report_finding`), `createToolLoopExecutor({ maxTurns })`, `reuseExisting` on an app whose `app.url` asks for a free port, or a port-0 `app.url` with no `app.command` to serve it | configuration | 2 |
87
+ | `CONFIG_LOAD_FAILED` | The config module failed to import: a package that is declared but not installed, an undeclared package, a wrong subpath, a removed export, or any other throw while it evaluates. The message names the fix. A config error raised while it evaluates keeps its own code: an option a factory the config calls (`web()`, `mobile()`, `defineEngine()`, `decisionExecutor()`) refuses, or a `secrets.get()` handle turned into a string, is `INVALID_CONFIG` | configuration | 2 |
88
+ | `INVALID_CONFIG` | A factory the config module calls (`web()`, `mobile()`, `defineEngine()`, `decisionExecutor()`) refused its options, with the factory's own message; or an unknown key, out-of-range value, invalid enum, missing `targets`, a target key outside `{ name, platform, engine, app, trace, video }`, a target `app` key that is unknown or malformed, `app` on a target without an engine, a `web()` target without `app.url` or with a device field, a `mobile()` target without `app.bundleId` or `app.appPath`, with `app.url` (not supported yet), a link in `bundleId`, or an unknown permission name or state (the message names the nearest name), an unknown key in a `web()` or `mobile()` option, including `web({ connect })` and `web({ basicAuth })` (the message names the nearest one), a `trace` or `video` that is not a recording mode, the removed artifact kinds list (`artifacts: [...]` or `artifacts.kinds`) and `artifacts.trace` block (the message names the `trace` mode they meant), the removed `artifacts.video` block or `video` artifact kind (the message names the `video` option), the removed `specVersion` key, a `secrets.get()` in an engine option that names no `secrets` entry (a credential's password is not one), a `secrets.get()` reference turned into a string while the config evaluates (a template literal, `String()`) or given where a string is needed (an `app.command` `args` entry or `env` value; only an engine option that declares secrets takes one), an `app.command` `args` entry or `env` value that is not a string (an `undefined` `env` value is dropped instead), an unknown key in `app.command` (the message names the nearest one), an `engine` that is not a `defineEngine` handle, `json` reporter combined with `list`, duplicate or malformed target name, a secret or credential password shorter than 6 characters (code points), two credentials or two secrets whose names map to one `E2E_USER_*` or `E2E_SECRET_*` override variable (`api-key` and `api_key`), oversized `agents.<name>.context`, an `expect.poll` `timeout` or `interval` that is not a finite number of milliseconds, a `tests` value that is not a glob or a list of globs, a `tests` list of only `!` exclusions, the removed top-level `limits` key, an agents entry that is a bare `StepExecutor` (the message names `{ executor }`) or a model instance (the message names `{ model }`), an unknown agent key (the message names the nearest one), the removed agent keys `timeout` and `maxTurns` (the message names `judgmentTimeout` and `maxModelCalls`), `system` or `tools` beside `executor`, a `tools` entry `defineTool` did not build or that takes a built-in tool name (a grammar verb, `complete_step`, `locate`, `start_recording`, `stop_recording`, or `report_finding`), `createToolLoopExecutor({ maxTurns })`, `reuseExisting` on an app whose `app.url` asks for a free port, or a port-0 `app.url` with no `app.command` to serve it | configuration | 2 |
88
89
  | `CONFIG_NOT_DETERMINISTIC` | A worker re-resolved the config module and got a different digest than the runner | configuration | 2 |
89
90
  | `APP_URL_REQUIRED` | A navigation or relative URL on a target that declares no `app.url`, or an `app.command` with no `app.url` or `app.readyUrl` to poll | configuration | 2 |
90
91
  | `INVALID_APP_URL` | Base URL with userinfo, a query, a fragment, a non-HTTP scheme, plain HTTP on a non-loopback host, port 0 on a host other than a literal loopback address (`127.0.0.1`, `[::1]`), a loopback address the run could not bind a free port on, or unparseable | configuration | 2 |
@@ -104,24 +105,26 @@ These appear in terminal output and in the report as plain string codes.
104
105
  | `NO_LAST_RUN` | `--last-failed` found no `report.json` where the run writes it, or a file there that is not a `report-1` document. Run once without the flag first. | configuration | 2 |
105
106
  | `ONLY_IN_CI` | A focused test reached a CI run | configuration | 2 |
106
107
  | `COLLECTION_ERROR` | Registration outside collection, an invalid title, a non-function body, an async `describe` body, nested serial groups, an option forbidden inside a serial group, a setup test inside `describe`, an invalid session name, a malformed test option such as `tags` that is not a list of distinct names, a test or `describe` option key the call does not take (the message names the nearest one), a test file that fails to import (in a run with no positionals, or a file the positionals selected), a session that a selected test consumes or `e2e explore --session` names but no collected setup test produces (the message lists the declared sessions), a consumed session on a target whose engine has no `state` capability to save it, or a positional path outside the project root | configuration | 2 |
107
- | `POLICY_DENIED` | A denied destination (a navigation scheme other than `http:` or `https:`, `about:blank` aside, a malformed URL), a forbidden fill (an undeclared secret, a non-editable sink, a mismatched purpose, a `Secret` reaching a JSON sink), or tainted pixels (`app.screenshot()` after a secret fill, `vision: 'only'` with pixels withheld); see the [security model](/security). An unknown tool or an undeclared field goes back to the model as the call's result, never this code | configuration | 2 |
108
+ | `POLICY_DENIED` | A denied destination (a navigation scheme other than `http:` or `https:`, `about:blank` aside, a malformed URL), a forbidden fill (an undeclared secret, a non-editable sink, a mismatched purpose, a `Secret` reaching a JSON sink), or tainted pixels (`app.screenshot()` after a secret fill, `vision: 'only'` with pixels withheld, an `act` or `vision: true` step whose semantic capture timed out before the step did); see the [security model](/security). An unknown tool or an undeclared field goes back to the model as the call's result, never this code | configuration | 2 |
108
109
  | `SESSION_MISMATCH` | A stored session does not match the current run, target, engine, platform, or app identity | configuration | 2 |
109
110
  | `SESSION_EXPIRED` | A stored session is past its expiry at restore time (configuration, exit 2), or a setup test saved state whose expiry is not after its creation (test, exit 1) | configuration or test | 2 or 1 |
110
111
  | `SESSION_INVALID` | A stored session failed authentication. Tampered or corrupt. | configuration | 2 |
111
112
  | `SESSION_UNAVAILABLE` | A test requires a session no selected setup test produced | configuration | 2 |
112
113
  | `SESSION_CONTRACT` | A setup test saved a session twice, saved an undeclared name, or finished without saving a declared one | test | 1 |
113
- | `TEST_TIMEOUT` | The attempt exceeded the resolved test `timeout`. The body is not stopped: the harness operation it is in is cancelled and its fixtures die with the attempt, so it cannot reach the next test | test | 1 |
114
+ | `TEST_TIMEOUT` | The attempt exceeded the resolved test `timeout`. The body is not stopped: the harness operation it is in is cancelled and its fixtures die with the attempt, so it cannot reach the next test. A test that blocks the event loop gets its worker killed once `cleanupTimeout` (at least 5 s) passes after the timeout; see [Hooks](/reference/test#hooks) | test | 1 |
114
115
  | `STEP_NOT_AWAITED` | The test body settled while a step it started was still running: a step call without `await`. The step is failed at the line of the call and cancelled; the attempt fails with this code when the body returned, or keeps the body's own error and records this one under `secondaryErrors` when the body threw. An `expect.poll` matcher left running when the body, an `afterEach` hook, or a fixture teardown returned fails that phase the same way at the line of the call, and is cancelled; one left by a `beforeAll` or `afterAll` hook fails the hook with `HOOK_FAILED` | test | 1 |
115
116
  | `UNHANDLED_REJECTION` | A promise rejected with no test running. An unhandled rejection is charged to the test running when it surfaces, since Node cannot say which test created the promise; between tests it is this run error, naming the last test that finished in the worker as the likely source. Before any test has finished in the worker it ends the worker instead: the rejection is the run error and the test on its way fails with `WORKER_CRASH`. The body that left it keeps running like one past `TEST_TIMEOUT` | test | 1 |
116
117
  | `HOOK_FAILED` | `beforeAll` or `afterAll` threw. Skips the suite's tests. | test | 1 |
117
118
  | `LAUNCH_TIMEOUT` | Engine init or attempt start exceeded `config.launchTimeout` | infrastructure | 3 |
118
119
  | `CLEANUP_TIMEOUT` | Engine attempt end exceeded `config.cleanupTimeout`, or a route or dialog handler the engine ran for the test was still running when the attempt settled | infrastructure | 3 |
119
- | `TRACE_WITHHELD` | A trace from an attempt a secret reached (filled, or held by the engine) could not be rewritten, so it was deleted. Recorded in the attempt's `secondaryErrors`; never fails the attempt. | infrastructure | 3 |
120
+ | `TRACE_WITHHELD` | A trace from a session that knows a secret value could not be rewritten, so it was deleted, or the engine returned an archive path outside the attempt's artifact directory, so nothing was registered. Recorded in the attempt's `secondaryErrors`; never fails the attempt. | infrastructure | 3 |
120
121
  | `BROWSER_INSTALL_FAILED` | Browser provisioning failed | infrastructure | 3 |
121
122
  | `WORKER_INIT_FAILED` | A worker process never became ready | infrastructure | 3 |
122
- | `WORKER_EXIT` | A worker exited unexpectedly while running a file: the test in flight fails with `WORKER_CRASH` and the file's other tests are skipped. An unhandled rejection is charged to the test running when it surfaces, or recorded as `UNHANDLED_REJECTION` between tests; before any test has finished there it ends the worker, and the rejection itself is the run error, not this one | infrastructure | 3 |
123
+ | `WORKER_EXIT` | A worker exited unexpectedly while running a file: the test in flight fails with `WORKER_CRASH` and the file's other tests are skipped. Attempts that finished before the exit stay in the report, a retry's earlier attempts and a serial group's finished members included, and the crash is recorded as the attempt after them, or on the last attempt when it came after it (an `afterAll`). An unhandled rejection is charged to the test running when it surfaces, or recorded as `UNHANDLED_REJECTION` between tests; before any test has finished there it ends the worker, and the rejection itself is the run error, not this one | infrastructure | 3 |
123
124
  | `WORKER_CRASH` | A worker exited during a specific test | infrastructure | 3 |
124
125
  | `WORKER_ENTRY_MISSING` | The worker entry module could not be resolved. Packaging fault. | infrastructure | 3 |
126
+ | `NODE_UNSUPPORTED` | The runtime cannot run e2e's TypeScript loader: a Node.js outside `^22.22.3 \|\| >=24.8.0`, Bun (no `module.registerHooks`), or Deno. The CLI checks this before it starts and exits 2 with the same sentence; the loader checks it again where it registers its hooks, for a host that runs e2e's runner without the CLI | infrastructure | 3 |
127
+ | `TYPESCRIPT_COMPILER_UNAVAILABLE` | oxc, which compiles e2e's TypeScript, could not be loaded; the message gives the cause. Most often its native bindings for this platform (`@oxc-transform/binding-<platform>-<arch>`, `@oxc-parser/binding-<platform>-<arch>`) are not installed. npm leaves it out with `--omit=optional`, or when `package-lock.json` was written on another platform (npm/cli#4828): install again without `--omit=optional`, or delete `package-lock.json` and `node_modules` and run `npm install` | infrastructure | 3 |
125
128
  | `ENGINE_FAILURE` | Any engine fault with no more specific mapping | infrastructure | 3 |
126
129
  | `WORKER_PROTOCOL` | A worker reported a unit other than the one it was running, or started an attempt while another was still running | infrastructure | 3 |
127
130
  | `SESSION_KEY_INVALID` | A worker received a session key that is not 32 bytes. Packaging or IPC fault. | infrastructure | 3 |
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: expect
3
3
  description: "Locator assertions, fixture assertions, and plain value matchers."
4
+ icon: "circle-check"
4
5
  ---
5
6
 
6
7
  Use `expect(locator)` to wait for an app condition and `expect(value)` to
@@ -370,7 +371,7 @@ The unit-runner matchers (`toThrow`, `toBeInstanceOf`, `toBeNaN`,
370
371
  `toStrictEqual`, `resolves`, `rejects`, `expect.extend`) are not part of
371
372
  `expect`. An end-to-end test checks what the app shows and what its API
372
373
  returns; the value matchers cover that, see
373
- [Testing APIs](/api-testing). Code-level
374
+ [API](/api-testing). Code-level
374
375
  checks belong in the unit runner.
375
376
 
376
377
  Pass a second argument to describe the check in its failure message:
@@ -1,19 +1,20 @@
1
1
  ---
2
2
  title: MCP server (e2e mcp)
3
3
  description: Let a coding agent inspect and interact with your app over MCP.
4
+ icon: "plug"
4
5
  ---
5
6
 
6
7
  <CodeGroup>
7
8
  ```bash npm
8
- npx e2e mcp [--config <path>] [--target <name>] [--headless] [--max-sessions <n>]
9
+ npx e2e mcp [--config <path>] [--target <name>] [--headed] [--max-sessions <n>]
9
10
  ```
10
11
 
11
12
  ```bash pnpm
12
- pnpm exec e2e mcp [--config <path>] [--target <name>] [--headless] [--max-sessions <n>]
13
+ pnpm exec e2e mcp [--config <path>] [--target <name>] [--headed] [--max-sessions <n>]
13
14
  ```
14
15
 
15
16
  ```bash bun
16
- bunx e2e mcp [--config <path>] [--target <name>] [--headless] [--max-sessions <n>]
17
+ bunx e2e mcp [--config <path>] [--target <name>] [--headed] [--max-sessions <n>]
17
18
  ```
18
19
  </CodeGroup>
19
20
 
@@ -64,21 +65,32 @@ Start the server in the project directory. Config discovery works like
64
65
  | --- | --- |
65
66
  | `--config <path>` | The config every session loads unless `open_session` names one. Configs are reloaded on every `open_session`, with every file they import by path. |
66
67
  | `--target <name>` | The target every session opens on. Otherwise `open_session` names one, or the only target is used; several targets and no name is `TARGET_REQUIRED`. |
67
- | `--headless` | Hide the browser or simulator. Sessions are headed by default outside CI. |
68
+ | `--headed` | Show the browser or simulator, when the engine supports it, in every session whose `open_session` does not set `headed`. Sessions are headless by default, as in `e2e run`. |
68
69
  | `--max-sessions <n>` | How many sessions may be open at once, 1 through 16; default 4. Each session is its own browser or device. |
69
70
 
70
71
  Operator output goes to stderr, prefixed `e2e mcp:`. Stdout carries only the
71
- protocol; anything a config module writes to stdout is redirected.
72
+ protocol. What your code writes through `console`, `process.stdout`, or
73
+ `process.stderr` (the config's top-level code, project tools) goes to stderr
74
+ with secret values redacted. Output printed while a config loads appears once
75
+ it has loaded; if the load fails, that output is withheld, since the config's
76
+ secrets are unknown. An error nobody caught is logged redacted and shuts the
77
+ server down. A child process that inherits the server's stdio writes past
78
+ this.
72
79
 
73
80
  ## Tools
74
81
 
75
82
  | Tool | Arguments | Result |
76
83
  | --- | --- | --- |
77
- | `open_session` | `target` (optional), `config` (optional path) | Loads the config, starts the target's app command, boots the engine, starts one attempt, opens the app URL when the engine can navigate. Returns the session id, the catalog, and the first observation. |
84
+ | `open_session` | `target` (optional), `config` (optional path), `headed` (optional boolean) | Loads the config, starts the target's app command, boots the engine, starts one attempt, opens the app URL when the engine can navigate. Returns the session id, the catalog, and the first observation. `headed: true` shows this session's browser or simulator, `false` hides it; without it the session follows `--headed`. |
78
85
  | `tools` | `tool` (optional), `session` (required while several are open) | The catalog, one line per tool. With `tool`, its full description and argument schema. |
79
86
  | `call` | `tool`, `args` (optional), `session` (required while several are open) | Runs one catalog tool. Arguments are validated against the tool's closed schema first: a wrong type or an argument the tool does not declare is `INVALID_ARGUMENT`, and nothing runs. An action that fails is an error result that still carries the screen it re-observed, and leads with the tool, its target, and the code: `navigate file:///etc/passwd failed: POLICY_DENIED: forbidden URL scheme: file:`. |
80
87
  | `close_session` | `session` (required while several are open) | Saves a recording still running, ends the attempt, disposes the engine, stops the app processes the session started once no other session uses them. |
81
88
 
89
+ Project tools honor their AI SDK `toModelOutput` renderer, including when
90
+ `execute` returns a string. Rendered `error-text` and `error-json` outputs
91
+ are MCP error results (`isError: true`) so the coding agent can distinguish
92
+ a failed tool call from a successful one.
93
+
82
94
  ## Several sessions
83
95
 
84
96
  Several sessions can be open at once, up to `--max-sessions`, each on its
@@ -183,8 +195,9 @@ Sessions enforce what tests enforce. Node ids are valid only against the
183
195
  newest observation. A secret fills only through `type_secret`; once one has
184
196
  been filled, `screenshot` and the point tools (`tap_at`, `hover_at`,
185
197
  `type_at`, `press_at`, `select_at`) stay in the catalog but answer
186
- `PIXEL_TAINTED` for the rest of the session. Every text a `call` returns, a node `locate` found,
187
- an action's result, or an error, passes the attempt's secret ledger. Nothing a
198
+ `PIXEL_TAINTED` for the rest of the session. Every text a tool returns, a node `locate` found,
199
+ an action's result, an `open_session` or `close_session` failure, or a cleanup line, has the
200
+ config's secret values redacted once the config has loaded, as do the server's own log lines. Nothing a
188
201
  session does is recorded as a test result or into the replay cache; its
189
202
  artifacts land under `<output>/artifacts/<target>/sessions/`, its videos under
190
203
  `<output>/videos/`. A later `e2e run` clears `<output>/artifacts`; the
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Mobile
3
3
  description: Mobile engine options and the device fixture for iOS and Android.
4
+ icon: "mobile-screen"
4
5
  ---
5
6
 
6
7
  `@e2e-dev/mobile` drives iOS simulators, Android emulators, and connected
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Reporters
3
3
  description: Receive run events and publish results with a custom reporter.
4
+ icon: "file-lines"
4
5
  ---
5
6
 
6
7
  Add reporter objects to `reporters` in `e2e.config.ts`, alongside the
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: screen and Locator
3
3
  description: Queries, refinement, actions, and reads.
4
+ icon: "display"
4
5
  ---
5
6
 
6
7
  A `Locator` stores a query and resolves it when an action, read, or assertion
@@ -178,14 +179,15 @@ not the finger: `'down'` reveals what is below. `momentum` is its fling
178
179
  strength, `'none'` by default: `'none'` a plain drag of half the surface,
179
180
  `'slow'` a short fling, `'fast'` a long one. How far each carries is the
180
181
  engine's.
181
- `{ from, to }` is a swipe along a path between two viewport points: a touch
182
- swipe on a device, a pointer drag on a document platform, which is what
183
- reveals a row action, dismisses a sheet, or drags a card between columns.
184
- Uses `config.actionTimeout`. Throws
185
- `ACTION_FAILED` when the gesture is rejected, `INVALID_ARGUMENT` for a mix
186
- of the two shapes or a coordinate off the plane, and `UNSUPPORTED_CAPABILITY`
187
- when the engine does not list `swipe` in its actions (a direction) or
188
- `swipeTo` in its pointer actions (a path).
182
+ `{ from, to, duration? }` is a swipe along a path between two viewport points:
183
+ a touch swipe on a device, a pointer drag on a document platform, which is
184
+ what reveals a row action, dismisses a sheet, or drags a card between
185
+ columns. `duration` sets how long the gesture takes in milliseconds; omitted
186
+ leaves the duration to the engine's default. Uses `config.actionTimeout`.
187
+ Throws `ACTION_FAILED` when the gesture is rejected, `INVALID_ARGUMENT` for
188
+ a mix of the two shapes, an out-of-range duration, or a coordinate off the
189
+ plane, and `UNSUPPORTED_CAPABILITY` when the engine does not list `swipe` in
190
+ its actions (a direction) or `swipeTo` in its pointer actions (a path).
189
191
 
190
192
  ```ts
191
193
  await screen.swipe({ direction: 'up', momentum: 'fast' });
@@ -194,6 +196,7 @@ const row = await screen.getByText('Design review').boundingBox();
194
196
  await screen.swipe({
195
197
  from: { x: row.x + row.width - 10, y: row.y + row.height / 2 },
196
198
  to: { x: row.x + 10, y: row.y + row.height / 2 },
199
+ duration: 300,
197
200
  });
198
201
  ```
199
202
 
@@ -585,6 +588,7 @@ export interface SwipeOptions {
585
588
  momentum?: Momentum;
586
589
  from?: never;
587
590
  to?: never;
591
+ duration?: never;
588
592
  }
589
593
 
590
594
  /** A swipe along a path between two viewport points: a touch swipe on a device, a pointer drag on a document platform. */
@@ -593,6 +597,8 @@ export interface SwipePathOptions {
593
597
  from: Point;
594
598
  /** Where it lifts. */
595
599
  to: Point;
600
+ /** Gesture duration in milliseconds; omitted = the engine's default. */
601
+ duration?: number;
596
602
  direction?: never;
597
603
  momentum?: never;
598
604
  }
@@ -663,7 +669,11 @@ and `<details>` are `group`, `<input type="number">` is `spinbutton`,
663
669
  `<select size="3">` is a `listbox` like a `multiple` one, an `<input list>`
664
670
  naming a `<datalist>` is a `combobox`, a `<td>` of a `role="grid"` table is a
665
671
  `gridcell`,
666
- `<thead>` and `<tbody>` are `rowgroup`, `<th scope="row">` is `rowheader`,
672
+ `<thead>` and `<tbody>` are `rowgroup`, `<th scope="row">` is `rowheader`.
673
+ A scope-less `<th>` beside a data cell with text or children is also a
674
+ `rowheader`; header-only rows keep `columnheader`. A lone scope-less header in a one-row
675
+ table has no header role, while `scope="col"` and `scope="colgroup"` explicitly
676
+ keep `columnheader`.
667
677
  `<figure>` is `figure`, `<article>` is `article`, `<header>` and `<footer>` are
668
678
  `banner` and `contentinfo` only outside sectioning content or an element with
669
679
  a sectioning role (`article`, `complementary`, `main`, `navigation`, `region`),
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: test
3
3
  description: Tests, setup tests, groups, hooks, options, and fixtures.
4
+ icon: "flask-vial"
4
5
  ---
5
6
 
6
7
  Declare tests, groups, and hooks synchronously while the test file is
@@ -192,6 +193,19 @@ for every scope whose `beforeAll` started.
192
193
  its own `cleanupTimeout` budget with working fixtures, so teardown can still
193
194
  drive the app after the body timed out.
194
195
 
196
+ A test that blocks its worker's event loop (`while (true) {}` in a fixture,
197
+ `beforeEach`, the body, or `afterEach`) stops every timer in that worker, so
198
+ the worker cannot time it out. A `beforeAll` or `afterAll` that blocks still
199
+ hangs the run. When the worker has not responded `cleanupTimeout` (at least 5 s) after
200
+ the test timeout, the runner kills it. That attempt is reported `timed-out`
201
+ with `TEST_TIMEOUT` and no artifacts, after the attempts before it, and is
202
+ not retried. The rest of its file runs on a new worker. In a serial group,
203
+ the members after it are skipped. A worker with a debugger attached when the
204
+ test starts (an editor's auto-attach, or `NODE_OPTIONS=--inspect=0`) is
205
+ never killed this way, so a breakpoint can stay paused. A programmatic run with an inline
206
+ config or in-memory tests runs in the runner's own process and has no worker
207
+ to kill: a blocked event loop hangs it.
208
+
195
209
  A throw inside `beforeAll` or `afterAll` is `HOOK_FAILED` and skips the
196
210
  suite's tests with cause `hook-failed`. Inside a serial group it skips every
197
211
  remaining member and fails the group without a retry.
@@ -302,7 +316,13 @@ group's attempt. A retry starts fresh. Inside it, `retries`, `trace`,
302
316
  `video`, `session`, `platforms`, `requires`, `skip`, `only`, and a nested
303
317
  `serial` on a member are collection errors. Set them on the group. The group
304
318
  records one trace and one video per group attempt, from the `trace` and
305
- `video` on the serial `describe` or a group around it.
319
+ `video` on the serial `describe` or a group around it. A group attempt that
320
+ cannot start, because its file fails to load or its session fails to launch,
321
+ fails the first member with that error and skips the rest with cause
322
+ `serial-predecessor-failed`. When the run is interrupted during the launch,
323
+ every member skips instead. On a retry, the members keep the verdicts of
324
+ the attempt before it. The exit code follows the error's category, as for any
325
+ other test.
306
326
 
307
327
  ## Fixtures
308
328
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Browser
3
3
  description: The browser engine, its options, and the browser fixture it contributes.
4
+ icon: "globe"
4
5
  ---
5
6
 
6
7
  `@e2e-dev/web` drives Chromium, Firefox, and WebKit through Playwright.
@@ -162,7 +163,7 @@ That protection is text only: unlike a fill, it withholds no screenshot or
162
163
  model pixels, so a page that renders the password on screen is not masked in
163
164
  them ([Security](/security#secrets)).
164
165
 
165
- `headers` and `basicAuth` are how a test reaches a [protected preview](/starting-your-app#protected-previews).
166
+ `headers` and `basicAuth` are how a test reaches a [protected preview](/web#protected-previews).
166
167
 
167
168
  `screencast` shapes the recording, not which attempts record: that is the
168
169
  config's [`video`](/reference/config#video) mode. `size` takes whole pixels,
@@ -291,7 +292,7 @@ export interface Browser extends Expectable<BrowserExpectation> {
291
292
  locator(selector: string): Locator;
292
293
  /** Creates a screen query scope inside one iframe. */
293
294
  frameLocator(selector: string): FrameScreen;
294
- /** Evaluates trusted test code in the page. */
295
+ /** Evaluates trusted test code in the page: a function, or a string expression whose value is returned, called first if it is a function. */
295
296
  evaluate<T extends JsonValue>(fn: string | (() => T | Promise<T>)): Promise<T>;
296
297
  /** Evaluates trusted test code with one required JSON-safe argument. */
297
298
  evaluate<T extends JsonValue, Arg extends JsonValue>(
@@ -428,6 +429,10 @@ close over anything outside its own body; helpers declared inside the body
428
429
  work. Pass outside values through `arg`. Both `arg` and the return value
429
430
  must be `JsonValue`, otherwise the call fails with `INVALID_ARGUMENT`.
430
431
 
432
+ A string is evaluated as a single expression, not as statements. If it
433
+ evaluates to a function, that function is called with `arg`; otherwise its
434
+ value is the result. A promise, from either, is awaited.
435
+
431
436
  An exception in the evaluated code is `EVALUATE_FAILED` with its message. A
432
437
  Playwright timeout is `ACTION_FAILED`; a closed page or lost context is
433
438
  `ENGINE_FAILURE`.
@@ -438,6 +443,7 @@ const flag = await browser.evaluate(
438
443
  (name: string) => window.localStorage.getItem(name),
439
444
  'featureFlags',
440
445
  );
446
+ const title = await browser.evaluate<string>('document.title');
441
447
  ```
442
448
 
443
449
  ### Init scripts