@browserbasehq/stagehand 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,7 +137,7 @@ This constructor is used to create an instance of Stagehand.
137
137
  - `1`: SDK-level logging
138
138
  - `2`: LLM-client level logging (most granular)
139
139
  - `debugDom`: a `boolean` that draws bounding boxes around elements presented to the LLM during automation.
140
- - `domSettleTimeoutMs`: an `integer` that specifies the timeout in milliseconds for waiting for the DOM to settle. Defaults to 30000 (30 seconds).
140
+ - `domSettleTimeoutMs`: an `integer` that specifies the timeout in milliseconds for waiting for the DOM to settle. It can be overriden in individual function calls if needed. Defaults to 30000 (30 seconds).
141
141
  - `enableCaching`: a `boolean` that enables caching of LLM responses. When set to `true`, the LLM requests will be cached on disk and reused for identical requests. Defaults to `false`.
142
142
 
143
143
  - **Returns:**
@@ -179,6 +179,7 @@ This constructor is used to create an instance of Stagehand.
179
179
  - `action`: a `string` describing the action to perform, e.g., `"search for 'x'"`.
180
180
  - `modelName`: (optional) an `AvailableModel` string to specify the model to use.
181
181
  - `useVision`: (optional) a `boolean` or `"fallback"` to determine if vision-based processing should be used. Defaults to `"fallback"`.
182
+ - `domSettleTimeoutMs`: (optional) an `integer` that specifies the timeout in milliseconds for waiting for the DOM to settle. If not set, defaults to the timeout value specified during initialization.
182
183
 
183
184
  - **Returns:**
184
185
 
@@ -201,6 +202,7 @@ This constructor is used to create an instance of Stagehand.
201
202
  - `instruction`: a `string` providing instructions for extraction.
202
203
  - `schema`: a `z.AnyZodObject` defining the structure of the data to extract.
203
204
  - `modelName`: (optional) an `AvailableModel` string to specify the model to use.
205
+ - `domSettleTimeoutMs`: (optional) an `integer` that specifies the timeout in milliseconds for waiting for the DOM to settle. If not set, defaults to the timeout value specified during initialization.
204
206
 
205
207
  - **Returns:**
206
208
 
@@ -229,6 +231,7 @@ If you are looking for a specific element, you can also pass in an instruction t
229
231
 
230
232
  - `instruction`: a `string` providing instructions for the observation.
231
233
  - `useVision`: (optional) a `boolean` or `"fallback"` to determine if vision-based processing should be used. Defaults to `"fallback"`.
234
+ - `domSettleTimeoutMs`: (optional) an `integer` that specifies the timeout in milliseconds for waiting for the DOM to settle. If not set, defaults to the timeout value specified during initialization.
232
235
 
233
236
  - **Returns:**
234
237
 
