@e2edev/e2e 0.12.0 → 0.14.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 (319) hide show
  1. package/README.md +4 -4
  2. package/dist/agent/act.d.ts +13 -7
  3. package/dist/agent/act.d.ts.map +1 -1
  4. package/dist/agent/act.js +125 -730
  5. package/dist/agent/act.js.map +1 -1
  6. package/dist/agent/action-dispatcher.d.ts +98 -0
  7. package/dist/agent/action-dispatcher.d.ts.map +1 -0
  8. package/dist/agent/action-dispatcher.js +319 -0
  9. package/dist/agent/action-dispatcher.js.map +1 -0
  10. package/dist/agent/actions.d.ts +16 -1
  11. package/dist/agent/actions.d.ts.map +1 -1
  12. package/dist/agent/actions.js +5 -1
  13. package/dist/agent/actions.js.map +1 -1
  14. package/dist/agent/default-agent.d.ts +1 -1
  15. package/dist/agent/default-agent.d.ts.map +1 -1
  16. package/dist/agent/default-agent.js +30 -9
  17. package/dist/agent/default-agent.js.map +1 -1
  18. package/dist/agent/error.d.ts.map +1 -1
  19. package/dist/agent/error.js +1 -0
  20. package/dist/agent/error.js.map +1 -1
  21. package/dist/agent/executor.d.ts +33 -5
  22. package/dist/agent/executor.d.ts.map +1 -1
  23. package/dist/agent/executor.js.map +1 -1
  24. package/dist/agent/index.js +1 -1
  25. package/dist/agent/index.js.map +1 -1
  26. package/dist/agent/invocation.d.ts +4 -9
  27. package/dist/agent/invocation.d.ts.map +1 -1
  28. package/dist/agent/invocation.js +1 -6
  29. package/dist/agent/invocation.js.map +1 -1
  30. package/dist/agent/model/sdk.d.ts +1 -1
  31. package/dist/agent/model/sdk.js +1 -1
  32. package/dist/agent/observation-feed.d.ts +123 -0
  33. package/dist/agent/observation-feed.d.ts.map +1 -0
  34. package/dist/agent/observation-feed.js +281 -0
  35. package/dist/agent/observation-feed.js.map +1 -0
  36. package/dist/agent/observation.d.ts +14 -1
  37. package/dist/agent/observation.d.ts.map +1 -1
  38. package/dist/agent/observation.js +58 -2
  39. package/dist/agent/observation.js.map +1 -1
  40. package/dist/agent/operation-queue.d.ts +21 -0
  41. package/dist/agent/operation-queue.d.ts.map +1 -0
  42. package/dist/agent/operation-queue.js +27 -0
  43. package/dist/agent/operation-queue.js.map +1 -0
  44. package/dist/agent/phases.js +1 -1
  45. package/dist/agent/phases.js.map +1 -1
  46. package/dist/agent/pixels.d.ts +21 -0
  47. package/dist/agent/pixels.d.ts.map +1 -0
  48. package/dist/agent/pixels.js +85 -0
  49. package/dist/agent/pixels.js.map +1 -0
  50. package/dist/agent/point-tap.d.ts +52 -0
  51. package/dist/agent/point-tap.d.ts.map +1 -0
  52. package/dist/agent/point-tap.js +88 -0
  53. package/dist/agent/point-tap.js.map +1 -0
  54. package/dist/agent/primitives.d.ts +7 -1
  55. package/dist/agent/primitives.d.ts.map +1 -1
  56. package/dist/agent/primitives.js +99 -27
  57. package/dist/agent/primitives.js.map +1 -1
  58. package/dist/agent/replay.d.ts +8 -3
  59. package/dist/agent/replay.d.ts.map +1 -1
  60. package/dist/agent/replay.js +44 -15
  61. package/dist/agent/replay.js.map +1 -1
  62. package/dist/agent/screen-update.d.ts +50 -18
  63. package/dist/agent/screen-update.d.ts.map +1 -1
  64. package/dist/agent/screen-update.js +76 -94
  65. package/dist/agent/screen-update.js.map +1 -1
  66. package/dist/agent/secrets.d.ts.map +1 -1
  67. package/dist/agent/secrets.js +13 -9
  68. package/dist/agent/secrets.js.map +1 -1
  69. package/dist/agent/step-accounting.d.ts +106 -0
  70. package/dist/agent/step-accounting.d.ts.map +1 -0
  71. package/dist/agent/step-accounting.js +216 -0
  72. package/dist/agent/step-accounting.js.map +1 -0
  73. package/dist/agent/step-cache.d.ts.map +1 -1
  74. package/dist/agent/step-cache.js +2 -2
  75. package/dist/agent/step-cache.js.map +1 -1
  76. package/dist/agent/tool-loop.d.ts.map +1 -1
  77. package/dist/agent/tool-loop.js +9 -2
  78. package/dist/agent/tool-loop.js.map +1 -1
  79. package/dist/agent/transcript-compaction.d.ts +38 -0
  80. package/dist/agent/transcript-compaction.d.ts.map +1 -0
  81. package/dist/agent/transcript-compaction.js +165 -0
  82. package/dist/agent/transcript-compaction.js.map +1 -0
  83. package/dist/cache/recorder.d.ts.map +1 -1
  84. package/dist/cache/recorder.js +20 -0
  85. package/dist/cache/recorder.js.map +1 -1
  86. package/dist/cache/trace.d.ts +28 -1
  87. package/dist/cache/trace.d.ts.map +1 -1
  88. package/dist/cache/trace.js +44 -0
  89. package/dist/cache/trace.js.map +1 -1
  90. package/dist/cli/index.d.ts.map +1 -1
  91. package/dist/cli/index.js +55 -32
  92. package/dist/cli/index.js.map +1 -1
  93. package/dist/cli/init/engine-versions.json +1 -1
  94. package/dist/cli/init/engines.d.ts +29 -10
  95. package/dist/cli/init/engines.d.ts.map +1 -1
  96. package/dist/cli/init/engines.js +34 -12
  97. package/dist/cli/init/engines.js.map +1 -1
  98. package/dist/cli/init/gateways.js +3 -3
  99. package/dist/cli/init/mcp-config.js +1 -1
  100. package/dist/cli/init/mcp-config.js.map +1 -1
  101. package/dist/cli/init/scaffold.d.ts.map +1 -1
  102. package/dist/cli/init/scaffold.js +1 -3
  103. package/dist/cli/init/scaffold.js.map +1 -1
  104. package/dist/cli/init.d.ts.map +1 -1
  105. package/dist/cli/init.js +3 -2
  106. package/dist/cli/init.js.map +1 -1
  107. package/dist/collect/collect.d.ts +8 -3
  108. package/dist/collect/collect.d.ts.map +1 -1
  109. package/dist/collect/collect.js +29 -5
  110. package/dist/collect/collect.js.map +1 -1
  111. package/dist/collect/registry.d.ts +20 -5
  112. package/dist/collect/registry.d.ts.map +1 -1
  113. package/dist/collect/registry.js +139 -54
  114. package/dist/collect/registry.js.map +1 -1
  115. package/dist/collect/select.js +3 -1
  116. package/dist/collect/select.js.map +1 -1
  117. package/dist/config/agent.d.ts +1 -7
  118. package/dist/config/agent.d.ts.map +1 -1
  119. package/dist/config/agent.js +5 -8
  120. package/dist/config/agent.js.map +1 -1
  121. package/dist/config/app.d.ts +24 -2
  122. package/dist/config/app.d.ts.map +1 -1
  123. package/dist/config/app.js +82 -15
  124. package/dist/config/app.js.map +1 -1
  125. package/dist/config/diagnose.d.ts.map +1 -1
  126. package/dist/config/diagnose.js +2 -0
  127. package/dist/config/diagnose.js.map +1 -1
  128. package/dist/config/resolve.d.ts +26 -3
  129. package/dist/config/resolve.d.ts.map +1 -1
  130. package/dist/config/resolve.js +66 -23
  131. package/dist/config/resolve.js.map +1 -1
  132. package/dist/config/secrets.d.ts +89 -0
  133. package/dist/config/secrets.d.ts.map +1 -0
  134. package/dist/config/secrets.js +67 -0
  135. package/dist/config/secrets.js.map +1 -0
  136. package/dist/engine/contract.d.ts +17 -2
  137. package/dist/engine/contract.d.ts.map +1 -1
  138. package/dist/engine/contract.js.map +1 -1
  139. package/dist/engine/index.d.ts +18 -4
  140. package/dist/engine/index.d.ts.map +1 -1
  141. package/dist/engine/index.js +8 -0
  142. package/dist/engine/index.js.map +1 -1
  143. package/dist/engine/session.d.ts.map +1 -1
  144. package/dist/engine/session.js +3 -0
  145. package/dist/engine/session.js.map +1 -1
  146. package/dist/engine/surface.d.ts +6 -2
  147. package/dist/engine/surface.d.ts.map +1 -1
  148. package/dist/engine/surface.js.map +1 -1
  149. package/dist/expect/attempt.d.ts +15 -0
  150. package/dist/expect/attempt.d.ts.map +1 -0
  151. package/dist/expect/attempt.js +33 -0
  152. package/dist/expect/attempt.js.map +1 -0
  153. package/dist/expect/index.d.ts +7 -5
  154. package/dist/expect/index.d.ts.map +1 -1
  155. package/dist/expect/index.js +8 -1
  156. package/dist/expect/index.js.map +1 -1
  157. package/dist/expect/poll.d.ts +5 -0
  158. package/dist/expect/poll.d.ts.map +1 -0
  159. package/dist/expect/poll.js +118 -0
  160. package/dist/expect/poll.js.map +1 -0
  161. package/dist/expect/values.d.ts.map +1 -1
  162. package/dist/expect/values.js +6 -1
  163. package/dist/expect/values.js.map +1 -1
  164. package/dist/explore/body.d.ts.map +1 -1
  165. package/dist/explore/body.js +3 -1
  166. package/dist/explore/body.js.map +1 -1
  167. package/dist/explore/executor.js +16 -11
  168. package/dist/explore/executor.js.map +1 -1
  169. package/dist/explore/index.d.ts +3 -2
  170. package/dist/explore/index.d.ts.map +1 -1
  171. package/dist/explore/index.js +5 -5
  172. package/dist/explore/index.js.map +1 -1
  173. package/dist/explore/plan.d.ts.map +1 -1
  174. package/dist/explore/plan.js +16 -5
  175. package/dist/explore/plan.js.map +1 -1
  176. package/dist/explore/progress.d.ts +43 -0
  177. package/dist/explore/progress.d.ts.map +1 -0
  178. package/dist/explore/progress.js +9 -0
  179. package/dist/explore/progress.js.map +1 -0
  180. package/dist/explore/state.d.ts +13 -12
  181. package/dist/explore/state.d.ts.map +1 -1
  182. package/dist/explore/state.js +28 -11
  183. package/dist/explore/state.js.map +1 -1
  184. package/dist/index.d.ts +3 -1
  185. package/dist/index.d.ts.map +1 -1
  186. package/dist/index.js +4 -1
  187. package/dist/index.js.map +1 -1
  188. package/dist/internal/brands.d.ts +2 -0
  189. package/dist/internal/brands.d.ts.map +1 -1
  190. package/dist/internal/brands.js +2 -0
  191. package/dist/internal/brands.js.map +1 -1
  192. package/dist/internal/geometry.d.ts +13 -0
  193. package/dist/internal/geometry.d.ts.map +1 -0
  194. package/dist/internal/geometry.js +16 -0
  195. package/dist/internal/geometry.js.map +1 -0
  196. package/dist/internal/urls.d.ts +6 -0
  197. package/dist/internal/urls.d.ts.map +1 -1
  198. package/dist/internal/urls.js +28 -0
  199. package/dist/internal/urls.js.map +1 -1
  200. package/dist/locator/engine.d.ts.map +1 -1
  201. package/dist/locator/engine.js +1 -0
  202. package/dist/locator/engine.js.map +1 -1
  203. package/dist/mcp/catalog.d.ts +5 -4
  204. package/dist/mcp/catalog.d.ts.map +1 -1
  205. package/dist/mcp/catalog.js +5 -36
  206. package/dist/mcp/catalog.js.map +1 -1
  207. package/dist/mcp/server.js +1 -1
  208. package/dist/mcp/server.js.map +1 -1
  209. package/dist/mcp/session.d.ts +1 -1
  210. package/dist/mcp/session.d.ts.map +1 -1
  211. package/dist/mcp/session.js +12 -6
  212. package/dist/mcp/session.js.map +1 -1
  213. package/dist/report/builtin.d.ts +14 -0
  214. package/dist/report/builtin.d.ts.map +1 -0
  215. package/dist/report/builtin.js +22 -0
  216. package/dist/report/builtin.js.map +1 -0
  217. package/dist/report/explore-text.d.ts +17 -0
  218. package/dist/report/explore-text.d.ts.map +1 -0
  219. package/dist/report/explore-text.js +37 -0
  220. package/dist/report/explore-text.js.map +1 -0
  221. package/dist/report/format.d.ts +2 -0
  222. package/dist/report/format.d.ts.map +1 -1
  223. package/dist/report/format.js +1 -1
  224. package/dist/report/format.js.map +1 -1
  225. package/dist/report/junit.d.ts +2 -4
  226. package/dist/report/junit.d.ts.map +1 -1
  227. package/dist/report/junit.js +7 -40
  228. package/dist/report/junit.js.map +1 -1
  229. package/dist/report/list-explore.d.ts +109 -0
  230. package/dist/report/list-explore.d.ts.map +1 -0
  231. package/dist/report/list-explore.js +368 -0
  232. package/dist/report/list-explore.js.map +1 -0
  233. package/dist/report/list.d.ts +21 -0
  234. package/dist/report/list.d.ts.map +1 -1
  235. package/dist/report/list.js +79 -3
  236. package/dist/report/list.js.map +1 -1
  237. package/dist/report/markdown.d.ts +39 -0
  238. package/dist/report/markdown.d.ts.map +1 -0
  239. package/dist/report/markdown.js +426 -0
  240. package/dist/report/markdown.js.map +1 -0
  241. package/dist/report/outcome.d.ts +29 -0
  242. package/dist/report/outcome.d.ts.map +1 -0
  243. package/dist/report/outcome.js +36 -0
  244. package/dist/report/outcome.js.map +1 -0
  245. package/dist/report/running-tree.d.ts +7 -7
  246. package/dist/report/running-tree.d.ts.map +1 -1
  247. package/dist/report/running-tree.js +12 -13
  248. package/dist/report/running-tree.js.map +1 -1
  249. package/dist/report/write.d.ts +10 -1
  250. package/dist/report/write.d.ts.map +1 -1
  251. package/dist/report/write.js +22 -1
  252. package/dist/report/write.js.map +1 -1
  253. package/dist/run/app-ports.d.ts +17 -0
  254. package/dist/run/app-ports.d.ts.map +1 -0
  255. package/dist/run/app-ports.js +74 -0
  256. package/dist/run/app-ports.js.map +1 -0
  257. package/dist/run/events.d.ts +10 -5
  258. package/dist/run/events.d.ts.map +1 -1
  259. package/dist/run/events.js.map +1 -1
  260. package/dist/run/execute.d.ts.map +1 -1
  261. package/dist/run/execute.js +24 -7
  262. package/dist/run/execute.js.map +1 -1
  263. package/dist/run/extended-fixtures.d.ts +33 -0
  264. package/dist/run/extended-fixtures.d.ts.map +1 -0
  265. package/dist/run/extended-fixtures.js +91 -0
  266. package/dist/run/extended-fixtures.js.map +1 -0
  267. package/dist/run/fixtures.d.ts.map +1 -1
  268. package/dist/run/fixtures.js +39 -11
  269. package/dist/run/fixtures.js.map +1 -1
  270. package/dist/run/managed-process.d.ts +18 -0
  271. package/dist/run/managed-process.d.ts.map +1 -1
  272. package/dist/run/managed-process.js +101 -6
  273. package/dist/run/managed-process.js.map +1 -1
  274. package/dist/run/realm.d.ts +5 -4
  275. package/dist/run/realm.d.ts.map +1 -1
  276. package/dist/run/realm.js +8 -7
  277. package/dist/run/realm.js.map +1 -1
  278. package/dist/run/runner.d.ts +9 -3
  279. package/dist/run/runner.d.ts.map +1 -1
  280. package/dist/run/runner.js +15 -12
  281. package/dist/run/runner.js.map +1 -1
  282. package/dist/run/secrecy.d.ts +3 -3
  283. package/dist/run/secrecy.d.ts.map +1 -1
  284. package/dist/run/secrecy.js +4 -4
  285. package/dist/run/secrecy.js.map +1 -1
  286. package/dist/run/standalone.js +6 -6
  287. package/dist/run/standalone.js.map +1 -1
  288. package/dist/run/worker/entry.js +4 -3
  289. package/dist/run/worker/entry.js.map +1 -1
  290. package/dist/run/worker/protocol.d.ts +7 -1
  291. package/dist/run/worker/protocol.d.ts.map +1 -1
  292. package/dist/run/worker/protocol.js.map +1 -1
  293. package/dist/secrets.d.ts +21 -0
  294. package/dist/secrets.d.ts.map +1 -0
  295. package/dist/secrets.js +66 -0
  296. package/dist/secrets.js.map +1 -0
  297. package/dist/types.d.ts +90 -50
  298. package/dist/types.d.ts.map +1 -1
  299. package/package.json +6 -3
  300. package/skills/e2e/SKILL.md +42 -25
  301. package/skills/e2e/references/agent.md +80 -24
  302. package/skills/e2e/references/debugging.md +6 -5
  303. package/skills/e2e/references/explore.md +13 -7
  304. package/skills/e2e/references/mcp.md +4 -4
  305. package/skills/e2e/references/running.md +32 -21
  306. package/skills/e2e/references/setup.md +42 -16
  307. package/skills/e2e/references/writing-tests.md +32 -16
  308. package/dist/agent/model/router.d.ts +0 -36
  309. package/dist/agent/model/router.d.ts.map +0 -1
  310. package/dist/agent/model/router.js +0 -45
  311. package/dist/agent/model/router.js.map +0 -1
  312. package/dist/credentials.d.ts +0 -13
  313. package/dist/credentials.d.ts.map +0 -1
  314. package/dist/credentials.js +0 -51
  315. package/dist/credentials.js.map +0 -1
  316. package/dist/explore/reporter.d.ts +0 -11
  317. package/dist/explore/reporter.d.ts.map +0 -1
  318. package/dist/explore/reporter.js +0 -54
  319. package/dist/explore/reporter.js.map +0 -1
