e2e 0.16.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 (256) hide show
  1. package/README.md +10 -0
  2. package/dist/.build.json +1 -1
  3. package/dist/agent/act.js +7 -4
  4. package/dist/agent/action-dispatcher.js +4 -5
  5. package/dist/agent/actions.d.ts +32 -23
  6. package/dist/agent/actions.js +38 -24
  7. package/dist/agent/ai-sdk.js +5 -4
  8. package/dist/agent/error.js +3 -3
  9. package/dist/agent/invocation.js +3 -4
  10. package/dist/agent/model/sdk.js +2 -0
  11. package/dist/agent/model/tool-choice.d.ts +9 -0
  12. package/dist/agent/model/tool-choice.js +14 -0
  13. package/dist/agent/observation-feed.d.ts +5 -6
  14. package/dist/agent/observation-feed.js +8 -11
  15. package/dist/agent/observation.d.ts +40 -11
  16. package/dist/agent/observation.js +109 -59
  17. package/dist/agent/phases.d.ts +7 -1
  18. package/dist/agent/phases.js +12 -3
  19. package/dist/agent/point-tap.d.ts +8 -8
  20. package/dist/agent/point-tap.js +1 -1
  21. package/dist/agent/prompts.js +3 -0
  22. package/dist/agent/replay.d.ts +19 -8
  23. package/dist/agent/replay.js +29 -29
  24. package/dist/agent/scroll-target.d.ts +2 -3
  25. package/dist/agent/step-cache.d.ts +43 -15
  26. package/dist/agent/step-cache.js +156 -67
  27. package/dist/agent/tool-loop.js +94 -5
  28. package/dist/cache/anchors.d.ts +72 -23
  29. package/dist/cache/anchors.js +233 -60
  30. package/dist/cache/context.d.ts +25 -14
  31. package/dist/cache/context.js +50 -8
  32. package/dist/cache/decide.d.ts +2 -2
  33. package/dist/cache/decide.js +4 -6
  34. package/dist/cache/identity.d.ts +18 -4
  35. package/dist/cache/identity.js +9 -4
  36. package/dist/cache/recorder.d.ts +15 -1
  37. package/dist/cache/recorder.js +36 -23
  38. package/dist/cache/rekeyed.d.ts +46 -0
  39. package/dist/cache/rekeyed.js +89 -0
  40. package/dist/cache/relocate.d.ts +14 -14
  41. package/dist/cache/relocate.js +33 -25
  42. package/dist/cache/route.d.ts +32 -33
  43. package/dist/cache/route.js +94 -50
  44. package/dist/cache/store.d.ts +6 -0
  45. package/dist/cache/store.js +21 -1
  46. package/dist/cache/trace.d.ts +53 -14
  47. package/dist/cache/trace.js +76 -30
  48. package/dist/cli/bin.js +2 -2
  49. package/dist/cli/index.js +43 -32
  50. package/dist/cli/init/engines.d.ts +1 -3
  51. package/dist/cli/init/engines.js +2 -2
  52. package/dist/cli/init/gateways.d.ts +1 -1
  53. package/dist/cli/init/gateways.js +11 -1
  54. package/dist/cli/init/mcp-config.d.ts +2 -1
  55. package/dist/cli/init/mcp-config.js +8 -3
  56. package/dist/cli/init/sibling-versions.json +1 -1
  57. package/dist/cli/init/versions.d.ts +0 -8
  58. package/dist/cli/init/versions.js +1 -10
  59. package/dist/cli/mcp.d.ts +3 -2
  60. package/dist/cli/mcp.js +52 -16
  61. package/dist/cli/run-output.d.ts +17 -0
  62. package/dist/cli/run-output.js +45 -0
  63. package/dist/collect/collect.js +8 -4
  64. package/dist/collect/registry.js +27 -16
  65. package/dist/config/compiled-files.d.ts +36 -0
  66. package/dist/config/compiled-files.js +50 -0
  67. package/dist/config/diagnose.js +21 -19
  68. package/dist/config/esm-hooks.d.ts +39 -44
  69. package/dist/config/esm-hooks.js +238 -63
  70. package/dist/config/load.d.ts +1 -7
  71. package/dist/config/load.js +23 -66
  72. package/dist/config/register.d.ts +8 -0
  73. package/dist/config/register.js +9 -0
  74. package/dist/config/resolve.d.ts +1 -0
  75. package/dist/config/resolve.js +33 -1
  76. package/dist/config/specifiers.d.ts +19 -0
  77. package/dist/config/specifiers.js +33 -0
  78. package/dist/config/tsconfig.d.ts +56 -0
  79. package/dist/config/tsconfig.js +115 -0
  80. package/dist/config/typescript.d.ts +19 -0
  81. package/dist/config/typescript.js +253 -0
  82. package/dist/engine/contract.d.ts +1 -0
  83. package/dist/engine/index.d.ts +4 -2
  84. package/dist/engine/session.js +2 -2
  85. package/dist/expect/async.js +4 -2
  86. package/dist/expect/poll-scope.d.ts +52 -0
  87. package/dist/expect/poll-scope.js +113 -0
  88. package/dist/expect/poll.js +33 -6
  89. package/dist/expect/values.js +11 -4
  90. package/dist/internal/abandoned.d.ts +11 -0
  91. package/dist/internal/abandoned.js +23 -0
  92. package/dist/internal/brands.d.ts +2 -0
  93. package/dist/internal/brands.js +2 -0
  94. package/dist/internal/client-identity.d.ts +9 -0
  95. package/dist/internal/client-identity.js +14 -0
  96. package/dist/internal/errors.d.ts +10 -3
  97. package/dist/internal/errors.js +23 -8
  98. package/dist/internal/node-version.d.ts +16 -7
  99. package/dist/internal/node-version.js +50 -15
  100. package/dist/internal/package-manager.js +1 -1
  101. package/dist/internal/realm-slot.d.ts +5 -4
  102. package/dist/internal/redact.d.ts +9 -4
  103. package/dist/internal/redact.js +120 -23
  104. package/dist/internal/time.d.ts +26 -4
  105. package/dist/internal/time.js +58 -14
  106. package/dist/internal/urls.d.ts +9 -5
  107. package/dist/internal/urls.js +19 -10
  108. package/dist/locator/engine.d.ts +12 -2
  109. package/dist/locator/engine.js +55 -14
  110. package/dist/locator/screen.js +59 -9
  111. package/dist/mcp/catalog.d.ts +3 -2
  112. package/dist/mcp/catalog.js +6 -6
  113. package/dist/mcp/output.d.ts +59 -0
  114. package/dist/mcp/output.js +129 -0
  115. package/dist/mcp/server.d.ts +4 -0
  116. package/dist/mcp/server.js +9 -12
  117. package/dist/mcp/session.d.ts +8 -1
  118. package/dist/mcp/session.js +56 -15
  119. package/dist/mcp/tools.js +7 -5
  120. package/dist/oauth/chatgpt.js +3 -23
  121. package/dist/oauth/cli.js +1 -0
  122. package/dist/oauth/copilot.d.ts +5 -4
  123. package/dist/oauth/copilot.js +80 -7
  124. package/dist/oauth/grok.js +2 -1
  125. package/dist/oauth/models.js +2 -1
  126. package/dist/oauth/opencode-console.d.ts +10 -0
  127. package/dist/oauth/opencode-console.js +133 -0
  128. package/dist/oauth/providers/github-copilot.d.ts +12 -0
  129. package/dist/oauth/providers/github-copilot.js +77 -23
  130. package/dist/oauth/providers/opencode-console.d.ts +31 -0
  131. package/dist/oauth/providers/opencode-console.js +148 -0
  132. package/dist/oauth/providers.d.ts +2 -6
  133. package/dist/oauth/providers.js +3 -7
  134. package/dist/oauth/responses.d.ts +9 -0
  135. package/dist/oauth/responses.js +23 -0
  136. package/dist/report/build.d.ts +26 -0
  137. package/dist/report/build.js +16 -8
  138. package/dist/report/failure-text.d.ts +6 -1
  139. package/dist/report/failure-text.js +14 -4
  140. package/dist/report/format.d.ts +10 -5
  141. package/dist/report/format.js +12 -5
  142. package/dist/report/junit.js +11 -12
  143. package/dist/report/list.d.ts +12 -0
  144. package/dist/report/list.js +61 -24
  145. package/dist/report/markdown.d.ts +1 -1
  146. package/dist/report/markdown.js +37 -14
  147. package/dist/report/repeats.d.ts +6 -1
  148. package/dist/report/repeats.js +12 -3
  149. package/dist/run/artifacts.d.ts +22 -4
  150. package/dist/run/artifacts.js +71 -3
  151. package/dist/run/execute.d.ts +34 -5
  152. package/dist/run/execute.js +93 -44
  153. package/dist/run/extended-fixtures.js +6 -2
  154. package/dist/run/failure-evidence.js +10 -4
  155. package/dist/run/fixtures.js +6 -8
  156. package/dist/run/in-process.d.ts +2 -0
  157. package/dist/run/in-process.js +52 -10
  158. package/dist/run/last-run.d.ts +42 -8
  159. package/dist/run/last-run.js +121 -14
  160. package/dist/run/process-output.d.ts +73 -0
  161. package/dist/run/process-output.js +130 -0
  162. package/dist/run/provision.d.ts +2 -0
  163. package/dist/run/provision.js +1 -0
  164. package/dist/run/realm.d.ts +4 -1
  165. package/dist/run/realm.js +20 -6
  166. package/dist/run/records.d.ts +20 -4
  167. package/dist/run/records.js +26 -0
  168. package/dist/run/retry.d.ts +10 -1
  169. package/dist/run/retry.js +30 -12
  170. package/dist/run/runner.d.ts +9 -1
  171. package/dist/run/runner.js +101 -39
  172. package/dist/run/scheduler.js +255 -19
  173. package/dist/run/secrecy.d.ts +22 -19
  174. package/dist/run/secrecy.js +22 -18
  175. package/dist/run/serial.d.ts +75 -2
  176. package/dist/run/serial.js +243 -59
  177. package/dist/run/standalone.js +1 -1
  178. package/dist/run/steps.d.ts +0 -6
  179. package/dist/run/steps.js +3 -20
  180. package/dist/run/trace-redaction.d.ts +5 -6
  181. package/dist/run/trace-redaction.js +5 -6
  182. package/dist/run/worker/entry.js +40 -18
  183. package/dist/run/worker/handle.js +10 -5
  184. package/dist/run/worker/protocol.d.ts +85 -3
  185. package/dist/run/worker/session.d.ts +2 -0
  186. package/dist/run/worker/session.js +29 -0
  187. package/dist/telemetry/environment.d.ts +2 -1
  188. package/dist/telemetry/environment.js +12 -2
  189. package/dist/telemetry/events.js +1 -0
  190. package/dist/telemetry/project.d.ts +5 -6
  191. package/dist/telemetry/project.js +6 -11
  192. package/dist/telemetry/store.d.ts +5 -5
  193. package/dist/telemetry/store.js +5 -7
  194. package/dist/telemetry/telemetry.d.ts +2 -3
  195. package/dist/telemetry/telemetry.js +2 -4
  196. package/dist/types.d.ts +15 -6
  197. package/docs/agent-steps.mdx +1 -0
  198. package/docs/agents.mdx +7 -3
  199. package/docs/api-testing.mdx +2 -1
  200. package/docs/authentication.mdx +6 -3
  201. package/docs/browser.mdx +49 -12
  202. package/docs/bug-bash.mdx +2 -1
  203. package/docs/cache.mdx +66 -19
  204. package/docs/ci.mdx +14 -9
  205. package/docs/coding-agents.mdx +2 -1
  206. package/docs/core-concepts.mdx +208 -0
  207. package/docs/debugging.mdx +25 -12
  208. package/docs/decision-models.mdx +142 -0
  209. package/docs/example-projects.mdx +32 -0
  210. package/docs/executors.mdx +7 -2
  211. package/docs/explore.mdx +1 -0
  212. package/docs/github.mdx +14 -5
  213. package/docs/index.mdx +9 -5
  214. package/docs/integrations/eas.mdx +1 -0
  215. package/docs/integrations/index.mdx +1 -0
  216. package/docs/integrations/kernel.mdx +3 -2
  217. package/docs/migrate/cypress.mdx +26 -3
  218. package/docs/migrate/detox.mdx +24 -1
  219. package/docs/migrate/maestro.mdx +28 -4
  220. package/docs/migrate/playwright.mdx +44 -18
  221. package/docs/migrate/selenium.mdx +27 -3
  222. package/docs/mobile-ci.mdx +6 -5
  223. package/docs/mobile.mdx +96 -8
  224. package/docs/models.mdx +60 -9
  225. package/docs/quickstart.mdx +28 -16
  226. package/docs/reference/agent.mdx +13 -3
  227. package/docs/reference/app.mdx +2 -1
  228. package/docs/reference/cli.mdx +41 -18
  229. package/docs/reference/config.mdx +67 -13
  230. package/docs/reference/engine.mdx +25 -13
  231. package/docs/reference/environment.mdx +10 -2
  232. package/docs/reference/errors.mdx +13 -10
  233. package/docs/reference/expect.mdx +36 -13
  234. package/docs/reference/mcp.mdx +21 -8
  235. package/docs/reference/mobile.mdx +24 -13
  236. package/docs/reference/reporters.mdx +9 -2
  237. package/docs/reference/screen.mdx +49 -16
  238. package/docs/reference/test.mdx +32 -5
  239. package/docs/reference/web.mdx +133 -23
  240. package/docs/security.mdx +58 -42
  241. package/docs/subscriptions.mdx +92 -12
  242. package/docs/telemetry.mdx +9 -6
  243. package/docs/tools.mdx +1 -0
  244. package/docs/{starting-your-app.mdx → web.mdx} +157 -11
  245. package/docs/writing-an-engine.mdx +2 -1
  246. package/package.json +25 -8
  247. package/schema/report-v1.schema.json +14 -1
  248. package/skills/e2e/SKILL.md +5 -3
  249. package/skills/e2e/references/agent.md +33 -11
  250. package/skills/e2e/references/debugging.md +4 -4
  251. package/skills/e2e/references/mcp.md +8 -3
  252. package/skills/e2e/references/running.md +15 -9
  253. package/skills/e2e/references/setup.md +45 -16
  254. package/skills/e2e/references/writing-tests.md +35 -18
  255. package/docs/viewports.mdx +0 -146
  256. package/docs/writing-tests.mdx +0 -256
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Telemetry
3
3
  description: What the CLI sends, what it never sends, and how to turn it off.