@@ -1,8 +1,173 @@
1
1
  (() => {
2
+ // lib/dom/xpathUtils.ts
3
+ function getParentElement(node) {
4
+ return isElementNode(node) ? node.parentElement : node.parentNode;
5
+ }
6
+ function getCombinations(attributes, size) {
7
+ const results = [];
8
+ function helper(start, combo) {
9
+ if (combo.length === size) {
10
+ results.push([...combo]);
11
+ return;
12
+ }
13
+ for (let i = start; i < attributes.length; i++) {
14
+ combo.push(attributes[i]);
15
+ helper(i + 1, combo);
16
+ combo.pop();
17
+ }
18
+ }
19
+ helper(0, []);
20
+ return results;
21
+ }
22
+ function isXPathFirstResultElement(xpath, target) {
23
+ try {
24
+ const result = document.evaluate(
25
+ xpath,
26
+ document.documentElement,
27
+ null,
28
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
29
+ null
30
+ );
31
+ return result.snapshotItem(0) === target;
32
+ } catch (error) {
33
+ console.warn(`Invalid XPath expression: ${xpath}`, error);
34
+ return false;
35
+ }
36
+ }
37
+ function escapeXPathString(value) {
38
+ if (value.includes("'")) {
39
+ if (value.includes('"')) {
40
+ return "concat(" + value.split(/('+)/).map((part) => {
41
+ if (part === "'") {
42
+ return `"'"`;
43
+ } else if (part.startsWith("'") && part.endsWith("'")) {
44
+ return `"${part}"`;
45
+ } else {
46
+ return `'${part}'`;
47
+ }
48
+ }).join(",") + ")";
49
+ } else {
50
+ return `"${value}"`;
51
+ }
52
+ } else {
53
+ return `'${value}'`;
54
+ }
55
+ }
56
+ async function generateXPathsForElement(element) {
57
+ if (!element) return [];
58
+ const [complexXPath, standardXPath, idBasedXPath] = await Promise.all([
59
+ generateComplexXPath(element),
60
+ generateStandardXPath(element),
61
+ generatedIdBasedXPath(element)
62
+ ]);
63
+ return [...idBasedXPath ? [idBasedXPath] : [], standardXPath, complexXPath];
64
+ }
65
+ async function generateComplexXPath(element) {
66
+ const parts = [];
67
+ let currentElement = element;
68
+ while (currentElement && (isTextNode(currentElement) || isElementNode(currentElement))) {
69
+ if (isElementNode(currentElement)) {
70
+ const el = currentElement;
71
+ let selector = el.tagName.toLowerCase();
72
+ const attributePriority = [
73
+ "data-qa",
74
+ "data-component",
75
+ "data-role",
76
+ "role",
77
+ "aria-role",
78
+ "type",
79
+ "name",
80
+ "aria-label",
81
+ "placeholder",
82
+ "title",
83
+ "alt"
84
+ ];
85
+ const attributes = attributePriority.map((attr) => {
86
+ let value = el.getAttribute(attr);
87
+ if (attr === "href-full" && value) {
88
+ value = el.getAttribute("href");
89
+ }
90
+ return value ? { attr: attr === "href-full" ? "href" : attr, value } : null;
91
+ }).filter((attr) => attr !== null);
92
+ let uniqueSelector = "";
93
+ for (let i = 1; i <= attributes.length; i++) {
94
+ const combinations = getCombinations(attributes, i);
95
+ for (const combo of combinations) {
96
+ const conditions = combo.map((a) => `@${a.attr}=${escapeXPathString(a.value)}`).join(" and ");
97
+ const xpath2 = `//${selector}[${conditions}]`;
98
+ if (isXPathFirstResultElement(xpath2, el)) {
99
+ uniqueSelector = xpath2;
100
+ break;
101
+ }
102
+ }
103
+ if (uniqueSelector) break;
104
+ }
105
+ if (uniqueSelector) {
106
+ parts.unshift(uniqueSelector.replace("//", ""));
107
+ break;
108
+ } else {
109
+ const parent = getParentElement(el);
110
+ if (parent) {
111
+ const siblings = Array.from(parent.children).filter(
112
+ (sibling) => sibling.tagName === el.tagName
113
+ );
114
+ const index = siblings.indexOf(el) + 1;
115
+ selector += siblings.length > 1 ? `[${index}]` : "";
116
+ }
117
+ parts.unshift(selector);
118
+ }
119
+ }
120
+ currentElement = getParentElement(currentElement);
121
+ }
122
+ const xpath = "//" + parts.join("/");
123
+ return xpath;
124
+ }
125
+ async function generateStandardXPath(element) {
126
+ const parts = [];
127
+ while (element && (isTextNode(element) || isElementNode(element))) {
128
+ let index = 0;
129
+ let hasSameTypeSiblings = false;
130
+ const siblings = element.parentElement ? Array.from(element.parentElement.childNodes) : [];
131
+ for (let i = 0; i < siblings.length; i++) {
132
+ const sibling = siblings[i];
133
+ if (sibling.nodeType === element.nodeType && sibling.nodeName === element.nodeName) {
134
+ index = index + 1;
135
+ hasSameTypeSiblings = true;
136
+ if (sibling.isSameNode(element)) {
137
+ break;
138
+ }
139
+ }
140
+ }
141
+ if (element.nodeName !== "#text") {
142
+ const tagName = element.nodeName.toLowerCase();
143
+ const pathIndex = hasSameTypeSiblings ? `[${index}]` : "";
144
+ parts.unshift(`${tagName}${pathIndex}`);
145
+ }
146
+ element = element.parentElement;
147
+ }
148
+ return parts.length ? `//${parts.join("//")}` : "";
149
+ }
150
+ async function generatedIdBasedXPath(element) {
151
+ if (isElementNode(element) && element.id) {
152
+ return `//*[@id='${element.id}']`;
153
+ }
154
+ return null;
155
+ }
156
+
2
157
  // lib/dom/process.ts
158
+ function isElementNode(node) {
159
+ return node.nodeType === Node.ELEMENT_NODE;
160
+ }
161
+ function isTextNode(node) {
162
+ return node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim());
163
+ }
3
164
  async function processDom(chunksSeen) {
4
165
  const { chunk, chunksArray } = await pickChunk(chunksSeen);
5
- const { outputString, selectorMap } = await processElements2(chunk);
166
+ const { outputString, selectorMap } = await processElements2(
167
+ chunk,
168
+ void 0,
169
+ void 0
170
+ );
6
171
  console.log(
7
172
  `Stagehand (Browser Process): Extracted dom elements:
8
173
  ${outputString}`
@@ -49,12 +214,13 @@ ${outputString}`
49
214
  scrollEndTimer = window.setTimeout(() => {
50
215
  window.removeEventListener("scroll", handleScrollEnd);
51
216
  resolve();
52
- }, 200);
217
+ }, 100);
53
218
  };
54
219
  window.addEventListener("scroll", handleScrollEnd, { passive: true });
55
220
  handleScrollEnd();
56
221
  });
57
222
  }
223
+ var xpathCache = /* @__PURE__ */ new Map();
58
224
  async function processElements2(chunk, scrollToChunk = true, indexOffset = 0) {
59
225
  console.time("processElements:total");
60
226
  const viewportHeight = window.innerHeight;
@@ -68,7 +234,6 @@ ${outputString}`
68
234
  }
69
235
  const candidateElements = [];
70
236
  const DOMQueue = [...document.body.childNodes];
71
- const xpathCache = /* @__PURE__ */ new Map();
72
237
  console.log("Stagehand (Browser Process): Generating candidate elements");
73
238
  console.time("processElements:findCandidates");
74
239
  while (DOMQueue.length > 0) {
@@ -105,16 +270,25 @@ ${outputString}`
105
270
  `Stagehand (Browser Process): Processing candidate elements: ${candidateElements.length}`
106
271
  );
107
272
  console.time("processElements:processCandidates");
273
+ console.time("processElements:generateXPaths");
274
+ const xpathLists = await Promise.all(
275
+ candidateElements.map(async (element) => {
276
+ if (xpathCache.has(element)) {
277
+ return xpathCache.get(element);
278
+ }
279
+ const xpaths = await generateXPathsForElement(element);
280
+ xpathCache.set(element, xpaths);
281
+ return xpaths;
282
+ })
283
+ );
284
+ console.timeEnd("processElements:generateXPaths");
108
285
  candidateElements.forEach((element, index) => {
109
- let xpath = xpathCache.get(element);
110
- if (!xpath) {
111
- xpath = generateXPath(element);
112
- xpathCache.set(element, xpath);
113
- }
286
+ const xpaths = xpathLists[index];
287
+ let elementOutput = "";
114
288
  if (isTextNode(element)) {
115
289
  const textContent = element.textContent?.trim();
116
290
  if (textContent) {
117
- outputString += `${index + indexOffset}:${textContent}
291
+ elementOutput += `${index + indexOffset}:${textContent}
118
292
  `;
119
293
  }
120
294
  } else if (isElementNode(element)) {
@@ -123,10 +297,11 @@ ${outputString}`
123
297
  const openingTag = `<${tagName}${attributes ? " " + attributes : ""}>`;
124
298
  const closingTag = `</${tagName}>`;
125
299
  const textContent = element.textContent?.trim() || "";
126
- outputString += `${index + indexOffset}:${openingTag}${textContent}${closingTag}
300
+ elementOutput += `${index + indexOffset}:${openingTag}${textContent}${closingTag}
127
301
  `;
128
302
  }
129
- selectorMap[index + indexOffset] = xpath;
303
+ outputString += elementOutput;
304
+ selectorMap[index + indexOffset] = xpaths;
130
305
  });
