@opensteer/engine-playwright 0.7.0 → 0.8.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/index.d.cts CHANGED
@@ -831,6 +831,40 @@ interface ComputerUseBridge {
831
831
  execute(input: ComputerUseBridgeInput): Promise<ComputerUseBridgeOutput>;
832
832
  }
833
833
 
834
+ type MatchOperator = "exact" | "startsWith" | "contains";
835
+ interface AttributeMatchClause {
836
+ readonly kind: "attr";
837
+ readonly key: string;
838
+ readonly op?: MatchOperator;
839
+ readonly value?: string;
840
+ }
841
+ interface PositionMatchClause {
842
+ readonly kind: "position";
843
+ readonly axis: "nthOfType" | "nthChild";
844
+ }
845
+ type MatchClause = AttributeMatchClause | PositionMatchClause;
846
+ interface PathNodePosition {
847
+ readonly nthChild: number;
848
+ readonly nthOfType: number;
849
+ }
850
+ interface PathNode {
851
+ readonly tag: string;
852
+ readonly attrs: Readonly<Record<string, string>>;
853
+ readonly position: PathNodePosition;
854
+ readonly match: readonly MatchClause[];
855
+ }
856
+ interface ContextHop {
857
+ readonly kind: "iframe" | "shadow";
858
+ readonly host: readonly PathNode[];
859
+ }
860
+ interface ElementRouteBase {
861
+ readonly context: readonly ContextHop[];
862
+ readonly nodes: readonly PathNode[];
863
+ }
864
+ interface ReplayElementPath extends ElementRouteBase {
865
+ readonly resolution: "deterministic";
866
+ }
867
+
834
868
  declare const OPENSTEER_DOM_ACTION_BRIDGE_SYMBOL: unique symbol;
835
869
  type DomActionScrollAlignment = "start" | "center" | "end" | "nearest";
