@nodebug/browser-element-finder 1.1.8 → 1.2.0
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 +136 -39
- package/index.js +209 -7
- package/index.min.js +2 -2
- package/package.json +10 -14
- package/src/element-finder.js +1558 -0
package/README.md
CHANGED
|
@@ -20,6 +20,14 @@ const results = ElementFinder.findElements('button', 'Submit')
|
|
|
20
20
|
// Find by text only (any type)
|
|
21
21
|
const results = ElementFinder.findElements(null, 'seleniumbase')
|
|
22
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
|
+
|
|
23
31
|
// Find in all frames (default)
|
|
24
32
|
const results = ElementFinder.findElements('button')
|
|
25
33
|
|
|
@@ -45,6 +53,7 @@ results.elements.forEach((e) => {
|
|
|
45
53
|
- For iframe results, switch context before interacting (see Selenium/Playwright docs)
|
|
46
54
|
- Use `getValidTypes()` to enumerate all supported semantic types
|
|
47
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
|
|
48
57
|
|
|
49
58
|
---
|
|
50
59
|
|
|
@@ -99,6 +108,10 @@ const results = ElementFinder.findElements('button')
|
|
|
99
108
|
const results = ElementFinder.findElements('button', 'Submit')
|
|
100
109
|
// Find by text only
|
|
101
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')
|
|
102
115
|
// Check visibility of found elements
|
|
103
116
|
results.elements.forEach((e) => {
|
|
104
117
|
console.log('Hidden:', e.isHidden)
|
|
@@ -144,6 +157,20 @@ ElementFinder.setSearchableAttributes([
|
|
|
144
157
|
])
|
|
145
158
|
```
|
|
146
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
|
+
|
|
147
174
|
### Pausing Animations for Screenshots
|
|
148
175
|
|
|
149
176
|
When taking screenshots or performing visual assertions, animations can cause flaky tests. Use `pauseAnimations()` and `resumeAnimations()` to freeze and restore animations:
|
|
@@ -177,8 +204,8 @@ The package exports JSON files containing element type definitions and searchabl
|
|
|
177
204
|
|
|
178
205
|
```javascript
|
|
179
206
|
// ESM - Import JSON directly
|
|
180
|
-
import ELEMENT_DEFINITIONS from '@nodebug/browser-element-finder/element-definitions.json'
|
|
181
|
-
import SEARCHABLE_ATTRIBUTES from '@nodebug/browser-element-finder/searchable-attributes.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' }
|
|
182
209
|
|
|
183
210
|
// Get all valid element types
|
|
184
211
|
console.log(Object.keys(ELEMENT_DEFINITIONS)) // ['button', 'checkbox', 'dropdown', ...]
|
|
@@ -187,38 +214,37 @@ console.log(Object.keys(ELEMENT_DEFINITIONS)) // ['button', 'checkbox', 'dropdow
|
|
|
187
214
|
console.log(SEARCHABLE_ATTRIBUTES) // ['placeholder', 'value', 'data-test-id', ...]
|
|
188
215
|
```
|
|
189
216
|
|
|
190
|
-
|
|
191
|
-
// CommonJS - Use require
|
|
192
|
-
const ELEMENT_DEFINITIONS = require('@nodebug/browser-element-finder/element-definitions.json')
|
|
193
|
-
const SEARCHABLE_ATTRIBUTES = require('@nodebug/browser-element-finder/searchable-attributes.json')
|
|
194
|
-
```
|
|
217
|
+
The package is ESM-only (`"type": "module"`), so CommonJS `require()` examples are not supported.
|
|
195
218
|
|
|
196
219
|
---
|
|
197
220
|
|
|
198
221
|
## API Summary
|
|
199
222
|
|
|
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
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
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) |
|
|
222
248
|
|
|
223
249
|
---
|
|
224
250
|
|
|
@@ -226,12 +252,12 @@ const SEARCHABLE_ATTRIBUTES = require('@nodebug/browser-element-finder/searchabl
|
|
|
226
252
|
|
|
227
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.
|
|
228
254
|
|
|
229
|
-
| Parameter | Type | Default | Description
|
|
230
|
-
| --------- | --------- | ------- |
|
|
231
|
-
| `type` | `string` | `null` | Element type (see supported types below). If `null`, matches any type. Throws `TypeError` for non-string values. |
|
|
232
|
-
| `text` | `string` | `null` | Text to search for in content/attributes. If `null`/`''`/`undefined`, matches any text.
|
|
233
|
-
| `exact` | `boolean` | `false` | Exact text match vs substring (only used when text is provided)
|
|
234
|
-
| `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 |
|
|
235
261
|
|
|
236
262
|
**Returns**: `{ elements: [{ element, boundingBox, tagName, frameIndex, isHidden }] }`
|
|
237
263
|
|
|
@@ -332,6 +358,47 @@ Sets custom searchable attributes.
|
|
|
332
358
|
|
|
333
359
|
Returns the current searchable attributes array.
|
|
334
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
|
+
|
|
335
402
|
### `matchesType(el, type)`
|
|
336
403
|
|
|
337
404
|
Checks if an element matches the specified type definition.
|
|
@@ -349,6 +416,36 @@ Finds elements by type only. Searches all frames by default.
|
|
|
349
416
|
| `type` | `string` | `"element"` | Element type (see supported types below). Throws `TypeError` for non-string values. |
|
|
350
417
|
| `parent` | `Element` | `null` | Parent element to search within |
|
|
351
418
|
|
|
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
|
+
|
|
352
449
|
### `findElementsByAttribute(value, exact, parent)`
|
|
353
450
|
|
|
354
451
|
Finds elements by text/attribute value only. Searches all frames by default.
|
|
@@ -471,19 +568,19 @@ Both `column` and `cell` types find table cells, but they behave differently:
|
|
|
471
568
|
|
|
472
569
|
```javascript
|
|
473
570
|
// Find all cells in the "City" column (header + 3 data cells = 4 total)
|
|
474
|
-
const columnResult = ElementFinder.
|
|
571
|
+
const columnResult = ElementFinder.findElements('column', 'City')
|
|
475
572
|
// Returns: [th:City, td:New York, td:London, td:Paris]
|
|
476
573
|
|
|
477
574
|
// Find all cells when searching for a data cell value
|
|
478
|
-
const columnResult2 = ElementFinder.
|
|
575
|
+
const columnResult2 = ElementFinder.findElements('column', 'Paris')
|
|
479
576
|
// Returns: [th:City, td:New York, td:London, td:Paris]
|
|
480
577
|
|
|
481
578
|
// Find only the specific cell containing "Paris"
|
|
482
|
-
const cellResult = ElementFinder.
|
|
579
|
+
const cellResult = ElementFinder.findElements('cell', 'Paris')
|
|
483
580
|
// Returns: [td:Paris]
|
|
484
581
|
|
|
485
582
|
// Find by header text with cell type - returns only the header cell
|
|
486
|
-
const headerCell = ElementFinder.
|
|
583
|
+
const headerCell = ElementFinder.findElements('cell', 'City')
|
|
487
584
|
// Returns: [] (no td elements match "City" header text)
|
|
488
585
|
```
|
|
489
586
|
|
package/index.js
CHANGED
|
@@ -28,6 +28,9 @@ var ElementFinder = (() => {
|
|
|
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,
|
|
@@ -122,6 +125,156 @@ var ElementFinder = (() => {
|
|
|
122
125
|
function getSearchableAttributes() {
|
|
123
126
|
return [...SEARCHABLE_ATTRIBUTES];
|
|
124
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
|
+
}
|
|
125
278
|
function parseXPath(expr, el, depth = 0) {
|
|
126
279
|
if (expr == null || el == null) return false;
|
|
127
280
|
if (depth > MAX_RECURSION_DEPTH) {
|
|
@@ -411,15 +564,20 @@ var ElementFinder = (() => {
|
|
|
411
564
|
}
|
|
412
565
|
return false;
|
|
413
566
|
}
|
|
414
|
-
function findElementsByType(type = "element", parent = null) {
|
|
567
|
+
function findElementsByType(type = "element", parent = null, options = null) {
|
|
415
568
|
if (type === null || type === void 0) {
|
|
416
569
|
type = "element";
|
|
417
570
|
}
|
|
418
571
|
if (typeof type !== "string") {
|
|
419
572
|
throw new TypeError(`type must be a string, got ${typeof type}`);
|
|
420
573
|
}
|
|
574
|
+
const failOnUnknownType = options && options.failOnUnknownType === true;
|
|
421
575
|
if (type && !ELEMENT_DEFINITIONS[type]) {
|
|
422
|
-
|
|
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);
|
|
423
581
|
return { elements: [] };
|
|
424
582
|
}
|
|
425
583
|
const matches = [];
|
|
@@ -556,7 +714,43 @@ var ElementFinder = (() => {
|
|
|
556
714
|
}
|
|
557
715
|
return false;
|
|
558
716
|
}
|
|
559
|
-
function
|
|
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) {
|
|
560
754
|
if (text === null || text === void 0) {
|
|
561
755
|
text = "";
|
|
562
756
|
}
|
|
@@ -565,7 +759,11 @@ var ElementFinder = (() => {
|
|
|
565
759
|
throw new TypeError(`type must be a string, got ${typeof type}`);
|
|
566
760
|
}
|
|
567
761
|
if (!ELEMENT_DEFINITIONS[type]) {
|
|
568
|
-
|
|
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);
|
|
569
767
|
return { elements: [] };
|
|
570
768
|
}
|
|
571
769
|
}
|
|
@@ -690,11 +888,11 @@ var ElementFinder = (() => {
|
|
|
690
888
|
}
|
|
691
889
|
return null;
|
|
692
890
|
}
|
|
693
|
-
function findProbableElements(elementType, attributeText, exact = false, parent = null) {
|
|
891
|
+
function findProbableElements(elementType, attributeText, exact = false, parent = null, options = null) {
|
|
694
892
|
const hasType = elementType !== null && elementType !== void 0 && elementType !== "";
|
|
695
893
|
const hasText = attributeText !== null && attributeText !== void 0 && attributeText !== "";
|
|
696
894
|
if (hasType && !hasText) {
|
|
697
|
-
return findElementsByType(elementType, parent);
|
|
895
|
+
return findElementsByType(elementType, parent, options);
|
|
698
896
|
}
|
|
699
897
|
if (!hasType && hasText) {
|
|
700
898
|
return findElementsByAttribute(attributeText, exact, parent);
|
|
@@ -704,7 +902,11 @@ var ElementFinder = (() => {
|
|
|
704
902
|
throw new TypeError(`elementType must be a string, got ${typeof elementType}`);
|
|
705
903
|
}
|
|
706
904
|
if (!ELEMENT_DEFINITIONS[elementType]) {
|
|
707
|
-
|
|
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);
|
|
708
910
|
return { elements: [] };
|
|
709
911
|
}
|
|
710
912
|
}
|
package/index.min.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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=`
|
|
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
2
|
*, *::before, *::after {
|
|
3
3
|
animation-play-state: paused !important;
|
|
4
4
|
transition-property: none !important;
|
|
@@ -12,4 +12,4 @@ var ElementFinder=(()=>{var I=Object.defineProperty;var F=Object.getOwnPropertyD
|
|
|
12
12
|
transition-duration: 0s !important;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
`,document.head.appendChild(n));let
|
|
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);})();
|