131
306
  console.timeEnd("processElements:processCandidates");
132
307
  console.timeEnd("processElements:total");
@@ -163,34 +338,6 @@ ${outputString}`
163
338
  window.processAllOfDom = processAllOfDom;
164
339
  window.processElements = processElements2;
165
340
  window.scrollToHeight = scrollToHeight;
166
- function generateXPath(element) {
167
- if (isElementNode(element) && element.id) {
168
- return `//*[@id='${element.id}']`;
169
- }
170
- const parts = [];
171
- while (element && (isTextNode(element) || isElementNode(element))) {
172
- let index = 0;
173
- let hasSameTypeSiblings = false;
174
- const siblings = element.parentElement ? Array.from(element.parentElement.childNodes) : [];
175
- for (let i = 0; i < siblings.length; i++) {
176
- const sibling = siblings[i];
177
- if (sibling.nodeType === element.nodeType && sibling.nodeName === element.nodeName) {
178
- index = index + 1;
179
- hasSameTypeSiblings = true;
180
- if (sibling.isSameNode(element)) {
181
- break;
182
- }
183
- }
184
- }
185
- if (element.nodeName !== "#text") {
186
- const tagName = element.nodeName.toLowerCase();
187
- const pathIndex = hasSameTypeSiblings ? `[${index}]` : "";
188
- parts.unshift(`${tagName}${pathIndex}`);
189
- }
190
- element = element.parentElement;
191
- }
192
- return parts.length ? `/${parts.join("/")}` : "";
193
- }
194
341
  var leafElementDenyList = ["SVG", "IFRAME", "SCRIPT", "STYLE", "LINK"];
