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.
Files changed (133) hide show
  1. package/README.md +2 -0
  2. package/dist/.build.json +1 -1
  3. package/dist/agent/act-validation.js +1 -0
  4. package/dist/agent/act.js +7 -3
  5. package/dist/agent/action-dispatcher.js +30 -8
  6. package/dist/agent/actions.d.ts +2 -0
  7. package/dist/agent/actions.js +5 -2
  8. package/dist/agent/error.js +1 -0
  9. package/dist/agent/executor.d.ts +14 -6
  10. package/dist/agent/index.js +5 -1
  11. package/dist/agent/invocation.d.ts +3 -0
  12. package/dist/agent/model/sdk.d.ts +5 -0
  13. package/dist/agent/model/sdk.js +18 -1
  14. package/dist/agent/observation.js +1 -0
  15. package/dist/agent/primitives.js +14 -3
  16. package/dist/agent/replay.js +9 -3
  17. package/dist/agent/step-cache.d.ts +3 -2
  18. package/dist/agent/step-cache.js +7 -4
  19. package/dist/agent/tool-loop.js +4 -1
  20. package/dist/cache/context.js +1 -1
  21. package/dist/cache/recorder.d.ts +2 -1
  22. package/dist/cache/recorder.js +11 -2
  23. package/dist/cache/trace-fields.d.ts +10 -0
  24. package/dist/cache/trace-fields.js +17 -0
  25. package/dist/cache/trace.d.ts +9 -1
  26. package/dist/cache/trace.js +26 -7
  27. package/dist/cli/feedback.js +2 -1
  28. package/dist/cli/init/sibling-versions.json +1 -1
  29. package/dist/collect/select.js +12 -2
  30. package/dist/config/agent.d.ts +1 -1
  31. package/dist/config/diagnose.js +9 -3
  32. package/dist/config/esm-hooks.js +3 -2
  33. package/dist/config/resolve.js +3 -2
  34. package/dist/config/typescript.js +52 -2
  35. package/dist/engine/contract.d.ts +3 -2
  36. package/dist/engine/manifest.js +2 -2
  37. package/dist/internal/errors.js +5 -2
  38. package/dist/internal/source.js +2 -0
  39. package/dist/internal/unserved-model.d.ts +5 -0
  40. package/dist/internal/unserved-model.js +22 -0
  41. package/dist/locator/screen.js +1 -2
  42. package/dist/oauth/copilot.js +23 -5
  43. package/dist/oauth/device-code.js +35 -19
  44. package/dist/oauth/errors.d.ts +3 -0
  45. package/dist/oauth/errors.js +26 -11
  46. package/dist/oauth/providers/github-copilot.d.ts +4 -0
  47. package/dist/oauth/providers/github-copilot.js +64 -16
  48. package/dist/oauth/providers/openai.d.ts +2 -2
  49. package/dist/oauth/providers/openai.js +47 -19
  50. package/dist/oauth/providers/opencode-console.js +36 -15
  51. package/dist/oauth/providers/xai.js +18 -9
  52. package/dist/oauth/token-endpoint.d.ts +15 -6
  53. package/dist/oauth/token-endpoint.js +18 -6
  54. package/dist/report/code-frame.js +9 -3
  55. package/dist/report/failure-text.d.ts +10 -2
  56. package/dist/report/failure-text.js +24 -8
  57. package/dist/report/list-model.d.ts +13 -0
  58. package/dist/report/list-steps.d.ts +6 -1
  59. package/dist/report/list-steps.js +29 -0
  60. package/dist/report/list.d.ts +1 -0
  61. package/dist/report/list.js +16 -7
  62. package/dist/report/live-window.js +2 -1
  63. package/dist/report/markdown.js +1 -1
  64. package/dist/run/fixtures.js +2 -0
  65. package/dist/run/host-trace.d.ts +3 -2
  66. package/dist/run/host-trace.js +2 -1
  67. package/dist/run/last-run.d.ts +9 -0
  68. package/dist/run/last-run.js +28 -0
  69. package/dist/run/open-session.d.ts +3 -1
  70. package/dist/run/open-session.js +1 -1
  71. package/dist/run/runner.js +6 -2
  72. package/dist/run/sessions.js +3 -3
  73. package/dist/run/standalone-attempt.d.ts +2 -1
  74. package/dist/run/standalone-attempt.js +3 -1
  75. package/dist/run/steps.d.ts +3 -1
  76. package/dist/run/units.d.ts +8 -0
  77. package/dist/run/units.js +14 -0
  78. package/dist/run/worker-models.d.ts +2 -0
  79. package/dist/run/worker-models.js +7 -0
  80. package/dist/types.d.ts +2 -2
  81. package/docs/authentication.mdx +26 -23
  82. package/docs/browser.mdx +7 -4
  83. package/docs/bug-bash.mdx +2 -2
  84. package/docs/cache.mdx +6 -1
  85. package/docs/ci/bitrise.mdx +1 -1
  86. package/docs/ci/codemagic.mdx +2 -2
  87. package/docs/ci/eas-workflows.mdx +1 -1
  88. package/docs/coding-agents.mdx +7 -35
  89. package/docs/debugging.mdx +24 -36
  90. package/docs/executors.mdx +1 -1
  91. package/docs/github.mdx +2 -1
  92. package/docs/integrations/eas.mdx +1 -1
  93. package/docs/integrations/index.mdx +6 -0
  94. package/docs/integrations/lightpanda.mdx +192 -0
  95. package/docs/integrations/testmu.mdx +152 -0
  96. package/docs/mcp.mdx +123 -0
  97. package/docs/migrate/detox.mdx +1 -1
  98. package/docs/migrate/maestro.mdx +101 -105
  99. package/docs/migrate/playwright.mdx +65 -70
  100. package/docs/mobile.mdx +18 -3
  101. package/docs/models.mdx +4 -2
  102. package/docs/quickstart.mdx +7 -0
  103. package/docs/reference/agent.mdx +369 -277
  104. package/docs/reference/app.mdx +141 -32
  105. package/docs/reference/cli.mdx +674 -558
  106. package/docs/reference/config.mdx +1406 -752
  107. package/docs/reference/engine.mdx +1062 -256
  108. package/docs/reference/environment.mdx +156 -84
  109. package/docs/reference/errors.mdx +106 -72
  110. package/docs/reference/expect.mdx +752 -372
  111. package/docs/reference/mcp.mdx +366 -141
  112. package/docs/reference/mobile.mdx +844 -403
  113. package/docs/reference/reporters.mdx +312 -106
  114. package/docs/reference/runner.mdx +373 -174
  115. package/docs/reference/screen.mdx +931 -398
  116. package/docs/reference/security.mdx +106 -40
  117. package/docs/reference/test.mdx +626 -273
  118. package/docs/reference/web.mdx +956 -533
  119. package/docs/security.mdx +6 -5
  120. package/docs/snippets/model-provider.jsx +763 -0
  121. package/docs/snippets/setup-prompt-card.jsx +22 -0
  122. package/docs/snippets/show-more.jsx +22 -0
  123. package/docs/snippets/video-card.jsx +34 -0
  124. package/docs/snippets/video.jsx +25 -0
  125. package/docs/subscriptions.mdx +10 -3
  126. package/docs/telemetry.mdx +39 -39
  127. package/package.json +3 -2
  128. package/schema/report-v1.schema.json +6 -0
  129. package/skills/e2e/references/agent.md +8 -3
  130. package/skills/e2e/references/debugging.md +1 -1
  131. package/skills/e2e/references/mcp.md +1 -1
  132. package/skills/e2e/references/setup.md +12 -3
  133. package/skills/e2e/references/writing-tests.md +1 -1
