@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 +119 -64
- package/index.js +155 -18
- package/index.min.js +15 -1
- package/package.json +1 -1
- package/src/element-definitions.json +3 -3
- package/src/searchable-attributes.json +1 -0
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 `
|
|
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.
|
|
97
|
+
const results = ElementFinder.findElements('button')
|
|
96
98
|
// Find by text
|
|
97
|
-
const results = ElementFinder.
|
|
99
|
+
const results = ElementFinder.findElements('button', 'Submit')
|
|
98
100
|
// Find by text only
|
|
99
|
-
const results = ElementFinder.
|
|
101
|
+
const results = ElementFinder.findElements(null, 'seleniumbase')
|
|
100
102
|
// Check visibility of found elements
|
|
101
103
|
results.elements.forEach((e) => {
|
|
102
|
-
console.log('
|
|
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.
|
|
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
|
-
| `
|
|
158
|
-
| `
|
|
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
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
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 `
|
|
211
|
-
- If only `text` is provided: delegates to `
|
|
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
|
|
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
|
|
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
|
-
### `
|
|
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
|
-
### `
|
|
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.
|
|
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.
|
|
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.
|
|
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` | `<
|
|
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`, `
|
|
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
|
|
54
|
-
colorpicker: "self::input
|
|
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
|
|
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
|
|
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
|
|
697
|
+
return findElementsByType(elementType, parent);
|
|
638
698
|
}
|
|
639
699
|
if (!hasType && hasText) {
|
|
640
|
-
return
|
|
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
|
-
|
|
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
|
@@ -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
|
|
10
|
-
"colorpicker": "self::input
|
|
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'",
|