195
342
  var interactiveElementTypes = [
196
343
  "A",
@@ -231,13 +378,6 @@ ${outputString}`
231
378
  "tooltip"
232
379
  ];
233
380
  var interactiveAriaRoles = ["menu", "menuitem", "button"];
234
- function isElementNode(node) {
235
- return node.nodeType === Node.ELEMENT_NODE;
236
- }
237
- function isTextNode(node) {
238
- const trimmedText = node.textContent?.trim().replace(/\s/g, "");
239
- return node.nodeType === Node.TEXT_NODE && trimmedText !== "";
240
- }
241
381
  var isVisible = (element) => {
242
382
  const rect = element.getBoundingClientRect();
243
383
  if (rect.width === 0 || rect.height === 0 || rect.top < 0 || rect.top > window.innerHeight) {
@@ -1,8 +1,173 @@
1
1
  (() => {
2
+ // lib/dom/xpathUtils.ts
3
+ function getParentElement(node) {
4
+ return isElementNode(node) ? node.parentElement : node.parentNode;
5
+ }
6
+ function getCombinations(attributes, size) {
7
+ const results = [];
8
+ function helper(start, combo) {
9
+ if (combo.length === size) {
10
+ results.push([...combo]);
11
+ return;
12
+ }
13
+ for (let i = start; i < attributes.length; i++) {
14
+ combo.push(attributes[i]);
15
+ helper(i + 1, combo);
16
+ combo.pop();
17
+ }
18
+ }
19
+ helper(0, []);
20
+ return results;
21
+ }
22
+ function isXPathFirstResultElement(xpath, target) {
23
+ try {
24
+ const result = document.evaluate(
25
+ xpath,
26
+ document.documentElement,
27
+ null,
28
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
29
+ null
30
+ );
31
+ return result.snapshotItem(0) === target;
32
+ } catch (error) {
33
+ console.warn(`Invalid XPath expression: ${xpath}`, error);
34
+ return false;
35
+ }
36
+ }
37
+ function escapeXPathString(value) {
38
+ if (value.includes("'")) {
39
+ if (value.includes('"')) {
40
+ return "concat(" + value.split(/('+)/).map((part) => {
41
+ if (part === "'") {
42
+ return `"'"`;
43
+ } else if (part.startsWith("'") && part.endsWith("'")) {
44
+ return `"${part}"`;
45
+ } else {
46
+ return `'${part}'`;
47
+ }
48
+ }).join(",") + ")";
49
+ } else {
50
+ return `"${value}"`;
51
+ }
52
+ } else {
53
+ return `'${value}'`;
54
+ }
55
+ }
56
+ async function generateXPathsForElement(element) {
57
+ if (!element) return [];
58
+ const [complexXPath, standardXPath, idBasedXPath] = await Promise.all([
59
+ generateComplexXPath(element),
60
+ generateStandardXPath(element),
61
+ generatedIdBasedXPath(element)
62
+ ]);
63
+ return [...idBasedXPath ? [idBasedXPath] : [], standardXPath, complexXPath];
64
+ }
65
+ async function generateComplexXPath(element) {
66
+ const parts = [];
67
+ let currentElement = element;
68
+ while (currentElement && (isTextNode(currentElement) || isElementNode(currentElement))) {
69
+ if (isElementNode(currentElement)) {
70
+ const el = currentElement;
71
+ let selector = el.tagName.toLowerCase();
72
+ const attributePriority = [
73
+ "data-qa",
74
+ "data-component",
75
+ "data-role",
76
+ "role",
77
+ "aria-role",
78
+ "type",
79
+ "name",
80
+ "aria-label",
81
+ "placeholder",
82
+ "title",
83
+ "alt"
84
+ ];
85
+ const attributes = attributePriority.map((attr) => {
86
+ let value = el.getAttribute(attr);
87
+ if (attr === "href-full" && value) {
88
+ value = el.getAttribute("href");
89
+ }
90
+ return value ? { attr: attr === "href-full" ? "href" : attr, value } : null;
91
+ }).filter((attr) => attr !== null);
92
+ let uniqueSelector = "";
93
+ for (let i = 1; i <= attributes.length; i++) {
94
+ const combinations = getCombinations(attributes, i);
95
+ for (const combo of combinations) {
96
+ const conditions = combo.map((a) => `@${a.attr}=${escapeXPathString(a.value)}`).join(" and ");
97
+ const xpath2 = `//${selector}[${conditions}]`;
98
+ if (isXPathFirstResultElement(xpath2, el)) {
99
+ uniqueSelector = xpath2;
100
+ break;
101
+ }
102
+ }
103
+ if (uniqueSelector) break;
104
+ }
105
+ if (uniqueSelector) {
106
+ parts.unshift(uniqueSelector.replace("//", ""));
107
+ break;
108
+ } else {
109
+ const parent = getParentElement(el);
110
+ if (parent) {
111
+ const siblings = Array.from(parent.children).filter(
112
+ (sibling) => sibling.tagName === el.tagName
113
+ );
114
+ const index = siblings.indexOf(el) + 1;
115
+ selector += siblings.length > 1 ? `[${index}]` : "";
116
+ }
117
+ parts.unshift(selector);
118
+ }
119
+ }
120
+ currentElement = getParentElement(currentElement);
121
+ }
122
+ const xpath = "//" + parts.join("/");
123
+ return xpath;
124
+ }
125
+ async function generateStandardXPath(element) {
126
+ const parts = [];
127
+ while (element && (isTextNode(element) || isElementNode(element))) {
128
+ let index = 0;
129
+ let hasSameTypeSiblings = false;
130
+ const siblings = element.parentElement ? Array.from(element.parentElement.childNodes) : [];
131
+ for (let i = 0; i < siblings.length; i++) {
132
+ const sibling = siblings[i];
133
+ if (sibling.nodeType === element.nodeType && sibling.nodeName === element.nodeName) {
134
+ index = index + 1;
135
+ hasSameTypeSiblings = true;
136
+ if (sibling.isSameNode(element)) {
137
+ break;
138
+ }
139
+ }
140
+ }
141
+ if (element.nodeName !== "#text") {
142
+ const tagName = element.nodeName.toLowerCase();
143
+ const pathIndex = hasSameTypeSiblings ? `[${index}]` : "";
144
+ parts.unshift(`${tagName}${pathIndex}`);
145
+ }
146
+ element = element.parentElement;
147
+ }
148
+ return parts.length ? `//${parts.join("//")}` : "";
149
+ }
150
+ async function generatedIdBasedXPath(element) {
151
+ if (isElementNode(element) && element.id) {
152
+ return `//*[@id='${element.id}']`;
153
+ }
154
+ return null;
155
+ }
156
+
2
157
  // lib/dom/process.ts