@@ -6,6 +6,26 @@ icon: "plug"
6
6
  "twitter:image": "https://e2e.tester.army/docs/images/og/reference/mcp.png?v=3d3880edad"
7
7
  ---
8
8
 
9
+ `e2e mcp` lets coding agents such as Claude Code and Cursor inspect your app,
10
+ try locators, and take masked screenshots before writing tests. It runs over
11
+ stdio and uses the project's configured engine. For setup and what to ask
12
+ the agent, see the [MCP server guide](/mcp).
13
+
14
+ ```json title=".mcp.json"
15
+ {
16
+ "mcpServers": {
17
+ "e2e": { "command": "npx", "args": ["e2e", "mcp"] }
18
+ }
19
+ }
20
+ ```
21
+
22
+ Four MCP tools manage the session and call its available app tools. The app
23
+ tool catalog reloads from the config whenever a session opens.
24
+
25
+ ## Reference
26
+
27
+ ### Flags
28
+
9
29
  <CodeGroup>
10
30
  ```bash npm
11
31
  npx e2e mcp [--config <path>] [--target <name>] [--headed] [--max-sessions <n>]
@@ -20,12 +40,263 @@ bunx e2e mcp [--config <path>] [--target <name>] [--headed] [--max-sessions <n>]
20
40
  ```
21
41
  </CodeGroup>
22
42
 
