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,10 +1,33 @@
1
1
  ---
2
2
  title: From Playwright
3
3
  description: Run e2e alongside Playwright and migrate tests one file at a time.
4
+ icon: "/images/icons/playwright.svg"
4
5
  ---
5
6
 
6
- `@e2e-dev/web` uses your installed Playwright when its version satisfies
7
- the engine's peer dependency (`>=1.63.0 <2`). Both runners can stay in one
7
+ import { SetupPrompt } from '../snippets/setup-prompt-card.jsx';
8
+
9
+ export const migrationPrompt = `Migrate this project's Playwright tests to e2e, one spec at a time. Docs: \`https://e2e.tester.army/docs/migrate/playwright.md\`
10
+
11
+ 1. Read the migration guide above. It maps every Playwright config key, API, and CLI flag to e2e and marks what e2e does not have yet.
12
+ 2. If the project has no e2e config (\`e2e.config.ts\` or \`e2e.config.mts\`), run \`npx e2e init --yes\` (or the pnpm or bun equivalent). It writes a config and an example test, and installs nothing yet.
13
+ 3. Read the e2e skill (\`npx e2e guide\` prints it, \`npx e2e guide <topic>\` prints a topic) and follow it for every step below.
14
+ 4. If you ran init, ask me which model to use for agent steps: a ChatGPT, GitHub Copilot, OpenCode Console, or SuperGrok subscription, an API key, or a local model. Configure it with its provider package. If it needs a sign-in, give me the \`npx e2e login\` command and wait until I confirm.
15
+ 5. Port \`playwright.config.ts\` into the e2e config with the guide's Config tables. \`baseURL\` and \`webServer\` become the target's \`app\`, \`projects\` become \`targets\`, and browser options under \`use\` (viewport, locale, testIdAttribute) become \`web(...)\` options; \`trace\`, \`video\`, and the timeouts are top-level keys. Keep \`@playwright/test\` installed and the two file patterns apart.
16
+ 6. Install dependencies.
17
+ 7. List the Playwright specs and ask me which one to move first. Suggest a flow whose steps change often, such as a wizard or a checkout.
18
+ 8. Rewrite that spec as a \`.e2e.ts\` file under \`tests/\` (e2e runs \`tests/**/*.e2e.ts\` by default). Map each \`page\` call to \`screen\`, \`app\`, or \`browser\` with the guide. String names in e2e queries match the whole text, case-sensitive, so add \`{ exact: false }\` or a RegExp where the Playwright query matched a fragment. Where the guide marks a feature missing, use the workaround it gives, or leave that test in Playwright and tell me why.
19
+ 9. Keep navigation and the final assertions deterministic, with \`app.open()\` and locator assertions. Where a run of scripted interactions breaks often, propose an \`agent.act\` goal for it instead.
20
+ 10. Run the new file with \`npx e2e run <file>\` and fix it until it passes. Keep the Playwright spec until I confirm it can go.
21
+ 11. Summarize which tests moved, which stayed in Playwright, and why.`;
22
+
23
+ <SetupPrompt>
24
+
25
+ <Prompt description={migrationPrompt} children={migrationPrompt} actions={["copy", "cursor"]} />
26
+
27
+ </SetupPrompt>
28
+
29
+ `@e2e-dev/web` brings its own pinned `playwright-core`, so your
30
+ `@playwright/test` version is yours to keep. Both runners can stay in one
8
31
  project. e2e reads `e2e.config.ts` and finds `tests/**/*.e2e.ts` by default,
9
32
  so keep the two file patterns apart.
10
33
 
