e2e 0.16.0 → 0.17.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 (174) hide show
  1. package/dist/.build.json +1 -1
  2. package/dist/agent/act.js +7 -4
  3. package/dist/agent/action-dispatcher.js +4 -5
  4. package/dist/agent/actions.d.ts +32 -23
  5. package/dist/agent/actions.js +38 -24
  6. package/dist/agent/model/sdk.js +2 -0
  7. package/dist/agent/observation-feed.d.ts +5 -6
  8. package/dist/agent/observation-feed.js +5 -7
  9. package/dist/agent/observation.d.ts +40 -11
  10. package/dist/agent/observation.js +109 -59
  11. package/dist/agent/point-tap.d.ts +8 -8
  12. package/dist/agent/point-tap.js +1 -1
  13. package/dist/agent/replay.d.ts +19 -8
  14. package/dist/agent/replay.js +11 -14
  15. package/dist/agent/scroll-target.d.ts +2 -3
  16. package/dist/agent/step-cache.d.ts +41 -14
  17. package/dist/agent/step-cache.js +147 -62
  18. package/dist/agent/tool-loop.js +2 -0
  19. package/dist/cache/anchors.d.ts +72 -23
  20. package/dist/cache/anchors.js +233 -60
  21. package/dist/cache/context.d.ts +25 -14
  22. package/dist/cache/context.js +50 -8
  23. package/dist/cache/decide.d.ts +2 -2
  24. package/dist/cache/decide.js +4 -6
  25. package/dist/cache/identity.d.ts +18 -4
  26. package/dist/cache/identity.js +9 -4
  27. package/dist/cache/recorder.d.ts +15 -1
  28. package/dist/cache/recorder.js +36 -23
  29. package/dist/cache/rekeyed.d.ts +46 -0
  30. package/dist/cache/rekeyed.js +89 -0
  31. package/dist/cache/relocate.d.ts +14 -14
  32. package/dist/cache/relocate.js +33 -25
  33. package/dist/cache/route.d.ts +32 -33
  34. package/dist/cache/route.js +94 -50
  35. package/dist/cache/store.d.ts +6 -0
  36. package/dist/cache/store.js +21 -1
  37. package/dist/cache/trace.d.ts +53 -14
  38. package/dist/cache/trace.js +70 -28
  39. package/dist/cli/index.js +8 -8
  40. package/dist/cli/init/engines.d.ts +1 -3
  41. package/dist/cli/init/engines.js +2 -2
  42. package/dist/cli/init/gateways.d.ts +1 -1
  43. package/dist/cli/init/gateways.js +11 -1
  44. package/dist/cli/init/sibling-versions.json +1 -1
  45. package/dist/cli/init/versions.d.ts +0 -8
  46. package/dist/cli/init/versions.js +1 -10
  47. package/dist/config/resolve.d.ts +1 -0
  48. package/dist/config/resolve.js +33 -1
  49. package/dist/engine/index.d.ts +2 -2
  50. package/dist/expect/async.js +2 -1
  51. package/dist/expect/poll-scope.d.ts +52 -0
  52. package/dist/expect/poll-scope.js +113 -0
  53. package/dist/expect/poll.js +31 -4
  54. package/dist/expect/values.js +11 -4
  55. package/dist/internal/abandoned.d.ts +11 -0
  56. package/dist/internal/abandoned.js +23 -0
  57. package/dist/internal/brands.d.ts +2 -0
  58. package/dist/internal/brands.js +2 -0
  59. package/dist/internal/client-identity.d.ts +9 -0
  60. package/dist/internal/client-identity.js +14 -0
  61. package/dist/internal/redact.d.ts +7 -4
  62. package/dist/internal/redact.js +116 -23
  63. package/dist/internal/time.d.ts +6 -0
  64. package/dist/internal/time.js +25 -13
  65. package/dist/internal/urls.d.ts +9 -5
  66. package/dist/internal/urls.js +19 -10
  67. package/dist/locator/engine.d.ts +12 -2
  68. package/dist/locator/engine.js +8 -3
  69. package/dist/locator/screen.js +28 -4
  70. package/dist/mcp/catalog.d.ts +3 -2
  71. package/dist/mcp/catalog.js +6 -6
  72. package/dist/mcp/session.js +1 -1
  73. package/dist/oauth/chatgpt.js +3 -23
  74. package/dist/oauth/cli.js +1 -0
  75. package/dist/oauth/copilot.d.ts +5 -4
  76. package/dist/oauth/copilot.js +80 -7
  77. package/dist/oauth/grok.js +2 -1
  78. package/dist/oauth/models.js +2 -1
  79. package/dist/oauth/opencode-console.d.ts +10 -0
  80. package/dist/oauth/opencode-console.js +133 -0
  81. package/dist/oauth/providers/github-copilot.d.ts +12 -0
  82. package/dist/oauth/providers/github-copilot.js +77 -23
  83. package/dist/oauth/providers/opencode-console.d.ts +31 -0
  84. package/dist/oauth/providers/opencode-console.js +148 -0
  85. package/dist/oauth/providers.d.ts +2 -6
  86. package/dist/oauth/providers.js +3 -7
  87. package/dist/oauth/responses.d.ts +9 -0
  88. package/dist/oauth/responses.js +23 -0
  89. package/dist/report/build.d.ts +26 -0
  90. package/dist/report/build.js +16 -8
  91. package/dist/report/failure-text.d.ts +6 -1
  92. package/dist/report/failure-text.js +14 -4
  93. package/dist/report/format.d.ts +10 -5
  94. package/dist/report/format.js +12 -5
  95. package/dist/report/junit.js +11 -12
  96. package/dist/report/list.d.ts +1 -0
  97. package/dist/report/list.js +38 -18
  98. package/dist/report/markdown.d.ts +1 -1
  99. package/dist/report/markdown.js +37 -14
  100. package/dist/report/repeats.d.ts +6 -1
  101. package/dist/report/repeats.js +12 -3
  102. package/dist/run/artifacts.d.ts +18 -0
  103. package/dist/run/artifacts.js +68 -1
  104. package/dist/run/execute.d.ts +9 -0
  105. package/dist/run/execute.js +49 -24
  106. package/dist/run/extended-fixtures.js +6 -2
  107. package/dist/run/failure-evidence.js +4 -2
  108. package/dist/run/fixtures.js +6 -8
  109. package/dist/run/in-process.d.ts +2 -0
  110. package/dist/run/in-process.js +1 -0
  111. package/dist/run/last-run.d.ts +42 -8
  112. package/dist/run/last-run.js +121 -14
  113. package/dist/run/realm.d.ts +4 -1
  114. package/dist/run/realm.js +20 -6
  115. package/dist/run/records.d.ts +16 -0
  116. package/dist/run/records.js +26 -0
  117. package/dist/run/retry.d.ts +3 -1
  118. package/dist/run/retry.js +4 -2
  119. package/dist/run/runner.d.ts +1 -1
  120. package/dist/run/runner.js +69 -30
  121. package/dist/run/serial.d.ts +2 -0
  122. package/dist/run/serial.js +8 -5
  123. package/dist/run/steps.d.ts +0 -6
  124. package/dist/run/steps.js +3 -20
  125. package/dist/run/worker/entry.js +5 -4
  126. package/dist/run/worker/protocol.d.ts +2 -0
  127. package/dist/run/worker/session.d.ts +2 -0
  128. package/dist/run/worker/session.js +1 -0
  129. package/dist/telemetry/events.js +1 -0
  130. package/dist/types.d.ts +9 -3
  131. package/docs/agents.mdx +6 -3
  132. package/docs/assertions.mdx +94 -0
  133. package/docs/authentication.mdx +3 -1
  134. package/docs/browser.mdx +42 -6
  135. package/docs/cache.mdx +64 -18
  136. package/docs/ci.mdx +11 -7
  137. package/docs/debugging.mdx +6 -3
  138. package/docs/executors.mdx +4 -2
  139. package/docs/github.mdx +12 -4
  140. package/docs/{writing-tests.mdx → goals.mdx} +21 -101
  141. package/docs/index.mdx +4 -4
  142. package/docs/integrations/kernel.mdx +1 -1
  143. package/docs/locators.mdx +64 -0
  144. package/docs/migrate/cypress.mdx +2 -2
  145. package/docs/migrate/maestro.mdx +2 -2
  146. package/docs/migrate/playwright.mdx +19 -16
  147. package/docs/migrate/selenium.mdx +2 -2
  148. package/docs/mobile.mdx +21 -4
  149. package/docs/models.mdx +53 -7
  150. package/docs/physical-devices.mdx +76 -0
  151. package/docs/quickstart.mdx +4 -3
  152. package/docs/reference/app.mdx +1 -1
  153. package/docs/reference/cli.mdx +30 -13
  154. package/docs/reference/config.mdx +25 -7
  155. package/docs/reference/engine.mdx +3 -3
  156. package/docs/reference/environment.mdx +8 -1
  157. package/docs/reference/errors.mdx +5 -5
  158. package/docs/reference/expect.mdx +34 -12
  159. package/docs/reference/mobile.mdx +23 -13
  160. package/docs/reference/reporters.mdx +8 -2
  161. package/docs/reference/screen.mdx +30 -7
  162. package/docs/reference/test.mdx +11 -4
  163. package/docs/reference/web.mdx +125 -21
  164. package/docs/security.mdx +26 -16
  165. package/docs/subscriptions.mdx +91 -12
  166. package/docs/telemetry.mdx +1 -1
  167. package/package.json +16 -3
  168. package/schema/report-v1.schema.json +14 -1
  169. package/skills/e2e/SKILL.md +2 -2
  170. package/skills/e2e/references/agent.md +22 -11
  171. package/skills/e2e/references/debugging.md +4 -4
  172. package/skills/e2e/references/running.md +13 -8
  173. package/skills/e2e/references/setup.md +36 -15
  174. package/skills/e2e/references/writing-tests.md +34 -17