23
- `e2e mcp` lets coding agents such as Claude Code and Cursor inspect your app,
24
- try locators, and take masked screenshots before writing tests. It runs over
25
- stdio and uses the project's configured engine.
43
+ | Flag | Default |
44
+ | --- | --- |
45
+ | [`--config <path>`](#config) | discovered like `e2e run` |
46
+ | [`--target <name>`](#target) | the only target |
47
+ | [`--headed`](#headed) | headless |
48
+ | [`--max-sessions <n>`](#max-sessions) | `4` |
26
49
 
27
- Four MCP tools manage the session and call its available app tools. The app
28
- tool catalog reloads from the config whenever a session opens.
50
+ #### `--config`
51
+
52
+ The config every session loads unless `open_session` names one. Each
53
+ session loads it afresh ([Several sessions](#several-sessions)).
54
+
55
+ #### `--target`
56
+
57
+ The target a session opens on when `open_session` names none.
58
+
59
+ - Without it and without a name, the only target is used.
60
+ - Several targets and no name is `TARGET_REQUIRED`.
61
+
62
+ #### `--headed`
63
+
64
+ Shows the browser or simulator, when the engine supports it, in every
65
+ session whose `open_session` does not set `headed`.
66
+
67
+ - Sessions are headless by default, as in `e2e run`.
68
+
69
+ #### `--max-sessions`
70
+
71
+ How many sessions may be open at once, 1 through 16. See
72
+ [Several sessions](#several-sessions).
73
+
74
+ ```bash
75
+ e2e mcp --max-sessions 8 # default: 4
76
+ ```
77
+
78
+ ### Tools
79
+
80
+ | Tool | Description |
81
+ | --- | --- |
82
+ | [`open_session`](#open_session) | Opens a session |
83
+ | [`tools`](#tools-2) | Lists the catalog or one tool |
84
+ | [`call`](#call) | Runs one catalog tool |
85
+ | [`close_session`](#close_session) | Ends the session and its engine |
86
+
87
+ `session` may be left out while exactly one session is open. See
88
+ [Several sessions](#several-sessions).
89
+
90
+ #### `open_session`
91
+
92
+ Loads the config, starts the target's app command, boots the engine, starts
93
+ one attempt, and opens the app URL when the engine can navigate. Returns the
94
+ session id, the catalog, and the first observation.
95
+
96
+ ```txt
97
+ open_session {target: "web", headed: true}
98
+ ```
99
+
100
+ | Input | Type | Required | Does |
101
+ | --- | --- | --- | --- |
102
+ | `target` | string | no | The target to open. Otherwise `--target`, else the only target. |
103
+ | `config` | string (path) | no | The config for this session. Otherwise `--config`, else discovered like `e2e run`. |
104
+ | `headed` | boolean | no | `true` shows this session's browser or simulator, `false` hides it. Without it the session follows `--headed`. |
105
+
106
+ #### `tools`
107
+
108
+ Returns the catalog, one line per tool.
109
+
110
+ ```txt
111
+ tools {tool: "locate"}
112
+ ```
113
+
114
+ | Input | Type | Required | Does |
115
+ | --- | --- | --- | --- |
116
+ | `tool` | string | no | A catalog tool. Returns its full description and argument schema. |
117
+ | `session` | string | while several are open | The session id. |
118
+
119
+ #### `call`
120
+
121
+ Runs one catalog tool.
122
+
123
+ ```txt
124
+ call {tool: "tap", args: {target: "n42"}, session: "<id>"}
125
+ ```
126
+
127
+ | Input | Type | Required | Does |
128
+ | --- | --- | --- | --- |
129
+ | `tool` | string | yes | A catalog tool name. |
130
+ | `args` | object | no | The tool's arguments. |
131
+ | `session` | string | while several are open | The session id. |
132
+
133
+ - Arguments are validated against the tool's closed schema first. A wrong
134
+ type or an argument the tool does not declare is `INVALID_ARGUMENT`, and
135
+ nothing runs.
136
+ - An action that fails is an error result that still carries the screen it
137
+ re-observed. It leads with the tool, its target, and the code:
138
+ `navigate file:///etc/passwd failed: POLICY_DENIED: forbidden URL scheme: file:`.
139
+ - Project tools honor their AI SDK `toModelOutput` renderer, including when
140
+ `execute` returns a string. Rendered `error-text` and `error-json` outputs
141
+ are MCP error results (`isError: true`). The coding agent can tell a failed
142
+ tool call from a successful one.
143
+
144
+ #### `close_session`
145
+
146
+ Saves a recording still running, ends the attempt, and disposes the engine.
147
+ It stops the app processes the session started once no other session uses
148
+ them.
149
+
150
+ ```txt
151
+ close_session {session: "<id>"}
152
+ ```
153
+
154
+ | Input | Type | Required | Does |
155
+ | --- | --- | --- | --- |
156
+ | `session` | string | while several are open | The session id. |
157
+
158
+ ### The catalog
159
+
160
+ What `call` can run depends on the session's target.
161
+
162
+ | Catalog tool | Description |
163
+ | --- | --- |
164
+ | [`observe`](#observe) | Returns a fresh observation |
165
+ | [Actions](#actions) | Act on an observed node |
166
+ | [`dismiss_keyboard`](#dismiss_keyboard) | Hides the on-screen keyboard |
167
+ | [`locate`](#locate) | Tries a semantic locator |
168
+ | [`screenshot`](#screenshot) | Returns the masked viewport |
169
+ | [Point tools](#point-tools) | Act at a screenshot point |
170
+ | [`start_recording`, `stop_recording`](#recording) | Records a video of the app |
171
+ | [Project tools](#project-tools) | Your `defineTool` tools |
172
+
173
+ #### `observe`
174
+
175
+ A fresh observation, one node per line as `#id role "name" ...`, with the
176
+ current path and revision. Takes no arguments.
177
+
178
+ - A link's `href` is its path when it is on the app URL's origin, else
179
+ origin and path.
180
+ - `mailto:`, `tel:`, and `blob:` targets keep their scheme.
181
+ - `…` marks what is left out: `?…` a query, `#…` a fragment, `data:…` or
182
+ `javascript:…` an inline payload, a trailing `…` a target cut at 256
183
+ characters.
184
+
185
+ #### Actions
186
+
187
+ `tap`, `double_tap`, `long_press`, `right_click`, `hover`, `type`, `press`,
188
+ `select`, `check`, `scroll`, `scroll_to`, `drag`, `upload`, `navigate`,
189
+ `back`, `type_secret`. Each returns the action's summary and what changed on
190
+ screen.
191
+
192
+ | Input | Does |
193
+ | --- | --- |
194
+ | A node id | From the newest observation. |
195
+ | The value, key, `checked` state, direction, drop target, project-relative `files`, URL, or secret name | What the verb needs. |
196
+ | `modifiers` (optional) | On `tap`, `double_tap`, and `right_click`, when the engine declares `tapModifiers`: a list of `Shift`, `Control`, `Alt`, `Meta`, `ControlOrMeta`, held for the click. |
197
+
198
+ - A verb the engine does not declare is not in the catalog.
199
+ - `upload` refuses a path outside the project, a hidden file, or a file
200
+ under a hidden directory with `POLICY_DENIED`.
201
+ - `type_secret` is listed when the config declares `credentials` or
202
+ `secrets` and the engine declares the `fill` action. The plaintext never reaches the agent.
203
+
204
+ #### `dismiss_keyboard`
205
+
206
+ Hides the on-screen keyboard. Takes no arguments. Listed when the engine's
207
+ keyboard declares `dismiss`, as the device engines do.
208
+
209
+ #### `locate`
210
+
211
+ Returns how many nodes match a semantic locator, which ones, and the
212
+ `screen.*` call to write.
213
+
214
+ | Input | Does |
215
+ | --- | --- |
216
+ | `role` (with optional `name`), `text`, `label`, `placeholder`, `testId` | Exactly one of them. |
217
+ | `exact` | Exact matching; `false` for substring, case-insensitive. |
218
+
219
+ - Zero or several matches name the error a test would get.
220
+ - A `name` matches the accessible name only. `observe` shows a `status`,
221
+ `alert`, or `listitem` by its text, so one of those with a name that
222
+ matches nothing also points at `getByRole(role).filter({ hasText })` and
223
+ `getByText`.
224
+ - What it shows of a node passes the secret ledger, as `observe` does: a
225
+ filled secret reads `<secret:name>`.
226
+
227
+ #### `screenshot`
228
+
229
+ The masked viewport as an image. Takes no arguments.
230
+
231
+ - From then on every action result carries a fresh screenshot.
232
+ - After a secret fill it answers `PIXEL_TAINTED`. See [Sessions](#sessions).
233
+
234
+ #### Point tools
235
+
236
+ Each takes `x` and `y` in the latest screenshot's pixels.
237
+
238
+ | Tool | Inputs | Does | Listed when the engine declares |
239
+ | --- | --- | --- | --- |
240
+ | `tap_at` | `x`, `y` | Taps a listed control under the point by id, otherwise the bare point through the engine's `performAt`. Without `performAt` the point must land on a listed control. | `tap` or `performAt` |
241
+ | `hover_at` | `x`, `y` | Hovers the same way: a listed control under it by id, otherwise the bare point through `performAt`'s `hover` kind. | the `hover` action or pointer kind |
242
+ | `type_at` | `x`, `y`, `value`; `replace` on an engine with a keyboard | Fills a listed input under the point by id. With a keyboard, anything else is tapped to focus it and typed into, at the caret unless `replace` is set. Without one, a point on nothing listed fails. | `type` or a keyboard |
243
+ | `press_at` | `x`, `y`, `key` | Sends the key to a listed control under the point by id. With a keyboard, anything else is tapped to focus it and the key goes through the keyboard. | `press` or a keyboard |
244
+ | `select_at` | `x`, `y`, `value` (the option's visible label) | Picks the option in the listed select-like control under the point. Nothing listed there fails. | `select` |
245
+
246
+ <Note>
247
+ Before the session's first `screenshot`, each point tool answers with the
248
+ line that says to take one, at no cost. Like `screenshot`, they answer
249
+ `PIXEL_TAINTED` after a secret fill.
250
+ </Note>
251
+
252
+ #### Recording
253
+
254
+ `start_recording` and `stop_recording` record a video of the app. Both are
255
+ listed when the engine records video.
256
+
257
+ ```txt
258
+ call {tool: "start_recording", args: {name: "checkout-flow"}}
259
+ call {tool: "stop_recording"}
260
+ ```
261
+
262
+ | Tool | Input | Does |
263
+ | --- | --- | --- |
264
+ | `start_recording` | `name` (optional): up to 64 letters, digits, `-`, and `_`, starting with a letter or digit | One recording at a time. A second start reports the one running. |
265
+ | `stop_recording` | none | Saves the video and returns each file's absolute path, or the URL where a provider keeps it. |
266
+
267
+ - A session records video only between `start_recording` and
268
+ `stop_recording`, as many times as needed. The configured
269
+ [`video`](/reference/config#video) mode applies to runs, not sessions, and
270
+ a session writes no [trace](/reference/config#trace).
271
+ - Each recording is saved to `<output>/videos/<session>/<n>[-<name>].webm`
272
+ (`.mp4` on mobile, and [`output`](/reference/config#output) is `.e2e` by
273
+ default), where `n` counts the session's recordings.
274
+ - A web recording that spans a restart or a state reset has one file per
275
+ page, `-part2`, `-part3`, and so on, in play order.
276
+ - A browser or device provider that records the lease itself records the
277
+ session instead. [Kernel](/integrations/kernel) downloads its replay, saved
278
+ here as `<n>[-<name>].mp4` like any other file. A provider that keeps its
279
+ recordings is listed by URL instead of a file.
280
+ - `close_session`, the idle timeout, and the session TTL save a recording
281
+ still running, and the close result lists its files.
282
+
283
+ <Warning>
284
+ Recordings are not masked and are not withheld after a secret was filled:
285
+ they are files for a person to watch, and no tool returns their pixels. A
286
+ secret typed into a plain input while recording is readable in the video,
287
+ and a recording stopped after a secret was filled says so.
288
+ </Warning>
289
+
290
+ #### Project tools
291
+
292
+ Project tools in `agents.default.tools` follow in the catalog under their
293
+ own names, scoped to the target's platform. A mobile project that passes
294
+ `mobileTools(...)` also gets `open_app`, `swipe`, and `alert`.
295
+
296
+ | Throws | When |
297
+ | --- | --- |
298
+ | `INVALID_CONFIG` | A project tool named like a built-in, at config load. |
299
+ | `UNKNOWN_TOOL` | A name not in the catalog. The message lists the names. |
29
300
 
30
301
  ## Setup
31
302
 
@@ -46,60 +317,80 @@ claude mcp add --scope project e2e -- bunx e2e mcp
46
317
  ```
47
318
  </CodeGroup>
48
319
 
49
- For clients that use a `mcpServers` config, add:
50
-
51
- ```json
52
- {
53
- "mcpServers": {
54
- "e2e": { "command": "npx", "args": ["e2e", "mcp"] }
55
- }
56
- }
57
- ```
58
-
320
+ For clients that use a `mcpServers` config, add the entry shown above.
59
321
  Codex reads the project's `.codex/config.toml` once the project is trusted:
60
322
 
61
- ```toml
323
+ ```toml title=".codex/config.toml"
62
324
  [mcp_servers.e2e]
63
325
  command = "npx"
64
326
  args = ["e2e", "mcp"]
65
327
  ```
66
328
 
329
+ The server is also listed in the
330
+ [MCP Registry](https://registry.modelcontextprotocol.io) as
331
+ `io.github.tester-army/e2e`. A client that installs it from there runs the
332
+ listed release with `npx` and must start it in the project directory. The
333
+ project still needs e2e and its engine installed, since the config imports
334
+ them. A listed release on another version than the project's e2e can reject
335
+ config keys it does not know; the registration above always matches.
336
+
67
337
  The server needs no model: a project without the optional `ai` package
68
338
  opens sessions and drives the app with the deterministic tools.
69
339
 
70
340
  Start the server in the project directory. Config discovery works like
71
- `e2e run`. Set `--config` for another location, or pass `config` to
72
- `open_session` for one session.
73
-
74
- | Flag | Behavior |
75
- | --- | --- |
76
- | `--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. |
77
- | `--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`. |
78
- | `--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`. |
79
- | `--max-sessions <n>` | How many sessions may be open at once, 1 through 16; default 4. Each session is its own browser or device. |
80
-
81
- Operator output goes to stderr, prefixed `e2e mcp:`. Stdout carries only the
82
- protocol. What your code writes through `console`, `process.stdout`, or
83
- `process.stderr` (the config's top-level code, project tools) goes to stderr
84
- with secret values redacted. Output printed while a config loads appears once
85
- it has loaded; if the load fails, that output is withheld, since the config's
86
- secrets are unknown. An error nobody caught is logged redacted and shuts the
87
- server down. A child process that inherits the server's stdio writes past
88
- this.
89
-
90
- ## Tools
91
-
92
- | Tool | Arguments | Result |
93
- | --- | --- | --- |
94
- | `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`. |
95
- | `tools` | `tool` (optional), `session` (required while several are open) | The catalog, one line per tool. With `tool`, its full description and argument schema. |
96
- | `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:`. |
97
- | `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. |
98
-
99
- Project tools honor their AI SDK `toModelOutput` renderer, including when
100
- `execute` returns a string. Rendered `error-text` and `error-json` outputs
101
- are MCP error results (`isError: true`) so the coding agent can distinguish
102
- a failed tool call from a successful one.
341
+ `e2e run`. For another config, see [`--config`](#config) and
342
+ [`open_session`](#open_session).
343
+
344
+ ## Output
345
+
346
+ - Operator output goes to stderr, prefixed `e2e mcp:`. Stdout carries only
347
+ the protocol.
348
+ - What your code writes through `console`, `process.stdout`, or
349
+ `process.stderr` (the config's top-level code, project tools) goes to
350
+ stderr with secret values redacted.
351
+ - Output printed while a config loads appears once it has loaded. If the
352
+ load fails, that output is withheld, since the config's secrets are
353
+ unknown.
354
+ - An error nobody caught is logged redacted and shuts the server down.
355
+
356
+ <Note>
357
+ A child process that inherits the server's stdio writes past this.
358
+ </Note>
359
+
360
+ ## Sessions
361
+
362
+ A session is one attempt on one target. A `call` before any session is open
363
+ fails with `NO_SESSION`.
364
+
365
+ A session closes:
366
+
367
+ - on `close_session`,
368
+ - after 30 idle minutes,
369
+ - after 4 hours,
370
+ - when the client disconnects,
371
+ - on SIGINT or SIGTERM.
372
+
373
+ The client disconnects when it closes the server's stdin or dies. Then every
374
+ session closes, the app commands the sessions started stop, and the server
375
+ exits.
376
+
377
+ Sessions enforce what tests enforce:
378
+
379
+ - Node ids are valid only against the newest observation.
380
+ - A secret fills only through `type_secret`. Once one has been filled,
381
+ [`screenshot`](#screenshot) and the [point tools](#point-tools) stay in the
382
+ catalog but answer `PIXEL_TAINTED` for the rest of the session.
383
+ - Once the config has loaded, its secret values are redacted from every text
384
+ a tool returns, a node `locate` found, an action's result, an
385
+ `open_session` or `close_session` failure, a cleanup line, and the server's
386
+ own log lines.
387
+ - Nothing a session does is recorded as a test result or into the replay
388
+ cache. Its artifacts land under
389
+ `<output>/results/mcp/<target>-<attemptId>/`, its videos under
390
+ `<output>/videos/`. A later `e2e run` clears `<output>/results`; the videos
391
+ stay.
392
+ - A queued call cancelled by the client does not execute when its turn
393
+ arrives.
103
394
 
104
395
  ## Several sessions
105
396
 
@@ -107,27 +398,30 @@ Several sessions can be open at once, up to `--max-sessions`, each on its
107
398
  own attempt and its own browser or device. Parallel agents, such as a
108
399
  coding agent's subagents, share one server this way: each opens a session
109
400
  and passes its id to every `tools`, `call`, and `close_session`.
401
+ [Bug bashes](/bug-bash) use this: every verifying subagent drives its own
402
+ session.
110
403
 
111
- - `session` may be left out only while exactly one session is open. With
112
- several open, a call without it fails with `SESSION_REQUIRED` and the
113
- list of open sessions.
114
- - A `session` id that is not open fails with `NO_SESSION`; for a session
115
- that is closing or ended, the message says why.
116
- - An `open_session` past the limit fails with `SESSION_OPEN`. A session
117
- holds its slot from `open_session` until its attempt has closed, so
118
- sessions still booting or still closing count toward it.
404
+ | Throws | When |
405
+ | --- | --- |
406
+ | `SESSION_REQUIRED` | A call without `session` while several are open. The message lists the open sessions. |
407
+ | `NO_SESSION` | A `session` id that is not open. For a session that is closing or ended, the message says why. |
408
+ | `SESSION_OPEN` | An `open_session` past the limit, or while the server shuts down. |
409
+ | `CONFIG_IN_USE` | An `open_session` on another config while sessions are open. |
410
+ | `ENGINE_IN_USE` | A second session on an engine created inside a package. |
411
+ | `CANCELLED` | An `open_session` the client cancelled or disconnected from, or the server shut down during, once what it started has stopped. |
412
+
413
+ - A session holds its slot from `open_session` until its attempt has closed,
414
+ so sessions still booting or still closing count toward the limit.
119
415
  - Sessions open at once share one config: credentials and secrets resolve
120
- through one registry per process. An `open_session` on another config
121
- fails with `CONFIG_IN_USE` until the others close.
416
+ through one registry per process.
122
417
  - While the server shuts down it cancels sessions still opening and waits
123
- for them, closes every session, and refuses new ones with `SESSION_OPEN`.
124
- A client that cancels an `open_session`, or disconnects, cancels it too.
125
- A cancelled open fails with `CANCELLED` once what it started (an app command
126
- still waiting on its `readyUrl`, say) has stopped.
418
+ for them, closes every session, and refuses new ones. A cancelled open
419
+ fails once what it started (an app command still waiting on its
420
+ `readyUrl`, say) has stopped.
127
421
  - Each session loads the config afresh, down to the files it imports by
128
422
  path, such as a base config it spreads, so each gets engine instances of
129
423
  its own. An engine created inside a package is one instance every session
130
- shares, and a second session on it fails with `ENGINE_IN_USE`.
424
+ shares.
131
425
  - Sessions on one app command share its process: the first session starts
132
426
  it, and it stops when the last of them closes. A URL declared with port
133
427
  `0` gets its own port in each session, and so its own process. A shared
@@ -137,80 +431,6 @@ and passes its id to every `tools`, `call`, and `close_session`.
137
431
  target per device, each naming its `device`, and open each session on its
138
432
  own target.
139
433
 
140
- [Bug bashes](/bug-bash) use this: every verifying subagent drives its own
141
- session.
142
-
143
- ### The catalog
144
-
145
- What `call` can run depends on the session's target:
146
-
147
- | Catalog tool | Arguments | Result |
148
- | --- | --- | --- |
149
- | `observe` | none | A fresh observation, one node per line as `#id role "name" ...`, with the current path and revision. A link's `href` is its path when it is on the app URL's origin, else origin and path; `mailto:`, `tel:`, and `blob:` targets keep their scheme. `…` marks what is left out: `?…` a query, `#…` a fragment, `data:…` or `javascript:…` an inline payload, a trailing `…` a target cut at 256 characters. |
150
- | `tap`, `double_tap`, `long_press`, `right_click`, `hover`, `type`, `press`, `select`, `check`, `scroll`, `scroll_to`, `drag`, `upload`, `navigate`, `back`, `type_secret` | A node id from the newest observation, plus the value, key, `checked` state, direction, drop target, project-relative `files`, URL, or secret name | The action's summary and what changed on screen. A verb the engine does not declare is not in the catalog. `upload` refuses a path outside the project, a hidden file, or a file under a hidden directory with `POLICY_DENIED`. `type_secret` is listed when the config declares `credentials` or `secrets`; the plaintext never reaches the agent. |
151
- | `dismiss_keyboard` | none | Hides the on-screen keyboard. Listed when the engine's keyboard declares `dismiss`, as the device engines do. |
152
- | `locate` | Exactly one of `role` (with optional `name`), `text`, `label`, `placeholder`, `testId`; `exact` | How many nodes match, which, and the `screen.*` call to write. Zero or several matches name the error a test would get. A `name` matches the accessible name only; `observe` shows a `status`, `alert`, or `listitem` by its text, so one of those with a name that matches nothing also points at `getByRole(role).filter({ hasText })` and `getByText`. What it shows of a node passes the secret ledger, as `observe` does: a filled secret reads `<secret:name>`. |
153
- | `screenshot` | none | The masked viewport as an image. From then on every action result carries a fresh screenshot. Once a secret has been filled it answers `PIXEL_TAINTED` instead, for the rest of the session. |
154
- | `tap_at` | `x`, `y` in the latest screenshot's pixels | Taps that point: a listed control under it by id, otherwise the bare point through the engine's `performAt`; without `performAt` the point must land on a listed control. Listed when the engine declares `tap` or `performAt`. |
155
- | `hover_at` | `x`, `y` in the latest screenshot's pixels | Hovers that point the same way: a listed control under it by id, otherwise the bare point through `performAt`'s `hover` kind. Listed when the engine declares the `hover` action or pointer kind. |
156
- | `type_at` | `x`, `y`, `value`; `replace` on an engine with a keyboard | Fills a listed input under the point by id. With a keyboard, anything else is tapped to focus it and typed into, at the caret unless `replace` is set; without one, a point on nothing listed fails. Listed when the engine declares `type` or a keyboard. |
157
- | `press_at` | `x`, `y`, `key` | Sends the key to a listed control under the point by id; with a keyboard, anything else is tapped to focus it and the key goes through the keyboard. Listed when the engine declares `press` or a keyboard. |
158
- | `select_at` | `x`, `y`, `value`, the option's visible label | Picks the option in the listed select-like control under the point; nothing listed there fails. Listed when the engine declares `select`. |
159
- | `start_recording` | `name` (optional; up to 64 letters, digits, `-`, and `_`, starting with a letter or digit) | Starts a video of the app. One recording at a time; a second start reports the one running. Listed when the engine records video. |
160
- | `stop_recording` | none | Stops the recording and returns the absolute path of each video file. Listed when the engine records video. |
161
-
162
- The five point tools answer `PIXEL_TAINTED` once a secret has been filled.
163
- Before the session's first `screenshot` each answers with the line that says
164
- to take one, at no cost.
165
-
166
- Project tools in `agents.default.tools` follow in the catalog under
167
- their own names, scoped to the target's platform. A mobile project that
168
- passes `mobileTools(...)` also gets `open_app`, `swipe`,
169
- and `alert`. A project tool named like a built-in fails the config load with `INVALID_CONFIG`. An unknown
170
- name fails with `UNKNOWN_TOOL` and the list of names.
171
-
172
- ### Recording
173
-
174
- A session records video only between `start_recording` and
175
- `stop_recording`, as many times as needed; the configured
176
- [`video`](/reference/config#video) mode applies to runs, not sessions, and a
177
- session writes no [trace](/reference/config#trace). Each recording is saved to
178
- `<output>/videos/<session>/<n>[-<name>].webm` (`.mp4` on mobile, and
179
- [`output`](/reference/config#output) is `.e2e` by default), where `n`
180
- counts the session's recordings. A web recording that spans a restart or a
181
- state reset has one file per page, `-part2`, `-part3`, and so on, in play
182
- order. A browser or device provider that records the lease itself records
183
- the session instead: [Kernel](/integrations/kernel) downloads its replay,
184
- saved here as `<n>[-<name>].mp4` like any other file, and a provider that
185
- keeps its recordings is listed by URL instead of a file. `close_session`, the idle timeout, and the session
186
- TTL save a recording still running, and the close result lists its files.
187
-
188
- Recordings are not masked and are not withheld after a secret was filled:
189
- they are files for a person to watch, and no tool returns their pixels. A
190
- secret typed into a plain input while recording is readable in the video,
191
- and a recording stopped after a secret was filled says so.
192
-
193
- A session is one attempt on one target. A `call` before any session is
194
- open fails with `NO_SESSION`. A session closes on `close_session`, after 30
195
- idle minutes, after 4 hours, when the client disconnects, and on SIGINT or
196
- SIGTERM. The client disconnects when it closes the server's stdin or dies,
197
- so the pipes break under the server: every session closes, the app commands
198
- the sessions started stop, and the server exits.
199
-
200
- Sessions enforce what tests enforce. Node ids are valid only against the
201
- newest observation. A secret fills only through `type_secret`; once one has
202
- been filled, `screenshot` and the point tools (`tap_at`, `hover_at`,
203
- `type_at`, `press_at`, `select_at`) stay in the catalog but answer
204
- `PIXEL_TAINTED` for the rest of the session. Every text a tool returns, a node `locate` found,
205
- an action's result, an `open_session` or `close_session` failure, or a cleanup line, has the
206
- config's secret values redacted once the config has loaded, as do the server's own log lines. Nothing a
207
- session does is recorded as a test result or into the replay cache; its
208
- artifacts land under `<output>/results/mcp/<target>-<attemptId>/`, its videos under
209
- `<output>/videos/`. A later `e2e run` clears `<output>/results`; the
210
- videos stay.
211
-
212
- A queued call cancelled by the client does not execute when its turn arrives.
213
-
214
434
  ## Resources
215
435
 
216
436
  | URI | Content |
@@ -220,7 +440,12 @@ A queued call cancelled by the client does not execute when its turn arrives.
220
440
 
221
441
  ## Exit codes
222
442
 
223
- `e2e mcp` exits 0 when the client disconnects, with or without a session
224
- open, or a signal ends it, and 2
225
- when the command line is invalid. Config and engine failures come back as
226
- tool errors with the same codes the CLI prints.
443
+ | Code | When |
444
+ | --- | --- |
445
+ | `0` | The client disconnects, with or without a session open, or a signal ends it. |
446
+ | `1` | An uncaught error shuts the server down. |
447
+ | `2` | The command line is invalid. |
448
+ | The error's [category code](/reference/cli#exit-codes) | A startup failure. |
449
+
450
+ Config and engine failures come back as tool errors with the same codes the
451
+ CLI prints.