158
+ function isElementNode(node) {
159
+ return node.nodeType === Node.ELEMENT_NODE;
160
+ }
161
+ function isTextNode(node) {
162
+ return node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim());
163
+ }
3
164
  async function processDom(chunksSeen) {
4
165
  const { chunk, chunksArray } = await pickChunk(chunksSeen);
5
- const { outputString, selectorMap } = await processElements(chunk);
166
+ const { outputString, selectorMap } = await processElements(
167
+ chunk,
168
+ void 0,
169
+ void 0
170
+ );
6
171
  console.log(
7
172
  `Stagehand (Browser Process): Extracted dom elements:
8
173
  ${outputString}`
@@ -49,12 +214,13 @@ ${outputString}`
49
214
  scrollEndTimer = window.setTimeout(() => {
50
215
  window.removeEventListener("scroll", handleScrollEnd);
51
216
  resolve();
52
- }, 200);
217
+ }, 100);
53
218
  };
54
219
  window.addEventListener("scroll", handleScrollEnd, { passive: true });
55
220
  handleScrollEnd();
56
221
  });
57
222
  }
223
+ var xpathCache = /* @__PURE__ */ new Map();
58
224
  async function processElements(chunk, scrollToChunk = true, indexOffset = 0) {
59
225
  console.time("processElements:total");
60
226
  const viewportHeight = window.innerHeight;
@@ -68,7 +234,6 @@ ${outputString}`
68
234
  }
69
235
  const candidateElements = [];
70
236
  const DOMQueue = [...document.body.childNodes];
71
- const xpathCache = /* @__PURE__ */ new Map();
72
237
  console.log("Stagehand (Browser Process): Generating candidate elements");
73
238
  console.time("processElements:findCandidates");
74
239
  while (DOMQueue.length > 0) {
@@ -105,16 +270,25 @@ ${outputString}`
105
270
  `Stagehand (Browser Process): Processing candidate elements: ${candidateElements.length}`
106
271
  );
107
272
  console.time("processElements:processCandidates");
273
+ console.time("processElements:generateXPaths");
274
+ const xpathLists = await Promise.all(
275
+ candidateElements.map(async (element) => {
276
+ if (xpathCache.has(element)) {
277
+ return xpathCache.get(element);
278
+ }
279
+ const xpaths = await generateXPathsForElement(element);
280
+ xpathCache.set(element, xpaths);
281
+ return xpaths;
282
+ })
283
+ );
284
+ console.timeEnd("processElements:generateXPaths");
108
285
  candidateElements.forEach((element, index) => {
109
- let xpath = xpathCache.get(element);
110
- if (!xpath) {
111
- xpath = generateXPath(element);
112
- xpathCache.set(element, xpath);
113
- }
286
+ const xpaths = xpathLists[index];
287
+ let elementOutput = "";
114
288
  if (isTextNode(element)) {
115
289
  const textContent = element.textContent?.trim();
116
290
  if (textContent) {
117
- outputString += `${index + indexOffset}:${textContent}
291
+ elementOutput += `${index + indexOffset}:${textContent}
118
292
  `;
119
293
  }
120
294
  } else if (isElementNode(element)) {
@@ -123,10 +297,11 @@ ${outputString}`
123
297
  const openingTag = `<${tagName}${attributes ? " " + attributes : ""}>`;
124
298
  const closingTag = `</${tagName}>`;
125
299
  const textContent = element.textContent?.trim() || "";
126
- outputString += `${index + indexOffset}:${openingTag}${textContent}${closingTag}
300
+ elementOutput += `${index + indexOffset}:${openingTag}${textContent}${closingTag}
127
301
  `;
128
302
  }