@@ -1,10 +1,11 @@
1
1
  ---
2
- title: Writing tests
3
- description: Goals, checks, extraction, and the deterministic APIs.
2
+ title: Goals
3
+ description: Write test steps as goals with agent.act, and structure the tests around them.
4
4
  ---
5
5
 
6
- Use agent calls for goals and questions about the screen. Use `screen` and
7
- `expect` when you know the exact action or value to check. A test can freely mix both.
6
+ Use agent calls for goals and questions about the screen. Use
7
+ [locators](/locators) when you know the exact action or value to check. A test
8
+ can freely mix both.
8
9
 
9
10
  ```ts title="tests/todos.e2e.ts"
10
11
  import { test, expect } from 'e2e';
@@ -23,7 +24,9 @@ test('todos survive a filter round-trip', async ({ app, agent, screen }) => {
23
24
  ## Goals: `agent.act`
24
25
 
25
26
  Give `act` one goal. The agent chooses and performs the actions, then returns
26
- when the goal passes or throws when it fails.
27
+ when the goal passes or throws when it fails. Await every agent and screen
28
+ call, and every `expect.poll`; a test that returns while one is still running
29
+ fails with `STEP_NOT_AWAITED`.
27
30
 
28
31
  ```ts
29
32
  await agent.act('complete checkout with the test card');
