@e2edev/web 0.11.0-canary-20260921180210
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/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.md +112 -0
- package/dist/.build.json +1 -0
- package/dist/actions.d.ts +23 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +174 -0
- package/dist/actions.js.map +1 -0
- package/dist/attempt-session.d.ts +76 -0
- package/dist/attempt-session.d.ts.map +1 -0
- package/dist/attempt-session.js +290 -0
- package/dist/attempt-session.js.map +1 -0
- package/dist/browser-connection.d.ts +34 -0
- package/dist/browser-connection.d.ts.map +1 -0
- package/dist/browser-connection.js +87 -0
- package/dist/browser-connection.js.map +1 -0
- package/dist/capture-scope.d.ts +21 -0
- package/dist/capture-scope.d.ts.map +1 -0
- package/dist/capture-scope.js +61 -0
- package/dist/capture-scope.js.map +1 -0
- package/dist/cdp-recovery.d.ts +29 -0
- package/dist/cdp-recovery.d.ts.map +1 -0
- package/dist/cdp-recovery.js +84 -0
- package/dist/cdp-recovery.js.map +1 -0
- package/dist/cdp-selectors.d.ts +14 -0
- package/dist/cdp-selectors.d.ts.map +1 -0
- package/dist/cdp-selectors.js +48 -0
- package/dist/cdp-selectors.js.map +1 -0
- package/dist/dialogs.d.ts +37 -0
- package/dist/dialogs.d.ts.map +1 -0
- package/dist/dialogs.js +78 -0
- package/dist/dialogs.js.map +1 -0
- package/dist/engine.d.ts +26 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +166 -0
- package/dist/engine.js.map +1 -0
- package/dist/evaluation.d.ts +11 -0
- package/dist/evaluation.d.ts.map +1 -0
- package/dist/evaluation.js +27 -0
- package/dist/evaluation.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +40 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +117 -0
- package/dist/install.js.map +1 -0
- package/dist/locators.d.ts +74 -0
- package/dist/locators.d.ts.map +1 -0
- package/dist/locators.js +260 -0
- package/dist/locators.js.map +1 -0
- package/dist/observation-capture.d.ts +12 -0
- package/dist/observation-capture.d.ts.map +1 -0
- package/dist/observation-capture.js +93 -0
- package/dist/observation-capture.js.map +1 -0
- package/dist/observation.d.ts +63 -0
- package/dist/observation.d.ts.map +1 -0
- package/dist/observation.js +262 -0
- package/dist/observation.js.map +1 -0
- package/dist/observe.d.ts +51 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +104 -0
- package/dist/observe.js.map +1 -0
- package/dist/operation-budget.d.ts +11 -0
- package/dist/operation-budget.d.ts.map +1 -0
- package/dist/operation-budget.js +40 -0
- package/dist/operation-budget.js.map +1 -0
- package/dist/protected-app.d.ts +32 -0
- package/dist/protected-app.d.ts.map +1 -0
- package/dist/protected-app.js +39 -0
- package/dist/protected-app.js.map +1 -0
- package/dist/read-node.d.ts +170 -0
- package/dist/read-node.d.ts.map +1 -0
- package/dist/read-node.js +823 -0
- package/dist/read-node.js.map +1 -0
- package/dist/refs.d.ts +43 -0
- package/dist/refs.d.ts.map +1 -0
- package/dist/refs.js +85 -0
- package/dist/refs.js.map +1 -0
- package/dist/route-pattern.d.ts +15 -0
- package/dist/route-pattern.d.ts.map +1 -0
- package/dist/route-pattern.js +68 -0
- package/dist/route-pattern.js.map +1 -0
- package/dist/support.d.ts +119 -0
- package/dist/support.d.ts.map +1 -0
- package/dist/support.js +299 -0
- package/dist/support.js.map +1 -0
- package/dist/surface.d.ts +257 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +587 -0
- package/dist/surface.js.map +1 -0
- package/dist/video.d.ts +59 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +115 -0
- package/dist/video.js.map +1 -0
- package/dist/web.d.ts +191 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +354 -0
- package/dist/web.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic tree capture for one observation: the in-page walk, nested-document
|
|
3
|
+
* stitching, and element-handle bookkeeping the surface keeps per generation.
|
|
4
|
+
*/
|
|
5
|
+
import type { ElementHandle, Frame } from 'playwright';
|
|
6
|
+
import { type NodeRef, type SemanticNode } from 'e2e/engine';
|
|
7
|
+
import { type RawNodeData } from './read-node.ts';
|
|
8
|
+
/**
|
|
9
|
+
* The id of every observation's root: the main document's element under one
|
|
10
|
+
* id the engine mints, stable across observations and navigations, so the
|
|
11
|
+
* harness can address the viewport (`perform(root, swipe)`) from an earlier
|
|
12
|
+
* observation. Outside the `n<number>` space stamped ids and located ids share.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ROOT_NODE_ID = "root";
|
|
15
|
+
/** A page or frame: anything one document's reader can be evaluated on. */
|
|
16
|
+
export type DocumentHost = Pick<Frame, 'evaluateHandle'>;
|
|
17
|
+
/** What one capture needs from the surface that owns the generation. */
|
|
18
|
+
export interface CaptureDeps {
|
|
19
|
+
readonly testIdAttribute: string;
|
|
20
|
+
/** The app's site; a child frame off it stays a boundary node. Undefined admits no child frame. */
|
|
21
|
+
readonly site: string | undefined;
|
|
22
|
+
/** Reserves a disjoint range before the reader can stamp any element, including on timeout. */
|
|
23
|
+
reserveIds(count: number): number;
|
|
24
|
+
/** Publishes one element handle under its id in the generation being built. */
|
|
25
|
+
commit(id: string, element: ElementHandle<Element>): void;
|
|
26
|
+
}
|
|
27
|
+
export interface CaptureOptions {
|
|
28
|
+
/** Enclosing frame selectors, outermost first; empty for the main document. */
|
|
29
|
+
readonly framePath: readonly string[];
|
|
30
|
+
/** Remaining node budget shared across every document of the observation. */
|
|
31
|
+
readonly budget: number;
|
|
32
|
+
/**
|
|
33
|
+
* Absolute time (epoch ms) by which the whole observation must be done.
|
|
34
|
+
* Every document's budget is derived from what remains of it, so the
|
|
35
|
+
* documents of one observation can never sum past the operation's budget.
|
|
36
|
+
*/
|
|
37
|
+
readonly deadline: number;
|
|
38
|
+
readonly signal: AbortSignal;
|
|
39
|
+
}
|
|
40
|
+
/** One document's tree with what the capture knows about its completeness. */
|
|
41
|
+
export interface CapturedDocument {
|
|
42
|
+
readonly tree: SemanticNode;
|
|
43
|
+
/** Nodes captured across this document and every child document stitched in. */
|
|
44
|
+
readonly nodeCount: number;
|
|
45
|
+
/**
|
|
46
|
+
* True when nodes on screen are missing from `tree`: the node budget cut
|
|
47
|
+
* the walk short in this document or a child, or an on-site child frame's
|
|
48
|
+
* document was not read (no budget or time left, past the depth limit, or
|
|
49
|
+
* its capture failed). Off-site frames are left out by design and never
|
|
50
|
+
* count.
|
|
51
|
+
*/
|
|
52
|
+
readonly truncated: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Captures one document's semantic tree, then descends into each observed
|
|
56
|
+
* iframe boundary node via its content frame and stitches the child
|
|
57
|
+
* document under it. Frame capture is best-effort: a detached or unloaded
|
|
58
|
+
* frame leaves its boundary node childless rather than failing the
|
|
59
|
+
* observation. The node budget is shared across all documents.
|
|
60
|
+
*/
|
|
61
|
+
export declare function captureDocument(deps: CaptureDeps, host: DocumentHost, options: CaptureOptions): Promise<CapturedDocument>;
|
|
62
|
+
export declare function toSemanticNode(ref: NodeRef, raw: RawNodeData, children?: readonly SemanticNode[], framePath?: readonly string[]): SemanticNode;
|
|
63
|
+
//# sourceMappingURL=observation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observation.d.ts","sourceRoot":"","sources":["../src/observation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAY,MAAM,YAAY,CAAC;AACjE,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAGL,KAAK,WAAW,EAEjB,MAAM,gBAAgB,CAAC;AAGxB;;;;;GAKG;AACH,eAAO,MAAM,YAAY,SAAS,CAAC;AAcnC,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;AAEzD,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,mGAAmG;IACnG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,+FAA+F;IAC/F,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAKD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AAkMD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,WAAW,EAChB,QAAQ,GAAE,SAAS,YAAY,EAAO,EACtC,SAAS,GAAE,SAAS,MAAM,EAAO,GAChC,YAAY,CAyBd"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic tree capture for one observation: the in-page walk, nested-document
|
|
3
|
+
* stitching, and element-handle bookkeeping the surface keeps per generation.
|
|
4
|
+
*/
|
|
5
|
+
import { EngineError, sameSite, OBSERVED_NAME_LIMIT, OBSERVED_TEXT_LIMIT, } from 'e2e/engine';
|
|
6
|
+
import { readDocumentSemanticsFunction, SECURE_FIELD_SELECTOR, } from './read-node.js';
|
|
7
|
+
import { CaptureScope } from './capture-scope.js';
|
|
8
|
+
/**
|
|
9
|
+
* The id of every observation's root: the main document's element under one
|
|
10
|
+
* id the engine mints, stable across observations and navigations, so the
|
|
11
|
+
* harness can address the viewport (`perform(root, swipe)`) from an earlier
|
|
12
|
+
* observation. Outside the `n<number>` space stamped ids and located ids share.
|
|
13
|
+
*/
|
|
14
|
+
export const ROOT_NODE_ID = 'root';
|
|
15
|
+
/** Nested iframe capture depth; deeper frames stay boundary nodes. */
|
|
16
|
+
const MAX_FRAME_DEPTH = 4;
|
|
17
|
+
/** Budget for capturing the main document, still capped by the observation deadline. */
|
|
18
|
+
const DOCUMENT_CAPTURE_TIMEOUT_MS = 15_000;
|
|
19
|
+
/**
|
|
20
|
+
* Budget for capturing one child document. A stalled frame (ads, trackers)
|
|
21
|
+
* must cost an observation a moment, not the context default timeout.
|
|
22
|
+
*/
|
|
23
|
+
const FRAME_CAPTURE_TIMEOUT_MS = 3_000;
|
|
24
|
+
/**
|
|
25
|
+
* Captures one document's semantic tree, then descends into each observed
|
|
26
|
+
* iframe boundary node via its content frame and stitches the child
|
|
27
|
+
* document under it. Frame capture is best-effort: a detached or unloaded
|
|
28
|
+
* frame leaves its boundary node childless rather than failing the
|
|
29
|
+
* observation. The node budget is shared across all documents.
|
|
30
|
+
*/
|
|
31
|
+
export async function captureDocument(deps, host, options) {
|
|
32
|
+
const { framePath, budget, signal } = options;
|
|
33
|
+
const cap = framePath.length === 0 ? DOCUMENT_CAPTURE_TIMEOUT_MS : FRAME_CAPTURE_TIMEOUT_MS;
|
|
34
|
+
const deadline = Math.min(options.deadline, Date.now() + cap);
|
|
35
|
+
const scope = new CaptureScope(deadline, signal);
|
|
36
|
+
// A child document's handles are published only once it captured whole; a
|
|
37
|
+
// frame that fails midway leaves no reachable ids behind.
|
|
38
|
+
const staged = [];
|
|
39
|
+
const stage = (id, element) => {
|
|
40
|
+
if (scope.own(element))
|
|
41
|
+
staged.push([id, element]);
|
|
42
|
+
};
|
|
43
|
+
return scope.run(async () => {
|
|
44
|
+
const captured = await captureInto(deps, scope, stage, host, framePath, budget, signal);
|
|
45
|
+
scope.check();
|
|
46
|
+
for (const [id, element] of staged) {
|
|
47
|
+
deps.commit(id, element);
|
|
48
|
+
scope.release(element);
|
|
49
|
+
}
|
|
50
|
+
return captured;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Reads one document and stages its child documents under the same capture owner. */
|
|
54
|
+
async function captureInto(deps, scope, stage, host, framePath, budget, signal) {
|
|
55
|
+
const captured = await scope.read(() => host.evaluateHandle(readDocumentSemanticsFunction, {
|
|
56
|
+
testIdAttribute: deps.testIdAttribute,
|
|
57
|
+
secureFieldSelector: SECURE_FIELD_SELECTOR,
|
|
58
|
+
mode: {
|
|
59
|
+
kind: 'tree',
|
|
60
|
+
maxNodes: budget,
|
|
61
|
+
idSeed: deps.reserveIds(budget),
|
|
62
|
+
nameLimit: OBSERVED_NAME_LIMIT,
|
|
63
|
+
textLimit: OBSERVED_TEXT_LIMIT,
|
|
64
|
+
},
|
|
65
|
+
}), (handle) => scope.own(handle));
|
|
66
|
+
// Property handles would keep earlier captures alive after their parent is disposed.
|
|
67
|
+
const { nodes, ids, truncated: walkTruncated } = await scope.read(() => captured.evaluate((observation) => ({
|
|
68
|
+
nodes: observation.nodes,
|
|
69
|
+
ids: observation.ids,
|
|
70
|
+
truncated: observation.truncated,
|
|
71
|
+
})));
|
|
72
|
+
if (!Array.isArray(ids) || ids.length !== nodes.length) {
|
|
73
|
+
throw new EngineError('ENGINE_FAILURE', 'observation ids do not align with its nodes', {
|
|
74
|
+
retryable: false,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// The main document's element is the observation root, under the minted
|
|
78
|
+
// id rather than the one stamped on it: a new document stamps a new
|
|
79
|
+
// number, the root's id must not change with it.
|
|
80
|
+
if (framePath.length === 0 && ids.length > 0)
|
|
81
|
+
ids[0] = ROOT_NODE_ID;
|
|
82
|
+
const elementsHandle = await scope.read(() => captured.getProperty('elements'), (handle) => scope.own(handle));
|
|
83
|
+
const elements = await collectElementHandles(scope, elementsHandle, nodes.length);
|
|
84
|
+
elements.forEach((element, index) => stage(ids[index], element));
|
|
85
|
+
let nodeCount = nodes.length;
|
|
86
|
+
let truncated = walkTruncated === true;
|
|
87
|
+
const frameChildren = new Map();
|
|
88
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
89
|
+
const selector = nodes[index].frameSelector;
|
|
90
|
+
if (selector === undefined)
|
|
91
|
+
continue;
|
|
92
|
+
// A frame with no document (detached, never loaded) shows nothing, so
|
|
93
|
+
// nothing is missing from the tree.
|
|
94
|
+
const frame = await scope.read(() => elements[index].contentFrame().catch(() => null));
|
|
95
|
+
if (frame === null)
|
|
96
|
+
continue;
|
|
97
|
+
// Only frames on the app's site enter observations. Third-party
|
|
98
|
+
// frames (ads, trackers, embeds) are not the agent's to read or act
|
|
99
|
+
// on - and a stalled ad frame must not tax the capture. They stay
|
|
100
|
+
// boundary nodes by design, so leaving them out is not truncation.
|
|
101
|
+
if (!isOnSiteFrame(frame.url(), deps.site))
|
|
102
|
+
continue;
|
|
103
|
+
// From here the frame's document is content the model should see: any
|
|
104
|
+
// reason it is not read (depth, budget, deadline, a failed or timed-out
|
|
105
|
+
// capture) leaves the tree incomplete, and the snapshot must say so.
|
|
106
|
+
if (framePath.length >= MAX_FRAME_DEPTH) {
|
|
107
|
+
truncated = true;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const remaining = budget - nodeCount;
|
|
111
|
+
if (remaining <= 0 || Date.now() >= scope.deadline) {
|
|
112
|
+
truncated = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const child = await scope.read(() => captureDocument({ ...deps, commit: stage }, frame, {
|
|
116
|
+
framePath: [...framePath, selector],
|
|
117
|
+
budget: remaining,
|
|
118
|
+
deadline: scope.deadline,
|
|
119
|
+
signal,
|
|
120
|
+
}).catch(() => undefined));
|
|
121
|
+
if (child === undefined) {
|
|
122
|
+
truncated = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
frameChildren.set(index, child.tree);
|
|
126
|
+
nodeCount += child.nodeCount;
|
|
127
|
+
truncated ||= child.truncated;
|
|
128
|
+
}
|
|
129
|
+
return { tree: assembleTree(nodes, ids, framePath, frameChildren), nodeCount, truncated };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* True when a frame document is on the app's site.
|
|
133
|
+
*
|
|
134
|
+
* `about:blank` and `srcdoc` documents inherit their parent's origin, so they
|
|
135
|
+
* are the app's own content (consent managers, editors) and always allowed; the
|
|
136
|
+
* parent frame was already admitted to be captured at all.
|
|
137
|
+
*
|
|
138
|
+
* A `data:` document is *not* admitted, even though its bytes are written by the
|
|
139
|
+
* page that embeds it. It has an opaque origin rather than an inherited one, and
|
|
140
|
+
* navigation denies the scheme by name alongside `file:` and `javascript:`.
|
|
141
|
+
*/
|
|
142
|
+
function isOnSiteFrame(url, site) {
|
|
143
|
+
if (url === '' || url === 'about:blank' || url === 'about:srcdoc')
|
|
144
|
+
return true;
|
|
145
|
+
if (site === undefined)
|
|
146
|
+
return false;
|
|
147
|
+
const protocol = URL.canParse(url) ? new URL(url).protocol : undefined;
|
|
148
|
+
return protocol !== 'data:' && sameSite(url, site);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Reads one element handle per observed node from the in-page element array.
|
|
152
|
+
* `asElement` types handles as `ElementHandle<Node>`, but the observation walk
|
|
153
|
+
* records `Element` nodes only, so the narrowing is safe by construction.
|
|
154
|
+
*/
|
|
155
|
+
async function collectElementHandles(scope, elementsHandle, count) {
|
|
156
|
+
const properties = await scope.read(() => elementsHandle.getProperties(), (handles) => {
|
|
157
|
+
for (const handle of handles.values())
|
|
158
|
+
scope.own(handle);
|
|
159
|
+
});
|
|
160
|
+
const elements = [];
|
|
161
|
+
for (let index = 0; index < count; index += 1) {
|
|
162
|
+
const property = properties.get(String(index));
|
|
163
|
+
const element = (property?.asElement() ?? null);
|
|
164
|
+
if (element === null) {
|
|
165
|
+
// The in-page array outlived its document (a navigation committed while
|
|
166
|
+
// the handles were being read back). The capture is repeatable.
|
|
167
|
+
throw new EngineError('NODE_STALE', `observation node ${index} lost its element`, {
|
|
168
|
+
retryable: true,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
elements.push(element);
|
|
172
|
+
}
|
|
173
|
+
return elements;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Rebuilds the observation tree from the depth-first node list. Descendants
|
|
177
|
+
* always follow their parent, so children are complete before a parent is
|
|
178
|
+
* built. Captured child documents attach under their iframe boundary nodes.
|
|
179
|
+
*/
|
|
180
|
+
function assembleTree(nodes, ids, framePath, frameChildren) {
|
|
181
|
+
if (nodes.length === 0 || ids.length === 0) {
|
|
182
|
+
// An empty document is what a navigation in flight looks like; a real page
|
|
183
|
+
// always has nodes, so the capture is worth repeating.
|
|
184
|
+
throw new EngineError('NODE_STALE', 'observation produced no nodes', { retryable: true });
|
|
185
|
+
}
|
|
186
|
+
const childLists = nodes.map(() => []);
|
|
187
|
+
const built = [];
|
|
188
|
+
for (let index = nodes.length - 1; index >= 0; index -= 1) {
|
|
189
|
+
const raw = nodes[index];
|
|
190
|
+
const embedded = frameChildren.get(index);
|
|
191
|
+
// A child document measured its nodes against its own viewport. Shifted by
|
|
192
|
+
// the boundary element's box and clipped to it, every box in the tree is
|
|
193
|
+
// in the top-level viewport's CSS pixels, the space the screenshot and a
|
|
194
|
+
// point tap share, and a child that overflows its frame cannot claim a
|
|
195
|
+
// point over the page around it. The shift is the frame's border box; a
|
|
196
|
+
// bordered iframe is off by its border width, which is within a tap
|
|
197
|
+
// target. A frame under a CSS transform is not unwound: its boxes are
|
|
198
|
+
// where the untransformed frame would put them.
|
|
199
|
+
if (embedded !== undefined)
|
|
200
|
+
childLists[index].unshift(placeInFrame(embedded, raw.rect));
|
|
201
|
+
const node = toSemanticNode({ id: ids[index], revision: '' }, raw, childLists[index], framePath);
|
|
202
|
+
built[index] = node;
|
|
203
|
+
if (raw.parent >= 0)
|
|
204
|
+
childLists[raw.parent].unshift(node);
|
|
205
|
+
}
|
|
206
|
+
return built[0];
|
|
207
|
+
}
|
|
208
|
+
/** The tree with every box translated into the frame's space and clipped to its box; a box left empty by the clip is dropped. */
|
|
209
|
+
function placeInFrame(node, frame) {
|
|
210
|
+
const { rect, ...rest } = node;
|
|
211
|
+
const placed = rect === undefined ? undefined : clipRect(offsetRect(rect, frame), frame);
|
|
212
|
+
return {
|
|
213
|
+
...rest,
|
|
214
|
+
...(placed === undefined ? {} : { rect: placed }),
|
|
215
|
+
...(node.children === undefined ? {} : { children: node.children.map((child) => placeInFrame(child, frame)) }),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function offsetRect(rect, by) {
|
|
219
|
+
return { x: rect.x + by.x, y: rect.y + by.y, width: rect.width, height: rect.height };
|
|
220
|
+
}
|
|
221
|
+
function clipRect(rect, bounds) {
|
|
222
|
+
const x = Math.max(rect.x, bounds.x);
|
|
223
|
+
const y = Math.max(rect.y, bounds.y);
|
|
224
|
+
const width = Math.min(rect.x + rect.width, bounds.x + bounds.width) - x;
|
|
225
|
+
const height = Math.min(rect.y + rect.height, bounds.y + bounds.height) - y;
|
|
226
|
+
return width > 0 && height > 0 ? { x, y, width, height } : undefined;
|
|
227
|
+
}
|
|
228
|
+
export function toSemanticNode(ref, raw, children = [], framePath = []) {
|
|
229
|
+
const states = {};
|
|
230
|
+
if (raw.states.checked !== null)
|
|
231
|
+
states['checked'] = raw.states.checked;
|
|
232
|
+
if (raw.states.disabled)
|
|
233
|
+
states['disabled'] = true;
|
|
234
|
+
if (raw.states.selected !== null)
|
|
235
|
+
states['selected'] = raw.states.selected;
|
|
236
|
+
if (raw.states.expanded !== null)
|
|
237
|
+
states['expanded'] = raw.states.expanded;
|
|
238
|
+
if (raw.states.pressed !== null)
|
|
239
|
+
states['pressed'] = raw.states.pressed;
|
|
240
|
+
if (raw.states.focused)
|
|
241
|
+
states['focused'] = true;
|
|
242
|
+
if (raw.states.hidden)
|
|
243
|
+
states['hidden'] = true;
|
|
244
|
+
if (raw.states.secure)
|
|
245
|
+
states['secure'] = true;
|
|
246
|
+
return {
|
|
247
|
+
ref,
|
|
248
|
+
...(raw.role !== null ? { role: raw.role } : {}),
|
|
249
|
+
...(raw.name !== null ? { name: raw.name } : {}),
|
|
250
|
+
...(raw.text !== null ? { text: raw.text } : {}),
|
|
251
|
+
...(raw.value !== null ? { value: raw.value } : {}),
|
|
252
|
+
...(raw.testId !== null ? { testId: raw.testId } : {}),
|
|
253
|
+
inputPurpose: raw.inputPurpose,
|
|
254
|
+
states,
|
|
255
|
+
...(raw.level !== null ? { level: raw.level } : {}),
|
|
256
|
+
attributes: raw.attributes,
|
|
257
|
+
rect: raw.rect,
|
|
258
|
+
...(framePath.length > 0 ? { framePath } : {}),
|
|
259
|
+
...(children.length > 0 ? { children } : {}),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=observation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observation.js","sourceRoot":"","sources":["../src/observation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Masked pixel capture for an observation. */
|
|
2
|
+
import type { Locator, Page } from 'playwright';
|
|
3
|
+
import type { ObservationPixels, OperationContext, ViewportSize } from 'e2e/engine';
|
|
4
|
+
/**
|
|
5
|
+
* Two mask locators per frame Playwright can reach, covering every secure
|
|
6
|
+
* field: one through light DOM and open shadow roots, one through the closed
|
|
7
|
+
* roots the reader also walks. Sweeping every frame rather than only the
|
|
8
|
+
* frames an observation walked makes the masked set a superset of the observed
|
|
9
|
+
* secure set - the direction that is safe. Shared by the observation pixels
|
|
10
|
+
* and the artifact screenshot so both are redacted at the source by the same
|
|
11
|
+
* rule.
|
|
12
|
+
*/
|
|
13
|
+
export declare function secureFieldMasks(page: Page): Locator[];
|
|
14
|
+
/** Screenshot options that apply the secure-field masks; empty when there is no frame to mask. */
|
|
15
|
+
export declare function maskOptions(masks: readonly Locator[]): Partial<{
|
|
16
|
+
mask: Locator[];
|
|
17
|
+
maskColor: string;
|
|
18
|
+
}>;
|
|
19
|
+
export interface PixelCapture {
|
|
20
|
+
readonly pixels: ObservationPixels;
|
|
21
|
+
readonly maskedRegionCount: number;
|
|
22
|
+
/** Every secure-field probe completed, independently of semantic capture. */
|
|
23
|
+
readonly maskingProven: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Captures masked viewport pixels.
|
|
27
|
+
*
|
|
28
|
+
* Secure fields are covered before the image leaves the engine and the covered
|
|
29
|
+
* regions are counted, so the runner can prove the image is at least as redacted
|
|
30
|
+
* as the tree (the runner's clearance check requires the masked set to be a
|
|
31
|
+
* superset of the observed secure set, which `secureFieldMasks` guarantees).
|
|
32
|
+
*
|
|
33
|
+
* `scale: 'css'` keeps the image in the CSS pixel space every observed node rect
|
|
34
|
+
* already uses, which is what lets the runner hit-test a point against the same
|
|
35
|
+
* tree.
|
|
36
|
+
*/
|
|
37
|
+
export declare function capturePixels(page: Page, operation: OperationContext, viewport: ViewportSize): Promise<PixelCapture>;
|
|
38
|
+
/**
|
|
39
|
+
* Reads pixel dimensions out of PNG bytes.
|
|
40
|
+
*
|
|
41
|
+
* The dimensions reported with a screenshot must be the dimensions of the bytes
|
|
42
|
+
* the model actually receives: every coordinate it returns is relative to them,
|
|
43
|
+
* so a viewport value that disagrees with the capture displaces every point
|
|
44
|
+
* silently. PNG puts IHDR first, so width and height are at fixed offsets and no
|
|
45
|
+
* decoder is needed.
|
|
46
|
+
*/
|
|
47
|
+
export declare function readPngSize(data: Uint8Array): {
|
|
48
|
+
width: number;
|
|
49
|
+
height: number;
|
|
50
|
+
} | null;
|
|
51
|
+
//# sourceMappingURL=observe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAE/C,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAepF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,EAAE,CAOtD;AAED,kGAAkG;AAClG,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAEtG;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,gBAAgB,EAC3B,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,YAAY,CAAC,CAkCvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAStF"}
|
package/dist/observe.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** Masked pixel capture for an observation. */
|
|
2
|
+
import { CaptureScope } from './capture-scope.js';
|
|
3
|
+
import { CLOSED_SHADOW_SELECTOR_ENGINE, SECURE_FIELD_SELECTOR } from './read-node.js';
|
|
4
|
+
/**
|
|
5
|
+
* Budget for one masked screenshot, still capped by the operation timeout. A
|
|
6
|
+
* heavy page that cannot produce a frame promptly must cost the observation a
|
|
7
|
+
* moment, not the whole step: the caller degrades to tree-only input rather
|
|
8
|
+
* than failing.
|
|
9
|
+
*/
|
|
10
|
+
const PIXEL_CAPTURE_TIMEOUT_MS = 10_000;
|
|
11
|
+
/** Opaque fill covering every masked region. */
|
|
12
|
+
const MASK_COLOR = '#000000';
|
|
13
|
+
/**
|
|
14
|
+
* Two mask locators per frame Playwright can reach, covering every secure
|
|
15
|
+
* field: one through light DOM and open shadow roots, one through the closed
|
|
16
|
+
* roots the reader also walks. Sweeping every frame rather than only the
|
|
17
|
+
* frames an observation walked makes the masked set a superset of the observed
|
|
18
|
+
* secure set - the direction that is safe. Shared by the observation pixels
|
|
19
|
+
* and the artifact screenshot so both are redacted at the source by the same
|
|
20
|
+
* rule.
|
|
21
|
+
*/
|
|
22
|
+
export function secureFieldMasks(page) {
|
|
23
|
+
return page
|
|
24
|
+
.frames()
|
|
25
|
+
.flatMap((frame) => [
|
|
26
|
+
frame.locator(SECURE_FIELD_SELECTOR),
|
|
27
|
+
frame.locator(`${CLOSED_SHADOW_SELECTOR_ENGINE}=${SECURE_FIELD_SELECTOR}`),
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
/** Screenshot options that apply the secure-field masks; empty when there is no frame to mask. */
|
|
31
|
+
export function maskOptions(masks) {
|
|
32
|
+
return masks.length === 0 ? {} : { mask: [...masks], maskColor: MASK_COLOR };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Captures masked viewport pixels.
|
|
36
|
+
*
|
|
37
|
+
* Secure fields are covered before the image leaves the engine and the covered
|
|
38
|
+
* regions are counted, so the runner can prove the image is at least as redacted
|
|
39
|
+
* as the tree (the runner's clearance check requires the masked set to be a
|
|
40
|
+
* superset of the observed secure set, which `secureFieldMasks` guarantees).
|
|
41
|
+
*
|
|
42
|
+
* `scale: 'css'` keeps the image in the CSS pixel space every observed node rect
|
|
43
|
+
* already uses, which is what lets the runner hit-test a point against the same
|
|
44
|
+
* tree.
|
|
45
|
+
*/
|
|
46
|
+
export async function capturePixels(page, operation, viewport) {
|
|
47
|
+
const deadline = Date.now() + operation.timeoutMs;
|
|
48
|
+
const scope = new CaptureScope(deadline, operation.signal);
|
|
49
|
+
return scope.run(async () => {
|
|
50
|
+
const masks = secureFieldMasks(page);
|
|
51
|
+
// A frame that detaches mid-sweep contributes nothing to the count, which can
|
|
52
|
+
// only push the observation toward withholding the image.
|
|
53
|
+
const counts = await scope.read(() => Promise.allSettled(masks.map((mask) => mask.count())));
|
|
54
|
+
const image = await scope.read(() => page.screenshot({
|
|
55
|
+
type: 'png',
|
|
56
|
+
scale: 'css',
|
|
57
|
+
animations: 'disabled',
|
|
58
|
+
caret: 'hide',
|
|
59
|
+
timeout: Math.max(1, Math.min(deadline - Date.now(), PIXEL_CAPTURE_TIMEOUT_MS)),
|
|
60
|
+
...maskOptions(masks),
|
|
61
|
+
}));
|
|
62
|
+
const data = new Uint8Array(image);
|
|
63
|
+
// The bytes are the authority on their own size. `scale: 'css'` is asked for
|
|
64
|
+
// precisely so one image pixel is one CSS pixel, but the ratio is measured
|
|
65
|
+
// rather than assumed: if a capture ever comes back at device scale, reporting
|
|
66
|
+
// the viewport instead would displace every coordinate the model reads off it.
|
|
67
|
+
const size = readPngSize(data) ?? { width: viewport.width, height: viewport.height };
|
|
68
|
+
return {
|
|
69
|
+
pixels: {
|
|
70
|
+
data,
|
|
71
|
+
mediaType: 'image/png',
|
|
72
|
+
width: size.width,
|
|
73
|
+
height: size.height,
|
|
74
|
+
scale: viewport.width > 0 ? size.width / viewport.width : 1,
|
|
75
|
+
},
|
|
76
|
+
maskedRegionCount: counts.reduce((total, count) => total + (count.status === 'fulfilled' ? count.value : 0), 0),
|
|
77
|
+
maskingProven: masks.length > 0 && counts.every((count) => count.status === 'fulfilled'),
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reads pixel dimensions out of PNG bytes.
|
|
83
|
+
*
|
|
84
|
+
* The dimensions reported with a screenshot must be the dimensions of the bytes
|
|
85
|
+
* the model actually receives: every coordinate it returns is relative to them,
|
|
86
|
+
* so a viewport value that disagrees with the capture displaces every point
|
|
87
|
+
* silently. PNG puts IHDR first, so width and height are at fixed offsets and no
|
|
88
|
+
* decoder is needed.
|
|
89
|
+
*/
|
|
90
|
+
export function readPngSize(data) {
|
|
91
|
+
if (data.byteLength < 24)
|
|
92
|
+
return null;
|
|
93
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
94
|
+
if (view.getUint32(0) !== 0x89504e47 || view.getUint32(4) !== 0x0d0a1a0a)
|
|
95
|
+
return null;
|
|
96
|
+
if (view.getUint32(12) !== 0x49484452)
|
|
97
|
+
return null;
|
|
98
|
+
const width = view.getUint32(16);
|
|
99
|
+
const height = view.getUint32(20);
|
|
100
|
+
if (width === 0 || height === 0)
|
|
101
|
+
return null;
|
|
102
|
+
return { width, height };
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=observe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observe.js","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** One deadline for connection recovery and the operation it precedes. */
|
|
2
|
+
import { EngineError } from 'e2e/engine';
|
|
3
|
+
export interface ConnectionBudget {
|
|
4
|
+
readonly timeoutMs: number;
|
|
5
|
+
readonly signal: AbortSignal;
|
|
6
|
+
}
|
|
7
|
+
/** Preserves the reason for an engine-owned deadline without relabeling caller cancellation. */
|
|
8
|
+
export declare function connectionAbort(signal: AbortSignal, label: string): EngineError;
|
|
9
|
+
/** Bounds the entire transition, aborts abandoned work, and never grants a fresh dispatch budget. */
|
|
10
|
+
export declare function withConnectionBudget<T>(budget: ConnectionBudget, label: string, work: (remaining: () => ConnectionBudget) => Promise<T>): Promise<T>;
|
|
11
|
+
//# sourceMappingURL=operation-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-budget.d.ts","sourceRoot":"","sources":["../src/operation-budget.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,OAAO,EAAE,WAAW,EAAa,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAI/E;AAED,qGAAqG;AACrG,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,GACtD,OAAO,CAAC,CAAC,CAAC,CAwBZ"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** One deadline for connection recovery and the operation it precedes. */
|
|
2
|
+
import { EngineError, raceAbort } from 'e2e/engine';
|
|
3
|
+
/** Preserves the reason for an engine-owned deadline without relabeling caller cancellation. */
|
|
4
|
+
export function connectionAbort(signal, label) {
|
|
5
|
+
return signal.reason instanceof EngineError && signal.reason.code === 'OPERATION_TIMEOUT'
|
|
6
|
+
? signal.reason
|
|
7
|
+
: new EngineError('CANCELLED', `${label} cancelled`, { retryable: false });
|
|
8
|
+
}
|
|
9
|
+
/** Bounds the entire transition, aborts abandoned work, and never grants a fresh dispatch budget. */
|
|
10
|
+
export async function withConnectionBudget(budget, label, work) {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const signal = AbortSignal.any([budget.signal, controller.signal]);
|
|
13
|
+
const endsAt = Date.now() + budget.timeoutMs;
|
|
14
|
+
const timeout = () => new EngineError('OPERATION_TIMEOUT', `${label} timed out`, { retryable: false });
|
|
15
|
+
const timer = setTimeout(() => controller.abort(timeout()), Math.max(0, budget.timeoutMs));
|
|
16
|
+
const remaining = () => {
|
|
17
|
+
if (signal.aborted)
|
|
18
|
+
throw connectionAbort(signal, label);
|
|
19
|
+
const timeoutMs = endsAt - Date.now();
|
|
20
|
+
if (timeoutMs <= 0) {
|
|
21
|
+
controller.abort(timeout());
|
|
22
|
+
throw connectionAbort(signal, label);
|
|
23
|
+
}
|
|
24
|
+
return { signal, timeoutMs };
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
return await raceAbort(() => work(remaining), signal, label);
|
|
28
|
+
}
|
|
29
|
+
catch (cause) {
|
|
30
|
+
if (budget.signal.aborted)
|
|
31
|
+
throw connectionAbort(budget.signal, label);
|
|
32
|
+
if (signal.aborted)
|
|
33
|
+
throw connectionAbort(signal, label);
|
|
34
|
+
throw cause;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=operation-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-budget.js","sourceRoot":"","sources":["../src/operation-budget.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAOpD,gGAAgG;AAChG,MAAM,UAAU,eAAe,CAAC,MAAmB,EAAE,KAAa;IAChE,OAAO,MAAM,CAAC,MAAM,YAAY,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,mBAAmB;QACvF,CAAC,CAAC,MAAM,CAAC,MAAM;QACf,CAAC,CAAC,IAAI,WAAW,CAAC,WAAW,EAAE,GAAG,KAAK,YAAY,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,qGAAqG;AACrG,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAwB,EACxB,KAAa,EACb,IAAuD;IAEvD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;IAC7C,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,WAAW,CAAC,mBAAmB,EAAE,GAAG,KAAK,YAAY,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IACvG,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IAC3F,MAAM,SAAS,GAAG,GAAqB,EAAE;QACvC,IAAI,MAAM,CAAC,OAAO;YAAE,MAAM,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACzD,MAAM,SAAS,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5B,MAAM,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC/B,CAAC,CAAC;IACF,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACvE,IAAI,MAAM,CAAC,OAAO;YAAE,MAAM,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACzD,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protected-preview access, scoped to the app's site: the configured headers
|
|
3
|
+
* ride only requests bound for the site, and basic-auth credentials answer
|
|
4
|
+
* a challenge wherever one is issued. The surface calls these on every
|
|
5
|
+
* context it creates; nothing here holds state.
|
|
6
|
+
*/
|
|
7
|
+
import type { BrowserContext } from 'playwright';
|
|
8
|
+
import type { WebBasicAuth } from './surface.ts';
|
|
9
|
+
/** The credentials Playwright answers an HTTP authentication challenge with. */
|
|
10
|
+
export interface PlaywrightHttpCredential {
|
|
11
|
+
readonly username: string;
|
|
12
|
+
readonly password: string;
|
|
13
|
+
}
|
|
14
|
+
/** Header names lowercased, as Playwright reports a request's own headers. */
|
|
15
|
+
export declare function lowercaseNames(headers: Readonly<Record<string, string>>): Readonly<Record<string, string>>;
|
|
16
|
+
/**
|
|
17
|
+
* Adds the configured headers to every request bound for the app's site,
|
|
18
|
+
* via a context route that falls back to the network. A request for any
|
|
19
|
+
* other site is not routed at all; without a site there is no policy and
|
|
20
|
+
* nothing is routed. Nothing awaits a route handler, so a fallback that
|
|
21
|
+
* fails (the page closed under the request) is dropped rather than left to
|
|
22
|
+
* surface as an unhandled rejection.
|
|
23
|
+
*/
|
|
24
|
+
export declare function installSiteHeaders(context: BrowserContext, site: string | undefined, headers: Readonly<Record<string, string>> | undefined): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* The basic-auth credentials a context answers a challenge with, as
|
|
27
|
+
* Playwright's own `httpCredentials` does: on a 401 from any origin. A site
|
|
28
|
+
* cannot be enumerated into the exact origins Playwright scopes by, and a
|
|
29
|
+
* challenge is answered only where one is issued.
|
|
30
|
+
*/
|
|
31
|
+
export declare function httpCredentials(basicAuth: WebBasicAuth | undefined): PlaywrightHttpCredential | undefined;
|
|
32
|
+
//# sourceMappingURL=protected-app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protected-app.d.ts","sourceRoot":"","sources":["../src/protected-app.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,gFAAgF;AAChF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAE1G;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GACpD,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,YAAY,GAAG,SAAS,GAAG,wBAAwB,GAAG,SAAS,CAIzG"}
|