836
870
  interface DomActionTargetInspection {
@@ -865,6 +899,7 @@ interface DomPointerHitAssessment {
865
899
  readonly hitOwner?: NodeLocator;
866
900
  }
867
901
  interface DomActionBridge {
902
+ buildReplayPath(locator: NodeLocator): Promise<ReplayElementPath>;
868
903
  inspectActionTarget(locator: NodeLocator): Promise<DomActionTargetInspection>;
869
904
  canonicalizePointerTarget(locator: NodeLocator): Promise<NodeLocator>;
870
905
  classifyPointerHit(input: {
@@ -1158,6 +1193,7 @@ declare class PlaywrightBrowserCoreEngine implements BrowserCoreEngine {
1158
1193
  private requireSession;
1159
1194
  private requirePage;
1160
1195
  private requireFrame;
1196
+ private requireLiveFrame;
1161
1197
  private requireDocument;
1162
1198
  private cleanupPageController;
1163
1199
  private isControllerAcceptingEvents;
@@ -1183,7 +1219,8 @@ declare function connectPlaywrightChromiumBrowser(input: {
1183
1219
  readonly url: string;
1184
1220
  readonly headers?: Readonly<Record<string, string>>;
1185
1221
  }): Promise<Browser>;
1222
+ declare function disconnectPlaywrightChromiumBrowser(browser: Browser): Promise<void>;
1186
1223
 
1187
1224
  declare function capturePlaywrightStorageOrigins(context: BrowserContext, origins: readonly string[]): Promise<StorageOriginSnapshot[]>;
1188
1225
 
1189
- export { type AdoptedChromiumBrowser, type PlaywrightBrowserContextOptions, PlaywrightBrowserCoreEngine, type PlaywrightBrowserCoreEngineOptions, type PlaywrightChromiumLaunchOptions, capturePlaywrightStorageOrigins, connectPlaywrightChromiumBrowser, createPlaywrightBrowserCoreEngine };
1226
+ export { type AdoptedChromiumBrowser, type PlaywrightBrowserContextOptions, PlaywrightBrowserCoreEngine, type PlaywrightBrowserCoreEngineOptions, type PlaywrightChromiumLaunchOptions, capturePlaywrightStorageOrigins, connectPlaywrightChromiumBrowser, createPlaywrightBrowserCoreEngine, disconnectPlaywrightChromiumBrowser };
package/dist/index.d.ts CHANGED
@@ -831,6 +831,40 @@ interface ComputerUseBridge {
831
831
  execute(input: ComputerUseBridgeInput): Promise<ComputerUseBridgeOutput>;
832
832
  }
833
833
 
834
+ type MatchOperator = "exact" | "startsWith" | "contains";
835
+ interface AttributeMatchClause {
836
+ readonly kind: "attr";
837
+ readonly key: string;
838
+ readonly op?: MatchOperator;
839
+ readonly value?: string;
840
+ }
841
+ interface PositionMatchClause {
842
+ readonly kind: "position";
843
+ readonly axis: "nthOfType" | "nthChild";
844
+ }
845
+ type MatchClause = AttributeMatchClause | PositionMatchClause;
846
+ interface PathNodePosition {
847
+ readonly nthChild: number;
848
+ readonly nthOfType: number;
849
+ }
850
+ interface PathNode {
851
+ readonly tag: string;
852
+ readonly attrs: Readonly<Record<string, string>>;
853
+ readonly position: PathNodePosition;
854
+ readonly match: readonly MatchClause[];
855
+ }
856
+ interface ContextHop {
857
+ readonly kind: "iframe" | "shadow";
858
+ readonly host: readonly PathNode[];
859
+ }
860
+ interface ElementRouteBase {
861
+ readonly context: readonly ContextHop[];
862
+ readonly nodes: readonly PathNode[];
863
+ }
864
+ interface ReplayElementPath extends ElementRouteBase {
865
+ readonly resolution: "deterministic";
866
+ }
867
+
834
868
  declare const OPENSTEER_DOM_ACTION_BRIDGE_SYMBOL: unique symbol;
835
869
  type DomActionScrollAlignment = "start" | "center" | "end" | "nearest";
836
870
  interface DomActionTargetInspection {
@@ -865,6 +899,7 @@ interface DomPointerHitAssessment {
865
899
  readonly hitOwner?: NodeLocator;
866
900
  }
867
901
  interface DomActionBridge {
902
+ buildReplayPath(locator: NodeLocator): Promise<ReplayElementPath>;
868
903
  inspectActionTarget(locator: NodeLocator): Promise<DomActionTargetInspection>;
869
904
  canonicalizePointerTarget(locator: NodeLocator): Promise<NodeLocator>;
870
905
  classifyPointerHit(input: {
@@ -1158,6 +1193,7 @@ declare class PlaywrightBrowserCoreEngine implements BrowserCoreEngine {
1158
1193
  private requireSession;
1159
1194
  private requirePage;
1160
1195
  private requireFrame;
1196
+ private requireLiveFrame;
1161
1197
  private requireDocument;
1162
1198
  private cleanupPageController;
1163
1199
  private isControllerAcceptingEvents;
@@ -1183,7 +1219,8 @@ declare function connectPlaywrightChromiumBrowser(input: {
1183
1219
  readonly url: string;
1184
1220
  readonly headers?: Readonly<Record<string, string>>;
1185
1221
  }): Promise<Browser>;
1222
+ declare function disconnectPlaywrightChromiumBrowser(browser: Browser): Promise<void>;
1186
1223
 
1187
1224
  declare function capturePlaywrightStorageOrigins(context: BrowserContext, origins: readonly string[]): Promise<StorageOriginSnapshot[]>;
1188
1225
 
1189
- export { type AdoptedChromiumBrowser, type PlaywrightBrowserContextOptions, PlaywrightBrowserCoreEngine, type PlaywrightBrowserCoreEngineOptions, type PlaywrightChromiumLaunchOptions, capturePlaywrightStorageOrigins, connectPlaywrightChromiumBrowser, createPlaywrightBrowserCoreEngine };
1226
+ export { type AdoptedChromiumBrowser, type PlaywrightBrowserContextOptions, PlaywrightBrowserCoreEngine, type PlaywrightBrowserCoreEngineOptions, type PlaywrightChromiumLaunchOptions, capturePlaywrightStorageOrigins, connectPlaywrightChromiumBrowser, createPlaywrightBrowserCoreEngine, disconnectPlaywrightChromiumBrowser };
package/dist/index.js CHANGED
@@ -2371,8 +2371,421 @@ var CLASSIFY_POINTER_HIT_DECLARATION = String.raw`function(hitNode, point) {
2371
2371
  ambiguous,
2372
2372
  };
2373
2373
  }`;
2374
+ var LIVE_REPLAY_PATH_MATCH_ATTRIBUTE_PRIORITY = [
2375
+ "class",
2376
+ "data-testid",
2377
+ "data-test",
2378
+ "data-qa",
2379
+ "data-cy",
2380
+ "name",
2381
+ "role",
2382
+ "type",
2383
+ "aria-label",
2384
+ "title",
2385
+ "placeholder",
2386
+ "for",
2387
+ "aria-controls",
2388
+ "aria-labelledby",
2389
+ "aria-describedby",
2390
+ "id",
2391
+ "href",
2392
+ "value",
2393
+ "src",
2394
+ "srcset",
2395
+ "imagesrcset",
2396
+ "ping",
2397
+ "alt"
2398
+ ];
2399
+ var LIVE_REPLAY_PATH_STABLE_PRIMARY_ATTR_KEYS = [
2400
+ "data-testid",
2401
+ "data-test",
2402
+ "data-qa",
2403
+ "data-cy",
2404
+ "name",
2405
+ "role",
2406
+ "type",
2407
+ "aria-label",
2408
+ "title",
2409
+ "placeholder"
2410
+ ];
2411
+ var LIVE_REPLAY_PATH_DEFERRED_MATCH_ATTR_KEYS = [
2412
+ "href",
2413
+ "src",
2414
+ "srcset",
2415
+ "imagesrcset",
2416
+ "ping",
2417
+ "value",
2418
+ "for",
2419
+ "aria-controls",
2420
+ "aria-labelledby",
2421
+ "aria-describedby"
2422
+ ];
2423
+ var LIVE_REPLAY_PATH_POLICY = {
2424
+ matchAttributePriority: LIVE_REPLAY_PATH_MATCH_ATTRIBUTE_PRIORITY,
2425
+ stablePrimaryAttrKeys: LIVE_REPLAY_PATH_STABLE_PRIMARY_ATTR_KEYS,
2426
+ deferredMatchAttrKeys: LIVE_REPLAY_PATH_DEFERRED_MATCH_ATTR_KEYS
2427
+ };
2428
+ var BUILD_LIVE_REPLAY_PATH_DECLARATION = String.raw`function(policy, source) {
2429
+ const buildReplayPath = (0, eval)(source);
2430
+ return buildReplayPath(this, policy);
2431
+ }`;
2432
+ var BUILD_LIVE_REPLAY_PATH_SOURCE = String.raw`(target, policy) => {
2433
+ const MAX_ATTRIBUTE_VALUE_LENGTH = 300;
2434
+
2435
+ function isValidAttrKey(key) {
2436
+ const trimmed = String(key || "").trim();
2437
+ if (!trimmed) return false;
2438
+ if (/[\s"'<>/]/.test(trimmed)) return false;
2439
+ return /^[A-Za-z_][A-Za-z0-9_:\-.]*$/.test(trimmed);
2440
+ }
2441
+
2442
+ function isMediaTag(tag) {
2443
+ return new Set(["img", "video", "source", "iframe"]).has(String(tag || "").toLowerCase());
2444
+ }
2445
+
2446
+ function shouldKeepAttr(tag, key, value) {
2447
+ const normalized = String(key || "").trim().toLowerCase();
2448
+ if (!normalized || !String(value || "").trim()) return false;
2449
+ if (!isValidAttrKey(key)) return false;
2450
+ if (normalized === "c") return false;
2451
+ if (/^on[a-z]/i.test(normalized)) return false;
2452
+ if (new Set(["style", "nonce", "integrity", "crossorigin", "referrerpolicy", "autocomplete"]).has(normalized)) {
2453
+ return false;
2454
+ }
2455
+ if (normalized.startsWith("data-os-") || normalized.startsWith("data-opensteer-")) {
2456
+ return false;
2457
+ }
2458
+ if (
2459
+ isMediaTag(tag) &&
2460
+ new Set([
2461
+ "data-src",
2462
+ "data-lazy-src",
2463
+ "data-original",
2464
+ "data-lazy",
2465
+ "data-image",
2466
+ "data-url",
2467
+ "data-srcset",
2468
+ "data-lazy-srcset",
2469
+ "data-was-processed",
2470
+ ]).has(normalized)
2471
+ ) {
2472
+ return false;
2473
+ }
2474
+ return true;
2475
+ }
2476
+
2477
+ function collectAttrs(node) {
2478
+ const tag = node.tagName.toLowerCase();
2479
+ const attrs = {};
2480
+ for (const attr of Array.from(node.attributes)) {
2481
+ if (!shouldKeepAttr(tag, attr.name, attr.value)) {
2482
+ continue;
2483
+ }
2484
+ const value = String(attr.value || "");
2485
+ if (!value.trim()) continue;
2486
+ if (value.length > MAX_ATTRIBUTE_VALUE_LENGTH) continue;
2487
+ attrs[attr.name] = value;
2488
+ }
2489
+ return attrs;
2490
+ }
2491
+
2492
+ function getSiblings(node, root) {
2493
+ if (node.parentElement) return Array.from(node.parentElement.children);
2494
+ return Array.from(root.children || []);
2495
+ }
2496
+
2497
+ function toPosition(node, root) {
2498
+ const siblings = getSiblings(node, root);
2499
+ const tag = node.tagName.toLowerCase();
2500
+ const sameTag = siblings.filter((candidate) => candidate.tagName.toLowerCase() === tag);
2501
+ return {
2502
+ nthChild: siblings.indexOf(node) + 1,
2503
+ nthOfType: sameTag.indexOf(node) + 1,
2504
+ };
2505
+ }
2506
+
2507
+ function buildChain(node) {
2508
+ const chain = [];
2509
+ let current = node;
2510
+ while (current) {
2511
+ chain.push(current);
2512
+ if (current.parentElement) {
2513
+ current = current.parentElement;
2514
+ continue;
2515
+ }
2516
+ break;
2517
+ }
2518
+ chain.reverse();
2519
+ return chain;
2520
+ }
2521
+
2522
+ function sortAttributeKeys(keys) {
2523
+ const priority = Array.isArray(policy?.matchAttributePriority)
2524
+ ? policy.matchAttributePriority.map((value) => String(value))
2525
+ : [];
2526
+ return [...keys].sort((left, right) => {
2527
+ const leftIndex = priority.indexOf(left);
2528
+ const rightIndex = priority.indexOf(right);
2529
+ const leftRank = leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;
2530
+ const rightRank = rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;
2531
+ if (leftRank !== rightRank) {
2532
+ return leftRank - rightRank;
2533
+ }
2534
+ return left.localeCompare(right);
2535
+ });
2536
+ }
2537
+
2538
+ function tokenizeClassValue(value) {
2539
+ const seen = new Set();
2540
+ const out = [];
2541
+ for (const token of String(value || "").split(/\s+/)) {
2542
+ const normalized = token.trim();
2543
+ if (!normalized || seen.has(normalized)) continue;
2544
+ seen.add(normalized);
2545
+ out.push(normalized);
2546
+ }
2547
+ return out;
2548
+ }
2549
+
2550
+ function clauseKey(clause) {
2551
+ return JSON.stringify(clause);
2552
+ }
2553
+
2554
+ function shouldDeferMatchAttribute(rawKey) {
2555
+ const key = String(rawKey || "").trim().toLowerCase();
2556
+ if (!key || key === "class") return false;
2557
+ if (key === "id" || /(?:^|[-_:])id$/.test(key)) return true;
2558
+ const deferred = new Set(
2559
+ Array.isArray(policy?.deferredMatchAttrKeys)
2560
+ ? policy.deferredMatchAttrKeys.map((value) => String(value))
2561
+ : [],
2562
+ );
2563
+ if (deferred.has(key)) return true;
2564
+ const stablePrimary = new Set(
2565
+ Array.isArray(policy?.stablePrimaryAttrKeys)
2566
+ ? policy.stablePrimaryAttrKeys.map((value) => String(value))
2567
+ : [],
2568
+ );
2569
+ if (key.startsWith("data-") && !stablePrimary.has(key)) return true;
2570
+ return !stablePrimary.has(key);
2571
+ }
2572
+
2573
+ function buildSegmentSelector(data) {
2574
+ let selector = String(data.tag || "*").toLowerCase();
2575
+ for (const clause of data.match || []) {
2576
+ if (clause.kind === "position") {
2577
+ if (clause.axis === "nthOfType") {
2578
+ selector += ":nth-of-type(" + Math.max(1, Number(data.position?.nthOfType || 1)) + ")";
2579
+ } else {
2580
+ selector += ":nth-child(" + Math.max(1, Number(data.position?.nthChild || 1)) + ")";
2581
+ }
2582
+ continue;
2583
+ }
2584
+
2585
+ const key = String(clause.key || "");
2586
+ const value = typeof clause.value === "string" ? clause.value : data.attrs?.[key];
2587
+ if (!key || !value) continue;
2588
+ if (key === "class" && (clause.op || "exact") === "exact") {
2589
+ for (const token of tokenizeClassValue(value)) {
2590
+ const escapedToken = String(token).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2591
+ selector += '[class~="' + escapedToken + '"]';
2592
+ }
2593
+ continue;
2594
+ }
2595
+ const escaped = String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2596
+ const op = clause.op || "exact";
2597
+ if (op === "startsWith") selector += "[" + key + '^="' + escaped + '"]';
2598
+ else if (op === "contains") selector += "[" + key + '*="' + escaped + '"]';
2599
+ else selector += "[" + key + '="' + escaped + '"]';
2600
+ }
2601
+ return selector;
2602
+ }
2603
+
2604
+ function buildCandidates(nodes) {
2605
+ const parts = nodes.map((node) => buildSegmentSelector(node));
2606
+ const out = [];
2607
+ const seen = new Set();
2608
+ for (let start = 0; start < parts.length; start += 1) {
2609
+ const selector = parts.slice(start).join(" ");
2610
+ if (!selector || seen.has(selector)) continue;
2611
+ seen.add(selector);
2612
+ out.push(selector);
2613
+ }
2614
+ return out;
2615
+ }
2616
+
2617
+ function selectReplayCandidate(nodes, root) {
2618
+ const selectors = buildCandidates(nodes);
2619
+ let fallback = null;
2620
+ let fallbackSelector = null;
2621
+ let fallbackCount = 0;
2622
+ for (const selector of selectors) {
2623
+ let matches = [];
2624
+ try {
2625
+ matches = Array.from(root.querySelectorAll(selector));
2626
+ } catch {
2627
+ matches = [];
2628
+ }
2629
+ if (!matches.length) continue;
2630
+ if (matches.length === 1) {
2631
+ return {
2632
+ element: matches[0],
2633
+ selector,
2634
+ count: 1,
2635
+ mode: "unique",
2636
+ };
2637
+ }
2638
+ if (!fallback) {
2639
+ fallback = matches[0];
2640
+ fallbackSelector = selector;
2641
+ fallbackCount = matches.length;
2642
+ }
2643
+ }
2644
+ if (fallback && fallbackSelector) {
2645
+ return {
2646
+ element: fallback,
2647
+ selector: fallbackSelector,
2648
+ count: fallbackCount,
2649
+ mode: "fallback",
2650
+ };
2651
+ }
2652
+ return null;
2653
+ }
2654
+
2655
+ function buildClausePool(data) {
2656
+ const attrs = data.attrs || {};
2657
+ const pool = [];
2658
+ const deferred = [];
2659
+ const used = new Set();
2660
+
2661
+ const classValue = String(attrs.class || "").trim();
2662
+ if (classValue) {
2663
+ const clause = { kind: "attr", key: "class", op: "exact", value: classValue };
2664
+ used.add(clauseKey(clause));
2665
+ pool.push(clause);
2666
+ }
2667
+
2668
+ for (const key of sortAttributeKeys(Object.keys(attrs))) {
2669
+ if (key === "class") continue;
2670
+ const value = attrs[key];
2671
+ if (!value || !String(value).trim()) continue;
2672
+ const clause = { kind: "attr", key, op: "exact" };
2673
+ const keyId = clauseKey(clause);
2674
+ if (used.has(keyId)) continue;
2675
+ used.add(keyId);
2676
+ if (shouldDeferMatchAttribute(key)) deferred.push(clause);
2677
+ else pool.push(clause);
2678
+ }
2679
+
2680
+ for (const clause of [
2681
+ { kind: "position", axis: "nthOfType" },
2682
+ { kind: "position", axis: "nthChild" },
2683
+ ]) {
2684
+ const keyId = clauseKey(clause);
2685
+ if (used.has(keyId)) continue;
2686
+ used.add(keyId);
2687
+ pool.push(clause);
2688
+ }
2689
+
2690
+ if (!pool.some((clause) => clause.kind === "attr")) {
2691
+ pool.push(...deferred);
2692
+ }
2693
+
2694
+ return pool;
2695
+ }
2696
+
2697
+ function finalizePath(elements, root) {
2698
+ if (!elements.length) return null;
2699
+ const nodes = elements.map((element) => ({
2700
+ tag: element.tagName.toLowerCase(),
2701
+ attrs: collectAttrs(element),
2702
+ position: toPosition(element, root),
2703
+ match: [],
2704
+ }));
2705
+
2706
+ const pools = nodes.map((node) => {
2707
+ node.match = [];
2708
+ return [...buildClausePool(node)];
2709
+ });
2710
+
2711
+ for (let index = 0; index < pools.length; index += 1) {
2712
+ const classIndex = pools[index].findIndex(
2713
+ (clause) => clause.kind === "attr" && clause.key === "class",
2714
+ );
2715
+ if (classIndex < 0) continue;
2716
+ const classClause = pools[index][classIndex];
2717
+ if (!classClause) continue;
2718
+ nodes[index].match.push(classClause);
2719
+ pools[index].splice(classIndex, 1);
2720
+ }
2721
+
2722
+ const expected = elements[elements.length - 1];
2723
+ const totalRemaining = pools.reduce((count, pool) => count + pool.length, 0);
2724
+ for (let iteration = 0; iteration <= totalRemaining; iteration += 1) {
2725
+ const chosen = selectReplayCandidate(nodes, root);
2726
+ if (chosen && chosen.mode === "unique" && chosen.element === expected) {
2727
+ return {
2728
+ nodes,
2729
+ selector: chosen.selector,
2730
+ };
2731
+ }
2732
+
2733
+ let added = false;
2734
+ for (let index = pools.length - 1; index >= 0; index -= 1) {
2735
+ const next = pools[index][0];
2736
+ if (!next) continue;
2737
+ nodes[index].match.push(next);
2738
+ pools[index].shift();
2739
+ added = true;
2740
+ break;
2741
+ }
2742
+ if (!added) break;
2743
+ }
2744
+
2745
+ return null;
2746
+ }
2747
+
2748
+ if (!(target instanceof Element)) return null;
2749
+
2750
+ const context = [];
2751
+ let currentRoot = target.getRootNode() instanceof ShadowRoot ? target.getRootNode() : document;
2752
+ const targetChain = buildChain(target);
2753
+ const finalizedTarget = finalizePath(targetChain, currentRoot);
2754
+ if (!finalizedTarget) return null;
2755
+
2756
+ while (currentRoot instanceof ShadowRoot) {
2757
+ const host = currentRoot.host;
2758
+ const hostRoot =
2759
+ host.getRootNode() instanceof ShadowRoot ? host.getRootNode() : document;
2760
+ const hostChain = buildChain(host);
2761
+ const finalizedHost = finalizePath(hostChain, hostRoot);
2762
+ if (!finalizedHost) return null;
2763
+ context.unshift({
2764
+ kind: "shadow",
2765
+ host: finalizedHost.nodes,
2766
+ });
2767
+ currentRoot = hostRoot;
2768
+ }
2769
+
2770
+ return {
2771
+ resolution: "deterministic",
2772
+ context,
2773
+ nodes: finalizedTarget.nodes,
2774
+ };
2775
+ }`;
2374
2776
  function createPlaywrightDomActionBridge(context) {
2375
2777
  return {
2778
+ buildReplayPath(locator) {
2779
+ return withLiveNode(context, locator, async ({ controller, document, backendNodeId }) => {
2780
+ const localPath = await buildLiveReplayPathForLocator(
2781
+ controller,
2782
+ document,
2783
+ locator,
2784
+ backendNodeId
2785
+ );
2786
+ return prefixIframeReplayPath(context, document.frameRef, localPath);
2787
+ });
2788
+ },
2376
2789
  inspectActionTarget(locator) {
2377
2790
  return withLiveNode(context, locator, async ({ controller, document, backendNodeId }) => {
2378
2791
  const nodeId = await resolveFrontendNodeId(controller, document, locator, backendNodeId);
@@ -2666,6 +3079,58 @@ function normalizePointerHitAssessment(value, canonicalTarget) {
2666
3079
  canonicalTarget
2667
3080
  };
2668
3081
  }
3082
+ async function buildLiveReplayPathForLocator(controller, document, locator, backendNodeId) {
3083
+ const raw = await callNodeFunction(controller, document, locator, backendNodeId, {
3084
+ functionDeclaration: BUILD_LIVE_REPLAY_PATH_DECLARATION,
3085
+ arguments: [{ value: LIVE_REPLAY_PATH_POLICY }, { value: BUILD_LIVE_REPLAY_PATH_SOURCE }],
3086
+ returnByValue: true
3087
+ });
3088
+ return requireReplayPath(raw, locator);
3089
+ }
3090
+ async function prefixIframeReplayPath(context, frameRef, localPath) {
3091
+ let currentPath = localPath;
3092
+ let currentFrame = context.requireFrame(frameRef);
3093
+ while (currentFrame.parentFrame() !== null) {
3094
+ const frameElement = await currentFrame.frameElement();
3095
+ try {
3096
+ const hostPath = await buildLiveReplayPathForHandle(frameElement);
3097
+ currentPath = {
3098
+ resolution: "deterministic",
3099
+ context: [
3100
+ ...hostPath.context,
3101
+ { kind: "iframe", host: hostPath.nodes },
3102
+ ...currentPath.context
3103
+ ],
3104
+ nodes: currentPath.nodes
3105
+ };
3106
+ } finally {
3107
+ await frameElement.dispose().catch(() => void 0);
3108
+ }
3109
+ currentFrame = currentFrame.parentFrame();
3110
+ }
3111
+ return currentPath;
3112
+ }
3113
+ async function buildLiveReplayPathForHandle(handle) {
3114
+ const raw = await handle.evaluate(
3115
+ (element, input) => {
3116
+ const buildReplayPath = (0, eval)(input.source);
3117
+ return buildReplayPath(element, input.policy);
3118
+ },
3119
+ {
3120
+ policy: LIVE_REPLAY_PATH_POLICY,
3121
+ source: BUILD_LIVE_REPLAY_PATH_SOURCE
3122
+ }
3123
+ );
3124
+ return requireReplayPath(raw);
3125
+ }
3126
+ function requireReplayPath(value, locator) {
3127
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.resolution !== "deterministic") {
3128
+ throw new Error(
3129
+ locator === void 0 ? "live DOM replay path builder returned an invalid result" : `live DOM replay path builder returned an invalid result for ${locator.nodeRef}`
3130
+ );
3131
+ }
3132
+ return value;
3133
+ }
2669
3134
  function unionQuadBounds(quads) {
2670
3135
  const bounds = quads.map((quad) => quadBounds(quad));
2671
3136
  const minX = Math.min(...bounds.map((rect) => rect.x));
@@ -2958,6 +3423,7 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
2958
3423
  document.documentEpoch,
2959
3424
  this.nodeRefForBackendNode(document, backendNodeId)
2960
3425
  ),
3426
+ requireFrame: (frameRef) => this.requireLiveFrame(frameRef),
2961
3427
  requireLiveNode: (locator) => this.requireLiveNode(locator)
2962
3428
  });
2963
3429
  return this.domActionBridge;
@@ -5104,6 +5570,18 @@ var PlaywrightBrowserCoreEngine = class _PlaywrightBrowserCoreEngine {
5104
5570
  }
5105
5571
  return frame;
5106
5572
  }
5573
+ requireLiveFrame(frameRef) {
5574
+ const state = this.requireFrame(frameRef);
5575
+ const controller = this.requirePage(state.pageRef);
5576
+ for (const frame of controller.page.frames()) {
5577
+ if (controller.frameBindings.get(frame) === frameRef) {
5578
+ return frame;
5579
+ }
5580
+ }
5581
+ throw createBrowserCoreError("not-found", `frame ${frameRef} is not attached to a live page`, {
5582
+ details: { frameRef }
5583
+ });
5584
+ }
5107
5585
  requireDocument(documentRef) {
5108
5586
  const document = this.documents.get(documentRef);
5109
5587
  if (!document) {
@@ -5305,6 +5783,9 @@ async function connectPlaywrightChromiumBrowser(input) {
5305
5783
  ...input.headers === void 0 ? {} : { headers: input.headers }
5306
5784
  });
5307
5785
  }
5786
+ async function disconnectPlaywrightChromiumBrowser(browser) {
5787
+ void browser.close().catch(() => void 0);
5788
+ }
5308
5789
  function objectHeadersToEntries(headers) {
5309
5790
  if (!headers) {
5310
5791
  return [];
@@ -5458,6 +5939,6 @@ function withTimeout(promise, timeoutMs) {
5458
5939
  ]);
5459
5940
  }
5460
5941
 
5461
- export { PlaywrightBrowserCoreEngine, capturePlaywrightStorageOrigins, connectPlaywrightChromiumBrowser, createPlaywrightBrowserCoreEngine };
5942
+ export { PlaywrightBrowserCoreEngine, capturePlaywrightStorageOrigins, connectPlaywrightChromiumBrowser, createPlaywrightBrowserCoreEngine, disconnectPlaywrightChromiumBrowser };
5462
5943
  //# sourceMappingURL=index.js.map
5463
5944
  //# sourceMappingURL=index.js.map