e2e 0.21.1 → 0.21.2
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 +2 -0
- package/dist/.build.json +1 -1
- package/dist/agent/act-validation.js +1 -0
- package/dist/agent/act.js +7 -3
- package/dist/agent/action-dispatcher.js +30 -8
- package/dist/agent/actions.d.ts +2 -0
- package/dist/agent/actions.js +5 -2
- package/dist/agent/error.js +1 -0
- package/dist/agent/executor.d.ts +14 -6
- package/dist/agent/index.js +5 -1
- package/dist/agent/invocation.d.ts +3 -0
- package/dist/agent/model/sdk.d.ts +5 -0
- package/dist/agent/model/sdk.js +18 -1
- package/dist/agent/observation.js +1 -0
- package/dist/agent/primitives.js +14 -3
- package/dist/agent/replay.js +9 -3
- package/dist/agent/step-cache.d.ts +3 -2
- package/dist/agent/step-cache.js +7 -4
- package/dist/agent/tool-loop.js +4 -1
- package/dist/cache/context.js +1 -1
- package/dist/cache/recorder.d.ts +2 -1
- package/dist/cache/recorder.js +11 -2
- package/dist/cache/trace-fields.d.ts +10 -0
- package/dist/cache/trace-fields.js +17 -0
- package/dist/cache/trace.d.ts +9 -1
- package/dist/cache/trace.js +26 -7
- package/dist/cli/feedback.js +2 -1
- package/dist/cli/init/sibling-versions.json +1 -1
- package/dist/collect/select.js +12 -2
- package/dist/config/agent.d.ts +1 -1
- package/dist/config/diagnose.js +9 -3
- package/dist/config/esm-hooks.js +3 -2
- package/dist/config/resolve.js +3 -2
- package/dist/config/typescript.js +52 -2
- package/dist/engine/contract.d.ts +3 -2
- package/dist/engine/manifest.js +2 -2
- package/dist/internal/errors.js +5 -2
- package/dist/internal/source.js +2 -0
- package/dist/internal/unserved-model.d.ts +5 -0
- package/dist/internal/unserved-model.js +22 -0
- package/dist/locator/screen.js +1 -2
- package/dist/oauth/copilot.js +23 -5
- package/dist/oauth/device-code.js +35 -19
- package/dist/oauth/errors.d.ts +3 -0
- package/dist/oauth/errors.js +26 -11
- package/dist/oauth/providers/github-copilot.d.ts +4 -0
- package/dist/oauth/providers/github-copilot.js +64 -16
- package/dist/oauth/providers/openai.d.ts +2 -2
- package/dist/oauth/providers/openai.js +47 -19
- package/dist/oauth/providers/opencode-console.js +36 -15
- package/dist/oauth/providers/xai.js +18 -9
- package/dist/oauth/token-endpoint.d.ts +15 -6
- package/dist/oauth/token-endpoint.js +18 -6
- package/dist/report/code-frame.js +9 -3
- package/dist/report/failure-text.d.ts +10 -2
- package/dist/report/failure-text.js +24 -8
- package/dist/report/list-model.d.ts +13 -0
- package/dist/report/list-steps.d.ts +6 -1
- package/dist/report/list-steps.js +29 -0
- package/dist/report/list.d.ts +1 -0
- package/dist/report/list.js +16 -7
- package/dist/report/live-window.js +2 -1
- package/dist/report/markdown.js +1 -1
- package/dist/run/fixtures.js +2 -0
- package/dist/run/host-trace.d.ts +3 -2
- package/dist/run/host-trace.js +2 -1
- package/dist/run/last-run.d.ts +9 -0
- package/dist/run/last-run.js +28 -0
- package/dist/run/open-session.d.ts +3 -1
- package/dist/run/open-session.js +1 -1
- package/dist/run/runner.js +6 -2
- package/dist/run/sessions.js +3 -3
- package/dist/run/standalone-attempt.d.ts +2 -1
- package/dist/run/standalone-attempt.js +3 -1
- package/dist/run/steps.d.ts +3 -1
- package/dist/run/units.d.ts +8 -0
- package/dist/run/units.js +14 -0
- package/dist/run/worker-models.d.ts +2 -0
- package/dist/run/worker-models.js +7 -0
- package/dist/types.d.ts +2 -2
- package/docs/authentication.mdx +26 -23
- package/docs/browser.mdx +7 -4
- package/docs/bug-bash.mdx +2 -2
- package/docs/cache.mdx +6 -1
- package/docs/ci/bitrise.mdx +1 -1
- package/docs/ci/codemagic.mdx +2 -2
- package/docs/ci/eas-workflows.mdx +1 -1
- package/docs/coding-agents.mdx +7 -35
- package/docs/debugging.mdx +24 -36
- package/docs/executors.mdx +1 -1
- package/docs/github.mdx +2 -1
- package/docs/integrations/eas.mdx +1 -1
- package/docs/integrations/index.mdx +6 -0
- package/docs/integrations/lightpanda.mdx +192 -0
- package/docs/integrations/testmu.mdx +152 -0
- package/docs/mcp.mdx +123 -0
- package/docs/migrate/detox.mdx +1 -1
- package/docs/migrate/maestro.mdx +101 -105
- package/docs/migrate/playwright.mdx +65 -70
- package/docs/mobile.mdx +18 -3
- package/docs/models.mdx +4 -2
- package/docs/quickstart.mdx +7 -0
- package/docs/reference/agent.mdx +369 -277
- package/docs/reference/app.mdx +141 -32
- package/docs/reference/cli.mdx +674 -558
- package/docs/reference/config.mdx +1406 -752
- package/docs/reference/engine.mdx +1062 -256
- package/docs/reference/environment.mdx +156 -84
- package/docs/reference/errors.mdx +106 -72
- package/docs/reference/expect.mdx +752 -372
- package/docs/reference/mcp.mdx +366 -141
- package/docs/reference/mobile.mdx +844 -403
- package/docs/reference/reporters.mdx +312 -106
- package/docs/reference/runner.mdx +373 -174
- package/docs/reference/screen.mdx +931 -398
- package/docs/reference/security.mdx +106 -40
- package/docs/reference/test.mdx +626 -273
- package/docs/reference/web.mdx +956 -533
- package/docs/security.mdx +6 -5
- package/docs/snippets/model-provider.jsx +763 -0
- package/docs/snippets/setup-prompt-card.jsx +22 -0
- package/docs/snippets/show-more.jsx +22 -0
- package/docs/snippets/video-card.jsx +34 -0
- package/docs/snippets/video.jsx +25 -0
- package/docs/subscriptions.mdx +10 -3
- package/docs/telemetry.mdx +39 -39
- package/package.json +3 -2
- package/schema/report-v1.schema.json +6 -0
- package/skills/e2e/references/agent.md +8 -3
- package/skills/e2e/references/debugging.md +1 -1
- package/skills/e2e/references/mcp.md +1 -1
- package/skills/e2e/references/setup.md +12 -3
- package/skills/e2e/references/writing-tests.md +1 -1
|
@@ -10,7 +10,15 @@ These variables configure the runner and first-party packages. In the tables,
|
|
|
10
10
|
`set` means the variable exists, `value` means it is not blank, and `flag`
|
|
11
11
|
means it is not blank, `0`, or `false`. Flag values are case-insensitive.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
```bash
|
|
14
|
+
export E2E_USER_ADMIN_PASSWORD="$ADMIN_PASSWORD"
|
|
15
|
+
export E2E_CACHE_DIR=~/.cache/e2e
|
|
16
|
+
export E2E_TELEMETRY_DISABLED=1
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Reference
|
|
20
|
+
|
|
21
|
+
### Model
|
|
14
22
|
|
|
15
23
|
Configure a model in `e2e.config.ts`. Its provider reads the API key:
|
|
16
24
|
`gateway()` uses `AI_GATEWAY_API_KEY`, `openrouter()` uses
|
|
@@ -21,109 +29,173 @@ credential fails on the first model call. Subscription models use a stored
|
|
|
21
29
|
login instead.
|
|
22
30
|
See [Models](/models) and [Subscriptions](/subscriptions).
|
|
23
31
|
|
|
24
|
-
| Variable | Reading | Effect |
|
|
25
|
-
| --- | --- | --- |
|
|
26
|
-
| `E2E_OAUTH_CREDENTIALS` | value | The stored subscription logins as JSON
|
|
27
|
-
| `OPENCODE_API_KEY`
|
|
28
|
-
| `XDG_CONFIG_HOME` | value | Directory of the stored logins, `$XDG_CONFIG_HOME/e2e/oauth.json`; default `~/.config/e2e/oauth.json` |
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
|
32
|
+
| Variable | Example | Reading | Effect |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| [`E2E_OAUTH_CREDENTIALS`](#e2e_oauth_credentials) | `E2E_OAUTH_CREDENTIALS="$(cat oauth.json)"` | value | The stored subscription logins as JSON |
|
|
35
|
+
| [`OPENCODE_API_KEY`](#opencode_api_key) | `OPENCODE_API_KEY=<key>` | value | Replaces the stored `opencode-console` login |
|
|
36
|
+
| `XDG_CONFIG_HOME` | `XDG_CONFIG_HOME=~/.config` | value | Directory of the stored logins, `$XDG_CONFIG_HOME/e2e/oauth.json`; default `~/.config/e2e/oauth.json` |
|
|
37
|
+
|
|
38
|
+
#### `E2E_OAUTH_CREDENTIALS`
|
|
39
|
+
|
|
40
|
+
The stored subscription logins as JSON, the contents of `oauth.json`, for a
|
|
41
|
+
machine nobody can sign in on.
|
|
42
|
+
|
|
43
|
+
- The `e2e/oauth/*` models and `e2e models` read it in place of
|
|
44
|
+
`$XDG_CONFIG_HOME/e2e/oauth.json`.
|
|
45
|
+
- `e2e login` and `e2e logout` refuse to change it.
|
|
46
|
+
- Refreshed tokens are not written back.
|
|
47
|
+
|
|
48
|
+
#### `OPENCODE_API_KEY`
|
|
49
|
+
|
|
50
|
+
An OpenCode Console service account API key or an OpenCode Go subscription
|
|
51
|
+
API key. `opencodeConsole()` and `e2e models opencode-console` use it in place
|
|
52
|
+
of the stored `opencode-console` login. OpenCode reads the same variable.
|
|
53
|
+
|
|
54
|
+
### Credentials and secrets
|
|
55
|
+
|
|
56
|
+
| Variable | Example | Reading | Effect |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| `E2E_USER_<NAME>_USERNAME` | `E2E_USER_ADMIN_USERNAME=admin@example.test` | set | Replaces `credentials.<name>.username` |
|
|
59
|
+
| `E2E_USER_<NAME>_PASSWORD` | `E2E_USER_ADMIN_PASSWORD="$ADMIN_PASSWORD"` | set | Replaces `credentials.<name>.password` |
|
|
60
|
+
| `E2E_SECRET_<NAME>` | `E2E_SECRET_API_KEY="$API_KEY"` | set | Replaces the value of `secrets.<name>` |
|
|
61
|
+
|
|
62
|
+
- `<NAME>` is the name uppercased with every character outside `A-Z0-9`
|
|
63
|
+
replaced by `_`, so `power-user` becomes `E2E_USER_POWER_USER_USERNAME`.
|
|
64
|
+
- Two credentials, or two secrets, whose names map to one `<NAME>` are
|
|
65
|
+
`INVALID_CONFIG` at config load, set or not. So one rotation never
|
|
66
|
+
replaces two entries. The message names the entries and the variables.
|
|
67
|
+
For example, `api-key` and `api_key` both read `E2E_SECRET_API_KEY`;
|
|
68
|
+
`svc-a` and `svc_a` both read `E2E_USER_SVC_A_USERNAME` and `_PASSWORD`.
|
|
69
|
+
- A credential and a secret may share a name, since their prefixes differ.
|
|
70
|
+
- A variable for a name absent from the config is ignored.
|
|
71
|
+
- An empty password or secret is `INVALID_CONFIG`, and so is one shorter
|
|
72
|
+
than 6 characters (code points). Redaction rewrites every occurrence of a
|
|
73
|
+
value, and one that short would take ordinary text with it. An empty
|
|
74
|
+
username is accepted.
|
|
75
|
+
|
|
76
|
+
### Run mode
|
|
77
|
+
|
|
78
|
+
| Variable | Example | Reading | Effect |
|
|
79
|
+
| --- | --- | --- | --- |
|
|
80
|
+
| [`CI`](#ci) | `CI=1` | flag | CI mode |
|
|
81
|
+
|
|
82
|
+
#### `CI`
|
|
83
|
+
|
|
84
|
+
Turns on CI mode:
|
|
85
|
+
|
|
86
|
+
- `retries` defaults to 1 and `workers` to 1.
|
|
87
|
+
- `cache` defaults to `read-only`.
|
|
88
|
+
- A focused test is `ONLY_IN_CI`.
|
|
89
|
+
- `reuseExisting` on an app command is ignored.
|
|
90
|
+
|
|
91
|
+
### Replay cache
|
|
92
|
+
|
|
93
|
+
| Variable | Example | Reading | Effect |
|
|
94
|
+
| --- | --- | --- | --- |
|
|
95
|
+
| [`E2E_CACHE_DIR`](#e2e_cache_dir) | `E2E_CACHE_DIR=~/.cache/e2e` | value | Replaces [`cache.dir`](/reference/config#cache) |
|
|
96
|
+
|
|
97
|
+
#### `E2E_CACHE_DIR`
|
|
98
|
+
|
|
99
|
+
Replaces [`cache.dir`](/reference/config#cache) for `e2e run` and
|
|
100
|
+
`e2e cache`, resolved from the project root.
|
|
101
|
+
|
|
102
|
+
- `~` or a leading `~/` is the home directory.
|
|
103
|
+
- `--cache-dir` wins over it.
|
|
104
|
+
- A custom `cache.store` ignores it.
|
|
105
|
+
- The cache mode stays as configured, and `--no-cache` still turns the cache
|
|
106
|
+
off.
|
|
107
|
+
|
|
108
|
+
### Diagnostics
|
|
109
|
+
|
|
110
|
+
| Variable | Example | Reading | Effect |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| `NO_COLOR` | `NO_COLOR=1` | set | Plain CLI output. Without it, colors are on for a TTY or in CI, off when piped |
|
|
113
|
+
| `FORCE_COLOR` | `FORCE_COLOR=1` | set | Colored CLI output even when piped |
|
|
114
|
+
| `TERM` | `TERM=dumb` | value | `dumb` draws the `e2e init` wordmark at rest, without animation, as in CI |
|
|
115
|
+
| `GITHUB_SHA`, `GITHUB_HEAD_REF`, `GITHUB_REF_NAME` | `GITHUB_REF_NAME=main` | value | The commit and branch the report records under `run.vcs` when `git` cannot read the project root. `GITHUB_HEAD_REF` wins over `GITHUB_REF_NAME` |
|
|
71
116
|
|
|
72
117
|
`--debug` and `--ai-trace` are flags, not variables; see
|
|
73
118
|
[CLI](/reference/cli#e2e-run).
|
|
74
119
|
|
|
75
|
-
|
|
120
|
+
### Telemetry
|
|
121
|
+
|
|
122
|
+
| Variable | Example | Reading | Effect |
|
|
123
|
+
| --- | --- | --- | --- |
|
|
124
|
+
| `E2E_TELEMETRY_DISABLED` | `E2E_TELEMETRY_DISABLED=1` | flag | Turns anonymous usage telemetry off. `e2e telemetry disable` saves the same choice in the preferences file |
|
|
125
|
+
| `DO_NOT_TRACK` | `DO_NOT_TRACK=1` | flag | Same as `E2E_TELEMETRY_DISABLED` |
|
|
126
|
+
| `E2E_TELEMETRY_DEBUG` | `E2E_TELEMETRY_DEBUG=1` | flag | Prints every telemetry event to stderr instead of sending it |
|
|
127
|
+
| [`E2E_TELEMETRY_FLEET`](#e2e_telemetry_fleet) | `E2E_TELEMETRY_FLEET=acme` | value | Names the platform running e2e for its users |
|
|
128
|
+
| `XDG_CONFIG_HOME` | `XDG_CONFIG_HOME=~/.config` | value | Directory of the preferences file, `$XDG_CONFIG_HOME/e2e`; default `~/.config/e2e` |
|
|
129
|
+
| `APPDATA` | `APPDATA=C:\Users\ada\AppData\Roaming` | value | The same on Windows, `%APPDATA%\e2e`; default `~/AppData/Roaming/e2e` |
|
|
130
|
+
|
|
131
|
+
#### `E2E_TELEMETRY_FLEET`
|
|
76
132
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
| `APPDATA` | value | The same on Windows, `%APPDATA%\e2e`; default `~/AppData/Roaming/e2e` |
|
|
133
|
+
Names the platform running e2e for its users.
|
|
134
|
+
|
|
135
|
+
- Events are attributed to `fleet:<name>` instead of a per-machine id.
|
|
136
|
+
- No preferences file is written, and no notice is printed.
|
|
137
|
+
- A value that is not a plain token is sent as `other`.
|
|
138
|
+
|
|
139
|
+
#### Detected environments
|
|
85
140
|
|
|
86
141
|
Telemetry also checks for the markers CI vendors and coding agents set. Only
|
|
87
142
|
the derived name is sent.
|
|
88
143
|
|
|
89
144
|
| Markers | Reported as |
|
|
90
145
|
| --- | --- |
|
|
91
|
-
| `GITHUB_ACTIONS`, `GITLAB_CI`, `CIRCLECI`, `BUILDKITE`, `JENKINS_URL`, `TRAVIS`, `BITBUCKET_BUILD_NUMBER`, `TF_BUILD`, `TEAMCITY_VERSION`, `CODEBUILD_BUILD_ID`, `VERCEL`, `NETLIFY`, `DRONE`, `SEMAPHORE`, `APPVEYOR`, `WOODPECKER` | `ci_name`: `github-actions`, `gitlab`, `circleci`, `buildkite`, `jenkins`, `travis`, `bitbucket`, `azure-pipelines`, `teamcity`, `aws-codebuild`, `vercel`, `netlify`, `drone`, `semaphore`, `appveyor`, `woodpecker`; `unknown` when only `CI` is set |
|
|
146
|
+
| `GITHUB_ACTIONS`, `GITLAB_CI`, `CIRCLECI`, `BUILDKITE`, `JENKINS_URL`, `TRAVIS`, `BITBUCKET_BUILD_NUMBER`, `TF_BUILD`, `TEAMCITY_VERSION`, `CODEBUILD_BUILD_ID`, `VERCEL`, `NETLIFY`, `DRONE`, `SEMAPHORE`, `APPVEYOR`, `WOODPECKER`, `EAS_BUILD`, `BITRISE_IO`, `CM_BUILD_ID`, `CI_XCODE_CLOUD` | `ci_name`: `github-actions`, `gitlab`, `circleci`, `buildkite`, `jenkins`, `travis`, `bitbucket`, `azure-pipelines`, `teamcity`, `aws-codebuild`, `vercel`, `netlify`, `drone`, `semaphore`, `appveyor`, `woodpecker`, `eas`, `bitrise`, `codemagic`, `xcode-cloud`; `unknown` when only `CI` is set |
|
|
92
147
|
| `CLAUDECODE`, `CLAUDE_CODE`, `CURSOR_AGENT`, `CURSOR_TRACE_ID`, `CODEX_SANDBOX`, `CODEX_THREAD_ID`, `CODEX_CI`, `GEMINI_CLI`, `COPILOT_GITHUB_TOKEN`, `COPILOT_MODEL`, `COPILOT_ALLOW_ALL`, `OPENCODE_CLIENT`, `AUGMENT_AGENT`, `ANTIGRAVITY_AGENT`, `REPL_ID`, `AI_AGENT` | `coding_agent`: `claude-code`, `cursor`, `codex`, `gemini-cli`, `copilot`, `opencode`, `augment`, `antigravity`, `replit`, `other` |
|
|
93
148
|
| `WSL_DISTRO_NAME` | `wsl: true` |
|
|
94
149
|
| `npm_config_user_agent` | `package_manager`: the invoking package manager. `e2e init` reads the same variable to pick the install command |
|
|
95
150
|
|
|
96
151
|
What is sent and how to read it is on the [Telemetry](/telemetry) page.
|
|
97
152
|
|
|
98
|
-
|
|
153
|
+
### Reporters
|
|
99
154
|
|
|
100
155
|
`@e2e-dev/github` reads the variables GitHub Actions sets, when a run finishes:
|
|
101
156
|
|
|
102
|
-
| Variable | Reading | Effect |
|
|
103
|
-
| --- | --- | --- |
|
|
104
|
-
| `GITHUB_ACTIONS` | value | Without it the reporter posts nothing and says so |
|
|
105
|
-
| `GITHUB_REPOSITORY`, `GITHUB_RUN_ID`, `GITHUB_EVENT_NAME`, `GITHUB_WORKFLOW`, `GITHUB_JOB` | value | Always set by the runner
|
|
106
|
-
| `GITHUB_TOKEN`, `GH_TOKEN` | value |
|
|
107
|
-
| `GITHUB_EVENT_PATH`, `GITHUB_REF` | value | The pull request number and head commit from the event payload, or the number from `refs/pull/N/merge` |
|
|
108
|
-
| `GITHUB_SHA` | value | The commit source links point at when the event is not about a pull request |
|
|
109
|
-
| `GITHUB_WORKSPACE` | value | The checkout root
|
|
110
|
-
| `GITHUB_SERVER_URL`, `GITHUB_API_URL` | value | The GitHub host, for Enterprise Server
|
|
111
|
-
| `GITHUB_STEP_SUMMARY` | value | The job summary file the comment text is appended to |
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
| Variable | Reading | Effect |
|
|
116
|
-
| --- | --- | --- |
|
|
117
|
-
| `PLAYWRIGHT_BROWSERS_PATH` | value | Playwright's browser cache, honored by the engine's `prepare` hook |
|
|
118
|
-
| `PLAYWRIGHT_SKIP_BROWSER_GC` | value | Defaults to `1` for the engine's browser install,
|
|
119
|
-
| `KERNEL_API_KEY` | value | The API key `kernel()` from `@e2e-dev/kernel` creates browsers with
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
157
|
+
| Variable | Example | Reading | Effect |
|
|
158
|
+
| --- | --- | --- | --- |
|
|
159
|
+
| `GITHUB_ACTIONS` | `GITHUB_ACTIONS=true` | value | Without it the reporter posts nothing and says so |
|
|
160
|
+
| `GITHUB_REPOSITORY`, `GITHUB_RUN_ID`, `GITHUB_EVENT_NAME`, `GITHUB_WORKFLOW`, `GITHUB_JOB` | `GITHUB_REPOSITORY=acme/shop` | value | Always set by the runner. A missing one fails the reporter, naming the variable. The repository and run id build the artifacts link. The event name shows in the "not a pull request" row. The workflow and job join the marker a rerun finds its own comment by |
|
|
161
|
+
| `GITHUB_TOKEN`, `GH_TOKEN` | `GITHUB_TOKEN=<token>` | value | Posts the comment; the first one set wins. Unset, the job summary is still written. A fork's `pull_request` token is read-only and cannot post; see [When nothing is posted](/github#when-nothing-is-posted) |
|
|
162
|
+
| `GITHUB_EVENT_PATH`, `GITHUB_REF` | `GITHUB_REF=refs/pull/42/merge` | value | The pull request number and head commit from the event payload, or the number from `refs/pull/N/merge` |
|
|
163
|
+
| `GITHUB_SHA` | `GITHUB_SHA=<commit>` | value | The commit source links point at when the event is not about a pull request |
|
|
164
|
+
| `GITHUB_WORKSPACE` | `GITHUB_WORKSPACE=/home/runner/work/shop/shop` | value | The checkout root. Source links add the project's path inside it, so a suite in a subdirectory links to the right file |
|
|
165
|
+
| `GITHUB_SERVER_URL`, `GITHUB_API_URL` | `GITHUB_SERVER_URL=https://github.example.com` | value | The GitHub host, for Enterprise Server. Default `https://github.com` and `https://api.github.com` |
|
|
166
|
+
| `GITHUB_STEP_SUMMARY` | `GITHUB_STEP_SUMMARY=<file>` | value | The job summary file the comment text is appended to |
|
|
167
|
+
|
|
168
|
+
### Engines
|
|
169
|
+
|
|
170
|
+
| Variable | Example | Reading | Effect |
|
|
171
|
+
| --- | --- | --- | --- |
|
|
172
|
+
| `PLAYWRIGHT_BROWSERS_PATH` | `PLAYWRIGHT_BROWSERS_PATH=~/.cache/ms-playwright` | value | Playwright's browser cache, honored by the engine's `prepare` hook |
|
|
173
|
+
| `PLAYWRIGHT_SKIP_BROWSER_GC` | `PLAYWRIGHT_SKIP_BROWSER_GC=0` | value | Defaults to `1` for the engine's browser install, which keeps other browsers in the cache. `0` lets the install remove them |
|
|
174
|
+
| `KERNEL_API_KEY` | `KERNEL_API_KEY=<key>` | value | The API key `kernel()` from `@e2e-dev/kernel` creates browsers with. Unset, the browser lease fails. See [Kernel](/integrations/kernel) |
|
|
175
|
+
| `LIGHTPANDA_PATH` | `LIGHTPANDA_PATH=/opt/lightpanda` | value | The binary `lightpanda()` from `@e2e-dev/lightpanda` starts a CDP server with. Unset, `lightpanda` on `PATH`, `~/.lightpanda`, or `~/.local/bin`. See [Lightpanda](/integrations/lightpanda) |
|
|
176
|
+
| [`EXPO_TOKEN`](#expo_token) | `EXPO_TOKEN=<token>` | value | The Expo access token `easSimulators()` from `@e2e-dev/eas` uses |
|
|
177
|
+
| `HOME`, `USERPROFILE` | `HOME=/home/ada` | value | Where `easSimulators()` finds the eas-cli login without `EXPO_TOKEN`: `USERPROFILE` on Windows, `HOME` elsewhere, the OS user's home when unset |
|
|
178
|
+
| `LIMRUN_API_KEY` | `LIMRUN_API_KEY=<key>` | value | The API key `limrun()` from `@e2e-dev/limrun` leases devices with. Unset, the lease fails with `INVALID_CONFIG`. See [Limrun](/integrations/limrun) |
|
|
179
|
+
| `LIMRUN_BASE_URL` | `LIMRUN_BASE_URL=<url>` | value | The Limrun API host. Default `https://api.limrun.com` |
|
|
180
|
+
| `LT_USERNAME`, `LT_ACCESS_KEY` | `LT_USERNAME=acme` | value | The TestMu AI username and access key `testmu()` from `@e2e-dev/testmu` leases devices with. With either unset, the device lease fails. See [TestMu AI](/integrations/testmu) |
|
|
181
|
+
| `TESTMU_API_ENDPOINT` | `TESTMU_API_ENDPOINT=<url>` | value | The TestMu AI sessions API base URL `testmu()` looks up session recordings at. Default `https://mobile-api.lambdatest.com/mobile-automation/api/v1`. A value that is not an http(s) URL, or carries a username or password, fails the lookup with `INVALID_CONFIG`. agent-device's TestMu AI plugin reads it too |
|
|
182
|
+
| `E2E_WEB_BROWSERS_<TARGET>_<digest>` | - | value | `@e2e-dev/web` writes it in `prepare`; each worker reads it to find its leased browser. Don't set it by hand |
|
|
183
|
+
| `E2E_AGENT_DEVICE_POOL_<TARGET>_<digest>` | - | value | `@e2e-dev/mobile` writes it in `prepare`; each worker reads it to find its booted device. Don't set it by hand |
|
|
184
|
+
|
|
185
|
+
#### `EXPO_TOKEN`
|
|
186
|
+
|
|
187
|
+
The Expo access token `easSimulators()` from `@e2e-dev/eas` starts and stops
|
|
188
|
+
simulator sessions with. See [EAS Simulators](/integrations/eas).
|
|
189
|
+
|
|
190
|
+
- Unset, it uses the eas-cli login in `.expo/state.json` under the home
|
|
191
|
+
directory ([`HOME`, `USERPROFILE`](#engines)).
|
|
192
|
+
- With neither, the device lease fails.
|
|
123
193
|
|
|
124
194
|
## What app processes inherit
|
|
125
195
|
|
|
126
|
-
An app `command` receives `PATH`, `HOME`, `TMPDIR`, `TMP`,
|
|
127
|
-
`
|
|
128
|
-
`
|
|
129
|
-
|
|
196
|
+
An app `command` receives `PATH`, `HOME`, `TMPDIR`, `TMP`, `TEMP`,
|
|
197
|
+
`SystemRoot`, and `COMSPEC`, plus the variables `command.inheritEnv` matches
|
|
198
|
+
and whatever `command.env` sets.
|
|
199
|
+
|
|
200
|
+
The scaffolded config module reads `APP_URL`, not the runner. If the dev
|
|
201
|
+
server needs it too, pass it through `command.env`.
|
|
@@ -6,8 +6,30 @@ icon: "triangle-exclamation"
|
|
|
6
6
|
"twitter:image": "https://e2e.tester.army/docs/images/og/reference/errors.png?v=b184354c61"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Every error carries a class. The class
|
|
10
|
-
|
|
9
|
+
Every runner error carries a class and a code. The class sets the exit code,
|
|
10
|
+
and the code names the cause. A model never selects either. `e2e login`,
|
|
11
|
+
`e2e logout`, and `e2e models` are the exception, under
|
|
12
|
+
[Exit codes](#exit-codes).
|
|
13
|
+
|
|
14
|
+
```ts title="tests/upgrade.e2e.ts"
|
|
15
|
+
import { isAgentError, test } from 'e2e';
|
|
16
|
+
|
|
17
|
+
test('upgrade to Pro', async ({ agent }) => {
|
|
18
|
+
try {
|
|
19
|
+
await agent.act('upgrade the workspace to Pro');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (isAgentError(error) && error.code === 'STEP_TIMEOUT') {
|
|
22
|
+
// the step ran out of time; the message carries the last judgment
|
|
23
|
+
console.error(error.category, error.details);
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Reference
|
|
31
|
+
|
|
32
|
+
### Exit codes
|
|
11
33
|
|
|
12
34
|
| Class | Exit code |
|
|
13
35
|
| --- | ---: |
|
|
@@ -17,13 +39,36 @@ identifies the cause. A model never selects either.
|
|
|
17
39
|
| internal | 4 |
|
|
18
40
|
| interrupted | 130 |
|
|
19
41
|
|
|
20
|
-
|
|
42
|
+
- In a mixed run the highest-precedence code present wins,
|
|
43
|
+
`130 > 4 > 3 > 2 > 1 > 0`. The report keeps every individual result.
|
|
44
|
+
- A setup assertion failure is a test failure and skips its dependents.
|
|
45
|
+
Missing setup infrastructure keeps its configuration or infrastructure
|
|
46
|
+
class.
|
|
47
|
+
- The `internal` class and exit code 4 are reserved for runner invariant
|
|
48
|
+
violations. No first-party code raises a code in that class today.
|
|
49
|
+
- `e2e login`, `e2e logout`, and `e2e models` print a failure's message with
|
|
50
|
+
no code and exit 1, or 130 when a sign-in or picker is cancelled.
|
|
51
|
+
|
|
52
|
+
| Codes | Description |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| [Agent codes](#agent-codes) | The `code` of an `AgentError` |
|
|
55
|
+
| [Runner codes](#runner-codes) | Plain strings in output and report |
|
|
56
|
+
| [Test code errors](#test-code-errors) | Thrown from test code |
|
|
57
|
+
|
|
58
|
+
### AgentError
|
|
59
|
+
|
|
60
|
+
`AgentError` is the runner-classified agent failure, exported from `e2e`.
|
|
21
61
|
|
|
22
62
|
```ts
|
|
23
63
|
/** Runner-classified agent failure. */
|
|
24
64
|
export class AgentError extends Error {
|
|
25
65
|
/** Closed union, assigned by the runner, never taken from model text. */
|
|
26
66
|
readonly code: AgentErrorCode;
|
|
67
|
+
/** The error class in the table above; it sets the exit code. */
|
|
68
|
+
readonly category: 'test' | 'configuration' | 'infrastructure' | 'internal' | 'interrupted';
|
|
69
|
+
readonly retryable: boolean;
|
|
70
|
+
/** Structured facts: locator, expected, observed, match count, wait time. */
|
|
71
|
+
readonly details: ErrorDetails | undefined;
|
|
27
72
|
/** Sanitized, bounded prose; the same value as `message`. */
|
|
28
73
|
readonly explanation: string;
|
|
29
74
|
/** Artifact path, present only when evidence was captured and permitted. */
|
|
@@ -33,52 +78,47 @@ export class AgentError extends Error {
|
|
|
33
78
|
}
|
|
34
79
|
```
|
|
35
80
|
|
|
81
|
+
<Note>
|
|
36
82
|
Test files load in their own module realm, so `instanceof AgentError` can be
|
|
37
|
-
false. Use `isAgentError` from `e2e` and branch on `code
|
|
83
|
+
false. Use `isAgentError` from `e2e` and branch on `code`, as in the example
|
|
84
|
+
at the top of this page.
|
|
85
|
+
</Note>
|
|
38
86
|
|
|
39
|
-
|
|
40
|
-
import { isAgentError } from 'e2e';
|
|
87
|
+
#### Agent codes
|
|
41
88
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
} catch (error) {
|
|
45
|
-
if (isAgentError(error) && error.code === 'STEP_TIMEOUT') {
|
|
46
|
-
// the step ran out of time; the message carries the last judgment
|
|
47
|
-
}
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
```
|
|
89
|
+
The members of the closed `AgentErrorCode` union. `blocked:` names the
|
|
90
|
+
category a blocked verdict reports as the obstacle.
|
|
51
91
|
|
|
52
92
|
| Code | When it happens | Class | Exit |
|
|
53
93
|
| --- | --- | --- | ---: |
|
|
54
|
-
| `AUTH_CREDENTIAL_UNAVAILABLE` | A `Secret` or `credentials.user()` names a credential
|
|
55
|
-
| `AUTH_CREDENTIAL_INVALID` |
|
|
56
|
-
| `SECRET_UNAVAILABLE` | A `Secret` or `secrets.get()` during a run names a secret
|
|
94
|
+
| `AUTH_CREDENTIAL_UNAVAILABLE` | A `Secret` or `credentials.user()` names a credential missing from `config.credentials`, the call ran outside an active runner, or the password's provider returned no string of at least 6 characters (code points) | configuration | 2 |
|
|
95
|
+
| `AUTH_CREDENTIAL_INVALID` | The application rejected a configured credential (blocked: credentials) | configuration | 2 |
|
|
96
|
+
| `SECRET_UNAVAILABLE` | A `Secret` or `secrets.get()` during a run names a secret missing from `config.secrets`, the secret's provider returned no string of at least 6 characters (code points), or an engine resolved a secret it did not declare in its `secrets` | configuration | 2 |
|
|
57
97
|
| `ENVIRONMENT_UNAVAILABLE` | The environment under test is down or unreachable beyond the app itself (blocked: environment) | infrastructure | 3 |
|
|
58
98
|
| `SEED_DATA_MISSING` | Data the step depends on does not exist in the environment (blocked: seed_data) | configuration | 2 |
|
|
59
|
-
| `TEST_SETUP_FAILED` | Test prerequisites outside the product failed to arrange (blocked: test_setup). Also a `test.extend()` fixture that broke its contract: returned without calling `use()`, called it twice, or redefined a fixture the target's engine contributes | configuration | 2 |
|
|
60
|
-
| `AUTOMATION_UNSUPPORTED` | The step needs an interaction the available toolset cannot perform,
|
|
61
|
-
| `MODEL_UNAVAILABLE` | No model configured: the built-in agent's `agents.<name>.model` holds no AI SDK model instance
|
|
62
|
-
| `POLICY_DENIED` |
|
|
63
|
-
| `APP_UNREACHABLE` | The app URL could not be reached, or an app command exited early or exceeded `startupTimeout`.
|
|
64
|
-
| `APP_ALREADY_RUNNING` | `readyUrl`
|
|
65
|
-
| `MODEL_PROVIDER_FAILED` | Provider transport failure (network error, 5xx, rate limit, or
|
|
66
|
-
| `CANCELLED` | The runner cancelled the operation
|
|
67
|
-
| `MODEL_OUTPUT_INVALID` | Structured output still failed validation after the repair budget was spent. Under a [decision model](/decision-models)
|
|
68
|
-
| `APP_NOT_OPEN` | An operation needed an open app
|
|
99
|
+
| `TEST_SETUP_FAILED` | Test prerequisites outside the product failed to arrange (blocked: test_setup). Also a `test.extend()` fixture that broke its contract: it returned without calling `use()`, called it twice, or redefined a fixture the target's engine contributes | configuration | 2 |
|
|
100
|
+
| `AUTOMATION_UNSUPPORTED` | The step needs an interaction the available toolset cannot perform, such as drag-and-drop (blocked: automation) | test | 1 |
|
|
101
|
+
| `MODEL_UNAVAILABLE` | No model configured: the built-in agent's `agents.<name>.model` holds no AI SDK model instance (the message names the selected agent), or GitHub Copilot does not serve the configured model to this account (`model_not_supported`; the message names the model). Recorded once as a run-level error, and the run stops. In a test run, a missing model is found when the `agent` fixture is first acquired, and an unserved model at its first call. `e2e explore` runs the built-in agent, so it checks for a missing model before anything starts, a custom executor's entry included. A custom executor without a model is not checked: its `act` and `assert` steps run, and `waitFor` or `extract` fails with this code on its first call. Evicts no [recording](/cache). | configuration | 2 |
|
|
102
|
+
| `POLICY_DENIED` | The security policy refused an operation: a denied destination, a forbidden fill, pixels after a secret fill, or a read from a secure field. [Policy denials](/reference/security#policy-denials) lists every case. An unknown tool or an undeclared field goes back to the model as the call's result, never as this code | configuration | 2 |
|
|
103
|
+
| `APP_UNREACHABLE` | The app URL could not be reached, or an app command exited early or exceeded `startupTimeout`. With a `log`, the startup error quotes the log's output since the command started, `command.env` values redacted, or says there was none. Without one, it says to set a `log`. A command that fails to spawn reports the spawn error. Under the agent, also any engine fault with no more specific code. On a device, a busy or wedged iOS automation runner reports here; the message names the runner, the session and device, and the recovery. See [Mobile](/mobile#troubleshooting) | infrastructure | 3 |
|
|
104
|
+
| `APP_ALREADY_RUNNING` | `readyUrl` answered before the target's `app.command` was spawned, and `reuseExisting` was not in effect. Stop that process, or set `reuseExisting: true` locally | infrastructure | 3 |
|
|
105
|
+
| `MODEL_PROVIDER_FAILED` | Provider transport failure (network error, 5xx, rate limit, or no response within 120 s) after the adapter's transport retries: up to 5 per model call, honoring `retry-after`, within the remaining step timeout. A stalled request is aborted at 120 s and sent again; an `agent.act` step's `--debug` transcript notes it. The 120 s covers the whole request, so a model that needs longer for one answer fails here too. An error body the provider package cannot read shows as the HTTP status and the body, secrets redacted, cut at 1 KB. Evicts no [recording](/cache) | infrastructure | 3 |
|
|
106
|
+
| `CANCELLED` | The runner cancelled the operation without a process signal: an `e2e mcp` `open_session` cut short by a client cancel, a closed connection, or the server's shutdown, after it stopped what it had started | infrastructure | 3 |
|
|
107
|
+
| `MODEL_OUTPUT_INVALID` | Structured output still failed validation after the repair budget was spent. Under a [decision model](/decision-models): an answer without a valid choice distribution, a choice the request never offered, or a question the provider refused. Decisions are not repaired | test | 1 |
|
|
108
|
+
| `APP_NOT_OPEN` | An operation needed an open app: `app.open()` had not run, or the engine reported invalid state | test | 1 |
|
|
69
109
|
| `LOCATOR_NOT_FOUND` | No matching node within the deadline, or an agent action refers to a node that is no longer available | test | 1 |
|
|
70
|
-
| `LOCATOR_AMBIGUOUS` | More than one match for a single-node operation. Fails
|
|
110
|
+
| `LOCATOR_AMBIGUOUS` | More than one match for a single-node operation. Fails at once, with no polling. | test | 1 |
|
|
71
111
|
| `ACTION_FAILED` | Actionability failure, operation timeout, or an action that may already have committed | test | 1 |
|
|
72
112
|
| `STEP_BUDGET_EXHAUSTED` | The model-call or action-step budget ran out before a conclusion | test | 1 |
|
|
73
|
-
| `STEP_TIMEOUT` | The invocation deadline expired, an operation
|
|
74
|
-
| `CONTEXT_OVERFLOW` | A request exceeded the model's context window. `act` shortens its history and retries once if that
|
|
113
|
+
| `STEP_TIMEOUT` | The invocation deadline expired, including an operation that timed out within a poll tick of it. Carries the last judgment where one exists, with the [vision hint](/reference/agent#agent-assert) when that judgment was inconclusive | test | 1 |
|
|
114
|
+
| `CONTEXT_OVERFLOW` | A request exceeded the model's context window. `act` shortens its history and retries once if that makes the request smaller; judgments fail at once. Lower `agents.<name>.maxObservationBytes`, or split the instruction into shorter steps. Reported as blocked: automation | test | 1 |
|
|
75
115
|
| `ASSERTION_FAILED` | `agent.assert` judged false, or a locator, `browser`, or value matcher failed | test | 1 |
|
|
76
|
-
| `ASSERTION_INCONCLUSIVE` | `agent.assert` could not judge: the screen did not show enough to decide either way (the relevant part is not on screen, still loading, or unreadable). `agent.extract` raises it when the model answers that the screen does not show the requested data, instead of returning a placeholder. A failure, never a pass; the explanation says what was missing. Judged from the tree alone with `vision` off, the message
|
|
77
|
-
| `AUTHENTICATION_FAILED` | The executor concluded that signing in failed inside the app: a product verdict. `AUTH_CREDENTIAL_INVALID` is the blocked counterpart for a configured credential the app rejects | test | 1 |
|
|
78
|
-
| `STEP_NO_CONCLUSION` | The executor used every turn without calling `complete_step
|
|
79
|
-
| `REPLAY_STALE` | Under `cache.strict` or `--strict-cache`, the step's recording exists but no longer replays
|
|
116
|
+
| `ASSERTION_INCONCLUSIVE` | `agent.assert` could not judge: the screen did not show enough to decide either way (the relevant part is not on screen, still loading, or unreadable). `agent.extract` raises it when the model answers that the screen does not show the requested data, instead of returning a placeholder. A failure, never a pass; the explanation says what was missing. Judged from the tree alone with `vision` off, the message can add a [vision hint](/reference/agent#agent-assert) | test | 1 |
|
|
117
|
+
| `AUTHENTICATION_FAILED` | The executor concluded that signing in failed inside the app: a product verdict. `AUTH_CREDENTIAL_INVALID` is the blocked counterpart, for a configured credential the app rejects | test | 1 |
|
|
118
|
+
| `STEP_NO_CONCLUSION` | The executor used every turn without calling `complete_step`. The step fails with the agent's last summary | test | 1 |
|
|
119
|
+
| `REPLAY_STALE` | Under `cache.strict` or `--strict-cache`, the step's recording exists but no longer replays, so the step fails with no model call instead of handing off to the agent. `step.cache.reason` is one of `invalid-entry`, `wrong-context`, `target-not-found`, `target-ambiguous`, `viewport-changed`, `action-failed`, `action-uncertain`, `end-mismatch`, or `no-entry` while the cache directory (not a custom `cache.store`) holds a recording of the step under another key, which the message names. Not retried: every attempt replays the same recording. To fix it, re-record with a read-write run without the flag, with `cache.strict` off if the config set it, then commit the changed entry. The message names the knob that turned strict on and the configured cache directory (or `cache.store`) | configuration | 2 |
|
|
80
120
|
|
|
81
|
-
|
|
121
|
+
### Runner codes
|
|
82
122
|
|
|
83
123
|
These appear in terminal output and in the report as plain string codes.
|
|
84
124
|
|
|
@@ -86,64 +126,58 @@ These appear in terminal output and in the report as plain string codes.
|
|
|
86
126
|
| --- | --- | --- | ---: |
|
|
87
127
|
| `CONFIG_NOT_FOUND` | `--config <path>` names a file that does not exist, or no `e2e.config.ts` or `e2e.config.mts` exists from the working directory up to the repository root | configuration | 2 |
|
|
88
128
|
| `CONFIG_AMBIGUOUS` | Both `e2e.config.ts` and `e2e.config.mts` in one directory | configuration | 2 |
|
|
89
|
-
| `CONFIG_LOAD_FAILED` | The config module failed to import: a package that is declared but not installed, an undeclared package, a wrong subpath, a removed export, or any other throw while it evaluates. The message names the fix. A config error raised while it evaluates keeps its own code:
|
|
90
|
-
| `INVALID_CONFIG` | A factory the config module calls (`web()`, `mobile()`, `defineEngine()`, `decisionExecutor()`) refused its options, with the factory's own message
|
|
129
|
+
| `CONFIG_LOAD_FAILED` | The config module failed to import: a package that is declared but not installed, an undeclared package, a wrong subpath, a removed export, or any other throw while it evaluates. The message names the fix. A config error raised while it evaluates keeps its own code: options a factory the config calls refuses, or a `secrets.get()` handle turned into a string, is `INVALID_CONFIG` | configuration | 2 |
|
|
130
|
+
| `INVALID_CONFIG` | A factory the config module calls (`web()`, `mobile()`, `defineEngine()`, `decisionExecutor()`) refused its options, with the factory's own message. Or: an unknown key, out-of-range value, invalid enum, or missing `targets`; a target key outside `{ name, platform, engine, app, trace, video }`; a target `app` key that is unknown or malformed; `app` on a target without an engine; a `web()` target without `app.url` or with a device field; a `mobile()` target without `app.bundleId` or `app.appPath`, with `app.url` (not supported yet), with a link in `bundleId`, or with an unknown permission name or state (the message names the nearest name); an unknown key in a `web()` or `mobile()` option, `web({ connect })` and `web({ basicAuth })` included (the message names the nearest one); a `trace` or `video` that is not a recording mode; the removed artifact kinds list (`artifacts: [...]` or `artifacts.kinds`) or `artifacts.trace` block (the message names the `trace` mode they meant); the removed `artifacts.video` block or `video` artifact kind (the message names the `video` option); the removed `specVersion` key; a `secrets.get()` in an engine option that names no `secrets` entry (a credential's password is not one); a `secrets.get()` reference turned into a string while the config evaluates (a template literal, `String()`), or given where a string is needed (an `app.command` `args` entry or `env` value; only an engine option that declares secrets takes one); an `app.command` `args` entry or `env` value that is not a string (an `undefined` `env` value is dropped instead); an unknown key in `app.command` (the message names the nearest one); an `engine` that is not a `defineEngine` handle; the `json` reporter combined with `list`; a duplicate or malformed target name; a secret or credential password shorter than 6 characters (code points); a credential that is not `{ username, password }`, whose `username` is not a string (only `password` may be a function), or that has no username in the config or `E2E_USER_<NAME>_USERNAME`; two credentials or two secrets whose names map to one `E2E_USER_*` or `E2E_SECRET_*` override variable (`api-key` and `api_key`); an oversized `agents.<name>.context`; an `expect.poll` `timeout` or `interval` that is not a finite number of milliseconds; a `tests` value that is not a glob or a list of globs, or a `tests` list of only `!` exclusions; the removed top-level `limits` key; an agents entry that is a bare `StepExecutor` (the message names `{ executor }`) or a model instance (the message names `{ model }`); an unknown agent key (the message names the nearest one); the removed agent keys `timeout` and `maxTurns` (the message names `judgmentTimeout` and `maxModelCalls`); `system` or `tools` beside `executor`; a `tools` entry that `defineTool` did not build, or that takes a built-in tool name (a grammar verb, `complete_step`, `locate`, `start_recording`, `stop_recording`, or `report_finding`); `createToolLoopExecutor({ maxTurns })`; `reuseExisting` on an app whose `app.url` asks for a free port; or a port-0 `app.url` with no `app.command` to serve it | configuration | 2 |
|
|
91
131
|
| `CONFIG_NOT_DETERMINISTIC` | A worker re-resolved the config module and got a different digest than the runner | configuration | 2 |
|
|
92
132
|
| `APP_URL_REQUIRED` | A navigation or relative URL on a target that declares no `app.url`, or an `app.command` with no `app.url` or `app.readyUrl` to poll | configuration | 2 |
|
|
93
|
-
| `INVALID_APP_URL` |
|
|
94
|
-
| `INVALID_GLOB` |
|
|
133
|
+
| `INVALID_APP_URL` | A base URL that is unparseable or has userinfo, a query, a fragment, a non-HTTP scheme, or plain HTTP on a non-loopback host; port 0 on a host other than a literal loopback address (`127.0.0.1`, `[::1]`); or a loopback address the run could not bind a free port on | configuration | 2 |
|
|
134
|
+
| `INVALID_GLOB` | An empty pattern; a first segment that starts with `!` beyond the one leading `!` of a `tests` exclusion (`!!tests/**`, `./!tests/**`); a bare `!`; braces, character classes, or extglobs; a backslash separator; an absolute path or a `..` segment; a trailing `/` or `.`; a `tests` entry with no wildcard that names a directory (the message names the glob to write); a pattern that names only the project root; or a `**` that is not a whole segment | configuration | 2 |
|
|
95
135
|
| `UNKNOWN_TARGET` | `--target`, `open_session.target`, or `openHost` (`e2e/runner`) named a target that is not configured | configuration | 2 |
|
|
96
136
|
| `TARGET_REQUIRED` | `e2e mcp` or `openHost` (`e2e/runner`): the config declares several targets and none was named | configuration | 2 |
|
|
97
137
|
| `NO_SESSION` | `e2e mcp`: `call` or `tools` before `open_session` or after the session ended (the message names why), or `call`, `tools`, or `close_session` naming a `session` id that is not open or is closing | configuration | 2 |
|
|
98
|
-
| `SESSION_REQUIRED` | `e2e mcp`: `call`, `tools`, or `close_session` without a `session` while several sessions are open
|
|
99
|
-
| `ENGINE_IN_USE` | `e2e mcp`: `open_session` on a target whose engine instance another open session drives, because the config gets it from a package every session shares
|
|
100
|
-
| `CONFIG_IN_USE` | `e2e mcp`: `open_session` on a config other than the one the open sessions use
|
|
101
|
-
| `SESSION_OPEN` | `e2e mcp`: `open_session` while every session slot (`--max-sessions`, default 4) holds
|
|
102
|
-
| `UNKNOWN_TOPIC` | `e2e mcp`: an `e2e://guide/{topic}` resource named a topic the skill does not have
|
|
103
|
-
| `UNKNOWN_TOOL` | `e2e mcp`: `call` or `tools` named a tool the session's catalog does not have
|
|
104
|
-
| `UNSUPPORTED_ARTIFACT` | A [`video` mode](/reference/config#recording) set on a target, or on a test that will run there, records on some attempt, and the target's engine cannot record video. The message names the target and
|
|
138
|
+
| `SESSION_REQUIRED` | `e2e mcp`: `call`, `tools`, or `close_session` without a `session` while several sessions are open. The message lists them | configuration | 2 |
|
|
139
|
+
| `ENGINE_IN_USE` | `e2e mcp`: `open_session` on a target whose engine instance another open session drives, because the config gets it from a package every session shares. Create the engine in the config, or in a file it imports by path | configuration | 2 |
|
|
140
|
+
| `CONFIG_IN_USE` | `e2e mcp`: `open_session` on a config other than the one the open sessions use. Credentials and secrets resolve process-wide, so open on that config or close those sessions first | configuration | 2 |
|
|
141
|
+
| `SESSION_OPEN` | `e2e mcp`: `open_session` while every session slot (`--max-sessions`, default 4) holds an open, opening, or closing session, or while the server shuts down. Close one first | configuration | 2 |
|
|
142
|
+
| `UNKNOWN_TOPIC` | `e2e mcp`: an `e2e://guide/{topic}` resource named a topic the skill does not have. The message lists them | configuration | 2 |
|
|
143
|
+
| `UNKNOWN_TOOL` | `e2e mcp`: `call` or `tools` named a tool the session's catalog does not have. The message lists the catalog. An action verb the engine does not declare is `UNSUPPORTED_CAPABILITY` instead | configuration | 2 |
|
|
144
|
+
| `UNSUPPORTED_ARTIFACT` | A [`video` mode](/reference/config#recording) set on a target, or on a test that will run there, records on some attempt, and the target's engine cannot record video. The message names the target, and the test for a test's mode. A mode from the config root or `--video` never raises it: it applies to the targets that can record, with a notice naming the rest | configuration | 2 |
|
|
105
145
|
| `UNSUPPORTED_CAPABILITY` | A fixture, action, or tier the target's engine does not declare: a contributed fixture it does not contribute, `screen` without `locate`, state restore without `state`, screenshots without `artifacts`, `app.clearState()` on a persistent browser context (the message names the mode), or `agent.assert` `vision` with an executor that does not declare `vision: true` | configuration | 2 |
|
|
106
|
-
| `NO_TESTS` | Zero runnable ordinary test-target pairs. The message names the cause: no file matched the `tests` globs (
|
|
107
|
-
| `RUNNER_BUSY` | A suite overlapped another suite, an opening or open host, or a live attempt; or a host opened during a suite. Await the suite and close existing hosts before
|
|
146
|
+
| `NO_TESTS` | Zero runnable ordinary test-target pairs. The message names the cause: no file matched the `tests` globs (it lists look-alike files), a positional matched nothing (it names the `!` entry that excludes a file the positional names), a file registered no tests, or filters excluded everything. Suppress with `--pass-with-no-tests`. | configuration | 2 |
|
|
147
|
+
| `RUNNER_BUSY` | A suite overlapped another suite, an opening or open host, or a live attempt; or a host opened during a suite. Await the suite and close existing hosts before you retry. | configuration | 2 |
|
|
108
148
|
| `NO_LAST_RUN` | `--last-failed` found no `report.json` where the run writes it, or a file there that is not a `report-1` document. Run once without the flag first. | configuration | 2 |
|
|
109
149
|
| `ONLY_IN_CI` | A focused test reached a CI run | configuration | 2 |
|
|
110
|
-
| `COLLECTION_ERROR` | Registration outside collection
|
|
111
|
-
| `POLICY_DENIED` | A denied destination (a navigation scheme other than `http:` or `https:`, `about:blank` aside, a malformed URL), a forbidden fill (an undeclared secret, a non-editable sink, a mismatched purpose, a `Secret` reaching a JSON sink), or tainted pixels (`app.screenshot()` after a secret fill, `vision: 'only'` with pixels withheld, an `act` or `vision: true` step whose semantic capture timed out before the step did); see the [security rules](/reference/security#policy-denials). An unknown tool or an undeclared field goes back to the model as the call's result, never this code | configuration | 2 |
|
|
150
|
+
| `COLLECTION_ERROR` | Registration outside collection; an invalid title; a non-function body; an async `describe` body; nested serial groups; an option forbidden inside a serial group; a setup test inside `describe`; an invalid session name; a malformed test option, such as `tags` that is not a list of distinct names; a test or `describe` option key the call does not take (the message names the nearest one); a test file that fails to import, in a run with no positionals or when the positionals selected it; a session that a selected test consumes, or `e2e explore --session` names, but no collected setup test produces (the message lists the declared sessions); a consumed session on a target whose engine has no `state` capability to save it; or a positional path outside the project root | configuration | 2 |
|
|
112
151
|
| `SESSION_MISMATCH` | A stored session does not match the current run, target, engine, platform, or app identity | configuration | 2 |
|
|
113
152
|
| `SESSION_EXPIRED` | A stored session is past its expiry at restore time (configuration, exit 2), or a setup test saved state whose expiry is not after its creation (test, exit 1) | configuration or test | 2 or 1 |
|
|
114
153
|
| `SESSION_INVALID` | A stored session failed authentication. Tampered or corrupt. | configuration | 2 |
|
|
115
154
|
| `SESSION_UNAVAILABLE` | A test requires a session no selected setup test produced | configuration | 2 |
|
|
116
155
|
| `SESSION_CONTRACT` | A setup test saved a session twice, saved an undeclared name, or finished without saving a declared one | test | 1 |
|
|
117
|
-
| `TEST_TIMEOUT` | The attempt exceeded the resolved test `timeout`. The body is not stopped: the harness operation it is in is cancelled and its fixtures die with the attempt, so it cannot reach the next test. A test that blocks the event loop gets its worker killed once `cleanupTimeout` (at least 5 s) passes after the timeout
|
|
118
|
-
| `STEP_NOT_AWAITED` | The test body settled while a step it started was still running: a step call without `await`. The step
|
|
119
|
-
| `UNHANDLED_REJECTION` | A promise rejected with no test running.
|
|
156
|
+
| `TEST_TIMEOUT` | The attempt exceeded the resolved test `timeout`. The body is not stopped: the harness operation it is in is cancelled, and its fixtures die with the attempt, so it cannot reach the next test. A test that blocks the event loop gets its worker killed once `cleanupTimeout` (at least 5 s) passes after the timeout. See [Hooks](/reference/test#hooks) | test | 1 |
|
|
157
|
+
| `STEP_NOT_AWAITED` | The test body settled while a step it started was still running: a step call without `await`. The step fails at the line of the call and is cancelled. The attempt fails with this code when the body returned. When the body threw, the attempt keeps the body's error and records this one under `secondaryErrors`. An `expect.poll` matcher still running when the body, an `afterEach` hook, or a fixture teardown returned fails that phase the same way, at the line of the call, and is cancelled. One left by a `beforeAll` or `afterAll` hook fails the hook with `HOOK_FAILED` | test | 1 |
|
|
158
|
+
| `UNHANDLED_REJECTION` | A promise rejected with no test running. Node cannot say which test created a promise, so an unhandled rejection is charged to the test running when it surfaces. Between tests it is this run error, naming the last test that finished in the worker as the likely source. Before any test has finished in the worker, it ends the worker instead: the rejection is the run error, and the test on its way fails with `WORKER_CRASH`. The body that left it keeps running like one past `TEST_TIMEOUT` | test | 1 |
|
|
120
159
|
| `HOOK_FAILED` | `beforeAll` or `afterAll` threw. Skips the suite's tests. | test | 1 |
|
|
121
|
-
| `RUN_TIMEOUT` | The run spent its [`maxDuration`](/reference/config#maxduration) or `--max-duration
|
|
160
|
+
| `RUN_TIMEOUT` | The run spent its [`maxDuration`](/reference/config#maxduration) or `--max-duration`. The running tests end as `interrupted`, and the rest are skipped with cause `time-limit` | test | 1 |
|
|
122
161
|
| `LAUNCH_TIMEOUT` | Engine init or attempt start exceeded `config.launchTimeout` | infrastructure | 3 |
|
|
123
162
|
| `CLEANUP_TIMEOUT` | Engine attempt end exceeded `config.cleanupTimeout`, or a route or dialog handler the engine ran for the test was still running when the attempt settled | infrastructure | 3 |
|
|
124
163
|
| `BROWSER_INSTALL_FAILED` | Browser provisioning failed | infrastructure | 3 |
|
|
125
164
|
| `WORKER_INIT_FAILED` | A worker process never became ready | infrastructure | 3 |
|
|
126
|
-
| `WORKER_EXIT` | A worker exited unexpectedly while running a file
|
|
165
|
+
| `WORKER_EXIT` | A worker exited unexpectedly while running a file. The test in flight fails with `WORKER_CRASH`, and the file's other tests are skipped. Attempts that finished before the exit stay in the report, including a retry's earlier attempts and a serial group's finished members. The crash is recorded as the attempt after them, or on the last attempt when it came after it (an `afterAll`). When an unhandled rejection ends the worker (see `UNHANDLED_REJECTION`), the rejection is the run error, not this one | infrastructure | 3 |
|
|
127
166
|
| `WORKER_CRASH` | A worker exited during a specific test | infrastructure | 3 |
|
|
128
167
|
| `WORKER_ENTRY_MISSING` | The worker entry module could not be resolved. Packaging fault. | infrastructure | 3 |
|
|
129
|
-
| `NODE_UNSUPPORTED` | The runtime cannot run e2e's TypeScript loader: a Node.js outside `^22.22.3 \|\| >=24.8.0`, Bun (no `module.registerHooks`), or Deno. The CLI checks this before it starts and exits 2 with the same sentence
|
|
130
|
-
| `TYPESCRIPT_COMPILER_UNAVAILABLE` | oxc, which compiles e2e's TypeScript, could not be loaded; the message gives the cause. Most often its native bindings for this platform (`@oxc-transform/binding-<platform>-<arch>`, `@oxc-parser/binding-<platform>-<arch>`) are not installed. npm leaves
|
|
168
|
+
| `NODE_UNSUPPORTED` | The runtime cannot run e2e's TypeScript loader: a Node.js outside `^22.22.3 \|\| >=24.8.0`, Bun (no `module.registerHooks`), or Deno. The CLI checks this before it starts and exits 2 with the same sentence. The loader checks again where it registers its hooks, for a host that runs e2e's runner without the CLI | infrastructure | 3 |
|
|
169
|
+
| `TYPESCRIPT_COMPILER_UNAVAILABLE` | oxc, which compiles e2e's TypeScript, could not be loaded; the message gives the cause. Most often its native bindings for this platform (`@oxc-transform/binding-<platform>-<arch>`, `@oxc-parser/binding-<platform>-<arch>`) are not installed. npm leaves them out with `--omit=optional`, or when `package-lock.json` was written on another platform (npm/cli#4828). Install again without `--omit=optional`, or delete `package-lock.json` and `node_modules` and run `npm install` | infrastructure | 3 |
|
|
131
170
|
| `ENGINE_FAILURE` | Any engine fault with no more specific mapping | infrastructure | 3 |
|
|
132
|
-
| `WORKER_PROTOCOL` | A worker reported the wrong unit, an attempt overlapped a live attempt, `openHost` tried to acquire an engine instance
|
|
171
|
+
| `WORKER_PROTOCOL` | A worker reported the wrong unit, an attempt overlapped a live attempt, `openHost` tried to acquire an engine instance another host owns, or `host.openSession` tried to reuse a closed host or overlap an opening, live, or closing session | infrastructure | 3 |
|
|
133
172
|
| `SESSION_KEY_INVALID` | A worker received a session key that is not 32 bytes. Packaging or IPC fault. | infrastructure | 3 |
|
|
134
|
-
| `REPORT_WRITE_FAILED` | `report.json` or `ai-trace.json` could not be written. Recorded as a run error; the in-memory outcome is still complete. A `junit.xml` that could not be written is the `junit` reporter's stderr
|
|
173
|
+
| `REPORT_WRITE_FAILED` | `report.json` or `ai-trace.json` could not be written. Recorded as a run error; the in-memory outcome is still complete. A `junit.xml` that could not be written is a line on the `junit` reporter's stderr instead | infrastructure | 3 |
|
|
135
174
|
| `EVALUATE_FAILED` | `browser.evaluate` threw inside the page (`@e2e-dev/web`) | test | 1 |
|
|
136
|
-
| `INTERRUPTED` | A user or CI signal aborted the run. A test cut short by `--max-failures` or the time limit also ends with this code, its message
|
|
137
|
-
|
|
138
|
-
Errors raised by argument validation in test code use `INVALID_ARGUMENT` and
|
|
139
|
-
`INVALID_LOCATOR`, both test failures with exit code 1. An unclassified thrown
|
|
140
|
-
value from test code is reported as `ERROR`. The `internal` class and exit
|
|
141
|
-
code 4 are reserved for runner invariant violations; no first-party code raises
|
|
142
|
-
a code in that class today.
|
|
175
|
+
| `INTERRUPTED` | A user or CI signal aborted the run. A test cut short by `--max-failures` or the time limit also ends with this code, and its message names the limit; that run exits 1, not 130 | interrupted | 130 |
|
|
143
176
|
|
|
144
|
-
|
|
177
|
+
#### Test code errors
|
|
145
178
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
179
|
+
| Code | When it happens | Class | Exit |
|
|
180
|
+
| --- | --- | --- | ---: |
|
|
181
|
+
| `INVALID_ARGUMENT` | Argument validation in test code refused an argument | test | 1 |
|
|
182
|
+
| `INVALID_LOCATOR` | Argument validation in test code refused a locator's arguments | test | 1 |
|
|
183
|
+
| `ERROR` | A value thrown from test code that no other code classifies | test | 1 |
|