@volter-ai-dev/supercode-browser-playwright 0.1.0 → 0.2.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 +36 -6
- package/dist/executor.d.ts +54 -10
- package/dist/executor.mjs +312 -32
- package/dist/executor.mjs.map +4 -4
- package/dist/guard.d.ts +63 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +310 -32
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,142 @@
|
|
|
1
1
|
// src/executor.ts
|
|
2
2
|
import { errors } from "playwright-core";
|
|
3
3
|
|
|
4
|
+
// src/guard.ts
|
|
5
|
+
var UnidentifiedTarget = class extends Error {
|
|
6
|
+
};
|
|
7
|
+
var HostElementTarget = class extends Error {
|
|
8
|
+
};
|
|
9
|
+
var WORLD = "supercode-guard";
|
|
10
|
+
var GROUP = "supercode-guard";
|
|
11
|
+
var MAX_NODES = 8;
|
|
12
|
+
var MAX_ANCESTORS = 6;
|
|
13
|
+
function findInWorld(query) {
|
|
14
|
+
const onHost = (element) => {
|
|
15
|
+
if (!query.host) return false;
|
|
16
|
+
for (let node = element; node; ) {
|
|
17
|
+
if (node.matches(query.host)) return true;
|
|
18
|
+
const parent = node.parentElement;
|
|
19
|
+
node = parent ?? (node.getRootNode().host ?? null);
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
if (query.kind === "point") {
|
|
24
|
+
let element = document.elementFromPoint(query.x, query.y);
|
|
25
|
+
if (!element) return "Nothing is at that point.";
|
|
26
|
+
if (onHost(element)) return "host";
|
|
27
|
+
while (element.shadowRoot) {
|
|
28
|
+
const inner = element.shadowRoot.elementFromPoint(query.x, query.y);
|
|
29
|
+
if (!inner || inner === element) break;
|
|
30
|
+
element = inner;
|
|
31
|
+
if (onHost(element)) return "host";
|
|
32
|
+
}
|
|
33
|
+
if (/^(IFRAME|FRAME|OBJECT|EMBED)$/.test(element.tagName))
|
|
34
|
+
return "The point is inside an embedded frame, which the guard cannot inspect.";
|
|
35
|
+
return [element];
|
|
36
|
+
}
|
|
37
|
+
const { box } = query;
|
|
38
|
+
const found = [];
|
|
39
|
+
const near = (a, b) => Math.abs(a - b) <= 1;
|
|
40
|
+
const visit = (root) => {
|
|
41
|
+
for (const element of Array.from(root.querySelectorAll("*"))) {
|
|
42
|
+
if (found.length >= query.max) return;
|
|
43
|
+
const rect = element.getBoundingClientRect();
|
|
44
|
+
if (near(rect.x, box.x) && near(rect.y, box.y) && near(rect.width, box.width) && near(rect.height, box.height))
|
|
45
|
+
found.push(element);
|
|
46
|
+
if (element.shadowRoot) visit(element.shadowRoot);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
visit(document);
|
|
50
|
+
return found.length ? found : "The element could not be found in the page's top document (it may be inside an embedded frame or a closed shadow root).";
|
|
51
|
+
}
|
|
52
|
+
var BrowserSideTargets = class {
|
|
53
|
+
constructor(page, host) {
|
|
54
|
+
this.page = page;
|
|
55
|
+
this.host = host;
|
|
56
|
+
}
|
|
57
|
+
session = null;
|
|
58
|
+
/** The element a resolved handle is, scrolled into view. */
|
|
59
|
+
async ofHandle(handle, timeout) {
|
|
60
|
+
await handle.scrollIntoViewIfNeeded({ timeout });
|
|
61
|
+
const box = await handle.boundingBox();
|
|
62
|
+
if (!box || box.width === 0 || box.height === 0)
|
|
63
|
+
throw new UnidentifiedTarget("The element has no box on the page.");
|
|
64
|
+
return await this.resolve({ kind: "box", box, host: this.host, max: MAX_NODES });
|
|
65
|
+
}
|
|
66
|
+
/** The element at a viewport point. */
|
|
67
|
+
async atPoint(x, y) {
|
|
68
|
+
return { ...await this.resolve({ kind: "point", x, y, host: this.host }), point: { x, y } };
|
|
69
|
+
}
|
|
70
|
+
cdp() {
|
|
71
|
+
this.session ??= this.page.context().newCDPSession(this.page).catch((error) => {
|
|
72
|
+
this.session = null;
|
|
73
|
+
throw error;
|
|
74
|
+
});
|
|
75
|
+
return this.session;
|
|
76
|
+
}
|
|
77
|
+
async resolve(query) {
|
|
78
|
+
const cdp = await this.cdp();
|
|
79
|
+
const tree = await cdp.send("Page.getFrameTree");
|
|
80
|
+
const world = await cdp.send("Page.createIsolatedWorld", { frameId: tree.frameTree.frame.id, worldName: WORLD });
|
|
81
|
+
try {
|
|
82
|
+
const evaluated = await cdp.send("Runtime.evaluate", {
|
|
83
|
+
expression: `(${findInWorld.toString()})(${JSON.stringify(query)})`,
|
|
84
|
+
contextId: world.executionContextId,
|
|
85
|
+
objectGroup: GROUP,
|
|
86
|
+
returnByValue: false
|
|
87
|
+
});
|
|
88
|
+
if (evaluated.exceptionDetails)
|
|
89
|
+
throw new UnidentifiedTarget(`The target could not be resolved: ${evaluated.exceptionDetails.text ?? "exception"}.`);
|
|
90
|
+
if (evaluated.result.type === "string") {
|
|
91
|
+
if (evaluated.result.value === "host") throw new HostElementTarget("The point is on the host's own interface, which agents cannot operate.");
|
|
92
|
+
throw new UnidentifiedTarget(String(evaluated.result.value));
|
|
93
|
+
}
|
|
94
|
+
if (!evaluated.result.objectId) throw new UnidentifiedTarget("The target could not be resolved.");
|
|
95
|
+
const properties = await cdp.send("Runtime.getProperties", { objectId: evaluated.result.objectId, ownProperties: true });
|
|
96
|
+
const objects = properties.result.filter((property) => /^\d+$/.test(property.name) && property.value?.objectId).map((property) => property.value.objectId);
|
|
97
|
+
if (!objects.length) throw new UnidentifiedTarget("The target could not be resolved.");
|
|
98
|
+
const nodes = [];
|
|
99
|
+
const ancestors = [];
|
|
100
|
+
const seen = /* @__PURE__ */ new Set();
|
|
101
|
+
for (const objectId of objects) {
|
|
102
|
+
const described = await cdp.send("DOM.describeNode", { objectId });
|
|
103
|
+
const attributes = {};
|
|
104
|
+
const list = described.node.attributes ?? [];
|
|
105
|
+
for (let index = 0; index + 1 < list.length; index += 2) attributes[list[index].toLowerCase()] = list[index + 1];
|
|
106
|
+
const ax = await this.accessibility(cdp, described.node.backendNodeId);
|
|
107
|
+
nodes.push({
|
|
108
|
+
backendNodeId: described.node.backendNodeId,
|
|
109
|
+
tag: described.node.nodeName.toLowerCase(),
|
|
110
|
+
attributes,
|
|
111
|
+
role: ax.self?.role ?? "",
|
|
112
|
+
name: ax.self?.name ?? ""
|
|
113
|
+
});
|
|
114
|
+
seen.add(described.node.backendNodeId);
|
|
115
|
+
for (const ancestor of ax.ancestors) {
|
|
116
|
+
if (ancestors.length >= MAX_ANCESTORS || seen.has(ancestor.backendNodeId)) continue;
|
|
117
|
+
seen.add(ancestor.backendNodeId);
|
|
118
|
+
ancestors.push(ancestor);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { url: this.page.url(), nodes, ancestors };
|
|
122
|
+
} finally {
|
|
123
|
+
await cdp.send("Runtime.releaseObjectGroup", { objectGroup: GROUP }).catch(() => void 0);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async accessibility(cdp, backendNodeId) {
|
|
127
|
+
const tree = await cdp.send("Accessibility.getPartialAXTree", { backendNodeId, fetchRelatives: true });
|
|
128
|
+
const byId = new Map(tree.nodes.map((node) => [node.nodeId, node]));
|
|
129
|
+
const text = (value) => typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
130
|
+
const self = tree.nodes.find((node) => node.backendDOMNodeId === backendNodeId) ?? null;
|
|
131
|
+
const ancestors = [];
|
|
132
|
+
for (let node = self?.parentId ? byId.get(self.parentId) : void 0; node && ancestors.length < MAX_ANCESTORS; node = node.parentId ? byId.get(node.parentId) : void 0) {
|
|
133
|
+
if (node.ignored || typeof node.backendDOMNodeId !== "number") continue;
|
|
134
|
+
ancestors.push({ backendNodeId: node.backendDOMNodeId, tag: "", attributes: {}, role: text(node.role?.value), name: text(node.name?.value) });
|
|
135
|
+
}
|
|
136
|
+
return { self: self ? { role: text(self.role?.value), name: text(self.name?.value) } : null, ancestors };
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
4
140
|
// src/protocol.ts
|
|
5
141
|
var SUPERCODE_BROWSER_PROVIDER_PROTOCOL = "supercode/browser-provider-v1";
|
|
6
142
|
var SUPERCODE_BROWSER_OPERATION_PROTOCOL = "supercode/browser-operation-v1";
|
|
@@ -253,6 +389,49 @@ var PLAYWRIGHT_FEATURES = Object.freeze([
|
|
|
253
389
|
"page.screenshot.png",
|
|
254
390
|
"page.evaluate"
|
|
255
391
|
]);
|
|
392
|
+
var SNAPSHOT_EXCLUDE_LIMIT = 16;
|
|
393
|
+
function collectRefs(nodes, into) {
|
|
394
|
+
if (Array.isArray(nodes)) {
|
|
395
|
+
for (const node2 of nodes) collectRefs(node2, into);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (!nodes || typeof nodes !== "object") return;
|
|
399
|
+
const node = nodes;
|
|
400
|
+
if (typeof node.ref === "string") into.add(node.ref);
|
|
401
|
+
collectRefs(node.children, into);
|
|
402
|
+
}
|
|
403
|
+
function snapshotWithoutRefs(text, refs) {
|
|
404
|
+
if (refs.size === 0) return text;
|
|
405
|
+
const root = { text: "", indent: -1, children: [], removed: false };
|
|
406
|
+
const stack = [root];
|
|
407
|
+
const lines = [];
|
|
408
|
+
for (const raw of text.split("\n")) {
|
|
409
|
+
const indent = raw.length - raw.trimStart().length;
|
|
410
|
+
const line = { text: raw, indent, children: [], removed: false };
|
|
411
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) stack.pop();
|
|
412
|
+
stack[stack.length - 1].children.push(line);
|
|
413
|
+
stack.push(line);
|
|
414
|
+
lines.push(line);
|
|
415
|
+
}
|
|
416
|
+
const refOf = (line) => /\[ref=([^\]\s]+)\]/.exec(line.text)?.[1];
|
|
417
|
+
const remove = (line) => {
|
|
418
|
+
line.removed = true;
|
|
419
|
+
for (const child of line.children) remove(child);
|
|
420
|
+
};
|
|
421
|
+
const visit = (line) => {
|
|
422
|
+
const ref = refOf(line);
|
|
423
|
+
if (ref !== void 0 && refs.has(ref)) {
|
|
424
|
+
remove(line);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const before = line.children.length;
|
|
428
|
+
for (const child of line.children) visit(child);
|
|
429
|
+
if (line !== root && ref === void 0 && before > 0 && line.children.every((child) => child.removed))
|
|
430
|
+
line.removed = true;
|
|
431
|
+
};
|
|
432
|
+
visit(root);
|
|
433
|
+
return lines.filter((line) => !line.removed).map((line) => line.text).join("\n");
|
|
434
|
+
}
|
|
256
435
|
var REVISION_KEY = "__supercodeBrowserRevision";
|
|
257
436
|
function installRevisionCounter(key) {
|
|
258
437
|
const scope = globalThis;
|
|
@@ -386,9 +565,13 @@ var PlaywrightOperationExecutor = class {
|
|
|
386
565
|
page;
|
|
387
566
|
options;
|
|
388
567
|
ready;
|
|
568
|
+
targets;
|
|
569
|
+
/** Where the mouse is: `page.mouse` keeps it, but does not report it. */
|
|
570
|
+
mouse = { x: 0, y: 0 };
|
|
389
571
|
constructor(page, options) {
|
|
390
572
|
this.page = page;
|
|
391
573
|
this.options = options;
|
|
574
|
+
this.targets = new BrowserSideTargets(page, options.snapshotExclude ?? null);
|
|
392
575
|
this.ready = (async () => {
|
|
393
576
|
await page.addInitScript(installRevisionCounter, REVISION_KEY);
|
|
394
577
|
await page.evaluate(installRevisionCounter, REVISION_KEY).catch(() => void 0);
|
|
@@ -462,10 +645,68 @@ var PlaywrightOperationExecutor = class {
|
|
|
462
645
|
throw error;
|
|
463
646
|
}
|
|
464
647
|
}
|
|
465
|
-
/**
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
648
|
+
/**
|
|
649
|
+
* Resolves the locator's element once (waiting up to the action budget),
|
|
650
|
+
* asks the host's policy about it, and returns the handle the action must
|
|
651
|
+
* use, so the element guarded is the element acted on. Without a policy the
|
|
652
|
+
* action runs on the locator. Resolution or description failing refuses.
|
|
653
|
+
*/
|
|
654
|
+
async guarded(locator, action, value) {
|
|
655
|
+
const guard = this.options.actionGuard;
|
|
656
|
+
if (!guard) return null;
|
|
657
|
+
let handle;
|
|
658
|
+
try {
|
|
659
|
+
handle = await locator.elementHandle({ timeout: ACTION_TIMEOUT_MS });
|
|
660
|
+
} catch (error) {
|
|
661
|
+
if (error instanceof errors.TimeoutError)
|
|
662
|
+
throw new BrowserActionRefusal("NOT_FOUND", `${action} found no element for the locator in ${ACTION_TIMEOUT_MS}ms.`);
|
|
663
|
+
throw error;
|
|
664
|
+
}
|
|
665
|
+
if (!handle) throw new BrowserActionRefusal("NOT_FOUND", `${action} found no element for the locator.`);
|
|
666
|
+
try {
|
|
667
|
+
await guard({
|
|
668
|
+
action,
|
|
669
|
+
target: await this.describe(() => this.targets.ofHandle(handle, ACTION_TIMEOUT_MS)),
|
|
670
|
+
...value !== void 0 ? { value } : {}
|
|
671
|
+
});
|
|
672
|
+
} catch (error) {
|
|
673
|
+
await handle.dispose().catch(() => void 0);
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
676
|
+
return handle;
|
|
677
|
+
}
|
|
678
|
+
/** Asks the host's policy about the element at a viewport point. */
|
|
679
|
+
async guardPoint(action, x, y) {
|
|
680
|
+
const guard = this.options.actionGuard;
|
|
681
|
+
if (!guard) return;
|
|
682
|
+
await guard({ action, target: await this.describe(() => this.targets.atPoint(x, y)) });
|
|
683
|
+
}
|
|
684
|
+
/** A browser-side description, or the refusal that replaces the action. */
|
|
685
|
+
async describe(run) {
|
|
686
|
+
try {
|
|
687
|
+
return await run();
|
|
688
|
+
} catch (error) {
|
|
689
|
+
if (error instanceof HostElementTarget) throw new BrowserActionRefusal("UNSUPPORTED", error.message);
|
|
690
|
+
if (error instanceof UnidentifiedTarget)
|
|
691
|
+
throw new BrowserActionRefusal("UNSUPPORTED", `${error.message} The action was refused because its target could not be checked.`);
|
|
692
|
+
throw new BrowserActionRefusal(
|
|
693
|
+
"UNSUPPORTED",
|
|
694
|
+
`The action was refused because its target could not be checked: ${errorMessage(error)}`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/** The aria refs of the elements `snapshotExclude` matches, and of their contents. */
|
|
699
|
+
async excludedRefs() {
|
|
700
|
+
const refs = /* @__PURE__ */ new Set();
|
|
701
|
+
const selector = this.options.snapshotExclude;
|
|
702
|
+
if (!selector) return refs;
|
|
703
|
+
const matches = this.page.locator(selector);
|
|
704
|
+
const count = Math.min(await matches.count().catch(() => 0), SNAPSHOT_EXCLUDE_LIMIT);
|
|
705
|
+
for (let index = 0; index < count; index += 1) {
|
|
706
|
+
const nodes = await matches.nth(index).ariaSnapshotJSON({ mode: "ai", timeout: ACTION_TIMEOUT_MS }).catch(() => null);
|
|
707
|
+
collectRefs(nodes, refs);
|
|
708
|
+
}
|
|
709
|
+
return refs;
|
|
469
710
|
}
|
|
470
711
|
async revision() {
|
|
471
712
|
const value = await this.page.evaluate((key) => globalThis[key], REVISION_KEY).catch(() => void 0);
|
|
@@ -491,7 +732,8 @@ var PlaywrightOperationExecutor = class {
|
|
|
491
732
|
if (call.operation === "browser.snapshot") {
|
|
492
733
|
await this.refreshRefs(input.locator);
|
|
493
734
|
const scoped = input.locator ? this.locator(input.locator).first() : null;
|
|
494
|
-
const
|
|
735
|
+
const excluded = await this.excludedRefs();
|
|
736
|
+
const text = snapshotWithoutRefs(scoped ? await this.act(scoped, "ariaSnapshot", () => scoped.ariaSnapshot({ mode: "ai", timeout: ACTION_TIMEOUT_MS })) : await page.ariaSnapshot({ mode: "ai", timeout: ACTION_TIMEOUT_MS }), excluded);
|
|
495
737
|
const bounded = text.slice(0, 64e3);
|
|
496
738
|
return this.success(call.operation, { text: bounded, truncated: bounded.length < text.length });
|
|
497
739
|
}
|
|
@@ -527,35 +769,48 @@ var PlaywrightOperationExecutor = class {
|
|
|
527
769
|
if (call.operation === "browser.script") return this.script(call);
|
|
528
770
|
if (call.operation === "browser.mouse") {
|
|
529
771
|
const action = input.action;
|
|
530
|
-
|
|
531
|
-
|
|
772
|
+
const at = typeof input.x === "number" && typeof input.y === "number" ? { x: input.x, y: input.y } : this.mouse;
|
|
773
|
+
if (action === "move") await this.guardPoint("hover", at.x, at.y);
|
|
774
|
+
else await this.guardPoint(action === "click" ? "click" : action === "down" ? "mousedown" : "mouseup", at.x, at.y);
|
|
775
|
+
if (action === "move") await page.mouse.move(at.x, at.y);
|
|
776
|
+
else if (action === "click") await page.mouse.click(at.x, at.y);
|
|
532
777
|
else if (action === "down") await page.mouse.down();
|
|
533
778
|
else await page.mouse.up();
|
|
779
|
+
if (action === "move" || action === "click") this.mouse = { x: at.x, y: at.y };
|
|
534
780
|
return this.success(call.operation, { action, ...typeof input.x === "number" ? { x: input.x, y: input.y } : {} });
|
|
535
781
|
}
|
|
536
782
|
if (call.operation === "browser.wheel") {
|
|
537
783
|
const deltaX = typeof input.deltaX === "number" ? input.deltaX : 0;
|
|
538
784
|
const deltaY = typeof input.deltaY === "number" ? input.deltaY : 0;
|
|
785
|
+
await this.guardPoint("wheel", this.mouse.x, this.mouse.y);
|
|
539
786
|
await page.mouse.wheel(deltaX, deltaY);
|
|
540
787
|
return this.success(call.operation, { deltaX, deltaY });
|
|
541
788
|
}
|
|
542
789
|
if (call.operation === "browser.drag") {
|
|
543
790
|
const resolve = async (endpoint) => {
|
|
544
|
-
if (endpoint.locator === void 0)
|
|
791
|
+
if (endpoint.locator === void 0) {
|
|
792
|
+
const point = { x: endpoint.x, y: endpoint.y };
|
|
793
|
+
await this.guardPoint("drag", point.x, point.y);
|
|
794
|
+
return point;
|
|
795
|
+
}
|
|
545
796
|
await this.refreshRefs(endpoint.locator);
|
|
546
797
|
const locator2 = this.locator(endpoint.locator).first();
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
798
|
+
const handle2 = await this.guarded(locator2, "drag");
|
|
799
|
+
try {
|
|
800
|
+
const box = handle2 ? await handle2.boundingBox() : await locator2.count() === 0 ? null : await locator2.boundingBox({ timeout: ACTION_TIMEOUT_MS });
|
|
801
|
+
if (!box) throw new BrowserActionRefusal("NOT_FOUND", "The drag locator matched no element.");
|
|
802
|
+
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
803
|
+
} finally {
|
|
804
|
+
await handle2?.dispose().catch(() => void 0);
|
|
805
|
+
}
|
|
550
806
|
};
|
|
551
|
-
for (const end of [input.from, input.to])
|
|
552
|
-
if (end.locator !== void 0) await this.guard({ action: "drag", locator: this.locator(end.locator).first() });
|
|
553
807
|
const from = await resolve(input.from);
|
|
554
808
|
const to = await resolve(input.to);
|
|
555
809
|
await page.mouse.move(from.x, from.y);
|
|
556
810
|
await page.mouse.down();
|
|
557
811
|
await page.mouse.move(to.x, to.y, { steps: typeof input.steps === "number" ? input.steps : 8 });
|
|
558
812
|
await page.mouse.up();
|
|
813
|
+
this.mouse = to;
|
|
559
814
|
return this.success(call.operation, { from, to });
|
|
560
815
|
}
|
|
561
816
|
if (call.operation === "browser.back") {
|
|
@@ -579,28 +834,38 @@ var PlaywrightOperationExecutor = class {
|
|
|
579
834
|
return this.success(call.operation, box);
|
|
580
835
|
}
|
|
581
836
|
if (call.operation === "browser.press" && !locator) {
|
|
582
|
-
|
|
583
|
-
await page.
|
|
837
|
+
const focused = page.locator("*:focus").first();
|
|
838
|
+
const handle2 = await this.guarded(await focused.count() ? focused : page.locator("body"), "press", input.key);
|
|
839
|
+
try {
|
|
840
|
+
await page.keyboard.press(input.key);
|
|
841
|
+
} finally {
|
|
842
|
+
await handle2?.dispose().catch(() => void 0);
|
|
843
|
+
}
|
|
584
844
|
return this.success(call.operation, { key: input.key });
|
|
585
845
|
}
|
|
586
846
|
const target = locator;
|
|
587
|
-
const
|
|
588
|
-
await this.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
847
|
+
const guardedAction = call.operation === "browser.select" ? "select" : call.operation.slice("browser.".length);
|
|
848
|
+
const handle = await this.guarded(
|
|
849
|
+
target.first(),
|
|
850
|
+
guardedAction,
|
|
851
|
+
call.operation === "browser.fill" ? input.value : call.operation === "browser.press" ? input.key : call.operation === "browser.select" ? input.values : void 0
|
|
852
|
+
);
|
|
853
|
+
const element = handle ?? { ...bind(target), focus: () => target.focus({ timeout }) };
|
|
593
854
|
const before = await this.inspect(target).catch(() => null);
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
855
|
+
try {
|
|
856
|
+
if (call.operation === "browser.click") await this.act(target, "click", () => element.click({ timeout }));
|
|
857
|
+
else if (call.operation === "browser.fill") await this.act(target, "fill", () => element.fill(input.value, { timeout }));
|
|
858
|
+
else if (call.operation === "browser.press") await this.act(target, "press", () => element.press(input.key, { timeout }));
|
|
859
|
+
else if (call.operation === "browser.hover") await this.act(target, "hover", () => element.hover({ timeout }));
|
|
860
|
+
else if (call.operation === "browser.focus") await this.act(target, "focus", () => element.focus());
|
|
861
|
+
else if (call.operation === "browser.check") await this.act(target, "check", () => element.check({ timeout }));
|
|
862
|
+
else if (call.operation === "browser.uncheck") await this.act(target, "uncheck", () => element.uncheck({ timeout }));
|
|
863
|
+
else if (call.operation === "browser.select") {
|
|
864
|
+
const values = await this.act(target, "selectOption", () => element.selectOption(input.values, { timeout }));
|
|
865
|
+
return this.success(call.operation, { values });
|
|
866
|
+
}
|
|
867
|
+
} finally {
|
|
868
|
+
await handle?.dispose().catch(() => void 0);
|
|
604
869
|
}
|
|
605
870
|
const after = await this.inspect(target).catch(() => null);
|
|
606
871
|
const inspection = after ?? before;
|
|
@@ -615,6 +880,7 @@ var PlaywrightOperationExecutor = class {
|
|
|
615
880
|
const timeout = Math.min(requested, SCRIPT_TIMEOUT_CLAMP_MS);
|
|
616
881
|
const AsyncFunction = Object.getPrototypeOf(async function() {
|
|
617
882
|
}).constructor;
|
|
883
|
+
if (this.options.actionGuard) await this.options.actionGuard({ action: "script", source, args });
|
|
618
884
|
let run;
|
|
619
885
|
try {
|
|
620
886
|
run = new AsyncFunction("page", "args", source);
|
|
@@ -667,6 +933,17 @@ function operationName(raw) {
|
|
|
667
933
|
const candidate = raw && typeof raw === "object" && !Array.isArray(raw) ? raw.operation : void 0;
|
|
668
934
|
return typeof candidate === "string" && BROWSER_OPERATION_NAMES.includes(candidate) ? candidate : "browser.status";
|
|
669
935
|
}
|
|
936
|
+
function bind(locator) {
|
|
937
|
+
return {
|
|
938
|
+
click: locator.click.bind(locator),
|
|
939
|
+
fill: locator.fill.bind(locator),
|
|
940
|
+
press: locator.press.bind(locator),
|
|
941
|
+
hover: locator.hover.bind(locator),
|
|
942
|
+
check: locator.check.bind(locator),
|
|
943
|
+
uncheck: locator.uncheck.bind(locator),
|
|
944
|
+
selectOption: locator.selectOption.bind(locator)
|
|
945
|
+
};
|
|
946
|
+
}
|
|
670
947
|
|
|
671
948
|
// src/provider.ts
|
|
672
949
|
import { timingSafeEqual } from "node:crypto";
|
|
@@ -1011,6 +1288,7 @@ export {
|
|
|
1011
1288
|
parseBrowserOperationCall,
|
|
1012
1289
|
parseBrowserOperationResult,
|
|
1013
1290
|
serveHttp,
|
|
1014
|
-
serveTcp
|
|
1291
|
+
serveTcp,
|
|
1292
|
+
snapshotWithoutRefs
|
|
1015
1293
|
};
|
|
1016
1294
|
//# sourceMappingURL=index.mjs.map
|