@@ -1,8 +1,9 @@
1
1
  # Agent steps
2
2
 
3
- `agent` is a fixture like `screen`. Each call is one bounded invocation: a
4
- fresh redacted observation, a deadline, a model-call budget, and no shared
5
- transcript between calls. Deterministic tests never load a model.
3
+ `agent` is a fixture like `screen` and the main way a test drives the app.
4
+ Each call is one bounded invocation: a fresh redacted observation, a
5
+ deadline, a model-call budget, and no shared transcript between calls. A
6
+ test with no agent step never loads a model.
6
7
 
7
8
  ## Configure a model
8
9
 
@@ -15,18 +16,18 @@ import { gateway } from 'ai';
15
16
 
16
17
  export default {
17
18
  // 1. The built-in agent with an AI SDK model: gateway() from 'ai' is the Vercel AI Gateway and reads AI_GATEWAY_API_KEY.
18
- agents: { default: createAgent({ model: gateway('openai/gpt-5.4-mini'), system: 'You are a thorough QA agent. Verify every outcome on screen.' }) },
19
+ agents: { default: createAgent({ model: gateway('openai/gpt-5.6-luna'), system: 'You are a thorough QA agent. Verify every outcome on screen.' }) },
19
20
 
20
21
  // 2. An options block: a model plus project vocabulary. openrouter() from '@openrouter/ai-sdk-provider' reads OPENROUTER_API_KEY.
21
22
  // agents: { default: { model: openrouter('anthropic/claude-sonnet-4.5'), context: 'A billing dashboard. Plans are Free, Team, and Pro.' } },
22
23
 
23
24
  // 3. A live AI SDK model instance for a provider called directly.
24
- // agents: { default: createAgent({ model: openai('gpt-5.4-mini') }) },
25
+ // agents: { default: createAgent({ model: openai('gpt-5.6-luna') }) },
25
26
  } satisfies E2EConfig;
