@orkestrel/browser 0.0.7 → 0.0.9

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