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