26
27
  ```
27
28
 
28
29
  ```bash
29
- AI_GATEWAY_API_KEY=... npx --no-install e2e run
30
+ AI_GATEWAY_API_KEY=... npx e2e run
30
31
  ```
31
32
 
32
33
  - The model is always an AI SDK instance the config constructs; the runner
@@ -42,7 +43,6 @@ AI_GATEWAY_API_KEY=... npx --no-install e2e run
42
43
  lazily and fails without it.
43
44
  - `context` in the config and `agentContext` on a test or group add trusted
44
45
  project vocabulary to every prompt.
45
- - `visionModel` serves the calls that send pixels.
46
46
  - The model passed to `createAgent({ model })` is the one model for every
47
47
  `agent.*` call, `act` and the judgments alike.
48
48
  An agent `model` naming a different model is `INVALID_CONFIG`.
@@ -80,8 +80,15 @@ verdict.
80
80
  Options: `params` (the values the instruction refers to; a `Secret` is filled
81
81
  by the runner), `timeout` (default the test timeout), `maxSteps` (default 25
82
82
  actions), `maxModelCalls` (default 25). Per-call budgets can only lower the
83
- configured limits. `act` takes no `schema` and no `vision`: structured output
84
- is `extract({ schema })`, and `vision` belongs to the judgments.
83
+ configured limits. `act` takes no `schema` and no `vision` option: structured
84
+ output is `extract({ schema })`, and pixels reach an `act` step through the
85
+ `screenshot` and `tap_at` tools the agent offers while no secret has been
86
+ filled. `screenshot` attaches the viewport's pixels to the result and turns
87
+ on pixel mode, where every action result carries a fresh screenshot; `tap_at`
88
+ taps a point in the latest screenshot (a canvas shape, a map pin, an image
89
+ region, a control in a system sheet), hit-tested against the tree first so a
90
+ listed control is tapped by id. A screen with nothing to tap by id opens with
91
+ a screenshot already attached.
85
92
 
86
93
  ## assert, waitFor, extract: one question
87
94
 
@@ -142,6 +149,14 @@ await expect(screen.getByRole('status')).toHaveText('Created "Atlas" on the Pro
142
149
  The check makes the test model-portable (the path may differ between models,
143
150
  the end state may not), and it is what lets the trace cache record the step.
144
151
 
152
+ State the step writes off screen (a database row, an API read) can land after
153
+ `act` returns; poll the read instead of sleeping:
154
+
155
+ ```ts
156
+ await agent.act('create a test named "AI checkout regression"');
157
+ await expect.poll(() => getTest(workspace).then((row) => row?.title), { timeout: 15_000 }).toBe('AI checkout regression');
158
+ ```
159
+
145
160
  ## What the model sees
146
161
 
147
162
  A redacted snapshot of the screen (roles, names, text, states), a summary of
@@ -150,8 +165,9 @@ every action result after it reports what changed, keyed by node ids that
150
165
  stay stable while an element exists, or the whole screen again when most of
151
166
  it changed, and is read after the action's effect landed. Never raw HTML,
152
167
  cookies, headers, environment
153
- values, or a `Secret`'s value; password fields arrive masked. Pixels only
154
- with `vision`, and only while no secret has been filled. Nothing the model
168
+ values, or a `Secret`'s value; password fields arrive masked. Pixels reach a
169
+ model only through `vision` on a judgment or the act loop's `screenshot` and
170
+ pixel mode, masked, and only while no secret has been filled. Nothing the model
155
171
  returns runs as code or selectors: the runner validates and authorizes every
156
172
  tool call before it executes.
157
173
 
@@ -164,8 +180,9 @@ tool call before it executes.
164
180
  | `extract` | 2 | 30 s |
165
181
  | `waitFor` | up to `agent.maxModelCalls` (25) | 30 s |
166
182
 
167
- - Keep `agent.*` for steps whose path or wording varies; `expect` and
168
- `screen` are free.
183
+ - With the cache on, a passing `act` costs model calls once and replays on
184
+ later runs until the app changes. Budget for the runs where the UI moved,
185
+ not for every run.
169
186
  - Slow providers: raise the test `timeout` and `actionTimeout` (each
170
187
  observation and action inside a step is bounded by it) rather than reading
171
188
  latency as a defect. `STEP_TIMEOUT` and `STEP_BUDGET_EXHAUSTED` are test
@@ -178,14 +195,25 @@ tool call before it executes.
178
195
  Each passing `agent.act` records the actions it performed. The next run
179
196
  replays them with zero model calls and hands back to the live agent the
180
197
  moment the app no longer matches the recording, or when the recorded end
181
- state is not on screen after the replay.
198
+ state is not on screen after the replay. Replayed actions run as a test's
199
+ own steps do, without the agent's settle wait, so a replay is as fast as
200
+ the deterministic equivalent.
182
201
 
183
202
  - On by default (`read-write`), `read-only` in CI, `cache: 'off'` in the
184
203
  config or `--no-cache` on a run to disable. Entries live in `.e2e/cache/`;
185
204
  deleting the directory only slows the next run.
186
- - An entry is written only after a later verification step passes
187
- (`expect`, `locator.waitFor`, `agent.assert`, `agent.waitFor`). An `act`
188
- nothing checks is never replayed.
205
+ - An entry is written only after a later verification step passes: a
206
+ locator or engine `expect` matcher, `locator.waitFor`, `web.waitForURL`,
207
+ `agent.assert`, or `agent.waitFor`. A plain-value `expect`, `expect.poll`,
208
+ `agent.extract`, another `act`, or the attempt passing confirms nothing.
209
+ An `act` nothing checks is never replayed.
210
+ - A replay needs the app on the path the step was recorded on, unless the
211
+ recording opens with a navigation. It re-finds each control by role, name,
212
+ test id, placeholder, and input purpose, and passes on its own only when
213
+ the recorded end path and the controls that appeared during the step are
214
+ back. Otherwise the agent takes over mid-step. The report's
215
+ `step.cache.reason` says why: `no-entry`, `wrong-context`,
216
+ `target-not-found`, `target-ambiguous`, `end-mismatch`, and so on.
189
217
  - A step that records no actions creates no entry and skips the cache's
190
218
  end-state observation.
191
219
  - `e2e init` gitignores `.e2e/cache/`; committing entries is opt-in. Remove
@@ -197,8 +225,8 @@ state is not on screen after the replay.
197
225
  ## Inspect what the model did
198
226
 
199
227
  ```bash
