@nodebug/browser-element-finder 1.0.8 → 1.1.5

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/index.js CHANGED
@@ -21,14 +21,18 @@ var ElementFinder = (() => {
21
21
  var element_finder_exports = {};
22
22
  __export(element_finder_exports, {
23
23
  ELEMENT_DEFINITIONS: () => ELEMENT_DEFINITIONS,
24
- findElement: () => findElement,
24
+ findElementByAttributes: () => findElementByAttributes,
25
+ findElementByType: () => findElementByType,
26
+ findElements: () => findElements,
27
+ findProbableElements: () => findProbableElements,
25
28
  getAllElements: () => getAllElements,
26
29
  getAllFrames: () => getAllFrames,
27
30
  getBoundingBox: () => getBoundingBox,
28
31
  getSearchableAttributes: () => getSearchableAttributes,
32
+ getValidAttributes: () => getValidAttributes,
29
33
  getValidTypes: () => getValidTypes,
30
34
  highlight: () => highlight,
31
- matchesContent: () => matchesContent,
35
+ matchesAttribute: () => matchesAttribute,
32
36
  matchesType: () => matchesType,
33
37
  parseCondition: () => parseCondition,
34
38
  parseXPath: () => parseXPath,
@@ -37,25 +41,6 @@ var ElementFinder = (() => {
37
41
  unhighlight: () => unhighlight
38
42
  });
39
43
 
40
- // src/searchable-attributes.json
41
- var searchable_attributes_default = [
42
- "placeholder",
43
- "value",
44
- "data-test-id",
45
- "data-testid",
46
- "id",
47
- "resource-id",
48
- "name",
49
- "aria-label",
50
- "class",
51
- "hint",
52
- "title",
53
- "tooltip",
54
- "alt",
55
- "src",
56
- "aria-labelledby"
57
- ];
58
-
59
44
  // src/element-definitions.json
60
45
  var element_definitions_default = {
61
46
  link: "self::a or @role='link' or @href",
@@ -65,9 +50,11 @@ var ElementFinder = (() => {
65
50
  checkbox: "(self::input and @type='checkbox') or @role='checkbox'",
66
51
  switch: "(self::input and @type='checkbox') or @role='switch' or (self::button and (contains(@class, 'switch') or @data-state))",
67
52
  slider: "self::input[@type='range'] or @role='slider'",
53
+ datepicker: "self::input and @type='date'",
54
+ colorpicker: "self::input and @type='color'",
68
55
  radio: "(self::input and @type='radio') or @role='radio'",
69
56
  dropdown: "(self::select[descendant::option] or @role='combobox' or @role='listbox' or contains(@class, 'dropdown') or contains(@class, 'trigger') or ancestor::*[contains(@class, 'dropdown') or @role='combobox'])",
70
- textbox: "self::textarea or (self::input and (@type='text' or @type='password' or @type='search' or @type='email')) or @role='textbox'",
57
+ textbox: "self::textarea or (self::input and (@type='text' or @type='password' or @type='search' or @type='email' or @type='number' or @type='tel' or @type='url')) or @role='textbox'",
71
58
  file: "self::input and @type='file'",
72
59
  list: "self::ul or self::ol or @role='list'",
73
60
  listitem: "self::li or @role='listitem'",
@@ -78,10 +65,29 @@ var ElementFinder = (() => {
78
65
  table: "self::table or @role='table'",
79
66
  row: "self::tr or @role='row'",
80
67
  column: "self::td or self::th or @role='cell' or @role='gridcell' or @role='columnheader'",
68
+ cell: "self::td or @role='cell' or @role='gridcell'",
81
69
  image: "self::img or @role='img' or @alt",
82
70
  element: "true()"
83
71
  };
84
72
 
73
+ // src/searchable-attributes.json
74
+ var searchable_attributes_default = [
75
+ "placeholder",
76
+ "value",
77
+ "data-test-id",
78
+ "data-testid",
79
+ "id",
80
+ "resource-id",
81
+ "name",
82
+ "aria-label",
83
+ "hint",
84
+ "title",
85
+ "tooltip",
86
+ "alt",
87
+ "src",
88
+ "aria-labelledby"
89
+ ];
90
+
85
91
  // src/element-finder.js
86
92
  var REGEX_PATTERNS = {
87
93
  selfWithTag: /^self::([a-zA-Z0-9-]+)(?:\[([^\]]+)\])?$/,
@@ -94,29 +100,43 @@ var ElementFinder = (() => {
94
100
  operatorAnd: /^\s*\band\b\s*/i
95
101
  };
96
102
  var MAX_RECURSION_DEPTH = 100;
97
- function parseXPath(expr, el, depth = 0) {
98
- if (expr == null || el == null) {
99
- return false;
103
+ var TYPE_MATCHERS = /* @__PURE__ */ new Map();
104
+ for (const [type, expr] of Object.entries(element_definitions_default)) {
105
+ if (expr === "true()") {
106
+ TYPE_MATCHERS.set(type, () => true);
107
+ } else {
108
+ TYPE_MATCHERS.set(type, (el) => parseXPath(expr, el));
100
109
  }
110
+ }
111
+ var SEARCHABLE_ATTRIBUTES = searchable_attributes_default;
112
+ function setSearchableAttributes(attributes) {
113
+ if (!Array.isArray(attributes)) {
114
+ throw new TypeError("attributes must be an array");
115
+ }
116
+ SEARCHABLE_ATTRIBUTES = attributes;
117
+ }
118
+ function getSearchableAttributes() {
119
+ return [...SEARCHABLE_ATTRIBUTES];
120
+ }
121
+ function parseXPath(expr, el, depth = 0) {
122
+ if (expr == null || el == null) return false;
101
123
  if (depth > MAX_RECURSION_DEPTH) {
102
124
  throw new Error("XPath expression exceeds maximum recursion depth");
103
125
  }
104
126
  expr = expr.trim();
105
127
  if (expr === "true()") return true;
106
- if (expr.startsWith("(") && expr.endsWith(")")) {
107
- let parenDepth = 0;
128
+ if (expr[0] === "(" && expr[expr.length - 1] === ")") {
129
+ let parenDepth = 1;
108
130
  let matchedAll = true;
109
- for (let i = 0; i < expr.length; i++) {
131
+ for (let i = 1; i < expr.length - 1; i++) {
110
132
  if (expr[i] === "(") parenDepth++;
111
133
  else if (expr[i] === ")") parenDepth--;
112
- if (parenDepth === 0 && i < expr.length - 1) {
134
+ if (parenDepth === 0) {
113
135
  matchedAll = false;
114
136
  break;
115
137
  }
116
138
  }
117
- if (matchedAll) {
118
- return parseXPath(expr.slice(1, -1), el, depth + 1);
119
- }
139
+ if (matchedAll) return parseXPath(expr.slice(1, -1), el, depth + 1);
120
140
  }
121
141
  const orParts = splitByOperator(expr, "or");
122
142
  if (orParts.length > 1) {
@@ -171,41 +191,36 @@ var ElementFinder = (() => {
171
191
  return parts;
172
192
  }
173
193
  function parseCondition(expr, el, depth = 0) {
174
- if (expr == null || el == null) {
175
- return false;
176
- }
194
+ if (expr == null || el == null) return false;
177
195
  expr = expr.trim();
178
- let match = expr.match(REGEX_PATTERNS.selfWithTag);
179
- if (match) {
180
- const tagName = match[1].toUpperCase();
196
+ const selfMatch = expr.match(REGEX_PATTERNS.selfWithTag);
197
+ if (selfMatch) {
198
+ const tagName = selfMatch[1].toUpperCase();
181
199
  if (el.tagName !== tagName) return false;
182
- if (match[2]) {
183
- return parseXPath(match[2], el, depth + 1);
184
- }
185
- return true;
200
+ return selfMatch[2] ? parseXPath(selfMatch[2], el, depth + 1) : true;
186
201
  }
187
- match = expr.match(REGEX_PATTERNS.contains);
188
- if (match) {
189
- const attr = el.getAttribute(match[1]) || "";
190
- return attr.toLowerCase().includes(match[2].toLowerCase());
202
+ const containsMatch = expr.match(REGEX_PATTERNS.contains);
203
+ if (containsMatch) {
204
+ const attr = el.getAttribute(containsMatch[1]) || "";
205
+ return attr.toLowerCase().includes(containsMatch[2].toLowerCase());
191
206
  }
192
- match = expr.match(REGEX_PATTERNS.attrEquals);
193
- if (match) {
194
- return el.getAttribute(match[1]) === match[2];
207
+ const attrEqualsMatch = expr.match(REGEX_PATTERNS.attrEquals);
208
+ if (attrEqualsMatch) {
209
+ return el.getAttribute(attrEqualsMatch[1]) === attrEqualsMatch[2];
195
210
  }
196
- match = expr.match(REGEX_PATTERNS.attrExists);
197
- if (match) {
198
- return el.hasAttribute(match[1]);
211
+ const attrExistsMatch = expr.match(REGEX_PATTERNS.attrExists);
212
+ if (attrExistsMatch) {
213
+ return el.hasAttribute(attrExistsMatch[1]);
199
214
  }
200
- match = expr.match(REGEX_PATTERNS.descendant);
201
- if (match) {
202
- return el.querySelector(match[1]) !== null;
215
+ const descendantMatch = expr.match(REGEX_PATTERNS.descendant);
216
+ if (descendantMatch) {
217
+ return el.querySelector(descendantMatch[1]) !== null;
203
218
  }
204
- match = expr.match(REGEX_PATTERNS.ancestor);
205
- if (match) {
219
+ const ancestorMatch = expr.match(REGEX_PATTERNS.ancestor);
220
+ if (ancestorMatch) {
206
221
  let parent = el.parentElement;
207
222
  while (parent) {
208
- if (parseXPath(match[1], parent, depth + 1)) return true;
223
+ if (parseXPath(ancestorMatch[1], parent, depth + 1)) return true;
209
224
  parent = parent.parentElement;
210
225
  }
211
226
  return false;
@@ -213,26 +228,39 @@ var ElementFinder = (() => {
213
228
  return false;
214
229
  }
215
230
  var ELEMENT_DEFINITIONS = Object.freeze(element_definitions_default);
216
- var SEARCHABLE_ATTRIBUTES = searchable_attributes_default;
217
- function setSearchableAttributes(attributes) {
218
- if (!Array.isArray(attributes)) {
219
- throw new TypeError("attributes must be an array");
231
+ function getDirectText(el) {
232
+ let text = "";
233
+ for (let i = 0; i < el.childNodes.length; i++) {
234
+ const node = el.childNodes[i];
235
+ if (node.nodeType === Node.TEXT_NODE) {
236
+ text += node.textContent;
237
+ }
220
238
  }
221
- SEARCHABLE_ATTRIBUTES = attributes;
222
- }
223
- function getSearchableAttributes() {
224
- return [...SEARCHABLE_ATTRIBUTES];
239
+ return text.trim();
225
240
  }
226
- function matchesType(el, type) {
227
- if (el == null) return false;
228
- const expr = ELEMENT_DEFINITIONS[type];
229
- return expr ? parseXPath(expr, el) : false;
241
+ function isInsideStyleOrScript(el) {
242
+ if (el.tagName === "STYLE" || el.tagName === "SCRIPT") {
243
+ return true;
244
+ }
245
+ if (el.querySelector("STYLE, SCRIPT")) {
246
+ return true;
247
+ }
248
+ let parent = el.parentElement;
249
+ while (parent) {
250
+ if (parent.tagName === "STYLE" || parent.tagName === "SCRIPT") {
251
+ return true;
252
+ }
253
+ parent = parent.parentElement;
254
+ }
255
+ return false;
230
256
  }
231
- function matchesContent(el, value, exact = false) {
257
+ function matchesAttribute(el, value, exact = false) {
232
258
  if (el == null) return false;
233
259
  if (value === void 0 || value === null || value === "") return true;
234
- const normalizedValue = value.toLowerCase().trim();
235
- for (const attr of SEARCHABLE_ATTRIBUTES) {
260
+ if (isInsideStyleOrScript(el)) return false;
261
+ const attrs = SEARCHABLE_ATTRIBUTES;
262
+ for (let i = 0; i < attrs.length; i++) {
263
+ const attr = attrs[i];
236
264
  let attrValue;
237
265
  try {
238
266
  attrValue = el.getAttribute(attr);
@@ -240,81 +268,59 @@ var ElementFinder = (() => {
240
268
  continue;
241
269
  }
242
270
  if (attrValue) {
243
- const normalized = attrValue.toLowerCase().trim();
244
- if (exact ? normalized === normalizedValue : normalized.includes(normalizedValue)) {
271
+ if (exact ? attrValue === value : attrValue.includes(value)) {
245
272
  return true;
246
273
  }
247
274
  }
248
275
  }
249
- const directText = Array.from(el.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.textContent).join("").toLowerCase().trim();
250
- if (exact ? directText === normalizedValue : directText.includes(normalizedValue)) {
276
+ const directText = getDirectText(el);
277
+ if (exact ? directText === value : directText.includes(value)) {
251
278
  return true;
252
279
  }
253
- const textContent = el.textContent.toLowerCase().trim();
254
- if (exact ? textContent === normalizedValue : textContent.includes(normalizedValue)) {
280
+ const textContent = el.textContent;
281
+ if (exact ? textContent.trim() === value : textContent.includes(value)) {
255
282
  return true;
256
283
  }
257
- if (el.tagName === "SELECT") {
258
- const options = el.querySelectorAll("option");
259
- for (const option of options) {
260
- const optionText = option.textContent.toLowerCase().trim();
261
- if (exact ? optionText === normalizedValue : optionText.includes(normalizedValue)) {
262
- return true;
263
- }
264
- }
265
- }
266
284
  return false;
267
285
  }
268
- function getBoundingBox(el) {
269
- const rect = el.getBoundingClientRect();
270
- return {
271
- x: rect.x,
272
- y: rect.y,
273
- width: rect.width,
274
- height: rect.height,
275
- top: rect.top,
276
- bottom: rect.bottom,
277
- left: rect.left,
278
- right: rect.right,
279
- midx: rect.x + rect.width / 2,
280
- midy: rect.y + rect.height / 2,
281
- tagName: el.tagName.toLowerCase()
282
- };
286
+ function matchesType(el, type) {
287
+ if (el == null) return false;
288
+ const matcher = TYPE_MATCHERS.get(type);
289
+ return matcher ? matcher(el) : false;
283
290
  }
284
291
  function getAllElements(root = document) {
285
292
  const elements = [];
293
+ if (root == null) return elements;
286
294
  const rootNode = root.nodeType === Node.DOCUMENT_NODE ? root.documentElement : root;
287
295
  if (!rootNode) return elements;
288
- const walker = (rootNode.ownerDocument || rootNode).createTreeWalker(
289
- rootNode,
290
- NodeFilter.SHOW_ELEMENT,
291
- {
292
- acceptNode: (node2) => {
293
- if (node2.tagName === "SCRIPT" || node2.tagName === "STYLE") {
294
- return NodeFilter.FILTER_REJECT;
295
- }
296
- return NodeFilter.FILTER_ACCEPT;
297
- }
298
- }
299
- );
300
- let node;
301
- while (node = walker.nextNode()) {
296
+ const stack = [rootNode];
297
+ while (stack.length > 0) {
298
+ const node = stack.pop();
299
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
300
+ if (node.tagName === "SCRIPT" || node.tagName === "STYLE") continue;
302
301
  elements.push(node);
302
+ const children = node.children;
303
+ for (let i = children.length - 1; i >= 0; i--) {
304
+ stack.push(children[i]);
305
+ }
303
306
  try {
304
307
  if (node.shadowRoot) {
305
- elements.push(...getAllElements(node.shadowRoot));
308
+ const shadowChildren = node.shadowRoot.children;
309
+ for (let i = shadowChildren.length - 1; i >= 0; i--) {
310
+ stack.push(shadowChildren[i]);
311
+ }
306
312
  }
307
313
  } catch (e) {
308
314
  }
309
315
  }
310
316
  return elements;
311
317
  }
312
- function getAllFrames(root = window, maxFrames = Infinity) {
318
+ function getAllFrames(root = window) {
313
319
  const frames = [];
314
320
  try {
315
321
  frames.push({ window: root, document: root.document, isMainFrame: true, frameIndex: -1 });
316
322
  const iframes = root.document.querySelectorAll("iframe");
317
- for (let i = 0; i < iframes.length && frames.length < maxFrames; i++) {
323
+ for (let i = 0; i < iframes.length; i++) {
318
324
  const iframe = iframes[i];
319
325
  try {
320
326
  if (iframe.contentWindow && iframe.contentDocument) {
@@ -339,69 +345,23 @@ var ElementFinder = (() => {
339
345
  }
340
346
  return frames;
341
347
  }
342
- function expandColumnMatches(matches, text, exact, includeHidden) {
343
- var _a;
344
- if (!text) return matches;
345
- const expandedMatches = [];
346
- const seenElements = /* @__PURE__ */ new Set();
347
- for (const match of matches) {
348
- const el = match.element;
349
- const frame = match.frame;
350
- if (!seenElements.has(el)) {
351
- expandedMatches.push(match);
352
- seenElements.add(el);
353
- }
354
- if (el.tagName.toLowerCase() === "th") {
355
- const table = el.closest("table");
356
- if (!table) continue;
357
- const headerRow = el.closest("tr");
358
- if (!headerRow) continue;
359
- const headerCells = Array.from(headerRow.children);
360
- const colPositions = [];
361
- let currentCol = 0;
362
- for (const cell of headerCells) {
363
- const colspan = parseInt(cell.getAttribute("colspan")) || 1;
364
- colPositions.push({ cell, colStart: currentCol, colEnd: currentCol + colspan - 1 });
365
- currentCol += colspan;
366
- }
367
- const headerInfo = colPositions.find((info) => info.cell === el);
368
- if (!headerInfo) continue;
369
- const allRows = table.querySelectorAll("tr");
370
- for (const row of allRows) {
371
- const cells = Array.from(row.children);
372
- const rowColMap = /* @__PURE__ */ new Map();
373
- let rowCol = 0;
374
- for (const cell of cells) {
375
- const colspan = parseInt(cell.getAttribute("colspan")) || 1;
376
- for (let i = 0; i < colspan; i++) {
377
- rowColMap.set(rowCol + i, cell);
378
- }
379
- rowCol += colspan;
380
- }
381
- for (let col = headerInfo.colStart; col <= headerInfo.colEnd; col++) {
382
- const cell = rowColMap.get(col);
383
- if (!cell) continue;
384
- if (seenElements.has(cell)) continue;
385
- if (!includeHidden) {
386
- const cellWindow = ((_a = cell.ownerDocument) == null ? void 0 : _a.defaultView) || frame.window;
387
- const style = cellWindow.getComputedStyle(cell);
388
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
389
- continue;
390
- }
391
- if (cell.offsetWidth === 0 || cell.offsetHeight === 0) {
392
- continue;
393
- }
394
- }
395
- expandedMatches.push({ element: cell, frame });
396
- seenElements.add(cell);
397
- }
398
- }
399
- }
400
- }
401
- return expandedMatches;
348
+ function getBoundingBox(el) {
349
+ const rect = el.getBoundingClientRect();
350
+ return {
351
+ x: rect.x,
352
+ y: rect.y,
353
+ width: rect.width,
354
+ height: rect.height,
355
+ top: rect.top,
356
+ bottom: rect.bottom,
357
+ left: rect.left,
358
+ right: rect.right,
359
+ midx: rect.x + rect.width / 2,
360
+ midy: rect.y + rect.height / 2,
361
+ tagName: el.tagName.toLowerCase()
362
+ };
402
363
  }
403
- function findElement(type = "element", text = null, exact = false, includeHidden = false, parent = null, maxFrames = Infinity) {
404
- var _a;
364
+ function findElementByType(type = "element", parent = null) {
405
365
  if (type === null || type === void 0) {
406
366
  type = "element";
407
367
  }
@@ -413,22 +373,12 @@ var ElementFinder = (() => {
413
373
  return { elements: [] };
414
374
  }
415
375
  const matches = [];
416
- const frames = getAllFrames(window, maxFrames);
376
+ const frames = getAllFrames(window);
417
377
  for (const frame of frames) {
418
378
  const allElements = getAllElements(parent || frame.document);
419
- for (const el of allElements) {
379
+ for (let i = 0; i < allElements.length; i++) {
380
+ const el = allElements[i];
420
381
  if (type && !matchesType(el, type)) continue;
421
- if (text !== void 0 && !matchesContent(el, text, exact)) continue;
422
- if (!includeHidden) {
423
- const elWindow = ((_a = el.ownerDocument) == null ? void 0 : _a.defaultView) || frame.window;
424
- const style = elWindow.getComputedStyle(el);
425
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
426
- continue;
427
- }
428
- if (el.offsetWidth === 0 || el.offsetHeight === 0) {
429
- continue;
430
- }
431
- }
432
382
  matches.push({ element: el, frame });
433
383
  }
434
384
  }
@@ -441,18 +391,274 @@ var ElementFinder = (() => {
441
391
  const el = match.element;
442
392
  if (!excludedElements.has(el)) {
443
393
  innermostMatches.unshift(match);
444
- let parent2 = el.parentElement;
445
- while (parent2) {
446
- if (matchedElements.has(parent2)) {
447
- excludedElements.add(parent2);
394
+ let parentEl = el.parentElement;
395
+ while (parentEl) {
396
+ if (matchedElements.has(parentEl)) {
397
+ excludedElements.add(parentEl);
448
398
  }
449
- parent2 = parent2.parentElement;
399
+ parentEl = parentEl.parentElement;
450
400
  }
451
401
  }
452
402
  }
453
403
  }
454
- const expandedMatches = expandColumnMatches(innermostMatches, text, exact, includeHidden);
455
- const qualified = expandedMatches.map((item) => {
404
+ const qualified = innermostMatches.map((item) => {
405
+ const boundingBox = getBoundingBox(item.element);
406
+ const tagName = item.element.tagName.toLowerCase();
407
+ if (!item.frame.isMainFrame) {
408
+ return {
409
+ boundingBox,
410
+ tagName,
411
+ frameIndex: item.frame.frameIndex
412
+ };
413
+ }
414
+ return {
415
+ element: item.element,
416
+ boundingBox,
417
+ tagName,
418
+ frameIndex: item.frame.frameIndex
419
+ };
420
+ });
421
+ return { elements: qualified };
422
+ }
423
+ function findElementByAttributes(value, exact = false, parent = null) {
424
+ if (value === null || value === void 0) {
425
+ value = "";
426
+ }
427
+ if (typeof value !== "string") {
428
+ throw new TypeError(`value must be a string, got ${typeof value}`);
429
+ }
430
+ const matches = [];
431
+ const frames = getAllFrames(window);
432
+ for (const frame of frames) {
433
+ const allElements = getAllElements(parent || frame.document);
434
+ for (let i = 0; i < allElements.length; i++) {
435
+ const el = allElements[i];
436
+ if (!matchesAttribute(el, value, exact)) continue;
437
+ matches.push({ element: el, frame });
438
+ }
439
+ }
440
+ const filteredMatches = matches.filter((item) => {
441
+ const el = item.element;
442
+ const hasDirectMatch = hasOwnMatch(el, value, exact);
443
+ if (hasDirectMatch) return true;
444
+ for (const other of matches) {
445
+ if (other.element !== el && el.contains(other.element)) {
446
+ return false;
447
+ }
448
+ }
449
+ return true;
450
+ });
451
+ const qualified = filteredMatches.map((item) => {
452
+ const boundingBox = getBoundingBox(item.element);
453
+ const tagName = item.element.tagName.toLowerCase();
454
+ if (!item.frame.isMainFrame) {
455
+ return {
456
+ boundingBox,
457
+ tagName,
458
+ frameIndex: item.frame.frameIndex
459
+ };
460
+ }
461
+ return {
462
+ element: item.element,
463
+ boundingBox,
464
+ tagName,
465
+ frameIndex: item.frame.frameIndex
466
+ };
467
+ });
468
+ return { elements: qualified };
469
+ }
470
+ function hasOwnMatch(el, value, exact = false) {
471
+ if (value === void 0 || value === null || value === "") return true;
472
+ const attrs = SEARCHABLE_ATTRIBUTES;
473
+ for (let i = 0; i < attrs.length; i++) {
474
+ const attr = attrs[i];
475
+ let attrValue;
476
+ try {
477
+ attrValue = el.getAttribute(attr);
478
+ } catch (e) {
479
+ continue;
480
+ }
481
+ if (attrValue) {
482
+ if (exact ? attrValue === value : attrValue.includes(value)) {
483
+ return true;
484
+ }
485
+ }
486
+ }
487
+ const directText = getDirectText(el);
488
+ if (exact ? directText === value : directText.includes(value)) {
489
+ return true;
490
+ }
491
+ return false;
492
+ }
493
+ function findElements(type = null, text = null, exact = false, parent = null) {
494
+ if (text === null || text === void 0) {
495
+ text = "";
496
+ }
497
+ if (type !== null && type !== void 0) {
498
+ if (typeof type !== "string") {
499
+ throw new TypeError(`type must be a string, got ${typeof type}`);
500
+ }
501
+ if (!ELEMENT_DEFINITIONS[type]) {
502
+ console.warn(`Unknown element type: ${type}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`);
503
+ return { elements: [] };
504
+ }
505
+ }
506
+ if (text !== "" && typeof text !== "string") {
507
+ throw new TypeError(`text must be a string, got ${typeof text}`);
508
+ }
509
+ const matches = [];
510
+ const frames = getAllFrames(window);
511
+ for (const frame of frames) {
512
+ const allElements = getAllElements(parent || frame.document);
513
+ for (let i = 0; i < allElements.length; i++) {
514
+ const el = allElements[i];
515
+ if (type !== null && type !== void 0 && !matchesType(el, type)) continue;
516
+ if (text !== "" && !matchesAttribute(el, text, exact)) continue;
517
+ matches.push({ element: el, frame });
518
+ }
519
+ }
520
+ const filteredMatches = text !== "" ? matches.filter((item) => {
521
+ const el = item.element;
522
+ const hasDirectMatch = hasOwnMatch(el, text, exact);
523
+ if (hasDirectMatch) return true;
524
+ for (const other of matches) {
525
+ if (other.element !== el && el.contains(other.element)) {
526
+ return false;
527
+ }
528
+ }
529
+ return true;
530
+ }) : matches;
531
+ const qualified = filteredMatches.map((item) => {
532
+ const boundingBox = getBoundingBox(item.element);
533
+ const tagName = item.element.tagName.toLowerCase();
534
+ if (!item.frame.isMainFrame) {
535
+ return {
536
+ boundingBox,
537
+ tagName,
538
+ frameIndex: item.frame.frameIndex
539
+ };
540
+ }
541
+ return {
542
+ element: item.element,
543
+ boundingBox,
544
+ tagName,
545
+ frameIndex: item.frame.frameIndex
546
+ };
547
+ });
548
+ return { elements: qualified };
549
+ }
550
+ function getParentElement(el) {
551
+ if (el.parentElement) {
552
+ return el.parentElement;
553
+ }
554
+ try {
555
+ const rootNode = el.getRootNode();
556
+ if (rootNode && rootNode.host) {
557
+ return rootNode.host;
558
+ }
559
+ } catch (e) {
560
+ }
561
+ return null;
562
+ }
563
+ function getSiblingElements(el) {
564
+ const parent = getParentElement(el);
565
+ if (!parent) return [];
566
+ if (parent.shadowRoot) {
567
+ try {
568
+ return Array.from(parent.shadowRoot.children);
569
+ } catch (e) {
570
+ return [];
571
+ }
572
+ }
573
+ return Array.from(parent.children);
574
+ }
575
+ function findNearbyElementType(el, targetType) {
576
+ let parent = getParentElement(el);
577
+ while (parent) {
578
+ if (matchesType(parent, targetType)) {
579
+ return parent;
580
+ }
581
+ parent = getParentElement(parent);
582
+ }
583
+ const immediateChildren = el.children || [];
584
+ for (const child of immediateChildren) {
585
+ if (matchesType(child, targetType)) {
586
+ return child;
587
+ }
588
+ }
589
+ const siblings = getSiblingElements(el);
590
+ for (const sibling of siblings) {
591
+ if (sibling !== el && matchesType(sibling, targetType)) {
592
+ return sibling;
593
+ }
594
+ }
595
+ return null;
596
+ }
597
+ function findProbableElements(elementType, attributeText, exact = false, parent = null) {
598
+ const hasType = elementType !== null && elementType !== void 0 && elementType !== "";
599
+ const hasText = attributeText !== null && attributeText !== void 0 && attributeText !== "";
600
+ if (hasType && !hasText) {
601
+ return findElementByType(elementType, parent);
602
+ }
603
+ if (!hasType && hasText) {
604
+ return findElementByAttributes(attributeText, exact, parent);
605
+ }
606
+ if (hasType) {
607
+ if (typeof elementType !== "string") {
608
+ throw new TypeError(`elementType must be a string, got ${typeof elementType}`);
609
+ }
610
+ if (!ELEMENT_DEFINITIONS[elementType]) {
611
+ console.warn(`Unknown element type: ${elementType}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`);
612
+ return { elements: [] };
613
+ }
614
+ }
615
+ if (hasText) {
616
+ if (typeof attributeText !== "string") {
617
+ throw new TypeError(`attributeText must be a string, got ${typeof attributeText}`);
618
+ }
619
+ }
620
+ const matches = [];
621
+ const frames = getAllFrames(window);
622
+ for (const frame of frames) {
623
+ const allElements = getAllElements(parent || frame.document);
624
+ for (let i = 0; i < allElements.length; i++) {
625
+ const el = allElements[i];
626
+ if (hasType && !matchesType(el, elementType)) continue;
627
+ if (hasText && !matchesAttribute(el, attributeText, exact)) continue;
628
+ matches.push({ element: el, frame });
629
+ }
630
+ }
631
+ if (matches.length === 0 && hasType && hasText) {
632
+ const attributeMatches = [];
633
+ for (const frame of frames) {
634
+ const allElements = getAllElements(parent || frame.document);
635
+ for (let i = 0; i < allElements.length; i++) {
636
+ const el = allElements[i];
637
+ if (!matchesAttribute(el, attributeText, exact)) continue;
638
+ attributeMatches.push({ element: el, frame });
639
+ }
640
+ }
641
+ const foundElements = /* @__PURE__ */ new Set();
642
+ for (const match of attributeMatches) {
643
+ const nearbyElement = findNearbyElementType(match.element, elementType);
644
+ if (nearbyElement && !foundElements.has(nearbyElement)) {
645
+ foundElements.add(nearbyElement);
646
+ matches.push({ element: nearbyElement, frame: match.frame });
647
+ }
648
+ }
649
+ }
650
+ const filteredMatches = hasText ? matches.filter((item) => {
651
+ const el = item.element;
652
+ const hasDirectMatch = hasOwnMatch(el, attributeText, exact);
653
+ if (hasDirectMatch) return true;
654
+ for (const other of matches) {
655
+ if (other.element !== el && el.contains(other.element)) {
656
+ return false;
657
+ }
658
+ }
659
+ return true;
660
+ }) : matches;
661
+ const qualified = filteredMatches.map((item) => {
456
662
  const boundingBox = getBoundingBox(item.element);
457
663
  const tagName = item.element.tagName.toLowerCase();
458
664
  if (!item.frame.isMainFrame) {
@@ -480,7 +686,8 @@ var ElementFinder = (() => {
480
686
  }
481
687
  function highlight(elements, color = "red", width = 3) {
482
688
  const items = extractElements(elements);
483
- items.forEach((item) => {
689
+ for (let i = 0; i < items.length; i++) {
690
+ const item = items[i];
484
691
  const el = item.element ? item.element : item;
485
692
  if (el && el.style) {
486
693
  el.style.outline = `${width}px solid ${color}`;
@@ -488,11 +695,12 @@ var ElementFinder = (() => {
488
695
  el.style.boxShadow = `0 0 0 2px rgba(255, 255, 255, 0.8)`;
489
696
  el.classList.add("elementfinder-highlighted");
490
697
  }
491
- });
698
+ }
492
699
  }
493
700
  function unhighlight(elements) {
494
701
  const items = extractElements(elements);
495
- items.forEach((item) => {
702
+ for (let i = 0; i < items.length; i++) {
703
+ const item = items[i];
496
704
  const el = item.element ? item.element : item;
497
705
  if (el && el.style) {
498
706
  el.style.outline = "";
@@ -500,10 +708,13 @@ var ElementFinder = (() => {
500
708
  el.style.boxShadow = "";
501
709
  el.classList.remove("elementfinder-highlighted");
502
710
  }
503
- });
711
+ }
504
712
  }
505
713
  function getValidTypes() {
506
714
  return Object.keys(ELEMENT_DEFINITIONS);
507
715
  }
716
+ function getValidAttributes() {
717
+ return [...SEARCHABLE_ATTRIBUTES];
718
+ }
508
719
  return __toCommonJS(element_finder_exports);
509
720
  })();