@e2e-dev/web 0.11.2 → 0.13.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/README.md +15 -9
- package/dist/.build.json +1 -1
- package/dist/actions.d.ts +9 -3
- package/dist/actions.js +87 -9
- package/dist/attempt-session.d.ts +4 -3
- package/dist/attempt-session.js +17 -14
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +3 -0
- package/dist/browser-connection.d.ts +7 -3
- package/dist/browser-connection.js +7 -1
- package/dist/browser.d.ts +44 -7
- package/dist/browser.js +135 -72
- package/dist/capture-scope.d.ts +1 -1
- package/dist/cdp-recovery.d.ts +1 -1
- package/dist/cdp-selectors.d.ts +1 -1
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +42 -0
- package/dist/dialogs.d.ts +1 -1
- package/dist/downloads.d.ts +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.js +74 -16
- package/dist/evaluation.d.ts +8 -3
- package/dist/evaluation.js +10 -5
- package/dist/in-page/read-semantics.d.ts +1 -0
- package/dist/in-page/read-semantics.js +166 -40
- package/dist/index.d.ts +2 -1
- package/dist/init-scripts.d.ts +30 -0
- package/dist/init-scripts.js +105 -0
- package/dist/install.d.ts +14 -1
- package/dist/install.js +77 -20
- package/dist/locators.d.ts +1 -1
- package/dist/locators.js +30 -18
- package/dist/observation-capture.d.ts +1 -1
- package/dist/observation.d.ts +1 -1
- package/dist/observation.js +3 -3
- package/dist/observe.d.ts +1 -1
- package/dist/operation-budget.d.ts +14 -0
- package/dist/operation-budget.js +31 -0
- package/dist/protected-app.d.ts +20 -4
- package/dist/protected-app.js +35 -4
- package/dist/provider-video.d.ts +1 -1
- package/dist/read-node.d.ts +9 -2
- package/dist/route-options.d.ts +44 -0
- package/dist/route-options.js +125 -0
- package/dist/selector-engines.js +2 -0
- package/dist/shown-selector.d.ts +20 -0
- package/dist/shown-selector.js +27 -0
- package/dist/support.d.ts +18 -12
- package/dist/support.js +41 -20
- package/dist/surface.d.ts +46 -6
- package/dist/surface.js +60 -19
- package/dist/video.d.ts +1 -1
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -11,14 +11,13 @@ seam with no privilege either way.
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install --save-dev e2e @e2e-dev/web
|
|
14
|
+
npm install --save-dev e2e @e2e-dev/web
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
keeps its
|
|
20
|
-
|
|
21
|
-
(npm's `ERESOLVE`), so upgrade `playwright` within the range.
|
|
17
|
+
This package depends on `playwright-core` pinned to an exact version, so the
|
|
18
|
+
engine always runs the Playwright it was tested against. An app that depends
|
|
19
|
+
on Playwright itself keeps its own copy; the two share a browser cache only
|
|
20
|
+
when their versions match.
|
|
22
21
|
|
|
23
22
|
```ts title="e2e.config.ts"
|
|
24
23
|
import type { E2EConfig } from 'e2e';
|
|
@@ -41,7 +40,10 @@ target, so a test can `requires: ['browser']`. Browser options:
|
|
|
41
40
|
`BrowserProvider` that leases hosted browsers, see below), `viewport`
|
|
42
41
|
(`{ width, height }`; default 1280x720), `testIdAttribute` (the attribute
|
|
43
42
|
`getByTestId` and a node's `testId` read; default `data-testid`), `userAgent`
|
|
44
|
-
(the `User-Agent` every attempt sends and `navigator.userAgent` reports),
|
|
43
|
+
(the `User-Agent` every attempt sends and `navigator.userAgent` reports),
|
|
44
|
+
`locale` and `timezoneId` (the language and time zone every attempt runs in,
|
|
45
|
+
such as `de-DE` and `Europe/Berlin`), `initScripts` (scripts every document
|
|
46
|
+
runs before the page's own), and
|
|
45
47
|
`connect` — attach to a remote browser over CDP instead of launching a local
|
|
46
48
|
one.
|
|
47
49
|
|
|
@@ -71,7 +73,7 @@ engine verifies the default context ID and active page target ID, clears stale
|
|
|
71
73
|
references, and never repeats a dispatched operation. A missing target fails
|
|
72
74
|
the attempt. The host owns deleting the remote browser after cleanup.
|
|
73
75
|
|
|
74
|
-
This mode does not support `headers`, `basicAuth`, `userAgent`, context reset, or session
|
|
76
|
+
This mode does not support `headers`, `basicAuth`, `userAgent`, `locale`, `timezoneId`, context reset, or session
|
|
75
77
|
state capture and restore. Recording resumes after reconnect, but a segment
|
|
76
78
|
lost during the disconnect remains unavailable. See the
|
|
77
79
|
[Playwright reference](../../docs/reference/web.mdx#recovering-a-cdp-transport)
|
|
@@ -131,9 +133,13 @@ In CI, install browsers as their own step instead, so the cost is visible and
|
|
|
131
133
|
cacheable:
|
|
132
134
|
|
|
133
135
|
```bash
|
|
134
|
-
npx
|
|
136
|
+
npx @e2e-dev/web install chromium --with-deps
|
|
135
137
|
```
|
|
136
138
|
|
|
139
|
+
It downloads the browsers for the Playwright version this package pins, the
|
|
140
|
+
ones the engine launches. Under pnpm, run the bin by name:
|
|
141
|
+
`pnpm exec e2e-web install chromium --with-deps`.
|
|
142
|
+
|
|
137
143
|
`--with-deps` also installs the system libraries a slim container image lacks.
|
|
138
144
|
|
|
139
145
|
## Documentation
|
package/dist/.build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.
|
|
1
|
+
{"version":"0.13.0"}
|
package/dist/actions.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Locator action dispatch for the Playwright engine. */
|
|
2
|
-
import type { Page } from 'playwright';
|
|
2
|
+
import type { Page } from 'playwright-core';
|
|
3
3
|
import { type LocatorAction, type NodeRef, type PointerAction, type ViewportPoint } from 'e2e/engine';
|
|
4
4
|
import { type ActionTarget } from './support.ts';
|
|
5
5
|
/**
|
|
@@ -13,10 +13,16 @@ export declare function dispatchLocatorAction(target: ActionTarget, action: Loca
|
|
|
13
13
|
* behind it: no actionability wait, because there is no element to wait on,
|
|
14
14
|
* and the page decides what the gesture lands on, as it does for a person.
|
|
15
15
|
*/
|
|
16
|
-
export declare function dispatchPointerAction(page: Page, at: ViewportPoint, action: PointerAction): Promise<void>;
|
|
16
|
+
export declare function dispatchPointerAction(page: Page, at: ViewportPoint, action: PointerAction, signal?: AbortSignal): Promise<void>;
|
|
17
|
+
/** Translates the failure of an input operation with no element behind it: a pointer action at a point, or a keystroke. */
|
|
18
|
+
export declare function classifyInputError(cause: unknown, label: string): Error;
|
|
17
19
|
/**
|
|
18
20
|
* Classifies a failed action onto the error contract (see the table above
|
|
19
21
|
* `POST_DISPATCH_PATTERN` in support.ts): stale, not actionable, possibly
|
|
20
|
-
* committed, or an engine fault.
|
|
22
|
+
* committed, or an engine fault. A strict mode violation is a located match
|
|
23
|
+
* that turned ambiguous: stale like a detached one before any input, so the
|
|
24
|
+
* runner locates again and reports the ambiguity or acts on the one match
|
|
25
|
+
* left; possibly committed once input went out, as the second side of a drag
|
|
26
|
+
* resolves after the press.
|
|
21
27
|
*/
|
|
22
28
|
export declare function classifyActionError(rawCause: unknown, action: LocatorAction): Error;
|
package/dist/actions.js
CHANGED
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
/** Locator action dispatch for the Playwright engine. */
|
|
2
2
|
import { EngineError } from 'e2e/engine';
|
|
3
|
-
import { asActionable, isClassified, isPwTimeout, message, nearestPixel, performElementSwipe, performPointDrag, performPointerDrag, performViewportSwipe, POST_DISPATCH_PATTERN, } from './support.js';
|
|
3
|
+
import { asActionable, isClassified, isNavigationRace, isPwTimeout, message, nearestPixel, performElementSwipe, performPointDrag, performPointerDrag, performViewportSwipe, POST_DISPATCH_PATTERN, translatePwError, } from './support.js';
|
|
4
4
|
/** How long a long press holds the button when the action names no duration. */
|
|
5
5
|
const DEFAULT_LONG_PRESS_MS = 500;
|
|
6
6
|
/** The keys a click holds, as Playwright's `modifiers` takes them; the contract spells them the same. */
|
|
7
7
|
function heldKeys(action) {
|
|
8
8
|
return action.modifiers === undefined ? {} : { modifiers: [...action.modifiers] };
|
|
9
9
|
}
|
|
10
|
+
/** Playwright's words for an element handle whose element left the DOM. */
|
|
11
|
+
const DETACHED_PATTERN = /element (is |was )?(detached|not attached)/i;
|
|
12
|
+
/**
|
|
13
|
+
* Sets a checkbox, switch, or radio to `checked` with one click, as
|
|
14
|
+
* Playwright's `check` does, apart from the read after the click: a control
|
|
15
|
+
* that is gone by then (an app that swaps a picked radio for its selected
|
|
16
|
+
* view, or navigates on change) took the click, so the action is done, where
|
|
17
|
+
* Playwright reports it detached as if the click never happened. Whatever
|
|
18
|
+
* took its place is not read: the next observation or assertion shows it,
|
|
19
|
+
* as it does after a tap. The reads and the click share one element, so the state before and after the click
|
|
20
|
+
* is one control's.
|
|
21
|
+
*/
|
|
22
|
+
async function setChecked(target, checked, timeout) {
|
|
23
|
+
const deadline = Date.now() + timeout;
|
|
24
|
+
// Playwright reads a timeout of 0 as no timeout at all.
|
|
25
|
+
const remaining = () => Math.max(1, deadline - Date.now());
|
|
26
|
+
const element = target.kind === 'element' ? target.element : await target.locator.elementHandle({ timeout });
|
|
27
|
+
const verb = checked ? 'check' : 'uncheck';
|
|
28
|
+
try {
|
|
29
|
+
if ((await element.isChecked()) === checked)
|
|
30
|
+
return;
|
|
31
|
+
if (!checked && (await isRadio(element))) {
|
|
32
|
+
throw new EngineError('NOT_ACTIONABLE', 'uncheck cannot clear a radio button; select another radio in its group', {
|
|
33
|
+
retryable: false,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
await element.click({ timeout: remaining() });
|
|
37
|
+
let after;
|
|
38
|
+
try {
|
|
39
|
+
after = await element.isChecked();
|
|
40
|
+
}
|
|
41
|
+
catch (cause) {
|
|
42
|
+
if (DETACHED_PATTERN.test(message(cause)) || isNavigationRace(cause))
|
|
43
|
+
return;
|
|
44
|
+
throw cause;
|
|
45
|
+
}
|
|
46
|
+
if (after !== checked) {
|
|
47
|
+
throw new EngineError('NOT_ACTIONABLE', `${verb} clicked the control but its checked state did not change`, {
|
|
48
|
+
retryable: false,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (target.kind === 'locator')
|
|
54
|
+
void element.dispose().catch(() => undefined);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Whether a checkable element is a radio, which a click can select but never clear. */
|
|
58
|
+
function isRadio(element) {
|
|
59
|
+
return element.evaluate((node) => (node instanceof HTMLInputElement && node.type === 'radio') || node.getAttribute('role') === 'radio');
|
|
60
|
+
}
|
|
10
61
|
/**
|
|
11
62
|
* Dispatches one deterministic locator action onto a Playwright target with the
|
|
12
63
|
* platform's own actionability checks. `lookup` resolves the second ref of a
|
|
@@ -39,10 +90,8 @@ export async function dispatchLocatorAction(target, action, timeout, lookup) {
|
|
|
39
90
|
await locator.press(action.key, { timeout });
|
|
40
91
|
return;
|
|
41
92
|
case 'check':
|
|
42
|
-
await locator.check({ timeout });
|
|
43
|
-
return;
|
|
44
93
|
case 'uncheck':
|
|
45
|
-
await
|
|
94
|
+
await setChecked(target, action.kind === 'check', timeout);
|
|
46
95
|
return;
|
|
47
96
|
case 'focus':
|
|
48
97
|
// ElementHandle.focus takes no timeout: the element is already resolved.
|
|
@@ -102,7 +151,7 @@ export async function dispatchLocatorAction(target, action, timeout, lookup) {
|
|
|
102
151
|
* behind it: no actionability wait, because there is no element to wait on,
|
|
103
152
|
* and the page decides what the gesture lands on, as it does for a person.
|
|
104
153
|
*/
|
|
105
|
-
export async function dispatchPointerAction(page, at, action) {
|
|
154
|
+
export async function dispatchPointerAction(page, at, action, signal) {
|
|
106
155
|
const { mouse } = page;
|
|
107
156
|
const point = nearestPixel(at);
|
|
108
157
|
switch (action.kind) {
|
|
@@ -122,9 +171,11 @@ export async function dispatchPointerAction(page, at, action) {
|
|
|
122
171
|
await mouse.move(point.x, point.y);
|
|
123
172
|
return;
|
|
124
173
|
case 'dragTo':
|
|
174
|
+
await performPointDrag(mouse, point, action.target, undefined, signal);
|
|
175
|
+
return;
|
|
125
176
|
case 'swipeTo':
|
|
126
177
|
// A swipe along a path is a pointer drag on a document platform.
|
|
127
|
-
await performPointDrag(mouse, point, action.target);
|
|
178
|
+
await performPointDrag(mouse, point, action.target, action.durationMs, signal);
|
|
128
179
|
return;
|
|
129
180
|
case 'swipe':
|
|
130
181
|
// The pointer moves to the point first so the scrollable under it receives the wheel.
|
|
@@ -133,6 +184,23 @@ export async function dispatchPointerAction(page, at, action) {
|
|
|
133
184
|
return;
|
|
134
185
|
}
|
|
135
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* An input operation the operation deadline cut off: Playwright never
|
|
189
|
+
* answered, so whether the input reached the page is unknown, and it must not
|
|
190
|
+
* be repeated blindly.
|
|
191
|
+
*/
|
|
192
|
+
function inputCutOff(cause, label) {
|
|
193
|
+
if (!(cause instanceof EngineError) || cause.code !== 'OPERATION_TIMEOUT')
|
|
194
|
+
return undefined;
|
|
195
|
+
return new EngineError('ACTION_MAY_HAVE_COMMITTED', `${label} timed out before the page answered; its input may have been dispatched`, {
|
|
196
|
+
retryable: false,
|
|
197
|
+
cause,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/** Translates the failure of an input operation with no element behind it: a pointer action at a point, or a keystroke. */
|
|
201
|
+
export function classifyInputError(cause, label) {
|
|
202
|
+
return inputCutOff(cause, label) ?? translatePwError(cause, label);
|
|
203
|
+
}
|
|
136
204
|
/**
|
|
137
205
|
* Whether a failure of this action may carry the value it was given. A
|
|
138
206
|
* sensitive fill's plaintext must never leave the engine: its message is
|
|
@@ -167,17 +235,27 @@ function actionabilitySummary(text) {
|
|
|
167
235
|
/**
|
|
168
236
|
* Classifies a failed action onto the error contract (see the table above
|
|
169
237
|
* `POST_DISPATCH_PATTERN` in support.ts): stale, not actionable, possibly
|
|
170
|
-
* committed, or an engine fault.
|
|
238
|
+
* committed, or an engine fault. A strict mode violation is a located match
|
|
239
|
+
* that turned ambiguous: stale like a detached one before any input, so the
|
|
240
|
+
* runner locates again and reports the ambiguity or acts on the one match
|
|
241
|
+
* left; possibly committed once input went out, as the second side of a drag
|
|
242
|
+
* resolves after the press.
|
|
171
243
|
*/
|
|
172
244
|
export function classifyActionError(rawCause, action) {
|
|
245
|
+
const cut = inputCutOff(rawCause, action.kind);
|
|
246
|
+
if (cut !== undefined)
|
|
247
|
+
return cut;
|
|
173
248
|
if (isClassified(rawCause))
|
|
174
249
|
return rawCause;
|
|
175
250
|
const text = redactSensitive(message(rawCause), action);
|
|
176
251
|
const cause = isSensitive(action) ? undefined : rawCause;
|
|
177
252
|
if (/strict mode violation/i.test(text)) {
|
|
178
|
-
|
|
253
|
+
if (POST_DISPATCH_PATTERN.test(text)) {
|
|
254
|
+
return new EngineError('ACTION_MAY_HAVE_COMMITTED', `${action.kind} matched more than one element after its input was dispatched: ${text}`, { retryable: false, cause });
|
|
255
|
+
}
|
|
256
|
+
return new EngineError('NODE_STALE', text, { retryable: true, cause });
|
|
179
257
|
}
|
|
180
|
-
if (
|
|
258
|
+
if (DETACHED_PATTERN.test(text)) {
|
|
181
259
|
return new EngineError('NODE_STALE', text, { retryable: true, cause });
|
|
182
260
|
}
|
|
183
261
|
if (/Timeout .*exceeded/i.test(text) || isPwTimeout(rawCause)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Browser, BrowserContext, BrowserContextOptions, Page } from 'playwright';
|
|
1
|
+
import type { Browser, BrowserContext, BrowserContextOptions, Page } from 'playwright-core';
|
|
2
2
|
import { type EngineCleanupContext, type OperationContext, type VideoSegment, type ViewportSize } from 'e2e/engine';
|
|
3
3
|
import { type CdpEndpointResolver, type SessionBinding } from './cdp-recovery.ts';
|
|
4
|
+
import { type OperationBound } from './operation-budget.ts';
|
|
4
5
|
import { RefRegistry } from './refs.ts';
|
|
5
6
|
import type { LeaseRecording } from './provider.ts';
|
|
6
7
|
import type { WebScreencastOptions } from './surface.ts';
|
|
@@ -57,8 +58,8 @@ export declare class AttemptSession {
|
|
|
57
58
|
private invalidate;
|
|
58
59
|
/** Opens a context for this attempt, publishing only after its handlers are installed. */
|
|
59
60
|
start(signal: AbortSignal): Promise<void>;
|
|
60
|
-
/** Shares one reconnect, then dispatches once with the time that remains. */
|
|
61
|
-
run<T>(operation: OperationContext, label: string, work: (operation: OperationContext) => Promise<T
|
|
61
|
+
/** Shares one reconnect, then dispatches once with the time that remains, bounded by the operation's budget unless it runs test code. */
|
|
62
|
+
run<T>(operation: OperationContext, label: string, work: (operation: OperationContext) => Promise<T>, bound?: OperationBound): Promise<T>;
|
|
62
63
|
/** Owns a candidate through configuration and recording setup, then publishes it once. */
|
|
63
64
|
private transition;
|
|
64
65
|
/** Creates the attempt's active page and records its exact target before exposing it. */
|
package/dist/attempt-session.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdirSync } from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { EngineError, raceAbort, withinCleanupBudget } from 'e2e/engine';
|
|
5
5
|
import { attachPersistent, recoveryFailed, targetIdentity } from './cdp-recovery.js';
|
|
6
|
-
import { connectionAbort, withConnectionBudget } from './operation-budget.js';
|
|
6
|
+
import { connectionAbort, withConnectionBudget, withOperationDeadline } from './operation-budget.js';
|
|
7
7
|
import { RefRegistry } from './refs.js';
|
|
8
8
|
import { ProviderVideo } from './provider-video.js';
|
|
9
9
|
import { invalidState, translatePwError } from './support.js';
|
|
@@ -83,8 +83,8 @@ export class AttemptSession {
|
|
|
83
83
|
return binding;
|
|
84
84
|
}));
|
|
85
85
|
}
|
|
86
|
-
/** Shares one reconnect, then dispatches once with the time that remains. */
|
|
87
|
-
async run(operation, label, work) {
|
|
86
|
+
/** Shares one reconnect, then dispatches once with the time that remains, bounded by the operation's budget unless it runs test code. */
|
|
87
|
+
async run(operation, label, work, bound = 'deadline') {
|
|
88
88
|
const signal = AbortSignal.any([operation.signal, this.lifetime.signal]);
|
|
89
89
|
if (signal.aborted)
|
|
90
90
|
throw connectionAbort(signal, label);
|
|
@@ -92,23 +92,26 @@ export class AttemptSession {
|
|
|
92
92
|
throw this.state.error;
|
|
93
93
|
const persistent = this.options.persistent;
|
|
94
94
|
const reconnect = (this.state.kind === 'pending' || (this.state.kind === 'ready' && !this.state.binding.browser.isConnected()));
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
const endsAt = Date.now() + operation.timeoutMs;
|
|
96
|
+
// Recovery is the engine's own work, so the deadline bounds it whatever the operation runs.
|
|
97
|
+
if (persistent !== undefined && reconnect) {
|
|
98
|
+
await withConnectionBudget({ signal, timeoutMs: operation.timeoutMs }, label, async (remaining) => {
|
|
99
|
+
if (this.state.kind === 'pending') {
|
|
100
|
+
await raceAbort(this.state.work, remaining().signal, 'CDP recovery');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
102
103
|
const previous = this.current();
|
|
103
104
|
this.observed = false;
|
|
104
105
|
await this.transition(previous, remaining(), async () => {
|
|
105
106
|
await this.video.pageClosing();
|
|
106
107
|
return attachPersistent(persistent.reconnect, remaining(), previous.identity);
|
|
107
108
|
}, true);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const left = endsAt - Date.now();
|
|
112
|
+
if (left <= 0)
|
|
113
|
+
throw new EngineError('OPERATION_TIMEOUT', `${label} timed out`, { retryable: false });
|
|
114
|
+
return withOperationDeadline({ signal, timeoutMs: left }, label, (remaining) => work({ ...operation, ...remaining() }), bound);
|
|
112
115
|
}
|
|
113
116
|
/** Owns a candidate through configuration and recording setup, then publishes it once. */
|
|
114
117
|
async transition(previous, budget, open, recover = false) {
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/** Shares one browser process connection across a worker's attempts. */
|
|
2
|
-
import type { Browser } from 'playwright';
|
|
3
|
-
import { chromium } from 'playwright';
|
|
4
|
-
|
|
2
|
+
import type { Browser } from 'playwright-core';
|
|
3
|
+
import { chromium } from 'playwright-core';
|
|
4
|
+
/** Every browser the engine launches, in the order the CLI lists them. */
|
|
5
|
+
export declare const BROWSER_NAMES: readonly ['chromium', 'firefox', 'webkit'];
|
|
6
|
+
export type BrowserName = (typeof BROWSER_NAMES)[number];
|
|
7
|
+
/** Whether `name` is a browser the engine launches. */
|
|
8
|
+
export declare function isBrowserName(name: string): name is BrowserName;
|
|
5
9
|
/**
|
|
6
10
|
* Launching a browser process costs hundreds of milliseconds; per-attempt
|
|
7
11
|
* isolation lives in browser contexts, so every attempt a worker runs can
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/** Shares one browser process connection across a worker's attempts. */
|
|
2
|
-
import { chromium, firefox, selectors, webkit } from 'playwright';
|
|
2
|
+
import { chromium, firefox, selectors, webkit } from 'playwright-core';
|
|
3
3
|
import { SELECTOR_ENGINES } from './selector-engines.js';
|
|
4
|
+
/** Every browser the engine launches, in the order the CLI lists them. */
|
|
5
|
+
export const BROWSER_NAMES = ['chromium', 'firefox', 'webkit'];
|
|
6
|
+
/** Whether `name` is a browser the engine launches. */
|
|
7
|
+
export function isBrowserName(name) {
|
|
8
|
+
return BROWSER_NAMES.includes(name);
|
|
9
|
+
}
|
|
4
10
|
/**
|
|
5
11
|
* Playwright's default SIGINT handler closes the browser and exits the
|
|
6
12
|
* process, and its SIGTERM handler closes the browser. A terminal Ctrl-C
|
package/dist/browser.d.ts
CHANGED
|
@@ -15,20 +15,44 @@ import type { ActionOptions, Expectable, JsonValue, Locator, Screen, TextMatch }
|
|
|
15
15
|
import { type EngineFixtureContext } from 'e2e/engine';
|
|
16
16
|
import type { DialogHandler } from './dialogs.ts';
|
|
17
17
|
import type { PlaywrightSurface } from './surface.ts';
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* One of `json`, `body`, or `path`, or none for an empty body. `path` names a
|
|
20
|
+
* file relative to the project root; its `Content-Type` follows its
|
|
21
|
+
* extension unless `contentType` or a `content-type` header sets one.
|
|
22
|
+
*/
|
|
19
23
|
export type RouteFulfillResponse = {
|
|
20
24
|
status?: number;
|
|
21
25
|
headers?: Record<string, string>;
|
|
26
|
+
/** Sets the `Content-Type` response header. */
|
|
27
|
+
contentType?: string;
|
|
22
28
|
} & ({
|
|
23
29
|
json: JsonValue;
|
|
24
30
|
body?: never;
|
|
31
|
+
path?: never;
|
|
25
32
|
} | {
|
|
26
33
|
body: string;
|
|
27
34
|
json?: never;
|
|
35
|
+
path?: never;
|
|
36
|
+
} | {
|
|
37
|
+
path: string;
|
|
38
|
+
json?: never;
|
|
39
|
+
body?: never;
|
|
28
40
|
} | {
|
|
29
41
|
body?: never;
|
|
30
42
|
json?: never;
|
|
43
|
+
path?: never;
|
|
31
44
|
});
|
|
45
|
+
/** What `continue` changes about the request before it goes to the network. */
|
|
46
|
+
export interface RouteContinueOverrides {
|
|
47
|
+
/** Resolved against the base URL like `goto`; must keep the request's scheme. */
|
|
48
|
+
url?: string;
|
|
49
|
+
/** Request method. */
|
|
50
|
+
method?: string;
|
|
51
|
+
/** Replaces every request header; the configured `headers` for the app's site are added on top. */
|
|
52
|
+
headers?: Record<string, string>;
|
|
53
|
+
/** Request body. */
|
|
54
|
+
postData?: string;
|
|
55
|
+
}
|
|
32
56
|
export interface WebRoute {
|
|
33
57
|
/** The intercepted request. */
|
|
34
58
|
readonly request: {
|
|
@@ -39,8 +63,10 @@ export interface WebRoute {
|
|
|
39
63
|
};
|
|
40
64
|
/** Fulfills the intercepted request once. */
|
|
41
65
|
fulfill(response: RouteFulfillResponse): Promise<void>;
|
|
42
|
-
/**
|
|
43
|
-
continue(): Promise<void>;
|
|
66
|
+
/** Sends the intercepted request to the network once, skipping every other route. */
|
|
67
|
+
continue(overrides?: RouteContinueOverrides): Promise<void>;
|
|
68
|
+
/** Hands the intercepted request to the route registered before this one, or the network when none matches. */
|
|
69
|
+
fallback(): Promise<void>;
|
|
44
70
|
/** Aborts the intercepted request once. */
|
|
45
71
|
abort(): Promise<void>;
|
|
46
72
|
}
|
|
@@ -51,9 +77,9 @@ export interface WebResponse {
|
|
|
51
77
|
readonly status: number;
|
|
52
78
|
/** Response headers, lower-cased names. */
|
|
53
79
|
readonly headers: Readonly<Record<string, string>>;
|
|
54
|
-
/**
|
|
80
|
+
/** Waits for the body and parses it as JSON; rejects with `ACTION_FAILED` when the body could not be read. */
|
|
55
81
|
json<T = unknown>(): Promise<T>;
|
|
56
|
-
/**
|
|
82
|
+
/** Waits for the body and reads it as text; rejects with `ACTION_FAILED` when the body could not be read. */
|
|
57
83
|
text(): Promise<string>;
|
|
58
84
|
}
|
|
59
85
|
export interface CookieFields {
|
|
@@ -83,8 +109,9 @@ export type Cookie = CookieFields & ({
|
|
|
83
109
|
export interface BrowserExpectation {
|
|
84
110
|
/** Inverts the matcher. */
|
|
85
111
|
readonly not: BrowserExpectation;
|
|
86
|
-
/** Waits for the current URL to match. */
|
|
112
|
+
/** Waits for the current URL to match; `ignoreCase` compares a string case-insensitively and adds the `i` flag to a RegExp, `false` removes it. */
|
|
87
113
|
toHaveURL(expected: string | RegExp, options?: {
|
|
114
|
+
ignoreCase?: boolean;
|
|
88
115
|
timeout?: number;
|
|
89
116
|
}): Promise<void>;
|
|
90
117
|
/** Waits for the current title to match. */
|
|
@@ -142,10 +169,20 @@ export interface Browser extends Expectable<BrowserExpectation> {
|
|
|
142
169
|
locator(selector: string): Locator;
|
|
143
170
|
/** Creates a screen query scope inside one iframe. */
|
|
144
171
|
frameLocator(selector: string): FrameScreen;
|
|
145
|
-
/** Evaluates trusted test code in the page. */
|
|
172
|
+
/** Evaluates trusted test code in the page: a function, or a string expression whose value is returned, called first if it is a function. */
|
|
146
173
|
evaluate<T extends JsonValue>(fn: string | (() => T | Promise<T>)): Promise<T>;
|
|
147
174
|
/** Evaluates trusted test code with one required JSON-safe argument. */
|
|
148
175
|
evaluate<T extends JsonValue, Arg extends JsonValue>(fn: string | ((arg: Arg) => T | Promise<T>), arg: Arg): Promise<T>;
|
|
176
|
+
/**
|
|
177
|
+
* Adds a script every document runs before the page's own, from the next
|
|
178
|
+
* navigation on: JavaScript source, a `{ path }` relative to the project
|
|
179
|
+
* root, or a function.
|
|
180
|
+
*/
|
|
181
|
+
addInitScript(script: string | {
|
|
182
|
+
path: string;
|
|
183
|
+
} | (() => unknown)): Promise<void>;
|
|
184
|
+
/** Adds an init script function called with one JSON argument. */
|
|
185
|
+
addInitScript<Arg extends JsonValue>(script: (arg: Arg) => unknown, arg: Arg): Promise<void>;
|
|
149
186
|
/** Adds an attempt-scoped network route. */
|
|
150
187
|
route(pattern: string | RegExp, handler: (route: WebRoute) => void | Promise<void>): Promise<void>;
|
|
151
188
|
/** Removes matching attempt-scoped routes. */
|