200
- npx --no-install e2e run tests/checkout.e2e.ts --debug # step table, transcripts as artifacts
201
- npx --no-install e2e run tests/checkout.e2e.ts --ai-trace # writes .e2e/ai-trace.json
228
+ npx e2e run tests/checkout.e2e.ts --debug # step table, transcripts as artifacts
229
+ npx e2e run tests/checkout.e2e.ts --ai-trace # writes .e2e/ai-trace.json
202
230
  npx unbox-ai runs .e2e/ai-trace.json # one line per agent step
203
231
  npx unbox-ai summary .e2e/ai-trace.json --run 0 # turns, tokens, tool calls of one step
204
232
  ```
@@ -211,6 +239,32 @@ The trace replaces inline bytes and base64 in SDK image and file message
211
239
  parts with decoded byte counts. URLs and text remain; encoded strings in
212
240
  arbitrary tool result JSON or other fields are preserved.
213
241
 
242
+ ## Make the agent yours
243
+
244
+ The agent in the config is a starting point. The best agent for an app is
245
+ the one that knows its screens, and that comes from iterating on it:
246
+
247
+ 1. **The goal.** A failed step usually means the goal named something the
248
+ screen does not. Reword it with the labels on screen. Check the step's
249
+ transcript with `--debug` to see what the model saw and tried.
250
+ 2. **`context`.** Vocabulary every step needs: what the plans are called,
251
+ what a "workspace" is, which tab holds billing. Set it once on the agent
252
+ or per test with `agentContext`, not in every instruction.
253
+ 3. **`system` on `createAgent`.** How the agent works: how carefully it
254
+ verifies, what it never does, how it treats a modal. A UX reviewer, a
255
+ cautious QA persona, and a fast smoke agent are three `system` prompts on
256
+ the same model.
257
+ 4. **Tools.** A test API the agent may call mid-flow (seed a cart, mint a
258
+ coupon) via `createAgent({ tools })`; see below.
259
+ 5. **The model and its options.** `providerOptions` for reasoning effort,
260
+ or a different model for one persona. `npx e2e run --agent <name>` runs
261
+ the suite as any configured agent, so two candidates can be compared on
262
+ the same tests; every result records which agent ran it.
263
+
264
+ Personas are agents by name under `agents`, pinned with `{ agent }` on a
265
+ test or block, or swept with `--agent buyer,admin`. The trace cache records
266
+ per agent step, so a specialised agent gets the same replay benefit.
267
+
214
268
  ## Beyond the built-in agent
215
269
 
216
270
  - `createAgent({ tools: { seedCart } })` adds AI SDK tools wrapped with
@@ -226,8 +280,10 @@ Full reference: https://e2e.mintlify.app/agents
226
280
 
227
281
  ## In CI
228
282
 
229
- Deterministic tests gate merges; agentic tests are opt-in. Keep them in a
230
- separate config (`e2e.agent.config.ts` with its own `tests` glob and a larger
231
- `timeout`), run them on a schedule or `workflow_dispatch`, and pass the key
232
- the config's model reads (`env: { AI_GATEWAY_API_KEY }` for `gateway()`) from
233
- secrets.
283
+ One suite, one job, on every pull request, agent steps included. Pass the
284
+ key the config's model reads (`env: { AI_GATEWAY_API_KEY }` for `gateway()`)
285
+ from secrets. Commit `.e2e/cache/` so CI replays recorded steps with no
286
+ model call and consults the model only where the app changed; keep every
287
+ `act` followed by a check so the recording is trusted. CI retries once and
288
+ reports a pass-on-retry as flaky, so the report keeps naming the goals that
289
+ need tightening.
@@ -28,7 +28,7 @@ jq '.run.results[] | select(.status != "passed") | .attempts[-1]
28
28
  | `CONFIG_NOT_FOUND`, `CONFIG_AMBIGUOUS` | Wrong `--config` path; both `.ts` and `.mts` present | Fix the path; keep one config file |
29
29
  | `NO_TESTS` | The glob or a positional matched nothing | The message names each positional that matched nothing. Check `tests` in the config and the `.e2e.ts` suffix |
30
30
  | `COLLECTION_ERROR` | `async` describe body, `test.setup` inside `describe`, an option forbidden in a serial group, registration outside collection | Rework the structure per `writing-tests` |
31
- | `APP_UNREACHABLE` | `command` never answered `readyUrl` within `startupTimeout`; a service exited non-zero | Set `command.log` and read it; check the port and `url`; pass the env the app needs through `command.env`; raise `startupTimeout` |
31
+ | `APP_UNREACHABLE` | `command` never answered `readyUrl` within `startupTimeout`; a service exited non-zero | Read the last log lines quoted under the error; set `command.log` if it says to; check the port and `url`; pass the env the app needs through `command.env`; raise `startupTimeout` |
32
32
  | `APP_ALREADY_RUNNING` | Something already serves `url` when the runner wanted to start `command` | Stop it, or set `reuseExisting: true` for local runs |
33
33
  | `APP_URL_REQUIRED` | A navigation on an engine without `url` | Add `url` to `playwright({ ... })` |
34
34
  | `LOCATOR_NOT_FOUND` | Wrong role or name, text not exact, element off screen or inside an iframe, page not open | Read the markup for the accessible name; try `exact: false` or a RegExp; `web.frameLocator` for iframes; `app.open()` first; `--headed` to look |
@@ -36,16 +36,17 @@ jq '.run.results[] | select(.status != "passed") | .attempts[-1]
36
36
  | `ASSERTION_FAILED` | The expectation is wrong, or the state settles later than 5 s; for `agent.assert`, the judgment was false (explanation in the report) | Compare with the actual text in the report or screenshot; `{ timeout }` on the matcher; rewrite the question |
37
37
  | `ACTION_FAILED` | Element not actionable (covered, disabled, detached) or an operation timed out | Wait on the right condition with `expect` first; close overlays; check `actionTimeout` |
38
38
  | `TEST_TIMEOUT` | The attempt exceeded `timeout` (120 s) | Split the test, or raise `timeout` for slow flows and agent steps |
39
- | `MODEL_UNAVAILABLE` | No model: neither `createAgent({ model })` nor `agent.model` holds an AI SDK instance. Reported once for the run under `run.errors`; the run stops | Construct one in the config, e.g. `gateway('openai/gpt-5.4-mini')` from `ai`, and export the key its provider reads (`AI_GATEWAY_API_KEY`) |
39
+ | `MODEL_UNAVAILABLE` | No model: neither `createAgent({ model })` nor `agent.model` holds an AI SDK instance. Reported once for the run under `run.errors`; the run stops | Construct one in the config, e.g. `gateway('openai/gpt-5.6-luna')` from `ai`, and export the key its provider reads (`AI_GATEWAY_API_KEY`) |
40
40
  | `MODEL_PROVIDER_FAILED` | Network, 5xx, rate limit, or no credits after the transport retries | Check the key and the quota; retry; exit code 3 |
41
41
  | `STEP_TIMEOUT`, `STEP_BUDGET_EXHAUSTED` | The goal was too big or ambiguous, or the provider slow | Split the goal, use on-screen wording, add `context`, raise `timeout` and `actionTimeout`, `--debug` to read the transcript |
42
42
  | `CONTEXT_OVERFLOW` | The screen plus the step's history did not fit the model's context window, even after the loop shrank the history and retried once | Lower `agent.maxObservationBytes`, split the step, or pick a model with a larger window |
43
- | `POLICY_DENIED` | Navigation outside `allowedOrigins`; a `Secret` given to a sink that is not a password field; reading a secure field | Add the origin to `allowedOrigins`; fill secrets only into password inputs; assert the outcome instead of the value |
43
+ | `POLICY_DENIED` | Navigation outside `allowedOrigins`; a password `Secret` given to a sink that is not a password field; reading a secure field; `app.screenshot()` after a secret fill | Add the origin to `allowedOrigins`; fill passwords only into password inputs; assert the outcome instead of the value; capture screenshots before filling secrets |
44
44
  | `UNSUPPORTED_CAPABILITY` | A fixture the engine does not contribute (`web` on a device), `schema` or `vision` on `act`, an action the surface lacks | Declare `requires: ['web']`; drop the option; use a supported action |
45
45
  | `SESSION_UNAVAILABLE`, `SESSION_CONTRACT` | `session: 'x'` with no setup saving `x`; a setup that did not save every declared name | Add or fix the `test.setup` |
46
46
  | `ONLY_IN_CI` | `test.only` reached CI | Remove it |
47
47
  | `BROWSER_INSTALL_FAILED`, `LAUNCH_TIMEOUT` | Browser download or launch failed | `npx playwright install chromium --with-deps`; raise `launchTimeout` on slow machines |
48
48
  | `AUTH_CREDENTIAL_UNAVAILABLE` | `credentials.user('x')` for an undeclared name | Add it to `config.credentials` |
49
+ | `SECRET_UNAVAILABLE` | `secrets.get('x')` for an undeclared name | Add it to `config.secrets` or set `E2E_SECRET_X` |
49
50
 
50
51
  ## Tools
51
52
 
@@ -58,8 +59,8 @@ jq '.run.results[] | select(.status != "passed") | .attempts[-1]
58
59
  | `--ai-trace`, then `npx unbox-ai runs .e2e/ai-trace.json` | See exactly what the model was shown and called |
59
60
  | `--video` | Watch the failed attempt; `step.startedAt` minus the video artifact's `startedAt` is the step's offset into it |
60
61
  | `command.log: '.e2e/logs/app.log'` | Read the app's own output when it never becomes ready or errors mid-test |
61
- | `await app.screenshot('before-submit')` | Attach evidence at a chosen point |
62
- | `CI=1 npx --no-install e2e run` | Reproduce CI-only behaviour: `ONLY_IN_CI`, read-only cache, `reuseExisting` ignored |
62
+ | `await app.screenshot('before-submit')` | Attach evidence before any secret is filled; later calls fail with `POLICY_DENIED` |
63
+ | `CI=1 npx e2e run` | Reproduce CI-only behaviour: `ONLY_IN_CI`, read-only cache, `reuseExisting` ignored |
63
64
 
64
65
  ## Flaky tests
65
66
 
@@ -7,10 +7,10 @@ test. It needs a config with a target and an agent that holds a model,
7
7
  nothing else.
8
8
 
9
9
  ```bash