4
+ icon: "chart-line"
4
5
  ---
5
6
 
6
7
  The `e2e` CLI sends anonymous usage data so we can see which commands,
@@ -72,7 +73,7 @@ Who ran it:
72
73
  | Property | Value |
73
74
  | --- | --- |
74
75
  | `distinct_id` | A random id stored in the preferences file. CI uses `ci:<vendor>` instead, and a platform that runs e2e for its users uses `fleet:<name>` when it sets `E2E_TELEMETRY_FLEET` |
75
- | `project_id` | A hash derived from the repository's root commit, shared across full clones. Outside git or in a shallow clone, a hash of the project path with a random local salt. `null` in CI when no root commit is available |
76
+ | `project_id` | A hash derived from the repository's root commit, shared across full clones. `null` outside git and in a shallow clone |
76
77
  | `session_id` | A random id for this one invocation |
77
78
  | `first_run`, `days_since_first_run` | Whether this command created the preferences file, and whole days since it was created. `null` in CI and in a fleet, where no machine persists, and for a preferences file from before the time was recorded |
78
79
 
@@ -119,7 +120,7 @@ Versions:
119
120
  | `command` | `run` or `explore` |
120
121
  | `status`, `exit_code`, `duration_ms` | The run's outcome |
121
122
  | `flags` | The names of the flags given to the run command, never their values |