@@ -27,7 +50,7 @@ A Playwright key the runner recognizes (`testDir`, `baseURL`, `webServer`,
27
50
  | Playwright | e2e | Status |
28
51
  | --- | --- | --- |
29
52
  | `use.baseURL` | `app: { url }` on the target | renamed |
30
- | `webServer` | `app: { url, command }` on the target, see [Starting your app](/starting-your-app) | renamed |
53
+ | `webServer` | `app: { url, command }` on the target, see [Starting your app](/web#starting-your-app) | renamed |
31
54
  | `webServer` array | `app: { url, command }` on each target; targets declaring the same command share one process. Other dependency processes are not started by this version | missing |
32
55
  | `webServer.reuseExistingServer` | `app.command: { reuseExisting: true }`; CI ignores it | renamed |
33
56
  | `globalSetup`, `globalTeardown` | not yet; `test.setup` for sign-in, hooks and fixtures for setup code | missing |
@@ -59,7 +82,8 @@ A Playwright key the runner recognizes (`testDir`, `baseURL`, `webServer`,
59
82
  | `use.trace` | `trace` with the same modes (`'off'`, `'on'`, `'retain-on-failure'`, `'on-first-retry'`, `'on-all-retries'`), at the top of the config, per target, `test('x', { trace }, ...)`, or `--trace [mode]` for one run; the default differs, see [below](#defaults-that-differ) | same |
60
83
  | `use.video` | `video` with the same modes as `trace`, at the top of the config, per target in place of a project, `test('x', { video }, ...)` in place of `test.use`, or `--video [mode]` for one run | same |
61
84
  | `use.headless: false` | `--headed` on the CLI | renamed |
62
- | `use.locale`, `timezoneId`, `colorScheme`, `geolocation`, `permissions` | not yet | missing |
85
+ | `use.locale`, `use.timezoneId` | `web({ locale, timezoneId })` | renamed |
86
+ | `colorScheme`, `geolocation`, `permissions` | not yet | missing |
63
87
  | `use.userAgent` | `web({ userAgent })` | renamed |
64
88
  | `devices['iPhone 15']`, `deviceScaleFactor`, `isMobile`, `hasTouch` | not yet; `web({ viewport })` sets the size only | missing |
65
89
  | `ignoreHTTPSErrors`, `bypassCSP`, `offline`, `launchOptions`, `proxy` | not yet | missing |
@@ -139,7 +163,9 @@ what to do today.
139
163
  | `fill(value)` | the same; also takes a `Secret` | same |
140
164
  | `pressSequentially(text, { delay })` | the same; keystrokes through the platform keyboard, a `Secret` is refused | same |
141
165
  | `press(key)` | the same; modifiers join with `+`, see the [key grammar](/reference/engine#keys) | same |
142
- | `clear()`, `check()`, `uncheck()`, `focus()`, `hover()`, `selectOption()`, `setInputFiles()`, `dragTo()` | the same | same |
166
+ | `clear()`, `focus()`, `hover()`, `setInputFiles()`, `dragTo()` | the same | same |
167
+ | `check()`, `uncheck()` | the same, except for a control the click removes or navigates away from, such as a radio the app replaces with its selected view: the click landed, so the action passes, even when the app renders an unchecked control in its place. Playwright fails it because it cannot read the state back. Assert on what replaced the control | by design |
168
+ | `selectOption('pro')` | `selectOption('Pro')`: a string matches the option's label only, where Playwright matches its value or its label; `selectOption({ value: 'pro' })` for the value | by design |
143
169
  | `selectOption(['a', 'b'])` | not yet; one option per call. An array is `INVALID_ARGUMENT` | missing |
144
170
  | `setInputFiles({ name, buffer })` | not yet; paths only, resolved from the project root | missing |
145
171
  | `scrollIntoViewIfNeeded()` | `scrollIntoView()` | renamed |
@@ -149,8 +175,7 @@ what to do today.
149
175
  | `isChecked()` | the same on a checkbox, radio, or `aria-checked` node; `false` on any other node, where Playwright throws | by design |
150
176
  | `isHidden()`, `isDisabled()` | the same | same |
151
177
  | `isEditable()` | not yet | missing |
152
- | `waitFor({ state: 'visible' })`, `'hidden'` | the same | same |
153
- | `waitFor({ state: 'attached' })`, `'detached'` | not yet; `expect(locator).toBeAttached()` and `.not.toBeAttached()` | missing |
178
+ | `waitFor({ state: 'visible' })`, `'hidden'`, `'attached'`, `'detached'` | the same | same |
154
179
  | `locator.evaluate(fn)`, `evaluateAll(fn)` | not yet; `browser.evaluate(fn, selector)` with a CSS selector argument | missing |
155
180
  | `locator.screenshot()` | not yet; `app.screenshot(label)` captures the viewport | missing |
156
181
  | `innerHTML()`, `dispatchEvent()`, `selectText()`, `blur()`, `highlight()` | not yet; `browser.evaluate` | missing |
@@ -173,14 +198,14 @@ what to do today.
173
198
  | `toHaveText(['a', 'b'])`, `toContainText([...])` on several matches | the same: `toHaveText` needs the count to match, `toContainText` an ordered subsequence | same |
174
199
  | `toBeChecked({ checked: false })`, `toBeEnabled({ enabled })`, `toBeVisible({ visible })`, `toBeAttached({ attached })` | the same | same |
175
200
  | `{ ignoreCase }` on `toHaveText()`, `toContainText()`, `toHaveAccessibleName()`, `toHaveAttribute(name, value)` | the same | same |
176
- | `toBeChecked({ indeterminate })`, `{ useInnerText }`, `toHaveURL(url, { ignoreCase })` | not yet; `INVALID_ARGUMENT`, so the matcher never passes on its default | missing |
201
+ | `toBeChecked({ indeterminate })`, `{ useInnerText }` | not yet; `INVALID_ARGUMENT`, so the matcher never passes on its default | missing |
177
202
  | `toHaveAttribute('aria-selected', 'true')`, `'aria-expanded'` | `toBeSelected()`, `toBeExpanded()` | renamed |
178
203
  | a positive matcher on several matches | `LOCATOR_AMBIGUOUS` at once; narrow with `filter`, `first`, or `{ visible: true }` | by design |
179
204
  | `toBeAttached()` | the same; passes on a hidden match too | same |
180
205
  | `toBeEmpty()`, `toBeInViewport()`, `toBeEditable()`, `toHaveRole()`, `toHaveAccessibleDescription()` | not yet | missing |
181
206
  | `expect(locator).toHaveClass(cls)` | `expect(browser).toHaveClass(locator, cls)`; a DOM fact is asserted through `browser`; reads the `class` attribute, so `''` matches `class=""` and not a missing attribute | web only |
182
207
  | `toHaveCSS()`, `toHaveId()`, `toHaveJSProperty()`, `toContainClass()`, `toHaveValues()` | not yet; `browser.evaluate` plus a value matcher | missing |
183
- | `expect(page).toHaveURL()`, `toHaveTitle()` | `expect(browser).toHaveURL()`, `toHaveTitle()` | web only |
208
+ | `expect(page).toHaveURL()`, `toHaveTitle()` | `expect(browser).toHaveURL()`, `toHaveTitle()`; `toHaveURL` takes `ignoreCase` | web only |
184
209
  | `toHaveScreenshot()`, `toMatchSnapshot()` | not yet; `agent.assert('...', { vision: true })` is a judgment, not a pixel diff | missing |
185
210
  | `toMatchAriaSnapshot()` | not yet | missing |
186
211
  | `expect(value).toBe()`, `toEqual()`, `toMatch()`, `toBeTruthy()`, `toBeCloseTo()` and the rest | the same, except `toContain`, below, see [value matchers](/reference/expect#value-matchers) | same |
@@ -219,9 +244,9 @@ what to do today.
219
244
  | Playwright | e2e | Status |
220
245
  | --- | --- | --- |
221
246
  | `page.route(pattern, handler)` | `browser.route(pattern, handler)`; a string or `RegExp` pattern, no predicate function; the handler decides exactly once | web only |
222
- | `route.fulfill({ json, body, status, headers })` | the same; `json` or `body`, never both | web only |
223
- | `route.fulfill({ path, contentType })`, `route.fetch()` | not yet; read the file in the test and pass `body` | missing |
224
- | `route.continue(overrides)`, `route.abort(code)` | `continue()` and `abort()` take no arguments | missing |
247
+ | `route.fulfill({ json, body, path, status, headers, contentType })` | the same; one of `json`, `body`, or `path`, and `path` is relative to the project root | web only |
248
+ | `route.continue({ url, method, headers, postData })`, `route.fallback()` | the same; a `url` goes through the navigation rule, and `fallback()` takes no overrides | web only |
249
+ | `route.fetch()`, `route.abort(code)`, `route.fallback(overrides)` | not yet; `abort()` takes no error code | missing |
225
250
  | `page.unroute()` | `browser.unroute()` | web only |
226
251
  | `page.waitForResponse(pattern)` | `browser.waitForResponse(pattern)`: a string or `RegExp`, no predicate function; `url`, `status`, `headers`, `json()`, `text()` | web only |
227
252
  | `response.ok()`, `response.body()`, `response.request()` | not yet; `status` and `text()` | missing |
@@ -232,10 +257,11 @@ what to do today.
232
257
  | `page.waitForEvent('popup')`, `context.on('page')` | not yet; a window the app opens is not driven | missing |
233
258
  | `page.on('console')`, `page.on('pageerror')` | not yet; assert on what the page shows | missing |
234
259
  | `page.evaluate(fn, arg)` | `browser.evaluate(fn, arg)`; JSON in and out, no handles | web only |
235
- | `page.exposeFunction()`, `addInitScript()`, `addStyleTag()`, `setExtraHTTPHeaders()`, `emulateMedia()`, `clock` | not yet; `browser.evaluate` appends a `<style>` once the page is open, and returns `null` since its result must be JSON | missing |
260
+ | `page.addInitScript(script, arg)`, `context.addInitScript()` | `browser.addInitScript(script, arg)`, or `web({ initScripts })` for every test; pass `{ content }` as a plain string | web only |
261
+ | `page.exposeFunction()`, `addStyleTag()`, `setExtraHTTPHeaders()`, `emulateMedia()`, `clock` | not yet; `browser.evaluate` appends a `<style>` once the page is open, and returns `null` since its result must be JSON | missing |
236
262
  | `context.addCookies()`, `context.cookies()` | `browser.setCookies()`, `browser.cookies()` | web only |
237
263
  | `context.clearCookies()` | not yet; `app.clearState()` also clears other persisted state and recreates the context | missing |
238
- | `request` fixture | Node `fetch`, see [Testing APIs](/api-testing) | by design |
264
+ | `request` fixture | Node `fetch`, see [API](/api-testing) | by design |
239
265
  | `context.storageState({ path })` | not yet; sessions are per run and encrypted, see [By design](#by-design) | by design |
240
266
 
241
267
  ### Test structure
@@ -284,9 +310,9 @@ what to do today.
284
310
  | `--update-snapshots` | no equivalent; there is no pixel assertion | missing |
285
311
  | `--list` | `npx e2e list`, with the same selection flags | renamed |
286
312
  | `npx playwright codegen` | `npx e2e explore` walks the app from a goal and reports what it found; `npx e2e mcp` gives a coding agent a live session so it writes the test while driving the app, see [Coding agents](/coding-agents) | renamed |
287
- | `npx playwright show-trace` | the same, on `trace/<name>.zip` under the attempt's artifact directory | same |
313
+ | `npx playwright show-trace` | `npx playwright-core@1.63.0 show-trace` (`pnpm dlx` under pnpm), on `trace/<name>.zip` under the attempt's artifact directory | renamed |
288
314
  | `npx playwright show-report` | not yet; `.e2e/summary.md` from the `markdown` reporter | missing |
289
- | `npx playwright install` | still works; the engine also installs a missing browser once per run, before the run's clock starts | same |
315
+ | `npx playwright install` | `npx @e2e-dev/web install`, for the Playwright version the engine pins; the engine also installs a missing browser once per run, before the run's clock starts | renamed |
290
316
 
291
317
  ## Page objects
292
318
 
@@ -421,8 +447,8 @@ Start with a flow whose interaction sequence changes often, such as a wizard
421
447
  or checkout; stable Playwright tests can stay where they are.
422
448
 
423
449
  <CardGroup cols={2}>
424
- <Card title="Writing tests" icon="pen-to-square" href="/writing-tests">
425
- Goals, checks, extraction, and where a deterministic call still belongs.
450
+ <Card title="Core concepts" icon="pen-to-square" href="/core-concepts">
451
+ Goals, assertions, and locators instead of scripted selectors.
426
452
  </Card>
427
453
  <Card title="Web engine reference" icon="masks-theater" href="/reference/web">
428
454
  Every option and every `browser` method.
@@ -1,8 +1,32 @@
1
1
  ---
2
2
  title: From Selenium
3
3
  description: Rewrite Selenium flows in TypeScript with locators, retrying assertions, and agent goals.
4
+ icon: "/images/icons/selenium.svg"
4
5
  ---
5
6
 
7
+ import { SetupPrompt } from '../snippets/setup-prompt-card.jsx';
8
+
9
+ export const migrationPrompt = `Migrate this project's Selenium tests to e2e, one flow at a time. Docs: \`https://e2e.tester.army/docs/migrate/selenium.md\`
10
+
11
+ 1. Read the migration guide above. It maps Selenium config and WebDriver calls to e2e.
12
+ 2. e2e tests are TypeScript. If the Selenium suite is Java, Python, or another language, ask me where the new test project should live, for example an \`e2e/\` folder with its own \`package.json\`.
13
+ 3. If that project has no e2e config (\`e2e.config.ts\` or \`e2e.config.mts\`), run \`npx e2e init --yes\` (or the pnpm or bun equivalent) there. It writes a config and an example test, and installs nothing yet.
14
+ 4. Read the e2e skill (\`npx e2e guide\` prints it, \`npx e2e guide <topic>\` prints a topic) and follow it for every step below.
15
+ 5. If you ran init, ask me which model to use for agent steps: a ChatGPT, GitHub Copilot, OpenCode Console, or SuperGrok subscription, an API key, or a local model. Configure it with its provider package. If it needs a sign-in, give me the \`npx e2e login\` command and wait until I confirm.
16
+ 6. Set the target's \`app\` to the URL every \`driver.get\` used, and the command that starts the app if the suite starts it. Move passwords and tokens into \`credentials\` or \`secrets\`.
17
+ 7. Install dependencies.
18
+ 8. List the page objects and the flows that use them, and ask me which to move first. Suggest one page object and its flows.
19
+ 9. Rewrite those flows as \`.e2e.ts\` files under \`tests/\` (e2e runs \`tests/**/*.e2e.ts\` by default). Prefer role, label, and test id queries on \`screen\` over \`By.id\`, CSS, and XPath. Replace explicit waits, implicit waits, and \`Thread.sleep\` with locator assertions, which retry. \`findElement\` takes the first match and e2e fails on several, so narrow those queries. Turn the page object's setup code into a fixture.
20
+ 10. Keep navigation and the final assertions deterministic, with \`app.open()\` and locator assertions. Where a run of scripted interactions breaks often, propose an \`agent.act\` goal for it instead.
21
+ 11. Run the new files with \`npx e2e run <file>\` and fix them until they pass. Keep the Selenium tests until I confirm they can go.
22
+ 12. Summarize which flows moved, which stayed in Selenium, and why.`;
23
+
24
+ <SetupPrompt>
25
+
26
+ <Prompt description={migrationPrompt} children={migrationPrompt} actions={["copy", "cursor"]} />
27
+
28
+ </SetupPrompt>
29
+
6
30
  e2e tests use TypeScript, locators based on roles and labels, and assertions
7
31
  that retry until they pass or time out. A Java or Python suite needs to be
8
32
  rewritten. Run both suites while you move one flow at a time.
@@ -15,7 +39,7 @@ agent calls; the locator-based example below works without one.
15
39
  | Selenium | e2e |
16
40
  | --- | --- |
17
41
  | The URL in every `driver.get` | `app: { url }` on the target, then relative paths |
18
- | Starting the app in a script | `app: { url, command }`, see [Starting your app](/starting-your-app) |
42
+ | Starting the app in a script | `app: { url, command }`, see [Starting your app](/web#starting-your-app) |
19
43
  | One driver per browser | One target per browser, `web({ browser: 'firefox' })` |
20
44
  | Selenium Grid | `workers` for parallelism; `connect` attaches to a remote Chromium over CDP, and a [browser provider](/browser#hosted-browsers) such as [Kernel](/integrations/kernel) leases hosted browsers |
21
45
  | `manage().window().setSize` | `web({ viewport })` |
@@ -101,8 +125,8 @@ Start with one page object and the flows that use it; its setup code can
101
125
  become a fixture.
102
126
 
103
127
  <CardGroup cols={2}>
104
- <Card title="Writing tests" icon="pen-to-square" href="/writing-tests">
105
- Goals, checks, extraction, and the deterministic APIs.
128
+ <Card title="Core concepts" icon="pen-to-square" href="/core-concepts">
129
+ Goals, assertions, and locators instead of scripted selectors.
106
130
  </Card>
107
131
  <Card title="screen reference" icon="crosshairs" href="/reference/screen">
108
132
  Every query, refinement, action, and read.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Mobile CI
3
3
  description: Run iOS and Android tests on GitHub Actions, EAS Workflows, Bitrise, and Codemagic.
4
+ icon: "mobile-screen-button"
4
5
  ---
5
6
 
6
7
  A mobile CI job does the same four things on every service: build the app
@@ -16,7 +17,7 @@ credentials, sharding, and exit codes; everything there applies here.
16
17
  | Machine | macOS with Xcode and a simulator runtime | Linux with KVM (nested virtualization) |
17
18
  | Build | Release simulator `.app`, no signing | Release `.apk` |
18
19
  | Device | A booted simulator | A booted emulator |
19
- | Node | 22.12 or later | 22.12 or later |
20
+ | Node | 24.8 or later, or 22.22.3 or later on 22 | 24.8 or later, or 22.22.3 or later on 22 |
20
21
 
21
22
  Build Release on both platforms: a React Native or Expo debug build loads
22
23
  its JavaScript from Metro, which no CI job here starts, so the app opens on
@@ -115,7 +116,7 @@ simulator; swap in your own build command as long as it writes a simulator
115
116
  Release --device generic --no-bundler --output build/ios` builds without
116
117
  booting a simulator and writes the `.app` under `build/ios`.
117
118
 
118
- ```yaml title=".github/workflows/e2e-mobile.yml"
119
+ ```yaml title=".github/workflows/e2e-mobile.yml" expandable
119
120
  name: e2e mobile
120
121
 
121
122
  on:
@@ -247,7 +248,7 @@ The iOS job runs on a macOS runner; the Android one needs a
247
248
  `nested-virtualization` Linux runner, the only EAS machines that can run the
248
249
  emulator:
249
250
 
250
- ```yaml title=".eas/workflows/e2e.yml"
251
+ ```yaml title=".eas/workflows/e2e.yml" expandable
251
252
  name: e2e
252
253
 
253
254
  on:
@@ -357,7 +358,7 @@ booting both devices: `xcode-start-simulator` on macOS, and `avd-manager`
357
358
  with `wait-for-android-emulator` on Linux. Start the emulator before the
358
359
  Gradle build so it boots while the APK compiles.
359
360
 
360
- ```yaml title="bitrise.yml"
361
+ ```yaml title="bitrise.yml" expandable
361
362
  format_version: '25'
362
363
  default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
363
364
 
@@ -482,7 +483,7 @@ workflows:
482
483
  Android runs on a Linux instance. The Ubuntu image comes with AVDs named `emulator` (API 34),
483
484
  `emulator-35`, and `emulator-36`.
484
485
 
485
- ```yaml title="codemagic.yaml"
486
+ ```yaml title="codemagic.yaml" expandable
486
487
  workflows:
487
488
  e2e-ios:
488
489
  name: e2e iOS
package/docs/mobile.mdx CHANGED
@@ -1,10 +1,11 @@
1
1
  ---
2
- title: Testing iOS and Android
3
- description: Set up a simulator or emulator target, write a device test, and know what differs from the web.
2
+ title: Mobile
3
+ description: Set up a simulator, emulator, or connected phone as a target, write a device test, and know what differs from the web.
4
+ icon: "mobile-screen"
4
5
  ---
5
6
 
6
- `@e2e-dev/mobile` runs tests on iOS simulators and Android emulators. It is
7
- built on [agent-device](https://github.com/callstack/agent-device).
7
+ `@e2e-dev/mobile` runs tests on iOS simulators, Android emulators, and
8
+ phones connected to your machine. It is built on [agent-device](https://github.com/callstack/agent-device).
8
9
  Tests use the same `agent`, `screen`, and `expect` APIs as browser tests.
9
10
  A test launches the configured app with `app.open()`; between tests the app
10
11
  stays where the last one left it.
@@ -178,6 +179,82 @@ mobile({ platform: 'ios', device: ['iPhone 17', 'iPhone 17 Pro'] })
178
179
  Without `device`, the engine uses booted devices up to the worker limit.
179
180
  If none are booted, it boots one. Two workers never drive the same device.
180
181
 
182
+ ## Physical devices
183
+
184
+ agent-device drives a phone plugged into your machine the same way it
185
+ drives a simulator, so a physical device needs no other engine or option.
186
+ List what agent-device sees, then name the phone in `device` as it is
187
+ listed:
188
+
189
+ <CodeGroup>
190
+ ```bash npm
191
+ npx agent-device devices --platform ios
192
+ ```
193
+
194
+ ```bash pnpm
195
+ pnpm exec agent-device devices --platform ios
196
+ ```
197
+
198
+ ```bash bun
199
+ bunx agent-device devices --platform ios
200
+ ```
201
+ </CodeGroup>
202
+
203
+ ```ts
204
+ mobile({ platform: 'ios', device: 'QA iPhone' })
205
+ ```
206
+
207
+ Name a phone by its name, not its id. The engine reads a `device` string as
208
+ an id only when it is a simulator UDID or an `emulator-<port>` serial, so a
209
+ phone's UDID or adb serial is looked up as a name and not found. The first
210
+ device with a matching name wins, so give each phone a unique name: two
211
+ phones both called `iPhone` or `Pixel 8` would bind one phone to two
212
+ workers. Without `device`, a connected phone counts as booted and joins the
213
+ pool beside any booted simulators, so name it to be sure a run lands on it.
214
+
215
+ Set the phone up once:
216
+
217
+ - **iOS**: the phone is paired and trusted, unlocked, with Developer Mode
218
+ on. agent-device builds and signs its XCTest runner onto the phone, so set
219
+ `AGENT_DEVICE_IOS_TEAM_ID` to your team id and `AGENT_DEVICE_IOS_BUNDLE_ID`
220
+ to a unique reverse-DNS id (`com.example.agentdevice.runner`); the
221
+ signing profile must allow that id and `<id>.uitests`. Set them before the
222
+ first agent-device command, or run `npx agent-device daemon stop` after:
223
+ a running daemon keeps the environment it started with. The first run
224
+ builds the runner and takes longer. `app.appPath` must be a signed device
225
+ build: a simulator `.app` does not install on a phone.
226
+ - **Android**: USB debugging is on and the phone is authorized, listed as
227
+ `device` in `adb devices`. `app.appPath` is the same `.apk`.
228
+
229
+ `npx agent-device doctor` checks the setup, and
230
+ `npx agent-device help physical-device` covers iOS signing in detail.
231
+
232
+ Some device controls need a simulator or emulator, and fail on a phone with
233
+ agent-device's message:
234
+
235
+ - On an iPhone, every device setting is simulator-only: `app.permissions`
236
+ and `device.setPermission`, `app.clearState()`, `device.setNetwork`,
237
+ `device.setAirplaneMode`, `device.setLocation`, `device.clearLocation`,
238
+ `device.setAppearance`, the biometric methods, and `device.clearKeychain`.
239
+ So are `device.clipboard`, `device.setClipboard`, and `device.fold`. For a
240
+ fresh start, reinstall with `device.installApp(path, { reinstall: true })`,
241
+ with `app.bundleId` on the target or `app` in the options, so agent-device
242
+ knows which app to remove.
243
+ - An older iPhone that `xcrun devicectl list devices` does not list is
244
+ driven without CoreDevice: taps, typing, and the screen work, but
245
+ installs, `--video`, `device.openLink`, and `app.launchArguments` fail.
246
+ Install the build from Xcode and pin `app.bundleId`.
247
+ - On an Android phone, `device.setLocation` is emulator-only, and
248
+ `device.setBiometrics('fingerprint', ...)` works only where the phone
249
+ exposes `cmd fingerprint`.
250
+ - `--video` on an iPhone is built from screenshots at about 15 frames a
251
+ second, and drops frames.
252
+ - A phone is not this machine, so `localhost` in the app is the phone
253
+ itself. Point the app at your machine's network address, or on Android
254
+ forward the port with `adb reverse tcp:<port> tcp:<port>`.
255
+
256
+ To run on phones in a device farm, see [Hosted devices](#hosted-devices).
257
+
181
258
  ## Hosted devices
182
259
 
183
260
  A `device` can also be a `DeviceProvider`: an object that leases one hosted
@@ -264,7 +341,7 @@ record leaves `record` out, and the engine records with agent-device.
264
341
  A provider against a typical session API has three requests: create a
265
342
  session, poll it until the daemon is reachable, stop it.
266
343
 
267
- ```ts title="device-provider.ts"
344
+ ```ts title="device-provider.ts" expandable
268
345
  import type { DeviceProvider } from '@e2e-dev/mobile';
269
346
 
270
347
  /**
@@ -344,7 +421,7 @@ A device cloud agent-device speaks to itself needs no session API of your
344
421
  own: the provider allocates a lease through a daemon it starts for the run and
345
422
  hands the worker the lease scope as `client`.
346
423
 
347
- ```ts title="device-cloud-provider.ts"
424
+ ```ts title="device-cloud-provider.ts" expandable
348
425
  import { createAgentDeviceClient } from 'agent-device';
349
426
  import type { DeviceProvider } from '@e2e-dev/mobile';
350
427
 
@@ -560,6 +637,17 @@ daemon (`npx agent-device daemon stop`) and reboot the simulator
560
637
 
561
638
  Do what it says.
562
639
 
640
+ **`device.installApp()` on Android reports no bundle id or package.** The
641
+ build installed, but agent-device could not say which package it is, so
642
+ `app.open()` would have nothing to launch. agent-device 0.21.18 cannot parse
643
+ the manifest of an APK built with aapt2 (every current Android build), so it
644
+ falls back to the Android SDK's `aapt`, which it looks for under
645
+ `ANDROID_SDK_ROOT`, `ANDROID_HOME`, or `~/Android/Sdk`, not the macOS default
646
+ `~/Library/Android/sdk`. Without `aapt` it names only a package the install
647
+ added, and a build already on the emulator adds none. Set the target's
648
+ `app.bundleId`, or export `ANDROID_HOME` and stop the agent-device daemon
649
+ (`daemon stop` below) so the next run starts it with the variable.
650
+
563
651
  **A timeout on the device.** A simulator boot, runner connection, or app
564
652
  launch that times out on the device is `ENGINE_FAILURE` with agent-device's
565
653
  message and hint, never one of e2e's own timeout codes (`LAUNCH_TIMEOUT`,
@@ -589,7 +677,7 @@ xcrun simctl shutdown <udid> && xcrun simctl boot <udid>
589
677
  <Card title="Mobile engine reference" icon="mobile-screen" href="/reference/mobile">
590
678
  Every option, the `device` fixture, and the tool pack.
591
679
  </Card>
592
- <Card title="Writing tests" icon="pen-to-square" href="/writing-tests">
593
- Goals, checks, and the deterministic APIs, on any platform.
680
+ <Card title="Locators" icon="crosshairs" href="/core-concepts#locators">
681
+ Exact taps, swipes, and checks with `screen` and `expect`, on any platform.
594
682
  </Card>
595
683
  </CardGroup>
package/docs/models.mdx CHANGED
@@ -1,15 +1,21 @@
1
1
  ---
2
2
  title: Models
3
3
  description: Configure a model with your own subscription, an API key, or a local server.
4
+ icon: "brain"
4
5
  ---
5
6
 
7
+ import { ModelProvider } from './snippets/model-provider.jsx';
8
+
6
9
  Agent steps use the model configured in `e2e.config.ts`. You can use an
7
10
  [existing subscription](/subscriptions), an API key, or a local server.
11
+ e2e uses the [AI SDK](https://ai-sdk.dev/providers). You can use
12
+ [all AI SDK providers that support tool calls](#ai-sdk-providers).
8
13
  `e2e init` offers these choices and writes the config for you. For the first
9
14
  setup, follow the [quickstart](/quickstart#use-a-subscription-or-api-key).
10
15
 
11
- Tests without agent steps do not need a model. For the built-in agent,
12
- choose a model that supports tool calls and images. Agent steps need the `ai`
16
+ Tests without agent steps do not need a model. [Decision models](/decision-models), hosted or locally
17
+ hosted, such as Clef or Jev, can drive bounded actions and judge assertions through a custom executor. For the built-in agent,
18
+ choose a model that supports tool calls and images. Steps using the built-in agent need the `ai`
13
19
  package installed, whichever provider you use:
14
20
 
15
21
  <CodeGroup>
@@ -77,7 +83,8 @@ const agent = { model: openai('gpt-6-luna') };
77
83
  ```
78
84
 
79
85
  Use `agent` as `agents.default`. Other AI SDK providers work the same way,
80
- with their own package, model IDs, and authentication settings.
86
+ with their own package, model IDs, and authentication settings. See
87
+ [AI SDK providers](#ai-sdk-providers).
81
88
  </Tab>
82
89
  <Tab title="Local or self-hosted">
83
90
 
@@ -103,11 +110,6 @@ Use `agent` as `agents.default`. Add `apiKey` if the server requires one.
103
110
  Model IDs depend on the provider. Gateways usually use `provider/model`;
104
111
  direct providers use their own model IDs.
105
112
 
106
- DeepSeek's V4 models (`@ai-sdk/deepseek`) think by default, and in that mode
107
- they refuse the forced tool choice agent steps ask for. The step is sent again
108
- with `auto` and an instruction to reply with tool calls only, and later steps
109
- on that model start that way, so thinking can stay on.
110
-
111
113
  ### Vercel OIDC instead of a key
112
114
 
113
115
  Without `AI_GATEWAY_API_KEY`, `gateway()` authenticates with a Vercel OIDC
@@ -145,6 +147,46 @@ An OIDC failure is the same `MODEL_PROVIDER_FAILED` as a bad key, and the AI
145
147
  SDK's message names only `AI_GATEWAY_API_KEY`. With no key set, check that the
146
148
  Vercel CLI is signed in and that `.vercel/project.json` exists.
147
149
 
150
+ ## AI SDK providers
151
+
152
+ Select a provider. The three steps below show the setup for that provider.
153
+
154
+ <ModelProvider show="select" />
155
+
156
+ The list shows the AI SDK providers that support tool calls. A provider that
157
+ is not in the list also works if it uses the AI SDK language model
158
+ specification v2 or later. Its model must support tool calls and images.
159
+
160
+ ### Install the provider package
161
+
162
+ Install `ai` and the provider package.
163
+
164
+ <ModelProvider show="install" />
165
+
166
+ ### Set the credentials
167
+
168
+ The provider reads its credentials from the environment. e2e does not load
169
+ `.env` files. Export the variables in your shell or CI, or load the file in
170
+ the config with [`process.loadEnvFile()`](/reference/config#loading).
171
+
172
+ <ModelProvider show="env" />
173
+
174
+ If a key is missing or wrong, the first agent step fails with
175
+ `MODEL_PROVIDER_FAILED`. Some providers read the key when the config loads.
176
+ For these providers, a missing key stops all runs with `CONFIG_LOAD_FAILED`.
177
+
178
+ ### Add the model to the config
179
+
180
+ Set the model in `agents.default`. The model ID is an example. Use a model
181
+ that supports tool calls and images.
182
+
183
+ <ModelProvider show="config" />
184
+
185
+ DeepSeek's V4 models (`@ai-sdk/deepseek`) think by default, and in that mode
186
+ they refuse the forced tool choice agent steps ask for. The step is sent again
187
+ with `auto` and an instruction to reply with tool calls only, and later steps
188
+ on that model start that way, so thinking can stay on.
189
+
148
190
  ## Several models in one config
149
191
 
150
192
  Give agents different names to use different models:
@@ -192,12 +234,21 @@ Judgments use the current screen and the question, without the acting
192
234
  agent's transcript or summaries. Reports include the model used for each
193
235
  judgment. See [Custom executors](/executors) if you replace the built-in agent.
194
236
 
237
+ `judge` takes an AI SDK language model. Clef and Jev use a different API;
238
+ use the [decision executor](/decision-models) for `agent.act` and `agent.assert`.
239
+
195
240
  ## Data retention and compliance
196
241
 
197
242
  Model input is the redacted screen, the step instruction, and the
198
243
  screenshots the agent asks for. e2e reads nothing back from the provider
199
244
  after a response, so a run needs no state kept on the provider's side.
200
245
 
246
+ Every model request names e2e: the `User-Agent` starts with
247
+ `e2e/<version> (<platform>; <arch>)`, and `HTTP-Referer:
248
+ https://tester.army/e2e` with `X-Title: e2e` attribute the traffic to e2e on
249
+ the Vercel AI Gateway and OpenRouter. These replace the same headers set on
250
+ the provider instance.
251
+
201
252
  **OpenAI and Azure OpenAI.** Every request carries `store: false` and a
202
253
  prompt cache key. The provider keeps no copy of the conversation, a
203
254
  reasoning model's earlier turns travel inline as encrypted content, and the
@@ -233,7 +284,7 @@ organizations with zero data retention.
233
284
 
234
285
  <CardGroup cols={2}>
235
286
  <Card title="Subscriptions" icon="key" href="/subscriptions">
236
- Sign in with ChatGPT, Copilot, or SuperGrok.
287
+ Sign in with ChatGPT, Copilot, OpenCode Console, or SuperGrok.
237
288
  </Card>
238
289
  <Card title="Agents and personas" icon="users" href="/agents">
239
290
  Choose agents for tests and signed-in users.
@@ -1,16 +1,18 @@
1
1
  ---
2
2
  title: Quickstart
3
3
  description: Set up e2e with one prompt to your coding agent, or step by step.
4
+ icon: "rocket"
4
5
  ---
5
6
 
7
+ import { ModelProvider } from './snippets/model-provider.jsx';
6
8
  import { SetupPrompt } from './snippets/setup-prompt-card.jsx';
7
9
 
8
10
  export const setupPrompt = `Set up e2e end-to-end tests in this project. Docs: \`https://e2e.tester.army/docs/quickstart.md\`
9
11
 
10
12
  1. Run \`npx e2e init --yes\` (or the pnpm or bun equivalent for this project). It writes a web config using Vercel AI Gateway, an example test, the e2e skill, and MCP config. It installs nothing yet.
11
13
  2. Read the e2e skill (\`npx e2e guide\` prints it, \`npx e2e guide <topic>\` prints a topic) and follow it for every step below.
12
- 3. Point the target at this app. For a web app, set its URL and the command that starts its dev server. For an iOS or Android app, replace @e2e-dev/web and playwright with @e2e-dev/mobile and rewrite the example test for the mobile engine.
13
- 4. Ask me which model to use for agent steps: a ChatGPT, GitHub Copilot, or SuperGrok subscription, an API key, or a local model. Configure it with its provider package. If it needs a sign-in, give me the \`npx e2e login\` command and wait until I confirm.
14
+ 3. Point the target at this app. For a web app, set its URL and the command that starts its dev server. For an iOS or Android app, replace @e2e-dev/web with @e2e-dev/mobile and rewrite the example test for the mobile engine.
15
+ 4. Ask me which model to use for agent steps: a ChatGPT, GitHub Copilot, OpenCode Console, or SuperGrok subscription, an API key, or a local model. Configure it with its provider package. If it needs a sign-in, give me the \`npx e2e login\` command and wait until I confirm.
14
16
  5. Install dependencies, run the example test, and fix setup errors until it passes.
15
17
  6. Write one test for the most important user flow in this app, run it, and iterate until it passes.`;
16
18
 
@@ -29,7 +31,7 @@ The agent installs the [e2e skill](/coding-agents), so later prompts like
29
31
  "add a test for checkout" or "why did this run fail" work without more setup.
30
32
 
31
33
  <Note>
32
- Use Node.js 22.12 or newer. On Windows, run inside WSL. Mobile tests also
34
+ Use Node.js 24.8 or newer (22.22.3 or newer on Node.js 22). On Windows, run inside WSL. Mobile tests also
33
35
  need Xcode with an iOS simulator runtime, or the Android SDK with an emulator.
34
36
  </Note>
35
37
 
@@ -63,7 +65,7 @@ need Xcode with an iOS simulator runtime, or the Android SDK with an emulator.
63
65
  <Tabs>
64
66
  <Tab title="Web">
65
67
  Start your app's dev server at `http://localhost:3000`, or set its address
66
- in `e2e.config.ts`. You can also [let e2e start it](/starting-your-app).
68
+ in `e2e.config.ts`. You can also [let e2e start it](/web#starting-your-app).
67
69
 
68
70
  <Accordion title="Example web config and test">
69
71
  This config uses Vercel AI Gateway. The wizard configures the provider you chose.
@@ -122,7 +124,7 @@ test('Settings opens', async ({ app, screen }) => {
122
124
  ```
123
125
  </Accordion>
124
126
 
125
- See [Testing iOS and Android](/mobile) to use your own app.
127
+ See [Mobile](/mobile) to use your own app.
126
128
  </Tab>
127
129
  </Tabs>
128
130
  </Step>
@@ -159,23 +161,33 @@ Connect the provider you chose in the wizard.
159
161
  | --- | --- |
160
162
  | ChatGPT Plus or Pro | `npx e2e login openai` |
161
163
  | GitHub Copilot | `npx e2e login github-copilot` |
164
+ | OpenCode Console (OpenCode Zen and OpenCode Go) | `npx e2e login opencode-console` |
162
165
  | SuperGrok or X Premium+ | `npx e2e login spacexai` |
163
166
 
164
167
  Copilot reuses your GitHub CLI login if available. See
165
168
  [Subscriptions](/subscriptions) for details.
166
169
  </Tab>
167
170
  <Tab title="API key">
168
- Set your provider's key in the terminal where you run e2e. For Vercel AI
169
- Gateway:
171
+ Select your provider. Install its package and set the variables it reads,
172
+ if any, in the terminal where you run e2e.
170
173
 
171
- ```bash
172
- export AI_GATEWAY_API_KEY="your-api-key"
173
- ```
174
+ <ModelProvider show="select" />
175
+
176
+ <ModelProvider show="install" />
177
+
178
+ <ModelProvider show="env" />
179
+
180
+ Vercel AI Gateway reads `AI_GATEWAY_API_KEY`, or a
181
+ [Vercel OIDC token](/models#vercel-oidc-instead-of-a-key) in a project
182
+ linked with `vercel link`. OpenRouter reads `OPENROUTER_API_KEY`.
183
+
184
+ The wizard writes the config for Vercel AI Gateway, OpenRouter, and
185
+ OpenAI-compatible endpoints. For another provider, replace the model import
186
+ and `model` value in `e2e.config.ts`, and use `agent` as `agents.default`:
187
+
188
+ <ModelProvider show="model" />
174
189
 
175
- A project linked with `vercel link` can use a
176
- [Vercel OIDC token](/models#vercel-oidc-instead-of-a-key) instead.
177
- OpenRouter uses `OPENROUTER_API_KEY`. See [Models](/models) for other
178
- providers and local models.
190
+ See [Models](/models) for local models and more options.
179
191
  </Tab>
180
192
  </Tabs>
181
193
 
@@ -245,8 +257,8 @@ to watch a web test, `--no-cache` to skip replaying cached actions.
245
257
  ## Next
246
258
 
247
259
  <CardGroup cols={2}>
248
- <Card title="Writing tests" icon="pen-to-square" href="/writing-tests">
249
- Write goals, check outcomes, and extract data.
260
+ <Card title="Core concepts" icon="pen-to-square" href="/core-concepts">
261
+ Goals, assertions, and locators.
250
262
  </Card>
251
263
  <Card title="Signing in" icon="key" href="/authentication">
252
264
  Reuse an app login and keep passwords out of model input.