10
- npx --no-install e2e explore # goal: "Explore the app and find bugs"
11
- npx --no-install e2e explore 'Explore checkout like a first-time buyer and report anything off'
12
- npx --no-install e2e explore --target web --max-steps 4 --headed
13
- npx --no-install e2e explore 'Hunt for broken forms' --video
10
+ npx e2e explore # goal: "Explore the app and find bugs"
11
+ npx e2e explore 'Explore checkout like a first-time buyer and report anything off'
12
+ npx e2e explore --target web --max-steps 4 --headed
13
+ npx e2e explore 'Hunt for broken forms' --video
14
14
  ```
15
15
 
16
16
  ## What a run does
@@ -53,9 +53,15 @@ Exit code `0`: steps ran and no `issue` was reported (warnings allowed). Exit
53
53
  code `1`: at least one `issue`, or no step ran and nothing was found (the run
54
54
  is `blocked`). `2` and `3` as for `run`.
55
55
 
56
- The terminal prints each step live and, under the summary, `Explored`,
57
- `Findings`, one row per finding sorted by severity, and `Assessment`.
58
- `.e2e/report.json` has the record under `run.explore`:
56
+ The terminal shows the exploration step by step: each step by its title with
57
+ its duration, actions, and findings, and each finding the moment it is
58
+ reported as `⚑ high issue Title (/path)`. At the end a `Findings` section
59
+ lists every finding, issues first and the most severe first, each with where
60
+ it was seen, its screenshot path, what was expected against what the screen
61
+ showed, and the steps that reach it; then the `Assessment` and the summary
62
+ (`Findings`, `Steps`, `AI`, `Duration`, `Report`). Severity words: critical 5,
63
+ high 4, medium 3, low 2, trivial 1. `.e2e/report.json` has the record under
64
+ `run.explore`:
59
65
 
60
66
  ```json
