@orkestrel/browser 0.0.7 → 0.0.8

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.
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
3
  let _orkestrel_emitter = require("@orkestrel/emitter");
4
+ let _orkestrel_html = require("@orkestrel/html");
4
5
  //#region src/core/constants.ts
5
6
  /** Base64 alphabet, index-ordered, used to build {@link BASE64_LOOKUP}. */
6
7
  var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -75,7 +76,7 @@ var BROWSER_MOUSE_BUTTON_MASKS = Object.freeze({
75
76
  /** Tool identity embedded in HAR 1.2 documents. */
76
77
  var BROWSER_HAR_CREATOR = Object.freeze({
77
78
  name: "@orkestrel/browser",
78
- version: "0.0.5"
79
+ version: "0.0.8"
79
80
  });
80
81
  /** Attribute used to tag temporary screenshot styles and masks. */
81
82
  var BROWSER_SCREENSHOT_ATTRIBUTE = "data-orkestrel-screenshot";
@@ -2823,326 +2824,6 @@ function decodeBrowserSnapshot(value, styles = [], limit = BROWSER_SNAPSHOT_NODE
2823
2824
  };
2824
2825
  }
2825
2826
  /**
2826
- * Lazily walk every captured browser node in structural depth-first order.
2827
- *
2828
- * @remarks
2829
- * Iframe content documents are entered at their owner element. Orphaned or
2830
- * cyclic protocol nodes are still yielded once through an identity guard.
2831
- *
2832
- * @param snapshot - Captured browser snapshot
2833
- * @returns A lazy node generator
2834
- */
2835
- function* walkBrowserSnapshot(snapshot) {
2836
- const seeds = [];
2837
- for (const document of snapshot.documents) seeds.push(...document.nodes.filter((node) => node.parent === void 0));
2838
- for (const document of snapshot.documents) seeds.push(...document.nodes);
2839
- const visited = /* @__PURE__ */ new Set();
2840
- for (const seed of seeds) {
2841
- const stack = [seed];
2842
- while (stack.length > 0) {
2843
- const node = stack.pop();
2844
- if (node === void 0) break;
2845
- const identity = `${node.document}:${node.index}`;
2846
- if (visited.has(identity)) continue;
2847
- visited.add(identity);
2848
- yield node;
2849
- const children = childrenOfBrowserNode(snapshot, node);
2850
- for (let index = children.length - 1; index >= 0; index -= 1) {
2851
- const child = children[index];
2852
- if (child !== void 0) stack.push(child);
2853
- }
2854
- }
2855
- }
2856
- }
2857
- /**
2858
- * Lazily walk every captured node in structural breadth-first order.
2859
- *
2860
- * @param snapshot - Captured browser snapshot
2861
- * @returns A lazy breadth-first generator
2862
- */
2863
- function* walkBrowserSnapshotBreadthFirst(snapshot) {
2864
- const seeds = [];
2865
- for (const document of snapshot.documents) seeds.push(...document.nodes.filter((node) => node.parent === void 0));
2866
- for (const document of snapshot.documents) seeds.push(...document.nodes);
2867
- const visited = /* @__PURE__ */ new Set();
2868
- for (const seed of seeds) {
2869
- const queue = [seed];
2870
- let cursor = 0;
2871
- while (cursor < queue.length) {
2872
- const node = queue[cursor];
2873
- cursor += 1;
2874
- if (node === void 0) continue;
2875
- const identity = `${node.document}:${node.index}`;
2876
- if (visited.has(identity)) continue;
2877
- visited.add(identity);
2878
- yield node;
2879
- queue.push(...childrenOfBrowserNode(snapshot, node));
2880
- }
2881
- }
2882
- }
2883
- /**
2884
- * Lazily walk one captured browser-node subtree in depth-first order.
2885
- *
2886
- * @param snapshot - Captured browser snapshot
2887
- * @param root - Root node, included as the first result
2888
- * @returns A lazy subtree generator
2889
- */
2890
- function* walkBrowserNode(snapshot, root) {
2891
- const stack = [root];
2892
- const visited = /* @__PURE__ */ new Set();
2893
- while (stack.length > 0) {
2894
- const node = stack.pop();
2895
- if (node === void 0) break;
2896
- const identity = `${node.document}:${node.index}`;
2897
- if (visited.has(identity)) continue;
2898
- visited.add(identity);
2899
- yield node;
2900
- const children = childrenOfBrowserNode(snapshot, node);
2901
- for (let index = children.length - 1; index >= 0; index -= 1) {
2902
- const child = children[index];
2903
- if (child !== void 0) stack.push(child);
2904
- }
2905
- }
2906
- }
2907
- /**
2908
- * Lazily walk one captured browser-node subtree in breadth-first order.
2909
- *
2910
- * @param snapshot - Captured browser snapshot
2911
- * @param root - Root node, included as the first result
2912
- * @returns A lazy breadth-first subtree generator
2913
- */
2914
- function* walkBrowserNodeBreadthFirst(snapshot, root) {
2915
- const queue = [root];
2916
- const visited = /* @__PURE__ */ new Set();
2917
- let cursor = 0;
2918
- while (cursor < queue.length) {
2919
- const node = queue[cursor];
2920
- cursor += 1;
2921
- if (node === void 0) continue;
2922
- const identity = `${node.document}:${node.index}`;
2923
- if (visited.has(identity)) continue;
2924
- visited.add(identity);
2925
- yield node;
2926
- queue.push(...childrenOfBrowserNode(snapshot, node));
2927
- }
2928
- }
2929
- /**
2930
- * Lazily walk descendants of one captured node, excluding the node itself.
2931
- *
2932
- * @param snapshot - Captured browser snapshot
2933
- * @param root - Root whose descendants are required
2934
- * @returns A lazy descendant generator
2935
- */
2936
- function* descendantsOfBrowserNode(snapshot, root) {
2937
- let first = true;
2938
- for (const node of walkBrowserNode(snapshot, root)) {
2939
- if (first) {
2940
- first = false;
2941
- continue;
2942
- }
2943
- yield node;
2944
- }
2945
- }
2946
- /**
2947
- * Resolve the document containing a browser node.
2948
- *
2949
- * @param snapshot - Captured browser snapshot
2950
- * @param node - Node whose document is required
2951
- * @returns The containing document, or undefined
2952
- */
2953
- function documentOfBrowserNode(snapshot, node) {
2954
- return snapshot.documents.find((document) => document.index === node.document);
2955
- }
2956
- /**
2957
- * Return direct children of a captured browser node.
2958
- *
2959
- * @remarks
2960
- * Includes the root nodes of an iframe's linked content document. Shadow DOM
2961
- * and template content already carry flattened parent indexes from CDP.
2962
- *
2963
- * @param snapshot - Captured browser snapshot
2964
- * @param node - Parent node
2965
- * @returns Direct children in snapshot order
2966
- */
2967
- function childrenOfBrowserNode(snapshot, node) {
2968
- const document = documentOfBrowserNode(snapshot, node);
2969
- if (document === void 0) return [];
2970
- const children = document.nodes.filter((candidate) => candidate.parent === node.index);
2971
- if (node.content === void 0) return children;
2972
- const content = snapshot.documents.find((candidate) => candidate.index === node.content);
2973
- if (content === void 0) return children;
2974
- return [...children, ...content.nodes.filter((candidate) => candidate.parent === void 0)];
2975
- }
2976
- /**
2977
- * Resolve the structural parent of a captured browser node.
2978
- *
2979
- * @remarks
2980
- * An iframe content-document root resolves to its owning iframe node.
2981
- *
2982
- * @param snapshot - Captured browser snapshot
2983
- * @param node - Child node
2984
- * @returns Parent node, or undefined for a top-level document root
2985
- */
2986
- function parentOfBrowserNode(snapshot, node) {
2987
- const document = documentOfBrowserNode(snapshot, node);
2988
- if (document === void 0) return void 0;
2989
- return node.parent === void 0 ? findBrowserNode(snapshot, (candidate) => candidate.content === node.document) : document.nodes[node.parent];
2990
- }
2991
- /**
2992
- * Return every structural sibling except the supplied browser node.
2993
- *
2994
- * @param snapshot - Captured browser snapshot
2995
- * @param node - Node whose siblings are required
2996
- * @returns Siblings in structural order
2997
- */
2998
- function siblingsOfBrowserNode(snapshot, node) {
2999
- const parent = parentOfBrowserNode(snapshot, node);
3000
- return (parent === void 0 ? documentOfBrowserNode(snapshot, node)?.nodes.filter((candidate) => candidate.parent === void 0) ?? [] : childrenOfBrowserNode(snapshot, parent)).filter((candidate) => candidate.document !== node.document || candidate.index !== node.index);
3001
- }
3002
- /**
3003
- * Return structural siblings before the supplied browser node.
3004
- *
3005
- * @param snapshot - Captured browser snapshot
3006
- * @param node - Reference node
3007
- * @returns Earlier siblings in structural order
3008
- */
3009
- function precedingSiblingsOfBrowserNode(snapshot, node) {
3010
- const parent = parentOfBrowserNode(snapshot, node);
3011
- const candidates = parent === void 0 ? documentOfBrowserNode(snapshot, node)?.nodes.filter((candidate) => candidate.parent === void 0) ?? [] : childrenOfBrowserNode(snapshot, parent);
3012
- const index = candidates.findIndex((candidate) => candidate.document === node.document && candidate.index === node.index);
3013
- return index < 0 ? [] : candidates.slice(0, index);
3014
- }
3015
- /**
3016
- * Return structural siblings after the supplied browser node.
3017
- *
3018
- * @param snapshot - Captured browser snapshot
3019
- * @param node - Reference node
3020
- * @returns Later siblings in structural order
3021
- */
3022
- function followingSiblingsOfBrowserNode(snapshot, node) {
3023
- const parent = parentOfBrowserNode(snapshot, node);
3024
- const candidates = parent === void 0 ? documentOfBrowserNode(snapshot, node)?.nodes.filter((candidate) => candidate.parent === void 0) ?? [] : childrenOfBrowserNode(snapshot, parent);
3025
- const index = candidates.findIndex((candidate) => candidate.document === node.document && candidate.index === node.index);
3026
- return index < 0 ? [] : candidates.slice(index + 1);
3027
- }
3028
- /**
3029
- * Return the nearest-first ancestor chain for a captured browser node.
3030
- *
3031
- * @param snapshot - Captured browser snapshot
3032
- * @param node - Descendant node
3033
- * @returns Nearest-first ancestor list
3034
- */
3035
- function ancestorsOfBrowserNode(snapshot, node) {
3036
- const ancestors = [];
3037
- const visited = /* @__PURE__ */ new Set();
3038
- let current = node;
3039
- while (true) {
3040
- const parent = parentOfBrowserNode(snapshot, current);
3041
- if (parent === void 0) break;
3042
- const identity = `${parent.document}:${parent.index}`;
3043
- if (visited.has(identity)) break;
3044
- visited.add(identity);
3045
- ancestors.push(parent);
3046
- current = parent;
3047
- }
3048
- return ancestors;
3049
- }
3050
- /**
3051
- * Test whether one captured node is below another across frame boundaries.
3052
- *
3053
- * @param snapshot - Captured browser snapshot
3054
- * @param node - Candidate descendant
3055
- * @param ancestor - Candidate ancestor
3056
- * @returns Whether the relationship exists
3057
- */
3058
- function isBrowserNodeDescendant(snapshot, node, ancestor) {
3059
- return ancestorsOfBrowserNode(snapshot, node).some((candidate) => candidate.document === ancestor.document && candidate.index === ancestor.index);
3060
- }
3061
- /**
3062
- * Resolve the nearest common ancestor of two captured browser nodes.
3063
- *
3064
- * @param snapshot - Captured browser snapshot
3065
- * @param first - First node
3066
- * @param second - Second node
3067
- * @returns Common ancestor, or undefined
3068
- */
3069
- function commonAncestorOfBrowserNodes(snapshot, first, second) {
3070
- const firstAncestors = [first, ...ancestorsOfBrowserNode(snapshot, first)];
3071
- const secondAncestors = [second, ...ancestorsOfBrowserNode(snapshot, second)];
3072
- const identities = new Set(firstAncestors.map((node) => `${node.document}:${node.index}`));
3073
- return secondAncestors.find((node) => identities.has(`${node.document}:${node.index}`));
3074
- }
3075
- /**
3076
- * Compute the edge count between two captured browser nodes.
3077
- *
3078
- * @param snapshot - Captured browser snapshot
3079
- * @param first - First node
3080
- * @param second - Second node
3081
- * @returns Tree distance, or undefined when no common ancestor exists
3082
- */
3083
- function computeBrowserNodeDistance(snapshot, first, second) {
3084
- const common = commonAncestorOfBrowserNodes(snapshot, first, second);
3085
- if (common === void 0) return void 0;
3086
- const firstPath = [first, ...ancestorsOfBrowserNode(snapshot, first)];
3087
- const secondPath = [second, ...ancestorsOfBrowserNode(snapshot, second)];
3088
- const firstDistance = firstPath.findIndex((node) => node.document === common.document && node.index === common.index);
3089
- const secondDistance = secondPath.findIndex((node) => node.document === common.document && node.index === common.index);
3090
- if (firstDistance < 0 || secondDistance < 0) return void 0;
3091
- return firstDistance + secondDistance;
3092
- }
3093
- /**
3094
- * Find the first captured node satisfying a predicate.
3095
- *
3096
- * @param snapshot - Captured browser snapshot
3097
- * @param predicate - Node predicate
3098
- * @returns First matching node, or undefined
3099
- */
3100
- function findBrowserNode(snapshot, predicate) {
3101
- for (const node of walkBrowserSnapshot(snapshot)) if (predicate(node)) return node;
3102
- }
3103
- /**
3104
- * Find captured nodes satisfying a predicate.
3105
- *
3106
- * @param snapshot - Captured browser snapshot
3107
- * @param predicate - Node predicate
3108
- * @param limit - Optional maximum result count
3109
- * @returns Matching nodes in snapshot order
3110
- */
3111
- function findBrowserNodes(snapshot, predicate, limit) {
3112
- if (limit !== void 0 && (!(0, _orkestrel_contract.isInteger)(limit) || limit < 0)) throw new BrowserError("Browser node result limit must be a non-negative integer", void 0, { limit });
3113
- if (limit === 0) return [];
3114
- const found = [];
3115
- for (const node of walkBrowserSnapshot(snapshot)) {
3116
- if (!predicate(node)) continue;
3117
- found.push(node);
3118
- if (limit !== void 0 && found.length >= limit) break;
3119
- }
3120
- return found;
3121
- }
3122
- /**
3123
- * Find the first matching descendant beneath one captured node.
3124
- *
3125
- * @param snapshot - Captured browser snapshot
3126
- * @param root - Root whose descendants are searched
3127
- * @param predicate - Node predicate
3128
- * @returns First matching descendant, or undefined
3129
- */
3130
- function findBrowserDescendant(snapshot, root, predicate) {
3131
- for (const node of descendantsOfBrowserNode(snapshot, root)) if (predicate(node)) return node;
3132
- }
3133
- /**
3134
- * Find the nearest matching node from the candidate through its ancestors.
3135
- *
3136
- * @param snapshot - Captured browser snapshot
3137
- * @param node - Starting node, included in the search
3138
- * @param predicate - Node predicate
3139
- * @returns Closest matching node, or undefined
3140
- */
3141
- function closestBrowserNode(snapshot, node, predicate) {
3142
- if (predicate(node)) return node;
3143
- return ancestorsOfBrowserNode(snapshot, node).find(predicate);
3144
- }
3145
- /**
3146
2827
  * Read one captured node attribute.
3147
2828
  *
3148
2829
  * @param node - Captured browser node
@@ -3153,6 +2834,23 @@ function attributeOfBrowserNode(node, name) {
3153
2834
  return node.attributes[name];
3154
2835
  }
3155
2836
  /**
2837
+ * Test whether a browser-node matcher is a declarative query.
2838
+ *
2839
+ * @param value - Browser-node query or predicate
2840
+ * @returns Whether the matcher is a declarative query
2841
+ *
2842
+ * @example
2843
+ * ```ts
2844
+ * import { isBrowserNodeQuery } from '@src/core'
2845
+ *
2846
+ * isBrowserNodeQuery({ name: 'main' }) // true
2847
+ * isBrowserNodeQuery(() => true) // false
2848
+ * ```
2849
+ */
2850
+ function isBrowserNodeQuery(value) {
2851
+ return !(0, _orkestrel_contract.isFunction)(value);
2852
+ }
2853
+ /**
3156
2854
  * Test a captured node against a declarative query.
3157
2855
  *
3158
2856
  * @param node - Captured browser node
@@ -3180,36 +2878,6 @@ function isBrowserNodeVisible(node) {
3180
2878
  const bounds = node.layout?.bounds;
3181
2879
  return bounds !== void 0 && bounds[2] > 0 && bounds[3] > 0;
3182
2880
  }
3183
- /**
3184
- * Build a deterministic, serializable path for a captured browser node.
3185
- *
3186
- * @param snapshot - Captured browser snapshot
3187
- * @param node - Captured browser node
3188
- * @returns Frame-qualified structural path
3189
- */
3190
- function nodeToPath(snapshot, node) {
3191
- const document = documentOfBrowserNode(snapshot, node);
3192
- if (document === void 0) return `document:${node.document} > node:${node.index}`;
3193
- const chain = [...ancestorsOfBrowserNode(snapshot, node)].reverse();
3194
- chain.push(node);
3195
- const segments = [];
3196
- for (const current of chain) {
3197
- if (current.type !== 1) {
3198
- segments.push(`${current.name.toLowerCase()}:${current.index}`);
3199
- continue;
3200
- }
3201
- let ordinal = 1;
3202
- if (current.parent !== void 0) {
3203
- const owner = documentOfBrowserNode(snapshot, current);
3204
- for (const sibling of owner?.nodes ?? []) {
3205
- if (sibling.index >= current.index) break;
3206
- if (sibling.parent === current.parent && sibling.name === current.name) ordinal += 1;
3207
- }
3208
- }
3209
- segments.push(`${current.name.toLowerCase()}:${ordinal}`);
3210
- }
3211
- return `frame(${JSON.stringify(document.frame)}) > ${segments.join(" > ")}`;
3212
- }
3213
2881
  //#endregion
3214
2882
  //#region src/core/CDPClient.ts
3215
2883
  /**
@@ -5012,9 +4680,7 @@ var BrowserLocator = class BrowserLocator {
5012
4680
  case "enabled":
5013
4681
  expression = `(() => { const element = ${compileLocatorExpression(this.#query)}; return Boolean(element && !element.matches(':disabled')) })()`;
5014
4682
  break;
5015
- case "editable":
5016
- expression = `(() => { const element = ${compileLocatorExpression(this.#query)}; return Boolean(element && !element.matches(':disabled,[readonly]') && (element.isContentEditable || 'value' in element)) })()`;
5017
- break;
4683
+ case "editable": expression = `(() => { const element = ${compileLocatorExpression(this.#query)}; return Boolean(element && !element.matches(':disabled,[readonly]') && (element.isContentEditable || 'value' in element)) })()`;
5018
4684
  }
5019
4685
  return await this.#frame.evaluate(expression) === true;
5020
4686
  }
@@ -5175,7 +4841,7 @@ var BrowserFrame = class {
5175
4841
  this.assert();
5176
4842
  const [title, html, text, currentUrl] = await Promise.all([
5177
4843
  this.#evaluate("document.title"),
5178
- this.#evaluate(guardEvaluateExpression("document.documentElement.outerHTML", BROWSER_RESULT_LIMIT)),
4844
+ this.#captureHTML(),
5179
4845
  this.#evaluate(guardEvaluateExpression("document.body ? document.body.innerText : \"\"", BROWSER_RESULT_LIMIT)),
5180
4846
  this.#evaluate("location.href")
5181
4847
  ]);
@@ -5188,6 +4854,11 @@ var BrowserFrame = class {
5188
4854
  text: requireBrowserString(text, "Document text")
5189
4855
  };
5190
4856
  }
4857
+ async article() {
4858
+ this.assert();
4859
+ const html = requireBrowserString(await this.#captureHTML(), "Document HTML");
4860
+ return (0, _orkestrel_html.renderText)((0, _orkestrel_html.createHTML)(html).distill().document);
4861
+ }
5191
4862
  async click(selector, options) {
5192
4863
  this.assert();
5193
4864
  await this.#selectors.css(selector).click(options);
@@ -5248,6 +4919,9 @@ var BrowserFrame = class {
5248
4919
  update(url) {
5249
4920
  this.#url = url;
5250
4921
  }
4922
+ async #captureHTML() {
4923
+ return await this.#evaluate(guardEvaluateExpression("document.documentElement.outerHTML", BROWSER_RESULT_LIMIT));
4924
+ }
5251
4925
  async #evaluate(expression, timeout) {
5252
4926
  const session = await this.#sessionId();
5253
4927
  const params = {
@@ -6075,6 +5749,189 @@ var BrowserScriptManager = class {
6075
5749
  }
6076
5750
  };
6077
5751
  //#endregion
5752
+ //#region src/core/BrowserSnapshot.ts
5753
+ /** A navigable, serializable browser DOM snapshot. */
5754
+ var BrowserSnapshot = class {
5755
+ #owners;
5756
+ documents;
5757
+ styles;
5758
+ constructor(input) {
5759
+ this.documents = Object.freeze([...input.documents]);
5760
+ this.styles = Object.freeze([...input.styles]);
5761
+ }
5762
+ walk(options) {
5763
+ return options?.order === "breadth" ? this.#breadth(options.root) : this.#depth(options?.root);
5764
+ }
5765
+ *descendants(node) {
5766
+ let first = true;
5767
+ for (const descendant of this.#depth(node)) {
5768
+ if (first) {
5769
+ first = false;
5770
+ continue;
5771
+ }
5772
+ yield descendant;
5773
+ }
5774
+ }
5775
+ document(node) {
5776
+ return this.documents.find((document) => document.index === node.document);
5777
+ }
5778
+ children(node) {
5779
+ const document = this.document(node);
5780
+ if (document === void 0) return [];
5781
+ const children = document.nodes.filter((candidate) => candidate.parent === node.index);
5782
+ if (node.content === void 0) return children;
5783
+ const content = this.documents.find((candidate) => candidate.index === node.content);
5784
+ if (content === void 0) return children;
5785
+ return [...children, ...content.nodes.filter((candidate) => candidate.parent === void 0)];
5786
+ }
5787
+ parent(node) {
5788
+ const document = this.document(node);
5789
+ if (document === void 0) return void 0;
5790
+ return node.parent === void 0 ? this.#owner(node.document) : document.nodes[node.parent];
5791
+ }
5792
+ siblings(node, relation) {
5793
+ const candidates = this.#candidates(node);
5794
+ const index = candidates.findIndex((candidate) => candidate.document === node.document && candidate.index === node.index);
5795
+ if (relation === "preceding") return index < 0 ? [] : candidates.slice(0, index);
5796
+ if (relation === "following") return index < 0 ? [] : candidates.slice(index + 1);
5797
+ return candidates.filter((candidate) => candidate.document !== node.document || candidate.index !== node.index);
5798
+ }
5799
+ ancestors(node) {
5800
+ const ancestors = [];
5801
+ const visited = /* @__PURE__ */ new Set();
5802
+ let current = node;
5803
+ while (true) {
5804
+ const parent = this.parent(current);
5805
+ if (parent === void 0) break;
5806
+ const identity = this.#identity(parent);
5807
+ if (visited.has(identity)) break;
5808
+ visited.add(identity);
5809
+ ancestors.push(parent);
5810
+ current = parent;
5811
+ }
5812
+ return ancestors;
5813
+ }
5814
+ common(first, second) {
5815
+ const firstAncestors = this.#chain(first);
5816
+ const secondAncestors = this.#chain(second);
5817
+ const identities = new Set(firstAncestors.map((node) => this.#identity(node)));
5818
+ return secondAncestors.find((node) => identities.has(this.#identity(node)));
5819
+ }
5820
+ distance(first, second) {
5821
+ const common = this.common(first, second);
5822
+ if (common === void 0) return void 0;
5823
+ const identity = this.#identity(common);
5824
+ const firstDistance = this.#chain(first).findIndex((node) => this.#identity(node) === identity);
5825
+ const secondDistance = this.#chain(second).findIndex((node) => this.#identity(node) === identity);
5826
+ if (firstDistance < 0 || secondDistance < 0) return void 0;
5827
+ return firstDistance + secondDistance;
5828
+ }
5829
+ find(query) {
5830
+ for (const node of this.walk()) if (this.#match(node, query)) return node;
5831
+ }
5832
+ filter(query, limit) {
5833
+ if (limit !== void 0 && (!(0, _orkestrel_contract.isInteger)(limit) || limit < 0)) throw new BrowserError("Browser node result limit must be a non-negative integer", void 0, { limit });
5834
+ if (limit === 0) return [];
5835
+ const found = [];
5836
+ for (const node of this.walk()) {
5837
+ if (!this.#match(node, query)) continue;
5838
+ found.push(node);
5839
+ if (limit !== void 0 && found.length >= limit) break;
5840
+ }
5841
+ return found;
5842
+ }
5843
+ closest(node, query) {
5844
+ if (this.#match(node, query)) return node;
5845
+ for (const ancestor of this.ancestors(node)) if (this.#match(ancestor, query)) return ancestor;
5846
+ }
5847
+ path(node) {
5848
+ const document = this.document(node);
5849
+ if (document === void 0) return `document:${node.document} > node:${node.index}`;
5850
+ const chain = [...this.ancestors(node)].reverse();
5851
+ chain.push(node);
5852
+ const segments = [];
5853
+ for (const current of chain) {
5854
+ if (current.type !== 1) {
5855
+ segments.push(`${current.name.toLowerCase()}:${current.index}`);
5856
+ continue;
5857
+ }
5858
+ let ordinal = 1;
5859
+ if (current.parent !== void 0) {
5860
+ const owner = this.document(current);
5861
+ for (const sibling of owner?.nodes ?? []) {
5862
+ if (sibling.index >= current.index) break;
5863
+ if (sibling.parent === current.parent && sibling.name === current.name) ordinal += 1;
5864
+ }
5865
+ }
5866
+ segments.push(`${current.name.toLowerCase()}:${ordinal}`);
5867
+ }
5868
+ return `frame(${JSON.stringify(document.frame)}) > ${segments.join(" > ")}`;
5869
+ }
5870
+ #roots() {
5871
+ const roots = [];
5872
+ for (const document of this.documents) roots.push(...document.nodes.filter((node) => node.parent === void 0));
5873
+ for (const document of this.documents) roots.push(...document.nodes);
5874
+ return roots;
5875
+ }
5876
+ #owner(document) {
5877
+ const owners = this.#owners;
5878
+ if (owners !== void 0) return owners.get(document);
5879
+ const index = /* @__PURE__ */ new Map();
5880
+ for (const node of this.walk()) if (node.content !== void 0 && !index.has(node.content)) index.set(node.content, node);
5881
+ this.#owners = index;
5882
+ return index.get(document);
5883
+ }
5884
+ *#depth(root) {
5885
+ const visited = /* @__PURE__ */ new Set();
5886
+ for (const seed of root === void 0 ? this.#roots() : [root]) {
5887
+ const stack = [seed];
5888
+ while (stack.length > 0) {
5889
+ const node = stack.pop();
5890
+ if (node === void 0) break;
5891
+ const identity = this.#identity(node);
5892
+ if (visited.has(identity)) continue;
5893
+ visited.add(identity);
5894
+ yield node;
5895
+ const children = this.children(node);
5896
+ for (let index = children.length - 1; index >= 0; index -= 1) {
5897
+ const child = children[index];
5898
+ if (child !== void 0) stack.push(child);
5899
+ }
5900
+ }
5901
+ }
5902
+ }
5903
+ *#breadth(root) {
5904
+ const visited = /* @__PURE__ */ new Set();
5905
+ for (const seed of root === void 0 ? this.#roots() : [root]) {
5906
+ const queue = [seed];
5907
+ let cursor = 0;
5908
+ while (cursor < queue.length) {
5909
+ const node = queue[cursor];
5910
+ cursor += 1;
5911
+ if (node === void 0) continue;
5912
+ const identity = this.#identity(node);
5913
+ if (visited.has(identity)) continue;
5914
+ visited.add(identity);
5915
+ yield node;
5916
+ queue.push(...this.children(node));
5917
+ }
5918
+ }
5919
+ }
5920
+ #candidates(node) {
5921
+ const parent = this.parent(node);
5922
+ return parent === void 0 ? this.document(node)?.nodes.filter((candidate) => candidate.parent === void 0) ?? [] : this.children(parent);
5923
+ }
5924
+ #chain(node) {
5925
+ return [node, ...this.ancestors(node)];
5926
+ }
5927
+ #match(node, query) {
5928
+ return isBrowserNodeQuery(query) ? matchesBrowserNode(node, query) : query(node);
5929
+ }
5930
+ #identity(node) {
5931
+ return `${node.document}:${node.index}`;
5932
+ }
5933
+ };
5934
+ //#endregion
6078
5935
  //#region src/core/BrowserWorker.ts
6079
5936
  /**
6080
5937
  * Dedicated, shared, or service worker attached through a flattened target session.
@@ -6363,11 +6220,11 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
6363
6220
  async snapshot(options) {
6364
6221
  this.assert();
6365
6222
  const styles = options?.styles ?? [];
6366
- return decodeBrowserSnapshot(await this.request("DOMSnapshot.captureSnapshot", {
6223
+ return new BrowserSnapshot(decodeBrowserSnapshot(await this.request("DOMSnapshot.captureSnapshot", {
6367
6224
  computedStyles: [...styles],
6368
6225
  includePaintOrder: options?.paint ?? false,
6369
6226
  includeDOMRects: options?.rects ?? false
6370
- }), styles, options?.limit ?? 1e5);
6227
+ }), styles, options?.limit ?? 1e5));
6371
6228
  }
6372
6229
  async codegen(options) {
6373
6230
  this.assert();
@@ -7250,6 +7107,23 @@ var BrowserContext = class {
7250
7107
  function createCDPClient(options) {
7251
7108
  return new CDPClient(options);
7252
7109
  }
7110
+ /**
7111
+ * Create a navigable browser snapshot from decoded serializable data.
7112
+ *
7113
+ * @param input - Captured documents and computed-style names
7114
+ * @returns A {@link BrowserSnapshotInterface}
7115
+ *
7116
+ * @example
7117
+ * ```ts
7118
+ * import { createBrowserSnapshot, decodeBrowserSnapshot } from '@src/core'
7119
+ *
7120
+ * const snapshot = createBrowserSnapshot(decodeBrowserSnapshot(captured, ['display']))
7121
+ * snapshot.find({ name: 'main' })
7122
+ * ```
7123
+ */
7124
+ function createBrowserSnapshot(input) {
7125
+ return new BrowserSnapshot(input);
7126
+ }
7253
7127
  //#endregion
7254
7128
  exports.BASE64_CHARS = BASE64_CHARS;
7255
7129
  exports.BASE64_LOOKUP = BASE64_LOOKUP;
@@ -7299,6 +7173,7 @@ exports.BrowserRoute = BrowserRoute;
7299
7173
  exports.BrowserScriptManager = BrowserScriptManager;
7300
7174
  exports.BrowserSelectorError = BrowserSelectorError;
7301
7175
  exports.BrowserSelectorManager = BrowserSelectorManager;
7176
+ exports.BrowserSnapshot = BrowserSnapshot;
7302
7177
  exports.BrowserStorageManager = BrowserStorageManager;
7303
7178
  exports.BrowserTouch = BrowserTouch;
7304
7179
  exports.BrowserTracing = BrowserTracing;
@@ -7308,16 +7183,12 @@ exports.CDPClient = CDPClient;
7308
7183
  exports.CDPConnectionError = CDPConnectionError;
7309
7184
  exports.CDPError = CDPError;
7310
7185
  exports.CDPTimeoutError = CDPTimeoutError;
7311
- exports.ancestorsOfBrowserNode = ancestorsOfBrowserNode;
7312
7186
  exports.attributeOfBrowserNode = attributeOfBrowserNode;
7313
7187
  exports.browserHARHeadersToRecord = browserHARHeadersToRecord;
7314
7188
  exports.browserHeadersToProtocol = browserHeadersToProtocol;
7315
7189
  exports.browserPDFToParams = browserPDFToParams;
7316
7190
  exports.browserScreenshotToParams = browserScreenshotToParams;
7317
7191
  exports.bytesToText = bytesToText;
7318
- exports.childrenOfBrowserNode = childrenOfBrowserNode;
7319
- exports.closestBrowserNode = closestBrowserNode;
7320
- exports.commonAncestorOfBrowserNodes = commonAncestorOfBrowserNodes;
7321
7192
  exports.compileActionabilityFunction = compileActionabilityFunction;
7322
7193
  exports.compileAttachedLocatorWaitExpression = compileAttachedLocatorWaitExpression;
7323
7194
  exports.compileAttachedWaitExpression = compileAttachedWaitExpression;
@@ -7344,10 +7215,10 @@ exports.compileVisibleLocatorWaitExpression = compileVisibleLocatorWaitExpressio
7344
7215
  exports.compileVisibleWaitExpression = compileVisibleWaitExpression;
7345
7216
  exports.computeBrowserButtons = computeBrowserButtons;
7346
7217
  exports.computeBrowserModifiers = computeBrowserModifiers;
7347
- exports.computeBrowserNodeDistance = computeBrowserNodeDistance;
7348
7218
  exports.concatBytes = concatBytes;
7349
7219
  exports.cookieToProtocol = cookieToProtocol;
7350
7220
  exports.createBrowserHAREntry = createBrowserHAREntry;
7221
+ exports.createBrowserSnapshot = createBrowserSnapshot;
7351
7222
  exports.createCDPClient = createCDPClient;
7352
7223
  exports.decodeBase64 = decodeBase64;
7353
7224
  exports.decodeBrowserAttributes = decodeBrowserAttributes;
@@ -7355,16 +7226,10 @@ exports.decodeBrowserSnapshot = decodeBrowserSnapshot;
7355
7226
  exports.decodeRareBooleanData = decodeRareBooleanData;
7356
7227
  exports.decodeRareIntegerData = decodeRareIntegerData;
7357
7228
  exports.decodeRareStringData = decodeRareStringData;
7358
- exports.descendantsOfBrowserNode = descendantsOfBrowserNode;
7359
- exports.documentOfBrowserNode = documentOfBrowserNode;
7360
7229
  exports.encodeBase64 = encodeBase64;
7361
- exports.findBrowserDescendant = findBrowserDescendant;
7362
- exports.findBrowserNode = findBrowserNode;
7363
- exports.findBrowserNodes = findBrowserNodes;
7364
- exports.followingSiblingsOfBrowserNode = followingSiblingsOfBrowserNode;
7365
7230
  exports.guardEvaluateExpression = guardEvaluateExpression;
7366
7231
  exports.isBrowserError = isBrowserError;
7367
- exports.isBrowserNodeDescendant = isBrowserNodeDescendant;
7232
+ exports.isBrowserNodeQuery = isBrowserNodeQuery;
7368
7233
  exports.isBrowserNodeVisible = isBrowserNodeVisible;
7369
7234
  exports.isBrowserResultLimitError = isBrowserResultLimitError;
7370
7235
  exports.isBrowserSelectorError = isBrowserSelectorError;
@@ -7377,12 +7242,9 @@ exports.matchesBrowserNode = matchesBrowserNode;
7377
7242
  exports.matchesBrowserRoute = matchesBrowserRoute;
7378
7243
  exports.matchesBrowserURL = matchesBrowserURL;
7379
7244
  exports.mediaToFeatures = mediaToFeatures;
7380
- exports.nodeToPath = nodeToPath;
7381
7245
  exports.normalizeCodegenActions = normalizeCodegenActions;
7382
- exports.parentOfBrowserNode = parentOfBrowserNode;
7383
7246
  exports.parseBrowserChord = parseBrowserChord;
7384
7247
  exports.parseCodegenActionPayload = parseCodegenActionPayload;
7385
- exports.precedingSiblingsOfBrowserNode = precedingSiblingsOfBrowserNode;
7386
7248
  exports.readBrowserAXString = readBrowserAXString;
7387
7249
  exports.readBrowserAXValue = readBrowserAXValue;
7388
7250
  exports.readBrowserAccessibility = readBrowserAccessibility;
@@ -7423,7 +7285,6 @@ exports.readEvaluationResult = readEvaluationResult;
7423
7285
  exports.readNumberArray = readNumberArray;
7424
7286
  exports.readSnapshotString = readSnapshotString;
7425
7287
  exports.requireBrowserString = requireBrowserString;
7426
- exports.siblingsOfBrowserNode = siblingsOfBrowserNode;
7427
7288
  exports.textToBytes = textToBytes;
7428
7289
  exports.validateBrowserAccessibilityOptions = validateBrowserAccessibilityOptions;
7429
7290
  exports.validateBrowserActionOptions = validateBrowserActionOptions;
@@ -7434,9 +7295,5 @@ exports.validateBrowserPoint = validateBrowserPoint;
7434
7295
  exports.validateBrowserRange = validateBrowserRange;
7435
7296
  exports.validateBrowserTimeout = validateBrowserTimeout;
7436
7297
  exports.validateBrowserViewport = validateBrowserViewport;
7437
- exports.walkBrowserNode = walkBrowserNode;
7438
- exports.walkBrowserNodeBreadthFirst = walkBrowserNodeBreadthFirst;
7439
- exports.walkBrowserSnapshot = walkBrowserSnapshot;
7440
- exports.walkBrowserSnapshotBreadthFirst = walkBrowserSnapshotBreadthFirst;
7441
7298
 
7442
7299
  //# sourceMappingURL=index.cjs.map