@e2e-dev/web 0.11.1 → 0.12.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 +1 -2
- package/dist/actions.js +54 -6
- package/dist/attempt-session.d.ts +1 -2
- package/dist/attempt-session.js +0 -1
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +3 -0
- package/dist/browser-connection.d.ts +7 -4
- package/dist/browser-connection.js +7 -2
- package/dist/browser.d.ts +43 -7
- package/dist/browser.js +93 -36
- package/dist/capture-scope.d.ts +1 -2
- package/dist/capture-scope.js +0 -1
- package/dist/cdp-recovery.d.ts +1 -2
- package/dist/cdp-recovery.js +0 -1
- package/dist/cdp-selectors.d.ts +1 -2
- package/dist/cdp-selectors.js +0 -1
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +42 -0
- package/dist/closed-shadow.d.ts +0 -1
- package/dist/closed-shadow.js +0 -1
- package/dist/dialogs.d.ts +1 -2
- package/dist/dialogs.js +0 -1
- package/dist/downloads.d.ts +1 -2
- package/dist/downloads.js +0 -1
- package/dist/engine.d.ts +1 -2
- package/dist/engine.js +74 -17
- package/dist/evaluation.d.ts +2 -1
- package/dist/evaluation.js +3 -2
- package/dist/in-page/read-semantics.d.ts +4 -6
- package/dist/in-page/read-semantics.js +184 -54
- package/dist/index.d.ts +2 -2
- package/dist/index.js +0 -1
- package/dist/init-scripts.d.ts +30 -0
- package/dist/init-scripts.js +105 -0
- package/dist/install.d.ts +8 -2
- package/dist/install.js +35 -9
- package/dist/label-selector.d.ts +0 -1
- package/dist/label-selector.js +0 -1
- package/dist/locators.d.ts +1 -2
- package/dist/locators.js +30 -19
- package/dist/observation-capture.d.ts +1 -2
- package/dist/observation-capture.js +0 -1
- package/dist/observation.d.ts +1 -2
- package/dist/observation.js +3 -4
- package/dist/observe.d.ts +1 -2
- package/dist/observe.js +0 -1
- package/dist/operation-budget.d.ts +0 -1
- package/dist/operation-budget.js +0 -1
- package/dist/protected-app.d.ts +20 -5
- package/dist/protected-app.js +36 -6
- package/dist/provider-video.d.ts +1 -2
- package/dist/provider-video.js +0 -1
- package/dist/provider.d.ts +0 -1
- package/dist/provider.js +0 -1
- package/dist/read-node.d.ts +16 -44
- package/dist/read-node.js +7 -22
- package/dist/read-selector.d.ts +51 -0
- package/dist/read-selector.js +69 -0
- package/dist/refs.d.ts +0 -1
- package/dist/refs.js +0 -1
- package/dist/route-options.d.ts +44 -0
- package/dist/route-options.js +125 -0
- package/dist/route-pattern.d.ts +0 -1
- package/dist/route-pattern.js +0 -1
- package/dist/selector-engines.d.ts +0 -1
- package/dist/selector-engines.js +4 -1
- package/dist/shown-selector.d.ts +20 -0
- package/dist/shown-selector.js +27 -0
- package/dist/support.d.ts +4 -3
- package/dist/support.js +1 -2
- package/dist/surface.d.ts +40 -4
- package/dist/surface.js +62 -28
- package/dist/video.d.ts +1 -2
- package/dist/video.js +0 -1
- package/package.json +9 -5
- package/dist/actions.d.ts.map +0 -1
- package/dist/actions.js.map +0 -1
- package/dist/attempt-session.d.ts.map +0 -1
- package/dist/attempt-session.js.map +0 -1
- package/dist/browser-connection.d.ts.map +0 -1
- package/dist/browser-connection.js.map +0 -1
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js.map +0 -1
- package/dist/capture-scope.d.ts.map +0 -1
- package/dist/capture-scope.js.map +0 -1
- package/dist/cdp-recovery.d.ts.map +0 -1
- package/dist/cdp-recovery.js.map +0 -1
- package/dist/cdp-selectors.d.ts.map +0 -1
- package/dist/cdp-selectors.js.map +0 -1
- package/dist/closed-shadow.d.ts.map +0 -1
- package/dist/closed-shadow.js.map +0 -1
- package/dist/dialogs.d.ts.map +0 -1
- package/dist/dialogs.js.map +0 -1
- package/dist/downloads.d.ts.map +0 -1
- package/dist/downloads.js.map +0 -1
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js.map +0 -1
- package/dist/evaluation.d.ts.map +0 -1
- package/dist/evaluation.js.map +0 -1
- package/dist/in-page/read-semantics.d.ts.map +0 -1
- package/dist/in-page/read-semantics.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js.map +0 -1
- package/dist/label-selector.d.ts.map +0 -1
- package/dist/label-selector.js.map +0 -1
- package/dist/locators.d.ts.map +0 -1
- package/dist/locators.js.map +0 -1
- package/dist/observation-capture.d.ts.map +0 -1
- package/dist/observation-capture.js.map +0 -1
- package/dist/observation.d.ts.map +0 -1
- package/dist/observation.js.map +0 -1
- package/dist/observe.d.ts.map +0 -1
- package/dist/observe.js.map +0 -1
- package/dist/operation-budget.d.ts.map +0 -1
- package/dist/operation-budget.js.map +0 -1
- package/dist/protected-app.d.ts.map +0 -1
- package/dist/protected-app.js.map +0 -1
- package/dist/provider-video.d.ts.map +0 -1
- package/dist/provider-video.js.map +0 -1
- package/dist/provider.d.ts.map +0 -1
- package/dist/provider.js.map +0 -1
- package/dist/read-node.d.ts.map +0 -1
- package/dist/read-node.js.map +0 -1
- package/dist/refs.d.ts.map +0 -1
- package/dist/refs.js.map +0 -1
- package/dist/route-pattern.d.ts.map +0 -1
- package/dist/route-pattern.js.map +0 -1
- package/dist/selector-engines.d.ts.map +0 -1
- package/dist/selector-engines.js.map +0 -1
- package/dist/support.d.ts.map +0 -1
- package/dist/support.js.map +0 -1
- package/dist/surface.d.ts.map +0 -1
- package/dist/surface.js.map +0 -1
- package/dist/video.d.ts.map +0 -1
- package/dist/video.js.map +0 -1
package/dist/install.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
+
import os from 'node:os';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import { EngineError, InfrastructureError } from 'e2e/engine';
|
|
7
8
|
import { browserType } from './browser-connection.js';
|
|
@@ -23,14 +24,40 @@ function isBrowserInstalled(name, env) {
|
|
|
23
24
|
return false;
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
|
-
/**
|
|
27
|
-
function
|
|
27
|
+
/** The `node` arguments that run the pinned `playwright-core` CLI with `args`. */
|
|
28
|
+
function playwrightCliArgs(args) {
|
|
28
29
|
const require = createRequire(import.meta.url);
|
|
29
|
-
return path.join(path.dirname(require.resolve('playwright/package.json')), 'cli.js');
|
|
30
|
+
return [path.join(path.dirname(require.resolve('playwright-core/package.json')), 'cli.js'), ...args];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Runs the pinned Playwright CLI with `args`, sharing this process's terminal,
|
|
34
|
+
* and resolves to its exit code, 128 plus the signal number when a signal
|
|
35
|
+
* ended it. SIGINT and SIGTERM sent to this process reach the child, so a
|
|
36
|
+
* cancelled CI job does not leave a download running. Backs the `e2e-web` command.
|
|
37
|
+
*/
|
|
38
|
+
export function runPlaywrightCli(args) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const child = spawn(process.execPath, playwrightCliArgs(args), { stdio: 'inherit' });
|
|
41
|
+
const forward = (signal) => child.kill(signal);
|
|
42
|
+
process.on('SIGINT', forward);
|
|
43
|
+
process.on('SIGTERM', forward);
|
|
44
|
+
const settle = () => {
|
|
45
|
+
process.off('SIGINT', forward);
|
|
46
|
+
process.off('SIGTERM', forward);
|
|
47
|
+
};
|
|
48
|
+
child.on('error', (error) => {
|
|
49
|
+
settle();
|
|
50
|
+
reject(error);
|
|
51
|
+
});
|
|
52
|
+
child.on('exit', (code, signal) => {
|
|
53
|
+
settle();
|
|
54
|
+
resolve(code ?? (signal === null ? 1 : 128 + os.constants.signals[signal]));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
30
57
|
}
|
|
31
58
|
/**
|
|
32
59
|
* Ensures the given Playwright browsers are installed, downloading any
|
|
33
|
-
* missing ones via `playwright install`. Called from the engine's `prepare`,
|
|
60
|
+
* missing ones via `playwright-core install`. Called from the engine's `prepare`,
|
|
34
61
|
* once per run before any worker starts, so a download is never charged
|
|
35
62
|
* against a launch timeout and never runs once per worker. Concurrent
|
|
36
63
|
* installs (two runs at once) are still safe: the Playwright CLI serializes
|
|
@@ -81,14 +108,14 @@ function forwardLines(stream, log) {
|
|
|
81
108
|
});
|
|
82
109
|
stream.on('end', () => flush(pending));
|
|
83
110
|
}
|
|
84
|
-
/** Spawns `node <playwright>/cli.js install <names>`, narrating its output through `log`. */
|
|
111
|
+
/** Spawns `node <playwright-core>/cli.js install <names>`, narrating its output through `log`. */
|
|
85
112
|
function runPlaywrightInstall(names, { log, signal, env }) {
|
|
86
113
|
return new Promise((resolve, reject) => {
|
|
87
114
|
if (signal?.aborted === true) {
|
|
88
115
|
reject(new EngineError('CANCELLED', 'browser install cancelled', { retryable: false }));
|
|
89
116
|
return;
|
|
90
117
|
}
|
|
91
|
-
const child = spawn(process.execPath, [
|
|
118
|
+
const child = spawn(process.execPath, playwrightCliArgs(['install', ...names]), {
|
|
92
119
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
93
120
|
env,
|
|
94
121
|
});
|
|
@@ -101,7 +128,7 @@ function runPlaywrightInstall(names, { log, signal, env }) {
|
|
|
101
128
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
102
129
|
child.on('exit', () => signal?.removeEventListener('abort', onAbort));
|
|
103
130
|
child.on('error', (cause) => {
|
|
104
|
-
reject(new InfrastructureError('BROWSER_INSTALL_FAILED', `failed to run playwright install: ${cause.message}`, {
|
|
131
|
+
reject(new InfrastructureError('BROWSER_INSTALL_FAILED', `failed to run playwright-core install: ${cause.message}`, {
|
|
105
132
|
cause,
|
|
106
133
|
}));
|
|
107
134
|
});
|
|
@@ -110,8 +137,7 @@ function runPlaywrightInstall(names, { log, signal, env }) {
|
|
|
110
137
|
resolve();
|
|
111
138
|
return;
|
|
112
139
|
}
|
|
113
|
-
reject(new InfrastructureError('BROWSER_INSTALL_FAILED', `playwright install ${names.join(' ')} exited with code ${String(code)}; run "npx
|
|
140
|
+
reject(new InfrastructureError('BROWSER_INSTALL_FAILED', `playwright-core install ${names.join(' ')} exited with code ${String(code)}; run "npx @e2e-dev/web install ${names.join(' ')}" (pnpm: "pnpm exec e2e-web install ${names.join(' ')}") manually`));
|
|
114
141
|
});
|
|
115
142
|
});
|
|
116
143
|
}
|
|
117
|
-
//# sourceMappingURL=install.js.map
|
package/dist/label-selector.d.ts
CHANGED
package/dist/label-selector.js
CHANGED
package/dist/locators.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** LocatorExpression -> Playwright locator projection. */
|
|
2
|
-
import type { Locator as PwLocator, Page } from 'playwright';
|
|
2
|
+
import type { Locator as PwLocator, Page } from 'playwright-core';
|
|
3
3
|
import { type LocatorExpression, type TextPattern } from 'e2e/engine';
|
|
4
4
|
/** One positional step applied after display-value filtering. */
|
|
5
5
|
export type PositionalStep = 'first' | 'last' | number;
|
|
@@ -73,4 +73,3 @@ export declare function applyPostSteps<T>(matches: readonly T[], steps: readonly
|
|
|
73
73
|
/** Returns every frame selector along an expression, outermost first. */
|
|
74
74
|
export declare function frameSelectors(expression: LocatorExpression): string[];
|
|
75
75
|
export {};
|
|
76
|
-
//# sourceMappingURL=locators.d.ts.map
|
package/dist/locators.js
CHANGED
|
@@ -3,11 +3,27 @@ import { EngineError } from 'e2e/engine';
|
|
|
3
3
|
import { SEARCH_ROOTS_SELECTOR_ENGINE } from './closed-shadow.js';
|
|
4
4
|
import { exactLabelSelector } from './label-selector.js';
|
|
5
5
|
import { SECURE_FIELD_SELECTOR } from './read-node.js';
|
|
6
|
+
import { SHOWN_SELECTOR } from './shown-selector.js';
|
|
6
7
|
function patternToPw(pattern) {
|
|
7
8
|
if (pattern.kind === 'regexp')
|
|
8
|
-
return new RegExp(pattern.source, pattern.flags);
|
|
9
|
+
return selectorSafeRegExp(new RegExp(pattern.source, pattern.flags));
|
|
9
10
|
return pattern.value;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* A RegExp Playwright can write into a selector as is. Playwright escapes the
|
|
14
|
+
* quotes and `>>` of a RegExp it writes into a selector, except under the `u`
|
|
15
|
+
* or `v` flag, where `\'` is a syntax error: a bare quote there opens a
|
|
16
|
+
* string that swallows every part chained after it. A quote or the second `>`
|
|
17
|
+
* of `>>` becomes the hex escape the flag accepts, matching the same text.
|
|
18
|
+
*/
|
|
19
|
+
function selectorSafeRegExp(re) {
|
|
20
|
+
if (!/[uv]/.test(re.flags))
|
|
21
|
+
return re;
|
|
22
|
+
const source = re.source
|
|
23
|
+
.replace(/["'`]/g, (quote) => `\\x${quote.charCodeAt(0).toString(16)}`)
|
|
24
|
+
.replace(/>>/g, '>\\x3e');
|
|
25
|
+
return source === re.source ? re : new RegExp(source, re.flags);
|
|
26
|
+
}
|
|
11
27
|
function patternExact(pattern) {
|
|
12
28
|
return pattern.kind === 'string' ? pattern.exact : false;
|
|
13
29
|
}
|
|
@@ -21,7 +37,7 @@ function patternExact(pattern) {
|
|
|
21
37
|
function testIdSelector(attribute, pattern) {
|
|
22
38
|
const name = attribute.includes(',') ? JSON.stringify(attribute) : attribute;
|
|
23
39
|
const value = pattern.kind === 'regexp'
|
|
24
|
-
? escapeRegexForSelector(new RegExp(pattern.source, pattern.flags))
|
|
40
|
+
? escapeRegexForSelector(selectorSafeRegExp(new RegExp(pattern.source, pattern.flags)))
|
|
25
41
|
: `"${pattern.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"s`;
|
|
26
42
|
return `internal:testid=[${name}=${value}]`;
|
|
27
43
|
}
|
|
@@ -39,8 +55,12 @@ function escapeRegexForSelector(re) {
|
|
|
39
55
|
* `image` query has to go back to `img` to match it.
|
|
40
56
|
*/
|
|
41
57
|
const ARIA_ROLE_BY_CONTRACT_ROLE = { image: 'img' };
|
|
42
|
-
/**
|
|
43
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Every control that displays a current value: the candidates of a
|
|
60
|
+
* display-value query. A checkbox or radio carries a value it never shows
|
|
61
|
+
* (`on` by default), so it is no candidate.
|
|
62
|
+
*/
|
|
63
|
+
const VALUED_SELECTOR = 'input:not([type="checkbox" i]):not([type="radio" i]), textarea, select';
|
|
44
64
|
/** Whitespace or an icon-font glyph (a private-use code point), the characters the tree's names drop or collapse. */
|
|
45
65
|
const GLYPH_OR_SPACE = '[\\s\\p{Co}]';
|
|
46
66
|
/**
|
|
@@ -58,7 +78,7 @@ function glyphTolerantName(name, exact) {
|
|
|
58
78
|
.split(/\s+/)
|
|
59
79
|
.map((word) => word.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&'))
|
|
60
80
|
.join(`${GLYPH_OR_SPACE}+`);
|
|
61
|
-
return exact ? new RegExp(`^${GLYPH_OR_SPACE}*${body}${GLYPH_OR_SPACE}*$`, 'u') : new RegExp(body, 'iu');
|
|
81
|
+
return selectorSafeRegExp(exact ? new RegExp(`^${GLYPH_OR_SPACE}*${body}${GLYPH_OR_SPACE}*$`, 'u') : new RegExp(body, 'iu'));
|
|
62
82
|
}
|
|
63
83
|
/**
|
|
64
84
|
* The one table of query kinds: each declares its Playwright getter or its
|
|
@@ -125,20 +145,12 @@ function queryToPw(scope, query, testIdAttribute) {
|
|
|
125
145
|
return candidates.locate(scope.locator(`${SEARCH_ROOTS_SELECTOR_ENGINE}=`));
|
|
126
146
|
}
|
|
127
147
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Self-selector for the part of the semantic `hidden` state Playwright's own
|
|
130
|
-
* visibility filter does not read: `aria-hidden` on the element itself.
|
|
131
|
-
*/
|
|
132
|
-
const NOT_ARIA_HIDDEN = ':scope:not([aria-hidden="true"])';
|
|
133
148
|
/**
|
|
134
149
|
* A `visible` query narrows its candidates inside the selector, before any
|
|
135
|
-
* enclosing scope, filter, or index runs
|
|
136
|
-
* (
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* The surface additionally holds a terminal query to the batch-read `hidden`
|
|
140
|
-
* state, which reads the same box, style, and `details` facts Playwright's
|
|
141
|
-
* predicate does, so a direct query agrees with `toBeVisible()`.
|
|
150
|
+
* enclosing scope, filter, or index runs, by the reader's own `hidden` state
|
|
151
|
+
* (`e2e-shown`). An indexed, filtered, or scoping visible query therefore
|
|
152
|
+
* never selects or retains a node that state calls hidden, and the surface's
|
|
153
|
+
* hold of a terminal query to the batch-read state agrees with it.
|
|
142
154
|
*/
|
|
143
155
|
function visibleQueryToPw(scope, query, testIdAttribute) {
|
|
144
156
|
return narrowedToVisible(queryToPw(scope, query, testIdAttribute), query);
|
|
@@ -147,7 +159,7 @@ function visibleQueryToPw(scope, query, testIdAttribute) {
|
|
|
147
159
|
function narrowedToVisible(located, query) {
|
|
148
160
|
if (query.visible !== true)
|
|
149
161
|
return located;
|
|
150
|
-
return located.
|
|
162
|
+
return located.locator(SHOWN_SELECTOR);
|
|
151
163
|
}
|
|
152
164
|
function positioned(locator, index) {
|
|
153
165
|
return index === 'first' ? locator.first() : index === 'last' ? locator.last() : locator.nth(index);
|
|
@@ -305,4 +317,3 @@ export function frameSelectors(expression) {
|
|
|
305
317
|
return [expression.selector, ...frameSelectors(expression.source)];
|
|
306
318
|
}
|
|
307
319
|
}
|
|
308
|
-
//# sourceMappingURL=locators.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** One observation transaction: settling, semantic capture, masked pixels, and staged refs. */
|
|
2
|
-
import type { Page } from 'playwright';
|
|
2
|
+
import type { Page } from 'playwright-core';
|
|
3
3
|
import { type EngineObserveOptions, type OperationContext } from 'e2e/engine';
|
|
4
4
|
import { RefRegistry, type CapturedObservation } from './refs.ts';
|
|
5
5
|
interface CaptureSettings {
|
|
@@ -9,4 +9,3 @@ interface CaptureSettings {
|
|
|
9
9
|
/** Captures under one deadline, retaining no refs from a failed or abandoned document. */
|
|
10
10
|
export declare function captureObservation(page: Page, refs: RefRegistry, settings: CaptureSettings, operation: OperationContext, options: EngineObserveOptions | undefined): Promise<CapturedObservation>;
|
|
11
11
|
export {};
|
|
12
|
-
//# sourceMappingURL=observation-capture.d.ts.map
|
package/dist/observation.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Semantic tree capture for one observation: the in-page walk, nested-document
|
|
3
3
|
* stitching, and element-handle bookkeeping the surface keeps per generation.
|
|
4
4
|
*/
|
|
5
|
-
import type { ElementHandle, Frame } from 'playwright';
|
|
5
|
+
import type { ElementHandle, Frame } from 'playwright-core';
|
|
6
6
|
import { type NodeRef, type SemanticNode } from 'e2e/engine';
|
|
7
7
|
import { type RawNodeData } from './read-node.ts';
|
|
8
8
|
/**
|
|
@@ -60,4 +60,3 @@ export interface CapturedDocument {
|
|
|
60
60
|
*/
|
|
61
61
|
export declare function captureDocument(deps: CaptureDeps, host: DocumentHost, options: CaptureOptions): Promise<CapturedDocument>;
|
|
62
62
|
export declare function toSemanticNode(ref: NodeRef, raw: RawNodeData, children?: readonly SemanticNode[], framePath?: readonly string[]): SemanticNode;
|
|
63
|
-
//# sourceMappingURL=observation.d.ts.map
|
package/dist/observation.js
CHANGED
|
@@ -154,7 +154,7 @@ async function captureInto(deps, scope, stage, host, framePath, budget, signal)
|
|
|
154
154
|
*
|
|
155
155
|
* A `data:` document is *not* admitted, even though its bytes are written by the
|
|
156
156
|
* page that embeds it. It has an opaque origin rather than an inherited one, and
|
|
157
|
-
* navigation
|
|
157
|
+
* navigation admits only http(s) and `about:blank`.
|
|
158
158
|
*/
|
|
159
159
|
function isOnSiteFrame(url, site) {
|
|
160
160
|
if (url === '' || url === 'about:blank' || url === 'about:srcdoc')
|
|
@@ -213,7 +213,7 @@ function assembleTree(nodes, ids, framePath, frameChildren) {
|
|
|
213
213
|
// bordered iframe is off by its border width, which is within a tap
|
|
214
214
|
// target. A frame under a CSS transform is not unwound: its boxes are
|
|
215
215
|
// where the untransformed frame would put them.
|
|
216
|
-
if (embedded !== undefined)
|
|
216
|
+
if (embedded !== undefined && raw.rect !== null)
|
|
217
217
|
childLists[index].unshift(placeInFrame(embedded, raw.rect));
|
|
218
218
|
const node = toSemanticNode({ id: ids[index], revision: '' }, raw, childLists[index], framePath);
|
|
219
219
|
built[index] = node;
|
|
@@ -272,9 +272,8 @@ export function toSemanticNode(ref, raw, children = [], framePath = []) {
|
|
|
272
272
|
states,
|
|
273
273
|
...(raw.level !== null ? { level: raw.level } : {}),
|
|
274
274
|
attributes: raw.attributes,
|
|
275
|
-
rect: raw.rect,
|
|
275
|
+
...(raw.rect !== null ? { rect: raw.rect } : {}),
|
|
276
276
|
...(framePath.length > 0 ? { framePath } : {}),
|
|
277
277
|
...(children.length > 0 ? { children } : {}),
|
|
278
278
|
};
|
|
279
279
|
}
|
|
280
|
-
//# sourceMappingURL=observation.js.map
|
package/dist/observe.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Masked pixel capture for an observation. */
|
|
2
|
-
import type { Locator, Page } from 'playwright';
|
|
2
|
+
import type { Locator, Page } from 'playwright-core';
|
|
3
3
|
import type { ObservationPixels, OperationContext, ViewportSize } from 'e2e/engine';
|
|
4
4
|
/**
|
|
5
5
|
* Two mask locators per frame Playwright can reach, covering every secure
|
|
@@ -48,4 +48,3 @@ export declare function readPngSize(data: Uint8Array): {
|
|
|
48
48
|
width: number;
|
|
49
49
|
height: number;
|
|
50
50
|
} | null;
|
|
51
|
-
//# sourceMappingURL=observe.d.ts.map
|
package/dist/observe.js
CHANGED
|
@@ -8,4 +8,3 @@ export interface ConnectionBudget {
|
|
|
8
8
|
export declare function connectionAbort(signal: AbortSignal, label: string): EngineError;
|
|
9
9
|
/** Bounds the entire transition, aborts abandoned work, and never grants a fresh dispatch budget. */
|
|
10
10
|
export declare function withConnectionBudget<T>(budget: ConnectionBudget, label: string, work: (remaining: () => ConnectionBudget) => Promise<T>): Promise<T>;
|
|
11
|
-
//# sourceMappingURL=operation-budget.d.ts.map
|
package/dist/operation-budget.js
CHANGED
package/dist/protected-app.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* a challenge wherever one is issued. The surface calls these on every
|
|
5
5
|
* context it creates; nothing here holds state.
|
|
6
6
|
*/
|
|
7
|
-
import type { BrowserContext } from 'playwright';
|
|
7
|
+
import type { BrowserContext } from 'playwright-core';
|
|
8
8
|
import { type ResolveSecretOptions, type Secret } from 'e2e/engine';
|
|
9
9
|
import type { WebBasicAuth } from './surface.ts';
|
|
10
10
|
/** The credentials Playwright answers an HTTP authentication challenge with. */
|
|
@@ -12,15 +12,31 @@ export interface PlaywrightHttpCredential {
|
|
|
12
12
|
readonly username: string;
|
|
13
13
|
readonly password: string;
|
|
14
14
|
}
|
|
15
|
+
/** A control character no HTTP field value may carry; a horizontal tab is the one the grammar allows. */
|
|
16
|
+
export declare const FIELD_VALUE_CONTROL: RegExp;
|
|
17
|
+
/**
|
|
18
|
+
* Why the browser could not send this header, or `undefined` when it can: a
|
|
19
|
+
* name outside the token grammar, a value that is not a string, or one
|
|
20
|
+
* carrying a control character (a line break is a header-injection vector).
|
|
21
|
+
* Config and route overrides both check headers here.
|
|
22
|
+
*/
|
|
23
|
+
export declare function headerProblem(name: string, value: unknown): string | undefined;
|
|
15
24
|
/** Header names lowercased, as Playwright reports a request's own headers. */
|
|
16
25
|
export declare function lowercaseNames(headers: Readonly<Record<string, string>>): Readonly<Record<string, string>>;
|
|
26
|
+
/**
|
|
27
|
+
* The configured headers a request to `url` carries: all of them for the
|
|
28
|
+
* app's site, none for any other site or when there is no site policy.
|
|
29
|
+
*/
|
|
30
|
+
export declare function siteHeadersFor(url: string | URL, site: string | undefined, headers: Readonly<Record<string, string>> | undefined): Readonly<Record<string, string>> | undefined;
|
|
17
31
|
/**
|
|
18
32
|
* Adds the configured headers to every request bound for the app's site,
|
|
19
33
|
* via a context route that falls back to the network. A request for any
|
|
20
34
|
* other site is not routed at all; without a site there is no policy and
|
|
21
|
-
* nothing is routed.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
35
|
+
* nothing is routed. Registered before any test route, so it runs last: a
|
|
36
|
+
* test route's `fallback` reaches it, and a `continue`, which skips it,
|
|
37
|
+
* merges the same headers itself (`siteHeadersFor`). Nothing awaits a route
|
|
38
|
+
* handler, so a fallback that fails (the page closed under the request) is
|
|
39
|
+
* dropped rather than left to surface as an unhandled rejection.
|
|
24
40
|
*/
|
|
25
41
|
export declare function installSiteHeaders(context: BrowserContext, site: string | undefined, headers: Readonly<Record<string, string>> | undefined): Promise<void>;
|
|
26
42
|
/**
|
|
@@ -33,4 +49,3 @@ export declare function installSiteHeaders(context: BrowserContext, site: string
|
|
|
33
49
|
* echoes its request headers shows that, not the password.
|
|
34
50
|
*/
|
|
35
51
|
export declare function httpCredentials(basicAuth: WebBasicAuth, resolveSecret: (secret: Secret, options?: ResolveSecretOptions) => Promise<string>): Promise<PlaywrightHttpCredential>;
|
|
36
|
-
//# sourceMappingURL=protected-app.d.ts.map
|
package/dist/protected-app.js
CHANGED
|
@@ -5,22 +5,53 @@
|
|
|
5
5
|
* context it creates; nothing here holds state.
|
|
6
6
|
*/
|
|
7
7
|
import { sameSite } from 'e2e/engine';
|
|
8
|
+
/** An HTTP header field name: one or more `token` characters (RFC 9110). */
|
|
9
|
+
const HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
10
|
+
/** A control character no HTTP field value may carry; a horizontal tab is the one the grammar allows. */
|
|
11
|
+
// oxlint-disable-next-line no-control-regex -- the control characters are the point
|
|
12
|
+
export const FIELD_VALUE_CONTROL = /[\u0000-\u0008\u000A-\u001F\u007F]/;
|
|
13
|
+
/**
|
|
14
|
+
* Why the browser could not send this header, or `undefined` when it can: a
|
|
15
|
+
* name outside the token grammar, a value that is not a string, or one
|
|
16
|
+
* carrying a control character (a line break is a header-injection vector).
|
|
17
|
+
* Config and route overrides both check headers here.
|
|
18
|
+
*/
|
|
19
|
+
export function headerProblem(name, value) {
|
|
20
|
+
if (!HEADER_NAME.test(name))
|
|
21
|
+
return `has an invalid header name: "${name}"`;
|
|
22
|
+
if (typeof value !== 'string')
|
|
23
|
+
return `header "${name}" must be a string, got ${typeof value}`;
|
|
24
|
+
if (FIELD_VALUE_CONTROL.test(value))
|
|
25
|
+
return `header "${name}" must not contain a control character`;
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
8
28
|
/** Header names lowercased, as Playwright reports a request's own headers. */
|
|
9
29
|
export function lowercaseNames(headers) {
|
|
10
30
|
return Object.fromEntries(Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value]));
|
|
11
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The configured headers a request to `url` carries: all of them for the
|
|
34
|
+
* app's site, none for any other site or when there is no site policy.
|
|
35
|
+
*/
|
|
36
|
+
export function siteHeadersFor(url, site, headers) {
|
|
37
|
+
if (headers === undefined || site === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
return sameSite(url, site) ? headers : undefined;
|
|
40
|
+
}
|
|
12
41
|
/**
|
|
13
42
|
* Adds the configured headers to every request bound for the app's site,
|
|
14
43
|
* via a context route that falls back to the network. A request for any
|
|
15
44
|
* other site is not routed at all; without a site there is no policy and
|
|
16
|
-
* nothing is routed.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
45
|
+
* nothing is routed. Registered before any test route, so it runs last: a
|
|
46
|
+
* test route's `fallback` reaches it, and a `continue`, which skips it,
|
|
47
|
+
* merges the same headers itself (`siteHeadersFor`). Nothing awaits a route
|
|
48
|
+
* handler, so a fallback that fails (the page closed under the request) is
|
|
49
|
+
* dropped rather than left to surface as an unhandled rejection.
|
|
19
50
|
*/
|
|
20
51
|
export async function installSiteHeaders(context, site, headers) {
|
|
21
52
|
if (headers === undefined || site === undefined)
|
|
22
53
|
return;
|
|
23
|
-
await context.route((url) =>
|
|
54
|
+
await context.route((url) => siteHeadersFor(url, site, headers) !== undefined, async (route) => {
|
|
24
55
|
await route.fallback({ headers: { ...route.request().headers(), ...headers } }).catch(() => undefined);
|
|
25
56
|
});
|
|
26
57
|
}
|
|
@@ -46,7 +77,6 @@ export async function httpCredentials(basicAuth, resolveSecret) {
|
|
|
46
77
|
*/
|
|
47
78
|
function basicCredentials(username, password) {
|
|
48
79
|
const encoded = Buffer.from(`${username}:${password}`, 'utf8').toString('base64');
|
|
49
|
-
const unpadded = encoded.replace(
|
|
80
|
+
const unpadded = encoded.replace(/={1,2}$/, '');
|
|
50
81
|
return unpadded === encoded ? [encoded] : [encoded, unpadded];
|
|
51
82
|
}
|
|
52
|
-
//# sourceMappingURL=protected-app.js.map
|
package/dist/provider-video.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ended by `stopProviderRecording` into the attempt's one segment, a file in
|
|
5
5
|
* its `video` directory or a link.
|
|
6
6
|
*/
|
|
7
|
-
import type { Page } from 'playwright';
|
|
7
|
+
import type { Page } from 'playwright-core';
|
|
8
8
|
import { type VideoSegment } from 'e2e/engine';
|
|
9
9
|
import type { LeaseRecording } from './provider.ts';
|
|
10
10
|
import type { AttemptVideo } from './video.ts';
|
|
@@ -45,4 +45,3 @@ export declare class ProviderVideo implements AttemptVideo {
|
|
|
45
45
|
/** Ends one of the provider's recordings as a segment of the attempt. */
|
|
46
46
|
private stopLease;
|
|
47
47
|
}
|
|
48
|
-
//# sourceMappingURL=provider-video.d.ts.map
|
package/dist/provider-video.js
CHANGED
package/dist/provider.d.ts
CHANGED
package/dist/provider.js
CHANGED
package/dist/read-node.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The wire types of the in-page semantic reader and the
|
|
3
|
-
* assembled from its source for
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* The wire types of the in-page semantic reader and the document walk
|
|
3
|
+
* assembled from its source for page.evaluate. The reader itself lives in
|
|
4
|
+
* `in-page/read-semantics.ts`; it is serialized into the page, so the page
|
|
5
|
+
* function here is built from its source with `new Function` and stays free
|
|
6
|
+
* of module-scope captures. Locator reads run it inside the selector engines
|
|
7
|
+
* (`read-selector.ts`, `label-selector.ts`).
|
|
7
8
|
*/
|
|
8
9
|
/**
|
|
9
10
|
* The one definition of a secure field.
|
|
@@ -47,12 +48,13 @@ export interface RawNodeData {
|
|
|
47
48
|
attributes: Record<string, string>;
|
|
48
49
|
/** Value of the project's test-id attribute, when the element carries one. */
|
|
49
50
|
testId: string | null;
|
|
51
|
+
/** The element's box in its document's viewport; null when it lays out no box (`display: none`). */
|
|
50
52
|
rect: {
|
|
51
53
|
x: number;
|
|
52
54
|
y: number;
|
|
53
55
|
width: number;
|
|
54
56
|
height: number;
|
|
55
|
-
};
|
|
57
|
+
} | null;
|
|
56
58
|
}
|
|
57
59
|
/** One observed node plus its position in the flattened depth-first tree. */
|
|
58
60
|
export interface RawObservedNode extends RawNodeData {
|
|
@@ -80,6 +82,10 @@ interface RawObservation {
|
|
|
80
82
|
}
|
|
81
83
|
export type SemanticMode = {
|
|
82
84
|
kind: 'node';
|
|
85
|
+
}
|
|
86
|
+
/** The element's `states.hidden` alone, for the `visible` narrowing a selector engine applies. */
|
|
87
|
+
| {
|
|
88
|
+
kind: 'hidden';
|
|
83
89
|
} | {
|
|
84
90
|
kind: 'tree';
|
|
85
91
|
maxNodes: number;
|
|
@@ -94,27 +100,12 @@ export type SemanticMode = {
|
|
|
94
100
|
/** Cuts each node's text at this length; the caller owns the contract value. */
|
|
95
101
|
textLimit: number;
|
|
96
102
|
};
|
|
97
|
-
/**
|
|
98
|
-
* What a node read reports for an element no longer in its document. A
|
|
99
|
-
* detached element has no boxes, so everything else it would report (hidden,
|
|
100
|
-
* no labels) describes nothing on screen; its current value is still its own.
|
|
101
|
-
*/
|
|
102
|
-
export interface DetachedNodeData {
|
|
103
|
-
readonly detached: true;
|
|
104
|
-
readonly value: string | null;
|
|
105
|
-
}
|
|
106
103
|
/** Result of one read, selected by the mode discriminant. */
|
|
107
104
|
export type SemanticResult<Mode extends SemanticMode> = Mode extends {
|
|
108
105
|
kind: 'node';
|
|
109
|
-
} ? RawNodeData
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
readonly testIdAttribute: string;
|
|
113
|
-
readonly secureFieldSelector: string;
|
|
114
|
-
readonly mode: {
|
|
115
|
-
readonly kind: 'node';
|
|
116
|
-
};
|
|
117
|
-
}
|
|
106
|
+
} ? RawNodeData : Mode extends {
|
|
107
|
+
kind: 'hidden';
|
|
108
|
+
} ? boolean : RawObservation;
|
|
118
109
|
/** Options for one document's tree walk. */
|
|
119
110
|
interface TreeReadOptions {
|
|
120
111
|
readonly testIdAttribute: string;
|
|
@@ -125,28 +116,9 @@ interface TreeReadOptions {
|
|
|
125
116
|
}
|
|
126
117
|
/**
|
|
127
118
|
* Walks a whole document from its root element in one in-page call. Built
|
|
128
|
-
* from the reader's source
|
|
119
|
+
* from the reader's source, so the caller
|
|
129
120
|
* evaluates it directly on a page or frame instead of first resolving a
|
|
130
121
|
* `:root` locator - one fewer protocol round trip per document per capture.
|
|
131
122
|
*/
|
|
132
123
|
export declare const readDocumentSemanticsFunction: (options: TreeReadOptions) => RawObservation;
|
|
133
|
-
/**
|
|
134
|
-
* Reads every matched element in one in-page round trip. A page function
|
|
135
|
-
* cannot close over module scope, so the batch function is assembled from the
|
|
136
|
-
* reader's own source (the same source `evaluate` sends) and is a
|
|
137
|
-
* self-contained function Playwright serializes and calls with the elements.
|
|
138
|
-
*/
|
|
139
|
-
export declare const readManySemanticsFunction: (elements: Element[], options: NodeReadOptions) => (RawNodeData | DetachedNodeData)[];
|
|
140
|
-
/**
|
|
141
|
-
* Reads every handle the caller already holds in one round trip, so what is
|
|
142
|
-
* read and what is later acted on are the same elements by construction rather
|
|
143
|
-
* than by a second lookup. Evaluated on the first handle so the read runs in
|
|
144
|
-
* the frame the handles belong to; `page.evaluate` would reject handles taken
|
|
145
|
-
* inside an iframe.
|
|
146
|
-
*/
|
|
147
|
-
export declare const readHandlesSemanticsFunction: (first: Element, arg: {
|
|
148
|
-
elements: Element[];
|
|
149
|
-
options: NodeReadOptions;
|
|
150
|
-
}) => (RawNodeData | DetachedNodeData)[];
|
|
151
124
|
export {};
|
|
152
|
-
//# sourceMappingURL=read-node.d.ts.map
|
package/dist/read-node.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The wire types of the in-page semantic reader and the
|
|
3
|
-
* assembled from its source for
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* The wire types of the in-page semantic reader and the document walk
|
|
3
|
+
* assembled from its source for page.evaluate. The reader itself lives in
|
|
4
|
+
* `in-page/read-semantics.ts`; it is serialized into the page, so the page
|
|
5
|
+
* function here is built from its source with `new Function` and stays free
|
|
6
|
+
* of module-scope captures. Locator reads run it inside the selector engines
|
|
7
|
+
* (`read-selector.ts`, `label-selector.ts`).
|
|
7
8
|
*/
|
|
8
9
|
import { readSemanticsFunction } from './in-page/read-semantics.js';
|
|
9
10
|
/**
|
|
@@ -19,24 +20,8 @@ import { readSemanticsFunction } from './in-page/read-semantics.js';
|
|
|
19
20
|
export const SECURE_FIELD_SELECTOR = 'input[type="password" i]';
|
|
20
21
|
/**
|
|
21
22
|
* Walks a whole document from its root element in one in-page call. Built
|
|
22
|
-
* from the reader's source
|
|
23
|
+
* from the reader's source, so the caller
|
|
23
24
|
* evaluates it directly on a page or frame instead of first resolving a
|
|
24
25
|
* `:root` locator - one fewer protocol round trip per document per capture.
|
|
25
26
|
*/
|
|
26
27
|
export const readDocumentSemanticsFunction = new Function('options', `return (${readSemanticsFunction.toString()})(document.documentElement, options);`);
|
|
27
|
-
/**
|
|
28
|
-
* Reads every matched element in one in-page round trip. A page function
|
|
29
|
-
* cannot close over module scope, so the batch function is assembled from the
|
|
30
|
-
* reader's own source (the same source `evaluate` sends) and is a
|
|
31
|
-
* self-contained function Playwright serializes and calls with the elements.
|
|
32
|
-
*/
|
|
33
|
-
export const readManySemanticsFunction = new Function('elements', 'options', `return elements.map((element) => (${readSemanticsFunction.toString()})(element, options));`);
|
|
34
|
-
/**
|
|
35
|
-
* Reads every handle the caller already holds in one round trip, so what is
|
|
36
|
-
* read and what is later acted on are the same elements by construction rather
|
|
37
|
-
* than by a second lookup. Evaluated on the first handle so the read runs in
|
|
38
|
-
* the frame the handles belong to; `page.evaluate` would reject handles taken
|
|
39
|
-
* inside an iframe.
|
|
40
|
-
*/
|
|
41
|
-
export const readHandlesSemanticsFunction = new Function('_first', 'arg', `return arg.elements.map((element) => (${readSemanticsFunction.toString()})(element, arg.options));`);
|
|
42
|
-
//# sourceMappingURL=read-node.js.map
|