61
67
  {
@@ -12,14 +12,14 @@ The server ships with `@e2edev/e2e`. `e2e init` offers to register it; by
12
12
  hand:
13
13
 
14
14
  ```bash
15
- claude mcp add e2e -- npx --no-install e2e mcp # Claude Code
15
+ claude mcp add e2e -- npx e2e mcp # Claude Code
16
16
  ```
17
17
 
18
18
  Or declare it in the client's project config (`.mcp.json` for Claude Code,
19
19
  `.cursor/mcp.json` for Cursor, `.vscode/mcp.json` for VS Code):
20
20
 
21
21
  ```json
22
- { "mcpServers": { "e2e": { "command": "npx", "args": ["--no-install", "e2e", "mcp"] } } }
22
+ { "mcpServers": { "e2e": { "command": "npx", "args": ["e2e", "mcp"] } } }
23
23
  ```
24
24
 
25
25
  Flags: `--config <path>` names the default config file, `--target <name>`
@@ -45,7 +45,7 @@ The catalog, per session:
45
45
  | --- | --- |
46
46
  | `observe` | A fresh observation: one node per line as `#id role "name" ...`, plus the current path. |
47
47
  | `tap`, `type`, `press`, `select`, `scroll`, `navigate` | The grammar verbs, exactly as the testing agent gets them. Each reports what changed on screen; `observe` shows the whole screen. A verb the engine cannot honor is not listed and fails with `UNSUPPORTED_CAPABILITY`. |
48
- | `type_secret` | Fills a configured credential's password into a secure field by credential name; the plaintext never reaches the agent. Listed when the config declares `credentials`. |
48
+ | `type_secret` | Fills a configured secret by name: a credential's password into a password field, a `secrets` entry into any editable input; the plaintext never reaches the agent. Listed when the config declares `credentials` or `secrets`. |
49
49
  | `locate` | Tries a semantic locator (`role` + `name`, `text`, `label`, `placeholder`, `testId`, `exact`) and returns how many nodes match, which, and the `screen.*` call to write. |
50
50
  | `screenshot` | The masked pixels as an image, withheld once a secret was filled in the session. |
51
51
  | Project tools | Every `defineTool` passed to `createAgent({ tools })` that applies to the target's platform, under its own name; an engine pack such as `agentDeviceTools` adds `open_app`, `swipe`, `type_text`, `alert`. |
@@ -65,7 +65,7 @@ Resources: `e2e://guide` and `e2e://guide/<topic>` hold this skill.
65
65
  `LOCATOR_NOT_FOUND` or `LOCATOR_AMBIGUOUS`.
66
66
  3. Write `tests/<feature>.e2e.ts` (topic `writing-tests`). Deterministic steps
67
67
  where you saw exact names; `agent.act` where the flow varies.
68
- 4. Run it from the shell: `npx --no-install e2e run tests/<feature>.e2e.ts`,
68
+ 4. Run it from the shell: `npx e2e run tests/<feature>.e2e.ts`,
69
69
  read the failure (topic `debugging`), fix, repeat.
70
70
  5. `close_session` when you are done exploring; an idle session closes on its
71
71
  own after 30 minutes and never outlives 4 hours. To look at another
@@ -3,28 +3,28 @@
3
3
  ## Commands
4
4
 
5
5
  ```bash
6
- npx --no-install e2e run [files...] [options] # run tests
7
- npx --no-install e2e explore [goal] [options] # explore the app toward a goal, no test file (see the explore topic)
8
- npx --no-install e2e list [files...] [options] # print what run would select, without running
9
- npx --no-install e2e init [--yes] # scaffold a project, refresh the agent skill
10
- npx --no-install e2e guide [topic] # print this skill: setup, writing-tests, agent, running, explore, debugging, mcp
11
- npx --no-install e2e cache ls|clear|stats # read or empty the trace cache
12
- npx --no-install e2e mcp [--target <name>] # serve the project to a coding agent over MCP (topic mcp)
13
- npx --no-install e2e telemetry [disable|enable] # anonymous usage telemetry: status, or the switch
6
+ npx e2e run [files...] [options] # run tests
7
+ npx e2e explore [goal] [options] # explore the app toward a goal, no test file (see the explore topic)
8
+ npx e2e list [files...] [options] # print what run would select, without running
9
+ npx e2e init [--yes] # scaffold a project, refresh the agent skill
10
+ npx e2e guide [topic] # print this skill: setup, writing-tests, agent, running, explore, debugging, mcp
11
+ npx e2e cache ls|clear|stats # read or empty the trace cache
12
+ npx e2e mcp [--target <name>] # serve the project to a coding agent over MCP (topic mcp)
13
+ npx e2e telemetry [disable|enable] # anonymous usage telemetry: status, or the switch
14
14
  ```
15
15
 
16
16
  `run` flags:
17
17
 
18
18
  | Flag | Effect |
19
19
  | --- | --- |
20
- | `[files...]` | Files, directories, or quoted globs relative to the project root. They narrow the config `tests` glob, never bypass it. |
20
+ | `[files...]` | Files, directories, or quoted globs relative to the project root, or a bare file name (`signup.e2e.ts`, `signup`, `agent/signup.e2e.ts` all select `tests/agent/signup.e2e.ts`). They narrow the config `tests` glob, never bypass it. |
21
21
  | `--config <path>` | Explicit config file. Default: `e2e.config.ts` or `.mts` found upward from the working directory. |
22
22
  | `--target <ids>` | Comma-separated target names. Only selected targets start app commands and services; unknown names fail before startup. |
23
23
  | `--tag <tag>` | Repeatable tag filter; `--tag-mode all` requires every tag. |
24
24
  | `--headed` | Visible browser or simulator when the engine supports it. |
25
25
  | `--agent <names>` | Run unpinned tests as other configured agents (`agents.<name>`), comma-separated or repeated; several names run each such test once per agent. Default is `agents.default`. |
26
26
  | `--workers <n>`, `--retries <n>` | Override the resolved values. |
27
- | `--reporter <ids>` | `list`, `json`, `junit`, comma-separated. `json` cannot combine with `list`. |
27
+ | `--reporter <ids>` | `list`, `json`, `junit`, `markdown`, comma-separated. `json` cannot combine with `list`. |
28
28
  | `--artifacts <dir>` | Artifact root, default `.e2e/artifacts`. |
