@nodebug/browser-element-finder 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  ---
@@ -18,6 +20,14 @@ const results = ElementFinder.findElements('button', 'Submit')
18
20
  // Find by text only (any type)
19
21
  const results = ElementFinder.findElements(null, 'seleniumbase')
20
22
 
23
+ // Count semantic element types on the screen
24
+ const counts = ElementFinder.getElementCounts()
25
+ // Returns counts for all defined non-generic types, excluding `element`
26
+
27
+ // Count one semantic type
28
+ const buttonCount = ElementFinder.getElementCounts('button')
29
+ // Returns `{ button: 3 }`
30
+
21
31
  // Find in all frames (default)
22
32
  const results = ElementFinder.findElements('button')
23
33
 
@@ -43,6 +53,7 @@ results.elements.forEach((e) => {
43
53
  - For iframe results, switch context before interacting (see Selenium/Playwright docs)
44
54
  - Use `getValidTypes()` to enumerate all supported semantic types
45
55
  - Use `getSearchableAttributes()` to see which attributes are searched for text
56
+ - Use `getSearchableAttributeValues(element)` to inspect which searchable attributes are present on a specific element
46
57
 
47
58
  ---
48
59
 
@@ -52,7 +63,7 @@ results.elements.forEach((e) => {
52
63
  - **Text content search**: Search within element text, attributes, and placeholders
53
64
  - **Shadow DOM support**: Automatically traverses shadow roots to find nested elements
54
65
  - **Iframe support**: Automatically searches all frames (main document + iframes) by default
55
- - **Visibility detection**: All elements returned with `isVisible` property (`true`/`false`)
66
+ - **Visibility detection**: All elements returned with `isHidden` property (`true`/`false`)
56
67
  - **Bounding box data**: Returns position and dimensions for each found element
57
68
  - **XPath-like type definitions**: Extensible element type matching using XPath-like expressions
58
69
  - **Optimized performance**: Pre-compiled type matchers, O(n) innermost element filtering, and efficient Set-based lookups
@@ -92,14 +103,18 @@ browser-element-finder/
92
103
 
93
104
  ```js
94
105
  // Find all elements (visible and hidden)
95
- const results = ElementFinder.findElement('button')
106
+ const results = ElementFinder.findElements('button')
96
107
  // Find by text
97
- const results = ElementFinder.findElement('button', 'Submit')
108
+ const results = ElementFinder.findElements('button', 'Submit')
98
109
  // Find by text only
99
- const results = ElementFinder.findElement(null, 'seleniumbase')
110
+ const results = ElementFinder.findElements(null, 'seleniumbase')
111
+ // Count element types
112
+ const counts = ElementFinder.getElementCounts()
113
+ // Count one type
114
+ const buttonCount = ElementFinder.getElementCounts('button')
100
115
  // Check visibility of found elements
101
116
  results.elements.forEach((e) => {
102
- console.log('Visible:', e.isVisible)
117
+ console.log('Hidden:', e.isHidden)
103
118
  })
104
119
  ```
105
120
 
@@ -113,7 +128,7 @@ The library automatically searches all frames (main + iframes). For agent/automa
113
128
  **Example**:
114
129
 
115
130
  ```js
116
- const results = ElementFinder.findElement('button')
131
+ const results = ElementFinder.findElements('button')
117
132
  for (const item of results.elements) {
118
133
  if (item.frameIndex === -1 && item.element) {
119
134
  // Interact directly
@@ -125,14 +140,72 @@ for (const item of results.elements) {
125
140
  }
126
141
  ```
127
142
 
143
+ ### Customizing Searchable Attributes
144
+
145
+ You can customize which attributes the library searches for text (e.g., adding a custom `data-test-id` or removing `placeholder`).
146
+
147
+ ```js
148
+ // Get current attributes
149
+ const currentAttrs = ElementFinder.getSearchableAttributes()
150
+
151
+ // Set new priority list
152
+ ElementFinder.setSearchableAttributes([
153
+ 'id',
154
+ 'name',
155
+ 'data-testid',
156
+ 'placeholder',
157
+ ])
158
+ ```
159
+
160
+ ### Inspecting Attribute Values
161
+
162
+ Use `getSearchableAttributeValues(element)` to inspect which current searchable attributes are present on a specific element and what values they contain.
163
+
164
+ ```js
165
+ const input = document.querySelector('input')
166
+ const values = ElementFinder.getSearchableAttributeValues(input)
167
+
168
+ console.log(values)
169
+ // { placeholder: 'Email', 'data-testid': 'email-input', id: 'email' }
170
+ ```
171
+
172
+ The returned object only includes searchable attributes that exist on the element and have non-empty values. It respects any custom attribute order set with `setSearchableAttributes()`.
173
+
174
+ ### Pausing Animations for Screenshots
175
+
176
+ When taking screenshots or performing visual assertions, animations can cause flaky tests. Use `pauseAnimations()` and `resumeAnimations()` to freeze and restore animations:
177
+
178
+ ```js
179
+ // Pause all CSS animations and transitions
180
+ const pauseState = ElementFinder.pauseAnimations()
181
+
182
+ // Take screenshot or perform visual assertions
183
+ // ... screenshot code here ...
184
+
185
+ // Resume animations
186
+ ElementFinder.resumeAnimations(pauseState)
187
+ ```
188
+
189
+ For Selenium WebDriver tests, call the functions directly in the browser context:
190
+
191
+ ```js
192
+ // Pause animations (state is stored internally in browser)
193
+ await driver.executeScript('return ElementFinder.pauseAnimations()')
194
+
195
+ // ... take screenshot ...
196
+
197
+ // Resume animations (pops from internal stack - no argument needed)
198
+ await driver.executeScript('ElementFinder.resumeAnimations()')
199
+ ```
200
+
128
201
  ### Accessing Element Definitions and Searchable Attributes
129
202
 
130
203
  The package exports JSON files containing element type definitions and searchable attributes:
131
204
 
132
205
  ```javascript
133
206
  // ESM - Import JSON directly
134
- import ELEMENT_DEFINITIONS from '@nodebug/browser-element-finder/element-definitions.json' assert { type: 'json' }
135
- import SEARCHABLE_ATTRIBUTES from '@nodebug/browser-element-finder/searchable-attributes.json' assert { type: 'json' }
207
+ import ELEMENT_DEFINITIONS from '@nodebug/browser-element-finder/element-definitions.json' with { type: 'json' }
208
+ import SEARCHABLE_ATTRIBUTES from '@nodebug/browser-element-finder/searchable-attributes.json' with { type: 'json' }
136
209
 
137
210
  // Get all valid element types
138
211
  console.log(Object.keys(ELEMENT_DEFINITIONS)) // ['button', 'checkbox', 'dropdown', ...]
@@ -141,34 +214,37 @@ console.log(Object.keys(ELEMENT_DEFINITIONS)) // ['button', 'checkbox', 'dropdow
141
214
  console.log(SEARCHABLE_ATTRIBUTES) // ['placeholder', 'value', 'data-test-id', ...]
142
215
  ```
143
216
 
144
- ```javascript
145
- // CommonJS - Use require
146
- const ELEMENT_DEFINITIONS = require('@nodebug/browser-element-finder/element-definitions.json')
147
- const SEARCHABLE_ATTRIBUTES = require('@nodebug/browser-element-finder/searchable-attributes.json')
148
- ```
217
+ The package is ESM-only (`"type": "module"`), so CommonJS `require()` examples are not supported.
149
218
 
150
219
  ---
151
220
 
152
221
  ## API Summary
153
222
 
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 |
223
+ | Function | Description |
224
+ | ------------------------------------------------- | -------------------------------------------------------------------------------------------- |
225
+ | `findElements(type, text, exact, parent)` | Find elements by type/text, returns `{ elements: [...] }` |
226
+ | `findElementsByType(type, parent)` | Find elements by type only, returns `{ elements: [...] }` |
227
+ | `findElementsByAttribute(value, exact, parent)` | Find elements by text/attribute, returns `{ elements: [...] }` |
228
+ | `getElementCounts(type, parent)` | Count elements by semantic type, excluding generic `element` by default |
229
+ | `findProbableElements(type, text, exact, parent)` | Find elements with fallback to nearby elements, returns `{ elements: [...] }` |
230
+ | `highlight(elements, color, width)` | Highlight elements with outline |
231
+ | `unhighlight(elements)` | Remove highlight |
232
+ | `pauseAnimations()` | Pause all CSS animations and transitions, returns state object |
233
+ | `resumeAnimations(state)` | Resume animations using state from `pauseAnimations()` |
234
+ | `getValidTypes()` | List all supported element types |
235
+ | `getValidAttributes()` | List all valid searchable attribute names |
236
+ | `getBoundingBox(element)` | Get bounding box for an element |
237
+ | `setSearchableAttributes(attributes)` | Set custom attributes for text search |
238
+ | `getSearchableAttributes()` | Get current searchable attributes |
239
+ | `getSearchableAttributeValues(element)` | Get current non-empty searchable attribute values from an element |
240
+ | `getElementDescriptor(element)` | Get identifiable text, source attribute, occurrence index, type, and tag name for an element |
241
+ | `matchesType(el, type)` | Check if element matches a type |
242
+ | `matchesAttribute(el, value, exact)` | Check if element matches text/attribute |
243
+ | `getAllElements(root)` | Get all elements (with shadow DOM) |
244
+ | `getAllFrames(root)` | Get all frames (main + iframes) |
245
+ | `parseXPath(expr, el, depth)` | Parse XPath-like type expressions |
246
+ | `splitByOperator(expr, op)` | Split XPath by operator |
247
+ | `isHidden(el)` | Check if element is hidden (display:none, visibility:hidden, hidden attribute) |
172
248
 
173
249
  ---
174
250
 
@@ -176,17 +252,18 @@ const SEARCHABLE_ATTRIBUTES = require('@nodebug/browser-element-finder/searchabl
176
252
 
177
253
  Finds elements matching the specified type and/or text. Combines type and attribute matching in a single call. Searches all frames (main document + iframes) by default.
178
254
 
179
- | Parameter | Type | Default | Description |
180
- | --------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
181
- | `type` | `string` | `null` | Element type (see supported types below). If `null`, matches any type. Throws `TypeError` for non-string values. |
182
- | `text` | `string` | `null` | Text to search for in content/attributes. If `null`/`''`/`undefined`, matches any text. |
183
- | `exact` | `boolean` | `false` | Exact text match vs substring (only used when text is provided) |
184
- | `parent` | `Element` | `null` | Parent element to search within |
255
+ | Parameter | Type | Default | Description |
256
+ | --------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
257
+ | `type` | `string` | `null` | Element type (see supported types below). If `null` or `undefined`, matches any type. Throws `TypeError` for non-string values. |
258
+ | `text` | `string` | `null` | Text to search for in content/attributes. If `null`/`''`/`undefined`, matches any text. |
259
+ | `exact` | `boolean` | `false` | Exact text match vs substring (only used when text is provided) |
260
+ | `parent` | `Element` | `null` | Parent element to search within |
185
261
 
186
- **Returns**: `{ elements: [{ element, boundingBox, tagName, frameIndex }] }`
262
+ **Returns**: `{ elements: [{ element, boundingBox, tagName, frameIndex, isHidden }] }`
187
263
 
188
264
  - `element`: Raw DOM element (main frame only; for iframes, use `frameIndex` and re-query after switching context)
189
265
  - `frameIndex`: `-1` for main frame, `0, 1, 2...` for iframes
266
+ - `isHidden`: `true` if element is hidden (display:none, visibility:hidden, hidden attribute, or zero dimensions)
190
267
 
191
268
  **Agent/Automation Note**: Iframe elements cannot be interacted with directly. Use `frameIndex` to switch context, then re-run `findElements` inside the iframe.
192
269
 
@@ -203,12 +280,12 @@ Finds elements matching the specified type with intelligent fallback to nearby e
203
280
  | `exact` | `boolean` | `false` | Exact text match vs substring (only used when text is provided) |
204
281
  | `parent` | `Element` | `null` | Parent element to search within |
205
282
 
206
- **Returns**: `{ elements: [{ element, boundingBox, tagName, frameIndex }] }`
283
+ **Returns**: `{ elements: [{ element, boundingBox, tagName, frameIndex, isHidden }] }`
207
284
 
208
285
  **Behavior**:
209
286
 
210
- - If only `type` is provided: delegates to `findElementByType(type, parent)`
211
- - If only `text` is provided: delegates to `findElementByAttributes(text, exact, parent)`
287
+ - If only `type` is provided: delegates to `findElementsByType(type, parent)`
288
+ - If only `text` is provided: delegates to `findElementsByAttribute(text, exact, parent)`
212
289
  - If both are provided: attempts direct match, then falls back to nearby elements
213
290
 
214
291
  **Fallback Strategy**: When no element matches both type and text directly, searches for nearby elements in this order:
@@ -220,11 +297,11 @@ Finds elements matching the specified type with intelligent fallback to nearby e
220
297
  **Example**:
221
298
 
222
299
  ```javascript
223
- // Type-only search (delegates to findElementByType)
300
+ // Type-only search (delegates to findElementsByType)
224
301
  const result1 = ElementFinder.findProbableElements('button')
225
302
  // Returns all buttons on the page
226
303
 
227
- // Text-only search (delegates to findElementByAttributes)
304
+ // Text-only search (delegates to findElementsByAttribute)
228
305
  const result2 = ElementFinder.findProbableElements(null, 'Submit')
229
306
  // Returns all elements containing "Submit"
230
307
 
@@ -265,6 +342,10 @@ Removes highlighting from elements.
265
342
 
266
343
  Returns an array of all valid element type names.
267
344
 
345
+ ### `getValidAttributes()`
346
+
347
+ Returns an array of all valid searchable attribute names (same as `getSearchableAttributes()`).
348
+
268
349
  ### `getBoundingBox(element)`
269
350
 
270
351
  Returns the bounding box for an element.
@@ -277,6 +358,47 @@ Sets custom searchable attributes.
277
358
 
278
359
  Returns the current searchable attributes array.
279
360
 
361
+ ### `getElementDescriptor(element)`
362
+
363
+ Returns identifiable text for a DOM element, plus structured metadata about where it came from, its 1-based occurrence index, its semantic type, and its HTML tag name. Uniqueness can be inferred from `index`: `index === 1` means the identifiable text is unique within the current frame.
364
+
365
+ ```javascript
366
+ const descriptor = ElementFinder.getElementDescriptor(element)
367
+ ```
368
+
369
+ **Returns**:
370
+
371
+ ```javascript
372
+ {
373
+ identifiableText: 'Save', // Plain searchable text only; no CSS/XPath/index syntax
374
+ attributeName: 'title', // Attribute name, or 'text' for direct/textContent fallback
375
+ index: 2, // 1-based occurrence index; index > 1 means not unique
376
+ type: 'button', // Semantic element type, or null for non-elements
377
+ tagName: 'button' // Lowercase HTML tag name, or null for non-elements
378
+ }
379
+ ```
380
+
381
+ Descriptor selection follows the same searchable-attribute priority as text search:
382
+
383
+ 1. First non-empty searchable attribute value is used.
384
+ 2. `aria-labelledby` is resolved to referenced element text before returning the identifiable text.
385
+ 3. `src` values are returned as the image filename without path, query string, fragment, or extension.
386
+ 4. If no searchable attribute exists, direct text nodes are used and `attributeName` is set to `'text'`.
387
+ 5. If direct text is empty, trimmed full `textContent` is used and `attributeName` is set to `'text'`.
388
+ 6. If no text exists, `identifiableText` and `attributeName` are `null`, but `index`, `type`, and `tagName` are still returned.
389
+
390
+ Null or non-element input returns:
391
+
392
+ ```javascript
393
+ {
394
+ identifiableText: null,
395
+ attributeName: null,
396
+ index: 1,
397
+ type: null,
398
+ tagName: null
399
+ }
400
+ ```
401
+
280
402
  ### `matchesType(el, type)`
281
403
 
282
404
  Checks if an element matches the specified type definition.
@@ -285,7 +407,7 @@ Checks if an element matches the specified type definition.
285
407
 
286
408
  Checks if an element matches the specified text/attribute value. Safely handles edge case elements that may throw errors on attribute access.
287
409
 
288
- ### `findElementByType(type, parent)`
410
+ ### `findElementsByType(type, parent)`
289
411
 
290
412
  Finds elements by type only. Searches all frames by default.
291
413
 
@@ -294,7 +416,37 @@ Finds elements by type only. Searches all frames by default.
294
416
  | `type` | `string` | `"element"` | Element type (see supported types below). Throws `TypeError` for non-string values. |
295
417
  | `parent` | `Element` | `null` | Parent element to search within |
296
418
 
297
- ### `findElementByAttributes(value, exact, parent)`
419
+ ### `getElementCounts(type, parent)`
420
+
421
+ Counts elements by semantic type on the current screen. Searches all frames (main document + iframes) by default.
422
+
423
+ | Parameter | Type | Default | Description |
424
+ | --------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------- |
425
+ | `type` | `string` | `null` | Specific element type to count. If `null`/`undefined`, returns counts for all defined types except `element`. |
426
+ | `parent` | `Element` | `null` | Parent element to count within |
427
+
428
+ **Returns**: `Object.<string, number>` keyed by semantic element type.
429
+
430
+ ```javascript
431
+ // Count all defined non-generic types
432
+ const counts = ElementFinder.getElementCounts()
433
+ // { button: 3, textbox: 2, link: 1, ... }
434
+
435
+ // Count one type
436
+ const buttons = ElementFinder.getElementCounts('button')
437
+ // { button: 3 }
438
+
439
+ // Count within a parent element
440
+ const inputs = ElementFinder.getElementCounts(
441
+ 'textbox',
442
+ document.querySelector('form'),
443
+ )
444
+ // { textbox: 2 }
445
+ ```
446
+
447
+ The generic `element` type is excluded from the all-types count so the result contains only semantic types such as `button`, `textbox`, `link`, and `table`.
448
+
449
+ ### `findElementsByAttribute(value, exact, parent)`
298
450
 
299
451
  Finds elements by text/attribute value only. Searches all frames by default.
300
452
 
@@ -329,7 +481,7 @@ The library automatically searches all frames (main + iframes) by default. Howev
329
481
  ### Iframe Element Limitations
330
482
 
331
483
  ```javascript
332
- const results = ElementFinder.findElement('button')
484
+ const results = ElementFinder.findElements('button')
333
485
 
334
486
  results.elements.forEach((item) => {
335
487
  if (item.frameIndex === -1) {
@@ -352,7 +504,7 @@ To interact with elements inside an iframe, you must switch the Selenium driver
352
504
  ```javascript
353
505
  // Find iframe elements
354
506
  const results = await driver.executeScript(`
355
- return ElementFinder.findElement('button');
507
+ return ElementFinder.findElements('button');
356
508
  `)
357
509
 
358
510
  // Switch to iframe and interact
@@ -363,7 +515,7 @@ if (iframeElements.length > 0) {
363
515
 
364
516
  // Now find and interact with elements in the iframe
365
517
  const iframeResults = await driver.executeScript(`
366
- return ElementFinder.findElement('button');
518
+ return ElementFinder.findElements('button');
367
519
  `)
368
520
  // These elements will have the element property since we're in the iframe context
369
521
  }
@@ -373,33 +525,33 @@ if (iframeElements.length > 0) {
373
525
 
374
526
  ## Supported Element Types
375
527
 
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 |
528
+ | Type | Description |
529
+ | ------------- | ---------------------------------------------------------------------------------------------------- |
530
+ | `button` | `<button>`, `[role="button"]`, `[type="button"]`, `[type="submit"]` |
531
+ | `checkbox` | `<input type="checkbox">`, `[role="checkbox"]` |
532
+ | `switch` | Toggle switches, checkboxes with switch role, buttons with `class="switch"` or `data-state` |
533
+ | `slider` | `<input type="range">`, `[role="slider"]` |
534
+ | `datepicker` | `<input type="date">`, `[role="date"]` |
535
+ | `colorpicker` | `<input type="color">`, `[role="color"]` |
536
+ | `radio` | `<input type="radio">`, `[role="radio"]` |
537
+ | `dropdown` | `<select>`, `[role="combobox"]`, `[role="listbox"]`, class-based dropdown/trigger, ancestor matching |
538
+ | `textbox` | `<textarea>`, `<input>` (text/password/search/email/number/tel/url), `[role="textbox"]` |
539
+ | `link` | `<a>`, `[role="link"]`, `[href]` |
540
+ | `heading` | `<h1>-<h6>`, `[role="heading"]` |
541
+ | `navigation` | `<nav>`, `[role="navigation"]` |
542
+ | `list` | `<ul>`, `<ol>`, `[role="list"]` |
543
+ | `listitem` | `<li>`, `[role="listitem"]` |
544
+ | `menu` | `<menu>`, `[role="menu"]` |
545
+ | `menuitem` | `[role="menuitem"]` |
546
+ | `toolbar` | `[role="toolbar"]` |
547
+ | `dialog` | `[role="dialog"]`, `[role="alertdialog"]` |
548
+ | `table` | `<table>`, `[role="table"]` |
549
+ | `row` | `<tr>`, `[role="row"]` |
550
+ | `column` | `<td>`, `<th>`, `[role="cell"]`, `[role="gridcell"]`, `[role="columnheader"]` |
551
+ | `cell` | `<td>`, `[role="cell"]`, `[role="gridcell"]` (data cells only, no expansion) |
552
+ | `image` | `<img>`, `[role="img"]`, `[alt]` |
553
+ | `file` | `<input type="file">` |
554
+ | `element` | Matches all elements |
403
555
 
404
556
  ---
405
557
 
@@ -416,19 +568,19 @@ Both `column` and `cell` types find table cells, but they behave differently:
416
568
 
417
569
  ```javascript
418
570
  // Find all cells in the "City" column (header + 3 data cells = 4 total)
419
- const columnResult = ElementFinder.findElement('column', 'City')
571
+ const columnResult = ElementFinder.findElements('column', 'City')
420
572
  // Returns: [th:City, td:New York, td:London, td:Paris]
421
573
 
422
574
  // Find all cells when searching for a data cell value
423
- const columnResult2 = ElementFinder.findElement('column', 'Paris')
575
+ const columnResult2 = ElementFinder.findElements('column', 'Paris')
424
576
  // Returns: [th:City, td:New York, td:London, td:Paris]
425
577
 
426
578
  // Find only the specific cell containing "Paris"
427
- const cellResult = ElementFinder.findElement('cell', 'Paris')
579
+ const cellResult = ElementFinder.findElements('cell', 'Paris')
428
580
  // Returns: [td:Paris]
429
581
 
430
582
  // Find by header text with cell type - returns only the header cell
431
- const headerCell = ElementFinder.findElement('cell', 'City')
583
+ const headerCell = ElementFinder.findElements('cell', 'City')
432
584
  // Returns: [] (no td elements match "City" header text)
433
585
  ```
434
586
 
@@ -438,8 +590,8 @@ const headerCell = ElementFinder.findElement('cell', 'City')
438
590
 
439
591
  By default, the library searches these attributes (in priority order):
440
592
 
441
- - `placeholder`, `value`, `data-test-id`, `data-testid`, `id`
442
- - `resource-id`, `name`, `aria-label`, `class`, `hint`
593
+ - `placeholder`, `value`, `data-value`, `data-test-id`, `data-testid`, `id`
594
+ - `resource-id`, `name`, `aria-label`, `hint`
443
595
  - `title`, `tooltip`, `alt`, `src`, `aria-labelledby`
444
596
 
445
597
  ---
package/index.js CHANGED
@@ -21,21 +21,27 @@ 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,
30
30
  getBoundingBox: () => getBoundingBox,
31
+ getElementCounts: () => getElementCounts,
32
+ getElementDescriptor: () => getElementDescriptor,
33
+ getSearchableAttributeValues: () => getSearchableAttributeValues,
31
34
  getSearchableAttributes: () => getSearchableAttributes,
32
35
  getValidAttributes: () => getValidAttributes,
33
36
  getValidTypes: () => getValidTypes,
34
37
  highlight: () => highlight,
38
+ isHidden: () => isHidden,
35
39
  matchesAttribute: () => matchesAttribute,
36
40
  matchesType: () => matchesType,
37
41
  parseCondition: () => parseCondition,
38
42
  parseXPath: () => parseXPath,
43
+ pauseAnimations: () => pauseAnimations,
44
+ resumeAnimations: () => resumeAnimations,
39
45
  setSearchableAttributes: () => setSearchableAttributes,
40
46
  splitByOperator: () => splitByOperator,
41
47
  unhighlight: () => unhighlight
@@ -61,7 +67,7 @@ var ElementFinder = (() => {
61
67
  menu: "self::menu or @role='menu'",
62
68
  menuitem: "@role='menuitem'",
63
69
  toolbar: "@role='toolbar'",
64
- dialog: "@role='dialog'",
70
+ dialog: "@role='dialog' or @role='alertdialog'",
65
71
  table: "self::table or @role='table'",
66
72
  row: "self::tr or @role='row'",
67
73
  column: "self::td or self::th or @role='cell' or @role='gridcell' or @role='columnheader'",
@@ -74,6 +80,7 @@ var ElementFinder = (() => {
74
80
  var searchable_attributes_default = [
75
81
  "placeholder",
76
82
  "value",
83
+ "data-value",
77
84
  "data-test-id",
78
85
  "data-testid",
79
86
  "id",
@@ -118,6 +125,156 @@ var ElementFinder = (() => {
118
125
  function getSearchableAttributes() {
119
126
  return [...SEARCHABLE_ATTRIBUTES];
120
127
  }
128
+ function getSearchableAttributeValues(el) {
129
+ if (el == null || el.nodeType !== Node.ELEMENT_NODE) return {};
130
+ const values = {};
131
+ const attrs = SEARCHABLE_ATTRIBUTES;
132
+ for (let i = 0; i < attrs.length; i++) {
133
+ const attr = attrs[i];
134
+ let attrValue;
135
+ try {
136
+ attrValue = el.getAttribute(attr);
137
+ } catch (e) {
138
+ continue;
139
+ }
140
+ if (attrValue !== null && attrValue !== void 0 && attrValue !== "") {
141
+ values[attr] = attrValue;
142
+ }
143
+ }
144
+ return values;
145
+ }
146
+ function normalizeDescriptorText(text) {
147
+ if (text == null) return "";
148
+ return String(text).replace(/\s+/g, " ").trim();
149
+ }
150
+ function getImageFilenameWithoutExtension(src) {
151
+ const normalizedSrc = normalizeDescriptorText(src);
152
+ if (!normalizedSrc) return "";
153
+ const withoutQueryOrFragment = normalizedSrc.split(/[?#]/)[0];
154
+ const lastSlashIndex = Math.max(
155
+ withoutQueryOrFragment.lastIndexOf("/"),
156
+ withoutQueryOrFragment.lastIndexOf("\\")
157
+ );
158
+ const filenameWithExtension = lastSlashIndex >= 0 ? withoutQueryOrFragment.slice(lastSlashIndex + 1) : withoutQueryOrFragment;
159
+ const lastDotIndex = filenameWithExtension.lastIndexOf(".");
160
+ return lastDotIndex > 0 ? filenameWithExtension.slice(0, lastDotIndex) : filenameWithExtension;
161
+ }
162
+ function getResolvedAriaLabelledByText(el) {
163
+ const labelledBy = el.getAttribute("aria-labelledby");
164
+ if (!labelledBy) return "";
165
+ const ids = labelledBy.split(/\s+/);
166
+ const ownerDocument = el.ownerDocument || document;
167
+ let text = "";
168
+ for (const id of ids) {
169
+ try {
170
+ const refEl = ownerDocument.getElementById(id);
171
+ if (!refEl) continue;
172
+ const refText = normalizeDescriptorText(refEl.textContent);
173
+ if (refText) {
174
+ text = text ? `${text} ${refText}` : refText;
175
+ }
176
+ } catch (e) {
177
+ }
178
+ }
179
+ return text;
180
+ }
181
+ function getElementDescriptorText(el) {
182
+ const values = getSearchableAttributeValues(el);
183
+ const attrs = SEARCHABLE_ATTRIBUTES;
184
+ for (let i = 0; i < attrs.length; i++) {
185
+ const attr = attrs[i];
186
+ if (!Object.prototype.hasOwnProperty.call(values, attr)) continue;
187
+ const rawText = attr === "aria-labelledby" ? getResolvedAriaLabelledByText(el) : attr === "src" ? getImageFilenameWithoutExtension(values[attr]) : values[attr];
188
+ const identifiableText = normalizeDescriptorText(rawText);
189
+ if (identifiableText) {
190
+ return { attributeName: attr, identifiableText };
191
+ }
192
+ }
193
+ const directText = normalizeDescriptorText(getDirectText(el));
194
+ if (directText) {
195
+ return { attributeName: "text", identifiableText: directText };
196
+ }
197
+ const fullText = normalizeDescriptorText(el.textContent);
198
+ if (fullText) {
199
+ return { attributeName: "text", identifiableText: fullText };
200
+ }
201
+ return null;
202
+ }
203
+ function getElementDescriptorFrame(el) {
204
+ if (!el || !el.ownerDocument) return null;
205
+ try {
206
+ const frames = getAllFrames(window);
207
+ for (let i = 0; i < frames.length; i++) {
208
+ if (frames[i].document === el.ownerDocument) {
209
+ return frames[i].document;
210
+ }
211
+ }
212
+ } catch (e) {
213
+ }
214
+ return el.ownerDocument;
215
+ }
216
+ function getElementDescriptorUniqueness(el, text) {
217
+ const root = getElementDescriptorFrame(el);
218
+ if (!root) {
219
+ return { index: 1 };
220
+ }
221
+ const elements = getAllElements(root);
222
+ let index = 1;
223
+ let count = 0;
224
+ for (let i = 0; i < elements.length; i++) {
225
+ const candidate = elements[i];
226
+ if (candidate !== el && (candidate === root.documentElement || candidate.tagName === "BODY")) {
227
+ continue;
228
+ }
229
+ const candidateDescriptor = getElementDescriptorText(candidate);
230
+ if (!candidateDescriptor || candidateDescriptor.identifiableText !== text) continue;
231
+ count++;
232
+ if (candidate === el) {
233
+ index = count;
234
+ }
235
+ }
236
+ return { index };
237
+ }
238
+ function getElementDescriptorType(el) {
239
+ if (el == null || el.nodeType !== Node.ELEMENT_NODE) return null;
240
+ const types = Object.keys(ELEMENT_DEFINITIONS);
241
+ for (let i = 0; i < types.length; i++) {
242
+ const type = types[i];
243
+ if (type === "element") continue;
244
+ if (matchesType(el, type)) return type;
245
+ }
246
+ return "element";
247
+ }
248
+ function getElementDescriptor(el) {
249
+ if (el == null || el.nodeType !== Node.ELEMENT_NODE) {
250
+ return {
251
+ identifiableText: null,
252
+ attributeName: null,
253
+ index: 1,
254
+ type: null,
255
+ tagName: null
256
+ };
257
+ }
258
+ const type = getElementDescriptorType(el);
259
+ const descriptorSource = getElementDescriptorText(el);
260
+ if (!descriptorSource) {
261
+ return {
262
+ identifiableText: null,
263
+ attributeName: null,
264
+ index: 1,
265
+ type,
266
+ tagName: el.tagName.toLowerCase()
267
+ };
268
+ }
269
+ const uniqueness = getElementDescriptorUniqueness(el, descriptorSource.identifiableText);
270
+ return {
271
+ identifiableText: descriptorSource.identifiableText,
272
+ attributeName: descriptorSource.attributeName,
273
+ index: uniqueness.index,
274
+ type,
275
+ tagName: el.tagName.toLowerCase()
276
+ };
277
+ }
121
278
  function parseXPath(expr, el, depth = 0) {
122
279
  if (expr == null || el == null) return false;
123
280
  if (depth > MAX_RECURSION_DEPTH) {
@@ -387,15 +544,40 @@ var ElementFinder = (() => {
387
544
  tagName: el.tagName.toLowerCase()
388
545
  };
389
546
  }
390
- function findElementByType(type = "element", parent = null) {
547
+ function isHidden(el) {
548
+ if (el == null) return true;
549
+ if (el.offsetWidth === 0 && el.offsetHeight === 0) {
550
+ return true;
551
+ }
552
+ try {
553
+ const style = window.getComputedStyle(el);
554
+ if (style.visibility === "hidden" || style.visibility === "collapse") {
555
+ return true;
556
+ }
557
+ if (style.display === "none") {
558
+ return true;
559
+ }
560
+ } catch (e) {
561
+ }
562
+ if (el.hasAttribute("hidden")) {
563
+ return true;
564
+ }
565
+ return false;
566
+ }
567
+ function findElementsByType(type = "element", parent = null, options = null) {
391
568
  if (type === null || type === void 0) {
392
569
  type = "element";
393
570
  }
394
571
  if (typeof type !== "string") {
395
572
  throw new TypeError(`type must be a string, got ${typeof type}`);
396
573
  }
574
+ const failOnUnknownType = options && options.failOnUnknownType === true;
397
575
  if (type && !ELEMENT_DEFINITIONS[type]) {
398
- console.warn(`Unknown element type: ${type}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`);
576
+ const message = `Unknown element type: ${type}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`;
577
+ if (failOnUnknownType) {
578
+ throw new TypeError(`Unknown element type: ${type}`);
579
+ }
580
+ console.warn(message);
399
581
  return { elements: [] };
400
582
  }
401
583
  const matches = [];
@@ -430,23 +612,26 @@ var ElementFinder = (() => {
430
612
  const qualified = innermostMatches.map((item) => {
431
613
  const boundingBox = getBoundingBox(item.element);
432
614
  const tagName = item.element.tagName.toLowerCase();
615
+ const hidden = isHidden(item.element);
433
616
  if (!item.frame.isMainFrame) {
434
617
  return {
435
618
  boundingBox,
436
619
  tagName,
437
- frameIndex: item.frame.frameIndex
620
+ frameIndex: item.frame.frameIndex,
621
+ isHidden: hidden
438
622
  };
439
623
  }
440
624
  return {
441
625
  element: item.element,
442
626
  boundingBox,
443
627
  tagName,
444
- frameIndex: item.frame.frameIndex
628
+ frameIndex: item.frame.frameIndex,
629
+ isHidden: hidden
445
630
  };
446
631
  });
447
632
  return { elements: qualified };
448
633
  }
449
- function findElementByAttributes(value, exact = false, parent = null) {
634
+ function findElementsByAttribute(value, exact = false, parent = null) {
450
635
  if (value === null || value === void 0) {
451
636
  value = "";
452
637
  }
@@ -477,18 +662,21 @@ var ElementFinder = (() => {
477
662
  const qualified = filteredMatches.map((item) => {
478
663
  const boundingBox = getBoundingBox(item.element);
479
664
  const tagName = item.element.tagName.toLowerCase();
665
+ const hidden = isHidden(item.element);
480
666
  if (!item.frame.isMainFrame) {
481
667
  return {
482
668
  boundingBox,
483
669
  tagName,
484
- frameIndex: item.frame.frameIndex
670
+ frameIndex: item.frame.frameIndex,
671
+ isHidden: hidden
485
672
  };
486
673
  }
487
674
  return {
488
675
  element: item.element,
489
676
  boundingBox,
490
677
  tagName,
491
- frameIndex: item.frame.frameIndex
678
+ frameIndex: item.frame.frameIndex,
679
+ isHidden: hidden
492
680
  };
493
681
  });
494
682
  return { elements: qualified };
@@ -526,7 +714,43 @@ var ElementFinder = (() => {
526
714
  }
527
715
  return false;
528
716
  }
529
- function findElements(type = null, text = null, exact = false, parent = null) {
717
+ function getElementCounts(type = null, parent = null, options = null) {
718
+ const hasType = type !== null && type !== void 0;
719
+ if (hasType) {
720
+ if (typeof type !== "string") {
721
+ throw new TypeError(`type must be a string, got ${typeof type}`);
722
+ }
723
+ if (!ELEMENT_DEFINITIONS[type]) {
724
+ const message = `Unknown element type: ${type}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`;
725
+ if (options && options.failOnUnknownType === true) {
726
+ throw new TypeError(`Unknown element type: ${type}`);
727
+ }
728
+ console.warn(message);
729
+ return { [type]: 0 };
730
+ }
731
+ }
732
+ const counts = {};
733
+ const targetTypes = hasType ? [type] : Object.keys(ELEMENT_DEFINITIONS).filter((item) => item !== "element");
734
+ for (let i = 0; i < targetTypes.length; i++) {
735
+ counts[targetTypes[i]] = 0;
736
+ }
737
+ const frames = getAllFrames(window);
738
+ for (let i = 0; i < frames.length; i++) {
739
+ const frame = frames[i];
740
+ const allElements = getAllElements(parent || frame.document);
741
+ for (let j = 0; j < allElements.length; j++) {
742
+ const el = allElements[j];
743
+ for (let k = 0; k < targetTypes.length; k++) {
744
+ const targetType = targetTypes[k];
745
+ if (matchesType(el, targetType)) {
746
+ counts[targetType] += 1;
747
+ }
748
+ }
749
+ }
750
+ }
751
+ return counts;
752
+ }
753
+ function findElements(type = null, text = null, exact = false, parent = null, options = null) {
530
754
  if (text === null || text === void 0) {
531
755
  text = "";
532
756
  }
@@ -535,7 +759,11 @@ var ElementFinder = (() => {
535
759
  throw new TypeError(`type must be a string, got ${typeof type}`);
536
760
  }
537
761
  if (!ELEMENT_DEFINITIONS[type]) {
538
- console.warn(`Unknown element type: ${type}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`);
762
+ const message = `Unknown element type: ${type}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`;
763
+ if (options && options.failOnUnknownType === true) {
764
+ throw new TypeError(`Unknown element type: ${type}`);
765
+ }
766
+ console.warn(message);
539
767
  return { elements: [] };
540
768
  }
541
769
  }
@@ -567,18 +795,21 @@ var ElementFinder = (() => {
567
795
  const qualified = filteredMatches.map((item) => {
568
796
  const boundingBox = getBoundingBox(item.element);
569
797
  const tagName = item.element.tagName.toLowerCase();
798
+ const hidden = isHidden(item.element);
570
799
  if (!item.frame.isMainFrame) {
571
800
  return {
572
801
  boundingBox,
573
802
  tagName,
574
- frameIndex: item.frame.frameIndex
803
+ frameIndex: item.frame.frameIndex,
804
+ isHidden: hidden
575
805
  };
576
806
  }
577
807
  return {
578
808
  element: item.element,
579
809
  boundingBox,
580
810
  tagName,
581
- frameIndex: item.frame.frameIndex
811
+ frameIndex: item.frame.frameIndex,
812
+ isHidden: hidden
582
813
  };
583
814
  });
584
815
  return { elements: qualified };
@@ -637,23 +868,45 @@ var ElementFinder = (() => {
637
868
  }
638
869
  }
639
870
  }
871
+ let ancestor = el.parentElement;
872
+ while (ancestor) {
873
+ const ancestorSiblings = getSiblingElements(ancestor);
874
+ for (const sibling of ancestorSiblings) {
875
+ if (sibling !== ancestor) {
876
+ if (matchesType(sibling, targetType)) {
877
+ return sibling;
878
+ }
879
+ const siblingElements = getAllElements(sibling);
880
+ for (let i = 0; i < siblingElements.length; i++) {
881
+ if (matchesType(siblingElements[i], targetType)) {
882
+ return siblingElements[i];
883
+ }
884
+ }
885
+ }
886
+ }
887
+ ancestor = ancestor.parentElement;
888
+ }
640
889
  return null;
641
890
  }
642
- function findProbableElements(elementType, attributeText, exact = false, parent = null) {
891
+ function findProbableElements(elementType, attributeText, exact = false, parent = null, options = null) {
643
892
  const hasType = elementType !== null && elementType !== void 0 && elementType !== "";
644
893
  const hasText = attributeText !== null && attributeText !== void 0 && attributeText !== "";
645
894
  if (hasType && !hasText) {
646
- return findElementByType(elementType, parent);
895
+ return findElementsByType(elementType, parent, options);
647
896
  }
648
897
  if (!hasType && hasText) {
649
- return findElementByAttributes(attributeText, exact, parent);
898
+ return findElementsByAttribute(attributeText, exact, parent);
650
899
  }
651
900
  if (hasType) {
652
901
  if (typeof elementType !== "string") {
653
902
  throw new TypeError(`elementType must be a string, got ${typeof elementType}`);
654
903
  }
655
904
  if (!ELEMENT_DEFINITIONS[elementType]) {
656
- console.warn(`Unknown element type: ${elementType}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`);
905
+ const message = `Unknown element type: ${elementType}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`;
906
+ if (options && options.failOnUnknownType === true) {
907
+ throw new TypeError(`Unknown element type: ${elementType}`);
908
+ }
909
+ console.warn(message);
657
910
  return { elements: [] };
658
911
  }
659
912
  }
@@ -680,7 +933,9 @@ var ElementFinder = (() => {
680
933
  for (let i = 0; i < allElements.length; i++) {
681
934
  const el = allElements[i];
682
935
  if (!matchesAttribute(el, attributeText, exact)) continue;
683
- attributeMatches.push({ element: el, frame });
936
+ if (hasOwnMatch(el, attributeText, exact)) {
937
+ attributeMatches.push({ element: el, frame });
938
+ }
684
939
  }
685
940
  }
686
941
  const foundElements = /* @__PURE__ */ new Set();
@@ -706,18 +961,21 @@ var ElementFinder = (() => {
706
961
  const qualified = filteredMatches.map((item) => {
707
962
  const boundingBox = getBoundingBox(item.element);
708
963
  const tagName = item.element.tagName.toLowerCase();
964
+ const hidden = isHidden(item.element);
709
965
  if (!item.frame.isMainFrame) {
710
966
  return {
711
967
  boundingBox,
712
968
  tagName,
713
- frameIndex: item.frame.frameIndex
969
+ frameIndex: item.frame.frameIndex,
970
+ isHidden: hidden
714
971
  };
715
972
  }
716
973
  return {
717
974
  element: item.element,
718
975
  boundingBox,
719
976
  tagName,
720
- frameIndex: item.frame.frameIndex
977
+ frameIndex: item.frame.frameIndex,
978
+ isHidden: hidden
721
979
  };
722
980
  });
723
981
  return { elements: qualified };
@@ -755,6 +1013,78 @@ var ElementFinder = (() => {
755
1013
  }
756
1014
  }
757
1015
  }
1016
+ var animationPauseStack = [];
1017
+ function pauseAnimations() {
1018
+ const originalStyles = /* @__PURE__ */ new Map();
1019
+ const elements = getAllElements();
1020
+ for (const el of elements) {
1021
+ if (el && el.style) {
1022
+ if (el.style.animationPlayState !== "paused") {
1023
+ originalStyles.set(el, {
1024
+ animationPlayState: el.style.animationPlayState,
1025
+ transitionProperty: el.style.transitionProperty,
1026
+ webkitAnimationPlayState: el.style.webkitAnimationPlayState,
1027
+ webkitTransitionProperty: el.style.webkitTransitionProperty
1028
+ });
1029
+ el.style.animationPlayState = "paused";
1030
+ el.style.transitionProperty = "none";
1031
+ el.style.webkitAnimationPlayState = "paused";
1032
+ el.style.webkitTransitionProperty = "none";
1033
+ }
1034
+ }
1035
+ }
1036
+ let styleSheet = document.getElementById("elementfinder-animation-pause");
1037
+ if (!styleSheet) {
1038
+ styleSheet = document.createElement("style");
1039
+ styleSheet.id = "elementfinder-animation-pause";
1040
+ styleSheet.textContent = `
1041
+ *, *::before, *::after {
1042
+ animation-play-state: paused !important;
1043
+ transition-property: none !important;
1044
+ -webkit-animation-play-state: paused !important;
1045
+ -webkit-transition-property: none !important;
1046
+ }
1047
+ @media (prefers-reduced-motion: no-preference) {
1048
+ *, *::before, *::after {
1049
+ animation-duration: 0s !important;
1050
+ animation-iteration-count: 1 !important;
1051
+ transition-duration: 0s !important;
1052
+ }
1053
+ }
1054
+ `;
1055
+ document.head.appendChild(styleSheet);
1056
+ }
1057
+ const pauseState = { originalStyles, pausedCount: originalStyles.size };
1058
+ animationPauseStack.push(pauseState);
1059
+ return pauseState;
1060
+ }
1061
+ function resumeAnimations(pauseState) {
1062
+ if (!pauseState) {
1063
+ if (animationPauseStack.length === 0) return;
1064
+ pauseState = animationPauseStack.pop();
1065
+ } else {
1066
+ const index = animationPauseStack.indexOf(pauseState);
1067
+ if (index === -1) return;
1068
+ animationPauseStack.splice(index, 1);
1069
+ }
1070
+ const originalStyles = pauseState.originalStyles;
1071
+ if (originalStyles) {
1072
+ for (const [el, styles] of originalStyles) {
1073
+ if (el && el.style) {
1074
+ el.style.animationPlayState = styles.animationPlayState || "";
1075
+ el.style.transitionProperty = styles.transitionProperty || "";
1076
+ el.style.webkitAnimationPlayState = styles.webkitAnimationPlayState || "";
1077
+ el.style.webkitTransitionProperty = styles.webkitTransitionProperty || "";
1078
+ }
1079
+ }
1080
+ }
1081
+ if (animationPauseStack.length === 0) {
1082
+ const styleSheet = document.getElementById("elementfinder-animation-pause");
1083
+ if (styleSheet) {
1084
+ styleSheet.remove();
1085
+ }
1086
+ }
1087
+ }
758
1088
  function getValidTypes() {
759
1089
  return Object.keys(ELEMENT_DEFINITIONS);
760
1090
  }
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,i)=>{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:!(i=j(t,r))||i.enumerable});return e};var z=e=>V(M({},"__esModule",{value:!0}),e);var oe={};U(oe,{ELEMENT_DEFINITIONS:()=>p,findElementByAttributes:()=>q,findElementByType:()=>$,findElements:()=>Q,findProbableElements:()=>v,getAllElements:()=>w,getAllFrames:()=>N,getBoundingBox:()=>x,getSearchableAttributes:()=>H,getValidAttributes:()=>re,getValidTypes:()=>ne,highlight:()=>ee,matchesAttribute:()=>y,matchesType:()=>g,parseCondition:()=>k,parseXPath:()=>b,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[@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'",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=>b(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 b(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 o=1,l=!0;for(let a=1;a<e.length-1;a++)if(e[a]==="("?o++:e[a]===")"&&o--,o===0){l=!1;break}if(l)return b(e.slice(1,-1),t,n+1)}let i=T(e,"or");if(i.length>1){for(let o of i)if(b(o,t,n+1))return!0;return!1}let r=T(e,"and");if(r.length>1){for(let o of r)if(!b(o,t,n+1))return!1;return!0}return k(e,t,n)}function T(e,t){let n=[],i=0,r="",o=!1,l="",a=t==="or"?h.operatorOr:h.operatorAnd;for(let s=0;s<e.length;s++){let f=e[s];if((f==="'"||f==='"')&&(s===0||e[s-1]!=="\\")&&(o?f===l&&(o=!1):(o=!0,l=f)),!o&&(f==="("?i++:f===")"&&i--,i===0)){let u=e.slice(s).match(a);if(u){n.push(r.trim()),s+=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 i=e.match(h.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(h.contains);if(r)return(t.getAttribute(r[1])||"").toLowerCase().includes(r[2].toLowerCase());let o=e.match(h.attrEquals);if(o)return t.getAttribute(o[1])===o[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 s=e.match(h.ancestor);if(s){let f=t.parentElement;for(;f;){if(b(s[1],f,n+1))return!0;f=f.parentElement}return!1}return!1}var p=Object.freeze(I);function L(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 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+/),i="";for(let r of n)try{let o=document.getElementById(r);o&&(i+=o.textContent)}catch(o){}return i}function y(e,t,n=!1){if(e==null)return!1;if(t==null||t==="")return!0;if(G(e))return!1;let i=E;for(let l=0;l<i.length;l++){let a=i[l],s;try{s=e.getAttribute(a)}catch(f){continue}if(s){if(a==="aria-labelledby"){if(n?s===t:s.includes(t))return!0;let f=R(e);if(f&&(n?f===t:f.includes(t)))return!0}else if(n?s===t:s.includes(t))return!0}}let r=L(e);if(n?r===t:r.includes(t))return!0;let o=e.textContent;return!!(n?o.trim()===t:o.includes(t))}function g(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 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 o=r.children;for(let l=o.length-1;l>=0;l--)i.push(o[l]);try{if(r.shadowRoot){let l=r.shadowRoot.children;for(let a=l.length-1;a>=0;a--)i.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 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(o){o.name==="SecurityError"?console.warn("Skipping cross-origin iframe:",o.message):console.warn("Error accessing iframe:",o.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&&!p[e])return console.warn(`Unknown element type: ${e}. Valid types: ${Object.keys(p).join(", ")}`),{elements:[]};let n=[],i=N(window);for(let l of i){let a=w(t||l.document);for(let s=0;s<a.length;s++){let f=a[s];e&&!g(f,e)||n.push({element:f,frame:l})}}let r=[];if(n.length>0){let l=new Set(n.map(s=>s.element)),a=new Set;for(let s=n.length-1;s>=0;s--){let f=n[s],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),s=l.element.tagName.toLowerCase();return l.frame.isMainFrame?{element:l.element,boundingBox:a,tagName:s,frameIndex:l.frame.frameIndex}:{boundingBox:a,tagName:s,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 i=[],r=N(window);for(let a of r){let s=w(n||a.document);for(let f=0;f<s.length;f++){let c=s[f];y(c,e,t)&&i.push({element:c,frame:a})}}return{elements:i.filter(a=>{let s=a.element;if(B(s,e,t))return!0;for(let c of i)if(c.element!==s&&s.contains(c.element))return!1;return!0}).map(a=>{let s=x(a.element),f=a.element.tagName.toLowerCase();return a.frame.isMainFrame?{element:a.element,boundingBox:s,tagName:f,frameIndex:a.frame.frameIndex}:{boundingBox:s,tagName:f,frameIndex:a.frame.frameIndex}})}}function B(e,t,n=!1){if(t==null||t==="")return!0;let i=E;for(let o=0;o<i.length;o++){let l=i[o],a;try{a=e.getAttribute(l)}catch(s){continue}if(a){if(l==="aria-labelledby"){if(n?a===t:a.includes(t))return!0;let s=R(e);if(s&&(n?s===t:s.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,i=null){if(t==null&&(t=""),e!=null){if(typeof e!="string")throw new TypeError(`type must be a string, got ${typeof e}`);if(!p[e])return console.warn(`Unknown element type: ${e}. Valid types: ${Object.keys(p).join(", ")}`),{elements:[]}}if(t!==""&&typeof t!="string")throw new TypeError(`text must be a string, got ${typeof t}`);let r=[],o=N(window);for(let s of o){let f=w(i||s.document);for(let c=0;c<f.length;c++){let u=f[c];e!=null&&!g(u,e)||t!==""&&!y(u,t,n)||r.push({element:u,frame:s})}}return{elements:(t!==""?r.filter(s=>{let f=s.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(s=>{let f=x(s.element),c=s.element.tagName.toLowerCase();return s.frame.isMainFrame?{element:s.element,boundingBox:f,tagName:c,frameIndex:s.frame.frameIndex}:{boundingBox:f,tagName:c,frameIndex:s.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(g(n,t))return n;n=C(n)}let i=e.children||[];for(let o of i)if(g(o,t))return o;let r=J(e);for(let o of r)if(o!==e&&g(o,t))return o;for(let o of r){if(o===e)continue;let l=w(o);for(let a=0;a<l.length;a++)if(g(l[a],t))return l[a]}return null}function v(e,t,n=!1,i=null){let r=e!=null&&e!=="",o=t!=null&&t!=="";if(r&&!o)return $(e,i);if(!r&&o)return q(t,n,i);if(r){if(typeof e!="string")throw new TypeError(`elementType must be a string, got ${typeof e}`);if(!p[e])return console.warn(`Unknown element type: ${e}. Valid types: ${Object.keys(p).join(", ")}`),{elements:[]}}if(o&&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(i||c.document);for(let d=0;d<u.length;d++){let m=u[d];r&&!g(m,e)||o&&!y(m,t,n)||l.push({element:m,frame:c})}}if(l.length===0&&r&&o){let c=[];for(let d of a){let m=w(i||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:(o?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 i=P(e);for(let r=0;r<i.length;r++){let o=i[r],l=o.element?o.element:o;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 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"))}}function ne(){return Object.keys(p)}function re(){return[...E]}return z(oe);})();
1
+ var ElementFinder=(()=>{var P=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var Z=(e,t)=>{for(var n in t)P(e,n,{get:t[n],enumerable:!0})},Q=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of Y(t))!X.call(e,r)&&r!==n&&P(e,r,{get:()=>t[r],enumerable:!(o=W(t,r))||o.enumerable});return e};var G=e=>Q(P({},"__esModule",{value:!0}),e);var we={};Z(we,{ELEMENT_DEFINITIONS:()=>p,findElements:()=>ce,findElementsByAttribute:()=>V,findElementsByType:()=>F,findProbableElements:()=>me,getAllElements:()=>y,getAllFrames:()=>x,getBoundingBox:()=>A,getElementCounts:()=>fe,getElementDescriptor:()=>le,getSearchableAttributeValues:()=>j,getSearchableAttributes:()=>te,getValidAttributes:()=>be,getValidTypes:()=>ye,highlight:()=>de,isHidden:()=>k,matchesAttribute:()=>S,matchesType:()=>g,parseCondition:()=>H,parseXPath:()=>w,pauseAnimations:()=>ge,resumeAnimations:()=>pe,setSearchableAttributes:()=>ee,splitByOperator:()=>B,unhighlight:()=>he});var D={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 R=["placeholder","value","data-value","data-test-id","data-testid","id","resource-id","name","aria-label","hint","title","tooltip","alt","src","aria-labelledby"];var b={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},v=100,C=new Map;for(let[e,t]of Object.entries(D))t==="true()"?C.set(e,()=>!0):C.set(e,n=>w(t,n));var E=R;function ee(e){if(!Array.isArray(e))throw new TypeError("attributes must be an array");E=e}function te(){return[...E]}function j(e){if(e==null||e.nodeType!==Node.ELEMENT_NODE)return{};let t={},n=E;for(let o=0;o<n.length;o++){let r=n[o],i;try{i=e.getAttribute(r)}catch(l){continue}i!=null&&i!==""&&(t[r]=i)}return t}function T(e){return e==null?"":String(e).replace(/\s+/g," ").trim()}function ne(e){let t=T(e);if(!t)return"";let n=t.split(/[?#]/)[0],o=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\")),r=o>=0?n.slice(o+1):n,i=r.lastIndexOf(".");return i>0?r.slice(0,i):r}function re(e){let t=e.getAttribute("aria-labelledby");if(!t)return"";let n=t.split(/\s+/),o=e.ownerDocument||document,r="";for(let i of n)try{let l=o.getElementById(i);if(!l)continue;let s=T(l.textContent);s&&(r=r?`${r} ${s}`:s)}catch(l){}return r}function q(e){let t=j(e),n=E;for(let i=0;i<n.length;i++){let l=n[i];if(!Object.prototype.hasOwnProperty.call(t,l))continue;let s=l==="aria-labelledby"?re(e):l==="src"?ne(t[l]):t[l],a=T(s);if(a)return{attributeName:l,identifiableText:a}}let o=T(L(e));if(o)return{attributeName:"text",identifiableText:o};let r=T(e.textContent);return r?{attributeName:"text",identifiableText:r}:null}function oe(e){if(!e||!e.ownerDocument)return null;try{let t=x(window);for(let n=0;n<t.length;n++)if(t[n].document===e.ownerDocument)return t[n].document}catch(t){}return e.ownerDocument}function ie(e,t){let n=oe(e);if(!n)return{index:1};let o=y(n),r=1,i=0;for(let l=0;l<o.length;l++){let s=o[l];if(s!==e&&(s===n.documentElement||s.tagName==="BODY"))continue;let a=q(s);!a||a.identifiableText!==t||(i++,s===e&&(r=i))}return{index:r}}function se(e){if(e==null||e.nodeType!==Node.ELEMENT_NODE)return null;let t=Object.keys(p);for(let n=0;n<t.length;n++){let o=t[n];if(o!=="element"&&g(e,o))return o}return"element"}function le(e){if(e==null||e.nodeType!==Node.ELEMENT_NODE)return{identifiableText:null,attributeName:null,index:1,type:null,tagName:null};let t=se(e),n=q(e);if(!n)return{identifiableText:null,attributeName:null,index:1,type:t,tagName:e.tagName.toLowerCase()};let o=ie(e,n.identifiableText);return{identifiableText:n.identifiableText,attributeName:n.attributeName,index:o.index,type:t,tagName:e.tagName.toLowerCase()}}function w(e,t,n=0){if(e==null||t==null)return!1;if(n>v)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 i=1,l=!0;for(let s=1;s<e.length-1;s++)if(e[s]==="("?i++:e[s]===")"&&i--,i===0){l=!1;break}if(l)return w(e.slice(1,-1),t,n+1)}let o=B(e,"or");if(o.length>1){for(let i of o)if(w(i,t,n+1))return!0;return!1}let r=B(e,"and");if(r.length>1){for(let i of r)if(!w(i,t,n+1))return!1;return!0}return H(e,t,n)}function B(e,t){let n=[],o=0,r="",i=!1,l="",s=t==="or"?b.operatorOr:b.operatorAnd;for(let a=0;a<e.length;a++){let f=e[a];if((f==="'"||f==='"')&&(a===0||e[a-1]!=="\\")&&(i?f===l&&(i=!1):(i=!0,l=f)),!i&&(f==="("?o++:f===")"&&o--,o===0)){let c=e.slice(a).match(s);if(c){n.push(r.trim()),a+=c[0].length-1,r="";continue}}r+=f}return r.trim()&&n.push(r.trim()),n}function H(e,t,n=0){if(e==null||t==null)return!1;e=e.trim();let o=e.match(b.selfWithTag);if(o){let f=o[1].toUpperCase();return t.tagName!==f?!1:o[2]?w(o[2],t,n+1):!0}let r=e.match(b.contains);if(r)return(t.getAttribute(r[1])||"").toLowerCase().includes(r[2].toLowerCase());let i=e.match(b.attrEquals);if(i)return t.getAttribute(i[1])===i[2];let l=e.match(b.attrExists);if(l)return t.hasAttribute(l[1]);let s=e.match(b.descendant);if(s)return t.querySelector(s[1])!==null;let a=e.match(b.ancestor);if(a){let f=t.parentElement;for(;f;){if(w(a[1],f,n+1))return!0;f=f.parentElement}return!1}return!1}var p=Object.freeze(D);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 ae(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 _(e){let t=e.getAttribute("aria-labelledby");if(!t)return"";let n=t.split(/\s+/),o="";for(let r of n)try{let i=document.getElementById(r);i&&(o+=i.textContent)}catch(i){}return o}function S(e,t,n=!1){if(e==null)return!1;if(t==null||t==="")return!0;if(ae(e))return!1;let o=E;for(let l=0;l<o.length;l++){let s=o[l],a;try{a=e.getAttribute(s)}catch(f){continue}if(a){if(s==="aria-labelledby"){if(n?a===t:a.includes(t))return!0;let f=_(e);if(f&&(n?f===t:f.includes(t)))return!0}else if(n?a===t:a.includes(t))return!0}}let r=L(e);if(n?r===t:r.includes(t))return!0;let i=e.textContent;return!!(n?i.trim()===t:i.includes(t))}function g(e,t){if(e==null)return!1;let n=C.get(t);return n?n(e):!1}function y(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 i=r.children;for(let l=i.length-1;l>=0;l--)o.push(i[l]);try{if(r.shadowRoot){let l=r.shadowRoot.children;for(let s=l.length-1;s>=0;s--)o.push(l[s])}}catch(l){}}return t}function x(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(i){i.name==="SecurityError"?console.warn("Skipping cross-origin iframe:",i.message):console.warn("Error accessing iframe:",i.message)}}}catch(n){console.warn("Error getting frames:",n.message)}return t}function A(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 k(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 F(e="element",t=null,n=null){if(e==null&&(e="element"),typeof e!="string")throw new TypeError(`type must be a string, got ${typeof e}`);let o=n&&n.failOnUnknownType===!0;if(e&&!p[e]){let a=`Unknown element type: ${e}. Valid types: ${Object.keys(p).join(", ")}`;if(o)throw new TypeError(`Unknown element type: ${e}`);return console.warn(a),{elements:[]}}let r=[],i=x(window);for(let a of i){let f=y(t||a.document);for(let u=0;u<f.length;u++){let c=f[u];e&&!g(c,e)||r.push({element:c,frame:a})}}let l=[];if(r.length>0){let a=new Set(r.map(u=>u.element)),f=new Set;for(let u=r.length-1;u>=0;u--){let c=r[u],m=c.element;if(!f.has(m)){l.unshift(c);let d=m.parentElement;for(;d;)a.has(d)&&f.add(d),d=d.parentElement}}}return{elements:l.map(a=>{let f=A(a.element),u=a.element.tagName.toLowerCase(),c=k(a.element);return a.frame.isMainFrame?{element:a.element,boundingBox:f,tagName:u,frameIndex:a.frame.frameIndex,isHidden:c}:{boundingBox:f,tagName:u,frameIndex:a.frame.frameIndex,isHidden:c}})}}function V(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=x(window);for(let s of r){let a=y(n||s.document);for(let f=0;f<a.length;f++){let u=a[f];S(u,e,t)&&o.push({element:u,frame:s})}}return{elements:o.filter(s=>{let a=s.element;if(I(a,e,t))return!0;for(let u of o)if(u.element!==a&&a.contains(u.element))return!1;return!0}).map(s=>{let a=A(s.element),f=s.element.tagName.toLowerCase(),u=k(s.element);return s.frame.isMainFrame?{element:s.element,boundingBox:a,tagName:f,frameIndex:s.frame.frameIndex,isHidden:u}:{boundingBox:a,tagName:f,frameIndex:s.frame.frameIndex,isHidden:u}})}}function I(e,t,n=!1){if(t==null||t==="")return!0;let o=E;for(let i=0;i<o.length;i++){let l=o[i],s;try{s=e.getAttribute(l)}catch(a){continue}if(s){if(l==="aria-labelledby"){if(n?s===t:s.includes(t))return!0;let a=_(e);if(a&&(n?a===t:a.includes(t)))return!0}else if(n?s===t:s.includes(t))return!0}}let r=L(e);return!!(n?r===t:r.includes(t))}function fe(e=null,t=null,n=null){let o=e!=null;if(o){if(typeof e!="string")throw new TypeError(`type must be a string, got ${typeof e}`);if(!p[e]){let s=`Unknown element type: ${e}. Valid types: ${Object.keys(p).join(", ")}`;if(n&&n.failOnUnknownType===!0)throw new TypeError(`Unknown element type: ${e}`);return console.warn(s),{[e]:0}}}let r={},i=o?[e]:Object.keys(p).filter(s=>s!=="element");for(let s=0;s<i.length;s++)r[i[s]]=0;let l=x(window);for(let s=0;s<l.length;s++){let a=l[s],f=y(t||a.document);for(let u=0;u<f.length;u++){let c=f[u];for(let m=0;m<i.length;m++){let d=i[m];g(c,d)&&(r[d]+=1)}}}return r}function ce(e=null,t=null,n=!1,o=null,r=null){if(t==null&&(t=""),e!=null){if(typeof e!="string")throw new TypeError(`type must be a string, got ${typeof e}`);if(!p[e]){let f=`Unknown element type: ${e}. Valid types: ${Object.keys(p).join(", ")}`;if(r&&r.failOnUnknownType===!0)throw new TypeError(`Unknown element type: ${e}`);return console.warn(f),{elements:[]}}}if(t!==""&&typeof t!="string")throw new TypeError(`text must be a string, got ${typeof t}`);let i=[],l=x(window);for(let f of l){let u=y(o||f.document);for(let c=0;c<u.length;c++){let m=u[c];e!=null&&!g(m,e)||t!==""&&!S(m,t,n)||i.push({element:m,frame:f})}}return{elements:(t!==""?i.filter(f=>{let u=f.element;if(I(u,t,n))return!0;for(let m of i)if(m.element!==u&&u.contains(m.element))return!1;return!0}):i).map(f=>{let u=A(f.element),c=f.element.tagName.toLowerCase(),m=k(f.element);return f.frame.isMainFrame?{element:f.element,boundingBox:u,tagName:c,frameIndex:f.frame.frameIndex,isHidden:m}:{boundingBox:u,tagName:c,frameIndex:f.frame.frameIndex,isHidden:m}})}}function $(e){if(e.parentElement)return e.parentElement;try{let t=e.getRootNode();if(t&&t.host)return t.host}catch(t){}return null}function U(e){let t=$(e);if(!t)return[];if(t.shadowRoot)try{return Array.from(t.shadowRoot.children)}catch(n){return[]}return Array.from(t.children)}function ue(e,t){let n=$(e);for(;n;){if(g(n,t))return n;n=$(n)}let o=e.children||[];for(let l of o)if(g(l,t))return l;let r=U(e);for(let l of r)if(l!==e&&g(l,t))return l;for(let l of r){if(l===e)continue;let s=y(l);for(let a=0;a<s.length;a++)if(g(s[a],t))return s[a]}let i=e.parentElement;for(;i;){let l=U(i);for(let s of l)if(s!==i){if(g(s,t))return s;let a=y(s);for(let f=0;f<a.length;f++)if(g(a[f],t))return a[f]}i=i.parentElement}return null}function me(e,t,n=!1,o=null,r=null){let i=e!=null&&e!=="",l=t!=null&&t!=="";if(i&&!l)return F(e,o,r);if(!i&&l)return V(t,n,o);if(i){if(typeof e!="string")throw new TypeError(`elementType must be a string, got ${typeof e}`);if(!p[e]){let c=`Unknown element type: ${e}. Valid types: ${Object.keys(p).join(", ")}`;if(r&&r.failOnUnknownType===!0)throw new TypeError(`Unknown element type: ${e}`);return console.warn(c),{elements:[]}}}if(l&&typeof t!="string")throw new TypeError(`attributeText must be a string, got ${typeof t}`);let s=[],a=x(window);for(let c of a){let m=y(o||c.document);for(let d=0;d<m.length;d++){let h=m[d];i&&!g(h,e)||l&&!S(h,t,n)||s.push({element:h,frame:c})}}if(s.length===0&&i&&l){let c=[];for(let d of a){let h=y(o||d.document);for(let M=0;M<h.length;M++){let O=h[M];S(O,t,n)&&I(O,t,n)&&c.push({element:O,frame:d})}}let m=new Set;for(let d of c){let h=ue(d.element,e);h&&!m.has(h)&&(m.add(h),s.push({element:h,frame:d.frame}))}}return{elements:(l?s.filter(c=>{let m=c.element;if(I(m,t,n))return!0;for(let h of s)if(h.element!==m&&m.contains(h.element))return!1;return!0}):s).map(c=>{let m=A(c.element),d=c.element.tagName.toLowerCase(),h=k(c.element);return c.frame.isMainFrame?{element:c.element,boundingBox:m,tagName:d,frameIndex:c.frame.frameIndex,isHidden:h}:{boundingBox:m,tagName:d,frameIndex:c.frame.frameIndex,isHidden:h}})}}function z(e){return e?e&&e.elements&&Array.isArray(e.elements)?e.elements:Array.isArray(e)?e:[e]:[]}function de(e,t="red",n=3){let o=z(e);for(let r=0;r<o.length;r++){let i=o[r],l=i.element?i.element:i;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 he(e){let t=z(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"))}}var N=[];function ge(){let e=new Map,t=y();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 o={originalStyles:e,pausedCount:e.size};return N.push(o),o}function pe(e){if(e){let n=N.indexOf(e);if(n===-1)return;N.splice(n,1)}else{if(N.length===0)return;e=N.pop()}let t=e.originalStyles;if(t)for(let[n,o]of t)n&&n.style&&(n.style.animationPlayState=o.animationPlayState||"",n.style.transitionProperty=o.transitionProperty||"",n.style.webkitAnimationPlayState=o.webkitAnimationPlayState||"",n.style.webkitTransitionProperty=o.webkitTransitionProperty||"");if(N.length===0){let n=document.getElementById("elementfinder-animation-pause");n&&n.remove()}}function ye(){return Object.keys(p)}function be(){return[...E]}return G(we);})();
package/package.json CHANGED
@@ -1,29 +1,23 @@
1
1
  {
2
2
  "name": "@nodebug/browser-element-finder",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "Browser Element Finder - Find elements by type and text content",
5
5
  "exports": {
6
6
  ".": {
7
- "browser": "./index.js",
8
- "require": "./index.js",
9
7
  "import": "./src/element-finder.js",
8
+ "default": "./src/element-finder.js"
9
+ },
10
+ "./browser": {
10
11
  "default": "./index.js"
11
12
  },
12
13
  "./min": {
13
- "browser": "./index.min.js",
14
- "require": "./index.min.js",
15
- "import": "./index.min.js",
16
14
  "default": "./index.min.js"
17
15
  },
18
16
  "./element-definitions.json": {
19
- "browser": "./src/element-definitions.json",
20
- "require": "./src/element-definitions.json",
21
17
  "import": "./src/element-definitions.json",
22
18
  "default": "./src/element-definitions.json"
23
19
  },
24
20
  "./searchable-attributes.json": {
25
- "browser": "./src/searchable-attributes.json",
26
- "require": "./src/searchable-attributes.json",
27
21
  "import": "./src/searchable-attributes.json",
28
22
  "default": "./src/searchable-attributes.json"
29
23
  }
@@ -54,7 +48,8 @@
54
48
  "url": "https://github.com/node-bug/browser-element-finder/issues"
55
49
  },
56
50
  "scripts": {
57
- "build": "node build.js",
51
+ "build": "node scripts/build.js",
52
+ "prepack": "npm run build",
58
53
  "lint": "eslint .",
59
54
  "test": "npm run build && vitest run",
60
55
  "test:watch": "npm run build && vitest",
@@ -63,7 +58,7 @@
63
58
  },
64
59
  "devDependencies": {
65
60
  "@eslint/js": "^10.0.1",
66
- "@vitest/coverage-v8": "^3.2.4",
61
+ "@vitest/coverage-v8": "^3.2.6",
67
62
  "esbuild": "^0.28.0",
68
63
  "eslint": "^10.2.0",
69
64
  "eslint-config-prettier": "^10.1.8",
@@ -73,10 +68,10 @@
73
68
  "lint-staged": "^16.4.0",
74
69
  "prettier": "^3.8.2",
75
70
  "selenium-webdriver": "^4.43.0",
76
- "vitest": "^3.0.0"
71
+ "vitest": "^3.2.6"
77
72
  },
78
73
  "lint-staged": {
79
- "*.{js,jsx,ts,tsx}": [
74
+ "*.js": [
80
75
  "eslint --fix"
81
76
  ],
82
77
  "*.{json,yaml,yml,md,sh,groovy}": [
@@ -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",