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