e2e 0.15.1 → 0.15.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 +51 -24
- package/dist/.build.json +1 -1
- package/dist/agent/act.js +3 -3
- package/dist/agent/act.js.map +1 -1
- package/dist/agent/error.d.ts +16 -3
- package/dist/agent/error.d.ts.map +1 -1
- package/dist/agent/error.js +25 -6
- package/dist/agent/error.js.map +1 -1
- package/dist/agent/model/tool-choice.d.ts +24 -1
- package/dist/agent/model/tool-choice.d.ts.map +1 -1
- package/dist/agent/model/tool-choice.js +27 -1
- package/dist/agent/model/tool-choice.js.map +1 -1
- package/dist/agent/step-cache.d.ts +14 -3
- package/dist/agent/step-cache.d.ts.map +1 -1
- package/dist/agent/step-cache.js +13 -3
- package/dist/agent/step-cache.js.map +1 -1
- package/dist/agent/tool-loop.d.ts.map +1 -1
- package/dist/agent/tool-loop.js +62 -9
- package/dist/agent/tool-loop.js.map +1 -1
- package/dist/cache/context.d.ts +19 -6
- package/dist/cache/context.d.ts.map +1 -1
- package/dist/cache/context.js +15 -21
- package/dist/cache/context.js.map +1 -1
- package/dist/cli/init/scaffold.d.ts +1 -0
- package/dist/cli/init/scaffold.d.ts.map +1 -1
- package/dist/cli/init/scaffold.js +2 -1
- package/dist/cli/init/scaffold.js.map +1 -1
- package/dist/cli/init/sibling-versions.json +1 -1
- package/dist/expect/values.js +1 -1
- package/dist/expect/values.js.map +1 -1
- package/dist/run/execute.d.ts.map +1 -1
- package/dist/run/execute.js +21 -8
- package/dist/run/execute.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/agent-steps.mdx +126 -0
- package/docs/agents.mdx +217 -0
- package/docs/api-testing.mdx +65 -0
- package/docs/authentication.mdx +345 -0
- package/docs/browser.mdx +440 -0
- package/docs/bug-bash.mdx +90 -0
- package/docs/cache.mdx +306 -0
- package/docs/ci.mdx +210 -0
- package/docs/coding-agents.mdx +183 -0
- package/docs/debugging.mdx +291 -0
- package/docs/executors.mdx +243 -0
- package/docs/explore.mdx +174 -0
- package/docs/github.mdx +184 -0
- package/docs/index.mdx +82 -0
- package/docs/integrations/eas.mdx +216 -0
- package/docs/integrations/index.mdx +24 -0
- package/docs/integrations/kernel.mdx +163 -0
- package/docs/migrate/cypress.mdx +124 -0
- package/docs/migrate/detox.mdx +140 -0
- package/docs/migrate/maestro.mdx +229 -0
- package/docs/migrate/playwright.mdx +427 -0
- package/docs/migrate/selenium.mdx +110 -0
- package/docs/mobile-ci.mdx +567 -0
- package/docs/mobile.mdx +555 -0
- package/docs/models.mdx +241 -0
- package/docs/quickstart.mdx +260 -0
- package/docs/reference/agent.mdx +524 -0
- package/docs/reference/app.mdx +72 -0
- package/docs/reference/cli.mdx +817 -0
- package/docs/reference/config.mdx +986 -0
- package/docs/reference/engine.mdx +1362 -0
- package/docs/reference/environment.mdx +113 -0
- package/docs/reference/errors.mdx +143 -0
- package/docs/reference/expect.mdx +513 -0
- package/docs/reference/mcp.mdx +202 -0
- package/docs/reference/mobile.mdx +522 -0
- package/docs/reference/reporters.mdx +216 -0
- package/docs/reference/screen.mdx +688 -0
- package/docs/reference/test.mdx +449 -0
- package/docs/reference/web.mdx +611 -0
- package/docs/security.mdx +292 -0
- package/docs/starting-your-app.mdx +231 -0
- package/docs/subscriptions.mdx +157 -0
- package/docs/telemetry.mdx +185 -0
- package/docs/tools.mdx +150 -0
- package/docs/viewports.mdx +146 -0
- package/docs/writing-an-engine.mdx +316 -0
- package/docs/writing-tests.mdx +256 -0
- package/package.json +2 -1
- package/skills/e2e/SKILL.md +4 -1
- package/skills/e2e/references/agent.md +3 -1
- package/skills/e2e/references/setup.md +13 -4
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: agent
|
|
3
|
+
description: Run agent actions, check conditions, and extract structured data.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use `act` to interact with the app, `assert` to check the current screen,
|
|
7
|
+
`waitFor` to wait for a condition, and `extract` to read structured data.
|
|
8
|
+
Each call has its own timeout and model-call budget. The built-in agent
|
|
9
|
+
requires a configured model: without one, the first acquisition of the
|
|
10
|
+
`agent` fixture records one run-level `MODEL_UNAVAILABLE` and stops the run
|
|
11
|
+
(exit 2).
|
|
12
|
+
[Custom executors](/executors) can implement `act` and `assert` without one;
|
|
13
|
+
`waitFor` and `extract` still need a model.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
export interface Agent {
|
|
17
|
+
/** Plans and executes a bounded multi-action flow; resolves with what the step did. */
|
|
18
|
+
act(instruction: string, options?: ActOptions): Promise<ActResult>;
|
|
19
|
+
/** Polls a natural-language condition until true or timed out. */
|
|
20
|
+
waitFor(condition: string, options?: WaitForOptions): Promise<void>;
|
|
21
|
+
/** Extracts and validates structured screen data. */
|
|
22
|
+
extract<Schema extends StandardSchemaV1>(
|
|
23
|
+
instruction: string,
|
|
24
|
+
options: ExtractOptions<Schema>,
|
|
25
|
+
): Promise<StandardSchemaV1.InferOutput<Schema>>;
|
|
26
|
+
/** Judges a natural-language assertion against a fresh observation. */
|
|
27
|
+
assert(assertion: string, options?: AssertOptions): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Budgets
|
|
32
|
+
|
|
33
|
+
Model calls count every model request including repair rounds. Action steps
|
|
34
|
+
count committed engine actions. Every timeout is additionally capped by the
|
|
35
|
+
remaining test timeout.
|
|
36
|
+
|
|
37
|
+
| Method | Model calls | Action steps | Default timeout |
|
|
38
|
+
| --- | ---: | ---: | --- |
|
|
39
|
+
| `act` | `agents.<name>.maxModelCalls`, 25 | `agents.<name>.maxSteps`, 25 | `config.timeout`, 120000 ms |
|
|
40
|
+
| `waitFor` | `agents.<name>.maxModelCalls`, 25 | 0 | `agents.<name>.judgmentTimeout`, 30000 ms |
|
|
41
|
+
| `extract` | 2 | 0 | `agents.<name>.judgmentTimeout`, 30000 ms |
|
|
42
|
+
| `assert` | 2 | 0 | `agents.<name>.judgmentTimeout`, 30000 ms |
|
|
43
|
+
|
|
44
|
+
`actionTimeout` limits engine operations, not model calls. Per-call budgets
|
|
45
|
+
must be positive integers no larger than the configured limits; a larger one
|
|
46
|
+
is `INVALID_ARGUMENT`. All durations are in milliseconds.
|
|
47
|
+
|
|
48
|
+
The `assert` row holds for the built-in agent. When the selected agent has a
|
|
49
|
+
custom `executor`, `assert` is dispatched to it as a step: its budget is the
|
|
50
|
+
agent's `maxSteps` and `maxModelCalls`, its default timeout is
|
|
51
|
+
`config.timeout`, and `vision` on the call is `UNSUPPORTED_CAPABILITY`;
|
|
52
|
+
`screenshot` still applies.
|
|
53
|
+
|
|
54
|
+
## agent.act
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
act(instruction: string, options?: ActOptions): Promise<ActResult>;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Plans and executes one bounded multi-action flow. A passing verdict resolves
|
|
61
|
+
with an `ActResult`; a failing or blocked one throws an `AgentError` whose
|
|
62
|
+
`code` and `blocked` flag say why.
|
|
63
|
+
|
|
64
|
+
`instruction` is 1 through 8192 UTF-8 bytes after NFC normalization. An
|
|
65
|
+
unknown `agent` name is `INVALID_ARGUMENT` before the step opens.
|
|
66
|
+
|
|
67
|
+
Values come from your code through `options.params`, never from the model. The
|
|
68
|
+
model sees a `Secret` as its name and purpose only. The runner checks the
|
|
69
|
+
field the model picked before it fills the value: it must be an enabled
|
|
70
|
+
editable input, and a password goes only into a password field.
|
|
71
|
+
`locator.fill` runs no such check, since your code chose the field.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
await agent.act('invite {email} as an editor', { params: { email: 'ada@example.test' } });
|
|
75
|
+
|
|
76
|
+
const member = credentials.user('member');
|
|
77
|
+
const { summary, cache } = await agent.act('Sign in', {
|
|
78
|
+
params: { username: member.username, password: member.password },
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The result carries the step's `summary`, the `modelCalls` and `actions` it
|
|
83
|
+
spent, and `cache`: how the [replay cache](/cache) took part
|
|
84
|
+
(`self-finalized`, `agent-concluded`, or `missed` with a `reason`; absent when
|
|
85
|
+
caching is off).
|
|
86
|
+
|
|
87
|
+
### Action tools
|
|
88
|
+
|
|
89
|
+
The built-in agent offers one tool per verb the target's engine declares.
|
|
90
|
+
Every tool takes node ids from the newest observation, returns what changed
|
|
91
|
+
on screen, and is recorded for the [replay cache](/cache) with the node it
|
|
92
|
+
acted on. A verb the engine does not declare is not offered.
|
|
93
|
+
|
|
94
|
+
| Tool | Engine action | What it does |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `tap({ target })` | `tap` | Taps or clicks one node: the gesture for a button, link, menu item, tab, checkbox, row, or field. |
|
|
97
|
+
| `double_tap({ target })` | `doubleTap` | Double-clicks one node, for an item that opens or enters editing on the second click. |
|
|
98
|
+
| `long_press({ target })` | `longPress` | Presses one node and holds, for a long-press menu or action. |
|
|
99
|
+
| `right_click({ target })` | `secondaryTap` | Right-clicks one node to open its context menu. |
|
|
100
|
+
| `hover({ target })` | `hover` | Moves the pointer over one node without pressing: hover menus, flyouts, tooltips, controls revealed on mouse-over. |
|
|
101
|
+
| `type({ target?, value, replace? })` | `fill`; without `target`, `keyboard` | Replaces the node's value; without a target, types into whatever has focus. |
|
|
102
|
+
| `type_secret({ target, name })` | `fill` | Fills a secret declared in the step's params. Offered only when the step declares one. |
|
|
103
|
+
| `press({ target?, key, times? })` | `press`; without `target`, `keyboard` | Sends one key to a node or to the focused control, up to 20 times in one call. Each press is one action; with a target, the first press addresses the node and the rest go to whatever has focus, so a repeated `Tab` walks on. |
|
|
104
|
+
| `select({ target, value })` | `selectOption` | Picks an option by its visible label. |
|
|
105
|
+
| `check({ target, checked? })` | `check` and `uncheck` | Sets a checkbox, switch, or radio to a state (`true` by default). A control already in that state is left alone, unlike a tap, which flips it. |
|
|
106
|
+
| `scroll({ direction, target?, times? })` | `swipe` | Scrolls the viewport or one scrollable node, up to 20 screens per call. |
|
|
107
|
+
| `scroll_to({ target })` or `scroll_to({ text, direction?, target? })` | `scrollIntoView`; with `text`, `swipe` | With `target` alone, brings one listed node into the viewport, for a node the tree lists below or above the fold. With `text`, pages the list (`target`, else the viewport) screen by screen in `direction` (down by default) until a node reading that text shows (as its whole label, or bounded by nothing of a word, so `Row 12` never stops at `Row 120`; inside the list when `target` names one, so a heading elsewhere reading the same does not end the paging), then brings it in when the engine can: for a row deep in a long or windowed list the tree has not rendered yet. One action however many screens, up to 800; a screen that stops moving or the cap is `LOCATOR_NOT_FOUND`. Each form is offered only with its action kind. |
|
|
108
|
+
| `drag({ target, to })` | `dragTo` | Drags one node and drops it on another: reordering, kanban columns, drop zones. |
|
|
109
|
+
| `upload({ target, files })` | `setInputFiles` | Attaches files to a file input. Paths are project-relative; see the policy below. |
|
|
110
|
+
| `navigate({ url })` | `session.open` | Opens a URL or an app-relative path. |
|
|
111
|
+
| `back()` | `session.back` | Goes back once: browser history on a page, the in-app back on a device. |
|
|
112
|
+
| `observe()` | | Looks again and reports what changed since the last screen the model received. |
|
|
113
|
+
| `dismiss_keyboard()` | `keyboard.dismiss` | Hides an on-screen keyboard. Device engines only. |
|
|
114
|
+
|
|
115
|
+
`scroll_to({ text })` waits for each scrolled screen to settle. On a device,
|
|
116
|
+
the next observation checks stability without waiting for a second scroll
|
|
117
|
+
effect. Engines with `scrollIntoView` still wait for the final adjustment
|
|
118
|
+
that brings the matched node into view. Replay follows the same rule.
|
|
119
|
+
|
|
120
|
+
`upload` takes the model's path but the runner decides. Each path is
|
|
121
|
+
resolved against the project root the way `locator.setInputFiles` resolves
|
|
122
|
+
it, and is refused with `POLICY_DENIED` when it lies outside the project
|
|
123
|
+
(through a symlink too) or names a hidden file or a file under a hidden
|
|
124
|
+
directory such as `.env`, `.git`, or `.e2e`. A path that names no regular
|
|
125
|
+
file is `INVALID_ARGUMENT`, an ordinary action failure the model reads and
|
|
126
|
+
corrects. Every refusal and the final allow is a `policy` event named
|
|
127
|
+
`upload.path` on the step. Nothing reaches the engine until every path passed.
|
|
128
|
+
|
|
129
|
+
The screen the model reads lists the text selected inside the focused field
|
|
130
|
+
as `selection="..."` after its value, when the engine reports one: the web
|
|
131
|
+
engine does for an input, a textarea, and a `contenteditable` editor, and a
|
|
132
|
+
secure field reports none. The runner drops it from a field whose value or
|
|
133
|
+
text holds a registered secret, and from a field cut at its length limit when
|
|
134
|
+
the selection is not in the part shown, so selected characters of a secret
|
|
135
|
+
never reach the model. A `press` of `Shift+ArrowLeft` shows what it selected, and
|
|
136
|
+
`times` presses the key several times in one call, so the agent extends a
|
|
137
|
+
selection to exactly the word it wants in one more turn before it formats or
|
|
138
|
+
replaces it.
|
|
139
|
+
|
|
140
|
+
The tap variants are separate tools rather than an option on `tap` on
|
|
141
|
+
purpose: offered as an optional `kind`, a model filled it on ordinary
|
|
142
|
+
buttons (a double-click on Save), and a double-click on a submit button
|
|
143
|
+
submits twice. A tool has to be chosen, and each one says what takes a
|
|
144
|
+
plain tap instead.
|
|
145
|
+
|
|
146
|
+
Mobile engines declare `hover`, `doubleTap`, `longPress`, `check`,
|
|
147
|
+
`uncheck`, `dragTo`, and `session.back`, so a device offers `hover`,
|
|
148
|
+
`double_tap`, `long_press`, `check`, `drag`, and `back`; they declare no
|
|
149
|
+
`secondaryTap`, `scrollIntoView`, or `setInputFiles`. Tab switching has no
|
|
150
|
+
verb yet: a new tab is invisible to the model.
|
|
151
|
+
|
|
152
|
+
### Pixel tools: screenshot and the point verbs
|
|
153
|
+
|
|
154
|
+
`act` reads the semantic tree and requests screenshots when it needs them.
|
|
155
|
+
A screen with no listed interactive nodes, such as a canvas, opens with a
|
|
156
|
+
screenshot. The same model reads the tree and screenshots; there is no
|
|
157
|
+
`vision` option for `act`.
|
|
158
|
+
|
|
159
|
+
The built-in agent offers these tools while no secret has been filled:
|
|
160
|
+
|
|
161
|
+
| Tool | What it does |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `screenshot()` | Returns a masked viewport screenshot and includes a fresh screenshot after every later action in the step. |
|
|
164
|
+
| `tap_at({ x, y })` | Taps the control at the point by node id, or taps the coordinates directly when no control is listed. |
|
|
165
|
+
| `hover_at({ x, y })` | Hovers the control at the point by node id, or moves the pointer to the coordinates when no control is listed. Needs the `hover` action or pointer kind. |
|
|
166
|
+
| `type_at({ x, y, value, replace? })` | Replaces a listed input's value. For an unlisted input, taps to focus and types at the caret unless `replace` is set. Unlisted inputs require keyboard support. |
|
|
167
|
+
| `press_at({ x, y, key })` | Sends a key to the control at the point. If no control is listed, taps to focus and uses the engine's keyboard. |
|
|
168
|
+
| `select_at({ x, y, value })` | Selects an option by visible label in the control at the point. |
|
|
169
|
+
|
|
170
|
+
Coordinates refer to the latest screenshot. The model must take a screenshot
|
|
171
|
+
before using a point tool and should prefer node ids for listed controls.
|
|
172
|
+
The runner checks each point against the tree. A point on a listed control
|
|
173
|
+
uses the same policy checks and [replay cache](/cache) recording as an action
|
|
174
|
+
by id.
|
|
175
|
+
|
|
176
|
+
Once screenshots are in use, the post-action wait also detects pixel changes,
|
|
177
|
+
including canvas redraws. Screenshots have a maximum long side of 768 px;
|
|
178
|
+
older images are removed in batches, keeping the newest two.
|
|
179
|
+
|
|
180
|
+
On engines with a keyboard, including the browser and device engines, `type`
|
|
181
|
+
and `press` can omit `target` to act on the focused field. `type` inserts at
|
|
182
|
+
the caret; `replace: true` clears first. The browser engine rejects typing
|
|
183
|
+
when no editable field has focus. Device engines also offer `dismiss_keyboard`.
|
|
184
|
+
When an action closes an on-screen keyboard, the result says so: on a touch
|
|
185
|
+
screen that tap was often spent on closing it, and the model acts on the
|
|
186
|
+
control again.
|
|
187
|
+
|
|
188
|
+
If semantic capture times out, an engine that proves masking independently
|
|
189
|
+
can return a fresh screenshot with an explicit unavailable-tree warning.
|
|
190
|
+
Every `act` observation with unavailable semantics includes that image and
|
|
191
|
+
warning automatically. Earlier node ids are invalid, and the next successful
|
|
192
|
+
semantic capture presents the whole tree again. The screenshot shows the
|
|
193
|
+
current viewport; an unavailable tree does not prove that a control or
|
|
194
|
+
message is absent.
|
|
195
|
+
|
|
196
|
+
If semantic capture fails, trace replay stops and the step cannot record a
|
|
197
|
+
new trace entry, even if the tree later recovers.
|
|
198
|
+
|
|
199
|
+
A secret filled during the step withholds every later screenshot
|
|
200
|
+
(`PIXEL_TAINTED`), and a step that opens on a tainted viewport offers none of
|
|
201
|
+
the six pixel tools. `tap_at` needs an engine whose `actions` or
|
|
202
|
+
`pointerActions` include `tap`, and `hover_at` one whose include `hover`;
|
|
203
|
+
without the pointer kind the point must land on a listed control. A
|
|
204
|
+
bare-point tap or hover replays from the [replay cache](/cache) only on a
|
|
205
|
+
viewport of the recorded size, or at the same place inside the listed node
|
|
206
|
+
that contained it. A step that sent pixels records `visionInput: true` and
|
|
207
|
+
`metrics.pixelBytes`.
|
|
208
|
+
|
|
209
|
+
A failed or blocked step throws an `AgentError`. A failed verdict is
|
|
210
|
+
`ACTION_FAILED` unless the executor names a more specific code, such as
|
|
211
|
+
`LOCATOR_NOT_FOUND` or `AUTHENTICATION_FAILED`. A blocked verdict, a step
|
|
212
|
+
that could not reach a product verdict, carries one of these codes:
|
|
213
|
+
|
|
214
|
+
| Blocked on | Codes |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| credentials | `AUTH_CREDENTIAL_UNAVAILABLE`, `AUTH_CREDENTIAL_INVALID`, `SECRET_UNAVAILABLE` |
|
|
217
|
+
| environment | `ENVIRONMENT_UNAVAILABLE`, `APP_UNREACHABLE`, `APP_ALREADY_RUNNING` |
|
|
218
|
+
| seed data | `SEED_DATA_MISSING` |
|
|
219
|
+
| test setup | `TEST_SETUP_FAILED`, `APP_NOT_OPEN`, `POLICY_DENIED` |
|
|
220
|
+
| automation | `MODEL_UNAVAILABLE`, `AUTOMATION_UNSUPPORTED`, `STEP_BUDGET_EXHAUSTED`, `STEP_TIMEOUT`, `CONTEXT_OVERFLOW` |
|
|
221
|
+
|
|
222
|
+
The runner's own stops are
|
|
223
|
+
`STEP_BUDGET_EXHAUSTED` and `STEP_TIMEOUT` when a budget or the deadline ran
|
|
224
|
+
out, `CANCELLED`, `STEP_NO_CONCLUSION` when the turns ran out without a
|
|
225
|
+
conclusion, `CONTEXT_OVERFLOW`, `MODEL_PROVIDER_FAILED`,
|
|
226
|
+
`MODEL_OUTPUT_INVALID` for a verdict outside the grammar, and `REPLAY_STALE`
|
|
227
|
+
under a strict cache. Every code is in [Errors](/reference/errors#agenterror).
|
|
228
|
+
|
|
229
|
+
`act` also throws `INVALID_ARGUMENT` for a malformed instruction, oversized
|
|
230
|
+
`params`, an unknown option, or a budget above the configured limit, and
|
|
231
|
+
`UNSUPPORTED_CAPABILITY` for `schema` or `vision`, which `act` does not take.
|
|
232
|
+
|
|
233
|
+
## agent.assert
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
assert(assertion: string, options?: AssertOptions): Promise<void>;
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Checks one fresh observation without polling. The judge sees only the
|
|
240
|
+
assertion and current observation, never prior steps or `act` summaries.
|
|
241
|
+
It answers `holds`, `fails`, or `inconclusive`.
|
|
242
|
+
|
|
243
|
+
Throws `ASSERTION_FAILED` on a false judgment and `ASSERTION_INCONCLUSIVE`
|
|
244
|
+
when the screen did not show enough to decide. Both carry the explanation
|
|
245
|
+
and, when permitted, a screenshot path. An inconclusive judgment made from
|
|
246
|
+
the tree alone adds `pass vision: true when the answer is in pixels` to its
|
|
247
|
+
message, worded as a condition (`if the engine captures pixels, ...`) until
|
|
248
|
+
a step of the attempt has received pixels. There is no hint when the engine
|
|
249
|
+
declares no screenshot capture, a secret was filled, or an earlier step's
|
|
250
|
+
pixel request was degraded: `vision: true` could add no evidence then. The
|
|
251
|
+
hint is the built-in judge's; with a custom `executor`, `assert` carries the
|
|
252
|
+
executor's explanation alone. No evidence is captured with
|
|
253
|
+
`screenshot: false` or once a secret has been filled in the attempt.
|
|
254
|
+
`screenshot` controls failure evidence attached after the judgment; `vision`
|
|
255
|
+
controls what the model is shown before it. A custom `StepExecutor` judges
|
|
256
|
+
the assertion itself: it honors `screenshot`, since the runner takes that
|
|
257
|
+
evidence after the verdict, and rejects `vision` with
|
|
258
|
+
`UNSUPPORTED_CAPABILITY`, since the executor decides what its model sees.
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
await agent.assert('the dashboard shows a trial badge');
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## agent.waitFor
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
waitFor(condition: string, options?: WaitForOptions): Promise<void>;
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Observes and judges at once. After a false or inconclusive judgment it
|
|
271
|
+
waits out `interval`, then observes every 500 ms (or every `interval`, when
|
|
272
|
+
shorter) without a model call, and judges again only once the screen
|
|
273
|
+
differs from the one last judged, pixels included under `vision`. Succeeds
|
|
274
|
+
on the first judgment that holds.
|
|
275
|
+
|
|
276
|
+
Throws `STEP_TIMEOUT` on deadline expiry and `STEP_BUDGET_EXHAUSTED` on
|
|
277
|
+
budget exhaustion, each carrying the last judgment (with the same vision
|
|
278
|
+
hint as `agent.assert` when that round was inconclusive), and
|
|
279
|
+
`INVALID_ARGUMENT` for an out-of-range option or one `waitFor` does not
|
|
280
|
+
take.
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
await agent.waitFor('the upload progress reports completion', { interval: 500 });
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## agent.extract
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
extract<Schema extends StandardSchemaV1>(
|
|
290
|
+
instruction: string,
|
|
291
|
+
options: ExtractOptions<Schema>,
|
|
292
|
+
): Promise<StandardSchemaV1.InferOutput<Schema>>;
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Takes one fresh observation, requests structured output, validates it, and
|
|
296
|
+
returns the validated value. It performs no app actions.
|
|
297
|
+
|
|
298
|
+
When the schema has a JSON Schema projection (zod does), the model receives
|
|
299
|
+
its shape: types, fields, required fields, and allowed literals. Value rules
|
|
300
|
+
such as `min`, `max`, lengths, patterns, and formats stay with your schema,
|
|
301
|
+
which judges the value the model read. The model can also answer that the
|
|
302
|
+
screen does not show the data, which throws `ASSERTION_INCONCLUSIVE` with
|
|
303
|
+
what was missing, instead of returning a placeholder such as `""` or `0`.
|
|
304
|
+
To accept absence as an answer, say so in the instruction and the schema
|
|
305
|
+
(`'the phone number, or null when none is shown'` with
|
|
306
|
+
`z.string().nullable()`).
|
|
307
|
+
|
|
308
|
+
The budget is two model calls: one extraction and one repair round for
|
|
309
|
+
output that failed validation. Throws `INVALID_ARGUMENT` for a schema that is
|
|
310
|
+
not Standard Schema v1, `ASSERTION_INCONCLUSIVE` when the screen does not
|
|
311
|
+
show the data, and `MODEL_OUTPUT_INVALID` when validation still fails after
|
|
312
|
+
the repair.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
const summary = await agent.extract('the workspace name and plan', {
|
|
316
|
+
schema: z.object({ name: z.string(), plan: z.string() }),
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Vision
|
|
321
|
+
|
|
322
|
+
`assert`, `waitFor`, and `extract` accept `vision`. Screenshots require a
|
|
323
|
+
judge model that accepts images.
|
|
324
|
+
|
|
325
|
+
| `vision` | Evidence sent to the judge |
|
|
326
|
+
| --- | --- |
|
|
327
|
+
| `false`, the default | Semantic tree |
|
|
328
|
+
| `true` | Semantic tree and a masked screenshot |
|
|
329
|
+
| `'only'` | Masked screenshot only |
|
|
330
|
+
|
|
331
|
+
Use `'only'` for visual checks, such as whether an overlay covers a form or a
|
|
332
|
+
chart looks correct. Screenshots show the current viewport, so scroll to the
|
|
333
|
+
relevant content first. `act` requests its own screenshots and does not take
|
|
334
|
+
this option.
|
|
335
|
+
|
|
336
|
+
Pixels are withheld and the tree sent alone when a secret fill has tainted
|
|
337
|
+
the viewport (`visionDegraded: "PIXEL_TAINTED"` on the step), when the engine
|
|
338
|
+
masked fewer regions than the secure fields it observed (`MASKING_UNPROVEN`),
|
|
339
|
+
or when the engine cannot capture pixels (`UNSUPPORTED_CAPABILITY`). In
|
|
340
|
+
`'only'` mode those cases fail with `POLICY_DENIED` instead.
|
|
341
|
+
|
|
342
|
+
With `vision: true` or `'only'`, a recoverable semantic-capture failure can
|
|
343
|
+
use a fresh screenshot when the engine proves masking independently. With
|
|
344
|
+
`vision: false`, or after a secret fill, the capture failure stops the
|
|
345
|
+
judgment before any model call. A missing tree cannot support a judgment
|
|
346
|
+
about an empty screen.
|
|
347
|
+
|
|
348
|
+
If capture fails before a judgment, the report records zero model calls and
|
|
349
|
+
omits `observationRevision` and `explanation`. The step error describes the
|
|
350
|
+
capture failure. An answer the closed grammar rejects is a `schema` event on
|
|
351
|
+
the step and earns one repair round; when that answer is rejected too, the
|
|
352
|
+
step fails with `MODEL_OUTPUT_INVALID` and its `explanation` is the rejection.
|
|
353
|
+
|
|
354
|
+
## Types
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
/**
|
|
358
|
+
* Parameters and budgets for one `act` call.
|
|
359
|
+
*/
|
|
360
|
+
export interface ActOptions extends AgentOption {
|
|
361
|
+
/**
|
|
362
|
+
* JSON-safe values the instruction refers to, at most 64 KiB and 32 levels
|
|
363
|
+
* deep. A `Secret` reaches the model by name only; the runner fills it. A
|
|
364
|
+
* value wrapped in `unique()` is different on every run, and the replay
|
|
365
|
+
* cache records a slot for it instead of the value.
|
|
366
|
+
*/
|
|
367
|
+
params?: AgentParams;
|
|
368
|
+
/** Step deadline in milliseconds; defaults to `config.timeout`. */
|
|
369
|
+
timeout?: number;
|
|
370
|
+
/** Action budget; defaults to `agents.<name>.maxSteps` and can only lower it. */
|
|
371
|
+
maxSteps?: number;
|
|
372
|
+
/** Model-call budget; defaults to `agents.<name>.maxModelCalls` and can only lower it. */
|
|
373
|
+
maxModelCalls?: number;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** The configured agent (`agents.<name>`) one call runs with, in place of the test's. */
|
|
377
|
+
export interface AgentOption {
|
|
378
|
+
agent?: string;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** `assert` options: one judgment plus one repair round, within `timeout`. */
|
|
382
|
+
export interface AssertOptions extends VisionOption, AgentOption {
|
|
383
|
+
/** Deadline in milliseconds; defaults to the agent's `judgmentTimeout`, 30000. */
|
|
384
|
+
timeout?: number;
|
|
385
|
+
/** Attach a redacted screenshot to the step; on by default, denied after a secret fill. */
|
|
386
|
+
screenshot?: boolean;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** `waitFor` options: a judgment at most once per `interval` until `timeout`. */
|
|
390
|
+
export interface WaitForOptions extends VisionOption, AgentOption {
|
|
391
|
+
/** Deadline in milliseconds; defaults to the agent's `judgmentTimeout`, 30000. */
|
|
392
|
+
timeout?: number;
|
|
393
|
+
/** Least time between two judgments, in milliseconds; 100 through 60000, default 3000. */
|
|
394
|
+
interval?: number;
|
|
395
|
+
/** Judgment budget; defaults to `agents.<name>.maxModelCalls` and can only lower it. */
|
|
396
|
+
maxModelCalls?: number;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** `extract` options: one extraction plus one repair round, validated against `schema`. */
|
|
400
|
+
export interface ExtractOptions<Schema extends StandardSchemaV1> extends VisionOption, AgentOption {
|
|
401
|
+
/** Any Standard Schema v1 validator; the output is validated against it, with one repair round. */
|
|
402
|
+
schema: Schema;
|
|
403
|
+
/** Deadline in milliseconds; defaults to the agent's `judgmentTimeout`, 30000. */
|
|
404
|
+
timeout?: number;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export interface VisionOption {
|
|
408
|
+
/** What the judge is shown; `false` by default. */
|
|
409
|
+
vision?: VisionMode;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Evidence for `assert`, `waitFor`, and `extract`.
|
|
414
|
+
*
|
|
415
|
+
* - `false`: the tree.
|
|
416
|
+
* - `true`: the tree and a screenshot, on every call.
|
|
417
|
+
* - `'only'`: the screenshot, and not the tree.
|
|
418
|
+
*
|
|
419
|
+
* When pixels cannot be safely captured, `true` falls back to the tree;
|
|
420
|
+
* `'only'` fails with `POLICY_DENIED`.
|
|
421
|
+
*/
|
|
422
|
+
export type VisionMode = boolean | 'only';
|
|
423
|
+
|
|
424
|
+
/** What one passing `act` step did, as the report records it. */
|
|
425
|
+
export interface ActResult {
|
|
426
|
+
/** The executor's one-line account of the step, or the replay's when the cache finished it. */
|
|
427
|
+
readonly summary: string;
|
|
428
|
+
/** How the replay cache took part; absent when caching is off for the step. */
|
|
429
|
+
readonly cache?: StepCacheInfo;
|
|
430
|
+
/** Model calls the step spent; 0 when a cached replay finished it. */
|
|
431
|
+
readonly modelCalls: number;
|
|
432
|
+
/** Grammar actions and mutating tool calls the step performed. */
|
|
433
|
+
readonly actions: number;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* How the replay cache took part in one agent step. `self-finalized` means
|
|
438
|
+
* the full trace replayed and the step passed with zero model calls;
|
|
439
|
+
* `agent-concluded` means a replayed prefix handed the step to the executor;
|
|
440
|
+
* `missed` means the executor ran the step from the top.
|
|
441
|
+
*/
|
|
442
|
+
export interface StepCacheInfo {
|
|
443
|
+
mode: 'self-finalized' | 'agent-concluded' | 'missed';
|
|
444
|
+
/** The miss or hand-off reason token; absent on `self-finalized`. */
|
|
445
|
+
reason?: TraceReplayMissReason | ReplayHandOffReason;
|
|
446
|
+
/**
|
|
447
|
+
* On a `gap` at a typed value: the rule that made the value this run's
|
|
448
|
+
* data rather than the flow's. Absent for a gap at a project tool.
|
|
449
|
+
*/
|
|
450
|
+
derived?: DerivedReason;
|
|
451
|
+
/**
|
|
452
|
+
* Why a passing step that ran live was still not recorded, when the cause
|
|
453
|
+
* is the call itself: `param-collision` when a `unique()` value equals, is
|
|
454
|
+
* spelled inside, or is the encoded form of another param's value, so the
|
|
455
|
+
* recording could not tell which param a recorded input came from.
|
|
456
|
+
*/
|
|
457
|
+
notRecorded?: 'param-collision';
|
|
458
|
+
replayedActions: number;
|
|
459
|
+
totalActions: number;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
type TraceReplayMissReason = 'retry' | 'no-entry' | 'invalid-entry' | 'truncated' | 'wrong-context';
|
|
463
|
+
export type ReplayHandOffReason =
|
|
464
|
+
| 'gap'
|
|
465
|
+
| 'target-not-found'
|
|
466
|
+
| 'target-ambiguous'
|
|
467
|
+
| 'viewport-changed'
|
|
468
|
+
| 'action-failed'
|
|
469
|
+
| 'action-uncertain'
|
|
470
|
+
| 'end-mismatch';
|
|
471
|
+
export type DerivedReason = 'pixels' | 'whole-node' | 'minted-token' | 'date';
|
|
472
|
+
|
|
473
|
+
/** Values an `act` instruction refers to. */
|
|
474
|
+
export type AgentParams = Readonly<Record<string, AgentParam>>;
|
|
475
|
+
|
|
476
|
+
/** A JSON-safe value, a `Secret`, or a `Unique` string. */
|
|
477
|
+
export type AgentParam =
|
|
478
|
+
| JsonPrimitive
|
|
479
|
+
| Secret
|
|
480
|
+
| Unique
|
|
481
|
+
| readonly AgentParam[]
|
|
482
|
+
| { readonly [key: string]: AgentParam };
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Each `StepCacheInfo.reason` is explained under
|
|
486
|
+
[Why a step was not replayed](/cache#why-a-step-was-not-replayed).
|
|
487
|
+
|
|
488
|
+
`Unique` is what `unique()` returns: a value that differs on every run, which
|
|
489
|
+
the replay cache records as a slot and substitutes on replay. See
|
|
490
|
+
[Cache](/cache).
|
|
491
|
+
|
|
492
|
+
## unique
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
import { unique } from 'e2e';
|
|
496
|
+
|
|
497
|
+
/** A string param that differs on every run; the model sees `value`, the replay cache a slot. */
|
|
498
|
+
export interface Unique {
|
|
499
|
+
readonly value: string;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Marks a value that is different on every run. Mark values the flow types
|
|
504
|
+
* or looks for, not choices that steer it: two runs whose `unique()` values
|
|
505
|
+
* differ replay the same recording.
|
|
506
|
+
*/
|
|
507
|
+
export function unique(value: string): Unique;
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Throws `INVALID_ARGUMENT` for a value that is not a string, is empty or only
|
|
511
|
+
whitespace, or contains the placeholder text `{{param:`.
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
await agent.act('create a company named {name}', {
|
|
515
|
+
params: { name: unique(`Acme ${Date.now()}`) },
|
|
516
|
+
});
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## AgentError
|
|
520
|
+
|
|
521
|
+
Every failure is an `AgentError` with a closed `code`, an `explanation`, an
|
|
522
|
+
optional `screenshot` path, and a `blocked` flag. Check it with
|
|
523
|
+
`isAgentError(error)` from `e2e`, not `instanceof`. The class and
|
|
524
|
+
every code are in [Errors](/reference/errors#agenterror).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: app
|
|
3
|
+
description: The portable app lifecycle fixture, available on every platform.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`app` controls the app's lifecycle and captures screenshots. Each method
|
|
7
|
+
requires the corresponding engine capability; unsupported operations fail
|
|
8
|
+
with `UNSUPPORTED_CAPABILITY`.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
export interface App {
|
|
12
|
+
/**
|
|
13
|
+
* The resolved base URL of the target's app, with the port the run
|
|
14
|
+
* allocated when `app.url` declared port 0; undefined for a target that
|
|
15
|
+
* declares no `app.url`.
|
|
16
|
+
*/
|
|
17
|
+
readonly baseUrl: string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Opens the app: the declared URL, a path relative to it, or any absolute
|
|
20
|
+
* http(s) URL. On a device target, which has no URL, it launches the pinned
|
|
21
|
+
* app fresh and takes no path.
|
|
22
|
+
*/
|
|
23
|
+
open(path?: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Recreates the execution context while preserving persisted state, then
|
|
26
|
+
* reopens the app at its base URL when the target declares one.
|
|
27
|
+
*/
|
|
28
|
+
restart(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Clears persisted client state, recreates the execution context, then
|
|
31
|
+
* reopens the app at its base URL when the target declares one.
|
|
32
|
+
*/
|
|
33
|
+
clearState(): Promise<void>;
|
|
34
|
+
/** Navigates back once. */
|
|
35
|
+
back(): Promise<void>;
|
|
36
|
+
/** Captures a redacted evidence screenshot. */
|
|
37
|
+
screenshot(label?: string): Promise<string>;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`app` methods use the configured timeouts below, capped by the remaining test
|
|
42
|
+
timeout. They do not accept per-call timeout options.
|
|
43
|
+
|
|
44
|
+
| Method | Timeout | Throws |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `open(path?)` | `config.timeout`, 120000 ms | `APP_URL_REQUIRED` for a path when the target declares no `app.url`, `POLICY_DENIED` for a `file:`, `data:`, or `javascript:` URL, `APP_UNREACHABLE`, `ACTION_FAILED`, `UNSUPPORTED_CAPABILITY` without `session.open` on a target with a URL or without `session.restart` on one without (a device launches its pinned app) |
|
|
47
|
+
| `restart()` | `config.timeout`, 120000 ms | `ACTION_FAILED`, `ENGINE_FAILURE`, `UNSUPPORTED_CAPABILITY` without `session.restart` |
|
|
48
|
+
| `clearState()` | `config.timeout`, 120000 ms | `ACTION_FAILED`, `ENGINE_FAILURE`, `UNSUPPORTED_CAPABILITY` without `session.reset` |
|
|
49
|
+
| `back()` | `config.actionTimeout`, 30000 ms | `APP_NOT_OPEN`, `ACTION_FAILED`, `UNSUPPORTED_CAPABILITY` without `session.back` |
|
|
50
|
+
| `screenshot(label?)` | `config.actionTimeout`, 30000 ms | `POLICY_DENIED` after a secret fill, `UNSUPPORTED_CAPABILITY`, `ENGINE_FAILURE` |
|
|
51
|
+
|
|
52
|
+
`screenshot` registers the file as an attempt artifact and returns its path
|
|
53
|
+
relative to the attempt's artifact directory, under `screenshots/`; the report
|
|
54
|
+
records the artifact-root-relative path. `label` names the step and joins the
|
|
55
|
+
file name.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
await app.open('/settings/billing');
|
|
59
|
+
const shot = await app.screenshot('after-checkout');
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
<Warning>
|
|
63
|
+
`clearState` inside a serial group destroys the shared state the group
|
|
64
|
+
depends on. Use it in `beforeEach` of an ordinary group instead.
|
|
65
|
+
</Warning>
|
|
66
|
+
|
|
67
|
+
Once a `Secret` has been filled, pixels are withheld for the rest of the
|
|
68
|
+
session, including later tests in a serial group. `app.screenshot()` then
|
|
69
|
+
fails with `POLICY_DENIED` and creates no artifact.
|
|
70
|
+
|
|
71
|
+
Browser-only operations live on the [`browser`](/reference/web) fixture;
|
|
72
|
+
device operations on [`device`](/reference/mobile).
|