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
|
@@ -6,9 +6,11 @@ icon: "sliders"
|
|
|
6
6
|
"twitter:image": "https://e2e.tester.army/docs/images/og/reference/config.png?v=6c4dead1fc"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
`e2e.config.ts` configures the runner: the targets tests run on, the app each
|
|
10
|
+
one tests, the agents, and the replay cache. Export an object with
|
|
11
|
+
`satisfies E2EConfig`, which checks its shape and keeps its inferred types.
|
|
12
|
+
The runner validates the config again at load, including ranges and rules
|
|
13
|
+
TypeScript cannot check.
|
|
12
14
|
|
|
13
15
|
```ts title="e2e.config.ts"
|
|
14
16
|
import type { E2EConfig } from 'e2e';
|
|
@@ -25,13 +27,46 @@ export default {
|
|
|
25
27
|
} satisfies E2EConfig;
|
|
26
28
|
```
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
top-level `app` key: each target declares the app it tests (see
|
|
30
|
-
[the app under test](#the-app-under-test)).
|
|
30
|
+
## Reference
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
### Top level
|
|
33
|
+
|
|
34
|
+
| Option | Type | Default |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| [`projectId`](#projectid) | `string` | `package.json` name |
|
|
37
|
+
| [`targets`](#targets) | `Target[]` | required |
|
|
38
|
+
| [`tests`](#tests) | `string \| string[]` | `'tests/**/*.e2e.ts'` |
|
|
39
|
+
| [`timeout`](#timeout) | `number` | `120000` |
|
|
40
|
+
| [`launchTimeout`](#launchtimeout) | `number` | `60000` |
|
|
41
|
+
| [`actionTimeout`](#actiontimeout) | `number` | `30000` |
|
|
42
|
+
| [`assertionTimeout`](#assertiontimeout) | `number` | `5000` |
|
|
43
|
+
| [`cleanupTimeout`](#cleanuptimeout) | `number` | `30000` |
|
|
44
|
+
| [`retries`](#retries) | `number` | `0`, `1` in CI |
|
|
45
|
+
| [`failOnSkippedFailure`](#failonskippedfailure) | `boolean` | `false` |
|
|
46
|
+
| [`maxDuration`](#maxduration) | `number` | - |
|
|
47
|
+
| [`workers`](#workers) | `number` | half the cores, `1` in CI |
|
|
48
|
+
| [`reporters`](#reporters) | `(BuiltinReporter \| Reporter)[]` | `['list']` |
|
|
49
|
+
| [`output`](#output) | `string` | `'.e2e'` |
|
|
50
|
+
| [`artifacts`](#artifacts) | `ArtifactsConfig` | local only |
|
|
51
|
+
| [`trace`](#trace) | `RecordingMode` | `'retain-on-failure'` |
|
|
52
|
+
| [`video`](#video) | `RecordingMode` | `'off'` |
|
|
53
|
+
| [`agents`](#agents) | `Record<string, AgentConfig>` | `default`, no model |
|
|
54
|
+
| [`cache`](#cache) | `CacheMode \| CacheConfig` | `'read-write'`, `'read-only'` in CI |
|
|
55
|
+
| [`credentials`](#credentials) | `Record<string, CredentialConfig>` | - |
|
|
56
|
+
| [`secrets`](#secrets) | `Record<string, SecretConfig>` | - |
|
|
57
|
+
|
|
58
|
+
The full declaration is [`E2EConfig`](#e2econfig).
|
|
59
|
+
|
|
60
|
+
- An unknown key at any level is `INVALID_CONFIG`. There is no top-level
|
|
61
|
+
`app` key: each target declares [the app it tests](#the-app-under-test).
|
|
62
|
+
- Every numeric value must be a safe integer.
|
|
63
|
+
- An optional key takes a value or is left out. Under
|
|
64
|
+
`exactOptionalPropertyTypes`, `undefined` is a type error, so
|
|
65
|
+
`store: process.env.CACHE_URL && createStore(...)` fails to compile. At
|
|
66
|
+
runtime, `undefined` counts as unset.
|
|
67
|
+
- The target's `app` fields also accept `undefined` in their types, so they
|
|
68
|
+
can read `process.env` directly. Spread any other key that depends on the
|
|
69
|
+
environment in only when it has a value:
|
|
35
70
|
|
|
36
71
|
```ts
|
|
37
72
|
export default {
|
|
@@ -40,244 +75,231 @@ export default {
|
|
|
40
75
|
} satisfies E2EConfig;
|
|
41
76
|
```
|
|
42
77
|
|
|
43
|
-
|
|
78
|
+
#### `projectId`
|
|
44
79
|
|
|
45
|
-
|
|
46
|
-
working directory and each parent for `e2e.config.ts` or `e2e.config.mts`,
|
|
47
|
-
stopping at the repository root. Both names in one directory is
|
|
48
|
-
`CONFIG_AMBIGUOUS`. The selected file's directory is the project root.
|
|
49
|
-
|
|
50
|
-
The `tests` globs select files relative to the project root. An entry that
|
|
51
|
-
starts with `!` excludes: `tests: ['tests/**/*.e2e.ts', '!tests/wip/**']`
|
|
52
|
-
selects every test file except those under `tests/wip/`. A file is selected
|
|
53
|
-
when an including glob matches it and no exclusion does, whatever the order of
|
|
54
|
-
the list, and a list of only exclusions is `INVALID_CONFIG`. A glob names
|
|
55
|
-
files, so an entry with no wildcard that names a directory (`'!tests/wip'`)
|
|
56
|
-
is `INVALID_GLOB` naming the glob to write (`'!tests/wip/**'`). A file
|
|
57
|
-
passed to `e2e run` that an exclusion takes out is `NO_TESTS` naming that
|
|
58
|
-
entry. Discovery visits
|
|
59
|
-
only directories that could match, never enters one an exclusion takes whole,
|
|
60
|
-
skips `node_modules`, and ignores symlinks. A dot directory is included only
|
|
61
|
-
when a glob segment explicitly starts with a dot.
|
|
62
|
-
`?` matches one Unicode code point, including an emoji.
|
|
80
|
+
A stable id for the project.
|
|
63
81
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
```ts
|
|
83
|
+
projectId: 'acme-web', // default: the root package.json name
|
|
84
|
+
```
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
- 1 through 256 characters.
|
|
87
|
+
- Without a `name` in the root `package.json`, it falls back to
|
|
88
|
+
`unportable-<sha256 of the project root, 32 hex>`.
|
|
89
|
+
|
|
90
|
+
#### `tests`
|
|
91
|
+
|
|
92
|
+
The test file globs, relative to the project root.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
tests: ['tests/**/*.e2e.ts', '!tests/wip/**'], // default: 'tests/**/*.e2e.ts'
|
|
70
96
|
```
|
|
71
97
|
|
|
72
|
-
|
|
73
|
-
|
|
98
|
+
- A glob takes `*`, `?`, and a whole `**` segment. `?` matches one Unicode
|
|
99
|
+
code point, including an emoji.
|
|
100
|
+
- An entry that starts with `!` excludes. The example selects every test file
|
|
101
|
+
except those under `tests/wip/`.
|
|
102
|
+
- A file is selected when an including glob matches it and no exclusion does,
|
|
103
|
+
whatever the order of the list.
|
|
104
|
+
- Discovery visits only directories that could match. It never enters a
|
|
105
|
+
directory an exclusion takes whole, skips `node_modules`, and ignores
|
|
106
|
+
symlinks.
|
|
107
|
+
- A dot directory is included only when a glob segment starts with a dot.
|
|
108
|
+
|
|
109
|
+
| Throws | When |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `INVALID_CONFIG` | The list holds only exclusions. |
|
|
112
|
+
| `INVALID_GLOB` | An entry with no wildcard names a directory (`'!tests/wip'`). A glob names files; the error names the glob to write (`'!tests/wip/**'`). |
|
|
113
|
+
| `NO_TESTS` | A file passed to `e2e run` is taken out by an exclusion. The error names that entry. |
|
|
74
114
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
file a CommonJS file requires. A `.cts` file is CommonJS, written with
|
|
79
|
-
`require`, `import x = require()`, and `export =`; its `import` statements are
|
|
80
|
-
not turned into `require` calls. A `.ts` file written with `require` or
|
|
81
|
-
`module.exports` fails to load: name it `.cts`. JavaScript keeps the format
|
|
82
|
-
its package declares.
|
|
83
|
-
|
|
84
|
-
e2e compiles TypeScript and JSX itself, and an import written in such a file
|
|
85
|
-
resolves the way TypeScript resolves it: `./helper.js` and `./helper` load
|
|
86
|
-
`helper.ts`, `./view.jsx` loads `view.tsx`, `./lib` loads the file
|
|
87
|
-
`lib/package.json` names as `main`, else `lib/index.ts`, a bare import that
|
|
88
|
-
`tsconfig.json` maps through `paths` or `baseUrl` loads the mapped file, and
|
|
89
|
-
a `#` import or package export that names a missing `./x.js`, `./x.jsx`, or
|
|
90
|
-
`./x` loads its `x.ts` or `x.tsx` source when imported. One that names a
|
|
91
|
-
directory, or a subpath of a package without `exports` that is a directory
|
|
92
|
-
(`@acme/db/enums`), loads the directory the way `./lib` does: the file its
|
|
93
|
-
`package.json` names as `main`, else its `index.ts`. Under
|
|
94
|
-
Yarn PnP that package subpath still fails. A `require()` of either fails as
|
|
95
|
-
Node.js reports it. An import written in a JavaScript file resolves
|
|
96
|
-
as Node.js resolves it.
|
|
97
|
-
|
|
98
|
-
The `tsconfig.json` is the nearest one above the file, or, when that one has
|
|
99
|
-
`references` and does not include the file itself (as Vite writes it), the
|
|
100
|
-
transitively referenced one that does. References are visited in declaration order; cycles are skipped and, if none owns the file, the nearest config remains the fallback. e2e reads its `paths`, `baseUrl`, `jsx`, `jsxFactory`,
|
|
101
|
-
`jsxFragmentFactory`, `jsxImportSource`, `experimentalDecorators`,
|
|
102
|
-
`emitDecoratorMetadata`, `useDefineForClassFields` (and `target`, which sets
|
|
103
|
-
its default), and `verbatimModuleSyntax`, once per process and again for each
|
|
104
|
-
session `e2e mcp` opens. A `tsconfig.json` e2e cannot read, such as one that
|
|
105
|
-
`extends` a package that is not installed, is skipped with a warning.
|
|
106
|
-
TypeScript inside an installed package compiles without the project's
|
|
107
|
-
`tsconfig.json`.
|
|
108
|
-
|
|
109
|
-
e2e refuses, naming the file and line, two things it cannot compile:
|
|
110
|
-
auto-accessors (`accessor x = 1`) and decorators without
|
|
111
|
-
`experimentalDecorators`. Each file compiles on its own, without type
|
|
112
|
-
information, so a type imported without `import type` fails to load when the
|
|
113
|
-
import stays in the output (a decorated member's type under
|
|
114
|
-
`emitDecoratorMetadata`, a re-exported type); the error names the import to
|
|
115
|
-
change. A JSON file imported without `with { type: 'json' }` loads with its
|
|
116
|
-
parsed contents as the default export. Stack traces and failure locations
|
|
117
|
-
point at the TypeScript source, through another loader's source map too
|
|
118
|
-
(`NODE_OPTIONS=--import tsx`).
|
|
119
|
-
|
|
120
|
-
## Top level
|
|
115
|
+
#### `timeout`
|
|
116
|
+
|
|
117
|
+
The deadline of one test attempt, in milliseconds.
|
|
121
118
|
|
|
122
119
|
```ts
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
/** Engine init and attempt start budget in milliseconds; default 60000. */
|
|
133
|
-
launchTimeout?: number;
|
|
134
|
-
/** Budget of every engine operation in milliseconds; default 30000. */
|
|
135
|
-
actionTimeout?: number;
|
|
136
|
-
/** Default `expect` deadline in milliseconds; default 5000. */
|
|
137
|
-
assertionTimeout?: number;
|
|
138
|
-
/** Budget of each `afterEach` hook, fixture teardown, and engine cleanup in milliseconds; default 30000. */
|
|
139
|
-
cleanupTimeout?: number;
|
|
140
|
-
/** Retries per test, 0 through 10; default 1 in CI, else 0. */
|
|
141
|
-
retries?: number;
|
|
142
|
-
/** Fail the run when a test skips a retry after an earlier failed attempt; default false. */
|
|
143
|
-
failOnSkippedFailure?: boolean;
|
|
144
|
-
/** Wall-clock budget for running the tests, in milliseconds; default none. */
|
|
145
|
-
maxDuration?: number;
|
|
146
|
-
/** Parallel workers, 1 through 1024; default 1 in CI, else half the cores. An engine may cap it lower. */
|
|
147
|
-
workers?: number;
|
|
148
|
-
/** `{ store }` hands every artifact to a host store as it is produced. */
|
|
149
|
-
artifacts?: ArtifactsConfig;
|
|
150
|
-
/**
|
|
151
|
-
* Which tests keep a trace page, `trace.md` in the test's directory under
|
|
152
|
-
* `<output>/results/`; default
|
|
153
|
-
* `retain-on-failure`. A target's `trace` wins over it, `--trace [mode]`
|
|
154
|
-
* over both, and a test's own `trace` over all.
|
|
155
|
-
*/
|
|
156
|
-
trace?: RecordingMode;
|
|
157
|
-
/**
|
|
158
|
-
* Which attempts record a video; default `off`. A target's `video` wins
|
|
159
|
-
* over it, `--video [mode]` over both, and a test's own `video` over all.
|
|
160
|
-
* Applies to the targets whose engine can record.
|
|
161
|
-
*/
|
|
162
|
-
video?: RecordingMode;
|
|
163
|
-
/**
|
|
164
|
-
* The results directory, relative to the project root; default `.e2e`.
|
|
165
|
-
* `--output <dir>` overrides it for one run.
|
|
166
|
-
*/
|
|
167
|
-
output?: string;
|
|
168
|
-
/**
|
|
169
|
-
* Output renderers and reporter objects. `junit` writes `<output>/junit.xml`,
|
|
170
|
-
* `markdown` writes `<output>/summary.md`, `json` prints the report and
|
|
171
|
-
* excludes `list`; a `Reporter` object runs
|
|
172
|
-
* beside them and `--reporter` never removes it.
|
|
173
|
-
*/
|
|
174
|
-
reporters?: readonly (BuiltinReporter | Reporter)[];
|
|
175
|
-
/**
|
|
176
|
-
* Named agents, each one plain object: the built-in agent's options, or
|
|
177
|
-
* `{ executor }` for a custom brain. Tests use `default` unless selected
|
|
178
|
-
* otherwise. No agent inherits another's values. Each worker constructs
|
|
179
|
-
* its own agents from this config module.
|
|
180
|
-
*/
|
|
181
|
-
agents?: Readonly<Record<string, AgentConfig>>;
|
|
182
|
-
/**
|
|
183
|
-
* Replay cache mode or options. Defaults to `read-write` locally and
|
|
184
|
-
* `read-only` in CI. An explicit mode overrides the default;
|
|
185
|
-
* `--no-cache` disables caching for the run.
|
|
186
|
-
*/
|
|
187
|
-
cache?: CacheMode | CacheConfig;
|
|
188
|
-
/**
|
|
189
|
-
* Named accounts. `credentials.user(name).password` is the handle of a
|
|
190
|
-
* credential's password, a secret named `<name>.password`; `secrets.get()`
|
|
191
|
-
* never returns it, so a credential and a secret may share a name.
|
|
192
|
-
*/
|
|
193
|
-
credentials?: Readonly<Record<string, CredentialConfig>>;
|
|
194
|
-
/**
|
|
195
|
-
* Named values the model must never see: API keys, tokens, anything sourced
|
|
196
|
-
* from the environment. `secrets.get(name)` hands a test the opaque handle;
|
|
197
|
-
* the value is filled by the runner, masked in every observation, and
|
|
198
|
-
* redacted from logs, traces, and the report.
|
|
199
|
-
*/
|
|
200
|
-
secrets?: Readonly<Record<string, SecretConfig>>;
|
|
201
|
-
}
|
|
120
|
+
timeout: 180_000, // default: 120000
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### `launchTimeout`
|
|
124
|
+
|
|
125
|
+
The budget for engine init and attempt start, in milliseconds.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
launchTimeout: 90_000, // default: 60000
|
|
202
129
|
```
|
|
203
130
|
|
|
204
|
-
|
|
205
|
-
`unportable-<sha256 of the project root, 32 hex>` when there is no
|
|
206
|
-
`package.json`. `workers` never exceeds what a target's engine declares. A
|
|
207
|
-
targeted agent action is bounded by the smaller of `actionTimeout` and 15 s.
|
|
131
|
+
#### `actionTimeout`
|
|
208
132
|
|
|
209
|
-
|
|
133
|
+
The budget of every engine operation, in milliseconds.
|
|
210
134
|
|
|
211
|
-
|
|
212
|
-
|
|
135
|
+
```ts
|
|
136
|
+
actionTimeout: 10_000, // default: 30000
|
|
137
|
+
```
|
|
213
138
|
|
|
214
|
-
|
|
215
|
-
report, and the CLI shows them under `Skipped After Failure` with either
|
|
216
|
-
setting. Clean skips, successful retries, and engine cleanup diagnostics do
|
|
217
|
-
not trigger this policy. A skip after a failed `expect.soft` needs no option:
|
|
218
|
-
it [fails the attempt](/reference/test#members).
|
|
139
|
+
A targeted agent action is bounded by the smaller of `actionTimeout` and 15 s.
|
|
219
140
|
|
|
220
|
-
|
|
141
|
+
#### `assertionTimeout`
|
|
221
142
|
|
|
222
|
-
|
|
143
|
+
The default `expect` deadline, in milliseconds.
|
|
223
144
|
|
|
224
145
|
```ts
|
|
225
|
-
|
|
146
|
+
assertionTimeout: 10_000, // default: 5000
|
|
226
147
|
```
|
|
227
148
|
|
|
228
|
-
|
|
229
|
-
command's [`startupTimeout`](#command) instead. Once the time is spent, nothing more is
|
|
230
|
-
dispatched, the running tests end as `interrupted`, and the tests not
|
|
231
|
-
started are skipped with cause `time-limit`. The run records a
|
|
232
|
-
`RUN_TIMEOUT` error and exits 1. `--last-failed` runs the interrupted and
|
|
233
|
-
skipped tests next. `--max-duration <ms>` overrides it for one run.
|
|
234
|
-
`e2e explore` ignores it and keeps its own `--timeout`.
|
|
149
|
+
#### `cleanupTimeout`
|
|
235
150
|
|
|
236
|
-
|
|
151
|
+
The budget of each `afterEach` hook, fixture teardown, and engine cleanup, in
|
|
152
|
+
milliseconds.
|
|
237
153
|
|
|
238
154
|
```ts
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
155
|
+
cleanupTimeout: 60_000, // default: 30000
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### `retries`
|
|
159
|
+
|
|
160
|
+
Retries per test.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
retries: 2, // default: 1 in CI, else 0
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- 0 through 10.
|
|
167
|
+
|
|
168
|
+
#### `failOnSkippedFailure`
|
|
169
|
+
|
|
170
|
+
Fails the run with exit code 1 when a test skips a retry after an earlier
|
|
171
|
+
failed attempt.
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
failOnSkippedFailure: true, // default: false
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- The test keeps its `skipped` status and reason.
|
|
178
|
+
- Its failures remain in the report, and the CLI shows them under
|
|
179
|
+
`Skipped After Failure` with either setting.
|
|
180
|
+
- Clean skips, successful retries, and engine cleanup diagnostics do not
|
|
181
|
+
trigger it.
|
|
182
|
+
|
|
183
|
+
<Note>
|
|
184
|
+
A skip after a failed `expect.soft` needs no option: it
|
|
185
|
+
[fails the attempt](/reference/test#members).
|
|
186
|
+
</Note>
|
|
187
|
+
|
|
188
|
+
#### `maxDuration`
|
|
189
|
+
|
|
190
|
+
The wall-clock budget for running the tests, in milliseconds. It stops a run
|
|
191
|
+
that takes too long.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
export default { targets, maxDuration: 900_000 } satisfies E2EConfig; // default: none
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- At most 2147483647 (about 24 days).
|
|
198
|
+
- The clock starts when the tests start. The app command's
|
|
199
|
+
[`startupTimeout`](#command) bounds app startup instead.
|
|
200
|
+
- Once the time is spent, the runner dispatches nothing more. The running
|
|
201
|
+
tests end as `interrupted`, and the tests not started are skipped with cause
|
|
202
|
+
`time-limit`. The run records a `RUN_TIMEOUT` error and exits 1.
|
|
203
|
+
- `--last-failed` runs the interrupted and skipped tests next.
|
|
204
|
+
- `--max-duration <ms>` overrides it for one run.
|
|
205
|
+
- `e2e explore` ignores it and keeps its own `--timeout`.
|
|
206
|
+
|
|
207
|
+
#### `workers`
|
|
208
|
+
|
|
209
|
+
Parallel workers.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
workers: 4, // default: 1 in CI, else half the cores
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- 1 through 1024.
|
|
216
|
+
- Locally the default is `max(1, floor(availableParallelism() / 2))`.
|
|
217
|
+
- An engine may cap it lower: `workers` never exceeds what a target's engine
|
|
218
|
+
declares.
|
|
219
|
+
|
|
220
|
+
#### `reporters`
|
|
221
|
+
|
|
222
|
+
Output renderers and reporter objects.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
reporters: ['list', 'junit'], // default: ['list']
|
|
268
226
|
```
|
|
269
227
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
228
|
+
| Value | Does |
|
|
229
|
+
| --- | --- |
|
|
230
|
+
| `'list'` | Terminal progress, failures, and a run summary. |
|
|
231
|
+
| `'junit'` | Writes `<output>/junit.xml`. |
|
|
232
|
+
| `'markdown'` | Writes `<output>/summary.md`. |
|
|
233
|
+
| `'json'` | Prints the report. Excludes `list`: the two together are `INVALID_CONFIG`. |
|
|
234
|
+
| a `Reporter` object | Runs beside the built-in ones. `--reporter` never removes it. |
|
|
235
|
+
|
|
236
|
+
An unknown id is `INVALID_CONFIG`. See [Reporters](/reference/reporters).
|
|
237
|
+
|
|
238
|
+
### targets
|
|
239
|
+
|
|
240
|
+
A target is one named surface tests run on: the app under test and the
|
|
241
|
+
[engine](#engine) that drives it. `targets` is required and holds at least
|
|
242
|
+
one.
|
|
243
|
+
|
|
244
|
+
```ts title="e2e.config.ts"
|
|
245
|
+
targets: [
|
|
246
|
+
{ name: 'chromium', engine: web(), app: { url: 'http://localhost:3000' } },
|
|
247
|
+
{ name: 'firefox', engine: web({ browser: 'firefox' }), app: { url: 'http://localhost:3000' } },
|
|
248
|
+
],
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
| Option | Type | Default |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| [`name`](#name) | `string` | the platform |
|
|
254
|
+
| [`platform`](#platform) | `string` | the engine's platform |
|
|
255
|
+
| [`engine`](#engine) | `EngineHandle` | - |
|
|
256
|
+
| [`app`](#the-app-under-test) | `TargetApp` | - |
|
|
257
|
+
| [`trace`](#trace) | `RecordingMode` | the config's `trace` |
|
|
258
|
+
| [`video`](#video) | `RecordingMode` | the config's `video` |
|
|
259
|
+
|
|
260
|
+
- `{ engine: web(), app: { url } }` is a complete target.
|
|
261
|
+
- A test whose `requires` names a capability the target lacks is skipped for
|
|
262
|
+
it. A `test.setup` that a selected test depends on must be runnable there,
|
|
263
|
+
or selection fails with `COLLECTION_ERROR`.
|
|
264
|
+
|
|
265
|
+
The full declaration is [`Target`](#target).
|
|
266
|
+
|
|
267
|
+
#### `name`
|
|
268
|
+
|
|
269
|
+
The id `--target`, reports, sessions, and artifact paths use.
|
|
273
270
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
271
|
+
```ts
|
|
272
|
+
name: 'chromium', // default: the platform
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
- ASCII letters, digits, `_`, `-`, and `.`, not only dots (`.` and `..` are
|
|
276
|
+
path segments).
|
|
277
|
+
- Unique across targets. Two targets on one platform need names.
|
|
278
|
+
|
|
279
|
+
#### `platform`
|
|
280
|
+
|
|
281
|
+
The platform label.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
platform: 'web', // default: inherited from the engine
|
|
285
|
+
```
|
|
279
286
|
|
|
280
|
-
|
|
287
|
+
- Required for a target without an engine.
|
|
288
|
+
- When the target and its engine both name one, they must agree.
|
|
289
|
+
|
|
290
|
+
#### `engine`
|
|
291
|
+
|
|
292
|
+
The engine driving the surface: `web(...)`, `mobile(...)`, or any
|
|
293
|
+
`defineEngine` handle.
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
engine: web({ browser: 'firefox' }),
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
A target without an engine runs through agent tools only. A target with `app`
|
|
300
|
+
and no engine is `INVALID_CONFIG`.
|
|
301
|
+
|
|
302
|
+
### The app under test
|
|
281
303
|
|
|
282
304
|
A target declares the app it tests in `app`; the engine only drives it. A
|
|
283
305
|
browser target names the URL it opens, a device target the installed app or
|
|
@@ -307,52 +329,223 @@ targets: [
|
|
|
307
329
|
],
|
|
308
330
|
```
|
|
309
331
|
|
|
310
|
-
|
|
|
332
|
+
| Option | Type | Default |
|
|
311
333
|
| --- | --- | --- |
|
|
312
|
-
| `url`
|
|
313
|
-
| `bundleId` |
|
|
314
|
-
| `appPath`
|
|
315
|
-
| `identity` |
|
|
316
|
-
| `environment` |
|
|
317
|
-
| `launchArguments` |
|
|
318
|
-
| `permissions` |
|
|
319
|
-
| `command`
|
|
320
|
-
| `readyUrl`
|
|
321
|
-
|
|
322
|
-
Each engine checks the keys at config load, with `INVALID_CONFIG` naming
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
`
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
334
|
+
| [`url`](#url) | `string` | - |
|
|
335
|
+
| [`bundleId`](#bundleid) | `string` | - |
|
|
336
|
+
| [`appPath`](#apppath) | `string` | - |
|
|
337
|
+
| [`identity`](#identity) | `string` | derived from the app |
|
|
338
|
+
| [`environment`](#environment) | `'test' \| 'staging' \| 'production'` | `'test'` or `'production'` |
|
|
339
|
+
| [`launchArguments`](#launcharguments) | `string[]` | - |
|
|
340
|
+
| [`permissions`](#permissions) | `Record<string, AppPermissionState>` | - |
|
|
341
|
+
| [`command`](#command) | `CommandConfig` | - |
|
|
342
|
+
| [`readyUrl`](#readyurl) | `string` | `url` |
|
|
343
|
+
|
|
344
|
+
Each engine checks the keys at config load, with `INVALID_CONFIG` naming the
|
|
345
|
+
target. A target with `app` needs an [engine](#engine).
|
|
346
|
+
|
|
347
|
+
- A `web()` target needs `app.url` and refuses `bundleId`, `appPath`,
|
|
348
|
+
`launchArguments`, and `permissions`.
|
|
349
|
+
- A `mobile()` target needs `app.bundleId` or `app.appPath`. `app.url` on a
|
|
350
|
+
mobile target is not supported yet.
|
|
351
|
+
- An unknown key in `app` or `app.command` fails with a did-you-mean.
|
|
329
352
|
|
|
353
|
+
<Note>
|
|
330
354
|
The old spellings are unknown keys and fail at config load with
|
|
331
355
|
`INVALID_CONFIG`: `web({ url })` and the other app options of `web()` and
|
|
332
356
|
`mobile()`, `mobile({ app })` (now `app.bundleId`), an app key directly on the
|
|
333
|
-
target, a top-level `app` or `url`, and `services` anywhere
|
|
334
|
-
|
|
357
|
+
target, a top-level `app` or `url`, and [`services`](#services) anywhere.
|
|
358
|
+
</Note>
|
|
359
|
+
|
|
360
|
+
#### `url`
|
|
361
|
+
|
|
362
|
+
The base URL: `app.open()` opens it and relative navigation resolves against
|
|
363
|
+
it.
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
url: 'http://localhost:3000',
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
- WHATWG-normalized; no userinfo, query, or fragment.
|
|
370
|
+
- A missing scheme becomes `https://`, or `http://` for a loopback host.
|
|
371
|
+
- Plain HTTP is accepted for loopback hosts only.
|
|
372
|
+
- Port `0` on `127.0.0.1` or `[::1]` asks for a [free port](#free-ports).
|
|
373
|
+
- Its site scopes engine headers and observed frames: see
|
|
374
|
+
[the app's site](#the-apps-site).
|
|
375
|
+
|
|
376
|
+
| Throws | When |
|
|
377
|
+
| --- | --- |
|
|
378
|
+
| `INVALID_APP_URL` | Port `0` on any host other than `127.0.0.1` or `[::1]`, `localhost` included. |
|
|
379
|
+
| `INVALID_CONFIG` | A port-0 `url` with nothing to start on it (no `app.command`). |
|
|
380
|
+
|
|
381
|
+
#### `bundleId`
|
|
382
|
+
|
|
383
|
+
The installed app a device launches: a bundle id, an Android package name, or
|
|
384
|
+
a display name the device resolves (`Settings`).
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
bundleId: 'com.example.app',
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
A link is `INVALID_CONFIG`: a test opens one with `device.openLink`.
|
|
391
|
+
|
|
392
|
+
#### `appPath`
|
|
393
|
+
|
|
394
|
+
The build the suite runs against, an iOS `.app` bundle or an Android `.apk`,
|
|
395
|
+
resolved against the project root.
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
appPath: './build/App.app',
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Without `bundleId`, the app it installs is the one launched. See
|
|
402
|
+
[the device engine](/reference/mobile#engine) for when it is installed.
|
|
403
|
+
|
|
404
|
+
#### `identity`
|
|
405
|
+
|
|
406
|
+
The stable identity keying replay cache and session entries.
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
identity: 'checkout-web', // default: the URL's origin and base path, else bundleId, else appPath
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Set a stable value to reuse entries across preview URLs or versioned build
|
|
413
|
+
paths. With a [free port](#free-ports), the default derives from the declared
|
|
414
|
+
`:0`, so cache and session entries do not churn with the port.
|
|
415
|
+
|
|
416
|
+
<Note>
|
|
417
|
+
Different apps must have different identities. A shared identity replays
|
|
418
|
+
recordings across them.
|
|
419
|
+
</Note>
|
|
420
|
+
|
|
421
|
+
#### `environment`
|
|
422
|
+
|
|
423
|
+
Labels the target in the report and joins the cache and session identity
|
|
424
|
+
digest. It never gates a run.
|
|
335
425
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
426
|
+
```ts
|
|
427
|
+
environment: 'staging', // default: 'test' for a local host or no URL, else 'production'
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
- `'test'`, `'staging'`, or `'production'`.
|
|
431
|
+
- A local host is a loopback, `.localhost`, or `.test` host.
|
|
432
|
+
|
|
433
|
+
#### `launchArguments`
|
|
434
|
+
|
|
435
|
+
Arguments every fresh launch passes the app: `app.open()`, `app.restart()`,
|
|
436
|
+
and `app.clearState()`.
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
launchArguments: ['-e2e', 'YES'],
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Arguments that select a build mode change what the app is: give each mode its
|
|
443
|
+
own `identity`.
|
|
444
|
+
|
|
445
|
+
#### `permissions`
|
|
446
|
+
|
|
447
|
+
Permissions the app holds on every fresh launch, by name.
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
permissions: { camera: 'grant' },
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Each is `'grant'`, `'deny'`, or `'reset'`. See
|
|
454
|
+
[permissions](/reference/mobile#engine).
|
|
455
|
+
|
|
456
|
+
#### `command`
|
|
457
|
+
|
|
458
|
+
The process the runner starts before the first test and stops on every exit
|
|
459
|
+
path: a dev server, or Metro for a debug build.
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
command: {
|
|
463
|
+
executable: 'pnpm',
|
|
464
|
+
args: ['dev', '--port', '{port}'],
|
|
465
|
+
inheritEnv: ['STRIPE_*'],
|
|
466
|
+
startupTimeout: 120_000, // default: 60000
|
|
467
|
+
},
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
| Field | Example | Default |
|
|
471
|
+
| --- | --- | --- |
|
|
472
|
+
| `executable` | `'npm'` | required |
|
|
473
|
+
| `args` | `['run', 'dev']` | none |
|
|
474
|
+
| `cwd` | `'apps/web'` | the project root |
|
|
475
|
+
| `env` | `{ PORT: '{port}' }` | none |
|
|
476
|
+
| `inheritEnv` | `['STRIPE_*']` | none |
|
|
477
|
+
| `startupTimeout` | `120_000` | `60000` |
|
|
478
|
+
| `shutdownTimeout` | `5_000` | `10000` |
|
|
479
|
+
| `log` | `'.e2e/logs/app.log'` | output discarded |
|
|
480
|
+
| `reuseExisting` | `true` | `false` |
|
|
481
|
+
|
|
482
|
+
- `executable` is resolved with `PATH` and never shell-interpreted.
|
|
483
|
+
- `args` pass verbatim. `{port}` in `args` and `env` values expands to the
|
|
484
|
+
port of `url`.
|
|
485
|
+
- `cwd` resolves from the project root.
|
|
486
|
+
- The child inherits only `PATH`, `HOME`, the temp-directory variables, the
|
|
487
|
+
variables `inheritEnv` matches, and `command.env`. Pass what the app needs
|
|
488
|
+
explicitly.
|
|
489
|
+
- `inheritEnv` takes exact names or prefixes ending in `*` (`'STRIPE_*'`). It
|
|
490
|
+
copies a variable only when it is set. `env` wins over an inherited value.
|
|
491
|
+
- Every `args` entry and `env` value is a string. An `undefined` `env` value
|
|
492
|
+
is dropped.
|
|
493
|
+
- `startupTimeout` is the budget of the [`readyUrl`](#readyurl) probe.
|
|
494
|
+
- `shutdownTimeout` is the grace period before force-kill.
|
|
495
|
+
- `log` receives the process's stdout and stderr, appended, resolved from the
|
|
496
|
+
project root. It must stay inside the project root.
|
|
497
|
+
- A startup failure quotes the last 20 lines appended since the command
|
|
498
|
+
started, with `command.env` and `inheritEnv` values replaced by
|
|
499
|
+
`<secret:NAME>` markers.
|
|
500
|
+
- `reuseExisting`: when the readiness URL already answers before the command
|
|
501
|
+
starts, the runner uses that process. It starts nothing and stops nothing.
|
|
502
|
+
CI ignores it, with a notice, and always starts the command.
|
|
503
|
+
- Targets declaring the same command share one process, probed at the first
|
|
504
|
+
declaring target's `readyUrl`.
|
|
505
|
+
- The runner never terminates a process it did not start.
|
|
506
|
+
|
|
507
|
+
| Throws | When |
|
|
508
|
+
| --- | --- |
|
|
509
|
+
| `INVALID_CONFIG` | A bare `'*'` in `inheritEnv`. |
|
|
510
|
+
| `INVALID_CONFIG` | An `args` entry or `env` value that is not a string, a number or a `secrets.get()` handle included. |
|
|
511
|
+
| `INVALID_CONFIG` | `reuseExisting` on an app whose `url` asks for port 0: a process already running cannot serve the port the run assigns. |
|
|
512
|
+
| `APP_URL_REQUIRED` | An `app.command` with neither `app.url` nor `app.readyUrl`, at config load. |
|
|
513
|
+
| `APP_ALREADY_RUNNING` | Without `reuseExisting`, a `readyUrl` that already answers. |
|
|
514
|
+
| `APP_UNREACHABLE` | `startupTimeout` expires. |
|
|
515
|
+
|
|
516
|
+
<Warning>
|
|
517
|
+
`log` holds the raw, unredacted output. Keep it in an ignored directory.
|
|
518
|
+
</Warning>
|
|
519
|
+
|
|
520
|
+
The full declaration is [`CommandConfig`](#commandconfig).
|
|
521
|
+
|
|
522
|
+
#### `readyUrl`
|
|
523
|
+
|
|
524
|
+
The URL polled until `command` is ready, at a 200-499 status.
|
|
525
|
+
|
|
526
|
+
```ts
|
|
527
|
+
readyUrl: 'http://localhost:3000/health', // default: url
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
- Required without a `url`.
|
|
531
|
+
- `{port}` in it is the port of `url`.
|
|
340
532
|
|
|
341
|
-
|
|
533
|
+
#### The app's site
|
|
342
534
|
|
|
343
|
-
A test or the agent may open any http(s) URL, and `about:blank
|
|
344
|
-
|
|
345
|
-
`POLICY_DENIED`. The site of `url
|
|
346
|
-
|
|
347
|
-
the browser engine's [`headers`](/reference/web#options) reach the
|
|
348
|
-
site and no other host, and child frames off the site stay out of
|
|
349
|
-
observations.
|
|
535
|
+
A test or the agent may open any http(s) URL, and `about:blank`. Every other
|
|
536
|
+
scheme (`file:`, `data:`, `javascript:`, `view-source:`, `blob:`) is
|
|
537
|
+
`POLICY_DENIED`. The site of `url` is its registrable domain:
|
|
538
|
+
`app.example.com` and `auth.example.com` are one site. It scopes two things:
|
|
350
539
|
|
|
351
|
-
|
|
540
|
+
- The browser engine's [`headers`](/reference/web#options) reach the site and
|
|
541
|
+
no other host.
|
|
542
|
+
- Child frames off the site stay out of observations.
|
|
543
|
+
|
|
544
|
+
#### Free ports
|
|
352
545
|
|
|
353
546
|
A `url` on a literal loopback address with port `0` (`http://127.0.0.1:0`,
|
|
354
547
|
`http://[::1]:0`) asks the run for a free TCP port. The runner picks one when
|
|
355
|
-
the config loads
|
|
548
|
+
the config loads. It substitutes the port wherever the declaration used it:
|
|
356
549
|
the base URL, the default `readyUrl`, and the report. The command must take
|
|
357
550
|
the port through `{port}` in `args` or `env`.
|
|
358
551
|
|
|
@@ -364,17 +557,947 @@ app: {
|
|
|
364
557
|
},
|
|
365
558
|
```
|
|
366
559
|
|
|
367
|
-
`localhost:0` is `INVALID_APP_URL
|
|
368
|
-
the one the command binds. Write the address the command will
|
|
369
|
-
|
|
370
|
-
Each target asking for port `0` gets its own port and its own process
|
|
371
|
-
targets that declare the same command on a fixed port share one.
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
560
|
+
- `localhost:0` is [`INVALID_APP_URL`](#url): it may resolve to an address
|
|
561
|
+
other than the one the command binds. Write the address the command will
|
|
562
|
+
bind.
|
|
563
|
+
- Each target asking for port `0` gets its own port and its own process. Two
|
|
564
|
+
targets that declare the same command on a fixed port share one.
|
|
565
|
+
- The default [`identity`](#identity) derives from the declared `:0`.
|
|
566
|
+
- Tests read the allocated URL from [`app.baseUrl`](/reference/app).
|
|
567
|
+
|
|
568
|
+
#### services
|
|
569
|
+
|
|
570
|
+
<Note>
|
|
571
|
+
This version starts no dependency processes (a database, an API mock, a dev
|
|
572
|
+
server several targets share), and `services` is an unknown key wherever it
|
|
573
|
+
appears. Start them before the run, or have `app.command` start a script that
|
|
574
|
+
brings them up and serves the app. A services API returns in a later release.
|
|
575
|
+
</Note>
|
|
576
|
+
|
|
577
|
+
### output
|
|
578
|
+
|
|
579
|
+
The directory everything a run writes about itself goes under, resolved from
|
|
580
|
+
the project root.
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
output: 'test-results/e2e', // default: '.e2e'
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
`--output <dir>` on [`e2e run`](/reference/cli#e2e-run) and `e2e explore`
|
|
587
|
+
overrides it for one run.
|
|
588
|
+
|
|
589
|
+
| Path under the output | What |
|
|
590
|
+
| --- | --- |
|
|
591
|
+
| `report.json` | The canonical report; `--last-failed` reads the one here |
|
|
592
|
+
| `junit.xml`, `summary.md` | The `junit` and `markdown` reporters' files |
|
|
593
|
+
| `ai-trace.json` | `--ai-trace` |
|
|
594
|
+
| `results/` | One directory per test: its [trace page](/debugging#traces), `trace.md`, when the [`trace`](#trace) mode keeps one, and its screenshots, videos, downloads, and logs, one `attempt-<n>/` per attempt. Cleared once a run's tests start, so it holds the latest run's evidence and nothing its report no longer names. A `--last-failed` rerun keeps the files the report it reruns names and writes its own attempts under each test's `rerun-<n>/` |
|
|
595
|
+
| `sessions/` | The run's encrypted `test.setup` sessions, deleted when it ends |
|
|
596
|
+
| `videos/<session>/` | [`e2e mcp`](/reference/mcp#recording) session recordings |
|
|
597
|
+
|
|
598
|
+
- A run that stops before its tests start writes nothing here. Such stops are
|
|
599
|
+
a selection that matches no test (`NO_TESTS`, a mistyped `--grep`), a
|
|
600
|
+
collection error, a target that cannot record what it asks for
|
|
601
|
+
(`UNSUPPORTED_ARTIFACT`), `NO_LAST_RUN`, an app command that fails to start,
|
|
602
|
+
or an interrupt. The previous run's `report.json`, `ai-trace.json`, and
|
|
603
|
+
`results/` stay as they were, so `--last-failed` still reads the last run
|
|
604
|
+
that executed.
|
|
605
|
+
- An `e2e mcp` session writes its attempt's artifacts under `results/mcp/` and
|
|
606
|
+
never clears it.
|
|
607
|
+
- The replay cache stays in [`cache.dir`](#dir) whatever `output` says, so
|
|
608
|
+
moving the results never moves the recordings.
|
|
609
|
+
- `e2e init` writes `.gitignore` entries for the default `.e2e/` paths only.
|
|
610
|
+
With `output` set elsewhere, add that directory to `.gitignore` yourself.
|
|
611
|
+
|
|
612
|
+
A run clears and owns part of the output directory. So the config load fails
|
|
613
|
+
with `INVALID_CONFIG`, naming the reason, when the directory:
|
|
614
|
+
|
|
615
|
+
- is the project root, or outside it;
|
|
616
|
+
- is a file, or a path under one (a missing directory is created);
|
|
617
|
+
- is the cache directory (`cache.dir`, `--cache-dir`, or `E2E_CACHE_DIR`) or
|
|
618
|
+
inside it, or would hold it under `results/`, `sessions/`, or `videos/`;
|
|
619
|
+
- holds a directory a `tests` glob scans (`output: 'tests'` with the default
|
|
620
|
+
`tests/**/*.e2e.ts`).
|
|
621
|
+
|
|
622
|
+
Paths are compared with symlinks resolved. An absolute path spelled through a
|
|
623
|
+
symlink (`/tmp` for `/private/tmp` on macOS) names the same directory as its
|
|
624
|
+
target. A symlink whose target does not exist yet is followed too, so an
|
|
625
|
+
output through one that points outside the project root is refused.
|
|
626
|
+
|
|
627
|
+
<Note>
|
|
628
|
+
The removed `--artifacts <dir>` flag fails with the `--output` it maps to. The
|
|
629
|
+
report used to go to the directory's parent, so `--artifacts out/artifacts` is
|
|
630
|
+
`--output out`. A parent an output cannot be (the project root, or outside it)
|
|
631
|
+
maps to `--output .e2e`.
|
|
632
|
+
</Note>
|
|
633
|
+
|
|
634
|
+
### artifacts
|
|
635
|
+
|
|
636
|
+
`artifacts` says where artifacts go, not which are made. `{ store }` hands
|
|
637
|
+
every artifact to a host store as it is produced; without one, files stay
|
|
638
|
+
local only.
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
artifacts: {
|
|
642
|
+
store: {
|
|
643
|
+
async put(artifact) {
|
|
644
|
+
const key = await bucket.upload(artifact.path, artifact.bytes);
|
|
645
|
+
return { ref: key };
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
| Member | Returns |
|
|
652
|
+
| --- | --- |
|
|
653
|
+
| [`store.put`](#put) | `Promise<{ ref }>` |
|
|
654
|
+
| [`store.putLink`](#putlink) | `Promise<{ ref }>` |
|
|
655
|
+
|
|
656
|
+
- [`trace` and `video`](#recording) choose the recordings. The runner
|
|
657
|
+
captures a failure's screenshot and screen text whenever the engine can
|
|
658
|
+
(never after a secret fill, see
|
|
659
|
+
[Security rules](/reference/security#screenshots-after-a-secret-fill)).
|
|
660
|
+
- Each worker constructs its own store from the config module.
|
|
661
|
+
- A failed `put` or `putLink` never fails the run.
|
|
662
|
+
|
|
663
|
+
The removed forms fail with `INVALID_CONFIG` naming what replaced them:
|
|
664
|
+
|
|
665
|
+
| Written | Now |
|
|
666
|
+
| --- | --- |
|
|
667
|
+
| a kinds list (`artifacts: ['screenshot', 'trace']` or `artifacts.kinds`) | the `trace` mode it meant |
|
|
668
|
+
| `artifacts.trace` with `record: 'retries'` | `trace: 'on-all-retries'`, read together with a kinds list that holds `trace`, so the pair still means retries only |
|
|
669
|
+
| `artifacts.video` | the `video` option |
|
|
670
|
+
|
|
671
|
+
The declarations are under [`ArtifactsConfig`](#artifactsconfig).
|
|
672
|
+
|
|
673
|
+
#### `put`
|
|
674
|
+
|
|
675
|
+
Receives every artifact once it is complete on disk and returns a reference
|
|
676
|
+
the report records as `ref` beside the local path.
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
async put(artifact) {
|
|
680
|
+
return { ref: await bucket.upload(artifact.path, artifact.bytes) };
|
|
681
|
+
},
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
- Required on every store.
|
|
685
|
+
- `artifact.redaction` is the report's label. A store that exports only what
|
|
686
|
+
the runner vouches for keeps back an `incomplete` artifact: a video or a
|
|
687
|
+
download the runner did not rewrite.
|
|
688
|
+
|
|
689
|
+
#### `putLink`
|
|
690
|
+
|
|
691
|
+
Receives a video a hosted service keeps (a
|
|
692
|
+
[provider's own recording](#what-a-video-writes)). Such a video is a link with
|
|
693
|
+
no bytes, so `put` never receives it.
|
|
694
|
+
|
|
695
|
+
```ts
|
|
696
|
+
async putLink(link) {
|
|
697
|
+
return { ref: await index.record(link.url, link.attemptId) };
|
|
698
|
+
},
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
- Optional. Called for each link once the attempt stopped the recording.
|
|
702
|
+
- Its `ref` lands on the report's artifact beside `url`.
|
|
703
|
+
- A passed attempt's link under `retain-on-failure` is never handed over,
|
|
704
|
+
since the report leaves it out too.
|
|
705
|
+
|
|
706
|
+
### Recording
|
|
707
|
+
|
|
708
|
+
`trace` and `video` choose what is recorded, with one set of modes.
|
|
709
|
+
|
|
710
|
+
```ts
|
|
711
|
+
trace: 'on',
|
|
712
|
+
video: 'retain-on-failure',
|
|
713
|
+
targets: [
|
|
714
|
+
{ name: 'chromium', engine: web(), app: { url } },
|
|
715
|
+
{ name: 'firefox', engine: web({ browser: 'firefox' }), app: { url }, video: 'on-first-retry' },
|
|
716
|
+
],
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
| Option | Type | Default |
|
|
720
|
+
| --- | --- | --- |
|
|
721
|
+
| [`trace`](#trace) | `RecordingMode` | `'retain-on-failure'` |
|
|
722
|
+
| [`video`](#video) | `RecordingMode` | `'off'` |
|
|
723
|
+
|
|
724
|
+
```ts
|
|
725
|
+
/** Which tests keep a trace page, or which attempts record a video. */
|
|
726
|
+
export type RecordingMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'on-all-retries';
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
| Mode | Trace page | Video |
|
|
730
|
+
| --- | --- | --- |
|
|
731
|
+
| `off` | none | none |
|
|
732
|
+
| `on` | every test, passing ones included | every attempt |
|
|
733
|
+
| `retain-on-failure` | each failed, timed-out, interrupted, or flaky test | every attempt, keeping those that did not pass |
|
|
734
|
+
| `on-first-retry` | each test whose first retry ran | the first retry only, the second attempt |
|
|
735
|
+
| `on-all-retries` | each test that was retried | every attempt after the first |
|
|
736
|
+
|
|
737
|
+
The names are Playwright's `use.trace` and `use.video` modes. Under a retry
|
|
738
|
+
mode, a test with `retries: 0` records nothing, and neither does a failure
|
|
739
|
+
the runner does not retry, such as `LAUNCH_TIMEOUT`. When such a test runs,
|
|
740
|
+
the run prints one notice per kind at plan time: no traces (or videos) will
|
|
741
|
+
be recorded for it.
|
|
742
|
+
|
|
743
|
+
Four places set a mode, lowest first. Each replaces the one before it, for
|
|
744
|
+
`trace` and `video` alike:
|
|
745
|
+
|
|
746
|
+
1. `trace` or `video` at the top of the config.
|
|
747
|
+
2. A target's `trace` or `video`.
|
|
748
|
+
3. `--trace [mode]` or `--video [mode]` on [`e2e run`](/reference/cli#e2e-run),
|
|
749
|
+
over the config and every target. A bare flag is `on`.
|
|
750
|
+
4. A test's or describe's own [`trace` or `video` option](/reference/test#options),
|
|
751
|
+
innermost wins: `test('checkout', { video: 'on' }, ...)`.
|
|
752
|
+
|
|
753
|
+
- A serial group records as one unit, so its members cannot set `trace` or
|
|
754
|
+
`video` (a `COLLECTION_ERROR`, like `retries`). The value on the serial
|
|
755
|
+
`describe`, or on a group around it, applies to every member.
|
|
756
|
+
- `e2e explore` is one attempt and uses the target's mode, `--trace` and
|
|
757
|
+
`--video` included, so a retry mode records nothing there.
|
|
758
|
+
- An `e2e mcp` session writes no trace and never records video from the
|
|
759
|
+
configured mode. Its recordings are the ones `start_recording` and
|
|
760
|
+
`stop_recording` make.
|
|
761
|
+
- `trace` and `video`, at the top or on a target, enter neither the config
|
|
762
|
+
digest nor a replay's [cache key](/cache#what-must-match). Turning one on or
|
|
763
|
+
off never invalidates cached replays.
|
|
764
|
+
|
|
765
|
+
<Note>
|
|
766
|
+
The old trace spellings fail with `INVALID_CONFIG` naming the mode they
|
|
767
|
+
meant, wherever a mode goes (the config, a target, a test, `--trace`):
|
|
768
|
+
`'all'` and `{ record: 'all' }` are `'on'`, and `'retries'` and `{ record:
|
|
769
|
+
'retries' }` are `'on-all-retries'`.
|
|
770
|
+
</Note>
|
|
771
|
+
|
|
772
|
+
#### `trace`
|
|
773
|
+
|
|
774
|
+
Which tests keep a trace page.
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
trace: 'on', // default: 'retain-on-failure'
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
- The default is the same locally and in CI.
|
|
781
|
+
- A trace page is markdown, `trace.md` in the test's directory under
|
|
782
|
+
`<output>/results/`, such as
|
|
783
|
+
`.e2e/results/checkout-applies-the-coupon-1a2b3c4d5e6f7a8b/trace.md`: every
|
|
784
|
+
step with what it did, the cache's decisions, what the app logged, and the
|
|
785
|
+
screen at failure.
|
|
786
|
+
- It works on every engine. [Debugging](/debugging#traces) shows one.
|
|
787
|
+
|
|
788
|
+
#### `video`
|
|
789
|
+
|
|
790
|
+
Which attempts record a video.
|
|
791
|
+
|
|
792
|
+
```ts
|
|
793
|
+
video: 'retain-on-failure', // default: 'off'
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
- Where the mode is set decides what an engine that cannot record does. See
|
|
797
|
+
[Engines that cannot record](#engines-that-cannot-record).
|
|
798
|
+
- A video costs CPU and holds what the screen showed, secrets included, so it
|
|
799
|
+
is always asked for.
|
|
800
|
+
|
|
801
|
+
#### Engines that cannot record
|
|
802
|
+
|
|
803
|
+
Before any test starts, the run grades every target. Where a `video` mode
|
|
804
|
+
was set decides what an engine that cannot record video does:
|
|
805
|
+
|
|
806
|
+
- Set at the top of the config or with `--video`: the mode applies to the
|
|
807
|
+
targets whose engine can record it. The run prints one notice at plan time
|
|
808
|
+
naming the targets that cannot, and runs them without video.
|
|
809
|
+
- Set on a target, or on a test that will run on that target: the mode is
|
|
810
|
+
required. A target whose engine cannot record it fails the run with
|
|
811
|
+
`UNSUPPORTED_ARTIFACT`, naming the target and, for a test's, the test.
|
|
812
|
+
|
|
813
|
+
Only a mode that records on some attempt counts: a run whose tests all
|
|
814
|
+
resolve to `off`, or to a retry mode with `retries: 0`, asks nothing of the
|
|
815
|
+
engine.
|
|
816
|
+
|
|
817
|
+
#### What a video writes
|
|
818
|
+
|
|
819
|
+
| Recorder | Files under the attempt's artifact directory |
|
|
820
|
+
| --- | --- |
|
|
821
|
+
| Browser engine, Playwright screencast | `video/video.webm`, one segment per page the attempt showed, later ones as `video/video-part<n>.webm`. [`web({ screencast })`](/reference/web#options) sets the frame size and quality |
|
|
822
|
+
| Mobile engine, agent-device | `video/video.mp4` |
|
|
823
|
+
| A provider's own recording ([`BrowserProvider.record`](/browser#recording-on-the-provider), [`DeviceProvider.record`](/mobile#hosted-devices)) | the file the provider wrote, as `video/<file>`: Kernel's is `video/replay.mp4`. Or a link to a recording the service keeps, recorded with `url` in place of `path`; the runner never downloads it |
|
|
824
|
+
|
|
825
|
+
- Each segment carries `startedAt`, so `step.startedAt - artifact.startedAt`
|
|
826
|
+
is a step's offset into it.
|
|
827
|
+
- A recording masks nothing and is recorded with `redaction: 'incomplete'`.
|
|
828
|
+
|
|
829
|
+
<Note>
|
|
830
|
+
Under `retain-on-failure` a link is left out of the report of a passed
|
|
831
|
+
attempt, but the service still holds the recording.
|
|
832
|
+
</Note>
|
|
833
|
+
|
|
834
|
+
### agents
|
|
835
|
+
|
|
836
|
+
Named agents, each one plain object: the built-in agent's options, or
|
|
837
|
+
`{ executor }` for a custom brain.
|
|
838
|
+
|
|
839
|
+
```ts title="e2e.config.ts"
|
|
840
|
+
import type { E2EConfig } from 'e2e';
|
|
841
|
+
import { web } from '@e2e-dev/web';
|
|
842
|
+
import { gateway } from 'ai';
|
|
843
|
+
import { houseBrain } from './house-brain.ts';
|
|
844
|
+
|
|
845
|
+
export default {
|
|
846
|
+
targets: [{ engine: web(), app: { url: 'http://localhost:3000' } }],
|
|
847
|
+
agents: {
|
|
848
|
+
default: { model: gateway('openai/gpt-6-luna-fast') },
|
|
849
|
+
ux: { model: gateway('openai/gpt-6-luna-fast'), system: 'Check labels, contrast, and keyboard access.' },
|
|
850
|
+
thorough: { model: gateway('anthropic/claude-opus-5'), context: 'Verify every claim on screen.' },
|
|
851
|
+
buyer: {
|
|
852
|
+
model: gateway('openai/gpt-6-luna-fast'),
|
|
853
|
+
system: 'You are a first-time buyer.',
|
|
854
|
+
context: 'Plans are called tiers. Billing lives under Settings.',
|
|
855
|
+
maxSteps: 40,
|
|
856
|
+
},
|
|
857
|
+
house: { executor: houseBrain, model: gateway('openai/gpt-6-luna-fast'), maxModelCalls: 10 },
|
|
858
|
+
},
|
|
859
|
+
} satisfies E2EConfig;
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
| Option | Type | Default |
|
|
863
|
+
| --- | --- | --- |
|
|
864
|
+
| [`model`](#model) | `ModelInstance` | - |
|
|
865
|
+
| [`judge`](#judge) | `ModelInstance` | `model` |
|
|
866
|
+
| [`context`](#context) | `string` | - |
|
|
867
|
+
| [`system`](#system) | `string` | - |
|
|
868
|
+
| [`tools`](#tools) | `Record<string, AgentTool>` | - |
|
|
869
|
+
| [`executor`](#executor) | `StepExecutor` | the built-in agent |
|
|
870
|
+
| [`maxSteps`](#maxsteps) | `number` | `25` |
|
|
871
|
+
| [`maxModelCalls`](#maxmodelcalls) | `number` | `25` |
|
|
872
|
+
| [`judgmentTimeout`](#judgmenttimeout) | `number` | `30000` |
|
|
873
|
+
| [`maxObservationBytes`](#maxobservationbytes) | `number` | `262144` |
|
|
874
|
+
| [`maxInputTokens`](#maxinputtokens) | `number` | `64000` |
|
|
875
|
+
| [`providerOptions`](#provideroptions) | `ProviderOptions` | - |
|
|
876
|
+
|
|
877
|
+
- Tests use the `default` agent unless you select another: with
|
|
878
|
+
`e2e run --agent <name>`, a test or group option, or an `agent.*` call. A
|
|
879
|
+
test with `{ agent: ['buyer', 'admin'] }` runs once for each agent.
|
|
880
|
+
- `default` exists even when the config declares no agents, but has no model.
|
|
881
|
+
- Every entry starts from the built-in defaults. Agents do not inherit from
|
|
882
|
+
each other: `ux` above gets no `context` from `default`. A key left out of
|
|
883
|
+
an entry is the default value, never another entry's.
|
|
884
|
+
- Each worker constructs its own agents from this config module.
|
|
885
|
+
- Names may contain ASCII letters, numbers, `_`, `-`, or `.`, and are never
|
|
886
|
+
only dots: a name is an artifact path segment.
|
|
887
|
+
|
|
888
|
+
| Throws | When |
|
|
889
|
+
| --- | --- |
|
|
890
|
+
| `INVALID_CONFIG` | An unknown agent name on the `--agent` flag. |
|
|
891
|
+
| `COLLECTION_ERROR` | An unknown agent name on a test or group. |
|
|
892
|
+
| `INVALID_ARGUMENT` | An unknown agent name on a call. |
|
|
893
|
+
|
|
894
|
+
See [Agents and personas](/agents). The declarations are under
|
|
895
|
+
[`AgentConfig`](#agentconfig).
|
|
896
|
+
|
|
897
|
+
#### `model`
|
|
898
|
+
|
|
899
|
+
An AI SDK model instance. There is no implicit default.
|
|
900
|
+
|
|
901
|
+
```ts
|
|
902
|
+
model: gateway('openai/gpt-6-luna-fast'),
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
Construct the model in your config with an AI SDK provider. Every provider
|
|
906
|
+
that supports tool calls works, see
|
|
907
|
+
[AI SDK providers](/models#ai-sdk-providers). For subscription sign-in, use
|
|
908
|
+
the [`e2e/oauth/*` models](/subscriptions).
|
|
909
|
+
|
|
910
|
+
| Gateway | Constructor | Package | Reads |
|
|
911
|
+
| --- | --- | --- | --- |
|
|
912
|
+
| [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) | `gateway('openai/gpt-6-luna-fast')` | `ai` | `AI_GATEWAY_API_KEY`, or a Vercel OIDC token without it |
|
|
913
|
+
| [OpenRouter](https://openrouter.ai/models) | `openrouter('openai/gpt-6-luna-fast')` | `@openrouter/ai-sdk-provider` | `OPENROUTER_API_KEY` |
|
|
914
|
+
| Any OpenAI-compatible endpoint | `createOpenAICompatible({ name, baseURL, apiKey? }).chatModel('llama3.2')` | `@ai-sdk/openai-compatible` | `apiKey`, when the endpoint needs one |
|
|
915
|
+
| A provider directly | `openai('gpt-6-luna')`, `anthropic(...)`, ... | `@ai-sdk/openai`, `@ai-sdk/anthropic`, ... | the provider's own variable |
|
|
916
|
+
|
|
917
|
+
- Reports record `provider` and `modelId` per step.
|
|
918
|
+
- Each worker constructs its own instance from the config module. A digest
|
|
919
|
+
that differs between the runner and a worker is
|
|
920
|
+
`CONFIG_NOT_DETERMINISTIC`.
|
|
921
|
+
- A string is `INVALID_CONFIG` naming the constructor to write.
|
|
922
|
+
- An agent never falls back to `default`'s model. See
|
|
923
|
+
[Resolution order](#resolution-order).
|
|
924
|
+
|
|
925
|
+
See [Models](/models) and [`ModelInstance`](#modelinstance).
|
|
926
|
+
|
|
927
|
+
#### `judge`
|
|
928
|
+
|
|
929
|
+
The model for `assert`, `waitFor`, and `extract`.
|
|
930
|
+
|
|
931
|
+
```ts
|
|
932
|
+
judge: gateway('anthropic/claude-opus-5'), // default: model
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
- Judgments allow 8192 output tokens per call.
|
|
936
|
+
- A judgment sees the instruction and the current screen only.
|
|
937
|
+
|
|
938
|
+
See [A judge of its own](/models#a-judge-of-its-own).
|
|
939
|
+
|
|
940
|
+
#### `context`
|
|
941
|
+
|
|
942
|
+
What the app calls things, told to every model call this agent makes, act
|
|
943
|
+
turns and judgments alike.
|
|
944
|
+
|
|
945
|
+
```ts
|
|
946
|
+
context: 'Plans are called tiers. Billing lives under Settings.',
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
At most 16384 bytes.
|
|
950
|
+
|
|
951
|
+
#### `system`
|
|
952
|
+
|
|
953
|
+
How the acting agent should work, appended to the built-in rules. Only the
|
|
954
|
+
act loop reads it.
|
|
955
|
+
|
|
956
|
+
```ts
|
|
957
|
+
system: 'You are a first-time buyer.',
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
With `executor` it is `INVALID_CONFIG`: the executor brings its own prompt.
|
|
961
|
+
|
|
962
|
+
#### `tools`
|
|
963
|
+
|
|
964
|
+
Project tools from `defineTool` (`e2e/agent`), offered beside the built-in
|
|
965
|
+
toolset.
|
|
966
|
+
|
|
967
|
+
```ts
|
|
968
|
+
tools: { lookupOrder },
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
With `executor` it is `INVALID_CONFIG`: the executor brings its own tools.
|
|
972
|
+
|
|
973
|
+
#### `executor`
|
|
974
|
+
|
|
975
|
+
A custom brain `agent.act()` dispatches to instead of the built-in agent.
|
|
976
|
+
|
|
977
|
+
```ts
|
|
978
|
+
house: { executor: houseBrain, model: gateway('openai/gpt-6-luna-fast'), maxModelCalls: 10 },
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
- `model`, `judge`, `context`, and the budgets still apply; judgments need a
|
|
982
|
+
model. [`system`](#system) and [`tools`](#tools) beside it are
|
|
983
|
+
`INVALID_CONFIG`.
|
|
984
|
+
- `maxModelCalls` bounds the model turns of one step.
|
|
985
|
+
- A `model` or `judge` that differs from the one the executor brought is
|
|
986
|
+
`INVALID_CONFIG`.
|
|
987
|
+
|
|
988
|
+
See [Custom executors](/executors).
|
|
989
|
+
|
|
990
|
+
#### `maxSteps`
|
|
991
|
+
|
|
992
|
+
Committed actions per agent call.
|
|
993
|
+
|
|
994
|
+
```ts
|
|
995
|
+
maxSteps: 40, // default: 25
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
1 through 100.
|
|
999
|
+
|
|
1000
|
+
#### `maxModelCalls`
|
|
1001
|
+
|
|
1002
|
+
Model requests per agent call.
|
|
1003
|
+
|
|
1004
|
+
```ts
|
|
1005
|
+
maxModelCalls: 10, // default: 25
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
1 through 100.
|
|
1009
|
+
|
|
1010
|
+
#### `judgmentTimeout`
|
|
1011
|
+
|
|
1012
|
+
The deadline of one `assert`, `waitFor`, or `extract` call, in milliseconds.
|
|
1013
|
+
|
|
1014
|
+
```ts
|
|
1015
|
+
judgmentTimeout: 60_000, // default: 30000
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
Raise it for a slow judge. Engine operations keep `actionTimeout`.
|
|
1019
|
+
|
|
1020
|
+
#### `maxObservationBytes`
|
|
1021
|
+
|
|
1022
|
+
The observation payload ceiling for act turns and judgments.
|
|
1023
|
+
|
|
1024
|
+
```ts
|
|
1025
|
+
maxObservationBytes: 524_288, // default: 262144
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
1024 through 16777216.
|
|
1029
|
+
|
|
1030
|
+
#### `maxInputTokens`
|
|
1031
|
+
|
|
1032
|
+
Input tokens per model request.
|
|
1033
|
+
|
|
1034
|
+
```ts
|
|
1035
|
+
maxInputTokens: 128_000, // default: 64000
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
- 1 through 1000000.
|
|
1039
|
+
- A dense screen is cut to fit under it.
|
|
1040
|
+
|
|
1041
|
+
#### `providerOptions`
|
|
1042
|
+
|
|
1043
|
+
Provider options every model call carries, such as a reasoning effort.
|
|
1044
|
+
|
|
1045
|
+
```ts
|
|
1046
|
+
providerOptions: { openai: { reasoningEffort: 'low' } },
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
OpenAI and Azure OpenAI Responses calls also carry `store: false` and a prompt
|
|
1050
|
+
cache key unless set here.
|
|
1051
|
+
|
|
1052
|
+
#### Removed options
|
|
1053
|
+
|
|
1054
|
+
An unknown key (the error names the nearest known one), a removed key, or a
|
|
1055
|
+
value in an old shape fails the config load with `INVALID_CONFIG`:
|
|
1056
|
+
|
|
1057
|
+
| Written | Now |
|
|
1058
|
+
| --- | --- |
|
|
1059
|
+
| a bare `StepExecutor` as the entry | `{ executor }` |
|
|
1060
|
+
| `timeout` | `judgmentTimeout` |
|
|
1061
|
+
| `maxTurns` (on the entry or `createToolLoopExecutor`) | `maxModelCalls` |
|
|
1062
|
+
| top-level `limits.maxModelTokensPerCall` | `maxInputTokens` on each agent |
|
|
1063
|
+
| top-level `limits.maxAgentContextBytes`, `maxLedgerBytes`, `maxEventsPerStep` | removed; the runner fixes them at 16384 bytes, 8192 bytes, and 1000 events |
|
|
1064
|
+
|
|
1065
|
+
### credentials
|
|
1066
|
+
|
|
1067
|
+
Named accounts. Each entry is a username and a password the model never sees.
|
|
1068
|
+
|
|
1069
|
+
```ts
|
|
1070
|
+
credentials: {
|
|
1071
|
+
admin: { username: 'admin@example.com', password: () => readFile('.secrets/admin', 'utf8') },
|
|
1072
|
+
},
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
| Field | Type |
|
|
1076
|
+
| --- | --- |
|
|
1077
|
+
| [`username`](#username) | `string` |
|
|
1078
|
+
| [`password`](#password) | `string \| SecretProvider` |
|
|
1079
|
+
|
|
1080
|
+
- Credentials and secrets are separate namespaces. `secrets.get(name)`
|
|
1081
|
+
returns a `secrets` entry only. A credential's password is reachable only
|
|
1082
|
+
as `credentials.user(name).password`.
|
|
1083
|
+
- The agent, the replay cache, and the reports know that password as a secret
|
|
1084
|
+
named `<name>.password` (`<secret:admin.password>`). So `credentials.admin`
|
|
1085
|
+
and `secrets.admin` can both exist.
|
|
1086
|
+
|
|
1087
|
+
| Throws | When |
|
|
1088
|
+
| --- | --- |
|
|
1089
|
+
| `INVALID_CONFIG` | A `secrets` entry named exactly `admin.password` beside an `admin` credential. |
|
|
1090
|
+
| `INVALID_CONFIG` | Two credentials whose names map to one `E2E_USER_<NAME>_*` override, like `svc-a` and `svc_a` (see [Environment variables](/reference/environment)). |
|
|
1091
|
+
|
|
1092
|
+
See [Signing in](/authentication). The declarations are under
|
|
1093
|
+
[`CredentialConfig`](#credentialconfig).
|
|
1094
|
+
|
|
1095
|
+
#### `username`
|
|
1096
|
+
|
|
1097
|
+
The account's plain username.
|
|
1098
|
+
|
|
1099
|
+
```ts
|
|
1100
|
+
username: 'admin@example.com',
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
`E2E_USER_<NAME>_USERNAME` wins over the config value. With neither, the
|
|
1104
|
+
entry is `INVALID_CONFIG`.
|
|
1105
|
+
|
|
1106
|
+
#### `password`
|
|
1107
|
+
|
|
1108
|
+
The account's password: a static value, or a `SecretProvider` function.
|
|
1109
|
+
|
|
1110
|
+
```ts
|
|
1111
|
+
password: () => readFile('.secrets/admin', 'utf8'),
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
- A static password is at least 6 characters (code points), like a
|
|
1115
|
+
[secret](#secrets), or `INVALID_CONFIG`.
|
|
1116
|
+
- A `SecretProvider` runs on every authorized fill (a vault lookup, a freshly
|
|
1117
|
+
computed TOTP). The config digest records it by name only. A provider
|
|
1118
|
+
returning fewer than 6 characters fails the fill with
|
|
1119
|
+
`AUTH_CREDENTIAL_UNAVAILABLE`.
|
|
1120
|
+
- `E2E_USER_<NAME>_PASSWORD` wins over both.
|
|
1121
|
+
|
|
1122
|
+
#### `credentials.user()`
|
|
1123
|
+
|
|
1124
|
+
Resolves a configured entry into a `Credential` in a test.
|
|
1125
|
+
|
|
1126
|
+
```ts
|
|
1127
|
+
const admin = credentials.user('admin');
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
- The username is a plain string. The password is an opaque `Secret` that
|
|
1131
|
+
test code cannot read, accepted only by `locator.fill` and `agent.act`
|
|
1132
|
+
params.
|
|
1133
|
+
- An unconfigured name throws `AUTH_CREDENTIAL_UNAVAILABLE`.
|
|
1134
|
+
|
|
1135
|
+
### secrets
|
|
1136
|
+
|
|
1137
|
+
Named values the model must never see: API keys, tokens, anything sourced
|
|
1138
|
+
from the environment.
|
|
1139
|
+
|
|
1140
|
+
```ts
|
|
1141
|
+
secrets: {
|
|
1142
|
+
apiKey: () => readFile('.secrets/api-key', 'utf8'),
|
|
1143
|
+
},
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
| Entry | Example | Checked |
|
|
1147
|
+
| --- | --- | --- |
|
|
1148
|
+
| a static value | `'sk_test_51Hx…'` | at config load |
|
|
1149
|
+
| a provider | `() => readFile('.secrets/api-key', 'utf8')` | on every authorized fill |
|
|
1150
|
+
|
|
1151
|
+
- A provider runs on every authorized fill (a vault lookup, a freshly
|
|
1152
|
+
computed TOTP). The config digest records it by name only.
|
|
1153
|
+
- An `E2E_SECRET_<NAME>` override wins over both.
|
|
1154
|
+
- Every entry must resolve to a string of at least 6 characters (code points)
|
|
1155
|
+
or a function; anything else is `INVALID_CONFIG`. A provider's value is held
|
|
1156
|
+
to the same minimum at fill time, with `SECRET_UNAVAILABLE`.
|
|
1157
|
+
- The minimum exists for redaction. Every occurrence of a value becomes
|
|
1158
|
+
`<secret:name>`, so a shorter value would take ordinary text in reports and
|
|
1159
|
+
output with it.
|
|
1160
|
+
- Two entries whose names map to one variable, like `api-key` and `api_key`,
|
|
1161
|
+
are `INVALID_CONFIG`.
|
|
1162
|
+
|
|
1163
|
+
The declarations are under [`SecretConfig`](#secretconfig). See
|
|
1164
|
+
[Secrets that are not passwords](/authentication#secrets-that-are-not-passwords).
|
|
1165
|
+
|
|
1166
|
+
#### `secrets.get()`
|
|
1167
|
+
|
|
1168
|
+
Returns the opaque `Secret` for a configured entry.
|
|
1169
|
+
|
|
1170
|
+
```ts
|
|
1171
|
+
await screen.getByLabel('API key').fill(secrets.get('apiKey'));
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
- It has no plaintext accessor and is accepted only by `locator.fill` and
|
|
1175
|
+
`agent.act` params.
|
|
1176
|
+
- The runner fills the field, masks the value in every observation, and
|
|
1177
|
+
redacts it from logs, traces, and the report.
|
|
1178
|
+
- An unconfigured name throws `SECRET_UNAVAILABLE`.
|
|
1179
|
+
|
|
1180
|
+
#### Secrets in engine options
|
|
1181
|
+
|
|
1182
|
+
While `e2e.config.ts` evaluates, before any run exists, `secrets.get(name)`
|
|
1183
|
+
returns a reference by name that an engine option can hold.
|
|
1184
|
+
|
|
1185
|
+
```ts
|
|
1186
|
+
engine: web({ basicAuth: { username, password: secrets.get('previewPassword') } }),
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
- The engine declares it. When no `secrets` entry has the name, the config
|
|
1190
|
+
load fails with `INVALID_CONFIG`. A credential's password is not one:
|
|
1191
|
+
declare a value an engine option needs under `secrets`.
|
|
1192
|
+
- The engine resolves the value when each attempt starts, so a provider runs
|
|
1193
|
+
again every time. The value then joins the attempt's redaction. It goes to
|
|
1194
|
+
the engine, not onto the page, so it brings none of a fill's screenshot
|
|
1195
|
+
restrictions.
|
|
1196
|
+
- The digest records the name only.
|
|
1197
|
+
- The reference is not the value. Turned into a string (a template literal,
|
|
1198
|
+
`String()`, `+`, `JSON.stringify`), it throws `INVALID_CONFIG`. So does a
|
|
1199
|
+
reference in an `args` entry or `env` value of `app.command`, which takes
|
|
1200
|
+
strings and placeholders only. Read a value a string needs, such as an
|
|
1201
|
+
app's API key, from `process.env` yourself.
|
|
1202
|
+
|
|
1203
|
+
### cache
|
|
1204
|
+
|
|
1205
|
+
Replay cache mode or options. In `read-write` mode the replay cache records an
|
|
1206
|
+
`agent.act()` only after a later verification passes, such as a locator
|
|
1207
|
+
assertion or `agent.assert()`.
|
|
1208
|
+
|
|
1209
|
+
```ts
|
|
1210
|
+
cache: { mode: 'read-write', dir: '.e2e/cache', strict: true },
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
| Option | Type | Default |
|
|
1214
|
+
| --- | --- | --- |
|
|
1215
|
+
| [`mode`](#mode) | `CacheMode` | `'read-write'`, `'read-only'` in CI |
|
|
1216
|
+
| [`store`](#store) | `CacheStore` | the file store at `dir` |
|
|
1217
|
+
| [`dir`](#dir) | `string` | `'.e2e/cache'` |
|
|
1218
|
+
| [`strict`](#strict) | `boolean` | `false` |
|
|
1219
|
+
|
|
1220
|
+
A bare string is shorthand for `{ mode }`: `cache: 'read-only'`. See
|
|
1221
|
+
[Caching](/cache). The declarations are under [`CacheConfig`](#cacheconfig).
|
|
1222
|
+
|
|
1223
|
+
#### `mode`
|
|
1224
|
+
|
|
1225
|
+
What the replay cache reads and writes.
|
|
1226
|
+
|
|
1227
|
+
```ts
|
|
1228
|
+
cache: 'read-write', // default: 'read-write' locally, 'read-only' in CI
|
|
1229
|
+
```
|
|
1230
|
+
|
|
1231
|
+
| Mode | Does |
|
|
1232
|
+
| --- | --- |
|
|
1233
|
+
| `'read-write'` | Replays entries and records new ones. |
|
|
1234
|
+
| `'read-only'` | Replays existing entries and writes none. |
|
|
1235
|
+
| `'off'` | Disables both. |
|
|
1236
|
+
|
|
1237
|
+
- An explicit mode overrides the default. Set `'read-write'` explicitly when
|
|
1238
|
+
CI restores the cache from its own cache service.
|
|
1239
|
+
- `--no-cache` wins over both and disables caching for the run.
|
|
1240
|
+
|
|
1241
|
+
#### `store`
|
|
1242
|
+
|
|
1243
|
+
A custom entry store that replaces the file store.
|
|
1244
|
+
|
|
1245
|
+
```ts
|
|
1246
|
+
cache: { store: createStore(cacheUrl) },
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
- Each worker constructs its own.
|
|
1250
|
+
- Exempt from the CI demotion: it states its own trust through `writable`.
|
|
1251
|
+
- `--cache-dir` beside it is `INVALID_CONFIG`, and `E2E_CACHE_DIR` is ignored.
|
|
1252
|
+
|
|
1253
|
+
`CacheStore`, `CacheReadResult`, `TraceEntry`, and `ActionTrace` are exported
|
|
1254
|
+
from `e2e`.
|
|
1255
|
+
|
|
1256
|
+
#### `dir`
|
|
1257
|
+
|
|
1258
|
+
The file store directory, resolved against the project root.
|
|
1259
|
+
|
|
1260
|
+
```ts
|
|
1261
|
+
cache: { dir: '.cache/e2e' }, // default: '.e2e/cache'
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
- It stays where it is whatever `output` says.
|
|
1265
|
+
- `--cache-dir` and `E2E_CACHE_DIR` replace it.
|
|
1266
|
+
|
|
1267
|
+
#### `strict`
|
|
1268
|
+
|
|
1269
|
+
Fails a step whose recording exists but no longer replays with
|
|
1270
|
+
`REPLAY_STALE`, instead of handing it to the agent.
|
|
1271
|
+
|
|
1272
|
+
```ts
|
|
1273
|
+
cache: { strict: true }, // default: false
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
- `--strict-cache` turns it on for one run.
|
|
1277
|
+
- The run then never writes the cache.
|
|
1278
|
+
|
|
1279
|
+
See [Fail on a stale recording](/cache#fail-on-a-stale-recording).
|
|
1280
|
+
|
|
1281
|
+
#### Commit the replay cache
|
|
1282
|
+
|
|
1283
|
+
`e2e init` adds `.e2e/cache/` to `.gitignore`, so committing entries is
|
|
1284
|
+
opt-in. Remove that line to share replays with CI and teammates. CI stays
|
|
1285
|
+
`read-only` unless `cache: 'read-write'` is set. See
|
|
1286
|
+
[Caching](/cache#commit-the-replay-cache).
|
|
1287
|
+
|
|
1288
|
+
## Run limits
|
|
1289
|
+
|
|
1290
|
+
The report's `run.limits` block records the ceilings a run enforced. There is
|
|
1291
|
+
no `limits` key: two fields come from the agents, and the runner fixes the
|
|
1292
|
+
rest.
|
|
1293
|
+
|
|
1294
|
+
| Report field | Value |
|
|
1295
|
+
| --- | --- |
|
|
1296
|
+
| `maxAgentContextBytes` | 16384: an agent's `context` and a test's `agentContext` |
|
|
1297
|
+
| `maxLedgerBytes` | 8192: the prior-step ledger an act step reads |
|
|
1298
|
+
| `maxEventsPerStep` | 1000: events recorded per step |
|
|
1299
|
+
| `maxObservationBytes` | the largest `agents.<name>.maxObservationBytes` |
|
|
1300
|
+
| `maxModelTokensPerCall` | the largest `agents.<name>.maxInputTokens` |
|
|
1301
|
+
|
|
1302
|
+
Per-call options may only lower an agent's budgets.
|
|
1303
|
+
|
|
1304
|
+
## Environment variables
|
|
1305
|
+
|
|
1306
|
+
The runner reads a closed set, listed on the
|
|
1307
|
+
[Environment variables](/reference/environment) page. `E2E_USER_<NAME>_USERNAME`
|
|
1308
|
+
and `E2E_USER_<NAME>_PASSWORD` override a credential, `E2E_SECRET_<NAME>` a
|
|
1309
|
+
secret, and `CI` switches the defaults below.
|
|
1310
|
+
|
|
1311
|
+
### Resolution order
|
|
1312
|
+
|
|
1313
|
+
| Setting | Order |
|
|
1314
|
+
| --- | --- |
|
|
1315
|
+
| Base URL | the target's `app.url`, else `APP_URL_REQUIRED` at the first navigation. The runner reads no `APP_URL`; the scaffolded config reads it itself |
|
|
1316
|
+
| Built-in agent model | `agents.<name>.model`, then `MODEL_UNAVAILABLE` at `agent` acquisition; an agent never falls back to `default`'s model |
|
|
1317
|
+
| Custom executor model | `agents.<name>.model`, else the model the executor brought; two different values is `INVALID_CONFIG` |
|
|
1318
|
+
| Model credential | the provider package's own variable or the key passed at construction, read by the provider on the first model call |
|
|
1319
|
+
| Credential username and password | the `E2E_USER_*` variable, then the config value |
|
|
1320
|
+
|
|
1321
|
+
The built-in agent checks for a missing model once per run, when the `agent`
|
|
1322
|
+
fixture is first acquired, and stops the run with exit 2. A custom
|
|
1323
|
+
[executor](#executor) can implement `act` and `assert` without a model.
|
|
1324
|
+
`waitFor` and `extract` still require one.
|
|
1325
|
+
|
|
1326
|
+
### CI
|
|
1327
|
+
|
|
1328
|
+
CI mode is on unless `CI` is unset, blank, `0`, or `false`.
|
|
1329
|
+
|
|
1330
|
+
| Setting | Local | CI |
|
|
1331
|
+
| --- | --- | --- |
|
|
1332
|
+
| `retries` | `0` | `1` |
|
|
1333
|
+
| `workers` | `max(1, floor(availableParallelism() / 2))` | `1` |
|
|
1334
|
+
| `cache` | `read-write` | `read-only` when unset |
|
|
1335
|
+
| `test.only` | allowed | `ONLY_IN_CI`, exit code 2 |
|
|
1336
|
+
| `command.reuseExisting` | honored | ignored, with a notice |
|
|
1337
|
+
|
|
1338
|
+
## Loading
|
|
1339
|
+
|
|
1340
|
+
How the runner finds the config and loads it and the files it imports.
|
|
1341
|
+
|
|
1342
|
+
### Finding the config file
|
|
1343
|
+
|
|
1344
|
+
- `--config <path>` selects an explicit file.
|
|
1345
|
+
- Otherwise the runner searches the working directory and each parent for
|
|
1346
|
+
`e2e.config.ts` or `e2e.config.mts`, stopping at the repository root.
|
|
1347
|
+
- Both names in one directory is `CONFIG_AMBIGUOUS`.
|
|
1348
|
+
- The selected file's directory is the project root.
|
|
1349
|
+
|
|
1350
|
+
### `.env` files
|
|
1351
|
+
|
|
1352
|
+
e2e loads no `.env` file itself. Load one at the top of `e2e.config.ts` with
|
|
1353
|
+
Node's `process.loadEnvFile()`. Every worker imports the config again, so the
|
|
1354
|
+
variables reach the workers too:
|
|
1355
|
+
|
|
1356
|
+
```ts title="e2e.config.ts"
|
|
1357
|
+
process.loadEnvFile('.env');
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
It throws when the file is missing; guard it with `existsSync` from
|
|
1361
|
+
`node:fs` when only some machines have one.
|
|
1362
|
+
|
|
1363
|
+
### Module format
|
|
1364
|
+
|
|
1365
|
+
- Config and test files run as ESM whatever the nearest `package.json`
|
|
1366
|
+
`type` says. So does every `.ts`, `.mts`, `.tsx`, and `.jsx` file they
|
|
1367
|
+
reach: a helper by path, a package that exports its `.ts` source by name, or
|
|
1368
|
+
a `.ts` file a CommonJS file requires.
|
|
1369
|
+
- A `.cts` file is CommonJS, written with `require`, `import x = require()`,
|
|
1370
|
+
and `export =`; its `import` statements are not turned into `require`
|
|
1371
|
+
calls.
|
|
1372
|
+
- A `.ts` file written with `require` or `module.exports` fails to load: name
|
|
1373
|
+
it `.cts`.
|
|
1374
|
+
- JavaScript keeps the format its package declares.
|
|
1375
|
+
|
|
1376
|
+
### Import resolution
|
|
1377
|
+
|
|
1378
|
+
e2e compiles TypeScript and JSX itself. An import in such a file resolves the
|
|
1379
|
+
way TypeScript resolves it:
|
|
1380
|
+
|
|
1381
|
+
- `./helper.js` and `./helper` load `helper.ts`, and `./view.jsx` loads
|
|
1382
|
+
`view.tsx`.
|
|
1383
|
+
- `./lib` loads the file `lib/package.json` names as `main`, else
|
|
1384
|
+
`lib/index.ts`.
|
|
1385
|
+
- A bare import that `tsconfig.json` maps through `paths` or `baseUrl` loads
|
|
1386
|
+
the mapped file.
|
|
1387
|
+
- A `#` import or package export that names a missing `./x.js`, `./x.jsx`, or
|
|
1388
|
+
`./x` loads its `x.ts` or `x.tsx` source when imported.
|
|
1389
|
+
- A `#` import or package export that names a directory, or a subpath of a
|
|
1390
|
+
package without `exports` that is a directory (`@acme/db/enums`), loads it
|
|
1391
|
+
the way `./lib` loads. Under Yarn PnP that package subpath still fails.
|
|
1392
|
+
- A `require()` of any of the last two fails as Node.js reports it.
|
|
1393
|
+
- An import written in a JavaScript file resolves as Node.js resolves it.
|
|
1394
|
+
|
|
1395
|
+
### `tsconfig.json`
|
|
1396
|
+
|
|
1397
|
+
- The `tsconfig.json` is the nearest one above the file. When that one has
|
|
1398
|
+
`references` and does not include the file itself (as Vite writes it), it
|
|
1399
|
+
is the transitively referenced one that does.
|
|
1400
|
+
- References are visited in declaration order, and cycles are skipped. If
|
|
1401
|
+
none owns the file, the nearest config remains the fallback.
|
|
1402
|
+
- e2e reads its `paths`, `baseUrl`, `jsx`, `jsxFactory`,
|
|
1403
|
+
`jsxFragmentFactory`, `jsxImportSource`, `experimentalDecorators`,
|
|
1404
|
+
`emitDecoratorMetadata`, `useDefineForClassFields` (and `target`, which
|
|
1405
|
+
sets its default), and `verbatimModuleSyntax`, once per process and again
|
|
1406
|
+
for each session `e2e mcp` opens.
|
|
1407
|
+
- A `tsconfig.json` e2e cannot read, such as one that `extends` a package that
|
|
1408
|
+
is not installed, is skipped with a warning.
|
|
1409
|
+
- TypeScript inside an installed package compiles without the project's
|
|
1410
|
+
`tsconfig.json`.
|
|
1411
|
+
|
|
1412
|
+
### What fails to compile
|
|
1413
|
+
|
|
1414
|
+
- e2e refuses, naming the file and line, two things it cannot compile:
|
|
1415
|
+
auto-accessors (`accessor x = 1`) and decorators without
|
|
1416
|
+
`experimentalDecorators`.
|
|
1417
|
+
- Each file compiles on its own, without type information. So a type
|
|
1418
|
+
imported without `import type` fails to load when the import stays in the
|
|
1419
|
+
output (a decorated member's type under `emitDecoratorMetadata`, a
|
|
1420
|
+
re-exported type). The error names the import to change.
|
|
1421
|
+
- A JSON file imported without `with { type: 'json' }` loads with its parsed
|
|
1422
|
+
contents as the default export.
|
|
1423
|
+
- Stack traces and failure locations point at the TypeScript source, through
|
|
1424
|
+
another loader's source map too (`NODE_OPTIONS=--import tsx`).
|
|
1425
|
+
|
|
1426
|
+
## Types
|
|
1427
|
+
|
|
1428
|
+
### `E2EConfig`
|
|
1429
|
+
|
|
1430
|
+
```ts
|
|
1431
|
+
export interface E2EConfig {
|
|
1432
|
+
/** Stable project id, 1 through 256 characters; defaults to the root `package.json` name. */
|
|
1433
|
+
projectId?: string;
|
|
1434
|
+
/** The surfaces tests run on; required, at least one, each naming its engine. */
|
|
1435
|
+
targets: readonly Target[];
|
|
1436
|
+
/** Test file globs relative to the project root (`*`, `?`, and a whole `**` segment; a leading `!` excludes); default every `*.e2e.ts` under `tests/`. */
|
|
1437
|
+
tests?: string | readonly string[];
|
|
1438
|
+
/** Test attempt deadline in milliseconds; default 120000. */
|
|
1439
|
+
timeout?: number;
|
|
1440
|
+
/** Engine init and attempt start budget in milliseconds; default 60000. */
|
|
1441
|
+
launchTimeout?: number;
|
|
1442
|
+
/** Budget of every engine operation in milliseconds; default 30000. */
|
|
1443
|
+
actionTimeout?: number;
|
|
1444
|
+
/** Default `expect` deadline in milliseconds; default 5000. */
|
|
1445
|
+
assertionTimeout?: number;
|
|
1446
|
+
/** Budget of each `afterEach` hook, fixture teardown, and engine cleanup in milliseconds; default 30000. */
|
|
1447
|
+
cleanupTimeout?: number;
|
|
1448
|
+
/** Retries per test, 0 through 10; default 1 in CI, else 0. */
|
|
1449
|
+
retries?: number;
|
|
1450
|
+
/** Fail the run when a test skips a retry after an earlier failed attempt; default false. */
|
|
1451
|
+
failOnSkippedFailure?: boolean;
|
|
1452
|
+
/** Wall-clock budget for running the tests, in milliseconds; default none. */
|
|
1453
|
+
maxDuration?: number;
|
|
1454
|
+
/** Parallel workers, 1 through 1024; default 1 in CI, else half the cores. An engine may cap it lower. */
|
|
1455
|
+
workers?: number;
|
|
1456
|
+
/** `{ store }` hands every artifact to a host store as it is produced. */
|
|
1457
|
+
artifacts?: ArtifactsConfig;
|
|
1458
|
+
/** Which tests keep a trace page; default `retain-on-failure`. */
|
|
1459
|
+
trace?: RecordingMode;
|
|
1460
|
+
/** Which attempts record a video; default `off`. */
|
|
1461
|
+
video?: RecordingMode;
|
|
1462
|
+
/** The results directory, relative to the project root; default `.e2e`. */
|
|
1463
|
+
output?: string;
|
|
1464
|
+
/** Output renderers and reporter objects; default `['list']`. */
|
|
1465
|
+
reporters?: readonly (BuiltinReporter | Reporter)[];
|
|
1466
|
+
/** Named agents: the built-in agent's options, or `{ executor }` for a custom brain. */
|
|
1467
|
+
agents?: Readonly<Record<string, AgentConfig>>;
|
|
1468
|
+
/** Replay cache mode or options; default `read-write`, `read-only` in CI. */
|
|
1469
|
+
cache?: CacheMode | CacheConfig;
|
|
1470
|
+
/** Named accounts: a username and a password the model never sees. */
|
|
1471
|
+
credentials?: Readonly<Record<string, CredentialConfig>>;
|
|
1472
|
+
/** Named values the model must never see: API keys, tokens, anything sourced from the environment. */
|
|
1473
|
+
secrets?: Readonly<Record<string, SecretConfig>>;
|
|
1474
|
+
}
|
|
1475
|
+
```
|
|
1476
|
+
|
|
1477
|
+
### `Target`
|
|
1478
|
+
|
|
1479
|
+
```ts
|
|
1480
|
+
/**
|
|
1481
|
+
* One named test target: the app under test and the engine that drives it.
|
|
1482
|
+
* Without an engine, the target supports agent tools only.
|
|
1483
|
+
*/
|
|
1484
|
+
export interface Target {
|
|
1485
|
+
/** Label in reports and for `--target`; defaults to the platform. */
|
|
1486
|
+
name?: string;
|
|
1487
|
+
/** Platform label; inherited from the engine when omitted. */
|
|
1488
|
+
platform?: string;
|
|
1489
|
+
/** The engine driving the surface. */
|
|
1490
|
+
engine?: EngineHandle;
|
|
1491
|
+
/** The app under test: what it is, where it is served, and the command that starts it. */
|
|
1492
|
+
app?: TargetApp;
|
|
1493
|
+
/** Which tests on this target keep a trace page, in place of the config's `trace`. */
|
|
1494
|
+
trace?: RecordingMode;
|
|
1495
|
+
/** Which attempts on this target record a video, in place of the config's `video`. */
|
|
1496
|
+
video?: RecordingMode;
|
|
1497
|
+
}
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
### `CommandConfig`
|
|
378
1501
|
|
|
379
1502
|
```ts
|
|
380
1503
|
export interface CommandConfig {
|
|
@@ -386,163 +1509,20 @@ export interface CommandConfig {
|
|
|
386
1509
|
cwd?: string;
|
|
387
1510
|
/** Added to the inherited set; `{port}` expands in values. */
|
|
388
1511
|
env?: Readonly<Record<string, string>>;
|
|
389
|
-
/**
|
|
390
|
-
* Variables copied from the runner's environment when set: exact names, or
|
|
391
|
-
* prefixes ending in `*` (`'STRIPE_*'`). `env` wins over an inherited value.
|
|
392
|
-
* A bare `'*'` is `INVALID_CONFIG`.
|
|
393
|
-
*/
|
|
1512
|
+
/** Variables copied from the runner's environment when set: exact names, or prefixes ending in `*`. */
|
|
394
1513
|
inheritEnv?: readonly string[];
|
|
395
|
-
/** Ready-probe budget in milliseconds; default 60000.
|
|
1514
|
+
/** Ready-probe budget in milliseconds; default 60000. */
|
|
396
1515
|
startupTimeout?: number;
|
|
397
1516
|
/** Grace period before force-kill in milliseconds; default 10000. */
|
|
398
1517
|
shutdownTimeout?: number;
|
|
399
|
-
/** File that receives the process's stdout and stderr
|
|
1518
|
+
/** File that receives the process's stdout and stderr; omitted discards output. */
|
|
400
1519
|
log?: string;
|
|
401
|
-
/**
|
|
402
|
-
* When the readiness URL already answers before the command starts, use that
|
|
403
|
-
* process instead of spawning: nothing is started and nothing is stopped.
|
|
404
|
-
* Off by default; CI ignores it and always starts the command. A URL on a
|
|
405
|
-
* free port (port 0) can never already answer, so the two together are
|
|
406
|
-
* `INVALID_CONFIG`.
|
|
407
|
-
*/
|
|
1520
|
+
/** Use a process already answering the readiness URL instead of spawning; default `false`. */
|
|
408
1521
|
reuseExisting?: boolean;
|
|
409
1522
|
}
|
|
410
1523
|
```
|
|
411
1524
|
|
|
412
|
-
`
|
|
413
|
-
output, so keep it in an ignored directory. A startup failure quotes the last
|
|
414
|
-
20 lines appended since the command started, with `command.env` and
|
|
415
|
-
`inheritEnv` values replaced by `<secret:NAME>` markers. Without
|
|
416
|
-
`reuseExisting`, a `readyUrl` that already answers is `APP_ALREADY_RUNNING`.
|
|
417
|
-
`reuseExisting` on an app whose `app.url` asks for port 0 is
|
|
418
|
-
`INVALID_CONFIG`: a process already running cannot serve the port the run
|
|
419
|
-
assigns.
|
|
420
|
-
|
|
421
|
-
The child inherits only `PATH`, `HOME`, the temp-directory variables, the
|
|
422
|
-
variables `inheritEnv` matches, and `command.env`, so pass what the app needs
|
|
423
|
-
explicitly. `inheritEnv` takes names or trailing-`*` prefixes and copies a
|
|
424
|
-
variable only when it is set; `env` wins. A bare `*` is `INVALID_CONFIG`.
|
|
425
|
-
Every `args` entry and `env` value is a string; anything else, a number or a
|
|
426
|
-
`secrets.get()` handle included, is `INVALID_CONFIG`, except an `undefined`
|
|
427
|
-
`env` value, which is dropped. The runner never terminates a process it did
|
|
428
|
-
not start.
|
|
429
|
-
|
|
430
|
-
### services
|
|
431
|
-
|
|
432
|
-
<Note>
|
|
433
|
-
Dependency processes (a database, an API mock, a dev server several targets
|
|
434
|
-
share) are not started by this version, and `services` is an unknown key
|
|
435
|
-
wherever it appears. Start them before the run, or have `app.command` start
|
|
436
|
-
a script that brings them up and serves the app. A services API returns in a
|
|
437
|
-
later release.
|
|
438
|
-
</Note>
|
|
439
|
-
|
|
440
|
-
## output
|
|
441
|
-
|
|
442
|
-
```ts
|
|
443
|
-
output: 'test-results/e2e',
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
Everything a run writes about itself goes under one directory, `.e2e` by
|
|
447
|
-
default, resolved from the project root. `--output <dir>` on
|
|
448
|
-
[`e2e run`](/reference/cli#e2e-run) and `e2e explore` overrides it for one
|
|
449
|
-
run.
|
|
450
|
-
|
|
451
|
-
| Path under the output | What |
|
|
452
|
-
| --- | --- |
|
|
453
|
-
| `report.json` | The canonical report; `--last-failed` reads the one here |
|
|
454
|
-
| `junit.xml`, `summary.md` | The `junit` and `markdown` reporters' files |
|
|
455
|
-
| `ai-trace.json` | `--ai-trace` |
|
|
456
|
-
| `results/` | One directory per test: its [trace page](/debugging#traces), `trace.md`, when the [`trace`](#trace) mode keeps one, and its screenshots, videos, downloads, and logs, one `attempt-<n>/` per attempt. Cleared once a run's tests start, so it holds the latest run's evidence and nothing its report no longer names. A `--last-failed` rerun keeps the files the report it reruns names and writes its own attempts under each test's `rerun-<n>/` |
|
|
457
|
-
| `sessions/` | The run's encrypted `test.setup` sessions, deleted when it ends |
|
|
458
|
-
| `videos/<session>/` | [`e2e mcp`](/reference/mcp#recording) session recordings |
|
|
459
|
-
|
|
460
|
-
A run that stops before its tests start writes nothing here: a selection
|
|
461
|
-
that matches no test (`NO_TESTS`, a mistyped `--grep`), a collection error,
|
|
462
|
-
a target that cannot record what it asks for (`UNSUPPORTED_ARTIFACT`),
|
|
463
|
-
`NO_LAST_RUN`, an app or service that fails to start, or an interrupt
|
|
464
|
-
leaves the previous run's `report.json`, `ai-trace.json`, and `results/`
|
|
465
|
-
as they were, so `--last-failed` still reads the last run that executed. An `e2e mcp` session writes its attempt's artifacts under
|
|
466
|
-
`results/mcp/` and never clears it.
|
|
467
|
-
|
|
468
|
-
The replay cache is independent: [`cache.dir`](#cache) defaults to
|
|
469
|
-
`.e2e/cache` and resolves from the project root whatever `output` says, so
|
|
470
|
-
moving the results never moves the recordings. Because a run clears and
|
|
471
|
-
owns part of it, the output directory fails the config load with
|
|
472
|
-
`INVALID_CONFIG`, naming the reason, when it:
|
|
473
|
-
|
|
474
|
-
- is the project root, or outside it;
|
|
475
|
-
- is a file, or a path under one (a missing directory is created);
|
|
476
|
-
- is the cache directory (`cache.dir`, `--cache-dir`, or `E2E_CACHE_DIR`) or
|
|
477
|
-
inside it, or would hold it under `results/`, `sessions/`, or `videos/`;
|
|
478
|
-
- holds a directory a `tests` glob scans (`output: 'tests'` with the default
|
|
479
|
-
`tests/**/*.e2e.ts`).
|
|
480
|
-
|
|
481
|
-
Paths are compared with symlinks resolved, so an absolute path spelled
|
|
482
|
-
through a symlink (`/tmp` for `/private/tmp` on macOS) names the same
|
|
483
|
-
directory as its target. A symlink whose target does not exist yet is
|
|
484
|
-
followed too: an output through one that points outside the project root is
|
|
485
|
-
refused.
|
|
486
|
-
|
|
487
|
-
`e2e init` writes `.gitignore` entries for the default `.e2e/` paths only.
|
|
488
|
-
With `output` set elsewhere, add that directory to `.gitignore` yourself.
|
|
489
|
-
|
|
490
|
-
The removed `--artifacts <dir>` flag fails with the `--output` it maps to:
|
|
491
|
-
the report used to go to the directory's parent, so `--artifacts
|
|
492
|
-
out/artifacts` is `--output out`. A parent an output cannot be (the project
|
|
493
|
-
root, or outside it) maps to `--output .e2e`.
|
|
494
|
-
|
|
495
|
-
## artifacts
|
|
496
|
-
|
|
497
|
-
```ts
|
|
498
|
-
artifacts: {
|
|
499
|
-
store: {
|
|
500
|
-
async put(artifact) {
|
|
501
|
-
const key = await bucket.upload(artifact.path, artifact.bytes);
|
|
502
|
-
return { ref: key };
|
|
503
|
-
},
|
|
504
|
-
},
|
|
505
|
-
},
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
`artifacts` says where artifacts go, not which are made: [`trace` and
|
|
509
|
-
`video`](#recording) choose the recordings, and a failure's screenshot and
|
|
510
|
-
screen text are captured whenever the engine can (never after a secret
|
|
511
|
-
fill, see
|
|
512
|
-
[Security rules](/reference/security#screenshots-after-a-secret-fill)). The
|
|
513
|
-
removed forms fail with
|
|
514
|
-
`INVALID_CONFIG` naming what replaced them: a kinds list (`artifacts:
|
|
515
|
-
['screenshot', 'trace']` or `artifacts.kinds`) names the `trace` mode it
|
|
516
|
-
meant, `artifacts.trace` names `trace: 'on-all-retries'` for `record:
|
|
517
|
-
'retries'` (read together with a kinds list that holds `trace`, so the pair
|
|
518
|
-
still means retries only), and `artifacts.video` names the `video` option.
|
|
519
|
-
|
|
520
|
-
`store` receives every artifact once it is complete on disk and returns a
|
|
521
|
-
reference the report records as `ref` beside the local path. A failed `put`
|
|
522
|
-
never fails the run. Each worker constructs its own store from the config
|
|
523
|
-
module. `redaction` is the report's label: a store that exports only what
|
|
524
|
-
the runner vouches for keeps back an `incomplete` artifact, a video or a
|
|
525
|
-
download the runner did not rewrite.
|
|
526
|
-
|
|
527
|
-
A video a hosted service keeps (a [provider's own recording](#what-a-video-writes))
|
|
528
|
-
is a link with no bytes, so `put` never receives it. A store that implements
|
|
529
|
-
the optional `putLink` gets each one instead, once the attempt stopped the
|
|
530
|
-
recording, and its `ref` lands on the report's artifact beside `url`. A
|
|
531
|
-
passed attempt's link under `retain-on-failure` is never handed over, since
|
|
532
|
-
the report leaves it out too, and a failed `putLink` never fails the run.
|
|
533
|
-
|
|
534
|
-
```ts
|
|
535
|
-
artifacts: {
|
|
536
|
-
store: {
|
|
537
|
-
async put(artifact) {
|
|
538
|
-
return { ref: await bucket.upload(artifact.path, artifact.bytes) };
|
|
539
|
-
},
|
|
540
|
-
async putLink(link) {
|
|
541
|
-
return { ref: await index.record(link.url, link.attemptId) };
|
|
542
|
-
},
|
|
543
|
-
},
|
|
544
|
-
},
|
|
545
|
-
```
|
|
1525
|
+
### `ArtifactsConfig`
|
|
546
1526
|
|
|
547
1527
|
```ts
|
|
548
1528
|
export interface ArtifactsConfig {
|
|
@@ -594,113 +1574,7 @@ export interface StoredArtifactLink {
|
|
|
594
1574
|
}
|
|
595
1575
|
```
|
|
596
1576
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
```ts
|
|
600
|
-
trace: 'on',
|
|
601
|
-
video: 'retain-on-failure',
|
|
602
|
-
targets: [
|
|
603
|
-
{ name: 'chromium', engine: web(), app: { url } },
|
|
604
|
-
{ name: 'firefox', engine: web({ browser: 'firefox' }), app: { url }, video: 'on-first-retry' },
|
|
605
|
-
],
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
`trace` and `video` choose what is recorded, with one set of modes:
|
|
609
|
-
|
|
610
|
-
```ts
|
|
611
|
-
/** Which tests keep a trace page, or which attempts record a video. */
|
|
612
|
-
export type RecordingMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'on-all-retries';
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
| Mode | Trace page | Video |
|
|
616
|
-
| --- | --- | --- |
|
|
617
|
-
| `off` | none | none |
|
|
618
|
-
| `on` | every test, passing ones included | every attempt |
|
|
619
|
-
| `retain-on-failure` | each failed, timed-out, interrupted, or flaky test | every attempt, keeping those that did not pass |
|
|
620
|
-
| `on-first-retry` | each test whose first retry ran | the first retry only, the second attempt |
|
|
621
|
-
| `on-all-retries` | each test that was retried | every attempt after the first |
|
|
622
|
-
|
|
623
|
-
The names are Playwright's `use.trace` and `use.video` modes. Under a retry
|
|
624
|
-
mode a test with `retries: 0` records nothing, and neither does a failure the
|
|
625
|
-
runner does not retry, such as `LAUNCH_TIMEOUT`. When such a test runs, the
|
|
626
|
-
run prints one notice per kind at plan time saying no traces (or videos) will
|
|
627
|
-
be recorded for it.
|
|
628
|
-
|
|
629
|
-
The old trace spellings fail with `INVALID_CONFIG` naming the mode they
|
|
630
|
-
meant, wherever a mode goes (the config, a target, a test, `--trace`):
|
|
631
|
-
`'all'` and `{ record: 'all' }` are `'on'`, and `'retries'` and `{ record:
|
|
632
|
-
'retries' }` are `'on-all-retries'`.
|
|
633
|
-
|
|
634
|
-
Four places set a mode, lowest first, and each replaces the one before it.
|
|
635
|
-
The same rule holds for `trace` and `video`:
|
|
636
|
-
|
|
637
|
-
1. `trace` or `video` at the top of the config.
|
|
638
|
-
2. A target's `trace` or `video`.
|
|
639
|
-
3. `--trace [mode]` or `--video [mode]` on [`e2e run`](/reference/cli#e2e-run),
|
|
640
|
-
over the config and every target. A bare flag is `on`.
|
|
641
|
-
4. A test's or describe's own [`trace` or `video` option](/reference/test#options),
|
|
642
|
-
innermost wins: `test('checkout', { video: 'on' }, ...)`.
|
|
643
|
-
|
|
644
|
-
A serial group records as one unit, so its members cannot set `trace` or
|
|
645
|
-
`video` (a `COLLECTION_ERROR`, like `retries`). The value on the serial
|
|
646
|
-
`describe`, or on a group around it, applies to every member.
|
|
647
|
-
|
|
648
|
-
`e2e explore` is one attempt and uses the target's mode, `--trace` and
|
|
649
|
-
`--video` included, so a retry mode records nothing there. An `e2e mcp`
|
|
650
|
-
session writes no trace, and never records video from the configured mode:
|
|
651
|
-
its recordings are the ones `start_recording` and `stop_recording` make.
|
|
652
|
-
|
|
653
|
-
Neither `trace` nor `video`, at the top or on a target, enters the config
|
|
654
|
-
digest, and neither is part of a replay's
|
|
655
|
-
[cache key](/cache#what-must-match): turning one on or off never
|
|
656
|
-
invalidates cached replays.
|
|
657
|
-
|
|
658
|
-
### trace
|
|
659
|
-
|
|
660
|
-
The default is `retain-on-failure`, locally and in CI. A trace page is
|
|
661
|
-
markdown, `trace.md` in the test's directory under `<output>/results/`, such as
|
|
662
|
-
`.e2e/results/checkout-applies-the-coupon-1a2b3c4d5e6f7a8b/trace.md`: every step
|
|
663
|
-
with what it did, the cache's decisions, what the app logged, and the screen
|
|
664
|
-
at failure. It works on every engine. [Debugging](/debugging#traces) shows
|
|
665
|
-
one.
|
|
666
|
-
|
|
667
|
-
### video
|
|
668
|
-
|
|
669
|
-
The default is `off`. A video costs CPU and holds what the screen showed,
|
|
670
|
-
secrets included, so it is always asked for.
|
|
671
|
-
|
|
672
|
-
### Engines that cannot record
|
|
673
|
-
|
|
674
|
-
Before any test starts, the run grades every target. Where a `video` mode
|
|
675
|
-
was set decides what an engine that cannot record video does:
|
|
676
|
-
|
|
677
|
-
- Set at the top of the config or with `--video`: the mode applies
|
|
678
|
-
to the targets whose engine can record it. The run prints one notice at
|
|
679
|
-
plan time naming the targets that cannot, and runs them without.
|
|
680
|
-
- Set on a target, or on a test that will run on that target: the mode is
|
|
681
|
-
required. A target whose engine cannot record it fails the run with
|
|
682
|
-
`UNSUPPORTED_ARTIFACT`, naming the target and, for a test's, the test.
|
|
683
|
-
|
|
684
|
-
Only a mode that records on some attempt counts: a run whose tests all
|
|
685
|
-
resolve to `off`, or to a retry mode with `retries: 0`, asks nothing of the
|
|
686
|
-
engine.
|
|
687
|
-
|
|
688
|
-
### What a video writes
|
|
689
|
-
|
|
690
|
-
| Recorder | Files under the attempt's artifact directory |
|
|
691
|
-
| --- | --- |
|
|
692
|
-
| Browser engine, Playwright screencast | `video/video.webm`, one segment per page the attempt showed, later ones as `video/video-part<n>.webm`. [`web({ screencast })`](/reference/web#options) sets the frame size and quality |
|
|
693
|
-
| Mobile engine, agent-device | `video/video.mp4` |
|
|
694
|
-
| A provider's own recording ([`BrowserProvider.record`](/browser#recording-on-the-provider), [`DeviceProvider.record`](/mobile#hosted-devices)) | the file the provider wrote, as `video/<file>`: Kernel's is `video/replay.mp4`. Or a link to a recording the service keeps, recorded with `url` in place of `path`; the runner never downloads it |
|
|
695
|
-
|
|
696
|
-
Each segment carries `startedAt`, so `step.startedAt - artifact.startedAt` is
|
|
697
|
-
a step's offset into it.
|
|
698
|
-
|
|
699
|
-
A recording masks nothing and is recorded with `redaction: 'incomplete'`.
|
|
700
|
-
Under `retain-on-failure` a link is left out of the report of a passed
|
|
701
|
-
attempt, but the service still holds the recording.
|
|
702
|
-
|
|
703
|
-
## agents
|
|
1577
|
+
### `AgentConfig`
|
|
704
1578
|
|
|
705
1579
|
```ts
|
|
706
1580
|
/** A project tool as `defineTool` from `e2e/agent` returns it. */
|
|
@@ -715,26 +1589,19 @@ export interface AgentOptions {
|
|
|
715
1589
|
model?: ModelInstance;
|
|
716
1590
|
/** Model for `assert`, `waitFor`, and `extract`; defaults to `model`. */
|
|
717
1591
|
judge?: ModelInstance;
|
|
718
|
-
/**
|
|
719
|
-
* What the app calls things, told to every model call this agent makes,
|
|
720
|
-
* act turns and judgments alike. At most 16384 bytes.
|
|
721
|
-
*/
|
|
1592
|
+
/** What the app calls things, told to every model call; at most 16384 bytes. */
|
|
722
1593
|
context?: string;
|
|
723
1594
|
/** Committed actions per agent call, 1 through 100; default 25. */
|
|
724
1595
|
maxSteps?: number;
|
|
725
1596
|
/** Model requests per agent call, 1 through 100; default 25. */
|
|
726
1597
|
maxModelCalls?: number;
|
|
727
|
-
/** Deadline of one `assert`, `waitFor`, or `extract` call in milliseconds; default 30000.
|
|
1598
|
+
/** Deadline of one `assert`, `waitFor`, or `extract` call in milliseconds; default 30000. */
|
|
728
1599
|
judgmentTimeout?: number;
|
|
729
1600
|
/** Observation payload ceiling for act turns and judgments, 1024 through 16777216; default 262144. */
|
|
730
1601
|
maxObservationBytes?: number;
|
|
731
|
-
/** Input tokens per model request, 1 through 1000000; default 64000.
|
|
1602
|
+
/** Input tokens per model request, 1 through 1000000; default 64000. */
|
|
732
1603
|
maxInputTokens?: number;
|
|
733
|
-
/**
|
|
734
|
-
* Provider options every model call carries, e.g. a reasoning effort.
|
|
735
|
-
* OpenAI and Azure OpenAI Responses calls also carry `store: false` and a
|
|
736
|
-
* prompt cache key unless set here.
|
|
737
|
-
*/
|
|
1604
|
+
/** Provider options every model call carries, such as a reasoning effort. */
|
|
738
1605
|
providerOptions?: ProviderOptions;
|
|
739
1606
|
}
|
|
740
1607
|
|
|
@@ -742,7 +1609,7 @@ export interface AgentOptions {
|
|
|
742
1609
|
export type AgentConfig = AgentOptions &
|
|
743
1610
|
(
|
|
744
1611
|
| {
|
|
745
|
-
/** How the acting agent should work, appended to the built-in rules
|
|
1612
|
+
/** How the acting agent should work, appended to the built-in rules. */
|
|
746
1613
|
system?: string;
|
|
747
1614
|
/** Project tools from `defineTool`, offered beside the built-in toolset. */
|
|
748
1615
|
tools?: Readonly<Record<string, AgentTool>>;
|
|
@@ -754,66 +1621,7 @@ export type AgentConfig = AgentOptions &
|
|
|
754
1621
|
);
|
|
755
1622
|
```
|
|
756
1623
|
|
|
757
|
-
|
|
758
|
-
`e2e run --agent <name>`, a test or group option, or an individual `agent.*`
|
|
759
|
-
call. A test with `{ agent: ['buyer', 'admin'] }` runs once for each agent.
|
|
760
|
-
`default` exists even when the config declares no agents, but has no model.
|
|
761
|
-
|
|
762
|
-
Names may contain ASCII letters, numbers, `_`, `-`, or `.`, and are never
|
|
763
|
-
only dots: a name is an artifact path segment. An unknown name
|
|
764
|
-
is `INVALID_CONFIG` on the CLI flag, `COLLECTION_ERROR` on a test or group,
|
|
765
|
-
and `INVALID_ARGUMENT` on a call. See [Agents and personas](/agents).
|
|
766
|
-
|
|
767
|
-
```ts title="e2e.config.ts"
|
|
768
|
-
import type { E2EConfig } from 'e2e';
|
|
769
|
-
import { web } from '@e2e-dev/web';
|
|
770
|
-
import { gateway } from 'ai';
|
|
771
|
-
import { houseBrain } from './house-brain.ts';
|
|
772
|
-
|
|
773
|
-
export default {
|
|
774
|
-
targets: [{ engine: web(), app: { url: 'http://localhost:3000' } }],
|
|
775
|
-
agents: {
|
|
776
|
-
default: { model: gateway('openai/gpt-6-luna-fast') },
|
|
777
|
-
ux: { model: gateway('openai/gpt-6-luna-fast'), system: 'Check labels, contrast, and keyboard access.' },
|
|
778
|
-
thorough: { model: gateway('anthropic/claude-opus-5'), context: 'Verify every claim on screen.' },
|
|
779
|
-
buyer: {
|
|
780
|
-
model: gateway('openai/gpt-6-luna-fast'),
|
|
781
|
-
system: 'You are a first-time buyer.',
|
|
782
|
-
context: 'Plans are called tiers. Billing lives under Settings.',
|
|
783
|
-
maxSteps: 40,
|
|
784
|
-
},
|
|
785
|
-
house: { executor: houseBrain, model: gateway('openai/gpt-6-luna-fast'), maxModelCalls: 10 },
|
|
786
|
-
},
|
|
787
|
-
} satisfies E2EConfig;
|
|
788
|
-
```
|
|
789
|
-
|
|
790
|
-
Every entry starts from the built-in defaults. Agents do not inherit from
|
|
791
|
-
each other: `ux` above gets no `context` from `default`, and a key left out of
|
|
792
|
-
an entry is the default value, never another entry's.
|
|
793
|
-
|
|
794
|
-
With `executor`, `model`, `judge`, `context`, and the budgets still apply
|
|
795
|
-
(judgments need a model), while `system` and `tools` are `INVALID_CONFIG`: the
|
|
796
|
-
executor brings its own prompt and tools. `maxModelCalls` bounds the model
|
|
797
|
-
turns of one step. A string in `model` is `INVALID_CONFIG` naming the
|
|
798
|
-
constructor to write, and so is a `model` or `judge` that differs from the one
|
|
799
|
-
the executor brought. See [Custom executors](/executors) and
|
|
800
|
-
[A judge of its own](/models#a-judge-of-its-own).
|
|
801
|
-
|
|
802
|
-
Loading the config fails with `INVALID_CONFIG` for an unknown key (naming the
|
|
803
|
-
nearest known one), a removed key, and a value in the old shapes:
|
|
804
|
-
|
|
805
|
-
| Written | Now |
|
|
806
|
-
| --- | --- |
|
|
807
|
-
| a bare `StepExecutor` as the entry | `{ executor }` |
|
|
808
|
-
| `timeout` | `judgmentTimeout` |
|
|
809
|
-
| `maxTurns` (on the entry or `createToolLoopExecutor`) | `maxModelCalls` |
|
|
810
|
-
| top-level `limits.maxModelTokensPerCall` | `maxInputTokens` on each agent |
|
|
811
|
-
| top-level `limits.maxAgentContextBytes`, `maxLedgerBytes`, `maxEventsPerStep` | removed; the runner fixes them at 16384 bytes, 8192 bytes, and 1000 events |
|
|
812
|
-
|
|
813
|
-
Judgments (`assert`, `waitFor`, `extract`) allow 8192 output tokens per call
|
|
814
|
-
and see the instruction and the current screen only.
|
|
815
|
-
|
|
816
|
-
### model
|
|
1624
|
+
### `ModelInstance`
|
|
817
1625
|
|
|
818
1626
|
```ts
|
|
819
1627
|
/**
|
|
@@ -832,23 +1640,7 @@ export interface ModelInstance {
|
|
|
832
1640
|
}
|
|
833
1641
|
```
|
|
834
1642
|
|
|
835
|
-
|
|
836
|
-
providers that support tool calls, see [AI SDK providers](/models#ai-sdk-providers).
|
|
837
|
-
For subscription sign-in, use the [`e2e/oauth/*` models](/subscriptions).
|
|
838
|
-
|
|
839
|
-
| Gateway | Constructor | Package | Reads |
|
|
840
|
-
| --- | --- | --- | --- |
|
|
841
|
-
| [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) | `gateway('openai/gpt-6-luna-fast')` | `ai` | `AI_GATEWAY_API_KEY`, or a Vercel OIDC token without it |
|
|
842
|
-
| [OpenRouter](https://openrouter.ai/models) | `openrouter('openai/gpt-6-luna-fast')` | `@openrouter/ai-sdk-provider` | `OPENROUTER_API_KEY` |
|
|
843
|
-
| Any OpenAI-compatible endpoint | `createOpenAICompatible({ name, baseURL, apiKey? }).chatModel('llama3.2')` | `@ai-sdk/openai-compatible` | `apiKey`, when the endpoint needs one |
|
|
844
|
-
| A provider directly | `openai('gpt-6-luna')`, `anthropic(...)`, ... | `@ai-sdk/openai`, `@ai-sdk/anthropic`, ... | the provider's own variable |
|
|
845
|
-
|
|
846
|
-
Reports record `provider` and `modelId` per step. Each worker constructs its
|
|
847
|
-
own instance from the config module; a digest that differs between the
|
|
848
|
-
runner and a worker is `CONFIG_NOT_DETERMINISTIC`. See
|
|
849
|
-
[Models](/models).
|
|
850
|
-
|
|
851
|
-
## credentials
|
|
1643
|
+
### `CredentialConfig`
|
|
852
1644
|
|
|
853
1645
|
```ts
|
|
854
1646
|
/** One `config.credentials` entry: an account whose password is a secret named `<entry>.password`. */
|
|
@@ -858,30 +1650,7 @@ export interface CredentialConfig {
|
|
|
858
1650
|
}
|
|
859
1651
|
|
|
860
1652
|
type SecretProvider = () => string | Promise<string>;
|
|
861
|
-
```
|
|
862
1653
|
|
|
863
|
-
A `SecretProvider` is called on every authorized fill (a vault lookup, a
|
|
864
|
-
freshly computed TOTP) and recorded in the config digest by name only. An
|
|
865
|
-
`E2E_USER_*` override wins over it. A static password is at least 6
|
|
866
|
-
characters (code points), like a secret, or `INVALID_CONFIG`; a provider
|
|
867
|
-
returning fewer fails the fill with `AUTH_CREDENTIAL_UNAVAILABLE`.
|
|
868
|
-
|
|
869
|
-
`credentials.user(name)` resolves a configured entry into a `Credential`.
|
|
870
|
-
The username is a plain string; the password is an opaque `Secret` that test
|
|
871
|
-
code cannot read, accepted only by `locator.fill` and `agent.act` params. An
|
|
872
|
-
unconfigured name throws `AUTH_CREDENTIAL_UNAVAILABLE`.
|
|
873
|
-
|
|
874
|
-
Credentials and secrets are separate namespaces: `secrets.get(name)` returns a
|
|
875
|
-
`secrets` entry only, and a credential's password is reachable only as
|
|
876
|
-
`credentials.user(name).password`. The agent, the replay cache, and the
|
|
877
|
-
reports know that handle as `<name>.password` (`<secret:admin.password>`), so
|
|
878
|
-
`credentials.admin` and `secrets.admin` can both exist; a `secrets` entry
|
|
879
|
-
named exactly `admin.password` beside an `admin` credential is
|
|
880
|
-
`INVALID_CONFIG`. So are two credentials whose names map to one
|
|
881
|
-
`E2E_USER_<NAME>_*` override, like `svc-a` and `svc_a` (see
|
|
882
|
-
[Environment variables](/reference/environment)).
|
|
883
|
-
|
|
884
|
-
```ts
|
|
885
1654
|
/** Named test identity. The password remains an opaque Secret. */
|
|
886
1655
|
export interface Credential {
|
|
887
1656
|
/** The entry's name in `credentials`. */
|
|
@@ -894,47 +1663,12 @@ export interface Credential {
|
|
|
894
1663
|
}
|
|
895
1664
|
```
|
|
896
1665
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
## secrets
|
|
1666
|
+
### `SecretConfig`
|
|
900
1667
|
|
|
901
1668
|
```ts
|
|
902
1669
|
/** One `config.secrets` entry: the value itself, or a provider computing it at fill time. */
|
|
903
1670
|
export type SecretConfig = string | SecretProvider;
|
|
904
|
-
```
|
|
905
1671
|
|
|
906
|
-
An entry is a static value, or a provider called on every authorized fill (a
|
|
907
|
-
vault lookup, a freshly computed TOTP). It is recorded in the config digest by
|
|
908
|
-
name only, and an `E2E_SECRET_<NAME>` override wins over both. Two entries whose
|
|
909
|
-
names map to one variable, like `api-key` and `api_key`, are `INVALID_CONFIG`. Every entry
|
|
910
|
-
must resolve to a string of at least 6 characters (code points) or a
|
|
911
|
-
function; anything else is `INVALID_CONFIG`. The minimum exists for redaction: every occurrence
|
|
912
|
-
of a value becomes `<secret:name>`, and a shorter value would take ordinary
|
|
913
|
-
text in reports and output with it. A provider's value is held to the same
|
|
914
|
-
minimum at fill time, with `SECRET_UNAVAILABLE`.
|
|
915
|
-
|
|
916
|
-
`secrets.get(name)` returns the opaque `Secret` for a configured entry. It has
|
|
917
|
-
no plaintext accessor and is accepted only by `locator.fill` and `agent.act`
|
|
918
|
-
params; the runner fills the field, masks the value in every observation, and
|
|
919
|
-
redacts it from logs, traces, and the report. An unconfigured name throws
|
|
920
|
-
`SECRET_UNAVAILABLE`.
|
|
921
|
-
|
|
922
|
-
Called while `e2e.config.ts` evaluates, before any run exists, `secrets.get(name)`
|
|
923
|
-
returns a reference by name that an engine option can hold, such as
|
|
924
|
-
`web({ basicAuth: { username, password: secrets.get('previewPassword') } })`.
|
|
925
|
-
The engine declares it, the config load fails with `INVALID_CONFIG` when no
|
|
926
|
-
`secrets` entry has the name (a credential's password is not one: declare a
|
|
927
|
-
value an engine option needs under `secrets`), and the engine resolves the value
|
|
928
|
-
when each attempt starts (a provider runs again every time). The value joins
|
|
929
|
-
the attempt's redaction then, without the screenshot restrictions a fill
|
|
930
|
-
brings, since it goes to the engine rather than onto the page. The digest
|
|
931
|
-
records the name only. The reference is not the value: turned into a string
|
|
932
|
-
(a template literal, `String()`, `+`, `JSON.stringify`) it throws
|
|
933
|
-
`INVALID_CONFIG`, and so does one placed in an `args` entry or `env` value of
|
|
934
|
-
`app.command`, which takes strings and placeholders only. Read a value a string needs, such as an app's API
|
|
935
|
-
key, from `process.env` yourself.
|
|
936
|
-
|
|
937
|
-
```ts
|
|
938
1672
|
/**
|
|
939
1673
|
* What a secret is for, which decides where it may be filled: a `password`
|
|
940
1674
|
* goes only into a password field; a `generic-secret` (an API key, a token,
|
|
@@ -952,26 +1686,16 @@ export interface Secret {
|
|
|
952
1686
|
}
|
|
953
1687
|
```
|
|
954
1688
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
## cache
|
|
958
|
-
|
|
959
|
-
In `read-write` mode the replay cache records an `agent.act()` only after a
|
|
960
|
-
later verification passes, such as a locator assertion or `agent.assert()`.
|
|
961
|
-
`read-only` replays existing entries and writes none; `off` disables both.
|
|
962
|
-
See [Caching](/cache).
|
|
1689
|
+
### `CacheConfig`
|
|
963
1690
|
|
|
964
1691
|
```ts
|
|
965
|
-
/**
|
|
966
|
-
* Replay cache options. Defaults to files under `.e2e/cache/`.
|
|
967
|
-
* A custom store replaces file storage; each worker constructs its own.
|
|
968
|
-
*/
|
|
1692
|
+
/** Replay cache options. */
|
|
969
1693
|
export interface CacheConfig {
|
|
970
1694
|
/** Default `read-write`; `read-only` in CI when unset. */
|
|
971
1695
|
mode?: CacheMode;
|
|
972
1696
|
/** Custom entry store; omit it to use the file store at `dir`. */
|
|
973
1697
|
store?: CacheStore;
|
|
974
|
-
/** File store directory, resolved against the project root; default `.e2e/cache
|
|
1698
|
+
/** File store directory, resolved against the project root; default `.e2e/cache`. */
|
|
975
1699
|
dir?: string;
|
|
976
1700
|
/** Fail a step whose recording no longer replays with `REPLAY_STALE`. Default `false`. */
|
|
977
1701
|
strict?: boolean;
|
|
@@ -997,73 +1721,3 @@ export type CacheReadResult =
|
|
|
997
1721
|
| { readonly status: 'miss' }
|
|
998
1722
|
| { readonly status: 'invalid'; readonly reason: string; readonly bytes?: number };
|
|
999
1723
|
```
|
|
1000
|
-
|
|
1001
|
-
`CacheStore`, `CacheReadResult`, `TraceEntry`, and `ActionTrace` are
|
|
1002
|
-
exported from `e2e`.
|
|
1003
|
-
|
|
1004
|
-
A bare string is shorthand for `{ mode }`. Set `'read-write'` explicitly when
|
|
1005
|
-
CI restores the cache from its own cache service; `--no-cache` wins over
|
|
1006
|
-
both. A custom `store` is exempt from the CI demotion and states its own
|
|
1007
|
-
trust through `writable`. `strict` (or `--strict-cache` for one run) turns a
|
|
1008
|
-
recording that exists but no longer replays into a `REPLAY_STALE` failure
|
|
1009
|
-
instead of a hand-off to the agent, and the run then never writes the
|
|
1010
|
-
cache; see
|
|
1011
|
-
[Fail on a stale recording](/cache#fail-on-a-stale-recording).
|
|
1012
|
-
|
|
1013
|
-
### Commit the replay cache
|
|
1014
|
-
|
|
1015
|
-
`e2e init` adds `.e2e/cache/` to `.gitignore`, so committing entries is
|
|
1016
|
-
opt-in. Remove that line to share replays with CI and teammates; CI stays
|
|
1017
|
-
`read-only` unless `cache: 'read-write'` is set. See
|
|
1018
|
-
[Caching](/cache#commit-the-replay-cache).
|
|
1019
|
-
|
|
1020
|
-
## Run limits
|
|
1021
|
-
|
|
1022
|
-
The report's `run.limits` block records the ceilings a run enforced. There is
|
|
1023
|
-
no `limits` key: `maxObservationBytes` and `maxModelTokensPerCall` are the
|
|
1024
|
-
largest `maxObservationBytes` and `maxInputTokens` any configured agent
|
|
1025
|
-
sets, and the rest are fixed by the runner.
|
|
1026
|
-
|
|
1027
|
-
| Report field | Value |
|
|
1028
|
-
| --- | --- |
|
|
1029
|
-
| `maxAgentContextBytes` | 16384: an agent's `context` and a test's `agentContext` |
|
|
1030
|
-
| `maxLedgerBytes` | 8192: the prior-step ledger an act step reads |
|
|
1031
|
-
| `maxEventsPerStep` | 1000: events recorded per step |
|
|
1032
|
-
| `maxObservationBytes` | the largest `agents.<name>.maxObservationBytes` |
|
|
1033
|
-
| `maxModelTokensPerCall` | the largest `agents.<name>.maxInputTokens` |
|
|
1034
|
-
|
|
1035
|
-
Per-call options may only lower an agent's budgets.
|
|
1036
|
-
|
|
1037
|
-
## Environment variables
|
|
1038
|
-
|
|
1039
|
-
The runner reads a closed set, listed on the
|
|
1040
|
-
[Environment variables](/reference/environment) page. `E2E_USER_<NAME>_USERNAME`
|
|
1041
|
-
and `E2E_USER_<NAME>_PASSWORD` override a credential, `E2E_SECRET_<NAME>` a
|
|
1042
|
-
secret, and `CI` switches the defaults below.
|
|
1043
|
-
|
|
1044
|
-
### Resolution order
|
|
1045
|
-
|
|
1046
|
-
| Setting | Order |
|
|
1047
|
-
| --- | --- |
|
|
1048
|
-
| Base URL | the target's `app.url`, else `APP_URL_REQUIRED` at the first navigation. The runner reads no `APP_URL`; the scaffolded config reads it itself |
|
|
1049
|
-
| Built-in agent model | `agents.<name>.model`, then `MODEL_UNAVAILABLE` at `agent` acquisition; an agent never falls back to `default`'s model |
|
|
1050
|
-
| Custom executor model | `agents.<name>.model`, else the model the executor brought; two different values is `INVALID_CONFIG` |
|
|
1051
|
-
| Model credential | the provider package's own variable or the key passed at construction, read by the provider on the first model call |
|
|
1052
|
-
| Credential username and password | the `E2E_USER_*` variable, then the config value |
|
|
1053
|
-
|
|
1054
|
-
The built-in agent checks for a missing model once per run, when the `agent`
|
|
1055
|
-
fixture is first acquired, and stops the run with exit 2. A custom executor
|
|
1056
|
-
can implement `act` and `assert` without a model. `waitFor` and `extract`
|
|
1057
|
-
still require one.
|
|
1058
|
-
|
|
1059
|
-
### CI
|
|
1060
|
-
|
|
1061
|
-
CI mode is on unless `CI` is unset, blank, `0`, or `false`.
|
|
1062
|
-
|
|
1063
|
-
| Setting | Local | CI |
|
|
1064
|
-
| --- | --- | --- |
|
|
1065
|
-
| `retries` | `0` | `1` |
|
|
1066
|
-
| `workers` | `max(1, floor(availableParallelism() / 2))` | `1` |
|
|
1067
|
-
| `cache` | `read-write` | `read-only` when unset |
|
|
1068
|
-
| `test.only` | allowed | `ONLY_IN_CI`, exit code 2 |
|
|
1069
|
-
| `command.reuseExisting` | honored | ignored, with a notice |
|