@@ -68,60 +71,9 @@ A step can pass, fail, or be blocked by something such as missing credentials.
68
71
  [How agent steps work](/agent-steps#how-a-step-ends) explains the verdicts and
69
72
  exit codes.
70
73
 
71
- ## Checks: `assert`, `waitFor`, `extract`
72
-
73
- `assert` checks a statement against the current screen. A false statement
74
- fails with `ASSERTION_FAILED`. If the screen does not provide enough evidence,
75
- it fails with `ASSERTION_INCONCLUSIVE`.
76
-
77
- The judging model sees the statement and the current screen. It does not see
78
- earlier steps or the acting agent's summaries.
79
-
80
- ```ts
81
- await agent.assert('the dashboard shows a trial badge');
82
- ```
83
-
84
- `assert` checks the screen now. Use `waitFor` when the condition may become true
85
- later. It polls the screen and only calls the model when the observation changes.
86
-
87
- ```ts
88
- await agent.waitFor('the export finishes and a download link appears', {
89
- interval: 500,
90
- timeout: 120_000,
91
- });
92
- ```
93
-
94
- `extract` reads structured data from the screen. Provide a
95
- [Standard Schema](https://standardschema.dev) validator, such as Zod, to define
96
- the result.
97
-
98
- ```ts
99
- import { z } from 'zod';
100
-
101
- const data = await agent.extract('every todo title and how many remain', {
102
- schema: z.object({ titles: z.array(z.string()), remaining: z.number().int() }),
103
- });
104
- expect(data.titles).toContain('Buy milk');
105
- ```
106
-
107
- <Tip>
108
- For model-generated descriptions, check the relevant meaning rather than the
109
- entire sentence. Use a locator assertion when the exact text matters.
110
- </Tip>
111
-
112
- ### Judging pixels
113
-
114
- By default, judgments read a redacted text snapshot of the screen. Add
115
- `vision: true` for visual checks, such as charts or layout. Use
116
- `vision: 'only'` to judge the screenshot alone.
117
-
118
- ```ts
119
- await agent.assert('the chart trends upward', { vision: true });
120
- await agent.assert('the search form is not covered by a banner', { vision: 'only' });
121
- ```
122
-
123
- `act` requests screenshots itself when it needs them, so it has no `vision`
124
- option. On supported engines, it can tap and type at points in the image:
74
+ `act` requests screenshots itself when it needs them, so unlike the
75
+ [judgments](/assertions#judging-pixels) it has no `vision` option. On
76
+ supported engines, it can tap and type at points in the image:
125
77
 
126
78
  ```ts
127
79
  await agent.act('enter the code 3141 on the drawn keypad and press OK');
@@ -131,44 +83,8 @@ Images add input tokens. Screenshots are withheld for the rest of an attempt
131
83
  after a secret is filled. The [agent reference](/reference/agent#vision)
132
84
  lists the modes and restrictions.
133
85
 
134
- ## Deterministic APIs: `screen` and `expect`
135
-
136
- Use locators to interact with known controls and check exact values.
137
- An `expect` after `act` checks the exact result. It is also what lets the
138
- [replay cache](/cache) record the step and keep it.
139
-
140
- ```ts
141
- // Semantic queries: role and name first, label second, test ID last.
142
- await screen.getByRole('button', 'Sign in').tap();
143
- await screen.getByLabel('Email').fill('ada@example.test');
144
- await screen.getByTestId('todo').first().tap();
145
-
146
- // Scope into a row, gesture like a user:
147
- const row = screen.getByRole('listitem').filter({ hasText: 'Design review' });
148
- await row.getByRole('button', 'Archive').tap();
149
- await screen.swipe({ direction: 'up', momentum: 'fast' });
150
-
151
- // Coordinates, for what the tree does not list:
152
- await screen.getByRole('image', 'Seat map').tap({ position: { x: 120, y: 48 } });
153
- await screen.swipe({ from: { x: 300, y: 400 }, to: { x: 40, y: 400 } });
154
-
155
- // Polling assertions:
156
- await expect(screen.getByRole('status')).toHaveText('2 remaining');
157
- await expect(screen.getByRole('heading', 'Dashboard')).toBeVisible();
158
- ```
159
-
160
- Actions and single-node reads or assertions fail when a query matches more
161
- than one node. Narrow it with `.filter()`, `.first()`, or `.nth()`.
162
- Locator assertions retry until they pass or time out. Direct reads such as
163
- `textContent()` do not retry a mismatched value.
164
-
165
- Use the `browser` fixture for browser features such as routes, cookies, dialogs,
166
- frames, and downloads; see [Working with the browser](/browser). The
167
- [screen reference](/reference/screen) lists every query and action.
168
-
169
- Share setup and cleanup through `test.extend`. A fixture can create a test
170
- workspace before the test and delete it afterward, including when the test
171
- fails. See [Your own fixtures](/reference/test#your-own-fixtures).
86
+ To check what a goal left on screen, follow it with an
87
+ [assertion](/assertions) or a [locator check](/locators).
172
88
 
173
89
  ## Tests without a screen
174
90
 
@@ -220,6 +136,10 @@ fixtures or hooks. When a flow must span several tests, mark the group
220
136
  `{ serial: true }`. Members share app state, run in order on one worker,
221
137
  and retry as a whole.
222
138
 
139
+ Share setup and cleanup through `test.extend`. A fixture can create a test
140
+ workspace before the test and delete it afterward, including when the test
141
+ fails. See [Your own fixtures](/reference/test#your-own-fixtures).
142
+
223
143
  ## Run one test
224
144
 
225
145
  <CodeGroup>
@@ -247,10 +167,10 @@ The [CLI reference](/reference/cli#e2e-run) lists every flag.
247
167
  ## Next
248
168
 
249
169
  <CardGroup cols={2}>
250
- <Card title="Signing in" icon="key" href="/authentication">
251
- Sessions and secrets.
170
+ <Card title="Assertions" icon="circle-check" href="/assertions">
171
+ Check the screen with `assert`, `waitFor`, and `extract`.
252
172
  </Card>
253
- <Card title="Agents and personas" icon="users" href="/agents">
254
- Several agents in one suite, and running a flow as each of them.
173
+ <Card title="Locators" icon="crosshairs" href="/locators">
174
+ Exact actions and checks with `screen` and `expect`.
255
175
  </Card>
256
176
  </CardGroup>
package/docs/index.mdx CHANGED
@@ -9,9 +9,9 @@ e2e is a next-generation testing framework for web and mobile
9
9
  apps, built to keep up with the speed we ship software today.
10
10
 
11
11
  You choose how agentic each test is: fully deterministic with
12
- [locators](/writing-tests#deterministic-apis-screen-and-expect) and
12
+ [locators](/locators) and
13
13
  [assertions](/reference/expect#locator-matchers), fully driven by an agent
14
- from [goals](/writing-tests#goals-agent-act) in natural language, or a mix of
14
+ from [goals](/goals) in natural language, or a mix of
15
15
  both in the same test.
16
16
 
17
17
  <VideoCard
@@ -70,8 +70,8 @@ you shipped.
70
70
  <Card title="Quickstart" icon="rocket" href="/quickstart">
71
71
  One prompt for your coding agent, or set up by hand.
72
72
  </Card>
73
- <Card title="Writing tests" icon="pen-to-square" href="/writing-tests">
74
- Write goals, check outcomes, and extract data.
73
+ <Card title="Goals" icon="pen-to-square" href="/goals">
74
+ Write test steps as goals in natural language.
75
75
  </Card>
76
76
  <Card title="Coding agents" icon="robot" href="/coding-agents">
77
77
  Let your coding agent write, run, and debug tests.
@@ -90,7 +90,7 @@ cookies and storage are not there.
90
90
  that browser's default context, on its own first tab, so a `profile` applies
91
91
  and the live view shows one tab. It costs a browser start per test and
92
92
  carries the limits of a [per-attempt lease](/browser#hosted-browsers): no
93
- `headers`, `basicAuth`, or `userAgent`, no `app.clearState()`, and no
93
+ `headers`, `basicAuth`, `userAgent`, `locale`, or `timezoneId`, no `app.clearState()`, and no
94
94
  [sessions](/authentication), which fail the run before any test.
95
95
 
96
96
  ## Recordings
@@ -0,0 +1,64 @@
1
+ ---
2
+ title: Locators
3
+ description: Deterministic actions and checks with screen and expect.
4
+ ---
5
+
6
+ Use locators to interact with known controls and check exact values. Locator
7
+ steps call no model.
8
+
9
+ ```ts title="tests/todos.e2e.ts"
10
+ import { test, expect } from 'e2e';
11
+
12
+ test('adds a todo', async ({ app, screen }) => {
13
+ await app.open('/todos');
14
+ await screen.getByLabel('New todo').fill('Buy milk');
15
+ await screen.getByRole('button', 'Add').tap();
16
+ await expect(screen.getByTestId('todo')).toHaveCount(1);
17
+ });
18
+ ```
19
+
20
+ An `expect` after an [`agent.act` goal](/goals) checks the exact result. It is
21
+ also what lets the [replay cache](/cache) record the step and keep it.
22
+
23
+ ## Queries, actions, and assertions
24
+
25
+ ```ts
26
+ // Semantic queries: role and name first, label second, test ID last.
27
+ await screen.getByRole('button', 'Sign in').tap();
28
+ await screen.getByLabel('Email').fill('ada@example.test');
29
+ await screen.getByTestId('todo').first().tap();
30
+
31
+ // Scope into a row, gesture like a user:
32
+ const row = screen.getByRole('listitem').filter({ hasText: 'Design review' });
33
+ await row.getByRole('button', 'Archive').tap();
34
+ await screen.swipe({ direction: 'up', momentum: 'fast' });
35
+
36
+ // Coordinates, for what the tree does not list:
37
+ await screen.getByRole('image', 'Seat map').tap({ position: { x: 120, y: 48 } });
38
+ await screen.swipe({ from: { x: 300, y: 400 }, to: { x: 40, y: 400 } });
39
+
40
+ // Polling assertions:
41
+ await expect(screen.getByRole('status')).toHaveText('2 remaining');
42
+ await expect(screen.getByRole('heading', 'Dashboard')).toBeVisible();
43
+ ```
44
+
45
+ Actions and single-node reads or assertions fail when a query matches more
46
+ than one node. Narrow it with `.filter()`, `.first()`, or `.nth()`.
47
+ Locator assertions retry until they pass or time out. Direct reads such as
48
+ `textContent()` do not retry a mismatched value.
49
+
50
+ Use the `browser` fixture for browser features such as routes, cookies, dialogs,
51
+ frames, and downloads; see [Working with the browser](/browser). The
52
+ [screen reference](/reference/screen) lists every query and action, and the
53
+ [expect reference](/reference/expect#locator-matchers) lists every matcher.
54
+
55
+ ## Next
56
+
57
+ <CardGroup cols={2}>
58
+ <Card title="Signing in" icon="key" href="/authentication">
59
+ Sessions and secrets.
60
+ </Card>
61
+ <Card title="Agents and personas" icon="users" href="/agents">
62
+ Several agents in one suite, and running a flow as each of them.
63
+ </Card>
64
+ </CardGroup>
@@ -115,8 +115,8 @@ Start with a spec that often breaks and whose fixed `cy.wait` calls can
115
115
  become assertions on the condition the test needs.
116
116
 
117
117
  <CardGroup cols={2}>
118
- <Card title="Writing tests" icon="pen-to-square" href="/writing-tests">
119
- Goals, checks, extraction, and the deterministic APIs.
118
+ <Card title="Goals" icon="pen-to-square" href="/goals">
119
+ Write test steps as goals instead of scripted selectors.
120
120
  </Card>
121
121
  <Card title="Working with the browser" icon="globe" href="/browser">
122
122
  Routes, dialogs, downloads, iframes, and page evaluation.
@@ -223,7 +223,7 @@ often need updating.
223
223
  <Card title="Testing iOS and Android" icon="mobile-screen" href="/mobile">
224
224
  Configure your app and devices.
225
225
  </Card>
226
- <Card title="Writing tests" icon="pen-to-square" href="/writing-tests">
227
- Goals, checks, extraction, and the deterministic APIs.
226
+ <Card title="Goals" icon="pen-to-square" href="/goals">
227
+ Write test steps as goals instead of scripted selectors.
228
228
  </Card>
229
229
  </CardGroup>
@@ -3,8 +3,8 @@ title: From Playwright
3
3
  description: Run e2e alongside Playwright and migrate tests one file at a time.
4
4
  ---
5
5
 
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
6
+ `@e2e-dev/web` brings its own pinned `playwright-core`, so your
7
+ `@playwright/test` version is yours to keep. Both runners can stay in one
8
8
  project. e2e reads `e2e.config.ts` and finds `tests/**/*.e2e.ts` by default,
9
9
  so keep the two file patterns apart.
10
10
 
@@ -59,7 +59,8 @@ A Playwright key the runner recognizes (`testDir`, `baseURL`, `webServer`,
59
59
  | `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
60
  | `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
61
  | `use.headless: false` | `--headed` on the CLI | renamed |
62
- | `use.locale`, `timezoneId`, `colorScheme`, `geolocation`, `permissions` | not yet | missing |
62
+ | `use.locale`, `use.timezoneId` | `web({ locale, timezoneId })` | renamed |
63
+ | `colorScheme`, `geolocation`, `permissions` | not yet | missing |
63
64
  | `use.userAgent` | `web({ userAgent })` | renamed |
64
65
  | `devices['iPhone 15']`, `deviceScaleFactor`, `isMobile`, `hasTouch` | not yet; `web({ viewport })` sets the size only | missing |
65
66
  | `ignoreHTTPSErrors`, `bypassCSP`, `offline`, `launchOptions`, `proxy` | not yet | missing |
@@ -139,7 +140,9 @@ what to do today.
139
140
  | `fill(value)` | the same; also takes a `Secret` | same |
140
141
  | `pressSequentially(text, { delay })` | the same; keystrokes through the platform keyboard, a `Secret` is refused | same |
141
142
  | `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 |
143
+ | `clear()`, `focus()`, `hover()`, `setInputFiles()`, `dragTo()` | the same | same |
144
+ | `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 |
145
+ | `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
146
  | `selectOption(['a', 'b'])` | not yet; one option per call. An array is `INVALID_ARGUMENT` | missing |
144
147
  | `setInputFiles({ name, buffer })` | not yet; paths only, resolved from the project root | missing |
145
148
  | `scrollIntoViewIfNeeded()` | `scrollIntoView()` | renamed |
@@ -149,8 +152,7 @@ what to do today.
149
152
  | `isChecked()` | the same on a checkbox, radio, or `aria-checked` node; `false` on any other node, where Playwright throws | by design |
150
153
  | `isHidden()`, `isDisabled()` | the same | same |
151
154
  | `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 |
155
+ | `waitFor({ state: 'visible' })`, `'hidden'`, `'attached'`, `'detached'` | the same | same |
154
156
  | `locator.evaluate(fn)`, `evaluateAll(fn)` | not yet; `browser.evaluate(fn, selector)` with a CSS selector argument | missing |
155
157
  | `locator.screenshot()` | not yet; `app.screenshot(label)` captures the viewport | missing |
156
158
  | `innerHTML()`, `dispatchEvent()`, `selectText()`, `blur()`, `highlight()` | not yet; `browser.evaluate` | missing |
@@ -173,14 +175,14 @@ what to do today.
173
175
  | `toHaveText(['a', 'b'])`, `toContainText([...])` on several matches | the same: `toHaveText` needs the count to match, `toContainText` an ordered subsequence | same |
174
176
  | `toBeChecked({ checked: false })`, `toBeEnabled({ enabled })`, `toBeVisible({ visible })`, `toBeAttached({ attached })` | the same | same |
175
177
  | `{ 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 |
178
+ | `toBeChecked({ indeterminate })`, `{ useInnerText }` | not yet; `INVALID_ARGUMENT`, so the matcher never passes on its default | missing |
177
179
  | `toHaveAttribute('aria-selected', 'true')`, `'aria-expanded'` | `toBeSelected()`, `toBeExpanded()` | renamed |
178
180
  | a positive matcher on several matches | `LOCATOR_AMBIGUOUS` at once; narrow with `filter`, `first`, or `{ visible: true }` | by design |
179
181
  | `toBeAttached()` | the same; passes on a hidden match too | same |
180
182
  | `toBeEmpty()`, `toBeInViewport()`, `toBeEditable()`, `toHaveRole()`, `toHaveAccessibleDescription()` | not yet | missing |
181
183
  | `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
184
  | `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 |
185
+ | `expect(page).toHaveURL()`, `toHaveTitle()` | `expect(browser).toHaveURL()`, `toHaveTitle()`; `toHaveURL` takes `ignoreCase` | web only |
184
186
  | `toHaveScreenshot()`, `toMatchSnapshot()` | not yet; `agent.assert('...', { vision: true })` is a judgment, not a pixel diff | missing |
185
187
  | `toMatchAriaSnapshot()` | not yet | missing |
186
188
  | `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 +221,9 @@ what to do today.
219
221
  | Playwright | e2e | Status |
220
222
  | --- | --- | --- |
221
223
  | `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 |
224
+ | `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 |
225
+ | `route.continue({ url, method, headers, postData })`, `route.fallback()` | the same; a `url` goes through the navigation rule, and `fallback()` takes no overrides | web only |
226
+ | `route.fetch()`, `route.abort(code)`, `route.fallback(overrides)` | not yet; `abort()` takes no error code | missing |
225
227
  | `page.unroute()` | `browser.unroute()` | web only |
226
228
  | `page.waitForResponse(pattern)` | `browser.waitForResponse(pattern)`: a string or `RegExp`, no predicate function; `url`, `status`, `headers`, `json()`, `text()` | web only |
227
229
  | `response.ok()`, `response.body()`, `response.request()` | not yet; `status` and `text()` | missing |
@@ -232,7 +234,8 @@ what to do today.
232
234
  | `page.waitForEvent('popup')`, `context.on('page')` | not yet; a window the app opens is not driven | missing |
233
235
  | `page.on('console')`, `page.on('pageerror')` | not yet; assert on what the page shows | missing |
234
236
  | `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 |
237
+ | `page.addInitScript(script, arg)`, `context.addInitScript()` | `browser.addInitScript(script, arg)`, or `web({ initScripts })` for every test; pass `{ content }` as a plain string | web only |
238
+ | `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
239
  | `context.addCookies()`, `context.cookies()` | `browser.setCookies()`, `browser.cookies()` | web only |
237
240
  | `context.clearCookies()` | not yet; `app.clearState()` also clears other persisted state and recreates the context | missing |
238
241
  | `request` fixture | Node `fetch`, see [Testing APIs](/api-testing) | by design |
@@ -284,9 +287,9 @@ what to do today.
284
287
  | `--update-snapshots` | no equivalent; there is no pixel assertion | missing |
285
288
  | `--list` | `npx e2e list`, with the same selection flags | renamed |
286
289
  | `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 |
290
+ | `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
291
  | `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 |
292
+ | `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
293
 
291
294
  ## Page objects
292
295
 
@@ -421,8 +424,8 @@ Start with a flow whose interaction sequence changes often, such as a wizard
421
424
  or checkout; stable Playwright tests can stay where they are.
422
425
 
423
426
  <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.
427
+ <Card title="Goals" icon="pen-to-square" href="/goals">
428
+ Write test steps as goals instead of scripted selectors.
426
429
  </Card>
427
430
  <Card title="Web engine reference" icon="masks-theater" href="/reference/web">
428
431
  Every option and every `browser` method.
@@ -101,8 +101,8 @@ Start with one page object and the flows that use it; its setup code can
101
101
  become a fixture.
102
102
 
103
103
  <CardGroup cols={2}>
104
- <Card title="Writing tests" icon="pen-to-square" href="/writing-tests">
105
- Goals, checks, extraction, and the deterministic APIs.
104
+ <Card title="Goals" icon="pen-to-square" href="/goals">
105
+ Write test steps as goals instead of scripted selectors.
106
106
  </Card>
107
107
  <Card title="screen reference" icon="crosshairs" href="/reference/screen">
108
108
  Every query, refinement, action, and read.
package/docs/mobile.mdx CHANGED
@@ -3,8 +3,8 @@ title: Testing iOS and Android
3
3
  description: Set up a simulator or emulator target, write a device test, and know what differs from the web.
4
4
  ---
5
5
 
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).
6
+ `@e2e-dev/mobile` runs tests on iOS simulators, Android emulators, and
7
+ phones connected to your machine. It is built on [agent-device](https://github.com/callstack/agent-device).
8
8
  Tests use the same `agent`, `screen`, and `expect` APIs as browser tests.
9
9
  A test launches the configured app with `app.open()`; between tests the app
10
10
  stays where the last one left it.
@@ -178,6 +178,12 @@ mobile({ platform: 'ios', device: ['iPhone 17', 'iPhone 17 Pro'] })
178
178
  Without `device`, the engine uses booted devices up to the worker limit.
179
179
  If none are booted, it boots one. Two workers never drive the same device.
180
180
 
181
+ ## Physical devices
182
+
183
+ A phone connected to your machine works as a device too: name it in
184
+ `device`. Setup and the controls that need a simulator are on
185
+ [Physical devices](/physical-devices).
186
+
181
187
  ## Hosted devices
182
188
 
183
189
  A `device` can also be a `DeviceProvider`: an object that leases one hosted
@@ -560,6 +566,17 @@ daemon (`npx agent-device daemon stop`) and reboot the simulator
560
566
 
561
567
  Do what it says.
562
568
 
569
+ **`device.installApp()` on Android reports no bundle id or package.** The
570
+ build installed, but agent-device could not say which package it is, so
571
+ `app.open()` would have nothing to launch. agent-device 0.21.18 cannot parse
572
+ the manifest of an APK built with aapt2 (every current Android build), so it
573
+ falls back to the Android SDK's `aapt`, which it looks for under
574
+ `ANDROID_SDK_ROOT`, `ANDROID_HOME`, or `~/Android/Sdk`, not the macOS default
575
+ `~/Library/Android/sdk`. Without `aapt` it names only a package the install
576
+ added, and a build already on the emulator adds none. Set the target's
577
+ `app.bundleId`, or export `ANDROID_HOME` and stop the agent-device daemon
578
+ (`daemon stop` below) so the next run starts it with the variable.
579
+
563
580
  **A timeout on the device.** A simulator boot, runner connection, or app
564
581
  launch that times out on the device is `ENGINE_FAILURE` with agent-device's
565
582
  message and hint, never one of e2e's own timeout codes (`LAUNCH_TIMEOUT`,
@@ -589,7 +606,7 @@ xcrun simctl shutdown <udid> && xcrun simctl boot <udid>
589
606
  <Card title="Mobile engine reference" icon="mobile-screen" href="/reference/mobile">
590
607
  Every option, the `device` fixture, and the tool pack.
591
608
  </Card>
592
- <Card title="Writing tests" icon="pen-to-square" href="/writing-tests">
593
- Goals, checks, and the deterministic APIs, on any platform.
609
+ <Card title="Locators" icon="crosshairs" href="/locators">
610
+ Exact taps, swipes, and checks with `screen` and `expect`, on any platform.
594
611
  </Card>
595
612
  </CardGroup>
package/docs/models.mdx CHANGED
@@ -3,8 +3,12 @@ title: Models
3
3
  description: Configure a model with your own subscription, an API key, or a local server.
4
4
  ---
5
5
 
6
+ import { ModelProvider } from './snippets/model-provider.jsx';
7
+
6
8
  Agent steps use the model configured in `e2e.config.ts`. You can use an
7
9
  [existing subscription](/subscriptions), an API key, or a local server.
10
+ e2e uses the [AI SDK](https://ai-sdk.dev/providers). You can use
11
+ [all AI SDK providers that support tool calls](#ai-sdk-providers).
8
12
  `e2e init` offers these choices and writes the config for you. For the first
9
13
  setup, follow the [quickstart](/quickstart#use-a-subscription-or-api-key).
10
14
 
@@ -77,7 +81,8 @@ const agent = { model: openai('gpt-6-luna') };
77
81
  ```
78
82
 
79
83
  Use `agent` as `agents.default`. Other AI SDK providers work the same way,
80
- with their own package, model IDs, and authentication settings.
84
+ with their own package, model IDs, and authentication settings. See
85
+ [AI SDK providers](#ai-sdk-providers).
81
86
  </Tab>
82
87
  <Tab title="Local or self-hosted">
83
88
 
@@ -103,11 +108,6 @@ Use `agent` as `agents.default`. Add `apiKey` if the server requires one.
103
108
  Model IDs depend on the provider. Gateways usually use `provider/model`;
104
109
  direct providers use their own model IDs.
105
110
 
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
111
  ### Vercel OIDC instead of a key
112
112
 
113
113
  Without `AI_GATEWAY_API_KEY`, `gateway()` authenticates with a Vercel OIDC
@@ -145,6 +145,46 @@ An OIDC failure is the same `MODEL_PROVIDER_FAILED` as a bad key, and the AI
145
145
  SDK's message names only `AI_GATEWAY_API_KEY`. With no key set, check that the
146
146
  Vercel CLI is signed in and that `.vercel/project.json` exists.
147
147
 
148
+ ## AI SDK providers
149
+
150
+ Select a provider. The three steps below show the setup for that provider.
151
+
152
+ <ModelProvider show="select" />
153
+
154
+ The list shows the AI SDK providers that support tool calls. A provider that
155
+ is not in the list also works if it uses the AI SDK language model
156
+ specification v2 or later. Its model must support tool calls and images.
157
+
158
+ ### Install the provider package
159
+
160
+ Install `ai` and the provider package.
161
+
162
+ <ModelProvider show="install" />
163
+
164
+ ### Set the credentials
165
+
166
+ The provider reads its credentials from the environment. e2e does not load
167
+ `.env` files. Export the variables in your shell or CI, or load the file in
168
+ the config with [`process.loadEnvFile()`](/reference/config#loading).
169
+
170
+ <ModelProvider show="env" />
171
+
172
+ If a key is missing or wrong, the first agent step fails with
173
+ `MODEL_PROVIDER_FAILED`. Some providers read the key when the config loads.
174
+ For these providers, a missing key stops all runs with `CONFIG_LOAD_FAILED`.
175
+
176
+ ### Add the model to the config
177
+
178
+ Set the model in `agents.default`. The model ID is an example. Use a model
179
+ that supports tool calls and images.
180
+
181
+ <ModelProvider show="config" />
182
+
183
+ DeepSeek's V4 models (`@ai-sdk/deepseek`) think by default, and in that mode
184
+ they refuse the forced tool choice agent steps ask for. The step is sent again
185
+ with `auto` and an instruction to reply with tool calls only, and later steps
186
+ on that model start that way, so thinking can stay on.
187
+
148
188
  ## Several models in one config
149
189
 
150
190
  Give agents different names to use different models:
@@ -198,6 +238,12 @@ Model input is the redacted screen, the step instruction, and the
198
238
  screenshots the agent asks for. e2e reads nothing back from the provider
199
239
  after a response, so a run needs no state kept on the provider's side.
200
240
 
241
+ Every model request names e2e: the `User-Agent` starts with
242
+ `e2e/<version> (<platform>; <arch>)`, and `HTTP-Referer:
243
+ https://tester.army/e2e` with `X-Title: e2e` attribute the traffic to e2e on
244
+ the Vercel AI Gateway and OpenRouter. These replace the same headers set on
245
+ the provider instance.
246
+
201
247
  **OpenAI and Azure OpenAI.** Every request carries `store: false` and a
202
248
  prompt cache key. The provider keeps no copy of the conversation, a
203
249
  reasoning model's earlier turns travel inline as encrypted content, and the
@@ -233,7 +279,7 @@ organizations with zero data retention.
233
279
 
234
280
  <CardGroup cols={2}>
235
281
  <Card title="Subscriptions" icon="key" href="/subscriptions">
236
- Sign in with ChatGPT, Copilot, or SuperGrok.
282
+ Sign in with ChatGPT, Copilot, OpenCode Console, or SuperGrok.
237
283
  </Card>
238
284
  <Card title="Agents and personas" icon="users" href="/agents">
239
285
  Choose agents for tests and signed-in users.
@@ -0,0 +1,76 @@
1
+ ---
2
+ title: Physical devices
3
+ description: Run mobile tests on an iPhone or Android phone connected to your machine.
4
+ ---
5
+
6
+ agent-device drives a phone plugged into your machine the same way it
7
+ drives a simulator, so a physical device needs no other engine or option.
8
+ List what agent-device sees, then name the phone in `device` as it is
9
+ listed:
10
+
11
+ <CodeGroup>
12
+ ```bash npm
13
+ npx agent-device devices --platform ios
14
+ ```
15
+
16
+ ```bash pnpm
17
+ pnpm exec agent-device devices --platform ios
18
+ ```
19
+
20
+ ```bash bun
21
+ bunx agent-device devices --platform ios
22
+ ```
23
+ </CodeGroup>
24
+
25
+ ```ts
26
+ mobile({ platform: 'ios', device: 'QA iPhone' })
27
+ ```
28
+
29
+ Name a phone by its name, not its id. The engine reads a `device` string as
30
+ an id only when it is a simulator UDID or an `emulator-<port>` serial, so a
31
+ phone's UDID or adb serial is looked up as a name and not found. The first
32
+ device with a matching name wins, so give each phone a unique name: two
33
+ phones both called `iPhone` or `Pixel 8` would bind one phone to two
34
+ workers. Without `device`, a connected phone counts as booted and joins the
35
+ pool beside any booted simulators, so name it to be sure a run lands on it.
36
+
37
+ Set the phone up once:
38
+
39
+ - **iOS**: the phone is paired and trusted, unlocked, with Developer Mode
40
+ on. agent-device builds and signs its XCTest runner onto the phone, so set
41
+ `AGENT_DEVICE_IOS_TEAM_ID` to your team id and `AGENT_DEVICE_IOS_BUNDLE_ID`
42
+ to a unique reverse-DNS id (`com.example.agentdevice.runner`); the
43
+ signing profile must allow that id and `<id>.uitests`. Set them before the
44
+ first agent-device command, or run `npx agent-device daemon stop` after:
45
+ a running daemon keeps the environment it started with. The first run
46
+ builds the runner and takes longer. `app.appPath` must be a signed device
47
+ build: a simulator `.app` does not install on a phone.
48
+ - **Android**: USB debugging is on and the phone is authorized, listed as
49
+ `device` in `adb devices`. `app.appPath` is the same `.apk`.
50
+
51
+ `npx agent-device doctor` checks the setup, and
52
+ `npx agent-device help physical-device` covers iOS signing in detail.
53
+
54
+ Some device controls need a simulator or emulator, and fail on a phone with
55
+ agent-device's message:
56
+
57
+ - On an iPhone, every device setting is simulator-only: `app.permissions`
58
+ and `device.setPermission`, `app.clearState()`, `device.setNetwork`,
59
+ `device.setAirplaneMode`, `device.setLocation`, `device.clearLocation`,
60
+ `device.setAppearance`, the biometric methods, and `device.clearKeychain`.
61
+ So are `device.clipboard`, `device.setClipboard`, and `device.fold`. For a
62
+ fresh start, reinstall with `device.installApp(path, { reinstall: true })`.
63
+ - An older iPhone that `xcrun devicectl list devices` does not list is
64
+ driven without CoreDevice: taps, typing, and the screen work, but
65
+ installs, `--video`, `device.openLink`, and `app.launchArguments` fail.
66
+ Install the build from Xcode and pin `app.bundleId`.
67
+ - On an Android phone, `device.setLocation` is emulator-only, and
68
+ `device.setBiometrics('fingerprint', ...)` works only where the phone
69
+ exposes `cmd fingerprint`.
70
+ - `--video` on an iPhone is built from screenshots at about 15 frames a
71
+ second, and drops frames.
72
+ - A phone is not this machine, so `localhost` in the app is the phone
73
+ itself. Point the app at your machine's network address, or on Android
74
+ forward the port with `adb reverse tcp:<port> tcp:<port>`.
75
+
76
+ To run on phones in a device farm, see [Hosted devices](/mobile#hosted-devices).