e2e 0.17.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.
- package/README.md +10 -0
- package/dist/.build.json +1 -1
- package/dist/agent/ai-sdk.js +5 -4
- package/dist/agent/error.js +3 -3
- package/dist/agent/invocation.js +3 -4
- package/dist/agent/model/tool-choice.d.ts +9 -0
- package/dist/agent/model/tool-choice.js +14 -0
- package/dist/agent/observation-feed.js +3 -4
- package/dist/agent/phases.d.ts +7 -1
- package/dist/agent/phases.js +12 -3
- package/dist/agent/prompts.js +3 -0
- package/dist/agent/replay.js +18 -15
- package/dist/agent/step-cache.d.ts +2 -1
- package/dist/agent/step-cache.js +9 -5
- package/dist/agent/tool-loop.js +92 -5
- package/dist/cache/trace.js +6 -2
- package/dist/cli/bin.js +2 -2
- package/dist/cli/index.js +35 -24
- package/dist/cli/init/mcp-config.d.ts +2 -1
- package/dist/cli/init/mcp-config.js +8 -3
- package/dist/cli/init/sibling-versions.json +1 -1
- package/dist/cli/mcp.d.ts +3 -2
- package/dist/cli/mcp.js +52 -16
- package/dist/cli/run-output.d.ts +17 -0
- package/dist/cli/run-output.js +45 -0
- package/dist/collect/collect.js +8 -4
- package/dist/collect/registry.js +27 -16
- package/dist/config/compiled-files.d.ts +36 -0
- package/dist/config/compiled-files.js +50 -0
- package/dist/config/diagnose.js +21 -19
- package/dist/config/esm-hooks.d.ts +39 -44
- package/dist/config/esm-hooks.js +238 -63
- package/dist/config/load.d.ts +1 -7
- package/dist/config/load.js +23 -66
- package/dist/config/register.d.ts +8 -0
- package/dist/config/register.js +9 -0
- package/dist/config/specifiers.d.ts +19 -0
- package/dist/config/specifiers.js +33 -0
- package/dist/config/tsconfig.d.ts +56 -0
- package/dist/config/tsconfig.js +115 -0
- package/dist/config/typescript.d.ts +19 -0
- package/dist/config/typescript.js +253 -0
- package/dist/engine/contract.d.ts +1 -0
- package/dist/engine/index.d.ts +2 -0
- package/dist/engine/session.js +2 -2
- package/dist/expect/async.js +2 -1
- package/dist/expect/poll.js +2 -2
- package/dist/internal/errors.d.ts +10 -3
- package/dist/internal/errors.js +23 -8
- package/dist/internal/node-version.d.ts +16 -7
- package/dist/internal/node-version.js +50 -15
- package/dist/internal/package-manager.js +1 -1
- package/dist/internal/realm-slot.d.ts +5 -4
- package/dist/internal/redact.d.ts +2 -0
- package/dist/internal/redact.js +4 -0
- package/dist/internal/time.d.ts +21 -5
- package/dist/internal/time.js +35 -3
- package/dist/locator/engine.js +47 -11
- package/dist/locator/screen.js +31 -5
- package/dist/mcp/output.d.ts +59 -0
- package/dist/mcp/output.js +129 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +9 -12
- package/dist/mcp/session.d.ts +8 -1
- package/dist/mcp/session.js +55 -14
- package/dist/mcp/tools.js +7 -5
- package/dist/report/list.d.ts +11 -0
- package/dist/report/list.js +23 -6
- package/dist/run/artifacts.d.ts +4 -4
- package/dist/run/artifacts.js +3 -2
- package/dist/run/execute.d.ts +25 -5
- package/dist/run/execute.js +44 -20
- package/dist/run/failure-evidence.js +6 -2
- package/dist/run/in-process.js +51 -10
- package/dist/run/process-output.d.ts +73 -0
- package/dist/run/process-output.js +130 -0
- package/dist/run/provision.d.ts +2 -0
- package/dist/run/provision.js +1 -0
- package/dist/run/records.d.ts +4 -4
- package/dist/run/retry.d.ts +7 -0
- package/dist/run/retry.js +27 -11
- package/dist/run/runner.d.ts +8 -0
- package/dist/run/runner.js +32 -9
- package/dist/run/scheduler.js +255 -19
- package/dist/run/secrecy.d.ts +22 -19
- package/dist/run/secrecy.js +22 -18
- package/dist/run/serial.d.ts +73 -2
- package/dist/run/serial.js +239 -58
- package/dist/run/standalone.js +1 -1
- package/dist/run/trace-redaction.d.ts +5 -6
- package/dist/run/trace-redaction.js +5 -6
- package/dist/run/worker/entry.js +35 -14
- package/dist/run/worker/handle.js +10 -5
- package/dist/run/worker/protocol.d.ts +83 -3
- package/dist/run/worker/session.js +28 -0
- package/dist/telemetry/environment.d.ts +2 -1
- package/dist/telemetry/environment.js +12 -2
- package/dist/telemetry/project.d.ts +5 -6
- package/dist/telemetry/project.js +6 -11
- package/dist/telemetry/store.d.ts +5 -5
- package/dist/telemetry/store.js +5 -7
- package/dist/telemetry/telemetry.d.ts +2 -3
- package/dist/telemetry/telemetry.js +2 -4
- package/dist/types.d.ts +6 -3
- package/docs/agent-steps.mdx +1 -0
- package/docs/agents.mdx +1 -0
- package/docs/api-testing.mdx +2 -1
- package/docs/authentication.mdx +3 -2
- package/docs/browser.mdx +7 -6
- package/docs/bug-bash.mdx +2 -1
- package/docs/cache.mdx +2 -1
- package/docs/ci.mdx +3 -2
- package/docs/coding-agents.mdx +2 -1
- package/docs/core-concepts.mdx +208 -0
- package/docs/debugging.mdx +19 -9
- package/docs/decision-models.mdx +142 -0
- package/docs/example-projects.mdx +32 -0
- package/docs/executors.mdx +3 -0
- package/docs/explore.mdx +1 -0
- package/docs/github.mdx +2 -1
- package/docs/index.mdx +9 -5
- package/docs/integrations/eas.mdx +1 -0
- package/docs/integrations/index.mdx +1 -0
- package/docs/integrations/kernel.mdx +2 -1
- package/docs/migrate/cypress.mdx +26 -3
- package/docs/migrate/detox.mdx +24 -1
- package/docs/migrate/maestro.mdx +28 -4
- package/docs/migrate/playwright.mdx +27 -4
- package/docs/migrate/selenium.mdx +27 -3
- package/docs/mobile-ci.mdx +6 -5
- package/docs/mobile.mdx +79 -8
- package/docs/models.mdx +7 -2
- package/docs/quickstart.mdx +26 -15
- package/docs/reference/agent.mdx +13 -3
- package/docs/reference/app.mdx +1 -0
- package/docs/reference/cli.mdx +11 -5
- package/docs/reference/config.mdx +42 -6
- package/docs/reference/engine.mdx +22 -10
- package/docs/reference/environment.mdx +2 -1
- package/docs/reference/errors.mdx +11 -8
- package/docs/reference/expect.mdx +2 -1
- package/docs/reference/mcp.mdx +21 -8
- package/docs/reference/mobile.mdx +1 -0
- package/docs/reference/reporters.mdx +1 -0
- package/docs/reference/screen.mdx +19 -9
- package/docs/reference/test.mdx +21 -1
- package/docs/reference/web.mdx +8 -2
- package/docs/security.mdx +33 -27
- package/docs/subscriptions.mdx +1 -0
- package/docs/telemetry.mdx +8 -5
- package/docs/tools.mdx +1 -0
- package/docs/{starting-your-app.mdx → web.mdx} +157 -11
- package/docs/writing-an-engine.mdx +2 -1
- package/package.json +11 -7
- package/skills/e2e/SKILL.md +3 -1
- package/skills/e2e/references/agent.md +11 -0
- package/skills/e2e/references/debugging.md +1 -1
- package/skills/e2e/references/mcp.md +8 -3
- package/skills/e2e/references/running.md +2 -1
- package/skills/e2e/references/setup.md +9 -1
- package/skills/e2e/references/writing-tests.md +1 -1
- package/docs/assertions.mdx +0 -94
- package/docs/goals.mdx +0 -176
- package/docs/locators.mdx +0 -64
- package/docs/physical-devices.mdx +0 -76
- package/docs/viewports.mdx +0 -146
package/docs/security.mdx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Security model
|
|
3
3
|
description: What the model can access, how the runner handles secrets, and the limits of its protections.
|
|
4
|
+
icon: "shield-halved"
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
Test code and configuration run with your operating-system permissions.
|
|
@@ -106,7 +107,9 @@ redaction and disables pixels for the rest of the attempt.
|
|
|
106
107
|
After any secret fill, for the rest of the attempt, screenshot and pixel
|
|
107
108
|
tools are unavailable, `app.screenshot()` is denied, and the runner omits
|
|
108
109
|
assertion and failure screenshots. The app could display the value anywhere,
|
|
109
|
-
beyond the field's masked rectangle.
|
|
110
|
+
beyond the field's masked rectangle. An `act` or `vision: true` step whose
|
|
111
|
+
semantic capture times out before the step does fails with `POLICY_DENIED`
|
|
112
|
+
instead of reading a fallback screenshot.
|
|
110
113
|
|
|
111
114
|
A test that restores a [session](#sessions) whose setup filled a secret keeps
|
|
112
115
|
this protection. A setup that signs in without filling a secret, for example
|
|
@@ -130,13 +133,14 @@ password on screen is not masked in pixels.
|
|
|
130
133
|
### Redaction
|
|
131
134
|
|
|
132
135
|
The runner replaces every occurrence of a registered secret value with
|
|
133
|
-
`<secret:name>` in model input, reports,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
a `secrets.get()` handle: test and `describe`
|
|
137
|
-
`app.open` URL, a locator's text),
|
|
138
|
-
instruction, the strings in `agent.act`
|
|
139
|
-
`e2e explore` goal.
|
|
136
|
+
`<secret:name>` in model input, reports, what your code prints during
|
|
137
|
+
`e2e run` (tests, the config's and test files' top-level code, reporters),
|
|
138
|
+
trace text, and text downloads. That includes a value the test passes as a
|
|
139
|
+
plain string rather than a `secrets.get()` handle: test and `describe`
|
|
140
|
+
titles, step labels (an `app.open` URL, a locator's text), a `fill`, an
|
|
141
|
+
`agent.act` or `agent.assert` instruction, the strings in `agent.act`
|
|
142
|
+
params, `agentContext`, and an `e2e explore` goal. A plain string is not a
|
|
143
|
+
secret fill: it is protected as text only, and screenshots stay available. Titles are redacted as the test file is collected, so
|
|
140
144
|
the test id, `--grep`, the reporters, and the artifact and failure-page file
|
|
141
145
|
names all see `<secret:name>`, and two titles in one file that differ only
|
|
142
146
|
in a secret value are a duplicate title path. A custom executor's
|
|
@@ -155,6 +159,9 @@ replay cache descriptor, or an end anchor reads it. Redaction rewrites only
|
|
|
155
159
|
what the runner reports; assertions still compare the text the page shows.
|
|
156
160
|
Secret values and their cut parts never enter cache entries.
|
|
157
161
|
|
|
162
|
+
Output printed while the config loads appears once it has loaded. If the
|
|
163
|
+
load fails, that output is withheld, since the config's secrets are unknown.
|
|
164
|
+
|
|
158
165
|
A static value shorter than 6 characters is `INVALID_CONFIG`, and a provider
|
|
159
166
|
returning one fails the fill: every occurrence of so short a value would take
|
|
160
167
|
ordinary text with it.
|
|
@@ -176,31 +183,30 @@ ordinary text with it.
|
|
|
176
183
|
rule for text cut at a length limit.
|
|
177
184
|
- A title is redacted when its file is collected, before any provider
|
|
178
185
|
runs, so a value a secret provider returns is not redacted from titles.
|
|
179
|
-
- A Playwright trace from a session no secret reached is kept as recorded,
|
|
180
|
-
so a value the test only spelled into a URL or a locator stays in its
|
|
181
|
-
text.
|
|
182
186
|
- When tests run in the runner's own process, with the config or tests
|
|
183
187
|
passed in memory by an embedding host, their console output is not
|
|
184
188
|
redacted.
|
|
185
|
-
- A Playwright trace
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
- A Playwright trace is rewritten before it is kept whenever its session
|
|
190
|
+
knows a secret value, filled or not: every static one, and a provider's
|
|
191
|
+
once the session resolved it. Any run of 8 or more characters of a value,
|
|
192
|
+
its whitespace collapsed or not, becomes `<secret:name>`, so unrelated
|
|
193
|
+
text that shares 8 characters with a value is masked too. A base64 or
|
|
194
|
+
base64url run that decodes to a value or such a fragment (a basic-auth
|
|
195
|
+
header, a cookie, a token segment) is replaced whole. After a fill,
|
|
196
|
+
screencast frames are dropped; otherwise they are kept. An image the app
|
|
197
|
+
served that draws the secret survives. A trace the runner cannot rewrite is deleted and
|
|
193
198
|
the attempt records `TRACE_WITHHELD`.
|
|
194
199
|
- A video masks nothing and is labeled `redaction: "incomplete"`. It is
|
|
195
200
|
never recorded unless you ask. A recording a hosted browser or
|
|
196
201
|
device service keeps stays with that service; the report holds only its
|
|
197
202
|
URL.
|
|
198
|
-
- A download is what the app served.
|
|
199
|
-
|
|
200
|
-
extension) is
|
|
201
|
-
`redaction: "complete"`.
|
|
202
|
-
|
|
203
|
-
|
|
203
|
+
- A download is what the app served. When the session knows a secret
|
|
204
|
+
value, every whole occurrence of it in a text download (`text/*` or JSON
|
|
205
|
+
by file extension) is replaced, as in reports, and the download is labeled
|
|
206
|
+
`redaction: "complete"`. Fragments and base64 runs are matched in traces
|
|
207
|
+
only. Any other download, a binary, one that is not valid UTF-8 or cannot
|
|
208
|
+
be rewritten, or one from a session that knows no secret value, is kept
|
|
209
|
+
as served and labeled `redaction: "incomplete"`.
|
|
204
210
|
- The `headers` a protected preview needs are recorded in the trace as
|
|
205
211
|
request headers. Share that trace as you would the bypass secret.
|
|
206
212
|
- `command.log` is captured as the process writes it.
|
|
@@ -258,9 +264,9 @@ project-relative path you configure.
|
|
|
258
264
|
| --- | --- | --- |
|
|
259
265
|
| `report.json` | The run record. Errors, reasoning excerpts, and action summaries pass the redactor. | complete |
|
|
260
266
|
| `artifacts/.../*.png` | Screenshots. Secure fields are masked before screenshots are returned or saved as artifacts. | complete |
|
|
261
|
-
| `artifacts/.../trace.zip` | Playwright trace, rewritten
|
|
267
|
+
| `artifacts/.../trace.zip` | Playwright trace, rewritten whenever its session knows a secret value; screencast frames are dropped after a fill only. | not-required (no secret value), complete, or deleted |
|
|
262
268
|
| `artifacts/.../*.webm`, `*.mp4` | Video, opt-in through `video` or `--video`; `retain-on-failure` keeps only the attempts that did not pass. | incomplete |
|
|
263
|
-
| `artifacts/.../downloads/*` | Files the app served through a download. Text ones are rewritten
|
|
269
|
+
| `artifacts/.../downloads/*` | Files the app served through a download. Text ones are rewritten whenever the session knows a secret value; the rest are kept as served. | complete or incomplete |
|
|
264
270
|
| `artifacts/.../failure/screen.txt` | The redacted tree at the moment of failure. | complete |
|
|
265
271
|
| `ai-trace.json` | Every model call, opt-in via `--ai-trace`. Prompts are the redacted ones the model saw; inline image bytes are replaced by their size. | as the prompt |
|
|
266
272
|
| `sessions/<runId>/` | Encrypted sessions, deleted at cleanup. | encrypted |
|
package/docs/subscriptions.mdx
CHANGED
package/docs/telemetry.mdx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Telemetry
|
|
3
3
|
description: What the CLI sends, what it never sends, and how to turn it off.
|
|
4
|
+
icon: "chart-line"
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
The `e2e` CLI sends anonymous usage data so we can see which commands,
|
|
@@ -72,7 +73,7 @@ Who ran it:
|
|
|
72
73
|
| Property | Value |
|
|
73
74
|
| --- | --- |
|
|
74
75
|
| `distinct_id` | A random id stored in the preferences file. CI uses `ci:<vendor>` instead, and a platform that runs e2e for its users uses `fleet:<name>` when it sets `E2E_TELEMETRY_FLEET` |
|
|
75
|
-
| `project_id` | A hash derived from the repository's root commit, shared across full clones.
|
|
76
|
+
| `project_id` | A hash derived from the repository's root commit, shared across full clones. `null` outside git and in a shallow clone |
|
|
76
77
|
| `session_id` | A random id for this one invocation |
|
|
77
78
|
| `first_run`, `days_since_first_run` | Whether this command created the preferences file, and whole days since it was created. `null` in CI and in a fleet, where no machine persists, and for a preferences file from before the time was recorded |
|
|
78
79
|
|
|
@@ -181,16 +182,18 @@ listed above.
|
|
|
181
182
|
## In CI
|
|
182
183
|
|
|
183
184
|
CI runs are attributed to the CI vendor rather than to a machine, no
|
|
184
|
-
preferences file is written on the runner, and no notice is printed.
|
|
185
|
-
|
|
185
|
+
preferences file is written on the runner, and no notice is printed. A coding
|
|
186
|
+
agent that sets `CI` in its shell, with no CI vendor around it, counts as the
|
|
187
|
+
machine it runs on. Set `E2E_TELEMETRY_DISABLED: '1'` in the job's `env` to
|
|
188
|
+
send nothing from CI.
|
|
186
189
|
|
|
187
190
|
## In a fleet
|
|
188
191
|
|
|
189
192
|
A platform that runs e2e on its users' behalf starts each run on a fresh
|
|
190
193
|
machine, and every fresh machine would otherwise count as a new user. Set
|
|
191
194
|
`E2E_TELEMETRY_FLEET=<name>` in those machines and they are attributed to
|
|
192
|
-
`fleet:<name>` instead, like CI: no preferences file
|
|
193
|
-
|
|
195
|
+
`fleet:<name>` instead, like CI: no preferences file and no notice. Or set
|
|
196
|
+
`E2E_TELEMETRY_DISABLED=1` and send nothing.
|
|
194
197
|
|
|
195
198
|
## Where it goes
|
|
196
199
|
|
package/docs/tools.mdx
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
3
|
-
description: Point the
|
|
2
|
+
title: Web
|
|
3
|
+
description: Point the web engine at your app or let the runner start it, and run a suite at several viewport sizes.
|
|
4
|
+
icon: "globe"
|
|
4
5
|
---
|
|
5
6
|
|
|
7
|
+
`@e2e-dev/web` runs tests in a browser against your app's URL. This page
|
|
8
|
+
covers getting the app running for a run and testing it at different
|
|
9
|
+
sizes. For routes, cookies, dialogs, downloads, and the rest of the
|
|
10
|
+
`browser` fixture, see [Working with the browser](/browser).
|
|
11
|
+
|
|
12
|
+
## Starting your app
|
|
13
|
+
|
|
6
14
|
Set your app's URL in the target's `app`. If you also provide a start
|
|
7
15
|
command, the runner starts the app, waits until it is ready, and stops it
|
|
8
16
|
when the run ends.
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
### Point at a running app
|
|
11
19
|
|
|
12
20
|
Import `web` from `@e2e-dev/web` and put this in the exported
|
|
13
21
|
config object:
|
|
@@ -25,7 +33,7 @@ export default {
|
|
|
25
33
|
you can change the address for a run without editing the file. URLs without
|
|
26
34
|
a scheme use HTTPS, or HTTP for loopback hosts.
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
### Let the runner start it
|
|
29
37
|
|
|
30
38
|
Add `app.command` and the runner spawns the dev server, waits until `app.url`
|
|
31
39
|
answers, and stops it when the run ends, fails, or is interrupted:
|
|
@@ -79,7 +87,7 @@ temp-directory variables, and `env`, so pass what the app needs explicitly.
|
|
|
79
87
|
See the
|
|
80
88
|
[command reference](/reference/config#command) for all options.
|
|
81
89
|
|
|
82
|
-
|
|
90
|
+
#### Let the runner pick the port
|
|
83
91
|
|
|
84
92
|
Use port `0` to run multiple checkouts without a port conflict. The runner
|
|
85
93
|
chooses a free port and replaces `{port}` in the command's `args` and `env`.
|
|
@@ -98,7 +106,7 @@ valid when the port changes. Use `127.0.0.1` or `[::1]`, matching the address
|
|
|
98
106
|
the command binds; `localhost:0` is rejected. See
|
|
99
107
|
[free ports](/reference/config#free-ports) for details.
|
|
100
108
|
|
|
101
|
-
|
|
109
|
+
#### Next.js dev targets
|
|
102
110
|
|
|
103
111
|
Next.js 16 blocks cross-origin requests to its dev resources. A target that
|
|
104
112
|
opens `127.0.0.1` or `[::1]` while `next dev` identifies as `localhost`
|
|
@@ -122,7 +130,7 @@ Use `'[::1]'` in the list when `app.url` opens `http://[::1]:0`.
|
|
|
122
130
|
A target on `localhost` needs no entry, because `next dev` identifies as
|
|
123
131
|
`localhost`. A production `next start` target is unaffected either way.
|
|
124
132
|
|
|
125
|
-
|
|
133
|
+
### Dependencies
|
|
126
134
|
|
|
127
135
|
<Note>
|
|
128
136
|
This version starts only `app.command` (one process for every target that
|
|
@@ -132,7 +140,7 @@ have `app.command` start a script that brings them up and serves the app. A
|
|
|
132
140
|
services API returns in a later release.
|
|
133
141
|
</Note>
|
|
134
142
|
|
|
135
|
-
|
|
143
|
+
### Several targets
|
|
136
144
|
|
|
137
145
|
Two browsers on one app are two targets that each name it:
|
|
138
146
|
|
|
@@ -155,9 +163,9 @@ test runs on every target unless it is scoped with `platforms` or
|
|
|
155
163
|
`requires`; `--target <name>` selects at run time. A device target sits
|
|
156
164
|
beside a browser target in the same list; see the
|
|
157
165
|
[Mobile engine reference](/reference/mobile). One target per screen size is
|
|
158
|
-
how a suite runs at several [viewports](
|
|
166
|
+
how a suite runs at several [viewports](#viewports).
|
|
159
167
|
|
|
160
|
-
|
|
168
|
+
### Protected previews
|
|
161
169
|
|
|
162
170
|
Use `headers` to access protected previews or skip a tunnel's interstitial
|
|
163
171
|
page. For example, a Vercel preview can use a protection bypass header:
|
|
@@ -221,11 +229,149 @@ workers. Playwright traces include request headers, so a protected-preview
|
|
|
221
229
|
trace can contain your bypass secret. Restrict access to those traces.
|
|
222
230
|
</Note>
|
|
223
231
|
|
|
232
|
+
## Viewports
|
|
233
|
+
|
|
234
|
+
Set a viewport on the target to run a suite at that size. Use
|
|
235
|
+
`browser.setViewport()` to resize during a test. The agent uses the current size
|
|
236
|
+
and cannot resize it.
|
|
237
|
+
|
|
238
|
+
### One size per target
|
|
239
|
+
|
|
240
|
+
`viewport` sets the initial size of each attempt, in pixels. The default is
|
|
241
|
+
1280 by 720. Add one target for each size you want to test:
|
|
242
|
+
|
|
243
|
+
```ts title="e2e.config.ts"
|
|
244
|
+
import type { E2EConfig } from 'e2e';
|
|
245
|
+
import { web } from '@e2e-dev/web';
|
|
246
|
+
|
|
247
|
+
const app = { url: 'http://localhost:3000' };
|
|
248
|
+
|
|
249
|
+
export default {
|
|
250
|
+
targets: [
|
|
251
|
+
{ name: 'desktop', engine: web(), app },
|
|
252
|
+
{ name: 'tablet', engine: web({ viewport: { width: 820, height: 1180 } }), app },
|
|
253
|
+
{ name: 'phone', engine: web({ browser: 'webkit', viewport: { width: 390, height: 844 } }), app },
|
|
254
|
+
],
|
|
255
|
+
} satisfies E2EConfig;
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Each test runs once per target, with a separate result labeled `desktop`,
|
|
259
|
+
`tablet`, or `phone`. Run one size with `npx e2e run --target phone`, or list
|
|
260
|
+
the selected test-target pairs with `npx e2e list`.
|
|
261
|
+
|
|
262
|
+
To have the runner start one dev server for every size, give every target
|
|
263
|
+
the same `app.command`: targets declaring one command share one process; see
|
|
264
|
+
[several targets](#several-targets). Every retry starts at
|
|
265
|
+
the target's configured size.
|
|
266
|
+
|
|
267
|
+
### Fill the window
|
|
268
|
+
|
|
269
|
+
`viewport: null` sets no size, so the page fills the browser window. The
|
|
270
|
+
agent, screenshots, and videos use that size. Use it in a headed run, or
|
|
271
|
+
with a hosted browser whose live view would show a fixed 1280 by 720 page
|
|
272
|
+
in one corner:
|
|
273
|
+
|
|
274
|
+
```ts title="e2e.config.ts"
|
|
275
|
+
{ name: 'window', engine: web({ viewport: null }), app }
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The page takes the window's size, minus the toolbar in a headed browser. A
|
|
279
|
+
local headed launch opens a small window; a hosted browser uses the window
|
|
280
|
+
size you requested from the service. `browser.setViewport` still works.
|
|
281
|
+
|
|
282
|
+
### Resize inside a test
|
|
283
|
+
|
|
284
|
+
`browser.setViewport` resizes the page for the rest of the attempt, through
|
|
285
|
+
`app.restart()` and `app.clearState()`:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { test } from '@e2e-dev/web';
|
|
289
|
+
import { expect } from 'e2e';
|
|
290
|
+
|
|
291
|
+
test('the menu collapses on a narrow screen', async ({ app, screen, browser }) => {
|
|
292
|
+
await app.open('/');
|
|
293
|
+
await expect(screen.getByRole('navigation')).toBeVisible();
|
|
294
|
+
|
|
295
|
+
await browser.setViewport({ width: 390, height: 844 });
|
|
296
|
+
await expect(screen.getByRole('button', 'Menu')).toBeVisible();
|
|
297
|
+
await expect(screen.getByRole('navigation')).toBeHidden();
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Use this when one test needs to check a layout before and after resizing.
|
|
302
|
+
For a whole suite, configure separate targets instead.
|
|
303
|
+
|
|
304
|
+
Call it before `app.open` to load the first page at that size, so the app
|
|
305
|
+
never renders its desktop layout first:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
await browser.setViewport({ width: 390, height: 844 });
|
|
309
|
+
await app.open('/');
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The resize appears as a step in the report. To check the exact width, read
|
|
313
|
+
it from the page:
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
const width = await browser.evaluate(() => window.innerWidth);
|
|
317
|
+
expect(width).toBe(390);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### What the agent sees
|
|
321
|
+
|
|
322
|
+
The agent receives the current viewport size with each observation.
|
|
323
|
+
Screenshots cover that viewport. Off-screen elements can still appear in
|
|
324
|
+
the tree, and tapping them scrolls them into view. The agent can scroll to
|
|
325
|
+
reveal lazy-loaded content.
|
|
326
|
+
|
|
327
|
+
Resize before calling `agent.act`:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
test('the mobile menu reaches pricing', async ({ app, agent, browser }) => {
|
|
331
|
+
await browser.setViewport({ width: 390, height: 844 });
|
|
332
|
+
await app.open('/');
|
|
333
|
+
await agent.act('open the navigation menu and go to Pricing');
|
|
334
|
+
await expect(browser).toHaveURL('/pricing');
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Scoping a test to one size
|
|
339
|
+
|
|
340
|
+
`platforms` and `requires` cannot select a viewport; all Playwright targets
|
|
341
|
+
use platform `web`. To skip a test at some sizes, check the width in the
|
|
342
|
+
test:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
test('the sidebar stays open', async ({ app, screen, browser }) => {
|
|
346
|
+
await app.open('/');
|
|
347
|
+
test.skip((await browser.evaluate(() => window.innerWidth)) < 768, 'desktop layout only');
|
|
348
|
+
await expect(screen.getByRole('complementary')).toBeVisible();
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
To keep a whole group of size-specific tests apart, give them their own
|
|
353
|
+
config and run it with `--config`.
|
|
354
|
+
|
|
355
|
+
### Limits
|
|
356
|
+
|
|
357
|
+
A narrow viewport tests responsive layout. It still uses a desktop browser
|
|
358
|
+
with a scale factor of 1, desktop pointer behavior, and its usual user agent.
|
|
359
|
+
It does not emulate touch, color scheme, or reduced-motion preferences.
|
|
360
|
+
Use the [device engine](/mobile) for tests on a simulator or emulator.
|
|
361
|
+
|
|
362
|
+
A video keeps the size the page had when it started recording; set
|
|
363
|
+
`web({ screencast: { size } })` for one fixed size. A browser provider that
|
|
364
|
+
records, such as [Kernel](/integrations/kernel#recordings), records the whole
|
|
365
|
+
screen instead.
|
|
366
|
+
|
|
224
367
|
<CardGroup cols={2}>
|
|
368
|
+
<Card title="Working with the browser" icon="globe" href="/browser">
|
|
369
|
+
Routes, cookies, dialogs, downloads, and the rest of the `browser` fixture.
|
|
370
|
+
</Card>
|
|
225
371
|
<Card title="Config reference" icon="sliders" href="/reference/config#the-app-under-test">
|
|
226
372
|
`app`, `command`, and every app key.
|
|
227
373
|
</Card>
|
|
228
374
|
<Card title="Web engine reference" icon="masks-theater" href="/reference/web">
|
|
229
|
-
Browser, viewport, `connect`, `headers`, `basicAuth
|
|
375
|
+
Browser, viewport, `connect`, `headers`, `basicAuth`, and every `browser` method.
|
|
230
376
|
</Card>
|
|
231
377
|
</CardGroup>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Writing an engine
|
|
3
3
|
description: Implement the e2e engine contract for another UI platform.
|
|
4
|
+
icon: "gear"
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
An engine connects `screen`, `app`, and agent calls to a UI platform through
|
|
@@ -40,7 +41,7 @@ the engine's capabilities when the config loads.
|
|
|
40
41
|
This skeleton assumes a `createClient` adapter for your platform. Its nodes
|
|
41
42
|
must follow the semantic tree contract described below.
|
|
42
43
|
|
|
43
|
-
```ts title="src/index.ts"
|
|
44
|
+
```ts title="src/index.ts" expandable
|
|
44
45
|
import { ConfigurationError, defineEngine, resolveExpression, type EngineHandle } from 'e2e/engine';
|
|
45
46
|
|
|
46
47
|
export function hyperdrive(options: { device: string }): EngineHandle {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "e2e",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "SDK, runner, and CLI for agentic end-to-end testing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"e2e",
|
|
@@ -79,14 +79,18 @@
|
|
|
79
79
|
"NOTICE"
|
|
80
80
|
],
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@ai-sdk/provider": "4.0.
|
|
82
|
+
"@ai-sdk/provider": "4.0.21",
|
|
83
83
|
"@clack/prompts": "1.8.1",
|
|
84
|
-
"@
|
|
85
|
-
"@
|
|
84
|
+
"@jridgewell/remapping": "2.3.5",
|
|
85
|
+
"@modelcontextprotocol/server": "2.3.0",
|
|
86
|
+
"@oxc-project/runtime": "0.152.0",
|
|
87
|
+
"@vitest/expect": "5.0.3",
|
|
86
88
|
"commander": "15.0.0",
|
|
89
|
+
"get-tsconfig": "4.14.3",
|
|
90
|
+
"oxc-parser": "0.152.0",
|
|
91
|
+
"oxc-transform": "0.152.0",
|
|
87
92
|
"picocolors": "1.1.1",
|
|
88
93
|
"pngjs": "7.0.0",
|
|
89
|
-
"tsx": "4.23.14",
|
|
90
94
|
"zod": "4.6.1"
|
|
91
95
|
},
|
|
92
96
|
"peerDependencies": {
|
|
@@ -123,7 +127,7 @@
|
|
|
123
127
|
"@ai-sdk/openai": "4.0.71",
|
|
124
128
|
"@ai-sdk/openai-compatible": "3.0.53",
|
|
125
129
|
"@ai-sdk/xai": "5.0.4",
|
|
126
|
-
"@e2e-dev/web": "0.
|
|
130
|
+
"@e2e-dev/web": "0.13.0",
|
|
127
131
|
"@modelcontextprotocol/client": "2.2.0",
|
|
128
132
|
"@openrouter/ai-sdk-provider": "3.1.0",
|
|
129
133
|
"@types/node": "26.6.2",
|
|
@@ -135,7 +139,7 @@
|
|
|
135
139
|
"vitest": "5.0.1"
|
|
136
140
|
},
|
|
137
141
|
"engines": {
|
|
138
|
-
"node": "
|
|
142
|
+
"node": "^22.22.3 || >=24.8.0"
|
|
139
143
|
},
|
|
140
144
|
"scripts": {
|
|
141
145
|
"build": "node scripts/prepare-build.ts && tsc --project tsconfig.build.json && node scripts/write-sibling-versions.ts && node ../../scripts/stamp-dist.ts",
|
package/skills/e2e/SKILL.md
CHANGED
|
@@ -61,7 +61,9 @@ one; the installed CLI prints the same text with `npx e2e guide <topic>`
|
|
|
61
61
|
(`e2e guide` alone prints this page). For anything the topics do not cover,
|
|
62
62
|
the full documentation ships in the `docs/` directory of the installed `e2e`
|
|
63
63
|
package (`node_modules/e2e/docs` in a single-package project); a link such as
|
|
64
|
-
`/reference/cli` is `docs/reference/cli.mdx`.
|
|
64
|
+
`/reference/cli` is `docs/reference/cli.mdx`. Complete projects for Vite,
|
|
65
|
+
Next.js, Expo, and SwiftUI, each with its config, scripts, and a passing
|
|
66
|
+
suite, are in https://github.com/tester-army/e2e/tree/main/examples.
|
|
65
67
|
|
|
66
68
|
| Topic | File | Read it when |
|
|
67
69
|
| --- | --- | --- |
|
|
@@ -29,6 +29,14 @@ Subscription logins and local models:
|
|
|
29
29
|
[setup](setup.md#subscriptions-and-api-keys). Keep `ai@^7` installed with
|
|
30
30
|
any provider.
|
|
31
31
|
|
|
32
|
+
Decision models pick actions from choices the executor builds: install
|
|
33
|
+
`@e2e-dev/decision` and put `decisionExecutor({ model:
|
|
34
|
+
typeSafeAi.decisionModel('jev-latest'), textModel: openrouter('inception/mercury-2.5') })`
|
|
35
|
+
under `executor`. Tests stay plain language with no params; the text model
|
|
36
|
+
writes field values. See the shipped `docs/decision-models.mdx` or
|
|
37
|
+
[the online guide](https://e2e.tester.army/docs/decision-models) for setup,
|
|
38
|
+
gates, and limits.
|
|
39
|
+
|
|
32
40
|
- Pass a model instance, not a string (`INVALID_CONFIG`).
|
|
33
41
|
- An agents entry is one plain object of `model`, `judge`, `system`,
|
|
34
42
|
`context`, `tools`, `maxSteps`, `maxModelCalls`, `judgmentTimeout`,
|
|
@@ -162,6 +170,9 @@ expect(data.titles).toContain('Buy milk');
|
|
|
162
170
|
- Judgments see the assertion and the current screen only, never prior
|
|
163
171
|
steps or the act loop's summaries; malformed output gets one repair
|
|
164
172
|
round, then `MODEL_OUTPUT_INVALID`.
|
|
173
|
+
- A value shown in more than one place (a total in the summary and on the
|
|
174
|
+
pay button) must agree everywhere, or the judgment fails. Name the one
|
|
175
|
+
you mean (`'the order summary total is $42.00'`) when only it matters.
|
|
165
176
|
- `waitFor` observes every `interval` (default 3 s), judges only when the
|
|
166
177
|
screen changed, and is `STEP_TIMEOUT` after `timeout` (default 30 s).
|
|
167
178
|
- `extract` takes any Standard Schema validator (zod works); the model sees
|
|
@@ -65,7 +65,7 @@ jq '.run.results[] | select(.selected and .status != "passed") | .attempts[-1]
|
|
|
65
65
|
| `AUTOMATION_UNSUPPORTED` | The step needs an interaction the engine's toolset lacks (drag on a device, say); blocked, exit 1 | Do that step with `screen` actions, or run the test on a target that supports it |
|
|
66
66
|
| `STEP_TIMEOUT`, `STEP_BUDGET_EXHAUSTED` | Goal too big or ambiguous, or a slow provider | Split the goal, use on-screen wording, add vocabulary via `agents.<name>.context` or `agentContext`; raise `maxSteps` or `maxModelCalls` (budget), the config `timeout` (`act`), `judgmentTimeout` (judgments); `--debug` |
|
|
67
67
|
| `CONTEXT_OVERFLOW` | Screen plus step history did not fit the model's context window (`act` after one shrink-and-retry; a judgment on the first overflow) | Lower `agents.<name>.maxObservationBytes`, split the step, or pick a larger-window model |
|
|
68
|
-
| `POLICY_DENIED` | A URL whose scheme is not `http:` or `https:` (`file:`, `view-source:`, `data:`); a password `Secret` into a non-password sink; reading a secure field, `toHaveValue`, `toHaveText`, `toContainText`, `toHaveAttribute` included; `app.screenshot()` after a secret fill | http(s) or `about:blank` only; passwords into password inputs only; assert the outcome, not the value; screenshot before filling secrets |
|
|
68
|
+
| `POLICY_DENIED` | A URL whose scheme is not `http:` or `https:` (`file:`, `view-source:`, `data:`); a password `Secret` into a non-password sink; reading a secure field, `toHaveValue`, `toHaveText`, `toContainText`, `toHaveAttribute` included; `app.screenshot()` after a secret fill, or an `act` or `vision: true` step whose tree read timed out after one | http(s) or `about:blank` only; passwords into password inputs only; assert the outcome, not the value; screenshot before filling secrets |
|
|
69
69
|
| `UNSUPPORTED_CAPABILITY` | A fixture the engine lacks (`browser` on a device), `schema` or `vision` on `act`, an action the surface lacks | Declare `requires: ['browser']`; drop the option or action |
|
|
70
70
|
| `SESSION_UNAVAILABLE`, `SESSION_CONTRACT` | `session: 'x'` with no setup saving `x`; a setup that skipped a declared name. `SESSION_MISMATCH`, `SESSION_EXPIRED`, `SESSION_INVALID`: the stored session is another run's or app's, expired, or corrupt | Add or fix the `test.setup`; rerun it |
|
|
71
71
|
| `ONLY_IN_CI` | `test.only` reached CI | Remove it |
|
|
@@ -24,8 +24,9 @@ No model is needed: without the optional `ai` package, sessions open and the
|
|
|
24
24
|
deterministic tools work.
|
|
25
25
|
|
|
26
26
|
Flags: `--config <path>` names the default config file, `--target <name>`
|
|
27
|
-
fixes the target every session opens on, `--
|
|
28
|
-
simulator
|
|
27
|
+
fixes the target every session opens on, `--headed` shows the browser or
|
|
28
|
+
simulator when the engine supports it in sessions that do not set `headed`
|
|
29
|
+
(sessions are headless by default, as in `e2e run`), `--max-sessions <n>`
|
|
29
30
|
sets how many sessions may be open at once (default 4, 1 through 16).
|
|
30
31
|
|
|
31
32
|
## Tools
|
|
@@ -34,7 +35,7 @@ Four tools; everything a session can do is a catalog behind `call`.
|
|
|
34
35
|
|
|
35
36
|
| Tool | Does |
|
|
36
37
|
| --- | --- |
|
|
37
|
-
| `open_session` | Loads the config (`config` names another file; default the nearest `e2e.config.ts`), starts the declared app command if any, boots the engine, opens the app URL, and returns the session id, the catalog, and the first observation. `target` is required when the config declares several. Each session has its own browser or device. |
|
|
38
|
+
| `open_session` | Loads the config (`config` names another file; default the nearest `e2e.config.ts`), starts the declared app command if any, boots the engine, opens the app URL, and returns the session id, the catalog, and the first observation. `target` is required when the config declares several. `headed: true` shows the browser or simulator when the user wants to watch; without it the session follows the server's `--headed`, headless by default. Each session has its own browser or device. |
|
|
38
39
|
| `tools` | The catalog: one line per tool with its argument names (`?` marks optional), the first sentence of its description, and `[read-only]` where it changes nothing. `tools {tool}` shows the full description and the JSON Schema of its arguments. |
|
|
39
40
|
| `call` | Runs one catalog tool: `call {tool: "tap", args: {target: "n42"}}`. Arguments are checked against the tool's schema first; a wrong one fails with `INVALID_ARGUMENT` naming the field. |
|
|
40
41
|
| `close_session` | Saves a recording still running, ends the attempt, disposes the engine, stops the app processes the session started once no other session uses them. |
|
|
@@ -57,6 +58,10 @@ The catalog, per session:
|
|
|
57
58
|
| `stop_recording` | Stops it and returns the absolute path of each video file, under `<output>/videos/<session>/` (`.e2e` by default), or the URL of a provider's own recording. |
|
|
58
59
|
| Project tools | Every `defineTool` in the agent's `tools` that applies to the target's platform, under its own name; an engine pack such as `mobileTools` adds `open_app`, `swipe`, `alert`. |
|
|
59
60
|
|
|
61
|
+
Project tools use their `toModelOutput` renderer for strings as well as
|
|
62
|
+
structured results. Its `error-text` and `error-json` outputs set the MCP
|
|
63
|
+
result's `isError: true`; treat them as failed calls.
|
|
64
|
+
|
|
60
65
|
Once a secret is filled in the session, `screenshot` and the point tools
|
|
61
66
|
(`tap_at`, `hover_at`, `type_at`, `press_at`, `select_at`) answer with a
|
|
62
67
|
`PIXEL_TAINTED` line for the rest of it; act on listed nodes by id (topic
|
|
@@ -67,6 +67,7 @@ npx e2e list tests/signup.e2e.ts --tag smoke --reporter json
|
|
|
67
67
|
With a `package.json` script `"test:e2e": "e2e run"`, pnpm forwards `--`
|
|
68
68
|
literally: `pnpm test:e2e -- --headed` reaches e2e as `run -- --headed` and
|
|
69
69
|
exits 2. Write `pnpm test:e2e --headed` or `pnpm exec e2e run --headed`.
|
|
70
|
+
With npm, use `npm exec -- e2e run --headed` to pass flags to e2e.
|
|
70
71
|
|
|
71
72
|
## The replay cache
|
|
72
73
|
|
|
@@ -176,7 +177,7 @@ jobs:
|
|
|
176
177
|
- uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0
|
|
177
178
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
178
179
|
with:
|
|
179
|
-
node-version: 26 #
|
|
180
|
+
node-version: 26 # Node >= 24.8, or >= 22.22.3 on 22
|
|
180
181
|
cache: pnpm
|
|
181
182
|
- run: pnpm install --frozen-lockfile
|
|
182
183
|
- run: pnpm exec e2e-web install chromium --with-deps
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Requirements
|
|
4
4
|
|
|
5
|
-
- Node.js 22.
|
|
5
|
+
- Node.js 24.8 or newer, or 22.22.3 or newer on Node.js 22.
|
|
6
6
|
- ES modules: `.ts` config, tests, helpers, and workspace packages exporting
|
|
7
7
|
`.ts` source load as ESM regardless of the nearest `package.json` `type`
|
|
8
8
|
(CommonJS packages need no change); never `require` or `module.exports`.
|
|
9
|
+
Imports follow TypeScript: `./x.js` or `./x` loads `x.ts`, and the nearest
|
|
10
|
+
`tsconfig.json` `paths` and `baseUrl` apply. Decorators need
|
|
11
|
+
`experimentalDecorators`; CommonJS TypeScript goes in `.cts`.
|
|
9
12
|
- Browser tests: `@e2e-dev/web`, pinning `playwright-core` exactly; do not
|
|
10
13
|
add `playwright` for it. Missing browsers download on first boot; in CI run
|
|
11
14
|
`npx @e2e-dev/web install chromium --with-deps` (pnpm: `pnpm exec e2e-web
|
|
@@ -354,3 +357,8 @@ export default {
|
|
|
354
357
|
- `.gitignore` lists the `.e2e/` outputs (init adds them); drop the
|
|
355
358
|
`.e2e/cache/` line to commit replays.
|
|
356
359
|
- CI runs the whole suite, agent steps included, on PRs; see `running`.
|
|
360
|
+
|
|
361
|
+
### Existing MCP registration
|
|
362
|
+
|
|
363
|
+
Re-running `init` preserves the e2e MCP server's environment and client settings
|
|
364
|
+
while repairing its launch command. A current registration is left unchanged.
|
|
@@ -203,7 +203,7 @@ field and types one character per `delay`, plain string only (a `Secret` is
|
|
|
203
203
|
Coordinates are CSS pixels, for what the tree does not list: `tap({ position:
|
|
204
204
|
{ x, y } })` offsets from the node's top-left corner (with `modifiers` it is
|
|
205
205
|
`INVALID_ARGUMENT`), `screen.tapAt({ x, y })` taps a viewport point,
|
|
206
|
-
`screen.swipe({ from, to })` swipes along a path,
|
|
206
|
+
`screen.swipe({ from, to, duration? })` swipes along a path,
|
|
207
207
|
`screen.swipe({ direction, momentum? })` swipes the viewport. Prefer a
|
|
208
208
|
locator; a point moves with the layout.
|
|
209
209
|
|