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
package/docs/reference/web.mdx
CHANGED
|
@@ -6,14 +6,9 @@ icon: "globe"
|
|
|
6
6
|
"twitter:image": "https://e2e.tester.army/docs/images/og/reference/web.png?v=66fee465a4"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
`@e2e-dev/web` drives Chromium, Firefox, and WebKit
|
|
10
|
-
A web target names it as its engine
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
The package depends on `playwright-core` pinned to an exact version, so the
|
|
14
|
-
engine always runs the Playwright it was tested against. An app that depends
|
|
15
|
-
on Playwright itself keeps its own copy; the two share a browser cache only
|
|
16
|
-
when their versions match.
|
|
9
|
+
`@e2e-dev/web` is the browser engine. It drives Chromium, Firefox, and WebKit
|
|
10
|
+
through Playwright. A web target names it as its engine with `web()`, and it
|
|
11
|
+
contributes the `browser` fixture for browser-only work.
|
|
17
12
|
|
|
18
13
|
```ts title="e2e.config.ts"
|
|
19
14
|
import type { E2EConfig } from 'e2e';
|
|
@@ -24,12 +19,885 @@ export default {
|
|
|
24
19
|
} satisfies E2EConfig;
|
|
25
20
|
```
|
|
26
21
|
|
|
22
|
+
Import `test` from the package to have `browser` typed:
|
|
23
|
+
|
|
24
|
+
```ts title="tests/orders.e2e.ts"
|
|
25
|
+
import { expect } from 'e2e';
|
|
26
|
+
import { test } from '@e2e-dev/web';
|
|
27
|
+
|
|
28
|
+
test('orders in dark mode', async ({ app, browser, screen }) => {
|
|
29
|
+
await browser.emulateMedia({ colorScheme: 'dark' });
|
|
30
|
+
await app.open('/orders');
|
|
31
|
+
await expect(browser).toHaveURL('/orders');
|
|
32
|
+
await expect(screen.getByRole('heading', { name: 'Orders' })).toBeVisible();
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Reference
|
|
37
|
+
|
|
38
|
+
### Options
|
|
39
|
+
|
|
40
|
+
`web(options)` sets how the engine drives the app. The app itself (its
|
|
41
|
+
`url`, `environment`, `identity`, and the command that starts it) is the
|
|
42
|
+
target's `app`, documented under
|
|
43
|
+
[the app under test](/reference/config#the-app-under-test).
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
web({ viewport: { width: 390, height: 844 }, locale: 'de-DE' });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Option | Type | Default |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| [`browser`](#browser) | `BrowserName \| BrowserProvider` | `'chromium'` |
|
|
52
|
+
| [`viewport`](#viewport) | `ViewportSize \| null` | 1280 by 720 |
|
|
53
|
+
| [`screencast`](#screencast) | `WebScreencastOptions` | the viewport's size |
|
|
54
|
+
| [`connect`](#connect) | `WebConnectOptions` | launches locally |
|
|
55
|
+
| [`headers`](#headers) | `Record<string, string>` | - |
|
|
56
|
+
| [`basicAuth`](#basicauth) | `WebBasicAuth` | - |
|
|
57
|
+
| [`testIdAttribute`](#testidattribute) | `string` | `'data-testid'` |
|
|
58
|
+
| [`userAgent`](#useragent) | `string` | the browser's own |
|
|
59
|
+
| [`locale`](#locale) | `string` | the browser's own |
|
|
60
|
+
| [`timezoneId`](#timezoneid) | `string` | the machine's |
|
|
61
|
+
| [`colorScheme`](#media-emulation) | `'light' \| 'dark' \| 'no-preference'` | the browser's own |
|
|
62
|
+
| [`reducedMotion`](#media-emulation) | `'reduce' \| 'no-preference'` | the browser's own |
|
|
63
|
+
| [`initScripts`](#init-scripts) | `WebInitScript[]` | - |
|
|
64
|
+
|
|
65
|
+
- A `web()` target needs `app.url` and refuses the device keys (`bundleId`,
|
|
66
|
+
`appPath`, `launchArguments`, `permissions`).
|
|
67
|
+
- A target with `app.url` is a `browser` target, so `requires: ['browser']`
|
|
68
|
+
selects web targets.
|
|
69
|
+
- The full declaration with its JSDoc is [`WebOptions`](#weboptions) under
|
|
70
|
+
Types.
|
|
71
|
+
|
|
72
|
+
| Throws | When |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `INVALID_CONFIG` | An option `web()` does not take (`url` or any other app key included), or an unknown key inside `connect` or `basicAuth`. The message names the nearest known key for a likely typo (`viewprt` is `viewport`), else every key. |
|
|
75
|
+
| `INVALID_CONFIG` | `headers`, `basicAuth`, `userAgent`, `locale`, or `timezoneId` on a [persistent context](#recovering-a-cdp-transport) (`connect.reconnectEndpoint`, or a provider with `scope: 'attempt'`). |
|
|
76
|
+
|
|
77
|
+
#### `browser`
|
|
78
|
+
|
|
79
|
+
The browser to launch, or a provider that leases hosted browsers.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
web({ browser: 'firefox' }); // default: 'chromium'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `'chromium'`, `'firefox'`, or `'webkit'`.
|
|
86
|
+
- A `BrowserProvider` leases hosted browsers instead: one per worker slot for
|
|
87
|
+
the run, or one per attempt ([`WebOptions`](#weboptions)). The engine
|
|
88
|
+
drives each one as chromium over CDP, whether it is Chromium or another CDP
|
|
89
|
+
server such as [Lightpanda](/integrations/lightpanda).
|
|
90
|
+
- A provider with `scope: 'attempt'` rides a persistent context, with the
|
|
91
|
+
limits under [Recovering a CDP transport](#recovering-a-cdp-transport).
|
|
92
|
+
- [Hosted browsers](/browser#hosted-browsers) has the `BrowserProvider`
|
|
93
|
+
contract and an example provider.
|
|
94
|
+
|
|
95
|
+
| Throws | When |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `INVALID_CONFIG` | `browser: provider` together with `connect`. |
|
|
98
|
+
|
|
99
|
+
#### `viewport`
|
|
100
|
+
|
|
101
|
+
The initial viewport of every attempt's page.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
web({ viewport: { width: 390, height: 844 } }); // default: { width: 1280, height: 720 }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`null` emulates no size, so the page fills the browser window
|
|
108
|
+
([`WebOptions`](#weboptions)). [`browser.setViewport`](#setviewport) still
|
|
109
|
+
fixes one.
|
|
110
|
+
|
|
111
|
+
#### `screencast`
|
|
112
|
+
|
|
113
|
+
How the engine's own screencast records an attempt that records video.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
web({ screencast: { size: { width: 800, height: 450 }, quality: 60 } });
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- `size` takes whole pixels, each side at least 1. `quality` is an integer.
|
|
120
|
+
- Which attempts record is the config's [`video`](/reference/config#video)
|
|
121
|
+
mode.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
/** The engine's screencast of the page: the frame size and the JPEG quality of the frames it encodes. */
|
|
125
|
+
export interface WebScreencastOptions {
|
|
126
|
+
/** Frame size in pixels; default the viewport's, or the window's under `viewport: null`. A smaller size makes a smaller file. */
|
|
127
|
+
readonly size?: ViewportSize;
|
|
128
|
+
/** JPEG quality of each captured frame, 0 through 100; Playwright's default when unset. */
|
|
129
|
+
readonly quality?: number;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
| Throws | When |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| `INVALID_CONFIG` | Any other `size` or `quality` value, or another key, at config load. The old `web({ video })` spelling is also refused, naming `screencast`. |
|
|
136
|
+
|
|
137
|
+
#### `connect`
|
|
138
|
+
|
|
139
|
+
Attaches to a remote browser over CDP instead of launching one locally.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
web({ connect: { cdpEndpoint: async () => 'ws://127.0.0.1:9222' } });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`reconnectEndpoint` opts into a persistent context; see
|
|
146
|
+
[Recovering a CDP transport](#recovering-a-cdp-transport).
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
interface WebConnectOptions {
|
|
150
|
+
cdpEndpoint(signal: AbortSignal): string | Promise<string>;
|
|
151
|
+
reconnectEndpoint?(signal: AbortSignal): string | Promise<string>;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Throws | When |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `INVALID_CONFIG` | A `connect` without `cdpEndpoint`, or with one given as a string, at config load. Also `connect` beside a `browser` provider. |
|
|
158
|
+
|
|
159
|
+
<a id="recovering-a-cdp-transport" />
|
|
160
|
+
|
|
161
|
+
##### Recovering a CDP transport
|
|
162
|
+
|
|
163
|
+
`connect.reconnectEndpoint(signal)` opts into recovery of the same remote
|
|
164
|
+
browser after its CDP transport disconnects. Omit it to keep isolated
|
|
165
|
+
contexts.
|
|
166
|
+
|
|
167
|
+
The remote browser:
|
|
168
|
+
|
|
169
|
+
- Every attempt, retries included, needs its own fresh browser from
|
|
170
|
+
`cdpEndpoint`. One long-lived browser cannot serve a suite.
|
|
171
|
+
- The remote must have only its default context.
|
|
172
|
+
- The host must delete the browser when the attempt ends. Engine cleanup
|
|
173
|
+
detaches its connection and does not terminate the host's browser.
|
|
174
|
+
- The attempt's first page is the browser's own first tab, navigated to
|
|
175
|
+
`about:blank`, not a second tab beside it. A page opened later, such as
|
|
176
|
+
the one `app.restart` opens, is a new tab.
|
|
177
|
+
|
|
178
|
+
How recovery runs:
|
|
179
|
+
|
|
180
|
+
- After a disconnect, the next operation calls `reconnectEndpoint` once,
|
|
181
|
+
within that operation's timeout and abort signal. The resolver must return
|
|
182
|
+
the existing browser's endpoint.
|
|
183
|
+
- The engine checks the persistent default context ID and the original
|
|
184
|
+
page's target ID. It refuses a browser that does not expose the default
|
|
185
|
+
context ID. A missing page or a different context fails the attempt. A
|
|
186
|
+
matching URL does not select a replacement tab.
|
|
187
|
+
- The engine installs secure-field masking before it publishes each CDP
|
|
188
|
+
binding. An incompatible Playwright version fails the connection instead of
|
|
189
|
+
exposing pixels.
|
|
190
|
+
- The original page's frames must keep their closed shadow DOM tracking
|
|
191
|
+
hooks. Existing closed roots cannot recover without a reload of the
|
|
192
|
+
application. Mask queries reject any untracked document.
|
|
193
|
+
- Endpoint resolution, CDP attachment, setup, and dispatch share one timeout.
|
|
194
|
+
Cancelling a concurrent waiter does not cancel the operation that started
|
|
195
|
+
recovery. Cancelling that recovery owner fails the shared recovery.
|
|
196
|
+
|
|
197
|
+
| Throws | When |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| `ENGINE_FAILURE` | A document loaded without the closed shadow DOM hook while disconnected. |
|
|
200
|
+
| `OPERATION_TIMEOUT` | Endpoint resolution, attachment, setup, and dispatch exhausted their shared timeout. |
|
|
201
|
+
| `CANCELLED` | The caller or the attempt cancelled. |
|
|
202
|
+
|
|
203
|
+
What survives:
|
|
204
|
+
|
|
205
|
+
- Recovery keeps the page's unsaved state, storage, the attempt's secret
|
|
206
|
+
taint, and the agent ledger. Dialog and route handlers are reattached.
|
|
207
|
+
- Video recording resumes. A segment lost during the disconnect is gone.
|
|
208
|
+
- Operations already dispatched are never retried.
|
|
209
|
+
- Recovery clears old node references. Observation-derived `tapAt` calls,
|
|
210
|
+
observation-root swipes, and focused engine keyboard input need a fresh
|
|
211
|
+
engine observation before they resume.
|
|
212
|
+
- An observation or locator read started before recovery cannot publish
|
|
213
|
+
references after the connection changes. Each attempt owns its binding and
|
|
214
|
+
recordings, so late cleanup cannot change a later attempt.
|
|
215
|
+
- The deterministic `browser.mouse` and `browser.keyboard` fixtures stay
|
|
216
|
+
available without an agent observation. Test code owns its input and can
|
|
217
|
+
read current geometry and focus with `browser.evaluate` before acting.
|
|
218
|
+
- Code that uses `surfaceOf(engine)` must read its accessors again after
|
|
219
|
+
recovery. Playwright objects it kept belong to the disconnected transport.
|
|
220
|
+
|
|
221
|
+
A `browser` provider with `scope: 'attempt'` rides the same persistent
|
|
222
|
+
context, with the same limits. The lease's `cdpEndpoint` provisions the
|
|
223
|
+
attempt's browser, and its `reconnectEndpoint` (else `cdpEndpoint` again)
|
|
224
|
+
reconnects to it. A lease is fresh by the provider's contract, so the engine
|
|
225
|
+
does not compare its context with earlier attempts'. A hosted service may
|
|
226
|
+
restore browsers from one snapshot, which gives new browsers the same
|
|
227
|
+
context id.
|
|
228
|
+
|
|
229
|
+
To reuse one sign-in across files, keep isolated contexts (no
|
|
230
|
+
`reconnectEndpoint`, no provider `scope: 'attempt'`) and
|
|
231
|
+
[sign in once in a setup test](/authentication#sign-in-once-in-a-setup-test).
|
|
232
|
+
A saved session carries it, not a shared browser.
|
|
233
|
+
|
|
234
|
+
<Note>
|
|
235
|
+
A persistent context does not support `headers`, `basicAuth`, `userAgent`,
|
|
236
|
+
`locale`, `timezoneId` (see [Options](#options)), `app.clearState`, or
|
|
237
|
+
session state capture and restore. `app.clearState` fails with
|
|
238
|
+
`UNSUPPORTED_CAPABILITY`. `app.restart` remains available.
|
|
239
|
+
</Note>
|
|
240
|
+
|
|
241
|
+
#### `headers`
|
|
242
|
+
|
|
243
|
+
Headers added to requests within the app's site, including subdomains.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
web({ headers: { 'x-preview-token': process.env.PREVIEW_TOKEN ?? '' } });
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- A [continued](#route) request bound for the app's site still carries them.
|
|
250
|
+
- `headers` and `basicAuth` are how a test reaches a
|
|
251
|
+
[protected preview](/web#protected-previews).
|
|
252
|
+
|
|
253
|
+
| Throws | When |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| `INVALID_CONFIG` | A `user-agent` entry beside [`userAgent`](#useragent), or an `accept-language` entry beside [`locale`](#locale). |
|
|
256
|
+
|
|
257
|
+
<Note>
|
|
258
|
+
`headers` enables request routing, which disables the browser's HTTP cache and
|
|
259
|
+
blocks service workers.
|
|
260
|
+
</Note>
|
|
261
|
+
|
|
262
|
+
#### `basicAuth`
|
|
263
|
+
|
|
264
|
+
HTTP basic authentication. The browser answers a `401` challenge with these
|
|
265
|
+
credentials on any origin, matching Playwright's `httpCredentials`.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { secrets } from 'e2e';
|
|
269
|
+
|
|
270
|
+
web({ basicAuth: { username: 'staging', password: secrets.get('staging-password') } });
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
A `secrets.get(name)` password resolves when each attempt starts and is
|
|
274
|
+
redacted like any configured secret. So is the base64 credential of the
|
|
275
|
+
`Authorization` header it becomes.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
/** HTTP basic authentication the browser answers a `401` challenge with. */
|
|
279
|
+
export interface WebBasicAuth {
|
|
280
|
+
/** The user name; `:` is not allowed in one (RFC 7617). */
|
|
281
|
+
readonly username: string;
|
|
282
|
+
/** The password, or `secrets.get(name)` for one in `config.secrets`. */
|
|
283
|
+
readonly password: string | Secret;
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
| Throws | When |
|
|
288
|
+
| --- | --- |
|
|
289
|
+
| `INVALID_CONFIG` | A `:` in the user name, or an unknown key. |
|
|
290
|
+
|
|
291
|
+
<Warning>
|
|
292
|
+
The secret protection is text only. Unlike a fill, it withholds no screenshot
|
|
293
|
+
or model pixels, so a page that renders the password on screen is not masked
|
|
294
|
+
in them ([Security rules](/reference/security#secrets-in-engine-options)).
|
|
295
|
+
</Warning>
|
|
296
|
+
|
|
297
|
+
#### `testIdAttribute`
|
|
298
|
+
|
|
299
|
+
The attribute that carries an element's test id: what the `testId` query
|
|
300
|
+
(`screen.getByTestId`) resolves and what `SemanticNode.testId` reports.
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
web({ testIdAttribute: 'data-test' }); // default: 'data-testid'
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### `userAgent`
|
|
307
|
+
|
|
308
|
+
The `User-Agent` every attempt's context sends and `navigator.userAgent`
|
|
309
|
+
reports, for an app that switches into a test mode on a marker in the agent
|
|
310
|
+
string.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
web({ userAgent: 'Mozilla/5.0 e2e' }); // default: the browser's own
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### `locale`
|
|
317
|
+
|
|
318
|
+
The locale every attempt's context runs in, a BCP 47 tag: what
|
|
319
|
+
`navigator.language`, `Intl` formatting, and the `Accept-Language` header
|
|
320
|
+
report.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
web({ locale: 'de-DE' }); // default: the browser's own
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### `timezoneId`
|
|
327
|
+
|
|
328
|
+
The IANA time zone every attempt's context runs in: what `Date` and `Intl`
|
|
329
|
+
resolve local time against.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
web({ timezoneId: 'Europe/Berlin' }); // default: the machine's
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### Media emulation
|
|
336
|
+
|
|
337
|
+
`web({ colorScheme, reducedMotion })` sets the media features every page of
|
|
338
|
+
every attempt reports, as Playwright's context options of the same names do.
|
|
339
|
+
`browser.emulateMedia(media)` changes them for one attempt.
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
web({ colorScheme: 'dark', reducedMotion: 'reduce' }); // default: the browser's own
|
|
343
|
+
await browser.emulateMedia({ colorScheme: 'light' });
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- The page's `@media` rules and `matchMedia` read them from the first paint.
|
|
347
|
+
- A persistent context takes the options too: the engine sets them on each
|
|
348
|
+
page it opens.
|
|
349
|
+
- `emulateMedia` applies for the rest of the attempt, on every open tab and
|
|
350
|
+
every tab opened after them, through `app.restart()` and `app.clearState()`.
|
|
351
|
+
- It works before the first navigation, so the next `app.open` loads with
|
|
352
|
+
them.
|
|
353
|
+
- A feature it leaves out keeps its value.
|
|
354
|
+
- A tab the app opens itself gets a test's change as it appears, on a
|
|
355
|
+
persistent context too, so a script it runs at load may still read the
|
|
356
|
+
configured values.
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
/** User preferences a page reads through `@media` rules and `matchMedia`. */
|
|
360
|
+
export interface WebMedia {
|
|
361
|
+
/** `prefers-color-scheme`. */
|
|
362
|
+
readonly colorScheme?: 'light' | 'dark' | 'no-preference';
|
|
363
|
+
/** `prefers-reduced-motion`. */
|
|
364
|
+
readonly reducedMotion?: 'reduce' | 'no-preference';
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
| Throws | When |
|
|
369
|
+
| --- | --- |
|
|
370
|
+
| `INVALID_CONFIG` | A feature or value outside `WebMedia`, in `web()`. |
|
|
371
|
+
| `INVALID_ARGUMENT` | A feature or value outside `WebMedia`, in `emulateMedia`. |
|
|
372
|
+
|
|
373
|
+
#### Init scripts
|
|
374
|
+
|
|
375
|
+
An init script runs in every document before the page's own scripts, in every
|
|
376
|
+
tab and frame. `web({ initScripts })` applies to every test;
|
|
377
|
+
`browser.addInitScript` adds one for the rest of the test.
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
await browser.addInitScript((value) => { Math.random = () => value; }, 0.5);
|
|
381
|
+
await app.open('/raffle');
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
- A function cannot close over test variables. Pass a JSON `arg` instead.
|
|
385
|
+
- A script added after the page opened runs from the next navigation or
|
|
386
|
+
reload.
|
|
387
|
+
- `browser.addInitScript` takes the same three forms, or a function with its
|
|
388
|
+
JSON `arg`. Its overloads are on [`Browser`](#browser-interface).
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
/** JavaScript source, a file relative to the project root, or a function. */
|
|
392
|
+
export type WebInitScript = string | { readonly path: string } | (() => unknown);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### The browser fixture
|
|
396
|
+
|
|
397
|
+
`browser` is the whole browser, showing one active tab.
|
|
398
|
+
|
|
399
|
+
- What reads or acts on a page acts on the active tab: `goto`, `reload`,
|
|
400
|
+
`back`, `forward`, `url`, `title`, `waitForURL`, `locator`, `frameLocator`,
|
|
401
|
+
`evaluate`, `waitForResponse`, `waitForDownload`, `keyboard`, `mouse`, the
|
|
402
|
+
`expect(browser)` matchers, and every `screen` query.
|
|
403
|
+
- What belongs to the browser covers every tab: `cookies`, `setCookies`,
|
|
404
|
+
`route`, `unroute`, `onDialog`, and `addInitScript`.
|
|
405
|
+
- `setViewport` sizes the active tab and every tab opened after it, and
|
|
406
|
+
`emulateMedia` sets its media the same way.
|
|
407
|
+
- A tab the app opens itself, from a `target="_blank"` link or `window.open`,
|
|
408
|
+
is not followed. The active tab stays the one the attempt opened. Assert on
|
|
409
|
+
what that tab shows, or open the link's URL with `browser.goto`.
|
|
410
|
+
- Every `browser` call is a recorded `browser.<method>` step, except
|
|
411
|
+
`locator` and `frameLocator`. They build a locator without touching the
|
|
412
|
+
page, and the action or assertion on it is the step.
|
|
413
|
+
- A relative URL resolves against the target's `app.url`, else
|
|
414
|
+
`APP_URL_REQUIRED`.
|
|
415
|
+
- Declare `requires: ['browser']` in a suite that also runs on other
|
|
416
|
+
platforms.
|
|
417
|
+
- `expect(browser)` returns the `BrowserExpectation` the engine attaches:
|
|
418
|
+
`toHaveURL`, `toHaveTitle`, and `toHaveClass`. See
|
|
419
|
+
[browser matchers](/reference/expect#browser-matchers).
|
|
420
|
+
|
|
421
|
+
| Member | Returns |
|
|
422
|
+
| --- | --- |
|
|
423
|
+
| [`goto`](#goto) | `Promise<void>` |
|
|
424
|
+
| [`reload`](#reload-back-forward) | `Promise<void>` |
|
|
425
|
+
| [`back`](#reload-back-forward) | `Promise<void>` |
|
|
426
|
+
| [`forward`](#reload-back-forward) | `Promise<void>` |
|
|
427
|
+
| [`url`](#url-title) | `Promise<string>` |
|
|
428
|
+
| [`title`](#url-title) | `Promise<string>` |
|
|
429
|
+
| [`waitForURL`](#waitforurl) | `Promise<void>` |
|
|
430
|
+
| [`locator`](#locator) | `Locator` |
|
|
431
|
+
| [`frameLocator`](#framelocator) | `FrameScreen` |
|
|
432
|
+
| [`evaluate`](#evaluate) | `Promise<T>` |
|
|
433
|
+
| [`addInitScript`](#init-scripts) | `Promise<void>` |
|
|
434
|
+
| [`route`](#route) | `Promise<void>` |
|
|
435
|
+
| [`unroute`](#unroute) | `Promise<void>` |
|
|
436
|
+
| [`waitForResponse`](#waitforresponse) | `Promise<WebResponse>` |
|
|
437
|
+
| [`cookies`](#cookies) | `Promise<Cookie[]>` |
|
|
438
|
+
| [`setCookies`](#setcookies) | `Promise<void>` |
|
|
439
|
+
| [`setViewport`](#setviewport) | `Promise<void>` |
|
|
440
|
+
| [`emulateMedia`](#media-emulation) | `Promise<void>` |
|
|
441
|
+
| [`onDialog`](#ondialog) | `Promise<() => Promise<void>>` |
|
|
442
|
+
| [`waitForDownload`](#waitfordownload) | `Promise<{ path, absolutePath, … }>` |
|
|
443
|
+
| [`keyboard`](#keyboard) | `{ press, down, up, type }` |
|
|
444
|
+
| [`mouse`](#mouse) | `{ move, wheel, down, up }` |
|
|
445
|
+
|
|
446
|
+
The [config](/reference/config) sets each member's budget:
|
|
447
|
+
|
|
448
|
+
- `goto`, `reload`, `back`, and `forward`: [`timeout`](/reference/config#timeout),
|
|
449
|
+
the same budget `app.open()` uses.
|
|
450
|
+
- `waitForURL`: [`assertionTimeout`](/reference/config#assertiontimeout).
|
|
451
|
+
- Every other member: [`actionTimeout`](/reference/config#actiontimeout).
|
|
452
|
+
`locator` and `frameLocator` touch no page and take none.
|
|
453
|
+
|
|
454
|
+
A `{ timeout }` option overrides the budget on `goto`, `reload`, `back`,
|
|
455
|
+
`forward`, `waitForResponse`, and `waitForDownload`, capped by the test
|
|
456
|
+
deadline. `url`, `title`, `evaluate`, `addInitScript`, `route`, `unroute`,
|
|
457
|
+
`cookies`, `setCookies`, `setViewport`, `emulateMedia`, `onDialog`,
|
|
458
|
+
`keyboard.*`, and `mouse.*` take no override.
|
|
459
|
+
|
|
460
|
+
#### `goto`
|
|
461
|
+
|
|
462
|
+
Navigates the active tab to a URL.
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
await browser.goto('/dashboard', { waitUntil: 'networkidle' }); // default: 'load'
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
`waitUntil` is `'load'`, `'domcontentloaded'`, or `'networkidle'`.
|
|
469
|
+
|
|
470
|
+
| Throws | When |
|
|
471
|
+
| --- | --- |
|
|
472
|
+
| `POLICY_DENIED` | A URL whose scheme is not `http:` or `https:`. `about:blank` is admitted. |
|
|
473
|
+
| `APP_URL_REQUIRED` | A relative URL, on a target that declares no `app.url`. |
|
|
474
|
+
| `ACTION_FAILED` | The navigation failed. |
|
|
475
|
+
|
|
476
|
+
#### `reload`, `back`, `forward`
|
|
477
|
+
|
|
478
|
+
`reload` reloads the current document. `back` and `forward` navigate browser
|
|
479
|
+
history once.
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
await browser.reload();
|
|
483
|
+
await browser.back({ timeout: 10_000 });
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
| Throws | When |
|
|
487
|
+
| --- | --- |
|
|
488
|
+
| `ACTION_FAILED` | The navigation failed. |
|
|
489
|
+
|
|
490
|
+
#### `url`, `title`
|
|
491
|
+
|
|
492
|
+
Return the active tab's current URL and title.
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
const url = await browser.url();
|
|
496
|
+
const title = await browser.title();
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### `waitForURL`
|
|
500
|
+
|
|
501
|
+
Waits for the current URL to match, polling every 100 ms. It is the same poll
|
|
502
|
+
as `expect(browser).toHaveURL`.
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
await browser.waitForURL(/\/orders\/\d+$/, { timeout: 15_000 });
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
- A relative string resolves against the base URL and matches exactly after
|
|
509
|
+
normalization.
|
|
510
|
+
- A `RegExp` tests the full URL.
|
|
511
|
+
|
|
512
|
+
| Throws | When |
|
|
513
|
+
| --- | --- |
|
|
514
|
+
| `INVALID_ARGUMENT` | A pattern that is not a string or `RegExp` (a predicate function, say), or an option other than `timeout`, such as Playwright's `waitUntil`. |
|
|
515
|
+
| `ASSERTION_FAILED` | The URL did not match before the timeout. |
|
|
516
|
+
|
|
517
|
+
#### `locator`
|
|
518
|
+
|
|
519
|
+
Creates a web-only locator from a Playwright selector.
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
const conditions = browser.locator(
|
|
523
|
+
'[data-testid="row"] >> nth=2 >> [data-testid="conditions"] > div',
|
|
524
|
+
);
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
- The selector is CSS by default, XPath with `xpath=` or a leading `//`, and
|
|
528
|
+
takes `>>` chains, so the example scopes CSS to one row.
|
|
529
|
+
- Use `filter({ has })` for an element that contains another.
|
|
530
|
+
|
|
531
|
+
| Throws | When |
|
|
532
|
+
| --- | --- |
|
|
533
|
+
| `INVALID_LOCATOR` | An empty selector, or a `*` capture before the last part (`*css=article >> text=Hello`). |
|
|
534
|
+
|
|
535
|
+
<Note>
|
|
536
|
+
Every `screen` query searches its scope and each closed shadow root the page
|
|
537
|
+
attached under it, with Playwright's own role, name, text, label, and
|
|
538
|
+
attribute matching and each node found once. A control the observed tree
|
|
539
|
+
lists inside a closed root resolves and takes actions and reads. `locator`,
|
|
540
|
+
`frameLocator`, and `filter({ hasText })` keep Playwright's reach and stop at
|
|
541
|
+
a closed root; the [browser guide](/browser#shadow-dom) has the details.
|
|
542
|
+
</Note>
|
|
543
|
+
|
|
544
|
+
#### `frameLocator`
|
|
545
|
+
|
|
546
|
+
Creates a screen query scope inside one iframe.
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
const payment = browser.frameLocator('#payment-frame');
|
|
550
|
+
await payment.getByLabel('Card number').fill('4242424242424242');
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
It returns a `FrameScreen`, a `Screen` whose queries answer from inside the
|
|
554
|
+
frame. Use the frame's `locator` only when the control has no accessible
|
|
555
|
+
name, label, placeholder, or test id. The
|
|
556
|
+
[browser guide](/browser#reach-into-an-iframe) shows when that applies.
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
export interface FrameScreen extends Screen {
|
|
560
|
+
/** Creates a web-only CSS or XPath locator inside this frame. */
|
|
561
|
+
locator(selector: string): Locator;
|
|
562
|
+
/** Creates a screen query scope inside an iframe nested in this frame. */
|
|
563
|
+
frameLocator(selector: string): FrameScreen;
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
The errors are the same at the page and inside a frame:
|
|
568
|
+
|
|
569
|
+
| Throws | When |
|
|
570
|
+
| --- | --- |
|
|
571
|
+
| `INVALID_LOCATOR` | An empty selector, here or in the frame's `locator`. |
|
|
572
|
+
| `LOCATOR_NOT_FOUND` | No frame matches. |
|
|
573
|
+
| `LOCATOR_AMBIGUOUS` | Several frames match. |
|
|
574
|
+
|
|
575
|
+
#### `evaluate`
|
|
576
|
+
|
|
577
|
+
Evaluates trusted test code in the page and returns its result.
|
|
578
|
+
|
|
579
|
+
```ts
|
|
580
|
+
const count = await browser.evaluate(() => document.querySelectorAll('li').length);
|
|
581
|
+
const flag = await browser.evaluate(
|
|
582
|
+
(name: string) => window.localStorage.getItem(name),
|
|
583
|
+
'featureFlags',
|
|
584
|
+
);
|
|
585
|
+
const title = await browser.evaluate<string>('document.title');
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
- The function runs in the page and cannot access variables from the test.
|
|
589
|
+
e2e serializes it by its source and inlines it into the page, so it must
|
|
590
|
+
not close over anything outside its own body. Helpers declared inside the
|
|
591
|
+
body work. Pass outside values through `arg`.
|
|
592
|
+
- A string is evaluated as a single expression, not as statements. If it
|
|
593
|
+
evaluates to a function, that function is called with `arg`. Else its
|
|
594
|
+
value is the result.
|
|
595
|
+
- A promise, from either, is awaited.
|
|
596
|
+
|
|
597
|
+
| Throws | When |
|
|
598
|
+
| --- | --- |
|
|
599
|
+
| `INVALID_ARGUMENT` | `arg` or the return value is not a [`JsonValue`](#jsonvalue). |
|
|
600
|
+
| `EVALUATE_FAILED` | The evaluated code threw. The error carries its message. |
|
|
601
|
+
| `ACTION_FAILED` | A Playwright timeout. |
|
|
602
|
+
| `ENGINE_FAILURE` | The page closed or the context was lost. |
|
|
603
|
+
|
|
604
|
+
#### `route`
|
|
605
|
+
|
|
606
|
+
Adds an attempt-scoped network route.
|
|
607
|
+
|
|
608
|
+
```ts
|
|
609
|
+
await browser.route('**/api/quote', async (route) => {
|
|
610
|
+
await route.fulfill({ json: { cents: 4200 } });
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
- The pattern is a string or a `RegExp`, both tested against the full URL. In
|
|
615
|
+
a string, `*` matches within a path segment, `**` across `/`, `?` any one
|
|
616
|
+
character, and `\` escapes the next. `unroute` and `waitForResponse` take
|
|
617
|
+
the same patterns.
|
|
618
|
+
- A route survives `app.restart()`, `app.clearState()`, and session restore.
|
|
619
|
+
- Routes run newest first.
|
|
620
|
+
- The handler must decide exactly once, with `fulfill`, `continue`,
|
|
621
|
+
`fallback`, or `abort`, each described on `WebRoute` below.
|
|
622
|
+
- A `continue` URL goes through the same rule as `goto`.
|
|
623
|
+
- A handler that throws (a failed `expect` on the request, say) fails the
|
|
624
|
+
next step with that error, or the attempt at its end when no step follows.
|
|
625
|
+
A handler still running then gets the cleanup budget to finish. Whatever it
|
|
626
|
+
throws after the budget is dropped.
|
|
627
|
+
|
|
628
|
+
| Throws | When |
|
|
629
|
+
| --- | --- |
|
|
630
|
+
| `INVALID_ARGUMENT` | A predicate function as the pattern. Also an option a decision does not take (Playwright's `abort(errorCode)`, a misspelled key): the request is aborted and the next step fails. |
|
|
631
|
+
| `ACTION_FAILED` | The handler decided twice or not at all. |
|
|
632
|
+
| `POLICY_DENIED` | A `continue` URL with a forbidden scheme. |
|
|
633
|
+
| `CLEANUP_TIMEOUT` | A handler outlived the cleanup budget. It fails the attempt. |
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
/**
|
|
637
|
+
* One of `json`, `body`, or `path`, or none for an empty body. `path` names a
|
|
638
|
+
* file relative to the project root; its `Content-Type` follows its
|
|
639
|
+
* extension unless `contentType` or a `content-type` header sets one.
|
|
640
|
+
*/
|
|
641
|
+
export type RouteFulfillResponse = {
|
|
642
|
+
status?: number;
|
|
643
|
+
headers?: Record<string, string>;
|
|
644
|
+
/** Sets the `Content-Type` response header. */
|
|
645
|
+
contentType?: string;
|
|
646
|
+
} & (
|
|
647
|
+
| { json: JsonValue; body?: never; path?: never }
|
|
648
|
+
| { body: string; json?: never; path?: never }
|
|
649
|
+
| { path: string; json?: never; body?: never }
|
|
650
|
+
| { body?: never; json?: never; path?: never }
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
/** What `continue` changes about the request before it goes to the network. */
|
|
654
|
+
export interface RouteContinueOverrides {
|
|
655
|
+
/** Resolved against the base URL like `goto`; must keep the request's scheme. */
|
|
656
|
+
url?: string;
|
|
657
|
+
/** Request method. */
|
|
658
|
+
method?: string;
|
|
659
|
+
/** Replaces every request header; the configured `headers` for the app's site are added on top. */
|
|
660
|
+
headers?: Record<string, string>;
|
|
661
|
+
/** Request body. */
|
|
662
|
+
postData?: string;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export interface WebRoute {
|
|
666
|
+
/** The intercepted request. */
|
|
667
|
+
readonly request: {
|
|
668
|
+
readonly url: string;
|
|
669
|
+
readonly method: string;
|
|
670
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
671
|
+
readonly postData?: string;
|
|
672
|
+
};
|
|
673
|
+
/** Fulfills the intercepted request once. */
|
|
674
|
+
fulfill(response: RouteFulfillResponse): Promise<void>;
|
|
675
|
+
/** Sends the intercepted request to the network once, skipping every other route. */
|
|
676
|
+
continue(overrides?: RouteContinueOverrides): Promise<void>;
|
|
677
|
+
/** Hands the intercepted request to the route registered before this one, or the network when none matches. */
|
|
678
|
+
fallback(): Promise<void>;
|
|
679
|
+
/** Aborts the intercepted request once. */
|
|
680
|
+
abort(): Promise<void>;
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
#### `unroute`
|
|
685
|
+
|
|
686
|
+
Removes the attempt-scoped routes that match a pattern, in the
|
|
687
|
+
[`route`](#route) pattern syntax.
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
await browser.unroute('**/api/quote');
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
| Throws | When |
|
|
694
|
+
| --- | --- |
|
|
695
|
+
| `INVALID_ARGUMENT` | A predicate function as the pattern. |
|
|
696
|
+
|
|
697
|
+
#### `waitForResponse`
|
|
698
|
+
|
|
699
|
+
Waits for a response that matches a pattern, in the [`route`](#route) pattern
|
|
700
|
+
syntax.
|
|
701
|
+
|
|
702
|
+
```ts
|
|
703
|
+
const response = await browser.waitForResponse('**/api/orders');
|
|
704
|
+
expect(response.status).toBe(201);
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
- The deadline bounds the match: the response resolves as soon as its headers
|
|
708
|
+
arrive.
|
|
709
|
+
- `text()` and `json()` wait for the body on a budget of their own,
|
|
710
|
+
`config.actionTimeout` capped by the test deadline.
|
|
711
|
+
- `url`, `status`, and `headers` are always readable.
|
|
712
|
+
- A genuinely empty body reads as `''`.
|
|
713
|
+
|
|
714
|
+
| Throws | When |
|
|
715
|
+
| --- | --- |
|
|
716
|
+
| `INVALID_ARGUMENT` | A predicate function as the pattern. |
|
|
717
|
+
| `ACTION_FAILED` | Nothing matches inside the deadline. From `text()` and `json()`: a body still streaming when their budget runs out, or a body the browser could not read (a connection reset short of the declared `Content-Length`, a request the page aborted, or a redirect, whose body the browser never keeps), instead of an empty string. |
|
|
718
|
+
|
|
719
|
+
```ts
|
|
720
|
+
export interface WebResponse {
|
|
721
|
+
/** Response URL. */
|
|
722
|
+
readonly url: string;
|
|
723
|
+
/** HTTP status. */
|
|
724
|
+
readonly status: number;
|
|
725
|
+
/** Response headers, lower-cased names. */
|
|
726
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
727
|
+
/** Waits for the body and parses it as JSON. */
|
|
728
|
+
json<T = unknown>(): Promise<T>;
|
|
729
|
+
/** Waits for the body and reads it as text. */
|
|
730
|
+
text(): Promise<string>;
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
#### `cookies`
|
|
735
|
+
|
|
736
|
+
Returns the cookies visible to the current context.
|
|
737
|
+
|
|
738
|
+
```ts
|
|
739
|
+
const cookies = await browser.cookies();
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
#### `setCookies`
|
|
743
|
+
|
|
744
|
+
Validates every cookie, then sets them.
|
|
745
|
+
|
|
746
|
+
```ts
|
|
747
|
+
await browser.setCookies([{ url: 'http://localhost:3000', name: 'tz', value: 'UTC' }]);
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
- A relative `url` resolves against the base URL, as `goto` resolves it.
|
|
751
|
+
- For the `domain` form the scheme follows the base URL, and a leading `.` is
|
|
752
|
+
stripped.
|
|
753
|
+
|
|
754
|
+
| Throws | When |
|
|
755
|
+
| --- | --- |
|
|
756
|
+
| `POLICY_DENIED` | An unparseable target or a forbidden scheme. Nothing is set. |
|
|
757
|
+
|
|
758
|
+
```ts
|
|
759
|
+
export interface CookieFields {
|
|
760
|
+
/** Cookie name. */
|
|
761
|
+
name: string;
|
|
762
|
+
/** Cookie value. */
|
|
763
|
+
value: string;
|
|
764
|
+
/** Unix timestamp in whole seconds. */
|
|
765
|
+
expires?: number;
|
|
766
|
+
/** HttpOnly flag. */
|
|
767
|
+
httpOnly?: boolean;
|
|
768
|
+
/** Secure flag. */
|
|
769
|
+
secure?: boolean;
|
|
770
|
+
/** SameSite attribute. */
|
|
771
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/** `url`, or `domain` with an optional `path`, never both. */
|
|
775
|
+
export type Cookie = CookieFields &
|
|
776
|
+
(
|
|
777
|
+
| { url: string; domain?: never; path?: never }
|
|
778
|
+
| { url?: never; domain: string; path?: string }
|
|
779
|
+
);
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
#### `setViewport`
|
|
783
|
+
|
|
784
|
+
Sets the viewport size and records the new size on the report.
|
|
785
|
+
|
|
786
|
+
```ts
|
|
787
|
+
await browser.setViewport({ width: 390, height: 844 });
|
|
788
|
+
await app.open('/');
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
- It works before the first navigation too, as Playwright's
|
|
792
|
+
`setViewportSize` does. The next `app.open` loads at that size, so a
|
|
793
|
+
phone-sized test never renders the desktop layout first.
|
|
794
|
+
- The size holds for the rest of the attempt, through `app.restart()` and
|
|
795
|
+
`app.clearState()`.
|
|
796
|
+
|
|
797
|
+
#### `onDialog`
|
|
798
|
+
|
|
799
|
+
Registers an attempt-scoped handler for native dialogs and returns an
|
|
800
|
+
unsubscribe function.
|
|
801
|
+
|
|
802
|
+
```ts
|
|
803
|
+
const off = await browser.onDialog('accept');
|
|
804
|
+
await screen.getByRole('button', 'Delete').tap();
|
|
805
|
+
await off();
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
- The handler is `'accept'`, `'dismiss'`, or a function that receives the
|
|
809
|
+
`Dialog`.
|
|
810
|
+
- A function handler must call `accept` or `dismiss`. One that does neither
|
|
811
|
+
has the dialog dismissed, as an unhandled dialog does.
|
|
812
|
+
- A handler that throws fails the next step with its error, or the attempt
|
|
813
|
+
at its end when no step follows. One that throws before answering has its
|
|
814
|
+
dialog dismissed.
|
|
815
|
+
|
|
816
|
+
The errors surface on the next step, not on `onDialog`:
|
|
817
|
+
|
|
818
|
+
| Throws | When |
|
|
819
|
+
| --- | --- |
|
|
820
|
+
| `APP_NOT_OPEN` | A function handler answered neither way, or no handler took the dialog. |
|
|
821
|
+
| `ASSERTION_FAILED` | The handler's `expect` failed. |
|
|
822
|
+
| `ENGINE_FAILURE` | The handler threw an unclassified error. |
|
|
823
|
+
|
|
824
|
+
```ts
|
|
825
|
+
/** A native dialog as a test's handler sees it. */
|
|
826
|
+
export interface Dialog {
|
|
827
|
+
/** The dialog's text. */
|
|
828
|
+
readonly message: string;
|
|
829
|
+
/** Accepts the dialog once. */
|
|
830
|
+
accept(text?: string): Promise<void>;
|
|
831
|
+
/** Dismisses the dialog once. */
|
|
832
|
+
dismiss(): Promise<void>;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
export type DialogHandler = 'accept' | 'dismiss' | ((dialog: Dialog) => void | Promise<void>);
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
#### `waitForDownload`
|
|
839
|
+
|
|
840
|
+
Runs a trigger and waits for the download it starts.
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
const file = await browser.waitForDownload(() =>
|
|
844
|
+
screen.getByRole('link', 'Export CSV').tap(),
|
|
845
|
+
);
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
- It registers the file as a `download` artifact and returns its relative
|
|
849
|
+
`path`, the runner-side `absolutePath`, and `suggestedFilename`.
|
|
850
|
+
- Use `absolutePath` with file APIs to inspect the contents, including on a
|
|
851
|
+
hosted browser.
|
|
852
|
+
- On a hosted browser the file comes back through the provider's
|
|
853
|
+
[`downloads`](/browser#downloads-on-the-provider).
|
|
854
|
+
- The wait includes the trigger's own time.
|
|
855
|
+
|
|
856
|
+
| Throws | When |
|
|
857
|
+
| --- | --- |
|
|
858
|
+
| `ACTION_FAILED` | The download did not complete in time. A trigger that resolved in time but started no download fails with `no download started within <n>ms; the trigger resolved after <m>ms`. |
|
|
859
|
+
|
|
860
|
+
A trigger still running at the timeout fails with the step timeout. One that
|
|
861
|
+
throws fails with its own error.
|
|
862
|
+
|
|
863
|
+
#### `keyboard`
|
|
864
|
+
|
|
865
|
+
Viewport-level keyboard input, for whatever has focus.
|
|
866
|
+
|
|
867
|
+
```ts
|
|
868
|
+
await browser.keyboard.press('Escape');
|
|
869
|
+
await browser.keyboard.type('hello');
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
- `type(text)` types plain text only, never a `Secret`.
|
|
873
|
+
- Prefer `locator.press` and `locator.fill` when a target exists.
|
|
874
|
+
- [`Browser`](#browser-interface) describes each method.
|
|
875
|
+
|
|
876
|
+
#### `mouse`
|
|
877
|
+
|
|
878
|
+
Viewport-level pointer input from test code, independent of agent
|
|
879
|
+
observations.
|
|
880
|
+
|
|
881
|
+
```ts
|
|
882
|
+
await browser.mouse.move(120, 240);
|
|
883
|
+
await browser.mouse.down();
|
|
884
|
+
await browser.mouse.move(320, 240);
|
|
885
|
+
await browser.mouse.up();
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
[`Browser`](#browser-interface) describes each method, `wheel` included.
|
|
889
|
+
|
|
27
890
|
## Installing browsers
|
|
28
891
|
|
|
892
|
+
The package pins `playwright-core` to an exact version, so the engine always
|
|
893
|
+
runs the Playwright it was tested against. An app that depends on Playwright
|
|
894
|
+
itself keeps its own copy. The two share a browser cache only when their
|
|
895
|
+
versions match.
|
|
896
|
+
|
|
29
897
|
A missing browser downloads once per run, before the run's clock starts. To
|
|
30
898
|
install browsers as their own step, in CI or a container image, run the
|
|
31
|
-
package's command. It installs the browsers for the
|
|
32
|
-
|
|
899
|
+
package's command. It installs the browsers for the engine's pinned
|
|
900
|
+
Playwright:
|
|
33
901
|
|
|
34
902
|
<CodeGroup>
|
|
35
903
|
```bash npm
|
|
@@ -45,11 +913,79 @@ bunx @e2e-dev/web install chromium --with-deps
|
|
|
45
913
|
```
|
|
46
914
|
</CodeGroup>
|
|
47
915
|
|
|
48
|
-
Name any of `chromium`, `firefox`, and `webkit
|
|
49
|
-
`chromium`.
|
|
50
|
-
|
|
916
|
+
- Name any of `chromium`, `firefox`, and `webkit`. With none named it
|
|
917
|
+
installs `chromium`.
|
|
918
|
+
- `--with-deps` also installs the system libraries the browsers need on
|
|
919
|
+
Linux.
|
|
920
|
+
- The command exits with the installer's exit code.
|
|
921
|
+
|
|
922
|
+
## Semantic capture recovery
|
|
923
|
+
|
|
924
|
+
When the semantic reader times out, agent observations can recover with a
|
|
925
|
+
fresh viewport screenshot.
|
|
926
|
+
|
|
927
|
+
- Playwright runs a new secure-field mask sweep across every reachable frame,
|
|
928
|
+
including open and closed shadow roots. Every mask probe must succeed.
|
|
929
|
+
- The observation reports unavailable semantics and clears earlier node
|
|
930
|
+
references.
|
|
931
|
+
- Recovery requires time left in the operation budget and permission from the
|
|
932
|
+
runner. `act` permits it before a secret fill; judgments also require
|
|
933
|
+
`vision: true` or `'only'`.
|
|
934
|
+
- Failed masking, cancellation, and hard engine errors still stop the
|
|
935
|
+
observation.
|
|
936
|
+
|
|
937
|
+
When fallback is permitted, the reader reserves one quarter of the remaining
|
|
938
|
+
observation budget, capped at two seconds, for the screenshot. The deadline
|
|
939
|
+
covers the page walk, metadata and element readback, and nested frames.
|
|
940
|
+
|
|
941
|
+
Semantic metadata crosses the Playwright connection as one JSON string, to
|
|
942
|
+
cut serialization overhead. Actions still use the live element handles
|
|
943
|
+
captured with that metadata. A page that replaces `JSON.stringify`, or adds
|
|
944
|
+
`toJSON` methods to object or array prototypes, gets structured transfer.
|
|
945
|
+
Cleanup does not wait for stalled protocol replies. An abandoned reader
|
|
946
|
+
cannot reuse node IDs or publish handles over a newer observation.
|
|
947
|
+
|
|
948
|
+
## surfaceOf
|
|
949
|
+
|
|
950
|
+
Returns the live Playwright surface of a `web()` handle, for a
|
|
951
|
+
[custom executor](/executors#driving-the-page-yourself) that drives the page
|
|
952
|
+
itself.
|
|
953
|
+
|
|
954
|
+
```ts
|
|
955
|
+
import { web, surfaceOf } from '@e2e-dev/web';
|
|
956
|
+
|
|
957
|
+
const engine = web();
|
|
958
|
+
const live = surfaceOf(engine); // PlaywrightLiveSurface | undefined
|
|
959
|
+
live!.page(); // the Page of the current attempt
|
|
960
|
+
live!.context(); // its BrowserContext
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
- It returns `undefined` for a handle from another package.
|
|
964
|
+
- Direct Playwright calls are not recorded as steps.
|
|
965
|
+
- After [CDP recovery](#recovering-a-cdp-transport), read the accessors
|
|
966
|
+
again.
|
|
967
|
+
|
|
968
|
+
| Throws | When |
|
|
969
|
+
| --- | --- |
|
|
970
|
+
| `INVALID_STATE` | An accessor called outside an active attempt. |
|
|
971
|
+
|
|
972
|
+
## Types
|
|
973
|
+
|
|
974
|
+
The package exports these types for typing config helpers and fixtures:
|
|
975
|
+
`WebOptions`, `WebMedia`, `WebScreencastOptions`, `WebConnectOptions`,
|
|
976
|
+
`WebBasicAuth`, `WebInitScript`, `BrowserName`, `BrowserProvider`,
|
|
977
|
+
`BrowserProviderScope`, `BrowserRequest`, `BrowserLease`,
|
|
978
|
+
`BrowserReleaseContext`, `BrowserProviderDownloads`,
|
|
979
|
+
`BrowserDownloadContext`, `PlaywrightLiveSurface`, `Browser`, `FrameScreen`,
|
|
980
|
+
`BrowserExpectation`, `WebRoute`, `RouteFulfillResponse`,
|
|
981
|
+
`RouteContinueOverrides`, `WebResponse`, `Cookie`, `CookieFields`, `Dialog`,
|
|
982
|
+
and `DialogHandler`.
|
|
51
983
|
|
|
52
|
-
|
|
984
|
+
The runtime exports are `web`, [`surfaceOf`](#surfaceof), and `test`,
|
|
985
|
+
`describe`, `beforeEach`, `afterEach`, `beforeAll`, and `afterAll`, typed
|
|
986
|
+
with the `browser` fixture.
|
|
987
|
+
|
|
988
|
+
### `WebOptions`
|
|
53
989
|
|
|
54
990
|
```ts
|
|
55
991
|
/**
|
|
@@ -132,137 +1068,9 @@ export interface WebOptions {
|
|
|
132
1068
|
/** Scripts every document runs before the page's own. See [Init scripts](#init-scripts). */
|
|
133
1069
|
readonly initScripts?: readonly WebInitScript[];
|
|
134
1070
|
}
|
|
135
|
-
|
|
136
|
-
/** JavaScript source, a file relative to the project root, or a function. */
|
|
137
|
-
export type WebInitScript = string | { readonly path: string } | (() => unknown);
|
|
138
|
-
|
|
139
|
-
/** The engine's screencast of the page: the frame size and the JPEG quality of the frames it encodes. */
|
|
140
|
-
export interface WebScreencastOptions {
|
|
141
|
-
/** Frame size in pixels; default the viewport's, or the window's under `viewport: null`. A smaller size makes a smaller file. */
|
|
142
|
-
readonly size?: ViewportSize;
|
|
143
|
-
/** JPEG quality of each captured frame, 0 through 100; Playwright's default when unset. */
|
|
144
|
-
readonly quality?: number;
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
The app a web target opens is the target's `app`, not an engine option:
|
|
149
|
-
`targets: [{ engine: web(), app: { url } }]`, documented under
|
|
150
|
-
[the app under test](/reference/config#the-app-under-test). A `web()` target
|
|
151
|
-
needs `app.url` and refuses the device keys (`bundleId`, `appPath`,
|
|
152
|
-
`launchArguments`, `permissions`); `web({ url })` or any other app key on
|
|
153
|
-
`web()` is an unknown key, `INVALID_CONFIG`.
|
|
154
|
-
A target with `app.url` is a `browser` target, so `requires: ['browser']`
|
|
155
|
-
selects web targets.
|
|
156
|
-
|
|
157
|
-
`connect` needs chromium; `cdpEndpoint(signal)` is called once per worker and
|
|
158
|
-
again at an attempt start after a disconnect. A `browser` provider object leases hosted
|
|
159
|
-
browsers and releases them when the run or the attempt ends; see
|
|
160
|
-
[Hosted browsers](/browser#hosted-browsers) for the `BrowserProvider`
|
|
161
|
-
contract and an example provider. `browser: provider` together with
|
|
162
|
-
`connect` is `INVALID_CONFIG`, and so is an option `web()` does not take, or
|
|
163
|
-
an unknown key inside `connect` or `basicAuth`, naming the nearest known key
|
|
164
|
-
when it is a likely typo (`viewprt` is `viewport`) and every key otherwise. `:` is not allowed in the `basicAuth` user name.
|
|
165
|
-
The `basicAuth` password is a string or `secrets.get(name)`: a secret the
|
|
166
|
-
engine resolves when each attempt starts, redacted like any configured one,
|
|
167
|
-
and so is the base64 credential of the `Authorization` header it becomes.
|
|
168
|
-
That protection is text only: unlike a fill, it withholds no screenshot or
|
|
169
|
-
model pixels, so a page that renders the password on screen is not masked in
|
|
170
|
-
them ([Security rules](/reference/security#secrets-in-engine-options)).
|
|
171
|
-
|
|
172
|
-
`headers` and `basicAuth` are how a test reaches a [protected preview](/web#protected-previews).
|
|
173
|
-
|
|
174
|
-
`screencast` shapes the recording, not which attempts record: that is the
|
|
175
|
-
config's [`video`](/reference/config#video) mode. `size` takes whole pixels,
|
|
176
|
-
each side at least 1, and `quality` an integer from 0 through 100; anything
|
|
177
|
-
else, or another key, is `INVALID_CONFIG` at config load. The old
|
|
178
|
-
`web({ video })` spelling is `INVALID_CONFIG` naming `screencast`.
|
|
179
|
-
|
|
180
|
-
### Recovering a CDP transport
|
|
181
|
-
|
|
182
|
-
`connect.reconnectEndpoint(signal)` opts into recovery of the same remote
|
|
183
|
-
browser after its CDP transport disconnects. This mode uses the browser's
|
|
184
|
-
persistent default context because Chrome deletes ordinary Playwright contexts
|
|
185
|
-
when their transport detaches.
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
interface WebConnectOptions {
|
|
189
|
-
cdpEndpoint(signal: AbortSignal): string | Promise<string>;
|
|
190
|
-
reconnectEndpoint?(signal: AbortSignal): string | Promise<string>;
|
|
191
|
-
}
|
|
192
1071
|
```
|
|
193
1072
|
|
|
194
|
-
|
|
195
|
-
attempt start, including retries. The engine rejects a browser reused by a
|
|
196
|
-
previous attempt. The remote must have only its default context, and the host
|
|
197
|
-
must delete the browser when the attempt ends. Engine cleanup detaches its
|
|
198
|
-
connection; it does not terminate the host's browser.
|
|
199
|
-
|
|
200
|
-
After a disconnect, the next operation calls `reconnectEndpoint` once, bounded
|
|
201
|
-
by that operation's timeout and abort signal. This resolver must return the
|
|
202
|
-
existing browser's endpoint. The engine verifies the persistent default context
|
|
203
|
-
ID and the original page's target ID. It refuses browsers that do not expose
|
|
204
|
-
the default context ID. A missing page or a different context fails the attempt.
|
|
205
|
-
A URL match is insufficient to select a replacement tab.
|
|
206
|
-
The engine installs secure-field masking before publishing each CDP binding;
|
|
207
|
-
an incompatible Playwright version fails the connection instead of exposing pixels.
|
|
208
|
-
Recovery also requires the original page's frames to retain their closed shadow
|
|
209
|
-
DOM tracking hooks. If a document loaded without that hook while disconnected,
|
|
210
|
-
recovery fails with `ENGINE_FAILURE`: existing closed roots cannot be recovered
|
|
211
|
-
without reloading the application. Mask queries reject any untracked document.
|
|
212
|
-
|
|
213
|
-
Endpoint resolution, CDP attachment, setup, and dispatch share one timeout.
|
|
214
|
-
Exhausting it raises `OPERATION_TIMEOUT`; caller or attempt cancellation raises
|
|
215
|
-
`CANCELLED`. Cancelling a concurrent waiter does not cancel the operation that
|
|
216
|
-
started recovery. Cancelling that recovery owner fails the shared recovery.
|
|
217
|
-
|
|
218
|
-
Recovery clears old node references. Observation-derived `tapAt` calls,
|
|
219
|
-
observation-root swipes, and focused engine keyboard input require a fresh
|
|
220
|
-
engine observation before they resume.
|
|
221
|
-
An observation or locator read started before recovery cannot publish references
|
|
222
|
-
after the connection changes. Each attempt owns its binding and recordings, so
|
|
223
|
-
late cleanup cannot change a later attempt.
|
|
224
|
-
The deterministic `browser.mouse` and `browser.keyboard` fixtures remain available
|
|
225
|
-
without an agent observation. Test code owns its input and can read current
|
|
226
|
-
geometry and focus with `browser.evaluate` before acting.
|
|
227
|
-
|
|
228
|
-
Recovery preserves the page's unsaved state, storage, the attempt's secret
|
|
229
|
-
taint, and the agent ledger. Operations already dispatched
|
|
230
|
-
are never retried. Dialog and route handlers are reattached. Video recording
|
|
231
|
-
resumes, but a segment lost during the disconnect cannot be recovered.
|
|
232
|
-
Code using `surfaceOf(engine)` must read its accessors again after recovery;
|
|
233
|
-
previously retained Playwright objects belong to the disconnected transport.
|
|
234
|
-
|
|
235
|
-
Persistent recovery does not support `headers`, `basicAuth`, `userAgent`, `locale`, `timezoneId`, `app.clearState`,
|
|
236
|
-
or session state capture and restore. `app.restart` remains available. Omit
|
|
237
|
-
`reconnectEndpoint` to keep ordinary isolated contexts and the existing behavior.
|
|
238
|
-
A `browser` provider with `scope: 'attempt'` rides the same persistent
|
|
239
|
-
context, with the same limits: the lease's `cdpEndpoint` provisions the
|
|
240
|
-
attempt's browser and its `reconnectEndpoint` (else `cdpEndpoint` again)
|
|
241
|
-
reconnects to it. A lease is fresh by the provider's contract, so the engine
|
|
242
|
-
does not compare its context with earlier attempts': a hosted service may
|
|
243
|
-
restore browsers from one snapshot, which gives new browsers the same
|
|
244
|
-
context id.
|
|
245
|
-
|
|
246
|
-
The attempt's first page is the browser's own first tab, navigated to
|
|
247
|
-
`about:blank`, rather than a second tab beside it; a page opened after that,
|
|
248
|
-
such as the one `app.restart` opens, is a new tab.
|
|
249
|
-
|
|
250
|
-
## The active tab
|
|
251
|
-
|
|
252
|
-
`browser` is the whole browser, showing one active tab. What reads or acts on
|
|
253
|
-
a page acts on that tab: `goto`, `reload`, `back`, `forward`, `url`, `title`,
|
|
254
|
-
`waitForURL`, `locator`, `frameLocator`, `evaluate`, `waitForResponse`,
|
|
255
|
-
`waitForDownload`, `keyboard`, `mouse`, the `expect(browser)` matchers, and
|
|
256
|
-
every `screen` query. What belongs to the browser covers every tab:
|
|
257
|
-
`cookies`, `setCookies`, `route`, `unroute`, `onDialog`, and `addInitScript`. `setViewport`
|
|
258
|
-
sizes the active tab and every tab opened after it, and `emulateMedia` sets
|
|
259
|
-
its media the same way.
|
|
260
|
-
|
|
261
|
-
A tab the app opens itself, from a `target="_blank"` link or `window.open`,
|
|
262
|
-
is not followed: the active tab stays the one the attempt opened, so assert
|
|
263
|
-
on what that tab shows, or open the link's URL with `browser.goto`.
|
|
264
|
-
|
|
265
|
-
## The browser fixture
|
|
1073
|
+
### `Browser` interface
|
|
266
1074
|
|
|
267
1075
|
```ts
|
|
268
1076
|
/**
|
|
@@ -341,7 +1149,7 @@ export interface Browser extends Expectable<BrowserExpectation> {
|
|
|
341
1149
|
readonly keyboard: {
|
|
342
1150
|
/** Sends one key. */
|
|
343
1151
|
press(key: string): Promise<void>;
|
|
344
|
-
/** Holds
|
|
1152
|
+
/** Holds one key until up; repeated down calls send a repeat event. */
|
|
345
1153
|
down(key: string): Promise<void>;
|
|
346
1154
|
/** Releases a held key. */
|
|
347
1155
|
up(key: string): Promise<void>;
|
|
@@ -366,340 +1174,6 @@ export interface Browser extends Expectable<BrowserExpectation> {
|
|
|
366
1174
|
}
|
|
367
1175
|
```
|
|
368
1176
|
|
|
369
|
-
| Member | Default timeout |
|
|
370
|
-
| --- | --- |
|
|
371
|
-
| `goto`, `reload`, `back`, `forward` | `config.timeout`, 120000 ms, the same budget `app.open()` uses; `{ timeout }` overrides, capped by the test deadline |
|
|
372
|
-
| `waitForResponse`, `waitForDownload` | `config.actionTimeout`, 30000 ms; `{ timeout }` overrides, capped by the test deadline |
|
|
373
|
-
| `waitForURL` | `config.assertionTimeout`, 5000 ms |
|
|
374
|
-
| `url`, `title`, `evaluate`, `addInitScript`, `route`, `unroute`, `cookies`, `setCookies`, `setViewport`, `emulateMedia`, `onDialog`, `keyboard.*`, `mouse.*` | `config.actionTimeout`, 30000 ms, not overridable |
|
|
375
|
-
|
|
376
|
-
Every `browser` call is a recorded `browser.<method>` step, except `locator` and
|
|
377
|
-
`frameLocator`: they build a locator without touching the page, and the
|
|
378
|
-
action or assertion on it is the step. A relative URL resolves against the
|
|
379
|
-
target's `app.url`, else `APP_URL_REQUIRED`. Declare `requires: ['browser']`
|
|
380
|
-
in a suite that also runs on other platforms.
|
|
381
|
-
|
|
382
|
-
### Navigation
|
|
383
|
-
|
|
384
|
-
`goto` defaults `waitUntil` to `'load'` and throws `POLICY_DENIED` for a
|
|
385
|
-
URL whose scheme is not `http:` or `https:` (`about:blank` is admitted). `waitForURL` polls every 100 ms; a
|
|
386
|
-
relative string resolves against the base URL and matches exactly after
|
|
387
|
-
normalization, a `RegExp` tests the full URL, anything else (a predicate
|
|
388
|
-
function, say) is `INVALID_ARGUMENT`, and a timeout is `ASSERTION_FAILED`. It
|
|
389
|
-
takes `timeout` only: another key, such as Playwright's `waitUntil`, is
|
|
390
|
-
`INVALID_ARGUMENT`. The other navigations throw `ACTION_FAILED`.
|
|
391
|
-
|
|
392
|
-
```ts
|
|
393
|
-
await browser.goto('/dashboard', { waitUntil: 'networkidle' });
|
|
394
|
-
await browser.waitForURL(/\/orders\/\d+$/, { timeout: 15_000 });
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### Locators and frames
|
|
398
|
-
|
|
399
|
-
`locator` and `frameLocator`, at the page or inside a frame, throw
|
|
400
|
-
`INVALID_LOCATOR` for an empty selector. `frameLocator` throws
|
|
401
|
-
`LOCATOR_NOT_FOUND` for a missing frame and `LOCATOR_AMBIGUOUS` for several.
|
|
402
|
-
The selector is a Playwright selector: CSS by default, XPath with `xpath=` or
|
|
403
|
-
a leading `//`, and `>>` chains, so
|
|
404
|
-
`browser.locator('[data-testid="row"] >> nth=2 >> [data-testid="conditions"] > div')`
|
|
405
|
-
scopes CSS to one row. A `*` capture before the last part
|
|
406
|
-
(`*css=article >> text=Hello`) is `INVALID_LOCATOR`: use
|
|
407
|
-
`filter({ has })` for an element that contains another.
|
|
408
|
-
|
|
409
|
-
```ts
|
|
410
|
-
const payment = browser.frameLocator('#payment-frame');
|
|
411
|
-
await payment.getByLabel('Card number').fill('4242424242424242');
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
`frameLocator` returns a `FrameScreen`: a `Screen` whose queries answer from
|
|
415
|
-
inside the frame, plus the two web-only escape hatches scoped to it.
|
|
416
|
-
|
|
417
|
-
```ts
|
|
418
|
-
export interface FrameScreen extends Screen {
|
|
419
|
-
/** Creates a web-only CSS or XPath locator inside this frame. */
|
|
420
|
-
locator(selector: string): Locator;
|
|
421
|
-
/** Creates a screen query scope inside an iframe nested in this frame. */
|
|
422
|
-
frameLocator(selector: string): FrameScreen;
|
|
423
|
-
}
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
Reach for the frame's `locator` only when the control has no accessible
|
|
427
|
-
name, label, placeholder, or test id; the [browser guide](/browser#reach-into-an-iframe)
|
|
428
|
-
shows when that applies.
|
|
429
|
-
|
|
430
|
-
Every `screen` query searches its scope and each closed shadow root the page
|
|
431
|
-
attached under it, with Playwright's own role, name, text, label, and
|
|
432
|
-
attribute matching and each node found once, so a control the observed tree
|
|
433
|
-
lists inside a closed root resolves and takes actions and reads. `locator`,
|
|
434
|
-
`frameLocator`, and `filter({ hasText })` keep Playwright's reach and stop at
|
|
435
|
-
a closed root; the [browser guide](/browser#shadow-dom) has the details.
|
|
436
|
-
|
|
437
|
-
### evaluate
|
|
438
|
-
|
|
439
|
-
The function runs in the page and cannot access variables from the test.
|
|
440
|
-
It is serialized by its source and inlined into the page, so it must not
|
|
441
|
-
close over anything outside its own body; helpers declared inside the body
|
|
442
|
-
work. Pass outside values through `arg`. Both `arg` and the return value
|
|
443
|
-
must be `JsonValue`, otherwise the call fails with `INVALID_ARGUMENT`.
|
|
444
|
-
|
|
445
|
-
A string is evaluated as a single expression, not as statements. If it
|
|
446
|
-
evaluates to a function, that function is called with `arg`; otherwise its
|
|
447
|
-
value is the result. A promise, from either, is awaited.
|
|
448
|
-
|
|
449
|
-
An exception in the evaluated code is `EVALUATE_FAILED` with its message. A
|
|
450
|
-
Playwright timeout is `ACTION_FAILED`; a closed page or lost context is
|
|
451
|
-
`ENGINE_FAILURE`.
|
|
452
|
-
|
|
453
|
-
```ts
|
|
454
|
-
const count = await browser.evaluate(() => document.querySelectorAll('li').length);
|
|
455
|
-
const flag = await browser.evaluate(
|
|
456
|
-
(name: string) => window.localStorage.getItem(name),
|
|
457
|
-
'featureFlags',
|
|
458
|
-
);
|
|
459
|
-
const title = await browser.evaluate<string>('document.title');
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
### Init scripts
|
|
463
|
-
|
|
464
|
-
An init script runs in every document before the page's own scripts, in every
|
|
465
|
-
tab and frame. `web({ initScripts })` applies to every test;
|
|
466
|
-
`browser.addInitScript` adds one for the rest of the test.
|
|
467
|
-
|
|
468
|
-
A script is JavaScript source, a `{ path }` relative to the project root, or a
|
|
469
|
-
function. A function cannot close over test variables; pass a JSON `arg`
|
|
470
|
-
instead.
|
|
471
|
-
|
|
472
|
-
```ts
|
|
473
|
-
await browser.addInitScript((value) => { Math.random = () => value; }, 0.5);
|
|
474
|
-
await app.open('/raffle');
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
A script added after the page opened runs from the next navigation or reload.
|
|
478
|
-
|
|
479
|
-
### Routing
|
|
480
|
-
|
|
481
|
-
`route`, `unroute`, and `waitForResponse` take a string pattern (`*` within a
|
|
482
|
-
path segment, `**` across `/`, `?` for any one character, `\` to escape the
|
|
483
|
-
next) or a `RegExp`, both tested against the full URL; a predicate function
|
|
484
|
-
is `INVALID_ARGUMENT`. A route survives `app.restart()`, `app.clearState()`,
|
|
485
|
-
and session restore.
|
|
486
|
-
The handler must decide exactly once with `fulfill`, `continue`, `fallback`,
|
|
487
|
-
or `abort`; deciding twice or not at all is `ACTION_FAILED`. Routes run newest
|
|
488
|
-
first. `continue` sends the request to the network and no other route sees
|
|
489
|
-
it; its `url` resolves against the base URL and goes through the same rule as
|
|
490
|
-
`goto`, so a forbidden scheme is `POLICY_DENIED`, and it must keep the
|
|
491
|
-
request's scheme. `headers` replaces every request header. The configured
|
|
492
|
-
`headers` still ride a continued request bound for the app's site, and only
|
|
493
|
-
that site. `fallback` hands the request to the route registered before this
|
|
494
|
-
one, or the network when none matches. `fulfill` takes one of `json`, `body`,
|
|
495
|
-
or `path`, a file relative to the project root whose extension sets the
|
|
496
|
-
`Content-Type` unless `contentType` or a `content-type` header does. An option
|
|
497
|
-
a decision does not take (Playwright's `abort(errorCode)`, a misspelled key) is
|
|
498
|
-
`INVALID_ARGUMENT`: the request is aborted and the next step fails.
|
|
499
|
-
|
|
500
|
-
A `waitForResponse` that nothing matches inside the deadline is
|
|
501
|
-
`ACTION_FAILED`. The deadline bounds the match: the response resolves as soon
|
|
502
|
-
as its headers arrive, and `text()` and `json()` wait for the body on a budget
|
|
503
|
-
of their own, `config.actionTimeout` capped by the test deadline; a body still
|
|
504
|
-
streaming when it runs out is `ACTION_FAILED`. `url`, `status`, and `headers`
|
|
505
|
-
are always readable, and a body the
|
|
506
|
-
browser could not read (a connection reset short of the declared
|
|
507
|
-
`Content-Length`, a request the page aborted, or a redirect, whose body the
|
|
508
|
-
browser never keeps) rejects `text()` and `json()` with `ACTION_FAILED`
|
|
509
|
-
instead of an empty string. A genuinely empty body reads as `''`. A handler
|
|
510
|
-
that throws (a failed `expect` on the request, say) fails the next step with
|
|
511
|
-
that error, or the attempt at its end when no step follows; a handler still
|
|
512
|
-
running then gets the cleanup budget to finish. One that outlives the budget
|
|
513
|
-
fails the attempt with `CLEANUP_TIMEOUT`, and whatever it throws afterwards is
|
|
514
|
-
dropped.
|
|
515
|
-
|
|
516
|
-
```ts
|
|
517
|
-
await browser.route('**/api/quote', async (route) => {
|
|
518
|
-
await route.fulfill({ json: { cents: 4200 } });
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
const response = await browser.waitForResponse('**/api/orders');
|
|
522
|
-
expect(response.status).toBe(201);
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
```ts
|
|
526
|
-
/**
|
|
527
|
-
* One of `json`, `body`, or `path`, or none for an empty body. `path` names a
|
|
528
|
-
* file relative to the project root; its `Content-Type` follows its
|
|
529
|
-
* extension unless `contentType` or a `content-type` header sets one.
|
|
530
|
-
*/
|
|
531
|
-
export type RouteFulfillResponse = {
|
|
532
|
-
status?: number;
|
|
533
|
-
headers?: Record<string, string>;
|
|
534
|
-
/** Sets the `Content-Type` response header. */
|
|
535
|
-
contentType?: string;
|
|
536
|
-
} & (
|
|
537
|
-
| { json: JsonValue; body?: never; path?: never }
|
|
538
|
-
| { body: string; json?: never; path?: never }
|
|
539
|
-
| { path: string; json?: never; body?: never }
|
|
540
|
-
| { body?: never; json?: never; path?: never }
|
|
541
|
-
);
|
|
542
|
-
|
|
543
|
-
/** What `continue` changes about the request before it goes to the network. */
|
|
544
|
-
export interface RouteContinueOverrides {
|
|
545
|
-
/** Resolved against the base URL like `goto`; must keep the request's scheme. */
|
|
546
|
-
url?: string;
|
|
547
|
-
/** Request method. */
|
|
548
|
-
method?: string;
|
|
549
|
-
/** Replaces every request header; the configured `headers` for the app's site are added on top. */
|
|
550
|
-
headers?: Record<string, string>;
|
|
551
|
-
/** Request body. */
|
|
552
|
-
postData?: string;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
export interface WebRoute {
|
|
556
|
-
/** The intercepted request. */
|
|
557
|
-
readonly request: {
|
|
558
|
-
readonly url: string;
|
|
559
|
-
readonly method: string;
|
|
560
|
-
readonly headers: Readonly<Record<string, string>>;
|
|
561
|
-
readonly postData?: string;
|
|
562
|
-
};
|
|
563
|
-
/** Fulfills the intercepted request once. */
|
|
564
|
-
fulfill(response: RouteFulfillResponse): Promise<void>;
|
|
565
|
-
/** Sends the intercepted request to the network once, skipping every other route. */
|
|
566
|
-
continue(overrides?: RouteContinueOverrides): Promise<void>;
|
|
567
|
-
/** Hands the intercepted request to the route registered before this one, or the network when none matches. */
|
|
568
|
-
fallback(): Promise<void>;
|
|
569
|
-
/** Aborts the intercepted request once. */
|
|
570
|
-
abort(): Promise<void>;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
export interface WebResponse {
|
|
574
|
-
/** Response URL. */
|
|
575
|
-
readonly url: string;
|
|
576
|
-
/** HTTP status. */
|
|
577
|
-
readonly status: number;
|
|
578
|
-
/** Response headers, lower-cased names. */
|
|
579
|
-
readonly headers: Readonly<Record<string, string>>;
|
|
580
|
-
/** Waits for the body and parses it as JSON. */
|
|
581
|
-
json<T = unknown>(): Promise<T>;
|
|
582
|
-
/** Waits for the body and reads it as text. */
|
|
583
|
-
text(): Promise<string>;
|
|
584
|
-
}
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
### Cookies and viewport
|
|
588
|
-
|
|
589
|
-
`setCookies` validates every cookie before setting any; an unparseable
|
|
590
|
-
target or a forbidden scheme is `POLICY_DENIED` and nothing is set. A
|
|
591
|
-
relative `url` resolves against the base URL, as `goto` resolves it. For the
|
|
592
|
-
`domain` form the scheme follows the base URL and a leading `.` is stripped.
|
|
593
|
-
`setViewport` records the new size on the report. It works before the first
|
|
594
|
-
navigation too, as Playwright's `setViewportSize` does: the next `app.open`
|
|
595
|
-
loads at that size, so a phone-sized test never renders the desktop layout first.
|
|
596
|
-
The size holds for the rest of the attempt, through `app.restart()` and
|
|
597
|
-
`app.clearState()`.
|
|
598
|
-
|
|
599
|
-
```ts
|
|
600
|
-
export interface CookieFields {
|
|
601
|
-
/** Cookie name. */
|
|
602
|
-
name: string;
|
|
603
|
-
/** Cookie value. */
|
|
604
|
-
value: string;
|
|
605
|
-
/** Unix timestamp in whole seconds. */
|
|
606
|
-
expires?: number;
|
|
607
|
-
/** HttpOnly flag. */
|
|
608
|
-
httpOnly?: boolean;
|
|
609
|
-
/** Secure flag. */
|
|
610
|
-
secure?: boolean;
|
|
611
|
-
/** SameSite attribute. */
|
|
612
|
-
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/** `url`, or `domain` with an optional `path`, never both. */
|
|
616
|
-
export type Cookie = CookieFields &
|
|
617
|
-
(
|
|
618
|
-
| { url: string; domain?: never; path?: never }
|
|
619
|
-
| { url?: never; domain: string; path?: string }
|
|
620
|
-
);
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
```ts
|
|
624
|
-
await browser.setCookies([{ url: 'http://localhost:3000', name: 'tz', value: 'UTC' }]);
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
### Media emulation
|
|
628
|
-
|
|
629
|
-
`web({ colorScheme, reducedMotion })` sets the media features every page
|
|
630
|
-
of every attempt reports, as Playwright's context options of the same names
|
|
631
|
-
do. The page's `@media` rules and `matchMedia` read them from the first
|
|
632
|
-
paint. A persistent context takes them too: the engine sets them on each
|
|
633
|
-
page it opens. Each defaults to the browser's own.
|
|
634
|
-
|
|
635
|
-
`browser.emulateMedia(media)` changes them for the rest of the attempt, on
|
|
636
|
-
every open tab and every tab opened after them, through `app.restart()` and
|
|
637
|
-
`app.clearState()`. It works before the first navigation, so the next
|
|
638
|
-
`app.open` loads with them. A feature it leaves out keeps its value. A tab
|
|
639
|
-
the app opens itself gets a test's change as it appears, on a persistent
|
|
640
|
-
context too, so a script it runs at load may still read the configured
|
|
641
|
-
values. A feature or value outside `WebMedia` is `INVALID_CONFIG` in `web()` and
|
|
642
|
-
`INVALID_ARGUMENT` in `emulateMedia`.
|
|
643
|
-
|
|
644
|
-
```ts
|
|
645
|
-
/** User preferences a page reads through `@media` rules and `matchMedia`. */
|
|
646
|
-
export interface WebMedia {
|
|
647
|
-
/** `prefers-color-scheme`. */
|
|
648
|
-
readonly colorScheme?: 'light' | 'dark' | 'no-preference';
|
|
649
|
-
/** `prefers-reduced-motion`. */
|
|
650
|
-
readonly reducedMotion?: 'reduce' | 'no-preference';
|
|
651
|
-
}
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
### Dialogs and downloads
|
|
655
|
-
|
|
656
|
-
A function handler must call `accept` or `dismiss`; one that does neither
|
|
657
|
-
has the dialog dismissed and fails the next step with `APP_NOT_OPEN`, as an
|
|
658
|
-
unhandled dialog does. One that throws fails the next step, or the attempt at
|
|
659
|
-
its end when no step follows, with its error: a failed `expect` stays
|
|
660
|
-
`ASSERTION_FAILED`, an unclassified throw is `ENGINE_FAILURE`.
|
|
661
|
-
`waitForDownload` registers the file as a `download`
|
|
662
|
-
artifact and returns its relative `path`, readable runner-side `absolutePath`,
|
|
663
|
-
and `suggestedFilename`. Use `absolutePath` with file APIs to inspect its
|
|
664
|
-
contents, including on a hosted browser. It throws `ACTION_FAILED` when it
|
|
665
|
-
does not complete in time. A
|
|
666
|
-
trigger that resolves in time but starts no download fails with
|
|
667
|
-
`no download started within <n>ms; the trigger resolved after <m>ms`, where
|
|
668
|
-
the wait includes the trigger's own time. A trigger still running at the
|
|
669
|
-
timeout fails with the step timeout, and one that throws with its own error.
|
|
670
|
-
On a hosted browser the file comes back through the provider's
|
|
671
|
-
[`downloads`](/browser#downloads-on-the-provider).
|
|
672
|
-
|
|
673
|
-
```ts
|
|
674
|
-
/** A native dialog as a test's handler sees it. */
|
|
675
|
-
export interface Dialog {
|
|
676
|
-
/** The dialog's text. */
|
|
677
|
-
readonly message: string;
|
|
678
|
-
/** Accepts the dialog once. */
|
|
679
|
-
accept(text?: string): Promise<void>;
|
|
680
|
-
/** Dismisses the dialog once. */
|
|
681
|
-
dismiss(): Promise<void>;
|
|
682
|
-
}
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
```ts
|
|
686
|
-
const off = await browser.onDialog('accept');
|
|
687
|
-
await screen.getByRole('button', 'Delete').tap();
|
|
688
|
-
await off();
|
|
689
|
-
|
|
690
|
-
const file = await browser.waitForDownload(() =>
|
|
691
|
-
screen.getByRole('link', 'Export CSV').tap(),
|
|
692
|
-
);
|
|
693
|
-
```
|
|
694
|
-
|
|
695
|
-
A handler that throws before answering has its dialog dismissed, and its error fails the next step.
|
|
696
|
-
|
|
697
|
-
### keyboard and mouse
|
|
698
|
-
|
|
699
|
-
Both namespaces are viewport-level. Prefer `locator.press` and
|
|
700
|
-
`locator.fill` when a target exists. `keyboard.type` takes plain text only,
|
|
701
|
-
never a `Secret`.
|
|
702
|
-
|
|
703
1177
|
### JsonValue
|
|
704
1178
|
|
|
705
1179
|
```ts
|
|
@@ -713,59 +1187,8 @@ export type JsonValue =
|
|
|
713
1187
|
| readonly JsonValue[];
|
|
714
1188
|
```
|
|
715
1189
|
|
|
716
|
-
##
|
|
717
|
-
|
|
718
|
-
When the semantic reader times out, agent observations can recover using a
|
|
719
|
-
fresh viewport screenshot. Playwright runs a new secure-field mask sweep
|
|
720
|
-
across every reachable frame, including open and closed shadow roots. Every
|
|
721
|
-
mask probe must succeed. The observation explicitly reports unavailable
|
|
722
|
-
semantics and clears earlier node references.
|
|
723
|
-
|
|
724
|
-
Recovery requires time left in the operation budget and permission from the
|
|
725
|
-
runner. `act` permits it before a secret fill; judgments also require
|
|
726
|
-
`vision: true` or `'only'`. Failed masking, cancellation, and hard engine
|
|
727
|
-
errors still stop the observation.
|
|
728
|
-
|
|
729
|
-
When fallback is permitted, the reader reserves one quarter of the remaining
|
|
730
|
-
observation budget, capped at two seconds, for the screenshot. The deadline
|
|
731
|
-
covers the page walk, metadata and element readback, and nested frames.
|
|
732
|
-
Semantic metadata crosses the Playwright connection as one JSON string to
|
|
733
|
-
reduce serialization overhead. Actions still use the live element handles
|
|
734
|
-
captured with that metadata. Pages that replace `JSON.stringify` or add
|
|
735
|
-
`toJSON` methods to object or array prototypes use structured transfer.
|
|
736
|
-
Cleanup does not wait for stalled protocol replies. An abandoned reader
|
|
737
|
-
cannot reuse node IDs or publish handles over a newer observation.
|
|
738
|
-
|
|
739
|
-
## Exported types
|
|
740
|
-
|
|
741
|
-
`WebOptions`, `WebMedia`, `WebScreencastOptions`, `WebConnectOptions`, `WebBasicAuth`, `BrowserName`,
|
|
742
|
-
`BrowserProvider`, `BrowserProviderScope`, `BrowserRequest`, `BrowserLease`,
|
|
743
|
-
`BrowserReleaseContext`, `BrowserProviderDownloads`, `BrowserDownloadContext`,
|
|
744
|
-
`PlaywrightLiveSurface`, `Browser`, `FrameScreen`,
|
|
745
|
-
`BrowserExpectation`, `WebRoute`, `RouteFulfillResponse`, `RouteContinueOverrides`, `WebResponse`,
|
|
746
|
-
`Cookie`, `CookieFields`, `Dialog`, and `DialogHandler` are exported from the
|
|
747
|
-
package for typing config helpers and fixtures. The runtime exports are
|
|
748
|
-
`web`, [`surfaceOf`](#surfaceof), and `test`, `describe`, `beforeEach`,
|
|
749
|
-
`afterEach`, `beforeAll`, and `afterAll`, typed with the `browser` fixture.
|
|
750
|
-
|
|
751
|
-
## expect(browser)
|
|
752
|
-
|
|
753
|
-
`expect(browser)` returns the `BrowserExpectation` the engine attaches: `toHaveURL`,
|
|
754
|
-
`toHaveTitle`, and `toHaveClass`. See [browser matchers](/reference/expect#browser-matchers).
|
|
755
|
-
|
|
756
|
-
## surfaceOf
|
|
757
|
-
|
|
758
|
-
```ts
|
|
759
|
-
import { web, surfaceOf } from '@e2e-dev/web';
|
|
760
|
-
|
|
761
|
-
const engine = web();
|
|
762
|
-
const live = surfaceOf(engine); // PlaywrightLiveSurface | undefined
|
|
763
|
-
live!.page(); // the Page of the current attempt
|
|
764
|
-
live!.context(); // its BrowserContext
|
|
765
|
-
```
|
|
1190
|
+
## Related
|
|
766
1191
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
`
|
|
770
|
-
`INVALID_STATE` outside an active attempt. Direct Playwright calls are not
|
|
771
|
-
recorded as steps.
|
|
1192
|
+
The [browser guide](/browser) shows these members in recipes and covers
|
|
1193
|
+
[hosted browsers](/browser#hosted-browsers). The portable operations live on
|
|
1194
|
+
the [`app`](/reference/app) fixture.
|