29
29
  | `--no-cache` | Run with the trace cache off. |
30
30
  | `--pass-with-no-tests` | Exit 0 when nothing matches instead of `NO_TESTS`. |
@@ -33,10 +33,11 @@ npx --no-install e2e telemetry [disable|enable] # anonymous usage telemetry: sta
33
33
  | `--video` | Record every attempt (WebM on a browser engine, MP4 on a device engine) under its artifact directory; the failure recap names the file. Fails with `UNSUPPORTED_ARTIFACT` when the engine cannot record. |
34
34
 
35
35
  ```bash
36
- npx --no-install e2e run tests/signup.e2e.ts
37
- npx --no-install e2e run tests/agent --tag smoke
38
- npx --no-install e2e run 'tests/**/*.smoke.e2e.ts' --target chromium --workers 1 --retries 0
39
- CI=1 npx --no-install e2e run # reproduce the CI defaults locally
36
+ npx e2e run tests/signup.e2e.ts
37
+ npx e2e run signup.e2e.ts # the same file by name, from any directory the config globs cover
38
+ npx e2e run tests/agent --tag smoke
39
+ npx e2e run 'tests/**/*.smoke.e2e.ts' --target chromium --workers 1 --retries 0
40
+ CI=1 npx e2e run # reproduce the CI defaults locally
40
41
  ```
41
42
 
42
43
  `list` takes the same files and the selection flags (`--config`, `--target`,
@@ -46,12 +47,15 @@ app, an engine, or a worker. `--reporter json` prints `{ "pairs": [...] }`.
46
47
  Use it to check a filter before a run.
47
48
 
48
49
  ```bash
49
- npx --no-install e2e list --tag smoke
50
- npx --no-install e2e list tests/signup.e2e.ts --reporter json
50
+ npx e2e list --tag smoke
51
+ npx e2e list tests/signup.e2e.ts --reporter json
51
52
  ```
52
53
 
53
54
  A `package.json` script keeps it short: `"test:e2e": "e2e run"`, then
54
- `pnpm test:e2e tests/signup.e2e.ts`.
55
+ `pnpm test:e2e tests/signup.e2e.ts`. pnpm forwards a `--` separator literally,
56
+ so `pnpm test:e2e -- --headed` reaches e2e as `run -- --headed` and is
57
+ rejected with exit 2 rather than run headless; write `pnpm test:e2e --headed`
58
+ or `pnpm exec e2e run --headed` instead.
55
59
 
56
60
  ## The trace cache
57
61
 
@@ -80,6 +84,12 @@ recorded flow is stale — `--no-cache` only skips the cache for one run.
80
84
  `artifacts[]`, and `error`.
81
85
  - `junit`: `.e2e/junit.xml` beside the report, for CI test summaries.
82
86
  Combine it with the terminal output: `--reporter list,junit`.
87
+ - `markdown`: `.e2e/summary.md` beside the report: the counts and what the
88
+ run spent, a block per failed or flaky test with its error, the step it went
89
+ wrong at, the agent's explanation, and evidence paths, a row per file, every
90
+ test folded; or an exploration's findings and assessment. Paste it into a
91
+ pull request or a handoff rather than retelling the result:
92
+ `--reporter list,markdown`.
83
93
  - `json`: the report document on stdout.
84
94
  - `github()` from `@e2edev/github`: on GitHub Actions, one pull request comment per
85
95
  run (edited on rerun) and the job summary; needs `pull-requests: write` and
@@ -133,7 +143,7 @@ jobs:
133
143
  cache: pnpm
134
144
  - run: pnpm install --frozen-lockfile
135
145
  - run: npx playwright install chromium --with-deps
136
- - run: npx --no-install e2e run --reporter list,junit
146
+ - run: npx e2e run --reporter list,junit
137
147
  env:
138
148
  E2E_USER_ADMIN_USERNAME: ${{ secrets.E2E_USER_ADMIN_USERNAME }}
139
149
  E2E_USER_ADMIN_PASSWORD: ${{ secrets.E2E_USER_ADMIN_PASSWORD }}
@@ -156,6 +166,7 @@ jobs:
156
166
  launch timeout.
157
167
  - Start the app through the engine's `command`; the runner tears it down on
158
168
  every exit path.
159
- - Agentic suites: a separate config, run on `schedule` or
160
- `workflow_dispatch`, the key the config's model reads (`AI_GATEWAY_API_KEY`
161
- for `gateway()` from `ai`) as a secret, never a required check.
169
+ - Agent steps run in the same job as everything else. Pass the key the
170
+ config's model reads (`AI_GATEWAY_API_KEY` for `gateway()` from `ai`) as
171
+ a secret in the run step's `env`, and commit `.e2e/cache/` so recorded
172
+ steps replay in CI with no model call.
@@ -7,9 +7,17 @@
7
7
  whatever the nearest `package.json` `type` says, so a CommonJS package (a
8
8
  Next.js app, for instance) needs no change. Write them with `import`, never
9
9
  `require` or `module.exports`.