122
- | `tests_discovered`, `tests_selected`, `tests_executed`, `tests_passed`, `tests_failed`, `tests_flaky`, `tests_skipped` | The summary counts |
123
+ | `tests_discovered`, `tests_selected`, `tests_executed`, `tests_passed`, `tests_failed`, `tests_interrupted`, `tests_flaky`, `tests_skipped` | The summary counts |
123
124
  | `attempts_total`, `tests_retried` | How many attempts the run made across tests and serial groups, and how many of them needed more than one |
124
125
  | `targets`, `platforms`, `engines` | How many targets, on which platforms, and with which engines and versions, as your config declares them: `web`, `ios`, `web@0.6.1`, or your own engine's name and version. A name that is not a plain token of letters, digits, dots, dashes, and underscores is reported as `other` |
125
126
  | `steps_total`, `steps_agent`, `steps_locator`, `steps_assertion`, `steps_screen`, `steps_app`, `steps_session`, `steps_resource` | Step counts by kind |
@@ -181,16 +182,18 @@ listed above.
181
182
  ## In CI
182
183
 
183
184
  CI runs are attributed to the CI vendor rather than to a machine, no
184
- preferences file is written on the runner, and no notice is printed. Set
185
- `E2E_TELEMETRY_DISABLED: '1'` in the job's `env` to send nothing from CI.
185
+ preferences file is written on the runner, and no notice is printed. A coding
186
+ agent that sets `CI` in its shell, with no CI vendor around it, counts as the
187
+ machine it runs on. Set `E2E_TELEMETRY_DISABLED: '1'` in the job's `env` to
188
+ send nothing from CI.
186
189
 
187
190
  ## In a fleet
188
191
 
189
192
  A platform that runs e2e on its users' behalf starts each run on a fresh
190
193
  machine, and every fresh machine would otherwise count as a new user. Set
191
194
  `E2E_TELEMETRY_FLEET=<name>` in those machines and they are attributed to
192
- `fleet:<name>` instead, like CI: no preferences file, no notice, and no
193
- project id outside git. Or set `E2E_TELEMETRY_DISABLED=1` and send nothing.
195
+ `fleet:<name>` instead, like CI: no preferences file and no notice. Or set
196
+ `E2E_TELEMETRY_DISABLED=1` and send nothing.
194
197
 
195
198
  ## Where it goes
196
199
 
package/docs/tools.mdx CHANGED
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Project tools
3
3
  description: Add tools for test APIs, data setup, and device operations.
4
+ icon: "screwdriver-wrench"
4
5
  ---
5
6
 
6
7
  Put tools in an agent's `tools` so the agent can call your test API or perform
@@ -1,13 +1,21 @@
1
1
  ---
2
- title: Starting your app
3
- description: Point the runner at a running app, or let it start the app for the run.
2
+ title: Web
3
+ description: Point the web engine at your app or let the runner start it, and run a suite at several viewport sizes.
4
+ icon: "globe"
4
5
  ---
5
6
 
7
+ `@e2e-dev/web` runs tests in a browser against your app's URL. This page
8
+ covers getting the app running for a run and testing it at different
9
+ sizes. For routes, cookies, dialogs, downloads, and the rest of the
10
+ `browser` fixture, see [Working with the browser](/browser).
11
+
12
+ ## Starting your app
13
+
6
14
  Set your app's URL in the target's `app`. If you also provide a start
7
15
  command, the runner starts the app, waits until it is ready, and stops it
8
16
  when the run ends.
9
17
 
10
- ## Point at a running app
18
+ ### Point at a running app
11
19
 
12
20
  Import `web` from `@e2e-dev/web` and put this in the exported
13
21
  config object:
