@nodebug/browser-element-finder 1.1.6 → 1.1.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.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @nodebug/browser-element-finder
2
2
 
3
+ **Version**: 1.1.8
4
+
3
5
  **A robust, agent-friendly JavaScript library for identifying DOM elements by type and/or text content, with full support for shadow DOM, iframes, and automation workflows.**
4
6
 
5
7
  ---
@@ -52,7 +54,7 @@ results.elements.forEach((e) => {
52
54
  - **Text content search**: Search within element text, attributes, and placeholders
53
55
  - **Shadow DOM support**: Automatically traverses shadow roots to find nested elements
54
56
  - **Iframe support**: Automatically searches all frames (main document + iframes) by default
55
- - **Visibility detection**: All elements returned with `isVisible` property (`true`/`false`)
57
+ - **Visibility detection**: All elements returned with `isHidden` property (`true`/`false`)
56
58
  - **Bounding box data**: Returns position and dimensions for each found element
57
59
  - **XPath-like type definitions**: Extensible element type matching using XPath-like expressions
58
60
  - **Optimized performance**: Pre-compiled type matchers, O(n) innermost element filtering, and efficient Set-based lookups
@@ -92,14 +94,14 @@ browser-element-finder/
92
94
 
93
95
  ```js
94
96
  // Find all elements (visible and hidden)
95
- const results = ElementFinder.findElement('button')
97
+ const results = ElementFinder.findElements('button')
96
98
  // Find by text
97
- const results = ElementFinder.findElement('button', 'Submit')
99
+ const results = ElementFinder.findElements('button', 'Submit')
98
100
  // Find by text only
99
- const results = ElementFinder.findElement(null, 'seleniumbase')
101
+ const results = ElementFinder.findElements(null, 'seleniumbase')
100
102
  // Check visibility of found elements
101
103
  results.elements.forEach((e) => {
102
- console.log('Visible:', e.isVisible)
104
+ console.log('Hidden:', e.isHidden)
103
105
  })
104
106
  ```
105
107
 
@@ -113,7 +115,7 @@ The library automatically searches all frames (main + iframes). For agent/automa
113
115
  **Example**:
114
116
 
115
117
  ```js
116
- const results = ElementFinder.findElement('button')
118
+ const results = ElementFinder.findElements('button')
117
119
  for (const item of results.elements) {
118
120
  if (item.frameIndex === -1 && item.element) {
119
121
  // Interact directly
@@ -125,6 +127,50 @@ for (const item of results.elements) {
125
127
  }
126
128
  ```
127
129
 
130
+ ### Customizing Searchable Attributes
131
+
132
+ You can customize which attributes the library searches for text (e.g., adding a custom `data-test-id` or removing `placeholder`).
133
+
134
+ ```js
135
+ // Get current attributes
136
+ const currentAttrs = ElementFinder.getSearchableAttributes()
137
+
138
+ // Set new priority list
139
+ ElementFinder.setSearchableAttributes([
140
+ 'id',
141
+ 'name',
142
+ 'data-testid',
143
+ 'placeholder',
144
+ ])
145
+ ```
146
+
147
+ ### Pausing Animations for Screenshots
148
+
149
+ When taking screenshots or performing visual assertions, animations can cause flaky tests. Use `pauseAnimations()` and `resumeAnimations()` to freeze and restore animations:
150
+
151
+ ```js
152
+ // Pause all CSS animations and transitions
153
+ const pauseState = ElementFinder.pauseAnimations()
154
+
155
+ // Take screenshot or perform visual assertions
156
+ // ... screenshot code here ...
157
+
158
+ // Resume animations
159
+ ElementFinder.resumeAnimations(pauseState)
160
+ ```
161
+
162
+ For Selenium WebDriver tests, call the functions directly in the browser context:
163
+
164
+ ```js
165
+ // Pause animations (state is stored internally in browser)
166
+ await driver.executeScript('return ElementFinder.pauseAnimations()')
167
+
168
+ // ... take screenshot ...
169
+
170
+ // Resume animations (pops from internal stack - no argument needed)
171
+ await driver.executeScript('ElementFinder.resumeAnimations()')
172
+ ```
173
+
128
174
  ### Accessing Element Definitions and Searchable Attributes
129
175
 
130
176
  The package exports JSON files containing element type definitions and searchable attributes:
@@ -151,24 +197,28 @@ const SEARCHABLE_ATTRIBUTES = require('@nodebug/browser-element-finder/searchabl
151
197
 
152
198
  ## API Summary
153
199
 
154
- | Function | Description |
155
- | ------------------------------------------------- | ----------------------------------------------------------------------------- |
156
- | `findElements(type, text, exact, parent)` | Find elements by type/text, returns `{ elements: [...] }` |
157
- | `findElementByType(type, parent)` | Find elements by type only, returns `{ elements: [...] }` |
158
- | `findElementByAttributes(value, exact, parent)` | Find elements by text/attribute, returns `{ elements: [...] }` |
159
- | `findProbableElements(type, text, exact, parent)` | Find elements with fallback to nearby elements, returns `{ elements: [...] }` |
160
- | `highlight(elements, color, width)` | Highlight elements with outline |
161
- | `unhighlight(elements)` | Remove highlight |
162
- | `getValidTypes()` | List all supported element types |
163
- | `getBoundingBox(element)` | Get bounding box for an element |
164
- | `setSearchableAttributes(attributes)` | Set custom attributes for text search |
165
- | `getSearchableAttributes()` | Get current searchable attributes |
166
- | `matchesType(el, type)` | Check if element matches a type |
167
- | `matchesAttribute(el, value, exact)` | Check if element matches text/attribute |
168
- | `getAllElements(root)` | Get all elements (with shadow DOM) |
169
- | `getAllFrames(root)` | Get all frames (main + iframes) |
170
- | `parseXPath(expr, el, depth)` | Parse XPath-like type expressions |
171
- | `splitByOperator(expr, op)` | Split XPath by operator |
200
+ | Function | Description |
201
+ | ------------------------------------------------- | ------------------------------------------------------------------------------ |
202
+ | `findElements(type, text, exact, parent)` | Find elements by type/text, returns `{ elements: [...] }` |
203
+ | `findElementsByType(type, parent)` | Find elements by type only, returns `{ elements: [...] }` |
204
+ | `findElementsByAttribute(value, exact, parent)` | Find elements by text/attribute, returns `{ elements: [...] }` |
205
+ | `findProbableElements(type, text, exact, parent)` | Find elements with fallback to nearby elements, returns `{ elements: [...] }` |
206
+ | `highlight(elements, color, width)` | Highlight elements with outline |
207
+ | `unhighlight(elements)` | Remove highlight |
208
+ | `pauseAnimations()` | Pause all CSS animations and transitions, returns state object |
209
+ | `resumeAnimations(state)` | Resume animations using state from `pauseAnimations()` |
210
+ | `getValidTypes()` | List all supported element types |
211
+ | `getValidAttributes()` | List all valid searchable attribute names |
212
+ | `getBoundingBox(element)` | Get bounding box for an element |
213
+ | `setSearchableAttributes(attributes)` | Set custom attributes for text search |
214
+ | `getSearchableAttributes()` | Get current searchable attributes |
215
+ | `matchesType(el, type)` | Check if element matches a type |
216
+ | `matchesAttribute(el, value, exact)` | Check if element matches text/attribute |
217
+ | `getAllElements(root)` | Get all elements (with shadow DOM) |
218
+ | `getAllFrames(root)` | Get all frames (main + iframes) |
219
+ | `parseXPath(expr, el, depth)` | Parse XPath-like type expressions |
220
+ | `splitByOperator(expr, op)` | Split XPath by operator |
221
+ | `isHidden(el)` | Check if element is hidden (display:none, visibility:hidden, hidden attribute) |
172
222
 
173
223
  ---
174
224
 
@@ -183,10 +233,11 @@ Finds elements matching the specified type and/or text. Combines type and attrib
183
233
  | `exact` | `boolean` | `false` | Exact text match vs substring (only used when text is provided) |
184
234
  | `parent` | `Element` | `null` | Parent element to search within |
185
235
 
186
- **Returns**: `{ elements: [{ element, boundingBox, tagName, frameIndex }] }`
236
+ **Returns**: `{ elements: [{ element, boundingBox, tagName, frameIndex, isHidden }] }`
187
237
 
188
238
  - `element`: Raw DOM element (main frame only; for iframes, use `frameIndex` and re-query after switching context)
189
239
  - `frameIndex`: `-1` for main frame, `0, 1, 2...` for iframes
240
+ - `isHidden`: `true` if element is hidden (display:none, visibility:hidden, hidden attribute, or zero dimensions)
190
241
 
191
242
  **Agent/Automation Note**: Iframe elements cannot be interacted with directly. Use `frameIndex` to switch context, then re-run `findElements` inside the iframe.
192
243
 
@@ -203,12 +254,12 @@ Finds elements matching the specified type with intelligent fallback to nearby e
203
254
  | `exact` | `boolean` | `false` | Exact text match vs substring (only used when text is provided) |
204
255
  | `parent` | `Element` | `null` | Parent element to search within |
205
256
 
206
- **Returns**: `{ elements: [{ element, boundingBox, tagName, frameIndex }] }`
257
+ **Returns**: `{ elements: [{ element, boundingBox, tagName, frameIndex, isHidden }] }`
207
258
 
208
259
  **Behavior**:
209
260
 
210
- - If only `type` is provided: delegates to `findElementByType(type, parent)`
211
- - If only `text` is provided: delegates to `findElementByAttributes(text, exact, parent)`
261
+ - If only `type` is provided: delegates to `findElementsByType(type, parent)`
262
+ - If only `text` is provided: delegates to `findElementsByAttribute(text, exact, parent)`
212
263
  - If both are provided: attempts direct match, then falls back to nearby elements
213
264
 
214
265
  **Fallback Strategy**: When no element matches both type and text directly, searches for nearby elements in this order:
@@ -220,11 +271,11 @@ Finds elements matching the specified type with intelligent fallback to nearby e
220
271
  **Example**:
221
272
 
222
273
  ```javascript
223
- // Type-only search (delegates to findElementByType)
274
+ // Type-only search (delegates to findElementsByType)
224
275
  const result1 = ElementFinder.findProbableElements('button')
225
276
  // Returns all buttons on the page
226
277
 
227
- // Text-only search (delegates to findElementByAttributes)
278
+ // Text-only search (delegates to findElementsByAttribute)
228
279
  const result2 = ElementFinder.findProbableElements(null, 'Submit')
229
280
  // Returns all elements containing "Submit"
230
281
 
@@ -265,6 +316,10 @@ Removes highlighting from elements.
265
316
 
266
317
  Returns an array of all valid element type names.
267
318
 
319
+ ### `getValidAttributes()`
320
+
321
+ Returns an array of all valid searchable attribute names (same as `getSearchableAttributes()`).
322
+
268
323
  ### `getBoundingBox(element)`
269
324
 
270
325
  Returns the bounding box for an element.
@@ -285,7 +340,7 @@ Checks if an element matches the specified type definition.
285
340
 
286
341
  Checks if an element matches the specified text/attribute value. Safely handles edge case elements that may throw errors on attribute access.
287
342
 
288
- ### `findElementByType(type, parent)`
343
+ ### `findElementsByType(type, parent)`
289
344
 
290
345
  Finds elements by type only. Searches all frames by default.
291
346
 
@@ -294,7 +349,7 @@ Finds elements by type only. Searches all frames by default.
294
349
  | `type` | `string` | `"element"` | Element type (see supported types below). Throws `TypeError` for non-string values. |
295
350
  | `parent` | `Element` | `null` | Parent element to search within |
296
351
 
297
- ### `findElementByAttributes(value, exact, parent)`
352
+ ### `findElementsByAttribute(value, exact, parent)`
298
353
 
299
354
  Finds elements by text/attribute value only. Searches all frames by default.
300
355
 
@@ -329,7 +384,7 @@ The library automatically searches all frames (main + iframes) by default. Howev
329
384
  ### Iframe Element Limitations
330
385
 
331
386
  ```javascript
332
- const results = ElementFinder.findElement('button')
387
+ const results = ElementFinder.findElements('button')
333
388
 
334
389
  results.elements.forEach((item) => {
335
390
  if (item.frameIndex === -1) {
@@ -352,7 +407,7 @@ To interact with elements inside an iframe, you must switch the Selenium driver
352
407
  ```javascript
353
408
  // Find iframe elements
354
409
  const results = await driver.executeScript(`
355
- return ElementFinder.findElement('button');
410
+ return ElementFinder.findElements('button');
356
411
  `)
357
412
 
358
413
  // Switch to iframe and interact
@@ -363,7 +418,7 @@ if (iframeElements.length > 0) {
363
418
 
364
419
  // Now find and interact with elements in the iframe
365
420
  const iframeResults = await driver.executeScript(`
366
- return ElementFinder.findElement('button');
421
+ return ElementFinder.findElements('button');
367
422
  `)
368
423
  // These elements will have the element property since we're in the iframe context
369
424
  }
@@ -373,33 +428,33 @@ if (iframeElements.length > 0) {
373
428
 
374
429
  ## Supported Element Types
375
430
 
376
- | Type | Description |
377
- | ------------- | ------------------------------------------------------- |
378
- | `button` | `<button>`, `[role="button"]`, `[type="button"]` |
379
- | `checkbox` | `<input type="checkbox">`, `[role="checkbox"]` |
380
- | `switch` | Toggle switches, checkboxes with switch role |
381
- | `slider` | `<input type="range">`, `[role="slider"]` |
382
- | `datepicker` | `<input type="date">` |
383
- | `colorpicker` | `<input type="color">` |
384
- | `radio` | `<input type="radio">`, `[role="radio"]` |
385
- | `dropdown` | `<select>`, `[role="combobox"]`, `[role="listbox"]` |
386
- | `textbox` | `<input>`, `<textarea>`, `[role="textbox"]` |
387
- | `link` | `<a>`, `[role="link"]`, `[href]` |
388
- | `heading` | `<h1>-<h6>`, `[role="heading"]` |
389
- | `navigation` | `<nav>`, `[role="navigation"]` |
390
- | `list` | `<ul>`, `<ol>`, `[role="list"]` |
391
- | `listitem` | `<li>`, `[role="listitem"]` |
392
- | `menu` | `<menu>`, `[role="menu"]` |
393
- | `menuitem` | `[role="menuitem"]` |
394
- | `toolbar` | `[role="toolbar"]` |
395
- | `dialog` | `[role="dialog"]` |
396
- | `table` | `<table>`, `[role="table"]` |
397
- | `row` | `<tr>`, `[role="row"]` |
398
- | `column` | `<td>`, `<th>`, `[role="cell"]` |
399
- | `cell` | `<td>`, `[role="cell"]` (data cells only, no expansion) |
400
- | `image` | `<img>`, `[role="img"]` |
401
- | `file` | `<input type="file"]` |
402
- | `element` | Matches all elements |
431
+ | Type | Description |
432
+ | ------------- | ---------------------------------------------------------------------------------------------------- |
433
+ | `button` | `<button>`, `[role="button"]`, `[type="button"]`, `[type="submit"]` |
434
+ | `checkbox` | `<input type="checkbox">`, `[role="checkbox"]` |
435
+ | `switch` | Toggle switches, checkboxes with switch role, buttons with `class="switch"` or `data-state` |
436
+ | `slider` | `<input type="range">`, `[role="slider"]` |
437
+ | `datepicker` | `<input type="date">`, `[role="date"]` |
438
+ | `colorpicker` | `<input type="color">`, `[role="color"]` |
439
+ | `radio` | `<input type="radio">`, `[role="radio"]` |
440
+ | `dropdown` | `<select>`, `[role="combobox"]`, `[role="listbox"]`, class-based dropdown/trigger, ancestor matching |
441
+ | `textbox` | `<textarea>`, `<input>` (text/password/search/email/number/tel/url), `[role="textbox"]` |
442
+ | `link` | `<a>`, `[role="link"]`, `[href]` |
443
+ | `heading` | `<h1>-<h6>`, `[role="heading"]` |
444
+ | `navigation` | `<nav>`, `[role="navigation"]` |
445
+ | `list` | `<ul>`, `<ol>`, `[role="list"]` |
446
+ | `listitem` | `<li>`, `[role="listitem"]` |
447
+ | `menu` | `<menu>`, `[role="menu"]` |
448
+ | `menuitem` | `[role="menuitem"]` |
449
+ | `toolbar` | `[role="toolbar"]` |
450
+ | `dialog` | `[role="dialog"]`, `[role="alertdialog"]` |
451
+ | `table` | `<table>`, `[role="table"]` |
452
+ | `row` | `<tr>`, `[role="row"]` |
453
+ | `column` | `<td>`, `<th>`, `[role="cell"]`, `[role="gridcell"]`, `[role="columnheader"]` |
454
+ | `cell` | `<td>`, `[role="cell"]`, `[role="gridcell"]` (data cells only, no expansion) |
455
+ | `image` | `<img>`, `[role="img"]`, `[alt]` |
456
+ | `file` | `<input type="file">` |
457
+ | `element` | Matches all elements |
403
458
 
404
459
  ---
405
460
 
@@ -438,8 +493,8 @@ const headerCell = ElementFinder.findElement('cell', 'City')
438
493
 
439
494
  By default, the library searches these attributes (in priority order):
440
495
 
441
- - `placeholder`, `value`, `data-test-id`, `data-testid`, `id`
442
- - `resource-id`, `name`, `aria-label`, `class`, `hint`
496
+ - `placeholder`, `value`, `data-value`, `data-test-id`, `data-testid`, `id`
497
+ - `resource-id`, `name`, `aria-label`, `hint`
443
498
  - `title`, `tooltip`, `alt`, `src`, `aria-labelledby`
444
499
 
445
500
  ---
package/index.js CHANGED
@@ -21,9 +21,9 @@ var ElementFinder = (() => {
21
21
  var element_finder_exports = {};
22
22
  __export(element_finder_exports, {
23
23
  ELEMENT_DEFINITIONS: () => ELEMENT_DEFINITIONS,
24
- findElementByAttributes: () => findElementByAttributes,
25
- findElementByType: () => findElementByType,
26
24
  findElements: () => findElements,
25
+ findElementsByAttribute: () => findElementsByAttribute,
26
+ findElementsByType: () => findElementsByType,
27
27
  findProbableElements: () => findProbableElements,
28
28
  getAllElements: () => getAllElements,
29
29
  getAllFrames: () => getAllFrames,
@@ -32,10 +32,13 @@ var ElementFinder = (() => {
32
32
  getValidAttributes: () => getValidAttributes,
33
33
  getValidTypes: () => getValidTypes,
34
34
  highlight: () => highlight,
35
+ isHidden: () => isHidden,
35
36
  matchesAttribute: () => matchesAttribute,
36
37
  matchesType: () => matchesType,
37
38
  parseCondition: () => parseCondition,
38
39
  parseXPath: () => parseXPath,
40
+ pauseAnimations: () => pauseAnimations,
41
+ resumeAnimations: () => resumeAnimations,
39
42
  setSearchableAttributes: () => setSearchableAttributes,
40
43
  splitByOperator: () => splitByOperator,
41
44
  unhighlight: () => unhighlight
@@ -50,8 +53,8 @@ var ElementFinder = (() => {
50
53
  checkbox: "(self::input and @type='checkbox') or @role='checkbox'",
51
54
  switch: "(self::input and @type='checkbox') or @role='switch' or (self::button and (contains(@class, 'switch') or @data-state))",
52
55
  slider: "self::input[@type='range'] or @role='slider'",
53
- datepicker: "self::input and @type='date'",
54
- colorpicker: "self::input and @type='color'",
56
+ datepicker: "self::input[@type='date'] or @role='date'",
57
+ colorpicker: "self::input[@type='color'] or @role='color'",
55
58
  radio: "(self::input and @type='radio') or @role='radio'",
56
59
  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'])",
57
60
  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'",
@@ -61,7 +64,7 @@ var ElementFinder = (() => {
61
64
  menu: "self::menu or @role='menu'",
62
65
  menuitem: "@role='menuitem'",
63
66
  toolbar: "@role='toolbar'",
64
- dialog: "@role='dialog'",
67
+ dialog: "@role='dialog' or @role='alertdialog'",
65
68
  table: "self::table or @role='table'",
66
69
  row: "self::tr or @role='row'",
67
70
  column: "self::td or self::th or @role='cell' or @role='gridcell' or @role='columnheader'",
@@ -74,6 +77,7 @@ var ElementFinder = (() => {
74
77
  var searchable_attributes_default = [
75
78
  "placeholder",
76
79
  "value",
80
+ "data-value",
77
81
  "data-test-id",
78
82
  "data-testid",
79
83
  "id",
@@ -387,7 +391,27 @@ var ElementFinder = (() => {
387
391
  tagName: el.tagName.toLowerCase()
388
392
  };
389
393
  }
390
- function findElementByType(type = "element", parent = null) {
394
+ function isHidden(el) {
395
+ if (el == null) return true;
396
+ if (el.offsetWidth === 0 && el.offsetHeight === 0) {
397
+ return true;
398
+ }
399
+ try {
400
+ const style = window.getComputedStyle(el);
401
+ if (style.visibility === "hidden" || style.visibility === "collapse") {
402
+ return true;
403
+ }
404
+ if (style.display === "none") {
405
+ return true;
406
+ }
407
+ } catch (e) {
408
+ }
409
+ if (el.hasAttribute("hidden")) {
410
+ return true;
411
+ }
412
+ return false;
413
+ }
414
+ function findElementsByType(type = "element", parent = null) {
391
415
  if (type === null || type === void 0) {
392
416
  type = "element";
393
417
  }
@@ -430,23 +454,26 @@ var ElementFinder = (() => {
430
454
  const qualified = innermostMatches.map((item) => {
431
455
  const boundingBox = getBoundingBox(item.element);
432
456
  const tagName = item.element.tagName.toLowerCase();
457
+ const hidden = isHidden(item.element);
433
458
  if (!item.frame.isMainFrame) {
434
459
  return {
435
460
  boundingBox,
436
461
  tagName,
437
- frameIndex: item.frame.frameIndex
462
+ frameIndex: item.frame.frameIndex,
463
+ isHidden: hidden
438
464
  };
439
465
  }
440
466
  return {
441
467
  element: item.element,
442
468
  boundingBox,
443
469
  tagName,
444
- frameIndex: item.frame.frameIndex
470
+ frameIndex: item.frame.frameIndex,
471
+ isHidden: hidden
445
472
  };
446
473
  });
447
474
  return { elements: qualified };
448
475
  }
449
- function findElementByAttributes(value, exact = false, parent = null) {
476
+ function findElementsByAttribute(value, exact = false, parent = null) {
450
477
  if (value === null || value === void 0) {
451
478
  value = "";
452
479
  }
@@ -477,18 +504,21 @@ var ElementFinder = (() => {
477
504
  const qualified = filteredMatches.map((item) => {
478
505
  const boundingBox = getBoundingBox(item.element);
479
506
  const tagName = item.element.tagName.toLowerCase();
507
+ const hidden = isHidden(item.element);
480
508
  if (!item.frame.isMainFrame) {
481
509
  return {
482
510
  boundingBox,
483
511
  tagName,
484
- frameIndex: item.frame.frameIndex
512
+ frameIndex: item.frame.frameIndex,
513
+ isHidden: hidden
485
514
  };
486
515
  }
487
516
  return {
488
517
  element: item.element,
489
518
  boundingBox,
490
519
  tagName,
491
- frameIndex: item.frame.frameIndex
520
+ frameIndex: item.frame.frameIndex,
521
+ isHidden: hidden
492
522
  };
493
523
  });
494
524
  return { elements: qualified };
@@ -567,18 +597,21 @@ var ElementFinder = (() => {
567
597
  const qualified = filteredMatches.map((item) => {
568
598
  const boundingBox = getBoundingBox(item.element);
569
599
  const tagName = item.element.tagName.toLowerCase();
600
+ const hidden = isHidden(item.element);
570
601
  if (!item.frame.isMainFrame) {
571
602
  return {
572
603
  boundingBox,
573
604
  tagName,
574
- frameIndex: item.frame.frameIndex
605
+ frameIndex: item.frame.frameIndex,
606
+ isHidden: hidden
575
607
  };
576
608
  }
577
609
  return {
578
610
  element: item.element,
579
611
  boundingBox,
580
612
  tagName,
581
- frameIndex: item.frame.frameIndex
613
+ frameIndex: item.frame.frameIndex,
614
+ isHidden: hidden
582
615
  };
583
616
  });
584
617
  return { elements: qualified };
@@ -628,16 +661,43 @@ var ElementFinder = (() => {
628
661
  return sibling;
629
662
  }
630
663
  }
664
+ for (const sibling of siblings) {
665
+ if (sibling === el) continue;
666
+ const siblingElements = getAllElements(sibling);
667
+ for (let i = 0; i < siblingElements.length; i++) {
668
+ if (matchesType(siblingElements[i], targetType)) {
669
+ return siblingElements[i];
670
+ }
671
+ }
672
+ }
673
+ let ancestor = el.parentElement;
674
+ while (ancestor) {
675
+ const ancestorSiblings = getSiblingElements(ancestor);
676
+ for (const sibling of ancestorSiblings) {
677
+ if (sibling !== ancestor) {
678
+ if (matchesType(sibling, targetType)) {
679
+ return sibling;
680
+ }
681
+ const siblingElements = getAllElements(sibling);
682
+ for (let i = 0; i < siblingElements.length; i++) {
683
+ if (matchesType(siblingElements[i], targetType)) {
684
+ return siblingElements[i];
685
+ }
686
+ }
687
+ }
688
+ }
689
+ ancestor = ancestor.parentElement;
690
+ }
631
691
  return null;
632
692
  }
633
693
  function findProbableElements(elementType, attributeText, exact = false, parent = null) {
634
694
  const hasType = elementType !== null && elementType !== void 0 && elementType !== "";
635
695
  const hasText = attributeText !== null && attributeText !== void 0 && attributeText !== "";
636
696
  if (hasType && !hasText) {
637
- return findElementByType(elementType, parent);
697
+ return findElementsByType(elementType, parent);
638
698
  }
639
699
  if (!hasType && hasText) {
640
- return findElementByAttributes(attributeText, exact, parent);
700
+ return findElementsByAttribute(attributeText, exact, parent);
641
701
  }
642
702
  if (hasType) {
643
703
  if (typeof elementType !== "string") {
@@ -671,7 +731,9 @@ var ElementFinder = (() => {
671
731
  for (let i = 0; i < allElements.length; i++) {
672
732
  const el = allElements[i];
673
733
  if (!matchesAttribute(el, attributeText, exact)) continue;
674
- attributeMatches.push({ element: el, frame });
734
+ if (hasOwnMatch(el, attributeText, exact)) {
735
+ attributeMatches.push({ element: el, frame });
736
+ }
675
737
  }
676
738
  }
677
739
  const foundElements = /* @__PURE__ */ new Set();
@@ -697,18 +759,21 @@ var ElementFinder = (() => {
697
759
  const qualified = filteredMatches.map((item) => {
698
760
  const boundingBox = getBoundingBox(item.element);
699
761
  const tagName = item.element.tagName.toLowerCase();
762
+ const hidden = isHidden(item.element);
700
763
  if (!item.frame.isMainFrame) {
701
764
  return {
702
765
  boundingBox,
703
766
  tagName,
704
- frameIndex: item.frame.frameIndex
767
+ frameIndex: item.frame.frameIndex,
768
+ isHidden: hidden
705
769
  };
706
770
  }
707
771
  return {
708
772
  element: item.element,
709
773
  boundingBox,
710
774
  tagName,
711
- frameIndex: item.frame.frameIndex
775
+ frameIndex: item.frame.frameIndex,
776
+ isHidden: hidden
712
777
  };
713
778
  });
714
779
  return { elements: qualified };
@@ -746,6 +811,78 @@ var ElementFinder = (() => {
746
811
  }
747
812
  }
748
813
  }
814
+ var animationPauseStack = [];
815
+ function pauseAnimations() {
816
+ const originalStyles = /* @__PURE__ */ new Map();
817
+ const elements = getAllElements();
818
+ for (const el of elements) {
819
+ if (el && el.style) {
820
+ if (el.style.animationPlayState !== "paused") {
821
+ originalStyles.set(el, {
822
+ animationPlayState: el.style.animationPlayState,
823
+ transitionProperty: el.style.transitionProperty,
824
+ webkitAnimationPlayState: el.style.webkitAnimationPlayState,
825
+ webkitTransitionProperty: el.style.webkitTransitionProperty
826
+ });
827
+ el.style.animationPlayState = "paused";
828
+ el.style.transitionProperty = "none";
829
+ el.style.webkitAnimationPlayState = "paused";
830
+ el.style.webkitTransitionProperty = "none";
831
+ }
832
+ }
833
+ }
834
+ let styleSheet = document.getElementById("elementfinder-animation-pause");
835
+ if (!styleSheet) {
836
+ styleSheet = document.createElement("style");
837
+ styleSheet.id = "elementfinder-animation-pause";
838
+ styleSheet.textContent = `
839
+ *, *::before, *::after {
840
+ animation-play-state: paused !important;
841
+ transition-property: none !important;
842
+ -webkit-animation-play-state: paused !important;
843
+ -webkit-transition-property: none !important;
844
+ }
845
+ @media (prefers-reduced-motion: no-preference) {
846
+ *, *::before, *::after {
847
+ animation-duration: 0s !important;
848
+ animation-iteration-count: 1 !important;
849
+ transition-duration: 0s !important;
850
+ }
851
+ }
852
+ `;
853
+ document.head.appendChild(styleSheet);
854
+ }
855
+ const pauseState = { originalStyles, pausedCount: originalStyles.size };
856
+ animationPauseStack.push(pauseState);
857
+ return pauseState;
858
+ }
859
+ function resumeAnimations(pauseState) {
860
+ if (!pauseState) {
861
+ if (animationPauseStack.length === 0) return;
862
+ pauseState = animationPauseStack.pop();
863
+ } else {
864
+ const index = animationPauseStack.indexOf(pauseState);
865
+ if (index === -1) return;
866
+ animationPauseStack.splice(index, 1);
867
+ }
868
+ const originalStyles = pauseState.originalStyles;
869
+ if (originalStyles) {
870
+ for (const [el, styles] of originalStyles) {
871
+ if (el && el.style) {
872
+ el.style.animationPlayState = styles.animationPlayState || "";
873
+ el.style.transitionProperty = styles.transitionProperty || "";
874
+ el.style.webkitAnimationPlayState = styles.webkitAnimationPlayState || "";
875
+ el.style.webkitTransitionProperty = styles.webkitTransitionProperty || "";
876
+ }
877
+ }
878
+ }
879
+ if (animationPauseStack.length === 0) {
880
+ const styleSheet = document.getElementById("elementfinder-animation-pause");
881
+ if (styleSheet) {
882
+ styleSheet.remove();
883
+ }
884
+ }
885
+ }
749
886
  function getValidTypes() {
750
887
  return Object.keys(ELEMENT_DEFINITIONS);
751
888
  }
package/index.min.js CHANGED
@@ -1 +1,15 @@
1
- var ElementFinder=(()=>{var M=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var U=(e,t)=>{for(var n in t)M(e,n,{get:t[n],enumerable:!0})},V=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of _(t))!F.call(e,r)&&r!==n&&M(e,r,{get:()=>t[r],enumerable:!(o=j(t,r))||o.enumerable});return e};var z=e=>V(M({},"__esModule",{value:!0}),e);var oe={};U(oe,{ELEMENT_DEFINITIONS:()=>g,findElementByAttributes:()=>q,findElementByType:()=>$,findElements:()=>Q,findProbableElements:()=>v,getAllElements:()=>w,getAllFrames:()=>N,getBoundingBox:()=>x,getSearchableAttributes:()=>H,getValidAttributes:()=>re,getValidTypes:()=>ne,highlight:()=>ee,matchesAttribute:()=>y,matchesType:()=>b,parseCondition:()=>k,parseXPath:()=>p,setSearchableAttributes:()=>W,splitByOperator:()=>T,unhighlight:()=>te});var I={link:"self::a or @role='link' or @href",navigation:"@role='navigation' or self::nav",heading:"@role='heading' or self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6",button:"self::button or @role='button' or @type='button' or @type='submit'",checkbox:"(self::input and @type='checkbox') or @role='checkbox'",switch:"(self::input and @type='checkbox') or @role='switch' or (self::button and (contains(@class, 'switch') or @data-state))",slider:"self::input[@type='range'] or @role='slider'",datepicker:"self::input and @type='date'",colorpicker:"self::input and @type='color'",radio:"(self::input and @type='radio') or @role='radio'",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'])",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'",file:"self::input and @type='file'",list:"self::ul or self::ol or @role='list'",listitem:"self::li or @role='listitem'",menu:"self::menu or @role='menu'",menuitem:"@role='menuitem'",toolbar:"@role='toolbar'",dialog:"@role='dialog'",table:"self::table or @role='table'",row:"self::tr or @role='row'",column:"self::td or self::th or @role='cell' or @role='gridcell' or @role='columnheader'",cell:"self::td or @role='cell' or @role='gridcell'",image:"self::img or @role='img' or @alt",element:"true()"};var O=["placeholder","value","data-test-id","data-testid","id","resource-id","name","aria-label","hint","title","tooltip","alt","src","aria-labelledby"];var h={selfWithTag:/^self::([a-zA-Z0-9-]+)(?:\[([^\]]+)\])?$/,contains:/contains\(@([a-zA-Z0-9-]+),\s*['"]([^'"]+)['"]\)/i,attrEquals:/@([a-zA-Z0-9-]+)\s*=\s*['"]([^'"]*)['"]/,attrExists:/^@([a-zA-Z0-9-]+)$/,descendant:/descendant::([a-zA-Z0-9-]+)/i,ancestor:/ancestor::\*\[([^\]]+)\]/i,operatorOr:/^\s*\bor\b\s*/i,operatorAnd:/^\s*\band\b\s*/i},Z=100,S=new Map;for(let[e,t]of Object.entries(I))t==="true()"?S.set(e,()=>!0):S.set(e,n=>p(t,n));var E=O;function W(e){if(!Array.isArray(e))throw new TypeError("attributes must be an array");E=e}function H(){return[...E]}function p(e,t,n=0){if(e==null||t==null)return!1;if(n>Z)throw new Error("XPath expression exceeds maximum recursion depth");if(e=e.trim(),e==="true()")return!0;if(e[0]==="("&&e[e.length-1]===")"){let s=1,l=!0;for(let a=1;a<e.length-1;a++)if(e[a]==="("?s++:e[a]===")"&&s--,s===0){l=!1;break}if(l)return p(e.slice(1,-1),t,n+1)}let o=T(e,"or");if(o.length>1){for(let s of o)if(p(s,t,n+1))return!0;return!1}let r=T(e,"and");if(r.length>1){for(let s of r)if(!p(s,t,n+1))return!1;return!0}return k(e,t,n)}function T(e,t){let n=[],o=0,r="",s=!1,l="",a=t==="or"?h.operatorOr:h.operatorAnd;for(let i=0;i<e.length;i++){let f=e[i];if((f==="'"||f==='"')&&(i===0||e[i-1]!=="\\")&&(s?f===l&&(s=!1):(s=!0,l=f)),!s&&(f==="("?o++:f===")"&&o--,o===0)){let u=e.slice(i).match(a);if(u){n.push(r.trim()),i+=u[0].length-1,r="";continue}}r+=f}return r.trim()&&n.push(r.trim()),n}function k(e,t,n=0){if(e==null||t==null)return!1;e=e.trim();let o=e.match(h.selfWithTag);if(o){let f=o[1].toUpperCase();return t.tagName!==f?!1:o[2]?p(o[2],t,n+1):!0}let r=e.match(h.contains);if(r)return(t.getAttribute(r[1])||"").toLowerCase().includes(r[2].toLowerCase());let s=e.match(h.attrEquals);if(s)return t.getAttribute(s[1])===s[2];let l=e.match(h.attrExists);if(l)return t.hasAttribute(l[1]);let a=e.match(h.descendant);if(a)return t.querySelector(a[1])!==null;let i=e.match(h.ancestor);if(i){let f=t.parentElement;for(;f;){if(p(i[1],f,n+1))return!0;f=f.parentElement}return!1}return!1}var g=Object.freeze(I);function L(e){let t="";for(let n=0;n<e.childNodes.length;n++){let o=e.childNodes[n];o.nodeType===Node.TEXT_NODE&&(t+=o.textContent)}return t.trim()}function G(e){if(e.tagName==="STYLE"||e.tagName==="SCRIPT"||e.querySelector("STYLE, SCRIPT"))return!0;let t=e.parentElement;for(;t;){if(t.tagName==="STYLE"||t.tagName==="SCRIPT")return!0;t=t.parentElement}return!1}function R(e){let t=e.getAttribute("aria-labelledby");if(!t)return"";let n=t.split(/\s+/),o="";for(let r of n)try{let s=document.getElementById(r);s&&(o+=s.textContent)}catch(s){}return o}function y(e,t,n=!1){if(e==null)return!1;if(t==null||t==="")return!0;if(G(e))return!1;let o=E;for(let l=0;l<o.length;l++){let a=o[l],i;try{i=e.getAttribute(a)}catch(f){continue}if(i){if(a==="aria-labelledby"){if(n?i===t:i.includes(t))return!0;let f=R(e);if(f&&(n?f===t:f.includes(t)))return!0}else if(n?i===t:i.includes(t))return!0}}let r=L(e);if(n?r===t:r.includes(t))return!0;let s=e.textContent;return!!(n?s.trim()===t:s.includes(t))}function b(e,t){if(e==null)return!1;let n=S.get(t);return n?n(e):!1}function w(e=document){let t=[];if(e==null)return t;let n=e.nodeType===Node.DOCUMENT_NODE?e.documentElement:e;if(!n)return t;let o=[n];for(;o.length>0;){let r=o.pop();if(r.nodeType!==Node.ELEMENT_NODE||r.tagName==="SCRIPT"||r.tagName==="STYLE")continue;t.push(r);let s=r.children;for(let l=s.length-1;l>=0;l--)o.push(s[l]);try{if(r.shadowRoot){let l=r.shadowRoot.children;for(let a=l.length-1;a>=0;a--)o.push(l[a])}}catch(l){}}return t}function N(e=window){let t=[];try{t.push({window:e,document:e.document,isMainFrame:!0,frameIndex:-1});let n=e.document.querySelectorAll("iframe");for(let o=0;o<n.length;o++){let r=n[o];try{r.contentWindow&&r.contentDocument&&t.push({window:r.contentWindow,document:r.contentDocument,isMainFrame:!1,frameElement:r,frameIndex:o})}catch(s){s.name==="SecurityError"?console.warn("Skipping cross-origin iframe:",s.message):console.warn("Error accessing iframe:",s.message)}}}catch(n){console.warn("Error getting frames:",n.message)}return t}function x(e){let t=e.getBoundingClientRect();return{x:t.x,y:t.y,width:t.width,height:t.height,top:t.top,bottom:t.bottom,left:t.left,right:t.right,midx:t.x+t.width/2,midy:t.y+t.height/2,tagName:e.tagName.toLowerCase()}}function $(e="element",t=null){if(e==null&&(e="element"),typeof e!="string")throw new TypeError(`type must be a string, got ${typeof e}`);if(e&&!g[e])return console.warn(`Unknown element type: ${e}. Valid types: ${Object.keys(g).join(", ")}`),{elements:[]};let n=[],o=N(window);for(let l of o){let a=w(t||l.document);for(let i=0;i<a.length;i++){let f=a[i];e&&!b(f,e)||n.push({element:f,frame:l})}}let r=[];if(n.length>0){let l=new Set(n.map(i=>i.element)),a=new Set;for(let i=n.length-1;i>=0;i--){let f=n[i],c=f.element;if(!a.has(c)){r.unshift(f);let u=c.parentElement;for(;u;)l.has(u)&&a.add(u),u=u.parentElement}}}return{elements:r.map(l=>{let a=x(l.element),i=l.element.tagName.toLowerCase();return l.frame.isMainFrame?{element:l.element,boundingBox:a,tagName:i,frameIndex:l.frame.frameIndex}:{boundingBox:a,tagName:i,frameIndex:l.frame.frameIndex}})}}function q(e,t=!1,n=null){if(e==null&&(e=""),typeof e!="string")throw new TypeError(`value must be a string, got ${typeof e}`);let o=[],r=N(window);for(let a of r){let i=w(n||a.document);for(let f=0;f<i.length;f++){let c=i[f];y(c,e,t)&&o.push({element:c,frame:a})}}return{elements:o.filter(a=>{let i=a.element;if(B(i,e,t))return!0;for(let c of o)if(c.element!==i&&i.contains(c.element))return!1;return!0}).map(a=>{let i=x(a.element),f=a.element.tagName.toLowerCase();return a.frame.isMainFrame?{element:a.element,boundingBox:i,tagName:f,frameIndex:a.frame.frameIndex}:{boundingBox:i,tagName:f,frameIndex:a.frame.frameIndex}})}}function B(e,t,n=!1){if(t==null||t==="")return!0;let o=E;for(let s=0;s<o.length;s++){let l=o[s],a;try{a=e.getAttribute(l)}catch(i){continue}if(a){if(l==="aria-labelledby"){if(n?a===t:a.includes(t))return!0;let i=R(e);if(i&&(n?i===t:i.includes(t)))return!0}else if(n?a===t:a.includes(t))return!0}}let r=L(e);return!!(n?r===t:r.includes(t))}function Q(e=null,t=null,n=!1,o=null){if(t==null&&(t=""),e!=null){if(typeof e!="string")throw new TypeError(`type must be a string, got ${typeof e}`);if(!g[e])return console.warn(`Unknown element type: ${e}. Valid types: ${Object.keys(g).join(", ")}`),{elements:[]}}if(t!==""&&typeof t!="string")throw new TypeError(`text must be a string, got ${typeof t}`);let r=[],s=N(window);for(let i of s){let f=w(o||i.document);for(let c=0;c<f.length;c++){let u=f[c];e!=null&&!b(u,e)||t!==""&&!y(u,t,n)||r.push({element:u,frame:i})}}return{elements:(t!==""?r.filter(i=>{let f=i.element;if(B(f,t,n))return!0;for(let u of r)if(u.element!==f&&f.contains(u.element))return!1;return!0}):r).map(i=>{let f=x(i.element),c=i.element.tagName.toLowerCase();return i.frame.isMainFrame?{element:i.element,boundingBox:f,tagName:c,frameIndex:i.frame.frameIndex}:{boundingBox:f,tagName:c,frameIndex:i.frame.frameIndex}})}}function C(e){if(e.parentElement)return e.parentElement;try{let t=e.getRootNode();if(t&&t.host)return t.host}catch(t){}return null}function J(e){let t=C(e);if(!t)return[];if(t.shadowRoot)try{return Array.from(t.shadowRoot.children)}catch(n){return[]}return Array.from(t.children)}function K(e,t){let n=C(e);for(;n;){if(b(n,t))return n;n=C(n)}let o=e.children||[];for(let s of o)if(b(s,t))return s;let r=J(e);for(let s of r)if(s!==e&&b(s,t))return s;return null}function v(e,t,n=!1,o=null){let r=e!=null&&e!=="",s=t!=null&&t!=="";if(r&&!s)return $(e,o);if(!r&&s)return q(t,n,o);if(r){if(typeof e!="string")throw new TypeError(`elementType must be a string, got ${typeof e}`);if(!g[e])return console.warn(`Unknown element type: ${e}. Valid types: ${Object.keys(g).join(", ")}`),{elements:[]}}if(s&&typeof t!="string")throw new TypeError(`attributeText must be a string, got ${typeof t}`);let l=[],a=N(window);for(let c of a){let u=w(o||c.document);for(let d=0;d<u.length;d++){let m=u[d];r&&!b(m,e)||s&&!y(m,t,n)||l.push({element:m,frame:c})}}if(l.length===0&&r&&s){let c=[];for(let d of a){let m=w(o||d.document);for(let A=0;A<m.length;A++){let D=m[A];y(D,t,n)&&c.push({element:D,frame:d})}}let u=new Set;for(let d of c){let m=K(d.element,e);m&&!u.has(m)&&(u.add(m),l.push({element:m,frame:d.frame}))}}return{elements:(s?l.filter(c=>{let u=c.element;if(B(u,t,n))return!0;for(let m of l)if(m.element!==u&&u.contains(m.element))return!1;return!0}):l).map(c=>{let u=x(c.element),d=c.element.tagName.toLowerCase();return c.frame.isMainFrame?{element:c.element,boundingBox:u,tagName:d,frameIndex:c.frame.frameIndex}:{boundingBox:u,tagName:d,frameIndex:c.frame.frameIndex}})}}function P(e){return e?e&&e.elements&&Array.isArray(e.elements)?e.elements:Array.isArray(e)?e:[e]:[]}function ee(e,t="red",n=3){let o=P(e);for(let r=0;r<o.length;r++){let s=o[r],l=s.element?s.element:s;l&&l.style&&(l.style.outline=`${n}px solid ${t}`,l.style.outlineOffset="2px",l.style.boxShadow="0 0 0 2px rgba(255, 255, 255, 0.8)",l.classList.add("elementfinder-highlighted"))}}function te(e){let t=P(e);for(let n=0;n<t.length;n++){let o=t[n],r=o.element?o.element:o;r&&r.style&&(r.style.outline="",r.style.outlineOffset="",r.style.boxShadow="",r.classList.remove("elementfinder-highlighted"))}}function ne(){return Object.keys(g)}function re(){return[...E]}return z(oe);})();
1
+ var ElementFinder=(()=>{var I=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var V=(e,t)=>{for(var n in t)I(e,n,{get:t[n],enumerable:!0})},W=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of z(t))!U.call(e,r)&&r!==n&&I(e,r,{get:()=>t[r],enumerable:!(i=F(t,r))||i.enumerable});return e};var X=e=>W(I({},"__esModule",{value:!0}),e);var le={};V(le,{ELEMENT_DEFINITIONS:()=>y,findElements:()=>v,findElementsByAttribute:()=>j,findElementsByType:()=>H,findProbableElements:()=>te,getAllElements:()=>g,getAllFrames:()=>A,getBoundingBox:()=>S,getSearchableAttributes:()=>J,getValidAttributes:()=>ae,getValidTypes:()=>se,highlight:()=>ne,isHidden:()=>N,matchesAttribute:()=>E,matchesType:()=>h,parseCondition:()=>R,parseXPath:()=>b,pauseAnimations:()=>oe,resumeAnimations:()=>ie,setSearchableAttributes:()=>Q,splitByOperator:()=>B,unhighlight:()=>re});var T={link:"self::a or @role='link' or @href",navigation:"@role='navigation' or self::nav",heading:"@role='heading' or self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6",button:"self::button or @role='button' or @type='button' or @type='submit'",checkbox:"(self::input and @type='checkbox') or @role='checkbox'",switch:"(self::input and @type='checkbox') or @role='switch' or (self::button and (contains(@class, 'switch') or @data-state))",slider:"self::input[@type='range'] or @role='slider'",datepicker:"self::input[@type='date'] or @role='date'",colorpicker:"self::input[@type='color'] or @role='color'",radio:"(self::input and @type='radio') or @role='radio'",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'])",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'",file:"self::input and @type='file'",list:"self::ul or self::ol or @role='list'",listitem:"self::li or @role='listitem'",menu:"self::menu or @role='menu'",menuitem:"@role='menuitem'",toolbar:"@role='toolbar'",dialog:"@role='dialog' or @role='alertdialog'",table:"self::table or @role='table'",row:"self::tr or @role='row'",column:"self::td or self::th or @role='cell' or @role='gridcell' or @role='columnheader'",cell:"self::td or @role='cell' or @role='gridcell'",image:"self::img or @role='img' or @alt",element:"true()"};var D=["placeholder","value","data-value","data-test-id","data-testid","id","resource-id","name","aria-label","hint","title","tooltip","alt","src","aria-labelledby"];var p={selfWithTag:/^self::([a-zA-Z0-9-]+)(?:\[([^\]]+)\])?$/,contains:/contains\(@([a-zA-Z0-9-]+),\s*['"]([^'"]+)['"]\)/i,attrEquals:/@([a-zA-Z0-9-]+)\s*=\s*['"]([^'"]*)['"]/,attrExists:/^@([a-zA-Z0-9-]+)$/,descendant:/descendant::([a-zA-Z0-9-]+)/i,ancestor:/ancestor::\*\[([^\]]+)\]/i,operatorOr:/^\s*\bor\b\s*/i,operatorAnd:/^\s*\band\b\s*/i},G=100,C=new Map;for(let[e,t]of Object.entries(T))t==="true()"?C.set(e,()=>!0):C.set(e,n=>b(t,n));var x=D;function Q(e){if(!Array.isArray(e))throw new TypeError("attributes must be an array");x=e}function J(){return[...x]}function b(e,t,n=0){if(e==null||t==null)return!1;if(n>G)throw new Error("XPath expression exceeds maximum recursion depth");if(e=e.trim(),e==="true()")return!0;if(e[0]==="("&&e[e.length-1]===")"){let l=1,s=!0;for(let a=1;a<e.length-1;a++)if(e[a]==="("?l++:e[a]===")"&&l--,l===0){s=!1;break}if(s)return b(e.slice(1,-1),t,n+1)}let i=B(e,"or");if(i.length>1){for(let l of i)if(b(l,t,n+1))return!0;return!1}let r=B(e,"and");if(r.length>1){for(let l of r)if(!b(l,t,n+1))return!1;return!0}return R(e,t,n)}function B(e,t){let n=[],i=0,r="",l=!1,s="",a=t==="or"?p.operatorOr:p.operatorAnd;for(let o=0;o<e.length;o++){let f=e[o];if((f==="'"||f==='"')&&(o===0||e[o-1]!=="\\")&&(l?f===s&&(l=!1):(l=!0,s=f)),!l&&(f==="("?i++:f===")"&&i--,i===0)){let u=e.slice(o).match(a);if(u){n.push(r.trim()),o+=u[0].length-1,r="";continue}}r+=f}return r.trim()&&n.push(r.trim()),n}function R(e,t,n=0){if(e==null||t==null)return!1;e=e.trim();let i=e.match(p.selfWithTag);if(i){let f=i[1].toUpperCase();return t.tagName!==f?!1:i[2]?b(i[2],t,n+1):!0}let r=e.match(p.contains);if(r)return(t.getAttribute(r[1])||"").toLowerCase().includes(r[2].toLowerCase());let l=e.match(p.attrEquals);if(l)return t.getAttribute(l[1])===l[2];let s=e.match(p.attrExists);if(s)return t.hasAttribute(s[1]);let a=e.match(p.descendant);if(a)return t.querySelector(a[1])!==null;let o=e.match(p.ancestor);if(o){let f=t.parentElement;for(;f;){if(b(o[1],f,n+1))return!0;f=f.parentElement}return!1}return!1}var y=Object.freeze(T);function $(e){let t="";for(let n=0;n<e.childNodes.length;n++){let i=e.childNodes[n];i.nodeType===Node.TEXT_NODE&&(t+=i.textContent)}return t.trim()}function K(e){if(e.tagName==="STYLE"||e.tagName==="SCRIPT"||e.querySelector("STYLE, SCRIPT"))return!0;let t=e.parentElement;for(;t;){if(t.tagName==="STYLE"||t.tagName==="SCRIPT")return!0;t=t.parentElement}return!1}function q(e){let t=e.getAttribute("aria-labelledby");if(!t)return"";let n=t.split(/\s+/),i="";for(let r of n)try{let l=document.getElementById(r);l&&(i+=l.textContent)}catch(l){}return i}function E(e,t,n=!1){if(e==null)return!1;if(t==null||t==="")return!0;if(K(e))return!1;let i=x;for(let s=0;s<i.length;s++){let a=i[s],o;try{o=e.getAttribute(a)}catch(f){continue}if(o){if(a==="aria-labelledby"){if(n?o===t:o.includes(t))return!0;let f=q(e);if(f&&(n?f===t:f.includes(t)))return!0}else if(n?o===t:o.includes(t))return!0}}let r=$(e);if(n?r===t:r.includes(t))return!0;let l=e.textContent;return!!(n?l.trim()===t:l.includes(t))}function h(e,t){if(e==null)return!1;let n=C.get(t);return n?n(e):!1}function g(e=document){let t=[];if(e==null)return t;let n=e.nodeType===Node.DOCUMENT_NODE?e.documentElement:e;if(!n)return t;let i=[n];for(;i.length>0;){let r=i.pop();if(r.nodeType!==Node.ELEMENT_NODE||r.tagName==="SCRIPT"||r.tagName==="STYLE")continue;t.push(r);let l=r.children;for(let s=l.length-1;s>=0;s--)i.push(l[s]);try{if(r.shadowRoot){let s=r.shadowRoot.children;for(let a=s.length-1;a>=0;a--)i.push(s[a])}}catch(s){}}return t}function A(e=window){let t=[];try{t.push({window:e,document:e.document,isMainFrame:!0,frameIndex:-1});let n=e.document.querySelectorAll("iframe");for(let i=0;i<n.length;i++){let r=n[i];try{r.contentWindow&&r.contentDocument&&t.push({window:r.contentWindow,document:r.contentDocument,isMainFrame:!1,frameElement:r,frameIndex:i})}catch(l){l.name==="SecurityError"?console.warn("Skipping cross-origin iframe:",l.message):console.warn("Error accessing iframe:",l.message)}}}catch(n){console.warn("Error getting frames:",n.message)}return t}function S(e){let t=e.getBoundingClientRect();return{x:t.x,y:t.y,width:t.width,height:t.height,top:t.top,bottom:t.bottom,left:t.left,right:t.right,midx:t.x+t.width/2,midy:t.y+t.height/2,tagName:e.tagName.toLowerCase()}}function N(e){if(e==null||e.offsetWidth===0&&e.offsetHeight===0)return!0;try{let t=window.getComputedStyle(e);if(t.visibility==="hidden"||t.visibility==="collapse"||t.display==="none")return!0}catch(t){}return!!e.hasAttribute("hidden")}function H(e="element",t=null){if(e==null&&(e="element"),typeof e!="string")throw new TypeError(`type must be a string, got ${typeof e}`);if(e&&!y[e])return console.warn(`Unknown element type: ${e}. Valid types: ${Object.keys(y).join(", ")}`),{elements:[]};let n=[],i=A(window);for(let s of i){let a=g(t||s.document);for(let o=0;o<a.length;o++){let f=a[o];e&&!h(f,e)||n.push({element:f,frame:s})}}let r=[];if(n.length>0){let s=new Set(n.map(o=>o.element)),a=new Set;for(let o=n.length-1;o>=0;o--){let f=n[o],c=f.element;if(!a.has(c)){r.unshift(f);let u=c.parentElement;for(;u;)s.has(u)&&a.add(u),u=u.parentElement}}}return{elements:r.map(s=>{let a=S(s.element),o=s.element.tagName.toLowerCase(),f=N(s.element);return s.frame.isMainFrame?{element:s.element,boundingBox:a,tagName:o,frameIndex:s.frame.frameIndex,isHidden:f}:{boundingBox:a,tagName:o,frameIndex:s.frame.frameIndex,isHidden:f}})}}function j(e,t=!1,n=null){if(e==null&&(e=""),typeof e!="string")throw new TypeError(`value must be a string, got ${typeof e}`);let i=[],r=A(window);for(let a of r){let o=g(n||a.document);for(let f=0;f<o.length;f++){let c=o[f];E(c,e,t)&&i.push({element:c,frame:a})}}return{elements:i.filter(a=>{let o=a.element;if(P(o,e,t))return!0;for(let c of i)if(c.element!==o&&o.contains(c.element))return!1;return!0}).map(a=>{let o=S(a.element),f=a.element.tagName.toLowerCase(),c=N(a.element);return a.frame.isMainFrame?{element:a.element,boundingBox:o,tagName:f,frameIndex:a.frame.frameIndex,isHidden:c}:{boundingBox:o,tagName:f,frameIndex:a.frame.frameIndex,isHidden:c}})}}function P(e,t,n=!1){if(t==null||t==="")return!0;let i=x;for(let l=0;l<i.length;l++){let s=i[l],a;try{a=e.getAttribute(s)}catch(o){continue}if(a){if(s==="aria-labelledby"){if(n?a===t:a.includes(t))return!0;let o=q(e);if(o&&(n?o===t:o.includes(t)))return!0}else if(n?a===t:a.includes(t))return!0}}let r=$(e);return!!(n?r===t:r.includes(t))}function v(e=null,t=null,n=!1,i=null){if(t==null&&(t=""),e!=null){if(typeof e!="string")throw new TypeError(`type must be a string, got ${typeof e}`);if(!y[e])return console.warn(`Unknown element type: ${e}. Valid types: ${Object.keys(y).join(", ")}`),{elements:[]}}if(t!==""&&typeof t!="string")throw new TypeError(`text must be a string, got ${typeof t}`);let r=[],l=A(window);for(let o of l){let f=g(i||o.document);for(let c=0;c<f.length;c++){let u=f[c];e!=null&&!h(u,e)||t!==""&&!E(u,t,n)||r.push({element:u,frame:o})}}return{elements:(t!==""?r.filter(o=>{let f=o.element;if(P(f,t,n))return!0;for(let u of r)if(u.element!==f&&f.contains(u.element))return!1;return!0}):r).map(o=>{let f=S(o.element),c=o.element.tagName.toLowerCase(),u=N(o.element);return o.frame.isMainFrame?{element:o.element,boundingBox:f,tagName:c,frameIndex:o.frame.frameIndex,isHidden:u}:{boundingBox:f,tagName:c,frameIndex:o.frame.frameIndex,isHidden:u}})}}function O(e){if(e.parentElement)return e.parentElement;try{let t=e.getRootNode();if(t&&t.host)return t.host}catch(t){}return null}function L(e){let t=O(e);if(!t)return[];if(t.shadowRoot)try{return Array.from(t.shadowRoot.children)}catch(n){return[]}return Array.from(t.children)}function ee(e,t){let n=O(e);for(;n;){if(h(n,t))return n;n=O(n)}let i=e.children||[];for(let s of i)if(h(s,t))return s;let r=L(e);for(let s of r)if(s!==e&&h(s,t))return s;for(let s of r){if(s===e)continue;let a=g(s);for(let o=0;o<a.length;o++)if(h(a[o],t))return a[o]}let l=e.parentElement;for(;l;){let s=L(l);for(let a of s)if(a!==l){if(h(a,t))return a;let o=g(a);for(let f=0;f<o.length;f++)if(h(o[f],t))return o[f]}l=l.parentElement}return null}function te(e,t,n=!1,i=null){let r=e!=null&&e!=="",l=t!=null&&t!=="";if(r&&!l)return H(e,i);if(!r&&l)return j(t,n,i);if(r){if(typeof e!="string")throw new TypeError(`elementType must be a string, got ${typeof e}`);if(!y[e])return console.warn(`Unknown element type: ${e}. Valid types: ${Object.keys(y).join(", ")}`),{elements:[]}}if(l&&typeof t!="string")throw new TypeError(`attributeText must be a string, got ${typeof t}`);let s=[],a=A(window);for(let c of a){let u=g(i||c.document);for(let m=0;m<u.length;m++){let d=u[m];r&&!h(d,e)||l&&!E(d,t,n)||s.push({element:d,frame:c})}}if(s.length===0&&r&&l){let c=[];for(let m of a){let d=g(i||m.document);for(let M=0;M<d.length;M++){let k=d[M];E(k,t,n)&&P(k,t,n)&&c.push({element:k,frame:m})}}let u=new Set;for(let m of c){let d=ee(m.element,e);d&&!u.has(d)&&(u.add(d),s.push({element:d,frame:m.frame}))}}return{elements:(l?s.filter(c=>{let u=c.element;if(P(u,t,n))return!0;for(let d of s)if(d.element!==u&&u.contains(d.element))return!1;return!0}):s).map(c=>{let u=S(c.element),m=c.element.tagName.toLowerCase(),d=N(c.element);return c.frame.isMainFrame?{element:c.element,boundingBox:u,tagName:m,frameIndex:c.frame.frameIndex,isHidden:d}:{boundingBox:u,tagName:m,frameIndex:c.frame.frameIndex,isHidden:d}})}}function _(e){return e?e&&e.elements&&Array.isArray(e.elements)?e.elements:Array.isArray(e)?e:[e]:[]}function ne(e,t="red",n=3){let i=_(e);for(let r=0;r<i.length;r++){let l=i[r],s=l.element?l.element:l;s&&s.style&&(s.style.outline=`${n}px solid ${t}`,s.style.outlineOffset="2px",s.style.boxShadow="0 0 0 2px rgba(255, 255, 255, 0.8)",s.classList.add("elementfinder-highlighted"))}}function re(e){let t=_(e);for(let n=0;n<t.length;n++){let i=t[n],r=i.element?i.element:i;r&&r.style&&(r.style.outline="",r.style.outlineOffset="",r.style.boxShadow="",r.classList.remove("elementfinder-highlighted"))}}var w=[];function oe(){let e=new Map,t=g();for(let r of t)r&&r.style&&r.style.animationPlayState!=="paused"&&(e.set(r,{animationPlayState:r.style.animationPlayState,transitionProperty:r.style.transitionProperty,webkitAnimationPlayState:r.style.webkitAnimationPlayState,webkitTransitionProperty:r.style.webkitTransitionProperty}),r.style.animationPlayState="paused",r.style.transitionProperty="none",r.style.webkitAnimationPlayState="paused",r.style.webkitTransitionProperty="none");let n=document.getElementById("elementfinder-animation-pause");n||(n=document.createElement("style"),n.id="elementfinder-animation-pause",n.textContent=`
2
+ *, *::before, *::after {
3
+ animation-play-state: paused !important;
4
+ transition-property: none !important;
5
+ -webkit-animation-play-state: paused !important;
6
+ -webkit-transition-property: none !important;
7
+ }
8
+ @media (prefers-reduced-motion: no-preference) {
9
+ *, *::before, *::after {
10
+ animation-duration: 0s !important;
11
+ animation-iteration-count: 1 !important;
12
+ transition-duration: 0s !important;
13
+ }
14
+ }
15
+ `,document.head.appendChild(n));let i={originalStyles:e,pausedCount:e.size};return w.push(i),i}function ie(e){if(e){let n=w.indexOf(e);if(n===-1)return;w.splice(n,1)}else{if(w.length===0)return;e=w.pop()}let t=e.originalStyles;if(t)for(let[n,i]of t)n&&n.style&&(n.style.animationPlayState=i.animationPlayState||"",n.style.transitionProperty=i.transitionProperty||"",n.style.webkitAnimationPlayState=i.webkitAnimationPlayState||"",n.style.webkitTransitionProperty=i.webkitTransitionProperty||"");if(w.length===0){let n=document.getElementById("elementfinder-animation-pause");n&&n.remove()}}function se(){return Object.keys(y)}function ae(){return[...x]}return X(le);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodebug/browser-element-finder",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "Browser Element Finder - Find elements by type and text content",
5
5
  "exports": {
6
6
  ".": {
@@ -6,8 +6,8 @@
6
6
  "checkbox": "(self::input and @type='checkbox') or @role='checkbox'",
7
7
  "switch": "(self::input and @type='checkbox') or @role='switch' or (self::button and (contains(@class, 'switch') or @data-state))",
8
8
  "slider": "self::input[@type='range'] or @role='slider'",
9
- "datepicker": "self::input and @type='date'",
10
- "colorpicker": "self::input and @type='color'",
9
+ "datepicker": "self::input[@type='date'] or @role='date'",
10
+ "colorpicker": "self::input[@type='color'] or @role='color'",
11
11
  "radio": "(self::input and @type='radio') or @role='radio'",
12
12
  "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'])",
13
13
  "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'",
@@ -17,7 +17,7 @@
17
17
  "menu": "self::menu or @role='menu'",
18
18
  "menuitem": "@role='menuitem'",
19
19
  "toolbar": "@role='toolbar'",
20
- "dialog": "@role='dialog'",
20
+ "dialog": "@role='dialog' or @role='alertdialog'",
21
21
  "table": "self::table or @role='table'",
22
22
  "row": "self::tr or @role='row'",
23
23
  "column": "self::td or self::th or @role='cell' or @role='gridcell' or @role='columnheader'",
@@ -1,6 +1,7 @@
1
1
  [
2
2
  "placeholder",
3
3
  "value",
4
+ "data-value",
4
5
  "data-test-id",
5
6
  "data-testid",
6
7
  "id",