10
- - For browser tests, `@e2edev/playwright`. Missing browsers download when the
11
- engine first boots. In CI install them up front:
12
- `npx playwright install chromium --with-deps`.
10
+ - For browser tests, `@e2edev/playwright` plus `playwright` (`>=1.63.0 <2`),
11
+ a peer dependency the engine does not install itself: an app that already
12
+ depends on Playwright keeps its version and its browser cache. A version
13
+ outside the range may be rejected by the package manager as an unmet peer
14
+ (npm's `ERESOLVE`); upgrade `playwright` within the range.
15
+ Missing browsers download when the engine first boots. In CI install them
16
+ up front: `npx playwright install chromium --with-deps`.
17
+ - For mobile tests, `@e2edev/agent-device` plus `agent-device` (`0.21.x`),
18
+ likewise a peer dependency: a project that already drives devices with the
19
+ agent-device CLI keeps its version. The range pins the tested minor because
20
+ agent-device minors break; it moves with each engine release.
13
21
 
14
22
  ## Scaffold
15
23
 
@@ -18,8 +26,8 @@ npx @e2edev/e2e@beta init # npm
18
26
  pnpm dlx @e2edev/e2e@beta init # pnpm
19
27
  ```
20
28
 
21
- When `@e2edev/e2e` is already installed, run `npx --no-install e2e init`
22
- instead, so the installed version scaffolds.
29
+ When `@e2edev/e2e` is already installed, run `npx e2e init` instead, so the
30
+ installed version scaffolds.
23
31
 
24
32
  The wizard asks for the engine (Web with Playwright by default; Mobile with
25
33
  agent-device and None are the alternatives), which model gateway agent steps
@@ -42,7 +50,7 @@ project.
42
50
  Without the wizard:
43
51
 
44
52
  ```bash
45
- npm install --save-dev @e2edev/e2e@beta @e2edev/playwright@beta ai@^7
53
+ npm install --save-dev @e2edev/e2e@beta @e2edev/playwright@beta playwright ai@^7
46
54
  ```
47
55
 
48
56
  `ai` (the Vercel AI SDK, v7) is only needed for `agent.*` steps.
@@ -69,10 +77,10 @@ export default {
69
77
  }),
70
78
  },
71
79
  ],
72
- // Only for agent.* steps. The model is an AI SDK instance; gateway() from 'ai' reads AI_GATEWAY_API_KEY.
80
+ // The model behind every agent.* step: an AI SDK instance; gateway() from 'ai' reads AI_GATEWAY_API_KEY.
73
81
  agents: {
74
82
  default: createAgent({
75
- model: gateway('openai/gpt-5.4-mini'),
83
+ model: gateway('openai/gpt-5.6-luna'),
76
84
  system: 'You are a thorough QA agent. Verify every outcome on screen.',
77
85
  }),
78
86
  },
@@ -91,10 +99,11 @@ export default {
91
99
  | `assertionTimeout` | `5000` | `expect` polling window. |
92
100
  | `retries` | `0`, `1` in CI | 0 to 10. |
93
101
  | `workers` | half the cores, `1` in CI | Test files run in parallel across workers, at most the `workers` the engine declares per target (a device target: one per device). |
94
- | `reporters` | `['list']` | `list`, `json`, `junit`, and reporter objects (`{ name, onEvent?, onRunFinished? }`) that receive the finished run. `json` excludes `list`; `--reporter` keeps the objects. |
102
+ | `reporters` | `['list']` | `list`, `json`, `junit`, `markdown`, and reporter objects (`{ name, onEvent?, onRunFinished? }`) that receive the finished run. `json` excludes `list`; `--reporter` keeps the objects. |
95
103
  | `cache` | `'read-write'`, `'read-only'` in CI | The trace cache for `agent.act`; `'off'` disables it. |
96
- | `agents` | `{ default: built-in }` | Agents by name. `default` is what tests run with; `e2e run --agent <name>` runs with another. Each entry is `createAgent(...)`, an options block `{ model, context, visionModel, maxSteps, maxModelCalls, vision, providerOptions }`, or a custom `StepExecutor`. `model` is an AI SDK instance; without one anywhere, acquiring `agent` is `MODEL_UNAVAILABLE`. |
104
+ | `agents` | `{ default: built-in }` | Agents by name. `default` is what tests run with; `e2e run --agent <name>` runs with another. Each entry is `createAgent(...)`, an options block `{ model, context, maxSteps, maxModelCalls, vision, providerOptions }`, or a custom `StepExecutor`. `model` is an AI SDK instance; without one anywhere, acquiring `agent` is `MODEL_UNAVAILABLE`. |
97
105
  | `credentials` | `{}` | Named `{ username, password, allowedOrigins? }` entries; `password` may be a function returning the value. |
106
+ | `secrets` | `{}` | Named values the model never sees (API keys, tokens): a string, a function returning the value, or `{ value, allowedOrigins? }`. A name cannot also be a credential. |
98
107
  | `screen.testIdAttribute` | `'data-testid'` | Attribute read by `getByTestId`. |
99
108
  | `artifacts` | `['screenshot', 'trace']` | Kinds to keep (`screenshot`, `trace`, and the opt-in `video`), or `{ kinds, store, video }`; `video: { retain: 'on-failure' }` keeps only the recordings of attempts that did not pass. |
100
109
  | `projectId` | the package name | Report and cache identity. |
@@ -106,9 +115,9 @@ process, and identity. `playwright()` accepts:
106
115
 
107
116
  | Option | Meaning |
108
117
  | --- | --- |
109
- | `url` | Base URL for `app.open()` and relative navigation. A missing scheme becomes `https://`, or `http://` for a loopback host. Required once a test navigates. |
110
- | `command` | The process that serves `url`. See below. |
111
- | `readyUrl` | Readiness probe when it differs from `url`. |
118
+ | `url` | Base URL for `app.open()` and relative navigation. A missing scheme becomes `https://`, or `http://` for a loopback host. Required once a test navigates. Port `0` on `127.0.0.1` or `[::1]` asks the run for a free port. |
119
+ | `command` | The process that serves `url`. `{port}` in `args` and `env` expands to the port of `url`. See below. |
120
+ | `readyUrl` | Readiness probe when it differs from `url`. `{port}` expands here too. |
112
121
  | `services` | Dependency processes started before `command`, in order. |
113
122
  | `allowedOrigins` | Origins tests and the agent may navigate to. Default: the origin of `url`. |
114
123
  | `environment` | `'test'`, `'staging'`, `'production'`. Inferred from the host; a label for the report and the cache key. |
@@ -181,6 +190,15 @@ How it behaves:
181
190
  then runs their `teardown` commands in reverse.
182
191
  - `executable` is resolved on `PATH` and never shell-interpreted. Point it at
183
192
  the server itself rather than at a wrapper script.
193
+ - `url: 'http://127.0.0.1:0'` (or `[::1]:0`, never `localhost:0`) asks the
194
+ run for a free port, so two checkouts can run at once. The command must take
195
+ it through `{port}` in `args` or `env`; the token also expands in `readyUrl`
196
+ and the services:
197
+ `command: { executable: 'pnpm', args: ['dev', '--port', '{port}'], env: { PORT: '{port}' } }`.
198
+ Tests read the allocated URL from `app.baseUrl`; the cache identity keeps
199
+ the declared `:0`. Services keep their own ports. A port another process
200
+ grabs between allocation and spawn fails the start with `APP_UNREACHABLE`;
201
+ rerun.
184
202
 
185
203
  To test an app started elsewhere, point `url` at it and start it yourself,
186
204
  or read the address from the environment:
@@ -193,6 +211,7 @@ or read the address from the environment:
193
211
  | --- | --- |
194
212
  | `AI_GATEWAY_API_KEY`, `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, ... | Read by the provider package the config constructs the model with (`gateway()` from `ai`, `openrouter()`, `openai()`), not by the runner. The runner reads no model variable; the model is always an AI SDK instance in the config. |
195
213
  | `E2E_USER_<NAME>_USERNAME`, `E2E_USER_<NAME>_PASSWORD` | Override `credentials.<name>`. `<NAME>` is the credential name uppercased, other characters as `_`. |
214
+ | `E2E_SECRET_<NAME>` | Overrides the value of `secrets.<name>`, same uppercasing rule. |
196
215
  | `CI` | Turns on CI defaults: `retries: 1`, `workers: 1`, `test.only` rejected, cache read-only, `reuseExisting` ignored. |
197
216
  | `E2E_TELEMETRY_DISABLED`, `DO_NOT_TRACK` | Turn anonymous usage telemetry off; `e2e telemetry disable` saves the same choice. `E2E_TELEMETRY_DEBUG=1` prints each event instead of sending it. |
198
217
 
@@ -227,6 +246,11 @@ export default {
227
246
  single `device` runs one worker whatever `workers` says; a list,
228
247
  `device: ['iPhone 17', 'iPhone 17 Pro']`, is an explicit pool. Devices boot
229
248
  in `prepare`, before the run's clock starts.
249
+ - A test's steps never wait for the screen to settle; `expect` verifies the
250
+ outcome. Only a control that appeared or moved with the previous action
251
+ waits out the `transition` budget (default 500 ms) before it is acted on.
252
+ Agent actions settle for `settle` ms (default 150) before the agent
253
+ observes again; `settle: false` skips that wait.
230
254
  - Cancelled device commands keep running; the next attempt waits for them.
231
255
  Raw screenshot files are removed when capture finishes, including after
232
256
  cancellation.
@@ -244,8 +268,10 @@ export default {
244
268
 
245
269
  ## Done when
246
270
 
247
- - `npx --no-install e2e run tests/example.e2e.ts` passes against the app.
271
+ - `npx e2e run tests/example.e2e.ts` passes against the app.
248
272
  - `package.json` has a script such as `"test:e2e": "e2e run"`.
249
273
  - `.gitignore` lists the `.e2e/` outputs (init adds them). Committing
250
- `.e2e/cache/` is opt-in: remove that line to share `agent.act` replays.
251
- - CI runs the deterministic suite on pull requests; see `running`.
274
+ `.e2e/cache/` is opt-in: remove that line so CI and teammates replay
275
+ `agent.act` steps instead of re-running the model.
276
+ - CI runs the whole suite on pull requests, agent steps included; see
277
+ `running`.