@@ -25,7 +33,7 @@ export default {
25
33
  you can change the address for a run without editing the file. URLs without
26
34
  a scheme use HTTPS, or HTTP for loopback hosts.
27
35
 
28
- ## Let the runner start it
36
+ ### Let the runner start it
29
37
 
30
38
  Add `app.command` and the runner spawns the dev server, waits until `app.url`
31
39
  answers, and stops it when the run ends, fails, or is interrupted:
@@ -79,7 +87,7 @@ temp-directory variables, and `env`, so pass what the app needs explicitly.
79
87
  See the
80
88
  [command reference](/reference/config#command) for all options.
81
89
 
82
- ### Let the runner pick the port
90
+ #### Let the runner pick the port
83
91
 
84
92
  Use port `0` to run multiple checkouts without a port conflict. The runner
85
93
  chooses a free port and replaces `{port}` in the command's `args` and `env`.
@@ -98,7 +106,7 @@ valid when the port changes. Use `127.0.0.1` or `[::1]`, matching the address
98
106
  the command binds; `localhost:0` is rejected. See
99
107
  [free ports](/reference/config#free-ports) for details.
100
108
 
101
- ### Next.js dev targets
109
+ #### Next.js dev targets
102
110
 
103
111
  Next.js 16 blocks cross-origin requests to its dev resources. A target that
104
112
  opens `127.0.0.1` or `[::1]` while `next dev` identifies as `localhost`
@@ -122,7 +130,7 @@ Use `'[::1]'` in the list when `app.url` opens `http://[::1]:0`.
122
130
  A target on `localhost` needs no entry, because `next dev` identifies as
123
131
  `localhost`. A production `next start` target is unaffected either way.
124
132
 
125
- ## Dependencies
133
+ ### Dependencies
126
134
 
127
135
  <Note>
128
136
  This version starts only `app.command` (one process for every target that
@@ -132,7 +140,7 @@ have `app.command` start a script that brings them up and serves the app. A
132
140
  services API returns in a later release.
133
141
  </Note>
134
142
 
135
- ## Several targets
143
+ ### Several targets
136
144
 
137
145
  Two browsers on one app are two targets that each name it:
138
146
 
@@ -155,9 +163,9 @@ test runs on every target unless it is scoped with `platforms` or
155
163
  `requires`; `--target <name>` selects at run time. A device target sits
156
164
  beside a browser target in the same list; see the
157
165
  [Mobile engine reference](/reference/mobile). One target per screen size is
158
- how a suite runs at several [viewports](/viewports).
166
+ how a suite runs at several [viewports](#viewports).
159
167
 
160
- ## Protected previews
168
+ ### Protected previews
161
169
 
162
170
  Use `headers` to access protected previews or skip a tunnel's interstitial
163
171
  page. For example, a Vercel preview can use a protection bypass header:
@@ -221,11 +229,149 @@ workers. Playwright traces include request headers, so a protected-preview
221
229
  trace can contain your bypass secret. Restrict access to those traces.
222
230
  </Note>
223
231
 
232
+ ## Viewports
233
+
234
+ Set a viewport on the target to run a suite at that size. Use
235
+ `browser.setViewport()` to resize during a test. The agent uses the current size
236
+ and cannot resize it.
237
+
238
+ ### One size per target
239
+
240
+ `viewport` sets the initial size of each attempt, in pixels. The default is
241
+ 1280 by 720. Add one target for each size you want to test:
242
+
243
+ ```ts title="e2e.config.ts"
244
+ import type { E2EConfig } from 'e2e';
245
+ import { web } from '@e2e-dev/web';
246
+
247
+ const app = { url: 'http://localhost:3000' };
248
+
249
+ export default {
250
+ targets: [
251
+ { name: 'desktop', engine: web(), app },
252
+ { name: 'tablet', engine: web({ viewport: { width: 820, height: 1180 } }), app },
253
+ { name: 'phone', engine: web({ browser: 'webkit', viewport: { width: 390, height: 844 } }), app },
254
+ ],
255
+ } satisfies E2EConfig;
256
+ ```
257
+
258
+ Each test runs once per target, with a separate result labeled `desktop`,
259
+ `tablet`, or `phone`. Run one size with `npx e2e run --target phone`, or list
260
+ the selected test-target pairs with `npx e2e list`.
261
+
262
+ To have the runner start one dev server for every size, give every target
263
+ the same `app.command`: targets declaring one command share one process; see
264
+ [several targets](#several-targets). Every retry starts at
265
+ the target's configured size.
266
+
267
+ ### Fill the window
268
+
269
+ `viewport: null` sets no size, so the page fills the browser window. The
270
+ agent, screenshots, and videos use that size. Use it in a headed run, or
271
+ with a hosted browser whose live view would show a fixed 1280 by 720 page
272
+ in one corner:
273
+
274
+ ```ts title="e2e.config.ts"
275
+ { name: 'window', engine: web({ viewport: null }), app }
276
+ ```
277
+
278
+ The page takes the window's size, minus the toolbar in a headed browser. A
279
+ local headed launch opens a small window; a hosted browser uses the window
280
+ size you requested from the service. `browser.setViewport` still works.
281
+
282
+ ### Resize inside a test
283
+
284
+ `browser.setViewport` resizes the page for the rest of the attempt, through
285
+ `app.restart()` and `app.clearState()`:
286
+
287
+ ```ts
288
+ import { test } from '@e2e-dev/web';
289
+ import { expect } from 'e2e';
290
+
291
+ test('the menu collapses on a narrow screen', async ({ app, screen, browser }) => {
292
+ await app.open('/');
293
+ await expect(screen.getByRole('navigation')).toBeVisible();
294
+
295
+ await browser.setViewport({ width: 390, height: 844 });
296
+ await expect(screen.getByRole('button', 'Menu')).toBeVisible();
297
+ await expect(screen.getByRole('navigation')).toBeHidden();
298
+ });
299
+ ```
300
+
301
+ Use this when one test needs to check a layout before and after resizing.
302
+ For a whole suite, configure separate targets instead.
303
+
304
+ Call it before `app.open` to load the first page at that size, so the app
305
+ never renders its desktop layout first:
306
+
307
+ ```ts
308
+ await browser.setViewport({ width: 390, height: 844 });
309
+ await app.open('/');
310
+ ```
311
+
312
+ The resize appears as a step in the report. To check the exact width, read
313
+ it from the page:
314
+
315
+ ```ts
316
+ const width = await browser.evaluate(() => window.innerWidth);
317
+ expect(width).toBe(390);
318
+ ```
319
+
320
+ ### What the agent sees
321
+
322
+ The agent receives the current viewport size with each observation.
323
+ Screenshots cover that viewport. Off-screen elements can still appear in
324
+ the tree, and tapping them scrolls them into view. The agent can scroll to
325
+ reveal lazy-loaded content.
326
+
327
+ Resize before calling `agent.act`:
328
+
329
+ ```ts
330
+ test('the mobile menu reaches pricing', async ({ app, agent, browser }) => {
331
+ await browser.setViewport({ width: 390, height: 844 });
332
+ await app.open('/');
333
+ await agent.act('open the navigation menu and go to Pricing');
334
+ await expect(browser).toHaveURL('/pricing');
335
+ });
336
+ ```
337
+
338
+ ### Scoping a test to one size
339
+
340
+ `platforms` and `requires` cannot select a viewport; all Playwright targets
341
+ use platform `web`. To skip a test at some sizes, check the width in the
342
+ test:
343
+
344
+ ```ts
345
+ test('the sidebar stays open', async ({ app, screen, browser }) => {
346
+ await app.open('/');
347
+ test.skip((await browser.evaluate(() => window.innerWidth)) < 768, 'desktop layout only');
348
+ await expect(screen.getByRole('complementary')).toBeVisible();
349
+ });
350
+ ```
351
+
352
+ To keep a whole group of size-specific tests apart, give them their own
353
+ config and run it with `--config`.
354
+
355
+ ### Limits
356
+
357
+ A narrow viewport tests responsive layout. It still uses a desktop browser
358
+ with a scale factor of 1, desktop pointer behavior, and its usual user agent.
359
+ It does not emulate touch, color scheme, or reduced-motion preferences.
360
+ Use the [device engine](/mobile) for tests on a simulator or emulator.
361
+
362
+ A video keeps the size the page had when it started recording; set
363
+ `web({ screencast: { size } })` for one fixed size. A browser provider that
364
+ records, such as [Kernel](/integrations/kernel#recordings), records the whole
365
+ screen instead.
366
+
224
367
  <CardGroup cols={2}>
368
+ <Card title="Working with the browser" icon="globe" href="/browser">
369
+ Routes, cookies, dialogs, downloads, and the rest of the `browser` fixture.
370
+ </Card>
225
371
  <Card title="Config reference" icon="sliders" href="/reference/config#the-app-under-test">
226
372
  `app`, `command`, and every app key.
227
373
  </Card>
228
374
  <Card title="Web engine reference" icon="masks-theater" href="/reference/web">
229
- Browser, viewport, `connect`, `headers`, `basicAuth`.
375
+ Browser, viewport, `connect`, `headers`, `basicAuth`, and every `browser` method.
230
376
  </Card>
231
377
  </CardGroup>
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Writing an engine
3
3
  description: Implement the e2e engine contract for another UI platform.
4
+ icon: "gear"
4
5
  ---
5
6
 
6
7
  An engine connects `screen`, `app`, and agent calls to a UI platform through
@@ -40,7 +41,7 @@ the engine's capabilities when the config loads.
40
41
  This skeleton assumes a `createClient` adapter for your platform. Its nodes
41
42
  must follow the semantic tree contract described below.
42
43
 
43
- ```ts title="src/index.ts"
44
+ ```ts title="src/index.ts" expandable
44
45
  import { ConfigurationError, defineEngine, resolveExpression, type EngineHandle } from 'e2e/engine';
45
46
 
46
47
  export function hyperdrive(options: { device: string }): EngineHandle {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "e2e",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "SDK, runner, and CLI for agentic end-to-end testing",
5
5
  "keywords": [
6
6
  "e2e",
@@ -62,6 +62,10 @@
62
62
  "./oauth/grok": {
63
63
  "types": "./dist/oauth/grok.d.ts",
64
64
  "default": "./dist/oauth/grok.js"
65
+ },
66
+ "./oauth/opencode-console": {
67
+ "types": "./dist/oauth/opencode-console.d.ts",
68
+ "default": "./dist/oauth/opencode-console.js"
65
69
  }
66
70
  },
67
71
  "bin": {
@@ -75,23 +79,35 @@
75
79
  "NOTICE"
76
80
  ],
77
81
  "dependencies": {
78
- "@ai-sdk/provider": "4.0.18",
82
+ "@ai-sdk/provider": "4.0.21",
79
83
  "@clack/prompts": "1.8.1",
80
- "@modelcontextprotocol/server": "2.2.0",
81
- "@vitest/expect": "5.0.2",
84
+ "@jridgewell/remapping": "2.3.5",
85
+ "@modelcontextprotocol/server": "2.3.0",
86
+ "@oxc-project/runtime": "0.152.0",
87
+ "@vitest/expect": "5.0.3",
82
88
  "commander": "15.0.0",
89
+ "get-tsconfig": "4.14.3",
90
+ "oxc-parser": "0.152.0",
91
+ "oxc-transform": "0.152.0",
83
92
  "picocolors": "1.1.1",
84
93
  "pngjs": "7.0.0",
85
- "tsx": "4.23.14",
86
94
  "zod": "4.6.1"
87
95
  },
88
96
  "peerDependencies": {
97
+ "@ai-sdk/anthropic": "^4.0.0",
98
+ "@ai-sdk/google": "^4.0.0",
89
99
  "@ai-sdk/openai": "^4.0.0",
90
100
  "@ai-sdk/openai-compatible": "^3.0.0",
91
101
  "@ai-sdk/xai": "^5.0.0",
92
102
  "ai": "^7.0.0"
93
103
  },
94
104
  "peerDependenciesMeta": {
105
+ "@ai-sdk/anthropic": {
106
+ "optional": true
107
+ },
108
+ "@ai-sdk/google": {
109
+ "optional": true
110
+ },
95
111
  "@ai-sdk/openai": {
96
112
  "optional": true
97
113
  },
@@ -106,10 +122,12 @@
106
122
  }
107
123
  },
108
124
  "devDependencies": {
125
+ "@ai-sdk/anthropic": "4.0.58",
126
+ "@ai-sdk/google": "4.0.77",
109
127
  "@ai-sdk/openai": "4.0.71",
110
128
  "@ai-sdk/openai-compatible": "3.0.53",
111
129
  "@ai-sdk/xai": "5.0.4",
112
- "@e2e-dev/web": "0.11.2",
130
+ "@e2e-dev/web": "0.13.0",
113
131
  "@modelcontextprotocol/client": "2.2.0",
114
132
  "@openrouter/ai-sdk-provider": "3.1.0",
115
133
  "@types/node": "26.6.2",
@@ -117,12 +135,11 @@
117
135
  "ai": "7.0.107",
118
136
  "ajv": "8.20.0",
119
137
  "ajv-formats": "3.0.1",
120
- "playwright": "1.63.0",
121
138
  "typescript": "7.0.2",
122
139
  "vitest": "5.0.1"
123
140
  },
124
141
  "engines": {
125
- "node": ">=22.12.0"
142
+ "node": "^22.22.3 || >=24.8.0"
126
143
  },
127
144
  "scripts": {
128
145
  "build": "node scripts/prepare-build.ts && tsc --project tsconfig.build.json && node scripts/write-sibling-versions.ts && node ../../scripts/stamp-dist.ts",
@@ -570,13 +570,14 @@
570
570
  "summary": {
571
571
  "type": "object",
572
572
  "additionalProperties": false,
573
- "required": ["discovered", "selected", "executed", "passed", "failed", "flaky", "skipped"],
573
+ "required": ["discovered", "selected", "executed", "passed", "failed", "interrupted", "flaky", "skipped"],
574
574
  "properties": {
575
575
  "discovered": { "type": "integer", "minimum": 0 },
576
576
  "selected": { "type": "integer", "minimum": 0 },
577
577
  "executed": { "type": "integer", "minimum": 0 },
578
578
  "passed": { "type": "integer", "minimum": 0 },
579
579
  "failed": { "type": "integer", "minimum": 0 },
580
+ "interrupted": { "type": "integer", "minimum": 0 },
580
581
  "flaky": { "type": "integer", "minimum": 0 },
581
582
  "skipped": { "type": "integer", "minimum": 0 }
582
583
  }
@@ -670,10 +671,22 @@
670
671
  "summary": { "$ref": "#/$defs/summary" },
671
672
  "limits": { "$ref": "#/$defs/limits" },
672
673
  "usage": { "$ref": "#/$defs/usage" },
674
+ "carried": { "$ref": "#/$defs/carried" },
673
675
  "explore": { "$ref": "#/$defs/explore" },
674
676
  "extensions": { "$ref": "#/$defs/extensions" }
675
677
  }
676
678
  },
679
+ "carried": {
680
+ "description": "What earlier runs left unresolved that a --last-failed rerun did not run again: their results as they reported them, the serial groups those results' attempts live on, and the suite hook failures whose scope still holds one of them. The next --last-failed selects these tests too. Not part of the run's status, exit code, or summary.",
681
+ "type": "object",
682
+ "additionalProperties": false,
683
+ "required": ["results", "serialGroups", "errors"],
684
+ "properties": {
685
+ "results": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/result" } },
686
+ "serialGroups": { "type": "array", "items": { "$ref": "#/$defs/serialGroup" } },
687
+ "errors": { "type": "array", "items": { "$ref": "#/$defs/error" } }
688
+ }
689
+ },
677
690
  "exploreFinding": {
678
691
  "type": "object",
679
692
  "additionalProperties": false,
@@ -10,8 +10,8 @@ one goal; `agent.assert`, `agent.waitFor`, and `agent.extract` judge the
10
10
  screen. `screen`, `app`, `browser`, and `expect` make exact interactions and
11
11
  checks. The replay cache reruns verified actions and checks their recorded end
12
12
  state without a model call; agent judgments still run live. UI targets use
13
- `@e2e-dev/web` for browsers or `@e2e-dev/mobile` for iOS simulators and
14
- Android emulators. A test that takes only `app` can check an API with `fetch`
13
+ `@e2e-dev/web` for browsers or `@e2e-dev/mobile` for iOS simulators,
14
+ Android emulators, and connected phones. A test that takes only `app` can check an API with `fetch`
15
15
  and `expect` (topic `writing-tests`). Model sign-in commands are in
16
16
  [setup](references/setup.md#subscriptions-and-api-keys).
17
17
 
@@ -61,7 +61,9 @@ one; the installed CLI prints the same text with `npx e2e guide <topic>`
61
61
  (`e2e guide` alone prints this page). For anything the topics do not cover,
62
62
  the full documentation ships in the `docs/` directory of the installed `e2e`
63
63
  package (`node_modules/e2e/docs` in a single-package project); a link such as
64
- `/reference/cli` is `docs/reference/cli.mdx`.
64
+ `/reference/cli` is `docs/reference/cli.mdx`. Complete projects for Vite,
65
+ Next.js, Expo, and SwiftUI, each with its config, scripts, and a passing
66
+ suite, are in https://github.com/tester-army/e2e/tree/main/examples.
65
67
 
66
68
  | Topic | File | Read it when |
67
69
  | --- | --- | --- |
@@ -6,8 +6,13 @@ model-call budget, no shared transcript. No agent step, no model calls.
6
6
 
7
7
  ## Configure a model
8
8
 
9
- Put an AI SDK model under `agents.default`. Vercel AI Gateway reads
10
- `AI_GATEWAY_API_KEY` or, without it, a Vercel OIDC token:
9
+ Put an AI SDK model under `agents.default`. You can use all AI SDK providers
10
+ that support tool calls and the language model specification v2 or later
11
+ (https://ai-sdk.dev/providers). Examples: `@ai-sdk/openai`,
12
+ `@ai-sdk/anthropic`, `@ai-sdk/google`, `@ai-sdk/amazon-bedrock`,
13
+ `@openrouter/ai-sdk-provider`, `ollama-ai-provider-v2`. For a server with
14
+ `/v1/chat/completions`, use `@ai-sdk/openai-compatible`. Vercel AI Gateway
15
+ reads `AI_GATEWAY_API_KEY` or, without it, a Vercel OIDC token:
11
16
 
12
17
  ```ts
13
18
  import type { E2EConfig } from 'e2e';
@@ -24,6 +29,14 @@ Subscription logins and local models:
24
29
  [setup](setup.md#subscriptions-and-api-keys). Keep `ai@^7` installed with
25
30
  any provider.
26
31
 
32
+ Decision models pick actions from choices the executor builds: install
33
+ `@e2e-dev/decision` and put `decisionExecutor({ model:
34
+ typeSafeAi.decisionModel('jev-latest'), textModel: openrouter('inception/mercury-2.5') })`
35
+ under `executor`. Tests stay plain language with no params; the text model
36
+ writes field values. See the shipped `docs/decision-models.mdx` or
37
+ [the online guide](https://e2e.tester.army/docs/decision-models) for setup,
38
+ gates, and limits.
39
+
27
40
  - Pass a model instance, not a string (`INVALID_CONFIG`).
28
41
  - An agents entry is one plain object of `model`, `judge`, `system`,
29
42
  `context`, `tools`, `maxSteps`, `maxModelCalls`, `judgmentTimeout`,
@@ -32,7 +45,8 @@ any provider.
32
45
  - `model` drives `agent.act`. Judgments use `judge` when set, else `model`.
33
46
  - A missing model for the built-in agent raises one run-level
34
47
  `MODEL_UNAVAILABLE` when the first test acquires `agent`, exit 2; auth
35
- failures surface on the first model call as `MODEL_PROVIDER_FAILED`.
48
+ failures surface on the first model call as `MODEL_PROVIDER_FAILED`, or as
49
+ `CONFIG_LOAD_FAILED` for a provider that reads its key when the config loads.
36
50
  - `context` is what the app calls things, sent to every model call, judges
37
51
  included; `system` is how the acting agent works, read only by the act
38
52
  loop. Both live on the agents entry; `agentContext` on a test or group
@@ -50,8 +64,8 @@ Tests use `agents.default` unless selected otherwise:
50
64
  - `{ agent: 'name' }` on an `agent.*` call overrides the test's choice.
51
65
 
52
66
  For signed-in personas, pair an agent with a `session` in a describe block
53
- repeated per persona; the cache records per agent step, so a specialised
54
- agent replays too.
67
+ repeated per persona; the cache keys every entry by the agent and its
68
+ context, so each persona records and replays its own steps.
55
69
 
56
70
  ## act: one goal
57
71
 
@@ -156,6 +170,9 @@ expect(data.titles).toContain('Buy milk');
156
170
  - Judgments see the assertion and the current screen only, never prior
157
171
  steps or the act loop's summaries; malformed output gets one repair
158
172
  round, then `MODEL_OUTPUT_INVALID`.
173
+ - A value shown in more than one place (a total in the summary and on the
174
+ pay button) must agree everywhere, or the judgment fails. Name the one
175
+ you mean (`'the order summary total is $42.00'`) when only it matters.
159
176
  - `waitFor` observes every `interval` (default 3 s), judges only when the
160
177
  screen changed, and is `STEP_TIMEOUT` after `timeout` (default 30 s).
161
178
  - `extract` takes any Standard Schema validator (zod works); the model sees
@@ -236,14 +253,19 @@ matches. Misses and hand-offs use the model; `agent.assert`,
236
253
  `agent.assert`, `agent.waitFor`), so an unchecked `act` never replays; a
237
254
  plain-value `expect`, `expect.poll`, `agent.extract`, another `act`, or
238
255
  the attempt passing confirms nothing.
239
- - A replay needs the app on the recorded path (unless the recording opens
240
- with a navigation), re-finds each control by role, name, test id,
241
- placeholder, and input purpose, and passes alone only when the recorded
242
- end path and the controls seen during the step are back; otherwise the
243
- agent takes over mid-step. `step.cache.reason` says why: `no-entry`,
256
+ - A replay needs the app on the recorded route (origin, path, and query,
257
+ ids and tokens aside; unless the recording opens with a navigation),
258
+ re-finds each control by role, name, test id, placeholder, input purpose,
259
+ and its named row (an unnamed control with twins and no named container
260
+ hands off), and passes alone only when the recorded end route is back,
261
+ every control that appeared (with its checked or selected state) is
262
+ there, every one that went away is gone, at least one of those changed
263
+ during the replay, and no new alert showed; otherwise the agent takes
264
+ over mid-step. Entries are keyed per agent and per `agentContext`. `step.cache.reason` says why: `no-entry`,
244
265
  `wrong-context`, `target-not-found`, `target-ambiguous`, `end-mismatch`,
245
266
  and so on.
246
- - A step recording no actions creates no entry; one whose `unique()` value
267
+ - A step recording no actions, or changing nothing on screen or in the
268
+ route, creates no entry; one whose `unique()` value
247
269
  equals, is spelled inside, or is the encoded form of another param's value
248
270
  is not recorded either (`step.cache.notRecorded`: `param-collision`).
249
271
  - `e2e init` gitignores `.e2e/cache/`; remove that line to commit entries
@@ -28,7 +28,7 @@ jq '.run.results[] | select(.selected and .status != "passed") | .attempts[-1]
28
28
  (recorded as `skipped`).
29
29
  4. Artifacts, under `.e2e/artifacts/`: `failure/screen.txt`
30
30
  and the engine's screenshot per failed attempt; a Playwright trace per
31
- traced attempt (`npx playwright show-trace <file>`); downloads; with
31
+ traced attempt (`npx playwright-core@1.63.0 show-trace <file>`; `pnpm dlx` under pnpm); downloads; with
32
32
  `--video` the recording (`video/video.webm` in a local browser, each
33
33
  later page `video/video-part<n>.webm` with its own `startedAt`;
34
34
  `video/video.mp4` on a device; a provider's file or link); with
@@ -46,7 +46,7 @@ jq '.run.results[] | select(.selected and .status != "passed") | .attempts[-1]
46
46
  | `COLLECTION_ERROR` | `async` describe body, `test.setup` inside `describe`, an option forbidden in a serial group, registration outside collection | Restructure per `writing-tests` |
47
47
  | `HOOK_FAILED` | `beforeAll` or `afterAll` threw; its scope's tests skip | Fix the hook; the report carries its error |
48
48
  | `UNSUPPORTED_ARTIFACT` | A test's or target's `trace` or `video` on an engine that cannot record | Drop it there, or set it at the config root or CLI (such targets skip with a notice) |
49
- | `BROWSER_INSTALL_FAILED`, `LAUNCH_TIMEOUT` | Browser download failed; engine init or attempt start exceeded `launchTimeout` | Run the quoted `npx playwright install <names>` (`--with-deps` on bare Linux); raise the root `launchTimeout` (60 s default) |
49
+ | `BROWSER_INSTALL_FAILED`, `LAUNCH_TIMEOUT` | Browser download failed; engine init or attempt start exceeded `launchTimeout` | Run the quoted `npx @e2e-dev/web install <names>` or `pnpm exec e2e-web install <names>` (`--with-deps` on bare Linux); raise the root `launchTimeout` (60 s default) |
50
50
  | `APP_UNREACHABLE` | `app.command` never answered `readyUrl` within `startupTimeout`; on a device, a message naming the iOS automation runner: the runner failed, not the app | Read the quoted log lines; check the port, `app.url`, `app.command.env`, `app.command.startupTimeout`. Runner: rerun, else `npx agent-device daemon stop` and reboot the simulator |
51
51
  | `APP_ALREADY_RUNNING` | Something already serves `url` when `command` should start | Stop it, or `reuseExisting: true` locally |
52
52
  | `APP_URL_REQUIRED`, `APP_NOT_OPEN` | A navigation on a target without `app.url`; a `screen` call before `app.open()` | Add `app.url` to the target; open the app first |
@@ -58,14 +58,14 @@ jq '.run.results[] | select(.selected and .status != "passed") | .attempts[-1]
58
58
  | `MODEL_OUTPUT_INVALID` | An `extract` or `assert` answer failed the schema after one repair round | Simplify the schema or question; pick a stronger model |
59
59
  | `ACTION_FAILED` | Element not actionable (covered, disabled, detached), or an operation timed out | `expect` the condition first; close overlays; check `actionTimeout` |
60
60
  | `TEST_TIMEOUT` | The attempt exceeded `timeout` (120 s) | Split the test, or raise `timeout` |
61
- | `STEP_NOT_AWAITED` | The body returned while a step still ran: a call without `await` | `await` the call the code frame names (every `app`, `agent`, `screen`, `expect` call) |
61
+ | `STEP_NOT_AWAITED` | The body returned while a step or `expect.poll` still ran, or an `afterEach` hook or fixture teardown returned while an `expect.poll` still ran: a call without `await` | `await` the call the code frame names (every `app`, `agent`, `screen`, `expect`, `expect.poll` call) |
62
62
  | `MODEL_UNAVAILABLE` | `agents.<name>.model` holds no AI SDK instance: reported once under `run.errors` at the first `agent` fixture; the run stops. A model-less custom executor fails only `waitFor` and `extract` this way | Construct one, e.g. `gateway('openai/gpt-6-luna-fast')` from `ai`, and export its key (`AI_GATEWAY_API_KEY`) |
63
63
  | `MODEL_PROVIDER_FAILED` | Network, 5xx, rate limit, no credits, or no response in 120 s, after the transport retries | Check the credential (the key; for keyless `gateway()` the Vercel CLI login and `.vercel/project.json`) and quota; retry (exit 3) |
64
64
  | `REPLAY_STALE` | `--strict-cache` or `cache.strict`, and a committed recording no longer replays; `step.cache.reason` says why | Rerun read-write with the knob the message names off; commit the changed entry under the directory it names (a custom `cache.store` is written by that run) |
65
65
  | `AUTOMATION_UNSUPPORTED` | The step needs an interaction the engine's toolset lacks (drag on a device, say); blocked, exit 1 | Do that step with `screen` actions, or run the test on a target that supports it |
66
66
  | `STEP_TIMEOUT`, `STEP_BUDGET_EXHAUSTED` | Goal too big or ambiguous, or a slow provider | Split the goal, use on-screen wording, add vocabulary via `agents.<name>.context` or `agentContext`; raise `maxSteps` or `maxModelCalls` (budget), the config `timeout` (`act`), `judgmentTimeout` (judgments); `--debug` |
67
67
  | `CONTEXT_OVERFLOW` | Screen plus step history did not fit the model's context window (`act` after one shrink-and-retry; a judgment on the first overflow) | Lower `agents.<name>.maxObservationBytes`, split the step, or pick a larger-window model |
68
- | `POLICY_DENIED` | A `file:`, `data:`, or `javascript:` URL; a password `Secret` into a non-password sink; reading a secure field, `toHaveValue`, `toHaveText`, `toContainText`, `toHaveAttribute` included; `app.screenshot()` after a secret fill | http(s) only; passwords into password inputs only; assert the outcome, not the value; screenshot before filling secrets |
68
+ | `POLICY_DENIED` | A URL whose scheme is not `http:` or `https:` (`file:`, `view-source:`, `data:`); a password `Secret` into a non-password sink; reading a secure field, `toHaveValue`, `toHaveText`, `toContainText`, `toHaveAttribute` included; `app.screenshot()` after a secret fill, or an `act` or `vision: true` step whose tree read timed out after one | http(s) or `about:blank` only; passwords into password inputs only; assert the outcome, not the value; screenshot before filling secrets |
69
69
  | `UNSUPPORTED_CAPABILITY` | A fixture the engine lacks (`browser` on a device), `schema` or `vision` on `act`, an action the surface lacks | Declare `requires: ['browser']`; drop the option or action |
70
70
  | `SESSION_UNAVAILABLE`, `SESSION_CONTRACT` | `session: 'x'` with no setup saving `x`; a setup that skipped a declared name. `SESSION_MISMATCH`, `SESSION_EXPIRED`, `SESSION_INVALID`: the stored session is another run's or app's, expired, or corrupt | Add or fix the `test.setup`; rerun it |
71
71
  | `ONLY_IN_CI` | `test.only` reached CI | Remove it |
@@ -24,8 +24,9 @@ No model is needed: without the optional `ai` package, sessions open and the
24
24
  deterministic tools work.
25
25
 
26
26
  Flags: `--config <path>` names the default config file, `--target <name>`
27
- fixes the target every session opens on, `--headless` hides the browser or
28
- simulator (sessions are headed by default outside CI), `--max-sessions <n>`
27
+ fixes the target every session opens on, `--headed` shows the browser or
28
+ simulator when the engine supports it in sessions that do not set `headed`
29
+ (sessions are headless by default, as in `e2e run`), `--max-sessions <n>`
29
30
  sets how many sessions may be open at once (default 4, 1 through 16).
30
31
 
31
32
  ## Tools
@@ -34,7 +35,7 @@ Four tools; everything a session can do is a catalog behind `call`.
34
35
 
35
36
  | Tool | Does |
36
37
  | --- | --- |
37
- | `open_session` | Loads the config (`config` names another file; default the nearest `e2e.config.ts`), starts the declared app command if any, boots the engine, opens the app URL, and returns the session id, the catalog, and the first observation. `target` is required when the config declares several. Each session has its own browser or device. |
38
+ | `open_session` | Loads the config (`config` names another file; default the nearest `e2e.config.ts`), starts the declared app command if any, boots the engine, opens the app URL, and returns the session id, the catalog, and the first observation. `target` is required when the config declares several. `headed: true` shows the browser or simulator when the user wants to watch; without it the session follows the server's `--headed`, headless by default. Each session has its own browser or device. |
38
39
  | `tools` | The catalog: one line per tool with its argument names (`?` marks optional), the first sentence of its description, and `[read-only]` where it changes nothing. `tools {tool}` shows the full description and the JSON Schema of its arguments. |
39
40
  | `call` | Runs one catalog tool: `call {tool: "tap", args: {target: "n42"}}`. Arguments are checked against the tool's schema first; a wrong one fails with `INVALID_ARGUMENT` naming the field. |
40
41
  | `close_session` | Saves a recording still running, ends the attempt, disposes the engine, stops the app processes the session started once no other session uses them. |
@@ -57,6 +58,10 @@ The catalog, per session:
57
58
  | `stop_recording` | Stops it and returns the absolute path of each video file, under `<output>/videos/<session>/` (`.e2e` by default), or the URL of a provider's own recording. |
58
59
  | Project tools | Every `defineTool` in the agent's `tools` that applies to the target's platform, under its own name; an engine pack such as `mobileTools` adds `open_app`, `swipe`, `alert`. |
59
60
 
61
+ Project tools use their `toModelOutput` renderer for strings as well as
62
+ structured results. Its `error-text` and `error-json` outputs set the MCP
63
+ result's `isError: true`; treat them as failed calls.
64
+
60
65
  Once a secret is filled in the session, `screenshot` and the point tools
61
66
  (`tap_at`, `hover_at`, `type_at`, `press_at`, `select_at`) answer with a
62
67
  `PIXEL_TAINTED` line for the rest of it; act on listed nodes by id (topic