@runcofactor/cli 0.10.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{api-catalog-GFGCLVS4.js → api-catalog-7HLEN7C2.js} +5 -4
- package/dist/{api-catalog-GFGCLVS4.js.map → api-catalog-7HLEN7C2.js.map} +1 -1
- package/dist/{attachment-SJC7D7FY.js → attachment-OVNQD6Z2.js} +5 -4
- package/dist/{attachment-SJC7D7FY.js.map → attachment-OVNQD6Z2.js.map} +1 -1
- package/dist/{auth-EN6NQCJY.js → auth-VUHL74FH.js} +3 -2
- package/dist/{chunk-IMPZAX6L.js → chunk-5GD47G2D.js} +37 -1
- package/dist/{chunk-IMPZAX6L.js.map → chunk-5GD47G2D.js.map} +1 -1
- package/dist/{chunk-WOHL4FTB.js → chunk-CQE46HSM.js} +5 -5
- package/dist/chunk-CQE46HSM.js.map +1 -0
- package/dist/{chunk-52IO7ACJ.js → chunk-CRHPDPRS.js} +39 -3
- package/dist/{chunk-52IO7ACJ.js.map → chunk-CRHPDPRS.js.map} +1 -1
- package/dist/{chunk-XQXVG7RA.js → chunk-EEN2TZUQ.js} +3 -3
- package/dist/{chunk-I4QLI3JO.js → chunk-EJ4U242G.js} +2 -2
- package/dist/{chunk-CLAWT7BJ.js → chunk-ENDHMYEJ.js} +3 -3
- package/dist/chunk-G4UYGRZR.js +2 -0
- package/dist/chunk-JZMJUPZP.js +92 -0
- package/dist/chunk-JZMJUPZP.js.map +1 -0
- package/dist/{chunk-GKY2KXO5.js → chunk-NL7CVG56.js} +105 -35
- package/dist/chunk-NL7CVG56.js.map +1 -0
- package/dist/{chunk-A4OFTUVV.js → chunk-PZ4LCN6K.js} +7 -4
- package/dist/chunk-PZ4LCN6K.js.map +1 -0
- package/dist/{chunk-SJJW77WN.js → chunk-QGI4UCJX.js} +69 -5
- package/dist/chunk-QGI4UCJX.js.map +1 -0
- package/dist/{chunk-4PNOV7OV.js → chunk-QQVWGWPI.js} +1023 -150
- package/dist/chunk-QQVWGWPI.js.map +1 -0
- package/dist/{chunk-N7HFALX3.js → chunk-VGTKUOYS.js} +2 -2
- package/dist/{chunk-B2VISN2V.js → chunk-YN763NWQ.js} +230 -66
- package/dist/chunk-YN763NWQ.js.map +1 -0
- package/dist/{chunk-QFDOWABX.js → chunk-Z5LUTUDN.js} +6 -3
- package/dist/chunk-Z5LUTUDN.js.map +1 -0
- package/dist/{chunk-Q4BO4Z5V.js → chunk-ZTOE3ND3.js} +4 -4
- package/dist/{credential-COLTBV6D.js → credential-LO2SEEHU.js} +24 -20
- package/dist/credential-LO2SEEHU.js.map +1 -0
- package/dist/{credential-template-EEPYFG7R.js → credential-template-ZN7W7734.js} +30 -28
- package/dist/credential-template-ZN7W7734.js.map +1 -0
- package/dist/{debug-workflow-KOVH5GOP.js → debug-workflow-R3RYYAXK.js} +5 -4
- package/dist/{debug-workflow-KOVH5GOP.js.map → debug-workflow-R3RYYAXK.js.map} +1 -1
- package/dist/{docs-3SSORHB5.js → docs-DIAFBDXQ.js} +5 -4
- package/dist/{docs-3SSORHB5.js.map → docs-DIAFBDXQ.js.map} +1 -1
- package/dist/{doctor-OTSIWV6G.js → doctor-NZPUSCRE.js} +9 -5
- package/dist/doctor-NZPUSCRE.js.map +1 -0
- package/dist/environment-RQVU4P5M.js +221 -0
- package/dist/environment-RQVU4P5M.js.map +1 -0
- package/dist/{feature-flag-T2LRZME3.js → feature-flag-XG26RURR.js} +72 -10
- package/dist/feature-flag-XG26RURR.js.map +1 -0
- package/dist/index.js +57 -48
- package/dist/index.js.map +1 -1
- package/dist/{init-TGWRECT2.js → init-EDNLKGPT.js} +5 -4
- package/dist/{init-TGWRECT2.js.map → init-EDNLKGPT.js.map} +1 -1
- package/dist/{install-DXSNWZKO.js → install-FYZ3HNZK.js} +7 -7
- package/dist/{issues-XIZOPX6T.js → issues-NNRVTJ4L.js} +5 -4
- package/dist/{issues-XIZOPX6T.js.map → issues-NNRVTJ4L.js.map} +1 -1
- package/dist/{knobs-NBINZIUB.js → knobs-A2BOBH22.js} +5 -4
- package/dist/{knobs-NBINZIUB.js.map → knobs-A2BOBH22.js.map} +1 -1
- package/dist/{list-3TDHK637.js → list-F33VZ4BJ.js} +5 -4
- package/dist/{list-3TDHK637.js.map → list-F33VZ4BJ.js.map} +1 -1
- package/dist/{local-HICVD6PU.js → local-DQDTM3XA.js} +7 -6
- package/dist/{local-HICVD6PU.js.map → local-DQDTM3XA.js.map} +1 -1
- package/dist/{login-UOIUXVUK.js → login-BUMVIB36.js} +5 -4
- package/dist/{login-UOIUXVUK.js.map → login-BUMVIB36.js.map} +1 -1
- package/dist/{mcp-54IJEOUA.js → mcp-B5Y5CDMQ.js} +23 -24
- package/dist/mcp-B5Y5CDMQ.js.map +1 -0
- package/dist/{property-UXLOLPTD.js → property-NJ4SRXQG.js} +76 -4
- package/dist/property-NJ4SRXQG.js.map +1 -0
- package/dist/{psql-BYUUNLGG.js → psql-VBZFCMNR.js} +3 -2
- package/dist/{psql-BYUUNLGG.js.map → psql-VBZFCMNR.js.map} +1 -1
- package/dist/{record-4TR2UIIS.js → record-WAO36O7G.js} +16 -17
- package/dist/record-WAO36O7G.js.map +1 -0
- package/dist/{redis-35VBUYW7.js → redis-DHZXHL4S.js} +3 -2
- package/dist/{redis-35VBUYW7.js.map → redis-DHZXHL4S.js.map} +1 -1
- package/dist/{release-OOLTUBUT.js → release-STJKUKTE.js} +5 -4
- package/dist/{release-OOLTUBUT.js.map → release-STJKUKTE.js.map} +1 -1
- package/dist/{sandbox-XGSUU6G7.js → sandbox-DWSGLY7B.js} +9 -8
- package/dist/{sandbox-XGSUU6G7.js.map → sandbox-DWSGLY7B.js.map} +1 -1
- package/dist/{scenarios-GWLJEGHA.js → scenarios-HCIDY725.js} +5 -4
- package/dist/{scenarios-GWLJEGHA.js.map → scenarios-HCIDY725.js.map} +1 -1
- package/dist/{session-MYFNZPRP.js → session-Y5RHU54D.js} +76 -8
- package/dist/session-Y5RHU54D.js.map +1 -0
- package/dist/{skill-KM34LDMO.js → skill-23WAHUOB.js} +217 -67
- package/dist/skill-23WAHUOB.js.map +1 -0
- package/dist/skills/debug-run/SKILL.md +33 -22
- package/dist/skills/debug-run/history/1.2.0.md +262 -0
- package/dist/skills/debug-run/history/1.3.0.md +273 -0
- package/dist/skills/debug-run/version.json +1 -1
- package/dist/skills/optimize-cache/SKILL.md +206 -0
- package/dist/{spec-RA3CFEKG.js → spec-LM5XHOQ5.js} +54 -25
- package/dist/spec-LM5XHOQ5.js.map +1 -0
- package/dist/{src-LB4HBTJQ.js → src-22RI5VP2.js} +19 -11
- package/dist/src-22RI5VP2.js.map +1 -0
- package/dist/src-BGDRMOKC.js +5748 -0
- package/dist/src-BGDRMOKC.js.map +1 -0
- package/dist/{src-N3TPKGTY.js → src-NCJPUAE6.js} +3 -2
- package/dist/{src-UVDX5UP6.js → src-Y3K7UJEF.js} +4 -2
- package/dist/{start-PSEQSQA3.js → start-SWY55CZX.js} +5 -4
- package/dist/{start-PSEQSQA3.js.map → start-SWY55CZX.js.map} +1 -1
- package/dist/{tag-YY6YAABQ.js → tag-YSU2XXPI.js} +6 -5
- package/dist/{task-IQC3YR5K.js → task-NLBKLD7D.js} +5 -4
- package/dist/{task-IQC3YR5K.js.map → task-NLBKLD7D.js.map} +1 -1
- package/dist/{test-suite-DZNJFXDW.js → test-suite-QCPWZ2LS.js} +9 -6
- package/dist/{test-suite-DZNJFXDW.js.map → test-suite-QCPWZ2LS.js.map} +1 -1
- package/dist/{trace-HXIW7U2D.js → trace-A3ZY6K26.js} +6 -4
- package/dist/{triage-ORG45R5W.js → triage-TT4BMDBU.js} +6 -5
- package/dist/triage-TT4BMDBU.js.map +1 -0
- package/dist/{upgrade-L2VYMP2V.js → upgrade-6ZUFETKX.js} +3 -3
- package/dist/{workflow-XYFH5LTP.js → workflow-2CX3ZNOQ.js} +241 -237
- package/dist/workflow-2CX3ZNOQ.js.map +1 -0
- package/dist/{workflow-cache-NQJQFWMV.js → workflow-cache-GXNE6SU2.js} +140 -7
- package/dist/workflow-cache-GXNE6SU2.js.map +1 -0
- package/dist/{workflow-debug-BGD4UIXU.js → workflow-debug-PRX3DWGD.js} +8 -7
- package/dist/{workflow-debug-BGD4UIXU.js.map → workflow-debug-PRX3DWGD.js.map} +1 -1
- package/dist/{workflow-proposals-MTUDX46O.js → workflow-proposals-BW327WGL.js} +5 -4
- package/dist/{workflow-proposals-MTUDX46O.js.map → workflow-proposals-BW327WGL.js.map} +1 -1
- package/package.json +2 -1
- package/dist/chunk-4PNOV7OV.js.map +0 -1
- package/dist/chunk-A4OFTUVV.js.map +0 -1
- package/dist/chunk-B2VISN2V.js.map +0 -1
- package/dist/chunk-GKY2KXO5.js.map +0 -1
- package/dist/chunk-JICSYFRJ.js +0 -46
- package/dist/chunk-JICSYFRJ.js.map +0 -1
- package/dist/chunk-QFDOWABX.js.map +0 -1
- package/dist/chunk-SJJW77WN.js.map +0 -1
- package/dist/chunk-WOHL4FTB.js.map +0 -1
- package/dist/credential-COLTBV6D.js.map +0 -1
- package/dist/credential-template-EEPYFG7R.js.map +0 -1
- package/dist/doctor-OTSIWV6G.js.map +0 -1
- package/dist/feature-flag-T2LRZME3.js.map +0 -1
- package/dist/mcp-54IJEOUA.js.map +0 -1
- package/dist/property-UXLOLPTD.js.map +0 -1
- package/dist/record-4TR2UIIS.js.map +0 -1
- package/dist/session-MYFNZPRP.js.map +0 -1
- package/dist/skill-KM34LDMO.js.map +0 -1
- package/dist/spec-RA3CFEKG.js.map +0 -1
- package/dist/src-LB4HBTJQ.js.map +0 -1
- package/dist/workflow-XYFH5LTP.js.map +0 -1
- package/dist/workflow-cache-NQJQFWMV.js.map +0 -1
- /package/dist/{auth-EN6NQCJY.js.map → auth-VUHL74FH.js.map} +0 -0
- /package/dist/{chunk-XQXVG7RA.js.map → chunk-EEN2TZUQ.js.map} +0 -0
- /package/dist/{chunk-I4QLI3JO.js.map → chunk-EJ4U242G.js.map} +0 -0
- /package/dist/{chunk-CLAWT7BJ.js.map → chunk-ENDHMYEJ.js.map} +0 -0
- /package/dist/{src-N3TPKGTY.js.map → chunk-G4UYGRZR.js.map} +0 -0
- /package/dist/{chunk-N7HFALX3.js.map → chunk-VGTKUOYS.js.map} +0 -0
- /package/dist/{chunk-Q4BO4Z5V.js.map → chunk-ZTOE3ND3.js.map} +0 -0
- /package/dist/{install-DXSNWZKO.js.map → install-FYZ3HNZK.js.map} +0 -0
- /package/dist/{src-UVDX5UP6.js.map → src-NCJPUAE6.js.map} +0 -0
- /package/dist/{tag-YY6YAABQ.js.map → src-Y3K7UJEF.js.map} +0 -0
- /package/dist/{trace-HXIW7U2D.js.map → tag-YSU2XXPI.js.map} +0 -0
- /package/dist/{triage-ORG45R5W.js.map → trace-A3ZY6K26.js.map} +0 -0
- /package/dist/{upgrade-L2VYMP2V.js.map → upgrade-6ZUFETKX.js.map} +0 -0
|
@@ -2,6 +2,9 @@ import { createRequire as __cr } from "module"; const require = __cr(import.meta
|
|
|
2
2
|
import {
|
|
3
3
|
getCanaryTmpDir
|
|
4
4
|
} from "./chunk-JWHYXEA2.js";
|
|
5
|
+
import {
|
|
6
|
+
isVolatileIdentifier
|
|
7
|
+
} from "./chunk-QGI4UCJX.js";
|
|
5
8
|
import {
|
|
6
9
|
require_src
|
|
7
10
|
} from "./chunk-6LEZCKKV.js";
|
|
@@ -25583,8 +25586,22 @@ var TIMEOUTS = {
|
|
|
25583
25586
|
POST_ACTION_SNAPSHOT: 15e3,
|
|
25584
25587
|
// Hard cap for the best-effort `window.stop()` dispatched on per-tool-timeout
|
|
25585
25588
|
// abort. Bounds wedged JS contexts; a healthy page returns in <10ms.
|
|
25586
|
-
UNWEDGE_PAGE_CAP: 1e3
|
|
25589
|
+
UNWEDGE_PAGE_CAP: 1e3,
|
|
25590
|
+
// Driver-side grace added on top of `__waitDomQuiet`'s in-page maxWaitMs
|
|
25591
|
+
// before the evaluate itself is abandoned. The in-page cap is a page-side
|
|
25592
|
+
// setTimeout that never fires on a blocked main thread; the driver-side
|
|
25593
|
+
// race (maxWaitMs + this grace) guarantees forward progress regardless.
|
|
25594
|
+
WAIT_DOM_QUIET_EVAL_GRACE: 500,
|
|
25595
|
+
// Driver-side cap for the liveness probe (`page.evaluate(() => 1)`). Short on
|
|
25596
|
+
// purpose: a healthy page answers in <10ms; a blocked main thread caps and is
|
|
25597
|
+
// treated as Unresponsive so the per-tool-timeout recovery can tear it down.
|
|
25598
|
+
LIVENESS_PROBE_CAP: 750,
|
|
25599
|
+
// A captured request still 'pending' (no response headers) past this age is
|
|
25600
|
+
// ignored by the HTTP-settle stabilization signal — a long-poll / hung XHR /
|
|
25601
|
+
// header-less streaming fetch must never block a snapshot indefinitely.
|
|
25602
|
+
HTTP_SETTLE_MAX_AGE: 1e4
|
|
25587
25603
|
};
|
|
25604
|
+
var LARGE_PAGE_REF_THRESHOLD = 1500;
|
|
25588
25605
|
var DEFAULTS = {
|
|
25589
25606
|
MAX_DEPTH: 10,
|
|
25590
25607
|
MAX_PAGES: 100,
|
|
@@ -25911,6 +25928,10 @@ function extractAndStripV2Fields(attributes) {
|
|
|
25911
25928
|
cssHints.testIds.push(value);
|
|
25912
25929
|
sawCssHint = true;
|
|
25913
25930
|
break;
|
|
25931
|
+
case "~data-id":
|
|
25932
|
+
cssHints.dataId = value;
|
|
25933
|
+
sawCssHint = true;
|
|
25934
|
+
break;
|
|
25914
25935
|
case "~automation-id":
|
|
25915
25936
|
cssHints.automationId = value;
|
|
25916
25937
|
sawCssHint = true;
|
|
@@ -25921,6 +25942,12 @@ function extractAndStripV2Fields(attributes) {
|
|
|
25921
25942
|
case "~semantic-class":
|
|
25922
25943
|
out.semanticClass = value;
|
|
25923
25944
|
break;
|
|
25945
|
+
case "~field":
|
|
25946
|
+
out.dataField = value;
|
|
25947
|
+
break;
|
|
25948
|
+
case "~editable":
|
|
25949
|
+
out.isEditable = value === "true";
|
|
25950
|
+
break;
|
|
25924
25951
|
case "~v2":
|
|
25925
25952
|
out.substrateVersion = 2;
|
|
25926
25953
|
break;
|
|
@@ -26338,6 +26365,7 @@ var RESULT_ITEM_SELECTOR = "[role=option],[role=listitem],[role=row],[data-slot=
|
|
|
26338
26365
|
var FILTER_QUERY_MIN_LEN = 3;
|
|
26339
26366
|
var FILTER_MAX_RESULT_ITEMS = 200;
|
|
26340
26367
|
var FILTER_ANCESTOR_WALK_DEPTH = 4;
|
|
26368
|
+
var HCLIP_ANCESTOR_WALK_DEPTH = 10;
|
|
26341
26369
|
var MODAL_V2_MAX_ENRICHMENTS = 3;
|
|
26342
26370
|
var MODAL_V2_FRAMEWORK_SELECTORS = [
|
|
26343
26371
|
".cdk-overlay-pane",
|
|
@@ -26369,6 +26397,7 @@ var SUB_FRAME_REF_RE = /^f\d+e\d+$/;
|
|
|
26369
26397
|
function planChecks(elements, opts) {
|
|
26370
26398
|
const planByRef = /* @__PURE__ */ new Map();
|
|
26371
26399
|
const skipGridRefs = opts.skipGridRefs;
|
|
26400
|
+
const hclipCandidates = [];
|
|
26372
26401
|
const ensure = (ref) => {
|
|
26373
26402
|
let args = planByRef.get(ref);
|
|
26374
26403
|
if (!args) {
|
|
@@ -26431,9 +26460,32 @@ function planChecks(elements, opts) {
|
|
|
26431
26460
|
if (el.role === "switch" || el.role === "checkbox") {
|
|
26432
26461
|
addCheck(el.ref, "toggle");
|
|
26433
26462
|
}
|
|
26434
|
-
|
|
26463
|
+
const isTargetableForId = INTERACTIVE_ROLES3.has(el.role) || el.role === "option" || el.role === "row" || el.role === "gridcell" || el.role === "listitem" || el.attributes["cursor"] === "pointer";
|
|
26464
|
+
if (opts.v2 && isTargetableForId) {
|
|
26435
26465
|
addCheck(el.ref, "testId");
|
|
26436
26466
|
}
|
|
26467
|
+
if (isTargetableForId) {
|
|
26468
|
+
hclipCandidates.push(el.ref);
|
|
26469
|
+
}
|
|
26470
|
+
const CONTAINER_ROLES_FOR_ID = /* @__PURE__ */ new Set([
|
|
26471
|
+
"generic",
|
|
26472
|
+
"group",
|
|
26473
|
+
"region",
|
|
26474
|
+
"article",
|
|
26475
|
+
"section",
|
|
26476
|
+
"listitem",
|
|
26477
|
+
"rowgroup",
|
|
26478
|
+
"list",
|
|
26479
|
+
"navigation",
|
|
26480
|
+
"complementary",
|
|
26481
|
+
"form"
|
|
26482
|
+
]);
|
|
26483
|
+
if (opts.v2 && (isTargetableForId || CONTAINER_ROLES_FOR_ID.has(el.role) && hasInteractive(el))) {
|
|
26484
|
+
addCheck(el.ref, "dataId");
|
|
26485
|
+
}
|
|
26486
|
+
if (opts.v2 && (el.role === "gridcell" || el.role === "cell")) {
|
|
26487
|
+
addCheck(el.ref, "dataField");
|
|
26488
|
+
}
|
|
26437
26489
|
if (el.role === "generic" && el.ref && el.text) {
|
|
26438
26490
|
addCheck(el.ref, "svgPromote");
|
|
26439
26491
|
}
|
|
@@ -26441,6 +26493,9 @@ function planChecks(elements, opts) {
|
|
|
26441
26493
|
for (const child of el.children) walk(child, inGridCell || isGridCell);
|
|
26442
26494
|
};
|
|
26443
26495
|
for (const el of elements) walk(el, false);
|
|
26496
|
+
if (hclipCandidates.length <= LARGE_PAGE_REF_THRESHOLD) {
|
|
26497
|
+
for (const ref of hclipCandidates) addCheck(ref, "hclip");
|
|
26498
|
+
}
|
|
26444
26499
|
return [...planByRef].map(([ref, args]) => ({ ref, args }));
|
|
26445
26500
|
}
|
|
26446
26501
|
function pageSideFn(el, _ref, args, G) {
|
|
@@ -26462,6 +26517,31 @@ function pageSideFn(el, _ref, args, G) {
|
|
|
26462
26517
|
if (disabled) readonly = false;
|
|
26463
26518
|
out.editability = { readonly, disabled };
|
|
26464
26519
|
}
|
|
26520
|
+
if (checks.has("hclip")) {
|
|
26521
|
+
out.hclip = null;
|
|
26522
|
+
try {
|
|
26523
|
+
const win = el.ownerDocument.defaultView;
|
|
26524
|
+
if (win) {
|
|
26525
|
+
const elRect = el.getBoundingClientRect();
|
|
26526
|
+
if (elRect.width > 0 || elRect.height > 0) {
|
|
26527
|
+
let parent = el.parentElement;
|
|
26528
|
+
for (let i2 = 0; i2 < G.HCLIP_ANCESTOR_DEPTH && parent; i2++, parent = parent.parentElement) {
|
|
26529
|
+
if (parent.scrollWidth <= parent.clientWidth + 1) continue;
|
|
26530
|
+
const ox = win.getComputedStyle(parent).overflowX;
|
|
26531
|
+
if (ox !== "auto" && ox !== "scroll") continue;
|
|
26532
|
+
const cRect = parent.getBoundingClientRect();
|
|
26533
|
+
const bandLeft = Math.max(cRect.left, 0);
|
|
26534
|
+
const bandRight = Math.min(cRect.right, win.innerWidth);
|
|
26535
|
+
if (elRect.right <= bandLeft) out.hclip = "left";
|
|
26536
|
+
else if (elRect.left >= bandRight) out.hclip = "right";
|
|
26537
|
+
break;
|
|
26538
|
+
}
|
|
26539
|
+
}
|
|
26540
|
+
}
|
|
26541
|
+
} catch {
|
|
26542
|
+
out.hclip = null;
|
|
26543
|
+
}
|
|
26544
|
+
}
|
|
26465
26545
|
if (checks.has("gridCellValue")) {
|
|
26466
26546
|
let value = null;
|
|
26467
26547
|
const title = el.getAttribute("title");
|
|
@@ -26795,6 +26875,28 @@ function pageSideFn(el, _ref, args, G) {
|
|
|
26795
26875
|
out.testId = null;
|
|
26796
26876
|
}
|
|
26797
26877
|
}
|
|
26878
|
+
if (checks.has("dataId")) {
|
|
26879
|
+
try {
|
|
26880
|
+
const dataId = el.getAttribute("data-id") || void 0;
|
|
26881
|
+
out.dataId = dataId && dataId.length <= 120 ? dataId : null;
|
|
26882
|
+
} catch {
|
|
26883
|
+
out.dataId = null;
|
|
26884
|
+
}
|
|
26885
|
+
}
|
|
26886
|
+
if (checks.has("dataField")) {
|
|
26887
|
+
out.dataField = null;
|
|
26888
|
+
try {
|
|
26889
|
+
const field = el.getAttribute("data-field");
|
|
26890
|
+
const reserved = field === "__check__" || field === "__row_number__" || field === "__reorder__" || field === "__detail_panel_toggle__" || field === "__tree_data_group__";
|
|
26891
|
+
if (field && !reserved) {
|
|
26892
|
+
const cls = typeof el.className === "string" ? el.className : "";
|
|
26893
|
+
const editable = /(?:^|[\s-])editable(?:[\s-]|$)/i.test(cls) || el.getAttribute("aria-readonly") === "false" || el.querySelector('input, textarea, select, [contenteditable="true"]') !== null;
|
|
26894
|
+
out.dataField = { field: field.slice(0, 120), editable };
|
|
26895
|
+
}
|
|
26896
|
+
} catch {
|
|
26897
|
+
out.dataField = null;
|
|
26898
|
+
}
|
|
26899
|
+
}
|
|
26798
26900
|
if (checks.has("svgPromote")) {
|
|
26799
26901
|
try {
|
|
26800
26902
|
out.svgPromote = el.hasAttribute("data-svg-enriched");
|
|
@@ -26943,6 +27045,7 @@ async function runPerRefAugmentations(scope, yaml, logger2, opts = {}) {
|
|
|
26943
27045
|
FILTER_MAX_ITEMS: FILTER_MAX_RESULT_ITEMS,
|
|
26944
27046
|
FILTER_MIN_QUERY_LEN: FILTER_QUERY_MIN_LEN,
|
|
26945
27047
|
FILTER_ANCESTOR_DEPTH: FILTER_ANCESTOR_WALK_DEPTH,
|
|
27048
|
+
HCLIP_ANCESTOR_DEPTH: HCLIP_ANCESTOR_WALK_DEPTH,
|
|
26946
27049
|
MODAL_FRAMEWORK_SELECTORS: MODAL_V2_FRAMEWORK_SELECTORS,
|
|
26947
27050
|
MODAL_CLASS_PATTERNS: MODAL_V2_CLASS_PATTERNS
|
|
26948
27051
|
};
|
|
@@ -26970,6 +27073,15 @@ async function runPerRefAugmentations(scope, yaml, logger2, opts = {}) {
|
|
|
26970
27073
|
if (opts.v2 && r2.testId) {
|
|
26971
27074
|
lines[i2] = appendAttributeIfMissing(lines[i2], "~test-id", encodeV2Value(r2.testId));
|
|
26972
27075
|
}
|
|
27076
|
+
if (opts.v2 && r2.dataId) {
|
|
27077
|
+
lines[i2] = appendAttributeIfMissing(lines[i2], "~data-id", encodeV2Value(r2.dataId));
|
|
27078
|
+
}
|
|
27079
|
+
if (opts.v2 && r2.dataField) {
|
|
27080
|
+
lines[i2] = appendAttributeIfMissing(lines[i2], "~field", encodeV2Value(r2.dataField.field));
|
|
27081
|
+
if (r2.dataField.editable) {
|
|
27082
|
+
lines[i2] = appendAttributeIfMissing(lines[i2], "~editable", "true");
|
|
27083
|
+
}
|
|
27084
|
+
}
|
|
26973
27085
|
if (r2.editability) {
|
|
26974
27086
|
if (r2.editability.readonly) {
|
|
26975
27087
|
lines[i2] = appendAttributeIfMissing(lines[i2], "readonly", "true");
|
|
@@ -26978,6 +27090,9 @@ async function runPerRefAugmentations(scope, yaml, logger2, opts = {}) {
|
|
|
26978
27090
|
lines[i2] = appendAttributeIfMissing(lines[i2], "disabled", "true");
|
|
26979
27091
|
}
|
|
26980
27092
|
}
|
|
27093
|
+
if (r2.hclip) {
|
|
27094
|
+
lines[i2] = appendAttributeIfMissing(lines[i2], "~hclip", r2.hclip);
|
|
27095
|
+
}
|
|
26981
27096
|
if (r2.gridCellValue) {
|
|
26982
27097
|
const escaped = r2.gridCellValue.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
26983
27098
|
lines[i2] = appendValueTail(lines[i2], escaped);
|
|
@@ -38614,7 +38729,19 @@ function extractGridCell(cellEl) {
|
|
|
38614
38729
|
ref: cellEl.ref,
|
|
38615
38730
|
column: cellEl.text || "",
|
|
38616
38731
|
value: textValue,
|
|
38617
|
-
hasValue: true
|
|
38732
|
+
hasValue: true,
|
|
38733
|
+
dataField: cellEl.dataField,
|
|
38734
|
+
isEditable: cellEl.isEditable
|
|
38735
|
+
};
|
|
38736
|
+
}
|
|
38737
|
+
if (cellEl.isEditable && cellEl.ref) {
|
|
38738
|
+
return {
|
|
38739
|
+
ref: cellEl.ref,
|
|
38740
|
+
column: "",
|
|
38741
|
+
value: "",
|
|
38742
|
+
hasValue: false,
|
|
38743
|
+
dataField: cellEl.dataField,
|
|
38744
|
+
isEditable: true
|
|
38618
38745
|
};
|
|
38619
38746
|
}
|
|
38620
38747
|
return null;
|
|
@@ -38642,7 +38769,9 @@ function extractGridCell(cellEl) {
|
|
|
38642
38769
|
value,
|
|
38643
38770
|
hasValue: value !== "",
|
|
38644
38771
|
gridcellLabel: gridcellLabel || void 0,
|
|
38645
|
-
role
|
|
38772
|
+
role,
|
|
38773
|
+
dataField: cellEl.dataField,
|
|
38774
|
+
isEditable: cellEl.isEditable
|
|
38646
38775
|
};
|
|
38647
38776
|
}
|
|
38648
38777
|
function reconcileCellColumn(cell, knownColumns) {
|
|
@@ -38956,7 +39085,7 @@ function formatGridOutput(grid, indent, expanded = false) {
|
|
|
38956
39085
|
const MAX_GRID_ROWS = 30;
|
|
38957
39086
|
const displayRows = expanded ? grid.rows : grid.rows.slice(0, MAX_GRID_ROWS);
|
|
38958
39087
|
for (const row of displayRows) {
|
|
38959
|
-
lines.push(formatGridRow(row, indent + 1, knownColumns));
|
|
39088
|
+
lines.push(formatGridRow(row, indent + 1, knownColumns, expanded));
|
|
38960
39089
|
}
|
|
38961
39090
|
if (!expanded && grid.rows.length > MAX_GRID_ROWS) {
|
|
38962
39091
|
lines.push(
|
|
@@ -38965,7 +39094,8 @@ function formatGridOutput(grid, indent, expanded = false) {
|
|
|
38965
39094
|
}
|
|
38966
39095
|
return lines.join("\n");
|
|
38967
39096
|
}
|
|
38968
|
-
|
|
39097
|
+
var MAX_COMPACT_EDITABLE_CELLS_PER_ROW = 4;
|
|
39098
|
+
function formatGridRow(row, indent, knownColumns, expanded = false) {
|
|
38969
39099
|
const indentStr = " ".repeat(indent);
|
|
38970
39100
|
if (row.expandedContent) {
|
|
38971
39101
|
const parts = [];
|
|
@@ -38980,17 +39110,29 @@ function formatGridRow(row, indent, knownColumns) {
|
|
|
38980
39110
|
const marker = row.isSelected ? "ACTIVE \u2192 Row" : "Row";
|
|
38981
39111
|
const label = row.label ? ` "${row.label}"` : "";
|
|
38982
39112
|
const actionsPrefix = row.rowActions?.length ? row.rowActions.map((a2) => `[action ${a2.ref}: ${a2.label}]`).join(" ") + " " : "";
|
|
38983
|
-
const
|
|
38984
|
-
|
|
38985
|
-
);
|
|
38986
|
-
if (
|
|
38987
|
-
const
|
|
39113
|
+
const inScope = (c3) => !knownColumns || knownColumns.has(c3.column);
|
|
39114
|
+
const valueCells = row.cells.filter((c3) => c3.hasValue && inScope(c3));
|
|
39115
|
+
const editableEmptyCells = row.cells.filter((c3) => !c3.hasValue && c3.isEditable && inScope(c3));
|
|
39116
|
+
if (valueCells.length > 0 || editableEmptyCells.length > 0) {
|
|
39117
|
+
const renderCell = (c3) => {
|
|
39118
|
+
if (!c3.hasValue && c3.isEditable) {
|
|
39119
|
+
return `${c3.column} [${c3.ref}]: (editable)`;
|
|
39120
|
+
}
|
|
38988
39121
|
if (c3.column === c3.value) {
|
|
38989
39122
|
return `${c3.column} [${c3.ref}]`;
|
|
38990
39123
|
}
|
|
38991
39124
|
return `${c3.column} [${c3.ref}]: ${c3.value}`;
|
|
38992
|
-
}
|
|
38993
|
-
|
|
39125
|
+
};
|
|
39126
|
+
const cellStrs = valueCells.map(renderCell);
|
|
39127
|
+
const shownEditable = expanded ? editableEmptyCells : editableEmptyCells.slice(0, MAX_COMPACT_EDITABLE_CELLS_PER_ROW);
|
|
39128
|
+
cellStrs.push(...shownEditable.map(renderCell));
|
|
39129
|
+
const hiddenEditable = editableEmptyCells.length - shownEditable.length;
|
|
39130
|
+
if (hiddenEditable > 0) {
|
|
39131
|
+
cellStrs.push(`(+${hiddenEditable} more editable cells)`);
|
|
39132
|
+
}
|
|
39133
|
+
if (cellStrs.length > 0) {
|
|
39134
|
+
return `${indentStr}${actionsPrefix}${marker} [${row.ref}]${label}: ${cellStrs.join(" | ")}`;
|
|
39135
|
+
}
|
|
38994
39136
|
}
|
|
38995
39137
|
return `${indentStr}${actionsPrefix}${marker} [${row.ref}]${label}: (empty row)`;
|
|
38996
39138
|
}
|
|
@@ -40380,9 +40522,6 @@ function isMeaningfulDisabledControlWithoutRef(element) {
|
|
|
40380
40522
|
if (!label || label === element.role || label === "(icon)") return false;
|
|
40381
40523
|
return true;
|
|
40382
40524
|
}
|
|
40383
|
-
function isVolatileTestId(id) {
|
|
40384
|
-
return /[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i.test(id) || /\b[0-9A-HJKMNP-TV-Z]{26}\b/.test(id) || /\b\d{6,}\b/.test(id);
|
|
40385
|
-
}
|
|
40386
40525
|
function hasUsefulRenderLabel(label, role) {
|
|
40387
40526
|
const trimmed = label.trim();
|
|
40388
40527
|
return Boolean(trimmed && trimmed !== role && trimmed !== "(icon)" && trimmed !== "(no label)");
|
|
@@ -40395,7 +40534,7 @@ function renderableTestIdAttributes(element, label) {
|
|
|
40395
40534
|
const uniqueStableIds = [];
|
|
40396
40535
|
for (const id of testIds) {
|
|
40397
40536
|
const trimmed = id.trim();
|
|
40398
|
-
if (!trimmed || trimmed.length > 80 ||
|
|
40537
|
+
if (!trimmed || trimmed.length > 80 || isVolatileIdentifier(trimmed)) continue;
|
|
40399
40538
|
if (!uniqueStableIds.includes(trimmed)) uniqueStableIds.push(trimmed);
|
|
40400
40539
|
}
|
|
40401
40540
|
return uniqueStableIds.slice(0, 2).map((id) => `testid=${id}`);
|
|
@@ -41686,10 +41825,7 @@ function formatSemanticSnapshot(yaml, options) {
|
|
|
41686
41825
|
lines.push("");
|
|
41687
41826
|
}
|
|
41688
41827
|
}
|
|
41689
|
-
|
|
41690
|
-
if (options?.offScreenRefs && options.offScreenRefs.size > 0) {
|
|
41691
|
-
result = markOffScreenElements(result, options.offScreenRefs);
|
|
41692
|
-
}
|
|
41828
|
+
const result = lines.join("\n").trim();
|
|
41693
41829
|
return result;
|
|
41694
41830
|
}
|
|
41695
41831
|
function expandSection(yaml, sectionRef) {
|
|
@@ -41764,15 +41900,6 @@ Section with ref="${sectionRef}" not found in snapshot.
|
|
|
41764
41900
|
targetSection.collapsed = false;
|
|
41765
41901
|
return formatExpandedSectionOutput(targetSection);
|
|
41766
41902
|
}
|
|
41767
|
-
function markOffScreenElements(text, offScreenRefs) {
|
|
41768
|
-
if (offScreenRefs.size === 0) return text;
|
|
41769
|
-
return text.replace(/\[(e\d+)\](?! \[off-screen\])/g, (match, ref) => {
|
|
41770
|
-
if (offScreenRefs.has(ref)) {
|
|
41771
|
-
return `[${ref}] [off-screen]`;
|
|
41772
|
-
}
|
|
41773
|
-
return match;
|
|
41774
|
-
});
|
|
41775
|
-
}
|
|
41776
41903
|
function identifyLargeGridRefs(yaml, preParsedElements) {
|
|
41777
41904
|
if (!yaml || yaml.trim() === "") return [];
|
|
41778
41905
|
const elements = preParsedElements ?? parseSnapshot(yaml);
|
|
@@ -41943,6 +42070,15 @@ var INTERACTIVE_ROLES2 = /* @__PURE__ */ new Set([
|
|
|
41943
42070
|
"listbox"
|
|
41944
42071
|
]);
|
|
41945
42072
|
var DIALOG_ROLES2 = /* @__PURE__ */ new Set(["dialog", "alertdialog"]);
|
|
42073
|
+
var CONTENT_ROLES = /* @__PURE__ */ new Set([
|
|
42074
|
+
"text",
|
|
42075
|
+
"paragraph",
|
|
42076
|
+
"heading",
|
|
42077
|
+
"alert",
|
|
42078
|
+
"status",
|
|
42079
|
+
"note"
|
|
42080
|
+
]);
|
|
42081
|
+
var ALERT_ROLES = /* @__PURE__ */ new Set(["alert", "status"]);
|
|
41946
42082
|
function extractElementState(el) {
|
|
41947
42083
|
return {
|
|
41948
42084
|
ref: el.ref,
|
|
@@ -41981,6 +42117,8 @@ function captureSnapshotState(url, yaml) {
|
|
|
41981
42117
|
const editingContexts = extractEditingContextsFromElements(elements);
|
|
41982
42118
|
const elementsByRef = /* @__PURE__ */ new Map();
|
|
41983
42119
|
const interactiveRefs = /* @__PURE__ */ new Set();
|
|
42120
|
+
const contentTexts = /* @__PURE__ */ new Set();
|
|
42121
|
+
const alertTexts = /* @__PURE__ */ new Set();
|
|
41984
42122
|
function traverse(el) {
|
|
41985
42123
|
if (el.ref) {
|
|
41986
42124
|
const state = extractElementState(el);
|
|
@@ -41989,6 +42127,13 @@ function captureSnapshotState(url, yaml) {
|
|
|
41989
42127
|
interactiveRefs.add(el.ref);
|
|
41990
42128
|
}
|
|
41991
42129
|
}
|
|
42130
|
+
const text = el.text?.trim();
|
|
42131
|
+
if (text && CONTENT_ROLES.has(el.role)) {
|
|
42132
|
+
contentTexts.add(text);
|
|
42133
|
+
if (ALERT_ROLES.has(el.role)) {
|
|
42134
|
+
alertTexts.add(text);
|
|
42135
|
+
}
|
|
42136
|
+
}
|
|
41992
42137
|
for (const child of el.children) {
|
|
41993
42138
|
traverse(child);
|
|
41994
42139
|
}
|
|
@@ -42004,9 +42149,16 @@ function captureSnapshotState(url, yaml) {
|
|
|
42004
42149
|
dialogStack,
|
|
42005
42150
|
editingContexts,
|
|
42006
42151
|
elementsByRef,
|
|
42007
|
-
interactiveRefs
|
|
42152
|
+
interactiveRefs,
|
|
42153
|
+
contentTexts,
|
|
42154
|
+
alertTexts
|
|
42008
42155
|
};
|
|
42009
42156
|
}
|
|
42157
|
+
function added(before, after) {
|
|
42158
|
+
const out = [];
|
|
42159
|
+
for (const t2 of after) if (!before.has(t2)) out.push(t2);
|
|
42160
|
+
return out;
|
|
42161
|
+
}
|
|
42010
42162
|
function compareSnapshots(before, after) {
|
|
42011
42163
|
const urlChange = before.url !== after.url ? { before: before.url, after: after.url, navigated: true } : null;
|
|
42012
42164
|
const beforeActive = before.activeElement ? extractElementState(before.activeElement) : null;
|
|
@@ -42092,6 +42244,10 @@ function compareSnapshots(before, after) {
|
|
|
42092
42244
|
before.editingContexts,
|
|
42093
42245
|
after.editingContexts
|
|
42094
42246
|
);
|
|
42247
|
+
const addedTexts = added(before.contentTexts, after.contentTexts);
|
|
42248
|
+
const removedTexts = added(after.contentTexts, before.contentTexts);
|
|
42249
|
+
const newAlerts = added(before.alertTexts, after.alertTexts);
|
|
42250
|
+
const contentChange = addedTexts.length > 0 || removedTexts.length > 0 || newAlerts.length > 0 ? { addedTexts, removedTexts, newAlerts } : null;
|
|
42095
42251
|
return {
|
|
42096
42252
|
urlChange,
|
|
42097
42253
|
activeChange,
|
|
@@ -42100,11 +42256,35 @@ function compareSnapshots(before, after) {
|
|
|
42100
42256
|
removedElements,
|
|
42101
42257
|
stateChanges,
|
|
42102
42258
|
editingContextChanges,
|
|
42259
|
+
contentChange,
|
|
42103
42260
|
elementCountDelta
|
|
42104
42261
|
};
|
|
42105
42262
|
}
|
|
42263
|
+
function classifyScreenshotWorthiness(diff2, opts) {
|
|
42264
|
+
const structuralThreshold = opts?.structuralThreshold ?? 8;
|
|
42265
|
+
const contentThreshold = opts?.contentThreshold ?? 3;
|
|
42266
|
+
if (diff2.urlChange?.navigated) {
|
|
42267
|
+
return { warranted: true, reason: "navigation" };
|
|
42268
|
+
}
|
|
42269
|
+
if (diff2.dialogStackChange && diff2.dialogStackChange.after.length > diff2.dialogStackChange.before.length) {
|
|
42270
|
+
return { warranted: true, reason: "dialog-opened" };
|
|
42271
|
+
}
|
|
42272
|
+
const structuralDelta = diff2.newElements.length + diff2.removedElements.length;
|
|
42273
|
+
if (structuralDelta > structuralThreshold) {
|
|
42274
|
+
return { warranted: true, reason: "structural-change" };
|
|
42275
|
+
}
|
|
42276
|
+
if (diff2.contentChange) {
|
|
42277
|
+
if (diff2.contentChange.newAlerts.length > 0) {
|
|
42278
|
+
return { warranted: true, reason: "new-alert" };
|
|
42279
|
+
}
|
|
42280
|
+
if (diff2.contentChange.addedTexts.length > contentThreshold) {
|
|
42281
|
+
return { warranted: true, reason: "new-content" };
|
|
42282
|
+
}
|
|
42283
|
+
}
|
|
42284
|
+
return { warranted: false };
|
|
42285
|
+
}
|
|
42106
42286
|
function hasDiffChanges(diff2) {
|
|
42107
|
-
return diff2.urlChange !== null || diff2.activeChange !== null || diff2.dialogStackChange !== null || diff2.newElements.length > 0 || diff2.removedElements.length > 0 || diff2.stateChanges.length > 0 || diff2.editingContextChanges.length > 0;
|
|
42287
|
+
return diff2.urlChange !== null || diff2.activeChange !== null || diff2.dialogStackChange !== null || diff2.newElements.length > 0 || diff2.removedElements.length > 0 || diff2.stateChanges.length > 0 || diff2.editingContextChanges.length > 0 || diff2.contentChange !== null;
|
|
42108
42288
|
}
|
|
42109
42289
|
function formatElementBrief(el) {
|
|
42110
42290
|
if (!el) return "none";
|
|
@@ -42214,7 +42394,7 @@ function formatSemanticDiff(diff2, actionDescription, networkInfo, currentTime)
|
|
|
42214
42394
|
const after = diff2.dialogStackChange.after.length > 0 ? diff2.dialogStackChange.after.join(" -> ") : "(none)";
|
|
42215
42395
|
lines.push(`DIALOGS: [PREVIOUS] ${before} [CURRENT] ${after}`);
|
|
42216
42396
|
}
|
|
42217
|
-
const hasChanges = diff2.newElements.length > 0 || diff2.removedElements.length > 0 || diff2.stateChanges.length > 0 || diff2.editingContextChanges.length > 0;
|
|
42397
|
+
const hasChanges = diff2.newElements.length > 0 || diff2.removedElements.length > 0 || diff2.stateChanges.length > 0 || diff2.editingContextChanges.length > 0 || diff2.contentChange !== null;
|
|
42218
42398
|
if (hasChanges) {
|
|
42219
42399
|
lines.push("");
|
|
42220
42400
|
lines.push("CHANGES DETECTED:");
|
|
@@ -42258,6 +42438,25 @@ function formatSemanticDiff(diff2, actionDescription, networkInfo, currentTime)
|
|
|
42258
42438
|
` - REMOVED: ${removedToShow.map((e2) => `${e2.role} "${truncate(e2.text, 15)}" [${e2.ref}]`).join(", ")}${moreText}`
|
|
42259
42439
|
);
|
|
42260
42440
|
}
|
|
42441
|
+
if (diff2.contentChange) {
|
|
42442
|
+
for (const alert of diff2.contentChange.newAlerts.slice(0, 5)) {
|
|
42443
|
+
lines.push(` - NEW ALERT: "${truncate(alert, 80)}"`);
|
|
42444
|
+
}
|
|
42445
|
+
const addedToShow = diff2.contentChange.addedTexts.filter((t2) => !diff2.contentChange.newAlerts.includes(t2)).slice(0, 5);
|
|
42446
|
+
if (addedToShow.length > 0) {
|
|
42447
|
+
const moreCount = diff2.contentChange.addedTexts.length - diff2.contentChange.newAlerts.length - addedToShow.length;
|
|
42448
|
+
const moreText = moreCount > 0 ? ` (+${moreCount} more)` : "";
|
|
42449
|
+
lines.push(` - CONTENT: + ${addedToShow.map((t2) => `"${truncate(t2, 60)}"`).join(", ")}${moreText}`);
|
|
42450
|
+
}
|
|
42451
|
+
const removedToShow2 = diff2.contentChange.removedTexts.slice(0, 5);
|
|
42452
|
+
if (removedToShow2.length > 0) {
|
|
42453
|
+
const moreCount = diff2.contentChange.removedTexts.length - removedToShow2.length;
|
|
42454
|
+
const moreText = moreCount > 0 ? ` (+${moreCount} more)` : "";
|
|
42455
|
+
lines.push(
|
|
42456
|
+
` - CONTENT: - ${removedToShow2.map((t2) => `"${truncate(t2, 60)}"`).join(", ")}${moreText}`
|
|
42457
|
+
);
|
|
42458
|
+
}
|
|
42459
|
+
}
|
|
42261
42460
|
}
|
|
42262
42461
|
if (diff2.elementCountDelta !== 0) {
|
|
42263
42462
|
const sign = diff2.elementCountDelta > 0 ? "+" : "";
|
|
@@ -42825,13 +43024,21 @@ function extractFormattedSnapshotRefs(text) {
|
|
|
42825
43024
|
}
|
|
42826
43025
|
return [...refs];
|
|
42827
43026
|
}
|
|
42828
|
-
function
|
|
42829
|
-
|
|
42830
|
-
|
|
42831
|
-
|
|
42832
|
-
|
|
42833
|
-
|
|
42834
|
-
|
|
43027
|
+
function parseHorizontalScrollRefs(yaml) {
|
|
43028
|
+
const byRef = /* @__PURE__ */ new Map();
|
|
43029
|
+
for (const line of yaml.split("\n")) {
|
|
43030
|
+
const refMatch = line.match(/\[ref=(e\d+)\]/);
|
|
43031
|
+
if (!refMatch) continue;
|
|
43032
|
+
const dirMatch = line.match(/~hclip=(left|right)\b/);
|
|
43033
|
+
if (dirMatch) byRef.set(refMatch[1], dirMatch[1]);
|
|
43034
|
+
}
|
|
43035
|
+
return byRef;
|
|
43036
|
+
}
|
|
43037
|
+
function markHorizontalScrollRefsInText(text, byRef) {
|
|
43038
|
+
if (byRef.size === 0) return text;
|
|
43039
|
+
return text.replace(/\[(e\d+)\](?! \[scroll-(?:left|right)\])/g, (match, ref) => {
|
|
43040
|
+
const dir = byRef.get(ref);
|
|
43041
|
+
return dir ? `[${ref}] [scroll-${dir}]` : match;
|
|
42835
43042
|
});
|
|
42836
43043
|
}
|
|
42837
43044
|
var BrowserToolExecutor = class {
|
|
@@ -42851,12 +43058,20 @@ var BrowserToolExecutor = class {
|
|
|
42851
43058
|
* call share one allocation instead of calling `combineSignals` each time.
|
|
42852
43059
|
*/
|
|
42853
43060
|
cachedCombinedSignal;
|
|
43061
|
+
/**
|
|
43062
|
+
* In-flight forced page-recovery kicked off when a tool call's per-tool
|
|
43063
|
+
* timeout fired on a wedged page. The NEXT `withCallSignal` awaits it before
|
|
43064
|
+
* running, so the next action starts on a fresh, re-navigated page instead of
|
|
43065
|
+
* issuing another orphaning op against the frozen renderer.
|
|
43066
|
+
*/
|
|
43067
|
+
recoveryInFlight;
|
|
42854
43068
|
onScreenshot;
|
|
42855
43069
|
maxImageDimension;
|
|
42856
43070
|
clickValidation;
|
|
42857
43071
|
allowedHosts;
|
|
42858
43072
|
autoSnapshotAfterAction;
|
|
42859
43073
|
includeScreenshotWithSnapshot;
|
|
43074
|
+
liveSnapshotEnabled;
|
|
42860
43075
|
workspaceDir;
|
|
42861
43076
|
logger;
|
|
42862
43077
|
spanFactory;
|
|
@@ -42896,6 +43111,9 @@ var BrowserToolExecutor = class {
|
|
|
42896
43111
|
this.allowedHosts = options?.allowedHosts;
|
|
42897
43112
|
this.autoSnapshotAfterAction = options?.autoSnapshotAfterAction !== false;
|
|
42898
43113
|
this.includeScreenshotWithSnapshot = options?.includeScreenshotWithSnapshot !== false;
|
|
43114
|
+
this.liveSnapshotEnabled = options?.liveSnapshot === true;
|
|
43115
|
+
this.client.setLiveSnapshotEnabled?.(this.liveSnapshotEnabled);
|
|
43116
|
+
this.client.setLiveSnapshotRawOnMiss?.(options?.liveSnapshotRawOnMiss === true);
|
|
42899
43117
|
if (!options?.workspaceDir && !options?.tmpBaseDir) {
|
|
42900
43118
|
options?.logger?.debug?.(
|
|
42901
43119
|
"[BrowserToolExecutor] No tmpBaseDir provided, using flat canary dir (not org-scoped)"
|
|
@@ -42993,6 +43211,12 @@ var BrowserToolExecutor = class {
|
|
|
42993
43211
|
"[BrowserToolExecutor] withCallSignal invoked while another per-call signal was already active. Concurrent tool calls on a single executor are not supported \u2014 verify the session gate is in place."
|
|
42994
43212
|
);
|
|
42995
43213
|
}
|
|
43214
|
+
if (this.recoveryInFlight) {
|
|
43215
|
+
const pending = this.recoveryInFlight;
|
|
43216
|
+
this.recoveryInFlight = void 0;
|
|
43217
|
+
await pending.catch(() => {
|
|
43218
|
+
});
|
|
43219
|
+
}
|
|
42996
43220
|
const prev = this.callSignal;
|
|
42997
43221
|
const prevCached = this.cachedCombinedSignal;
|
|
42998
43222
|
this.callSignal = signal;
|
|
@@ -43007,6 +43231,7 @@ var BrowserToolExecutor = class {
|
|
|
43007
43231
|
error: err instanceof Error ? err.message : String(err)
|
|
43008
43232
|
});
|
|
43009
43233
|
}
|
|
43234
|
+
this.scheduleForceRecover("tool_timeout");
|
|
43010
43235
|
};
|
|
43011
43236
|
if (signal?.aborted) {
|
|
43012
43237
|
fireUnwedge();
|
|
@@ -43033,6 +43258,56 @@ var BrowserToolExecutor = class {
|
|
|
43033
43258
|
if (onAbort && signal) signal.removeEventListener("abort", onAbort);
|
|
43034
43259
|
}
|
|
43035
43260
|
}
|
|
43261
|
+
/**
|
|
43262
|
+
* Schedule a liveness-gated forced recovery, CHAINING onto any already-pending
|
|
43263
|
+
* one. Two triggers can fire within a single tool call — the per-tool abort
|
|
43264
|
+
* (`withCallSignal`) and the post-action snapshot timeout — so a bare
|
|
43265
|
+
* assignment would clobber the first (leaving it unawaited) and double-probe.
|
|
43266
|
+
* Chaining makes the next `withCallSignal` await the whole sequence and keeps
|
|
43267
|
+
* the probes serial. This is the single owner of `recoveryInFlight`.
|
|
43268
|
+
*/
|
|
43269
|
+
scheduleForceRecover(reason) {
|
|
43270
|
+
this.recoveryInFlight = (this.recoveryInFlight ?? Promise.resolve()).then(
|
|
43271
|
+
() => this.maybeForceRecover(reason)
|
|
43272
|
+
);
|
|
43273
|
+
}
|
|
43274
|
+
/**
|
|
43275
|
+
* Liveness-gated forced recovery. Probes whether the page's main thread is
|
|
43276
|
+
* responsive; only tears it down when it is genuinely wedged, so a
|
|
43277
|
+
* slow-but-progressing page is spared (its orphan settles on its own and the
|
|
43278
|
+
* abort-gated re-checks elsewhere prevent it from corrupting state). Never
|
|
43279
|
+
* throws — recovery is best-effort.
|
|
43280
|
+
*/
|
|
43281
|
+
async maybeForceRecover(reason) {
|
|
43282
|
+
const client = this.client;
|
|
43283
|
+
if (typeof client.probeLiveness !== "function" || typeof client.forceRecoverPage !== "function") {
|
|
43284
|
+
return;
|
|
43285
|
+
}
|
|
43286
|
+
try {
|
|
43287
|
+
const liveness = await client.probeLiveness();
|
|
43288
|
+
if (liveness === "alive") {
|
|
43289
|
+
this.logger?.info?.(
|
|
43290
|
+
"[BrowserToolExecutor] page alive after tool timeout; sparing it (no forced recovery)"
|
|
43291
|
+
);
|
|
43292
|
+
return;
|
|
43293
|
+
}
|
|
43294
|
+
this.logger?.warn?.(
|
|
43295
|
+
liveness === "closed" ? "[BrowserToolExecutor] page already closed after tool timeout; recreating" : "[BrowserToolExecutor] page unresponsive after tool timeout; forcing page recovery",
|
|
43296
|
+
{ liveness, reason }
|
|
43297
|
+
);
|
|
43298
|
+
const recovered = await client.forceRecoverPage(reason);
|
|
43299
|
+
if (!recovered) {
|
|
43300
|
+
this.logger?.warn?.(
|
|
43301
|
+
"[BrowserToolExecutor] forced recovery did not yield a live page; next action may fail",
|
|
43302
|
+
{ liveness, reason }
|
|
43303
|
+
);
|
|
43304
|
+
}
|
|
43305
|
+
} catch (err) {
|
|
43306
|
+
this.logger?.warn?.("[BrowserToolExecutor] forced recovery failed", {
|
|
43307
|
+
error: err instanceof Error ? err.message : String(err)
|
|
43308
|
+
});
|
|
43309
|
+
}
|
|
43310
|
+
}
|
|
43036
43311
|
// ==================== Navigation ====================
|
|
43037
43312
|
async navigate(url) {
|
|
43038
43313
|
ensureAllowedHost(url, this.allowedHosts);
|
|
@@ -43239,32 +43514,15 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
|
|
|
43239
43514
|
matchedHintIds: void 0
|
|
43240
43515
|
};
|
|
43241
43516
|
let semanticText = formatSemanticSnapshot(yaml, snapshotOpts);
|
|
43242
|
-
|
|
43243
|
-
|
|
43244
|
-
|
|
43245
|
-
|
|
43246
|
-
|
|
43247
|
-
|
|
43248
|
-
|
|
43249
|
-
|
|
43250
|
-
const offscreen = /* @__PURE__ */ new Set();
|
|
43251
|
-
for (const [ref, box] of boxes) {
|
|
43252
|
-
if (box.width === 0 && box.height === 0) continue;
|
|
43253
|
-
const right = box.x + box.width;
|
|
43254
|
-
const bottom = box.y + box.height;
|
|
43255
|
-
if (right < 0 || box.x > vw || bottom < 0 || box.y > vh) {
|
|
43256
|
-
offscreen.add(ref);
|
|
43257
|
-
}
|
|
43258
|
-
}
|
|
43259
|
-
return offscreen.size > 0 ? offscreen : void 0;
|
|
43260
|
-
});
|
|
43261
|
-
if (offScreenRefs) {
|
|
43262
|
-
semanticText = markOffScreenRefsInText(semanticText, offScreenRefs);
|
|
43517
|
+
const formattedRefs = extractFormattedSnapshotRefs(semanticText);
|
|
43518
|
+
if (formattedRefs.length > LARGE_PAGE_REF_THRESHOLD) {
|
|
43519
|
+
semanticText = `[Large page: ~${formattedRefs.length.toLocaleString()} interactive elements \u2014 scroll-position hints reduced for speed. Narrow with browser_snapshot search=\u2026 or scroll to a region.]
|
|
43520
|
+
` + semanticText;
|
|
43521
|
+
} else {
|
|
43522
|
+
const hScrollRefs = parseHorizontalScrollRefs(yaml);
|
|
43523
|
+
if (hScrollRefs.size > 0) {
|
|
43524
|
+
semanticText = markHorizontalScrollRefsInText(semanticText, hScrollRefs);
|
|
43263
43525
|
}
|
|
43264
|
-
} catch (e2) {
|
|
43265
|
-
this.logger?.debug?.("[browser_snapshot] Off-screen detection failed", {
|
|
43266
|
-
error: e2 instanceof Error ? e2.message : String(e2)
|
|
43267
|
-
});
|
|
43268
43526
|
}
|
|
43269
43527
|
if (snapshotOpts.matchedHintIds && snapshotOpts.matchedHintIds.length > 0 && this.hintProvider) {
|
|
43270
43528
|
this.hintProvider.recordMatches(snapshotOpts.matchedHintIds).catch(() => {
|
|
@@ -43812,11 +44070,14 @@ ${header}
|
|
|
43812
44070
|
const autoEnabled = this.autoSnapshotAfterAction;
|
|
43813
44071
|
const includeScreenshot = this.includeScreenshotWithSnapshot;
|
|
43814
44072
|
const beforeUrl = await this.client.getCurrentUrl();
|
|
43815
|
-
|
|
43816
|
-
|
|
44073
|
+
let beforeYaml = this.liveSnapshotEnabled ? await this.client.getReusableSnapshotYaml?.() ?? null : null;
|
|
44074
|
+
if (beforeYaml === null) {
|
|
44075
|
+
const beforeResult = await this.client.snapshot({ signal: this.currentSignal, mode: "tree" });
|
|
44076
|
+
beforeYaml = extractSnapshotYaml(beforeResult);
|
|
44077
|
+
}
|
|
43817
44078
|
const beforeState = captureSnapshotState(beforeUrl, beforeYaml);
|
|
43818
44079
|
const actionResult = await action();
|
|
43819
|
-
const afterMode = autoEnabled && includeScreenshot ? "both" : "tree";
|
|
44080
|
+
const afterMode = !this.liveSnapshotEnabled && autoEnabled && includeScreenshot ? "both" : "tree";
|
|
43820
44081
|
let afterUrl = beforeUrl;
|
|
43821
44082
|
let afterResult = "";
|
|
43822
44083
|
let afterYaml = "";
|
|
@@ -43902,6 +44163,7 @@ ${header}
|
|
|
43902
44163
|
}
|
|
43903
44164
|
if (snapshotTimedOut) {
|
|
43904
44165
|
this.consecutiveSnapshotTimeouts += 1;
|
|
44166
|
+
this.scheduleForceRecover("post_action_snapshot_timeout");
|
|
43905
44167
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
43906
44168
|
const baseDiff = formatSemanticDiff(
|
|
43907
44169
|
compareSnapshots(beforeState, beforeState),
|
|
@@ -43966,7 +44228,31 @@ ${recoveryHint}`;
|
|
|
43966
44228
|
consoleErrors: this.client.getNewConsoleErrors?.() ?? [],
|
|
43967
44229
|
failedRequests: this.client.getNewFailedRequests?.() ?? []
|
|
43968
44230
|
});
|
|
43969
|
-
|
|
44231
|
+
let afterImages;
|
|
44232
|
+
let withheldNote;
|
|
44233
|
+
if (this.liveSnapshotEnabled) {
|
|
44234
|
+
if (includeScreenshot) {
|
|
44235
|
+
const worthiness = classifyScreenshotWorthiness(diff2);
|
|
44236
|
+
if (worthiness.warranted) {
|
|
44237
|
+
try {
|
|
44238
|
+
const shot = await this.client.snapshot({
|
|
44239
|
+
signal: this.currentSignal,
|
|
44240
|
+
mode: "screenshot",
|
|
44241
|
+
maxImageDimension: this.maxImageDimension
|
|
44242
|
+
});
|
|
44243
|
+
afterImages = isMCPContentWithImages(shot) ? shot.images : void 0;
|
|
44244
|
+
} catch (e2) {
|
|
44245
|
+
this.logger?.warn?.("[withSemanticDiff] On-demand screenshot failed", {
|
|
44246
|
+
error: e2 instanceof Error ? e2.message : String(e2)
|
|
44247
|
+
});
|
|
44248
|
+
}
|
|
44249
|
+
} else {
|
|
44250
|
+
withheldNote = "IMAGE: withheld \u2014 no visual-orienting change (input/focus/state-only). Call browser_screenshot or browser_snapshot mode:'both' for a full image.";
|
|
44251
|
+
}
|
|
44252
|
+
}
|
|
44253
|
+
} else {
|
|
44254
|
+
afterImages = isMCPContentWithImages(afterResult) ? afterResult.images : void 0;
|
|
44255
|
+
}
|
|
43970
44256
|
if (afterImages?.length && this.onScreenshot) {
|
|
43971
44257
|
const img = afterImages[0];
|
|
43972
44258
|
await this.onScreenshot(`data:${img.mimeType};base64,${img.data}`, "auto-snapshot");
|
|
@@ -43979,6 +44265,9 @@ ${recoveryHint}`;
|
|
|
43979
44265
|
this.logger
|
|
43980
44266
|
);
|
|
43981
44267
|
snapshotText = resolutionNote + "\n" + semanticText;
|
|
44268
|
+
} else if (withheldNote) {
|
|
44269
|
+
snapshotText = `${withheldNote}
|
|
44270
|
+
${semanticText}`;
|
|
43982
44271
|
}
|
|
43983
44272
|
autoSnapshot = {
|
|
43984
44273
|
text: snapshotText,
|
|
@@ -43990,6 +44279,7 @@ ${recoveryHint}`;
|
|
|
43990
44279
|
});
|
|
43991
44280
|
}
|
|
43992
44281
|
}
|
|
44282
|
+
this.client.warmSnapshot?.();
|
|
43993
44283
|
return { diffText, actionResult, autoSnapshot };
|
|
43994
44284
|
}
|
|
43995
44285
|
/**
|
|
@@ -44348,7 +44638,7 @@ function getBrowserToolDefinitions() {
|
|
|
44348
44638
|
},
|
|
44349
44639
|
toRef: {
|
|
44350
44640
|
type: "string",
|
|
44351
|
-
description: "Scroll the page so this element is centered in the viewport. Handles both vertical and horizontal scrolling automatically \u2014 works for grid columns that are horizontally off-screen."
|
|
44641
|
+
description: "Scroll the page so this element is centered in the viewport. Handles both vertical and horizontal scrolling automatically \u2014 works for grid columns that are horizontally off-screen. A ref marked [scroll-left]/[scroll-right] in the snapshot is clipped inside a horizontal scroll container; pass it here to bring it into view."
|
|
44352
44642
|
},
|
|
44353
44643
|
x: {
|
|
44354
44644
|
type: "number",
|
|
@@ -47302,6 +47592,290 @@ async function augmentBlockingModalSignal(page, yaml, logger2, preParsedElements
|
|
|
47302
47592
|
}
|
|
47303
47593
|
}
|
|
47304
47594
|
|
|
47595
|
+
// ../browser-core/src/playwright-client/dom-revision-script.ts
|
|
47596
|
+
var DOM_REVISION_SCRIPT = `
|
|
47597
|
+
(() => {
|
|
47598
|
+
if (window.__domRevisionInstalled) return;
|
|
47599
|
+
window.__domRevisionInstalled = true;
|
|
47600
|
+
window.__domRevision = 0;
|
|
47601
|
+
window.__lastMutationTs = (typeof performance !== 'undefined' && performance.now)
|
|
47602
|
+
? performance.now()
|
|
47603
|
+
: Date.now();
|
|
47604
|
+
|
|
47605
|
+
var now = function () {
|
|
47606
|
+
return (typeof performance !== 'undefined' && performance.now)
|
|
47607
|
+
? performance.now()
|
|
47608
|
+
: Date.now();
|
|
47609
|
+
};
|
|
47610
|
+
|
|
47611
|
+
var bump = function () {
|
|
47612
|
+
window.__domRevision = (window.__domRevision || 0) + 1;
|
|
47613
|
+
window.__lastMutationTs = now();
|
|
47614
|
+
};
|
|
47615
|
+
|
|
47616
|
+
var install = function () {
|
|
47617
|
+
try {
|
|
47618
|
+
// Observe the Document node itself (not documentElement): it survives
|
|
47619
|
+
// document.open()/write() \u2014 which replaces documentElement and would
|
|
47620
|
+
// orphan an observer attached to it \u2014 while subtree:true still catches
|
|
47621
|
+
// every descendant childList/attribute/text mutation.
|
|
47622
|
+
var observer = new MutationObserver(bump);
|
|
47623
|
+
observer.observe(document, {
|
|
47624
|
+
childList: true,
|
|
47625
|
+
attributes: true,
|
|
47626
|
+
characterData: true,
|
|
47627
|
+
subtree: true,
|
|
47628
|
+
});
|
|
47629
|
+
window.__domRevisionObserver = observer;
|
|
47630
|
+
return true;
|
|
47631
|
+
} catch (e) {
|
|
47632
|
+
return false;
|
|
47633
|
+
}
|
|
47634
|
+
};
|
|
47635
|
+
|
|
47636
|
+
if (!install()) {
|
|
47637
|
+
document.addEventListener('DOMContentLoaded', install, { once: true });
|
|
47638
|
+
}
|
|
47639
|
+
|
|
47640
|
+
// Input/focus activity doesn't always fire a mutation (typing sets the .value
|
|
47641
|
+
// property), but it IS "the page is changing" for settle purposes \u2014 so count
|
|
47642
|
+
// it toward __lastMutationTs. Capture phase on window so it survives document
|
|
47643
|
+
// replacement and catches events from inside shadow DOM (they're composed).
|
|
47644
|
+
window.addEventListener('input', bump, true);
|
|
47645
|
+
window.addEventListener('change', bump, true);
|
|
47646
|
+
window.addEventListener('focusin', bump, true);
|
|
47647
|
+
window.addEventListener('focusout', bump, true);
|
|
47648
|
+
|
|
47649
|
+
// Resolves when the DOM has been quiet (no mutations / input) for quietMs, or
|
|
47650
|
+
// after maxWaitMs elapses \u2014 whichever comes first. Event-driven (no polling):
|
|
47651
|
+
// a single timer is (re)scheduled off __lastMutationTs.
|
|
47652
|
+
window.__waitDomQuiet = function (quietMs, maxWaitMs) {
|
|
47653
|
+
quietMs = typeof quietMs === 'number' ? quietMs : 100;
|
|
47654
|
+
maxWaitMs = typeof maxWaitMs === 'number' ? maxWaitMs : 2000;
|
|
47655
|
+
return new Promise(function (resolve) {
|
|
47656
|
+
var start = now();
|
|
47657
|
+
var timer = null;
|
|
47658
|
+
var done = false;
|
|
47659
|
+
|
|
47660
|
+
var finish = function (reason) {
|
|
47661
|
+
if (done) return;
|
|
47662
|
+
done = true;
|
|
47663
|
+
if (timer) clearTimeout(timer);
|
|
47664
|
+
resolve(reason);
|
|
47665
|
+
};
|
|
47666
|
+
|
|
47667
|
+
var schedule = function () {
|
|
47668
|
+
if (done) return;
|
|
47669
|
+
var sinceMutation = now() - (window.__lastMutationTs || 0);
|
|
47670
|
+
var sinceStart = now() - start;
|
|
47671
|
+
// Hard cap: never block past maxWaitMs (streaming/websocket pages).
|
|
47672
|
+
if (sinceStart >= maxWaitMs) {
|
|
47673
|
+
finish('maxwait');
|
|
47674
|
+
return;
|
|
47675
|
+
}
|
|
47676
|
+
if (sinceMutation >= quietMs) {
|
|
47677
|
+
finish('quiet');
|
|
47678
|
+
return;
|
|
47679
|
+
}
|
|
47680
|
+
// Wait for the shorter of "quiet window remaining" / "maxwait remaining",
|
|
47681
|
+
// then re-check (a mutation may have landed since, moving __lastMutationTs).
|
|
47682
|
+
var waitForQuiet = quietMs - sinceMutation;
|
|
47683
|
+
var waitForCap = maxWaitMs - sinceStart;
|
|
47684
|
+
timer = setTimeout(schedule, Math.max(0, Math.min(waitForQuiet, waitForCap)));
|
|
47685
|
+
};
|
|
47686
|
+
|
|
47687
|
+
schedule();
|
|
47688
|
+
});
|
|
47689
|
+
};
|
|
47690
|
+
})();
|
|
47691
|
+
`;
|
|
47692
|
+
|
|
47693
|
+
// ../browser-core/src/playwright-client/evaluate-with-cap.ts
|
|
47694
|
+
var EVALUATE_CAPPED = /* @__PURE__ */ Symbol("browser-core.evaluate-capped");
|
|
47695
|
+
async function evaluateWithCap(capMs, op) {
|
|
47696
|
+
let timer;
|
|
47697
|
+
let capped = false;
|
|
47698
|
+
const cap = new Promise((resolve) => {
|
|
47699
|
+
timer = setTimeout(() => {
|
|
47700
|
+
capped = true;
|
|
47701
|
+
resolve(EVALUATE_CAPPED);
|
|
47702
|
+
}, capMs);
|
|
47703
|
+
});
|
|
47704
|
+
const evaluation = op().catch(
|
|
47705
|
+
(err) => {
|
|
47706
|
+
if (capped) return EVALUATE_CAPPED;
|
|
47707
|
+
throw err;
|
|
47708
|
+
}
|
|
47709
|
+
);
|
|
47710
|
+
try {
|
|
47711
|
+
return await Promise.race([evaluation, cap]);
|
|
47712
|
+
} finally {
|
|
47713
|
+
if (timer) clearTimeout(timer);
|
|
47714
|
+
}
|
|
47715
|
+
}
|
|
47716
|
+
|
|
47717
|
+
// ../browser-core/src/playwright-client/http-settle.ts
|
|
47718
|
+
var STREAM_RESOURCE_TYPES = /* @__PURE__ */ new Set(["eventsource", "websocket"]);
|
|
47719
|
+
function countUnsettledRequests(requests, now, opts) {
|
|
47720
|
+
let n2 = 0;
|
|
47721
|
+
for (const req of requests) {
|
|
47722
|
+
if (req.status !== "pending") continue;
|
|
47723
|
+
if (req.resourceType !== void 0 && STREAM_RESOURCE_TYPES.has(req.resourceType)) continue;
|
|
47724
|
+
if (opts.noisePatterns.some((p2) => p2.test(req.url))) continue;
|
|
47725
|
+
if (req.startedAt !== void 0 && now - req.startedAt > opts.maxAgeMs) continue;
|
|
47726
|
+
n2++;
|
|
47727
|
+
}
|
|
47728
|
+
return n2;
|
|
47729
|
+
}
|
|
47730
|
+
|
|
47731
|
+
// ../browser-core/src/playwright-client/liveness-probe.ts
|
|
47732
|
+
var Liveness = {
|
|
47733
|
+
Alive: "alive",
|
|
47734
|
+
Unresponsive: "unresponsive",
|
|
47735
|
+
Closed: "closed"
|
|
47736
|
+
};
|
|
47737
|
+
var CLOSURE_HINTS = [
|
|
47738
|
+
"target closed",
|
|
47739
|
+
"has been closed",
|
|
47740
|
+
"browser has been closed",
|
|
47741
|
+
"execution context was destroyed",
|
|
47742
|
+
"browser closed",
|
|
47743
|
+
"context closed"
|
|
47744
|
+
];
|
|
47745
|
+
function looksClosed(err) {
|
|
47746
|
+
const message = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
47747
|
+
return CLOSURE_HINTS.some((h) => message.includes(h));
|
|
47748
|
+
}
|
|
47749
|
+
async function probePageLiveness(page, capMs) {
|
|
47750
|
+
if (!page || page.isClosed()) return Liveness.Closed;
|
|
47751
|
+
try {
|
|
47752
|
+
const result = await evaluateWithCap(capMs, () => page.evaluate(() => 1));
|
|
47753
|
+
if (result === EVALUATE_CAPPED) return Liveness.Unresponsive;
|
|
47754
|
+
return Liveness.Alive;
|
|
47755
|
+
} catch (err) {
|
|
47756
|
+
return looksClosed(err) ? Liveness.Closed : Liveness.Unresponsive;
|
|
47757
|
+
}
|
|
47758
|
+
}
|
|
47759
|
+
|
|
47760
|
+
// ../browser-core/src/live-snapshot.ts
|
|
47761
|
+
function mintedYaml(snap) {
|
|
47762
|
+
return snap.augmentedYaml ?? snap.rawYaml;
|
|
47763
|
+
}
|
|
47764
|
+
var LiveSnapshotManager = class {
|
|
47765
|
+
constructor(deps) {
|
|
47766
|
+
this.deps = deps;
|
|
47767
|
+
this.now = deps.now ?? (() => Date.now());
|
|
47768
|
+
}
|
|
47769
|
+
deps;
|
|
47770
|
+
current = null;
|
|
47771
|
+
generationCounter = 0;
|
|
47772
|
+
/** Serializes captures so two never run against the page at once. */
|
|
47773
|
+
chain = Promise.resolve();
|
|
47774
|
+
now;
|
|
47775
|
+
/** The current mint without re-checking the live page (may be stale). */
|
|
47776
|
+
peek() {
|
|
47777
|
+
return this.current;
|
|
47778
|
+
}
|
|
47779
|
+
/** Drop the cached snapshot (e.g. on navigation / new page). */
|
|
47780
|
+
reset() {
|
|
47781
|
+
this.current = null;
|
|
47782
|
+
}
|
|
47783
|
+
/** Run `fn` after any in-flight capture settles; never overlap captures. */
|
|
47784
|
+
enqueue(fn) {
|
|
47785
|
+
const run = this.chain.then(fn, fn);
|
|
47786
|
+
this.chain = run.then(
|
|
47787
|
+
() => void 0,
|
|
47788
|
+
() => void 0
|
|
47789
|
+
);
|
|
47790
|
+
return run;
|
|
47791
|
+
}
|
|
47792
|
+
/**
|
|
47793
|
+
* Produce a current snapshot, reusing the augmented one when the raw substrate
|
|
47794
|
+
* is unchanged. Always takes a fresh (cheap) raw substrate first, so it never
|
|
47795
|
+
* serves a stale representation.
|
|
47796
|
+
*/
|
|
47797
|
+
mint(opts) {
|
|
47798
|
+
return this.enqueue(() => this.doMint(opts));
|
|
47799
|
+
}
|
|
47800
|
+
async doMint(opts) {
|
|
47801
|
+
const raw = await this.deps.captureRaw(opts?.signal);
|
|
47802
|
+
if (!raw) {
|
|
47803
|
+
if (this.current) return this.current;
|
|
47804
|
+
throw new Error("[LiveSnapshot] captureRaw returned null and there is no current snapshot");
|
|
47805
|
+
}
|
|
47806
|
+
if (!opts?.force && this.current && this.current.augmentedYaml !== null && this.current.rawYaml === raw.rawYaml) {
|
|
47807
|
+
return this.current;
|
|
47808
|
+
}
|
|
47809
|
+
if (opts?.rawOnMiss) {
|
|
47810
|
+
const snap = this.store(raw.url, raw.rawYaml, null);
|
|
47811
|
+
this.scheduleAugment(snap, opts?.signal);
|
|
47812
|
+
return snap;
|
|
47813
|
+
}
|
|
47814
|
+
const augmentedYaml = await this.deps.augment(raw.rawYaml, opts?.signal);
|
|
47815
|
+
return this.store(raw.url, raw.rawYaml, augmentedYaml);
|
|
47816
|
+
}
|
|
47817
|
+
store(url, rawYaml, augmentedYaml) {
|
|
47818
|
+
const snap = {
|
|
47819
|
+
generation: ++this.generationCounter,
|
|
47820
|
+
url,
|
|
47821
|
+
rawYaml,
|
|
47822
|
+
augmentedYaml,
|
|
47823
|
+
capturedAtMs: this.now()
|
|
47824
|
+
};
|
|
47825
|
+
this.current = snap;
|
|
47826
|
+
return snap;
|
|
47827
|
+
}
|
|
47828
|
+
/** Background-augment a raw-only mint, upgrading `current` in place if still current. */
|
|
47829
|
+
scheduleAugment(snap, signal) {
|
|
47830
|
+
void this.enqueue(async () => {
|
|
47831
|
+
if (this.current !== snap) return;
|
|
47832
|
+
try {
|
|
47833
|
+
const augmentedYaml = await this.deps.augment(snap.rawYaml, signal);
|
|
47834
|
+
if (this.current === snap) {
|
|
47835
|
+
this.current = { ...snap, augmentedYaml };
|
|
47836
|
+
}
|
|
47837
|
+
} catch (err) {
|
|
47838
|
+
this.deps.logger?.debug?.("[LiveSnapshot] background augment failed (ignored)", {
|
|
47839
|
+
error: err instanceof Error ? err.message : String(err)
|
|
47840
|
+
});
|
|
47841
|
+
}
|
|
47842
|
+
}).catch(() => void 0);
|
|
47843
|
+
}
|
|
47844
|
+
/**
|
|
47845
|
+
* Return the current AUGMENTED snapshot if its raw substrate still matches the
|
|
47846
|
+
* live page, else null — used to reuse the warm snapshot as a diff baseline.
|
|
47847
|
+
* Takes a fresh (cheap) raw substrate to verify; never augments.
|
|
47848
|
+
*/
|
|
47849
|
+
getReusable(opts) {
|
|
47850
|
+
return this.enqueue(async () => {
|
|
47851
|
+
if (!this.current || this.current.augmentedYaml === null) return null;
|
|
47852
|
+
const raw = await this.deps.captureRaw(opts?.signal);
|
|
47853
|
+
if (!raw) return null;
|
|
47854
|
+
return raw.rawYaml === this.current.rawYaml ? this.current : null;
|
|
47855
|
+
});
|
|
47856
|
+
}
|
|
47857
|
+
/**
|
|
47858
|
+
* Fire-and-forget background refresh. Waits for the DOM to settle, then mints
|
|
47859
|
+
* (reusing or augmenting) so the next request hits the reuse path. Errors are
|
|
47860
|
+
* swallowed — warming must never surface to the agent.
|
|
47861
|
+
*/
|
|
47862
|
+
warm(opts) {
|
|
47863
|
+
void this.warmAsync(opts).catch((err) => {
|
|
47864
|
+
this.deps.logger?.debug?.("[LiveSnapshot] warm failed (ignored)", {
|
|
47865
|
+
error: err instanceof Error ? err.message : String(err)
|
|
47866
|
+
});
|
|
47867
|
+
});
|
|
47868
|
+
}
|
|
47869
|
+
/** Test seam: the awaitable warm body. */
|
|
47870
|
+
async warmAsync(opts) {
|
|
47871
|
+
if (this.deps.waitQuiet) {
|
|
47872
|
+
await this.deps.waitQuiet(opts?.quietMs ?? 100, opts?.maxWaitMs ?? 2e3);
|
|
47873
|
+
}
|
|
47874
|
+
if (opts?.signal?.aborted) return;
|
|
47875
|
+
await this.mint({ signal: opts?.signal, rawOnMiss: opts?.rawOnMiss });
|
|
47876
|
+
}
|
|
47877
|
+
};
|
|
47878
|
+
|
|
47305
47879
|
// ../browser-core/src/playwright-client/response-format.ts
|
|
47306
47880
|
var TOAST_SNAPSHOT_TTL = 5;
|
|
47307
47881
|
function formatMcpResponse(input) {
|
|
@@ -47565,6 +48139,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
47565
48139
|
screencastEncoderFactory;
|
|
47566
48140
|
tmpBaseDir;
|
|
47567
48141
|
spanFactory;
|
|
48142
|
+
stabilizeDomMs;
|
|
47568
48143
|
/** In-flight latch for `attemptUnwedgePage` so close-succession aborts
|
|
47569
48144
|
* don't dispatch redundant `window.stop()` evaluates. */
|
|
47570
48145
|
unwedgeInFlight = false;
|
|
@@ -47572,6 +48147,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
47572
48147
|
this.browserLease = options?.browserLease ?? null;
|
|
47573
48148
|
this.logger = options?.logger ?? consoleLogger;
|
|
47574
48149
|
this.spanFactory = options?.spanFactory ?? null;
|
|
48150
|
+
this.stabilizeDomMs = options?.stabilizeDomMs ?? TIMEOUTS.STABILIZE_DOM;
|
|
47575
48151
|
this.screencastEncoderFactory = options?.screencastEncoderFactory ?? null;
|
|
47576
48152
|
this.cursorOverlay = new CursorOverlay({ enabled: false });
|
|
47577
48153
|
if (!options?.tmpBaseDir) {
|
|
@@ -47580,6 +48156,95 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
47580
48156
|
);
|
|
47581
48157
|
}
|
|
47582
48158
|
this.tmpBaseDir = options?.tmpBaseDir ?? getCanaryTmpDir();
|
|
48159
|
+
this.liveSnapshot = new LiveSnapshotManager({
|
|
48160
|
+
logger: this.logger,
|
|
48161
|
+
// CHEAP tier: the raw substrate (the freshness ground truth).
|
|
48162
|
+
captureRaw: async (signal) => {
|
|
48163
|
+
const page = this.page;
|
|
48164
|
+
if (!page || page.isClosed()) return null;
|
|
48165
|
+
const rawYaml = await this.captureRawSubstrate(signal).catch(() => "");
|
|
48166
|
+
if (signal?.aborted || !rawYaml) return null;
|
|
48167
|
+
return { rawYaml, url: page.url() };
|
|
48168
|
+
},
|
|
48169
|
+
// EXPENSIVE tier: augment the raw substrate.
|
|
48170
|
+
augment: (rawYaml, signal) => this.augmentSubstrate(rawYaml, signal),
|
|
48171
|
+
waitQuiet: (quietMs, maxWaitMs) => this.waitDomQuiet(quietMs, maxWaitMs)
|
|
48172
|
+
});
|
|
48173
|
+
}
|
|
48174
|
+
/** Enable/disable the live-snapshot (warm + minted) path. Off = legacy v1. */
|
|
48175
|
+
setLiveSnapshotEnabled(enabled) {
|
|
48176
|
+
this.liveSnapshotEnabled = enabled;
|
|
48177
|
+
}
|
|
48178
|
+
/**
|
|
48179
|
+
* On a freshness miss, return the raw substrate immediately and augment in the
|
|
48180
|
+
* background (instead of blocking). Default off → block on augmentation
|
|
48181
|
+
* (matches legacy behavior and the snapshot formatter's expectations).
|
|
48182
|
+
*/
|
|
48183
|
+
setLiveSnapshotRawOnMiss(rawOnMiss) {
|
|
48184
|
+
this.liveSnapshotRawOnMiss = rawOnMiss;
|
|
48185
|
+
}
|
|
48186
|
+
/**
|
|
48187
|
+
* Capture the accessibility-tree YAML, routing through the live-snapshot
|
|
48188
|
+
* manager when enabled (raw substrate compared exactly; augmentation reused
|
|
48189
|
+
* when unchanged). `force` re-augments unconditionally (used by navigation).
|
|
48190
|
+
* Falls back to the legacy full capture when disabled.
|
|
48191
|
+
*/
|
|
48192
|
+
captureTreeYaml(signal, opts) {
|
|
48193
|
+
if (this.liveSnapshotEnabled) {
|
|
48194
|
+
return this.liveSnapshot.mint({ signal, force: opts?.force, rawOnMiss: this.liveSnapshotRawOnMiss }).then((s2) => mintedYaml(s2));
|
|
48195
|
+
}
|
|
48196
|
+
return this.traced(
|
|
48197
|
+
"snapshot_for_ai",
|
|
48198
|
+
"capture accessibility tree",
|
|
48199
|
+
() => this.captureSnapshotInternal(signal)
|
|
48200
|
+
);
|
|
48201
|
+
}
|
|
48202
|
+
/**
|
|
48203
|
+
* Wait until the DOM has been quiet for `quietMs` (bounded by `maxWaitMs`),
|
|
48204
|
+
* using the page-side `__waitDomQuiet` helper. If the helper is missing or
|
|
48205
|
+
* unreadable (init-script race, or a pre-existing CDP document), fall back to
|
|
48206
|
+
* a fixed `quietMs` delay — NOT an immediate return, which would drop the
|
|
48207
|
+
* stabilize wait and risk capturing a half-rendered SPA. Soft — never throws.
|
|
48208
|
+
*
|
|
48209
|
+
* The evaluate is bounded by a DRIVER-side race (`maxWaitMs` + grace): the
|
|
48210
|
+
* in-page cap is a page-side setTimeout that never fires when the main
|
|
48211
|
+
* thread is blocked, so without the race a single stalled page hangs this
|
|
48212
|
+
* await forever and wedges every subsequent tool call (June 2026
|
|
48213
|
+
* live-snapshot suite wedge). On cap we return immediately — the stabilize
|
|
48214
|
+
* budget is already spent; stacking the quietMs fallback on top would only
|
|
48215
|
+
* delay the caller further on a page that can't get healthier by waiting.
|
|
48216
|
+
*/
|
|
48217
|
+
async waitDomQuiet(quietMs, maxWaitMs) {
|
|
48218
|
+
const page = this.page;
|
|
48219
|
+
if (!page || page.isClosed()) return;
|
|
48220
|
+
let ran = false;
|
|
48221
|
+
try {
|
|
48222
|
+
const result = await evaluateWithCap(
|
|
48223
|
+
maxWaitMs + TIMEOUTS.WAIT_DOM_QUIET_EVAL_GRACE,
|
|
48224
|
+
() => page.evaluate(
|
|
48225
|
+
async ({ q, m }) => {
|
|
48226
|
+
const w = window;
|
|
48227
|
+
if (typeof w.__waitDomQuiet !== "function") return false;
|
|
48228
|
+
await w.__waitDomQuiet(q, m);
|
|
48229
|
+
return true;
|
|
48230
|
+
},
|
|
48231
|
+
{ q: quietMs, m: maxWaitMs }
|
|
48232
|
+
)
|
|
48233
|
+
);
|
|
48234
|
+
if (result === EVALUATE_CAPPED) {
|
|
48235
|
+
this.logger.warn(
|
|
48236
|
+
"[DirectPlaywright] waitDomQuiet evaluate exceeded driver-side cap (page main thread may be blocked)",
|
|
48237
|
+
{ maxWaitMs, graceMs: TIMEOUTS.WAIT_DOM_QUIET_EVAL_GRACE }
|
|
48238
|
+
);
|
|
48239
|
+
return;
|
|
48240
|
+
}
|
|
48241
|
+
ran = result;
|
|
48242
|
+
} catch {
|
|
48243
|
+
ran = false;
|
|
48244
|
+
}
|
|
48245
|
+
if (!ran) {
|
|
48246
|
+
await page.waitForTimeout(quietMs).catch(() => void 0);
|
|
48247
|
+
}
|
|
47583
48248
|
}
|
|
47584
48249
|
/**
|
|
47585
48250
|
* Race a Playwright op against `signal`:
|
|
@@ -47646,7 +48311,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
47646
48311
|
this.logger.info("[DirectPlaywright] attemptUnwedgePage dispatched");
|
|
47647
48312
|
let timer;
|
|
47648
48313
|
void Promise.race([
|
|
47649
|
-
page.evaluate(
|
|
48314
|
+
page.evaluate(() => window.stop()).then(() => this.logger.info("[DirectPlaywright] attemptUnwedgePage completed")).catch((err) => {
|
|
47650
48315
|
this.logger.warn("[DirectPlaywright] attemptUnwedgePage evaluate failed", {
|
|
47651
48316
|
error: err instanceof Error ? err.message : String(err)
|
|
47652
48317
|
});
|
|
@@ -47665,6 +48330,65 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
47665
48330
|
this.unwedgeInFlight = false;
|
|
47666
48331
|
});
|
|
47667
48332
|
}
|
|
48333
|
+
/**
|
|
48334
|
+
* Cheap, driver-bounded liveness check (`page.evaluate(() => 1)` under a
|
|
48335
|
+
* short cap). Used by the per-tool-timeout recovery path to decide whether a
|
|
48336
|
+
* timed-out page is genuinely wedged (force-recover) or just slow (spare it).
|
|
48337
|
+
*/
|
|
48338
|
+
async probeLiveness() {
|
|
48339
|
+
return probePageLiveness(this.page, TIMEOUTS.LIVENESS_PROBE_CAP);
|
|
48340
|
+
}
|
|
48341
|
+
/**
|
|
48342
|
+
* Force-recover the active page so orphaned Playwright ops die with the
|
|
48343
|
+
* renderer. `ariaSnapshot`/`screenshot` have no timeout and cannot be
|
|
48344
|
+
* cancelled; when the per-tool timeout fires they keep running for minutes on
|
|
48345
|
+
* a jammed renderer. Closing the page rejects every in-flight op bound to its
|
|
48346
|
+
* CDP session — the only thing that truly kills the orphans — then we recreate
|
|
48347
|
+
* the page and re-navigate to `lastKnownUrl` via the existing crash-recovery
|
|
48348
|
+
* path. Never throws; returns whether a live page is available afterward.
|
|
48349
|
+
*/
|
|
48350
|
+
async forceRecoverPage(reason) {
|
|
48351
|
+
if (this.pageClosedIntentionally) return false;
|
|
48352
|
+
if (this.restoreInProgress && this.restorePromise) {
|
|
48353
|
+
await this.restorePromise.catch(() => {
|
|
48354
|
+
});
|
|
48355
|
+
return !!this.page && !this.page.isClosed();
|
|
48356
|
+
}
|
|
48357
|
+
const startedAt = Date.now();
|
|
48358
|
+
try {
|
|
48359
|
+
const page = this.page;
|
|
48360
|
+
if (page && !page.isClosed()) {
|
|
48361
|
+
this.forcedTeardownInProgress = true;
|
|
48362
|
+
try {
|
|
48363
|
+
await this.withOperationTimeout(
|
|
48364
|
+
page.close({ runBeforeUnload: false }),
|
|
48365
|
+
2e3,
|
|
48366
|
+
"forceRecover:pageClose"
|
|
48367
|
+
).catch(() => {
|
|
48368
|
+
});
|
|
48369
|
+
} finally {
|
|
48370
|
+
this.forcedTeardownInProgress = false;
|
|
48371
|
+
}
|
|
48372
|
+
}
|
|
48373
|
+
this._pendingCrashUrl = this.lastKnownUrl;
|
|
48374
|
+
this._pendingCrashType = "page_crash";
|
|
48375
|
+
await this.restoreBrowser(reason);
|
|
48376
|
+
const recovered = !!this.page && !this.page.isClosed();
|
|
48377
|
+
this.logger.info("[DirectPlaywright] forceRecoverPage completed", {
|
|
48378
|
+
reason,
|
|
48379
|
+
recovered,
|
|
48380
|
+
durationMs: Date.now() - startedAt,
|
|
48381
|
+
restoredUrl: this.lastKnownUrl
|
|
48382
|
+
});
|
|
48383
|
+
return recovered;
|
|
48384
|
+
} catch (err) {
|
|
48385
|
+
this.logger.warn("[DirectPlaywright] forceRecoverPage failed", {
|
|
48386
|
+
reason,
|
|
48387
|
+
error: err instanceof Error ? err.message : String(err)
|
|
48388
|
+
});
|
|
48389
|
+
return false;
|
|
48390
|
+
}
|
|
48391
|
+
}
|
|
47668
48392
|
/**
|
|
47669
48393
|
* Set the span factory for sub-operation tracing after construction.
|
|
47670
48394
|
* Called by the API layer to propagate Sentry span context.
|
|
@@ -47710,12 +48434,20 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
47710
48434
|
* Used to guard against hung Playwright operations (launch, connect, newContext).
|
|
47711
48435
|
*/
|
|
47712
48436
|
async withOperationTimeout(operation, timeoutMs, label) {
|
|
47713
|
-
|
|
47714
|
-
|
|
47715
|
-
|
|
47716
|
-
|
|
47717
|
-
|
|
47718
|
-
|
|
48437
|
+
let timer;
|
|
48438
|
+
try {
|
|
48439
|
+
return await Promise.race([
|
|
48440
|
+
operation,
|
|
48441
|
+
new Promise((_, reject) => {
|
|
48442
|
+
timer = setTimeout(
|
|
48443
|
+
() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),
|
|
48444
|
+
timeoutMs
|
|
48445
|
+
);
|
|
48446
|
+
})
|
|
48447
|
+
]);
|
|
48448
|
+
} finally {
|
|
48449
|
+
if (timer) clearTimeout(timer);
|
|
48450
|
+
}
|
|
47719
48451
|
}
|
|
47720
48452
|
isRecoverableClosureError(error) {
|
|
47721
48453
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -47728,6 +48460,10 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
47728
48460
|
contextClosedIntentionally = false;
|
|
47729
48461
|
lastConnectOptions = null;
|
|
47730
48462
|
pageClosedIntentionally = false;
|
|
48463
|
+
// Set while `forceRecoverPage` is deliberately closing the page so the
|
|
48464
|
+
// page-close handler doesn't log it as an unexpected crash / re-stamp crash
|
|
48465
|
+
// state. Distinct from `pageClosedIntentionally` (which makes getPage throw).
|
|
48466
|
+
forcedTeardownInProgress = false;
|
|
47731
48467
|
restoreInProgress = false;
|
|
47732
48468
|
restorePromise = null;
|
|
47733
48469
|
// State tracking (MCP does this internally via Tab class)
|
|
@@ -47748,6 +48484,11 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
47748
48484
|
// Last snapshot for reference (used by some methods)
|
|
47749
48485
|
lastSnapshotYaml = "";
|
|
47750
48486
|
lastSnapshotTimestamp = 0;
|
|
48487
|
+
// Live-snapshot (warm/minted) state. Gated by `liveSnapshotEnabled`; when off,
|
|
48488
|
+
// snapshot() behaves exactly as before. See live-snapshot.ts.
|
|
48489
|
+
liveSnapshotEnabled = false;
|
|
48490
|
+
liveSnapshotRawOnMiss = false;
|
|
48491
|
+
liveSnapshot;
|
|
47751
48492
|
// Last resolved element metadata (for recording layer to avoid snapshot re-parsing)
|
|
47752
48493
|
lastResolvedElementMetadata = /* @__PURE__ */ new Map();
|
|
47753
48494
|
// Tracing state
|
|
@@ -48554,7 +49295,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
48554
49295
|
this.highlightStylesInjected = false;
|
|
48555
49296
|
this.cursorOverlay.resetInjectionState();
|
|
48556
49297
|
const [snapshotYaml, title] = await Promise.all([
|
|
48557
|
-
this.
|
|
49298
|
+
this.captureTreeYaml(opts?.signal, { force: true }),
|
|
48558
49299
|
this.getPage().then((p2) => p2.title())
|
|
48559
49300
|
]);
|
|
48560
49301
|
return this.formatMCPResponse(
|
|
@@ -48576,7 +49317,7 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
48576
49317
|
this.highlightStylesInjected = false;
|
|
48577
49318
|
this.cursorOverlay.resetInjectionState();
|
|
48578
49319
|
const [snapshotYaml, title] = await Promise.all([
|
|
48579
|
-
this.
|
|
49320
|
+
this.captureTreeYaml(opts?.signal, { force: true }),
|
|
48580
49321
|
this.getPage().then((p2) => p2.title())
|
|
48581
49322
|
]);
|
|
48582
49323
|
return this.formatMCPResponse("Navigated back", snapshotYaml, "await page.goBack();", title);
|
|
@@ -48593,14 +49334,21 @@ var PlaywrightClient = class _PlaywrightClient {
|
|
|
48593
49334
|
} else if (opts?.includeScreenshot) {
|
|
48594
49335
|
effectiveMode = "both";
|
|
48595
49336
|
}
|
|
48596
|
-
await this.traced(
|
|
49337
|
+
await this.traced(
|
|
49338
|
+
"stabilize",
|
|
49339
|
+
"DOM stabilize",
|
|
49340
|
+
() => this.stabilize("dom-only", opts?.signal)
|
|
49341
|
+
);
|
|
48597
49342
|
if (effectiveMode === "screenshot") {
|
|
48598
49343
|
try {
|
|
48599
49344
|
const title2 = await page.title();
|
|
48600
49345
|
const buffer = await page.screenshot({
|
|
48601
49346
|
type: "jpeg",
|
|
48602
49347
|
quality: 90,
|
|
48603
|
-
fullPage: false
|
|
49348
|
+
fullPage: false,
|
|
49349
|
+
// Hard cap: a screenshot blocks on a composited frame, which a jammed
|
|
49350
|
+
// renderer can't produce — without a timeout it orphans for minutes.
|
|
49351
|
+
timeout: TIMEOUTS.SCREENSHOT
|
|
48604
49352
|
});
|
|
48605
49353
|
const base64 = buffer.toString("base64");
|
|
48606
49354
|
const downsampledBase642 = await downsampleImage(base64, opts?.maxImageDimension);
|
|
@@ -48622,26 +49370,41 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
|
|
|
48622
49370
|
throw new Error("Failed to capture screenshot in screenshot-only mode");
|
|
48623
49371
|
}
|
|
48624
49372
|
}
|
|
48625
|
-
const captureScreenshot = effectiveMode === "both" ? this.traced(
|
|
48626
|
-
|
|
48627
|
-
|
|
48628
|
-
|
|
48629
|
-
|
|
48630
|
-
|
|
48631
|
-
|
|
48632
|
-
|
|
49373
|
+
const captureScreenshot = effectiveMode === "both" ? this.traced(
|
|
49374
|
+
"screenshot",
|
|
49375
|
+
"capture + downsample screenshot",
|
|
49376
|
+
() => (
|
|
49377
|
+
// Raced against the tool signal AND given a hard Playwright timeout:
|
|
49378
|
+
// a viewport screenshot still waits for a composited frame, which a
|
|
49379
|
+
// jammed renderer can't hand back, so without both bounds it orphans
|
|
49380
|
+
// for minutes behind the wedge.
|
|
49381
|
+
this.raceAgainstSignal("snapshot:screenshot", opts?.signal, async () => {
|
|
49382
|
+
const buffer = await page.screenshot({
|
|
49383
|
+
type: "jpeg",
|
|
49384
|
+
quality: 90,
|
|
49385
|
+
fullPage: false,
|
|
49386
|
+
timeout: TIMEOUTS.SCREENSHOT
|
|
49387
|
+
});
|
|
49388
|
+
return downsampleImage(buffer.toString("base64"), opts?.maxImageDimension);
|
|
49389
|
+
})
|
|
49390
|
+
)
|
|
49391
|
+
).catch((err) => {
|
|
48633
49392
|
this.logger.warn("[DirectPlaywright] Failed to capture screenshot for snapshot", {
|
|
48634
49393
|
error: err instanceof Error ? err.message : String(err)
|
|
48635
49394
|
});
|
|
48636
49395
|
return null;
|
|
48637
49396
|
}) : Promise.resolve(null);
|
|
48638
49397
|
const [rawSnapshotYaml, title, downsampledBase64] = await Promise.all([
|
|
48639
|
-
this.
|
|
48640
|
-
|
|
48641
|
-
"
|
|
48642
|
-
|
|
48643
|
-
|
|
48644
|
-
|
|
49398
|
+
this.captureTreeYaml(opts?.signal),
|
|
49399
|
+
this.raceAgainstSignal(
|
|
49400
|
+
"snapshot:title",
|
|
49401
|
+
opts?.signal,
|
|
49402
|
+
() => this.withOperationTimeout(
|
|
49403
|
+
this.getPage().then((p2) => p2.title()),
|
|
49404
|
+
TIMEOUTS.GET_TITLE,
|
|
49405
|
+
"snapshot:title"
|
|
49406
|
+
)
|
|
49407
|
+
).catch(() => ""),
|
|
48645
49408
|
captureScreenshot
|
|
48646
49409
|
]);
|
|
48647
49410
|
const snapshotYaml = this.filterSnapshot(
|
|
@@ -48874,15 +49637,7 @@ ${warning}` : ""}`,
|
|
|
48874
49637
|
// tracking pixels
|
|
48875
49638
|
];
|
|
48876
49639
|
async waitForNetworkIdle(timeoutMs = TIMEOUTS.STABILIZE_INTERACTION) {
|
|
48877
|
-
|
|
48878
|
-
if (!page || page.isClosed()) return;
|
|
48879
|
-
try {
|
|
48880
|
-
await page.waitForLoadState("networkidle", { timeout: timeoutMs });
|
|
48881
|
-
} catch {
|
|
48882
|
-
this.logger.debug("[DirectPlaywright] waitForNetworkIdle timed out, proceeding", {
|
|
48883
|
-
timeoutMs
|
|
48884
|
-
});
|
|
48885
|
-
}
|
|
49640
|
+
await this.waitForHttpSettle(timeoutMs);
|
|
48886
49641
|
}
|
|
48887
49642
|
getPendingNetworkInfo() {
|
|
48888
49643
|
const pendingRequests = [];
|
|
@@ -48903,6 +49658,40 @@ ${warning}` : ""}`,
|
|
|
48903
49658
|
});
|
|
48904
49659
|
return { pendingCount: pendingRequests.length, sampleUrls };
|
|
48905
49660
|
}
|
|
49661
|
+
/**
|
|
49662
|
+
* Stream-aware HTTP-settle wait (replaces Playwright `networkidle` in
|
|
49663
|
+
* stabilization). Resolves when no non-stream HTTP request is awaiting
|
|
49664
|
+
* response headers, or after `maxWaitMs`. SSE/WebSocket connections never
|
|
49665
|
+
* block it — their response headers land immediately so they're non-pending,
|
|
49666
|
+
* and `eventsource`/`websocket` resource types are excluded outright. A
|
|
49667
|
+
* request stuck pending past `HTTP_SETTLE_MAX_AGE` is aged out. Best-effort:
|
|
49668
|
+
* the cap always wins.
|
|
49669
|
+
*/
|
|
49670
|
+
async waitForHttpSettle(maxWaitMs) {
|
|
49671
|
+
const page = this.page;
|
|
49672
|
+
if (!page || page.isClosed()) return;
|
|
49673
|
+
const deadline = Date.now() + maxWaitMs;
|
|
49674
|
+
const POLL_MS = 50;
|
|
49675
|
+
while (this.countUnsettledRequests() > 0) {
|
|
49676
|
+
if (Date.now() >= deadline) {
|
|
49677
|
+
const pending = this.getPendingNetworkInfo();
|
|
49678
|
+
this.logger.debug("[DirectPlaywright] HTTP-settle deadline reached, proceeding", {
|
|
49679
|
+
maxWaitMs,
|
|
49680
|
+
pendingCount: pending.pendingCount,
|
|
49681
|
+
sampleUrls: pending.sampleUrls
|
|
49682
|
+
});
|
|
49683
|
+
return;
|
|
49684
|
+
}
|
|
49685
|
+
await new Promise((r2) => setTimeout(r2, POLL_MS));
|
|
49686
|
+
}
|
|
49687
|
+
}
|
|
49688
|
+
/** Pending non-stream HTTP requests younger than the age guard. 0 ⇒ settled. */
|
|
49689
|
+
countUnsettledRequests() {
|
|
49690
|
+
return countUnsettledRequests(this._capturedNetworkRequests.values(), Date.now(), {
|
|
49691
|
+
noisePatterns: _PlaywrightClient.NOISE_URL_PATTERNS,
|
|
49692
|
+
maxAgeMs: TIMEOUTS.HTTP_SETTLE_MAX_AGE
|
|
49693
|
+
});
|
|
49694
|
+
}
|
|
48906
49695
|
async networkRequests(_opts) {
|
|
48907
49696
|
if (this._capturedNetworkRequests.size === 0) {
|
|
48908
49697
|
return this.formatMCPResponse("No network requests captured");
|
|
@@ -50127,17 +50916,30 @@ ${warning}` : ""}`,
|
|
|
50127
50916
|
* - Link URLs, heading levels, cursor styles
|
|
50128
50917
|
* - Same role vocabulary (generic, paragraph, etc.)
|
|
50129
50918
|
*/
|
|
50919
|
+
/**
|
|
50920
|
+
* Full capture = raw substrate (cheap) + augmentation (expensive). Legacy
|
|
50921
|
+
* path (flag off) and any internal caller that wants the complete substrate.
|
|
50922
|
+
* Returns the previous YAML on abort (unchanged behavior).
|
|
50923
|
+
*/
|
|
50130
50924
|
async captureSnapshotInternal(signal) {
|
|
50131
50925
|
await this.traceGroup("Canary Internal: Capture Snapshot");
|
|
50132
50926
|
try {
|
|
50133
|
-
|
|
50927
|
+
const previousYaml = this.lastSnapshotYaml;
|
|
50928
|
+
const rawYaml = await this.captureRawSubstrate(signal);
|
|
50929
|
+
if (signal?.aborted || !rawYaml) return previousYaml;
|
|
50930
|
+
return await this.augmentSubstrate(rawYaml, signal);
|
|
50134
50931
|
} finally {
|
|
50135
50932
|
await this.traceGroupEnd();
|
|
50136
50933
|
}
|
|
50137
50934
|
}
|
|
50138
|
-
|
|
50935
|
+
/**
|
|
50936
|
+
* CHEAP tier: read toasts, run the pre-snapshot DOM enrichers, and take the
|
|
50937
|
+
* `ariaSnapshot`. Returns the raw (un-augmented) substrate YAML — the
|
|
50938
|
+
* ground-truth representation used for freshness comparison — or '' if the
|
|
50939
|
+
* capture was aborted mid-way.
|
|
50940
|
+
*/
|
|
50941
|
+
async captureRawSubstrate(signal) {
|
|
50139
50942
|
const page = await this.getPage();
|
|
50140
|
-
const previousYaml = this.lastSnapshotYaml;
|
|
50141
50943
|
let capturedToasts = [];
|
|
50142
50944
|
try {
|
|
50143
50945
|
capturedToasts = await this.raceAgainstSignal(
|
|
@@ -50152,7 +50954,7 @@ ${warning}` : ""}`,
|
|
|
50152
50954
|
);
|
|
50153
50955
|
} catch {
|
|
50154
50956
|
}
|
|
50155
|
-
if (signal?.aborted) return
|
|
50957
|
+
if (signal?.aborted) return "";
|
|
50156
50958
|
if (capturedToasts.length > 0) {
|
|
50157
50959
|
for (const t2 of capturedToasts) {
|
|
50158
50960
|
t2.snapshotsRemaining = TOAST_SNAPSHOT_TTL;
|
|
@@ -50161,14 +50963,14 @@ ${warning}` : ""}`,
|
|
|
50161
50963
|
for (const t2 of capturedToasts) {
|
|
50162
50964
|
existingByText.set(t2.text, t2);
|
|
50163
50965
|
}
|
|
50164
|
-
if (signal?.aborted) return
|
|
50966
|
+
if (signal?.aborted) return "";
|
|
50165
50967
|
this._lastCapturedToasts = Array.from(existingByText.values());
|
|
50166
50968
|
this.logger.debug("[DirectPlaywright] Captured ephemeral toasts", {
|
|
50167
50969
|
count: capturedToasts.length,
|
|
50168
50970
|
texts: capturedToasts.map((t2) => t2.text)
|
|
50169
50971
|
});
|
|
50170
50972
|
}
|
|
50171
|
-
if (signal?.aborted) return
|
|
50973
|
+
if (signal?.aborted) return "";
|
|
50172
50974
|
await this.traced(
|
|
50173
50975
|
"enrichment",
|
|
50174
50976
|
"DOM enrichment (SVG, hidden, modals)",
|
|
@@ -50182,43 +50984,56 @@ ${warning}` : ""}`,
|
|
|
50182
50984
|
]).then(() => void 0)
|
|
50183
50985
|
)
|
|
50184
50986
|
);
|
|
50185
|
-
if (signal?.aborted) return
|
|
50186
|
-
|
|
50187
|
-
|
|
50188
|
-
|
|
50189
|
-
|
|
50190
|
-
|
|
50191
|
-
|
|
50192
|
-
|
|
50193
|
-
|
|
50194
|
-
() => page.ariaSnapshot({ mode: "ai" })
|
|
50195
|
-
);
|
|
50196
|
-
break;
|
|
50197
|
-
} catch (err) {
|
|
50198
|
-
if (signal?.aborted) return previousYaml;
|
|
50199
|
-
if (attempt < maxRetries) {
|
|
50200
|
-
this.logger.debug("[DirectPlaywright] Snapshot attempt failed, retrying", {
|
|
50201
|
-
attempt,
|
|
50202
|
-
maxRetries,
|
|
50203
|
-
error: err instanceof Error ? err.message : String(err)
|
|
50204
|
-
});
|
|
50205
|
-
await this.raceAgainstSignal(
|
|
50206
|
-
"snapshot:retry-backoff",
|
|
50987
|
+
if (signal?.aborted) return "";
|
|
50988
|
+
const snapshot = await this.traced("snapshot_for_ai", "capture accessibility tree", async () => {
|
|
50989
|
+
let result;
|
|
50990
|
+
const maxRetries = 3;
|
|
50991
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
50992
|
+
if (signal?.aborted) return void 0;
|
|
50993
|
+
try {
|
|
50994
|
+
result = await this.raceAgainstSignal(
|
|
50995
|
+
"snapshot:aria",
|
|
50207
50996
|
signal,
|
|
50208
|
-
() =>
|
|
50997
|
+
() => page.ariaSnapshot({ mode: "ai" })
|
|
50209
50998
|
);
|
|
50210
|
-
|
|
50211
|
-
|
|
50212
|
-
|
|
50213
|
-
|
|
50214
|
-
|
|
50999
|
+
break;
|
|
51000
|
+
} catch (err) {
|
|
51001
|
+
if (signal?.aborted) return void 0;
|
|
51002
|
+
if (attempt < maxRetries) {
|
|
51003
|
+
this.logger.debug("[DirectPlaywright] Snapshot attempt failed, retrying", {
|
|
51004
|
+
attempt,
|
|
51005
|
+
maxRetries,
|
|
50215
51006
|
error: err instanceof Error ? err.message : String(err)
|
|
50216
|
-
}
|
|
50217
|
-
|
|
51007
|
+
});
|
|
51008
|
+
await this.raceAgainstSignal(
|
|
51009
|
+
"snapshot:retry-backoff",
|
|
51010
|
+
signal,
|
|
51011
|
+
() => new Promise((resolve) => setTimeout(resolve, 200 * attempt))
|
|
51012
|
+
);
|
|
51013
|
+
} else {
|
|
51014
|
+
this.logger.warn(
|
|
51015
|
+
"[DirectPlaywright] All snapshot attempts failed, using empty fallback",
|
|
51016
|
+
{
|
|
51017
|
+
attempts: maxRetries,
|
|
51018
|
+
error: err instanceof Error ? err.message : String(err)
|
|
51019
|
+
}
|
|
51020
|
+
);
|
|
51021
|
+
}
|
|
50218
51022
|
}
|
|
50219
51023
|
}
|
|
50220
|
-
|
|
50221
|
-
|
|
51024
|
+
return result;
|
|
51025
|
+
});
|
|
51026
|
+
if (signal?.aborted) return "";
|
|
51027
|
+
return snapshot || "- page: (empty)";
|
|
51028
|
+
}
|
|
51029
|
+
/**
|
|
51030
|
+
* EXPENSIVE tier: augment a raw substrate into the full substrate (per-ref
|
|
51031
|
+
* DOM-derived hints, v2 attrs, frame selectors). Publishes `lastSnapshotYaml`.
|
|
51032
|
+
* Returns the previous YAML on abort.
|
|
51033
|
+
*/
|
|
51034
|
+
async augmentSubstrate(rawYaml, signal) {
|
|
51035
|
+
const page = await this.getPage();
|
|
51036
|
+
const previousYaml = this.lastSnapshotYaml;
|
|
50222
51037
|
if (signal?.aborted) return previousYaml;
|
|
50223
51038
|
const augmentedYaml = await this.traced("augmentation", "YAML augmentation (labels, grids, forms, selects)", () => this.raceAgainstSignal("snapshot:augmentation", signal, async () => {
|
|
50224
51039
|
const elements = parseSnapshot(rawYaml);
|
|
@@ -50384,6 +51199,27 @@ ${warning}` : ""}`,
|
|
|
50384
51199
|
if (Date.now() - this.lastSnapshotTimestamp > maxAgeMs) return null;
|
|
50385
51200
|
return this.lastSnapshotYaml;
|
|
50386
51201
|
}
|
|
51202
|
+
/**
|
|
51203
|
+
* Return the warm (augmented) snapshot YAML if its raw substrate still matches
|
|
51204
|
+
* the live page, else null — verified with a cheap raw capture, never
|
|
51205
|
+
* augmenting. Used by the executor to reuse the warm snapshot as a diff
|
|
51206
|
+
* baseline. Returns null when live-snapshot is disabled (caller falls back to
|
|
51207
|
+
* a fresh capture).
|
|
51208
|
+
*/
|
|
51209
|
+
async getReusableSnapshotYaml() {
|
|
51210
|
+
if (!this.liveSnapshotEnabled) return null;
|
|
51211
|
+
const reusable = await this.liveSnapshot.getReusable();
|
|
51212
|
+
return reusable ? mintedYaml(reusable) : null;
|
|
51213
|
+
}
|
|
51214
|
+
/**
|
|
51215
|
+
* Fire-and-forget background *preparation* of the next snapshot. Waits for the
|
|
51216
|
+
* DOM to settle, then re-captures and re-augments on change so the next
|
|
51217
|
+
* request hits the reuse path. No-op when live-snapshot is disabled.
|
|
51218
|
+
*/
|
|
51219
|
+
warmSnapshot(opts) {
|
|
51220
|
+
if (!this.liveSnapshotEnabled) return;
|
|
51221
|
+
this.liveSnapshot.warm({ ...opts, rawOnMiss: this.liveSnapshotRawOnMiss });
|
|
51222
|
+
}
|
|
50387
51223
|
// ================ CDP SCREENCAST ================
|
|
50388
51224
|
async startScreencast(onFrame, options) {
|
|
50389
51225
|
if (!this.page) {
|
|
@@ -50499,22 +51335,51 @@ ${warning}` : ""}`,
|
|
|
50499
51335
|
* - 'interaction': Brief DOM delay + network idle (after click/submit)
|
|
50500
51336
|
* - 'dom-only': Just DOM/animation settling (before snapshot, after hover)
|
|
50501
51337
|
*/
|
|
50502
|
-
async stabilize(type) {
|
|
51338
|
+
async stabilize(type, signal) {
|
|
50503
51339
|
const page = this.page;
|
|
50504
51340
|
if (!page || page.isClosed()) return;
|
|
50505
51341
|
try {
|
|
50506
51342
|
switch (type) {
|
|
50507
51343
|
case "navigation":
|
|
50508
|
-
await
|
|
51344
|
+
await this.raceAgainstSignal(
|
|
51345
|
+
"stabilize:dom",
|
|
51346
|
+
signal,
|
|
51347
|
+
() => this.waitDomQuiet(this.stabilizeDomMs, TIMEOUTS.STABILIZE_NAV)
|
|
51348
|
+
).catch(() => {
|
|
51349
|
+
});
|
|
51350
|
+
await this.raceAgainstSignal(
|
|
51351
|
+
"stabilize:http",
|
|
51352
|
+
signal,
|
|
51353
|
+
() => this.waitForHttpSettle(TIMEOUTS.STABILIZE_NAV)
|
|
51354
|
+
).catch(() => {
|
|
50509
51355
|
});
|
|
50510
51356
|
break;
|
|
50511
51357
|
case "interaction":
|
|
50512
|
-
await
|
|
50513
|
-
|
|
51358
|
+
await this.raceAgainstSignal(
|
|
51359
|
+
"stabilize:dom-delay",
|
|
51360
|
+
signal,
|
|
51361
|
+
() => page.waitForTimeout(this.stabilizeDomMs)
|
|
51362
|
+
).catch(() => {
|
|
51363
|
+
});
|
|
51364
|
+
await this.raceAgainstSignal(
|
|
51365
|
+
"stabilize:http",
|
|
51366
|
+
signal,
|
|
51367
|
+
() => this.waitForHttpSettle(TIMEOUTS.STABILIZE_INTERACTION)
|
|
51368
|
+
).catch(() => {
|
|
50514
51369
|
});
|
|
50515
51370
|
break;
|
|
50516
51371
|
case "dom-only":
|
|
50517
|
-
|
|
51372
|
+
if (this.liveSnapshotEnabled) {
|
|
51373
|
+
await this.raceAgainstSignal(
|
|
51374
|
+
"stabilize:dom",
|
|
51375
|
+
signal,
|
|
51376
|
+
() => this.waitDomQuiet(this.stabilizeDomMs, TIMEOUTS.STABILIZE_INTERACTION)
|
|
51377
|
+
).catch(() => {
|
|
51378
|
+
});
|
|
51379
|
+
} else {
|
|
51380
|
+
await page.waitForTimeout(this.stabilizeDomMs).catch(() => {
|
|
51381
|
+
});
|
|
51382
|
+
}
|
|
50518
51383
|
break;
|
|
50519
51384
|
}
|
|
50520
51385
|
} catch (err) {
|
|
@@ -50740,7 +51605,7 @@ Legacy two-step (only if you need to change the action between calls): browser_h
|
|
|
50740
51605
|
});
|
|
50741
51606
|
});
|
|
50742
51607
|
page.on("close", () => {
|
|
50743
|
-
if (this.pageClosedIntentionally || this.contextClosedIntentionally || this.disconnectInProgress) {
|
|
51608
|
+
if (this.pageClosedIntentionally || this.contextClosedIntentionally || this.disconnectInProgress || this.forcedTeardownInProgress) {
|
|
50744
51609
|
this.logger.debug("[DirectPlaywright] Page closed intentionally");
|
|
50745
51610
|
return;
|
|
50746
51611
|
}
|
|
@@ -50783,7 +51648,9 @@ Legacy two-step (only if you need to change the action between calls): browser_h
|
|
|
50783
51648
|
method: request2.method(),
|
|
50784
51649
|
url: request2.url(),
|
|
50785
51650
|
status: "pending",
|
|
50786
|
-
statusText: ""
|
|
51651
|
+
statusText: "",
|
|
51652
|
+
resourceType: request2.resourceType(),
|
|
51653
|
+
startedAt: Date.now()
|
|
50787
51654
|
});
|
|
50788
51655
|
});
|
|
50789
51656
|
page.on("response", (response) => {
|
|
@@ -50929,6 +51796,7 @@ Legacy two-step (only if you need to change the action between calls): browser_h
|
|
|
50929
51796
|
this._pendingDialogs = [];
|
|
50930
51797
|
this.setPendingArm(null, "createPageWithListeners");
|
|
50931
51798
|
this.lastSnapshotYaml = "";
|
|
51799
|
+
this.liveSnapshot.reset();
|
|
50932
51800
|
this.highlightStylesInjected = false;
|
|
50933
51801
|
this.cursorOverlay.resetInjectionState();
|
|
50934
51802
|
this.setupPageEventListeners(page);
|
|
@@ -50949,6 +51817,11 @@ Legacy two-step (only if you need to change the action between calls): browser_h
|
|
|
50949
51817
|
error: err instanceof Error ? err.message : String(err)
|
|
50950
51818
|
});
|
|
50951
51819
|
});
|
|
51820
|
+
void context.addInitScript(DOM_REVISION_SCRIPT).catch((err) => {
|
|
51821
|
+
this.logger.warn("[DirectPlaywright] Failed to install dom-revision init script", {
|
|
51822
|
+
error: err instanceof Error ? err.message : String(err)
|
|
51823
|
+
});
|
|
51824
|
+
});
|
|
50952
51825
|
void context.grantPermissions(["microphone", "camera"]).catch((err) => {
|
|
50953
51826
|
this.logger.warn("[DirectPlaywright] Failed to grant media permissions", {
|
|
50954
51827
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -51977,4 +52850,4 @@ playwright-extra/dist/index.esm.js:
|
|
|
51977
52850
|
* @license MIT
|
|
51978
52851
|
*)
|
|
51979
52852
|
*/
|
|
51980
|
-
//# sourceMappingURL=chunk-
|
|
52853
|
+
//# sourceMappingURL=chunk-QQVWGWPI.js.map
|