129
- selectorMap[index + indexOffset] = xpath;
303
+ outputString += elementOutput;
304
+ selectorMap[index + indexOffset] = xpaths;
130
305
  });
131
306
  console.timeEnd("processElements:processCandidates");
132
307
  console.timeEnd("processElements:total");
@@ -163,34 +338,6 @@ ${outputString}`
163
338
  window.processAllOfDom = processAllOfDom;
164
339
  window.processElements = processElements;
165
340
  window.scrollToHeight = scrollToHeight;
166
- function generateXPath(element) {
167
- if (isElementNode(element) && element.id) {
168
- return `//*[@id='${element.id}']`;
169
- }
170
- const parts = [];
171
- while (element && (isTextNode(element) || isElementNode(element))) {
172
- let index = 0;
173
- let hasSameTypeSiblings = false;
174
- const siblings = element.parentElement ? Array.from(element.parentElement.childNodes) : [];
175
- for (let i = 0; i < siblings.length; i++) {
176
- const sibling = siblings[i];
177
- if (sibling.nodeType === element.nodeType && sibling.nodeName === element.nodeName) {
178
- index = index + 1;
179
- hasSameTypeSiblings = true;
180
- if (sibling.isSameNode(element)) {
181
- break;
182
- }
183
- }
184
- }
185
- if (element.nodeName !== "#text") {
186
- const tagName = element.nodeName.toLowerCase();
187
- const pathIndex = hasSameTypeSiblings ? `[${index}]` : "";
188
- parts.unshift(`${tagName}${pathIndex}`);
189
- }
190
- element = element.parentElement;
191
- }
192
- return parts.length ? `/${parts.join("/")}` : "";
193
- }
194
341
  var leafElementDenyList = ["SVG", "IFRAME", "SCRIPT", "STYLE", "LINK"];
195
342
  var interactiveElementTypes = [
196
343
  "A",
@@ -231,13 +378,6 @@ ${outputString}`
231
378
  "tooltip"
232
379
  ];
233
380
  var interactiveAriaRoles = ["menu", "menuitem", "button"];
234
- function isElementNode(node) {
235
- return node.nodeType === Node.ELEMENT_NODE;
236
- }
237
- function isTextNode(node) {
238
- const trimmedText = node.textContent?.trim().replace(/\s/g, "");
239
- return node.nodeType === Node.TEXT_NODE && trimmedText !== "";
240
- }
241
381
  var isVisible = (element) => {
242
382
  const rect = element.getBoundingClientRect();
243
383
  if (rect.width === 0 || rect.height === 0 || rect.top < 0 || rect.top > window.innerHeight) {