@muuktest/amikoo-playwright 2.0.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 +26 -0
- package/dist/capture.cjs +57 -0
- package/dist/capture.cjs.map +1 -0
- package/dist/capture.d.cts +6 -0
- package/dist/capture.d.ts +6 -0
- package/dist/capture.js +23 -0
- package/dist/capture.js.map +1 -0
- package/dist/cli/agent-setup.cjs +68 -0
- package/dist/cli/agent-setup.cjs.map +1 -0
- package/dist/cli/agent-setup.d.cts +11 -0
- package/dist/cli/agent-setup.d.ts +11 -0
- package/dist/cli/agent-setup.js +31 -0
- package/dist/cli/agent-setup.js.map +1 -0
- package/dist/cli/amikoo-playwright-agent.md +82 -0
- package/dist/cli/fixture-creator.cjs +163 -0
- package/dist/cli/fixture-creator.cjs.map +1 -0
- package/dist/cli/fixture-creator.d.cts +12 -0
- package/dist/cli/fixture-creator.d.ts +12 -0
- package/dist/cli/fixture-creator.js +128 -0
- package/dist/cli/fixture-creator.js.map +1 -0
- package/dist/cli/index.cjs +134 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +111 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/mcp-setup.cjs +116 -0
- package/dist/cli/mcp-setup.cjs.map +1 -0
- package/dist/cli/mcp-setup.d.cts +12 -0
- package/dist/cli/mcp-setup.d.ts +12 -0
- package/dist/cli/mcp-setup.js +81 -0
- package/dist/cli/mcp-setup.js.map +1 -0
- package/dist/cli/scanner.cjs +137 -0
- package/dist/cli/scanner.cjs.map +1 -0
- package/dist/cli/scanner.d.cts +16 -0
- package/dist/cli/scanner.d.ts +16 -0
- package/dist/cli/scanner.js +100 -0
- package/dist/cli/scanner.js.map +1 -0
- package/dist/cli/test-updater.cjs +131 -0
- package/dist/cli/test-updater.cjs.map +1 -0
- package/dist/cli/test-updater.d.cts +12 -0
- package/dist/cli/test-updater.d.ts +12 -0
- package/dist/cli/test-updater.js +96 -0
- package/dist/cli/test-updater.js.map +1 -0
- package/dist/dom/buildDomTree.js +1760 -0
- package/dist/helpers/dom-extractor.cjs +344 -0
- package/dist/helpers/dom-extractor.cjs.map +1 -0
- package/dist/helpers/dom-extractor.d.cts +9 -0
- package/dist/helpers/dom-extractor.d.ts +9 -0
- package/dist/helpers/dom-extractor.js +318 -0
- package/dist/helpers/dom-extractor.js.map +1 -0
- package/dist/helpers/dom-service.cjs +365 -0
- package/dist/helpers/dom-service.cjs.map +1 -0
- package/dist/helpers/dom-service.d.cts +82 -0
- package/dist/helpers/dom-service.d.ts +82 -0
- package/dist/helpers/dom-service.js +338 -0
- package/dist/helpers/dom-service.js.map +1 -0
- package/dist/helpers/failure-analyzer.cjs +276 -0
- package/dist/helpers/failure-analyzer.cjs.map +1 -0
- package/dist/helpers/failure-analyzer.d.cts +100 -0
- package/dist/helpers/failure-analyzer.d.ts +100 -0
- package/dist/helpers/failure-analyzer.js +241 -0
- package/dist/helpers/failure-analyzer.js.map +1 -0
- package/dist/index.cjs +32 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,1760 @@
|
|
|
1
|
+
if (!window._highlightQueue) window._highlightQueue = [];
|
|
2
|
+
if (!window._badgePositions) window._badgePositions = [];
|
|
3
|
+
function buildDomTreeMain(args) {
|
|
4
|
+
window._highlightQueue = [];
|
|
5
|
+
window._badgePositions = [];
|
|
6
|
+
window._highlightQueue.length = 0;
|
|
7
|
+
window._badgePositions.length = 0;
|
|
8
|
+
let {
|
|
9
|
+
doHighlightElements = true,
|
|
10
|
+
focusHighlightIndex = -1,
|
|
11
|
+
viewportExpansion = 0,
|
|
12
|
+
debugMode = false
|
|
13
|
+
} = args;
|
|
14
|
+
let highlightIndex = 0;
|
|
15
|
+
try {
|
|
16
|
+
const centerX = window.innerWidth / 2;
|
|
17
|
+
const centerY = window.innerHeight / 2;
|
|
18
|
+
const elementsAtCenter = document.elementsFromPoint(centerX, centerY);
|
|
19
|
+
let hasBackdrop = false;
|
|
20
|
+
for (const el of elementsAtCenter) {
|
|
21
|
+
const style = window.getComputedStyle(el);
|
|
22
|
+
const rect = el.getBoundingClientRect();
|
|
23
|
+
const bgColor = style.backgroundColor;
|
|
24
|
+
const isBackdrop = rect.width >= window.innerWidth * 0.99 && rect.height >= window.innerHeight * 0.99 && bgColor.includes("rgba") && bgColor.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/) && (() => {
|
|
25
|
+
const match = bgColor.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/);
|
|
26
|
+
if (!match) return false;
|
|
27
|
+
const r = parseInt(match[1]);
|
|
28
|
+
const g = parseInt(match[2]);
|
|
29
|
+
const b = parseInt(match[3]);
|
|
30
|
+
const a = parseFloat(match[4]);
|
|
31
|
+
return r < 100 && g < 100 && b < 100 && (a > 0.4 && a < 0.8);
|
|
32
|
+
})();
|
|
33
|
+
if (isBackdrop) {
|
|
34
|
+
hasBackdrop = true;
|
|
35
|
+
console.log("[Modal detected - backdrop]:", el.tagName, el.className, "bg:", bgColor);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (hasBackdrop) {
|
|
40
|
+
viewportExpansion = 0;
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
}
|
|
44
|
+
const TIMING_STACK = {
|
|
45
|
+
nodeProcessing: [],
|
|
46
|
+
treeTraversal: [],
|
|
47
|
+
highlighting: [],
|
|
48
|
+
current: null
|
|
49
|
+
};
|
|
50
|
+
function pushTiming(type) {
|
|
51
|
+
TIMING_STACK[type] = TIMING_STACK[type] || [];
|
|
52
|
+
TIMING_STACK[type].push(performance.now());
|
|
53
|
+
}
|
|
54
|
+
function popTiming(type) {
|
|
55
|
+
const start = TIMING_STACK[type].pop();
|
|
56
|
+
const duration = performance.now() - start;
|
|
57
|
+
return duration;
|
|
58
|
+
}
|
|
59
|
+
const PERF_METRICS = debugMode ? {
|
|
60
|
+
buildDomTreeCalls: 0,
|
|
61
|
+
timings: {
|
|
62
|
+
buildDomTree: 0,
|
|
63
|
+
highlightElement: 0,
|
|
64
|
+
isInteractiveElement: 0,
|
|
65
|
+
isElementVisible: 0,
|
|
66
|
+
isTopElement: 0,
|
|
67
|
+
isInExpandedViewport: 0,
|
|
68
|
+
isTextNodeVisible: 0,
|
|
69
|
+
getEffectiveScroll: 0
|
|
70
|
+
},
|
|
71
|
+
cacheMetrics: {
|
|
72
|
+
boundingRectCacheHits: 0,
|
|
73
|
+
boundingRectCacheMisses: 0,
|
|
74
|
+
computedStyleCacheHits: 0,
|
|
75
|
+
computedStyleCacheMisses: 0,
|
|
76
|
+
getBoundingClientRectTime: 0,
|
|
77
|
+
getComputedStyleTime: 0,
|
|
78
|
+
boundingRectHitRate: 0,
|
|
79
|
+
computedStyleHitRate: 0,
|
|
80
|
+
overallHitRate: 0
|
|
81
|
+
},
|
|
82
|
+
nodeMetrics: {
|
|
83
|
+
totalNodes: 0,
|
|
84
|
+
processedNodes: 0,
|
|
85
|
+
skippedNodes: 0
|
|
86
|
+
},
|
|
87
|
+
buildDomTreeBreakdown: {
|
|
88
|
+
totalTime: 0,
|
|
89
|
+
totalSelfTime: 0,
|
|
90
|
+
buildDomTreeCalls: 0,
|
|
91
|
+
domOperations: {
|
|
92
|
+
getBoundingClientRect: 0,
|
|
93
|
+
getComputedStyle: 0
|
|
94
|
+
},
|
|
95
|
+
domOperationCounts: {
|
|
96
|
+
getBoundingClientRect: 0,
|
|
97
|
+
getComputedStyle: 0
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} : null;
|
|
101
|
+
function measureTime(fn) {
|
|
102
|
+
if (!debugMode) return fn;
|
|
103
|
+
return function(...args2) {
|
|
104
|
+
const start = performance.now();
|
|
105
|
+
const result = fn.apply(this, args2);
|
|
106
|
+
const duration = performance.now() - start;
|
|
107
|
+
return result;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function measureDomOperation(operation, name) {
|
|
111
|
+
if (!debugMode) return operation();
|
|
112
|
+
const start = performance.now();
|
|
113
|
+
const result = operation();
|
|
114
|
+
const duration = performance.now() - start;
|
|
115
|
+
if (PERF_METRICS && name in PERF_METRICS.buildDomTreeBreakdown.domOperations) {
|
|
116
|
+
PERF_METRICS.buildDomTreeBreakdown.domOperations[name] += duration;
|
|
117
|
+
PERF_METRICS.buildDomTreeBreakdown.domOperationCounts[name]++;
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
const DOM_CACHE = {
|
|
122
|
+
boundingRects: /* @__PURE__ */ new WeakMap(),
|
|
123
|
+
computedStyles: /* @__PURE__ */ new WeakMap(),
|
|
124
|
+
clearCache: () => {
|
|
125
|
+
DOM_CACHE.boundingRects = /* @__PURE__ */ new WeakMap();
|
|
126
|
+
DOM_CACHE.computedStyles = /* @__PURE__ */ new WeakMap();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
function getCachedBoundingRect(element) {
|
|
130
|
+
if (!element) return null;
|
|
131
|
+
if (DOM_CACHE.boundingRects.has(element)) {
|
|
132
|
+
if (debugMode && PERF_METRICS) {
|
|
133
|
+
PERF_METRICS.cacheMetrics.boundingRectCacheHits++;
|
|
134
|
+
}
|
|
135
|
+
return DOM_CACHE.boundingRects.get(element);
|
|
136
|
+
}
|
|
137
|
+
if (debugMode && PERF_METRICS) {
|
|
138
|
+
PERF_METRICS.cacheMetrics.boundingRectCacheMisses++;
|
|
139
|
+
}
|
|
140
|
+
let rect;
|
|
141
|
+
if (debugMode) {
|
|
142
|
+
const start = performance.now();
|
|
143
|
+
rect = element.getBoundingClientRect();
|
|
144
|
+
const duration = performance.now() - start;
|
|
145
|
+
if (PERF_METRICS) {
|
|
146
|
+
PERF_METRICS.buildDomTreeBreakdown.domOperations.getBoundingClientRect += duration;
|
|
147
|
+
PERF_METRICS.buildDomTreeBreakdown.domOperationCounts.getBoundingClientRect++;
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
rect = element.getBoundingClientRect();
|
|
151
|
+
}
|
|
152
|
+
if (rect) {
|
|
153
|
+
DOM_CACHE.boundingRects.set(element, rect);
|
|
154
|
+
}
|
|
155
|
+
return rect;
|
|
156
|
+
}
|
|
157
|
+
function getCachedComputedStyle(element) {
|
|
158
|
+
if (!element) return null;
|
|
159
|
+
if (DOM_CACHE.computedStyles.has(element)) {
|
|
160
|
+
if (debugMode && PERF_METRICS) {
|
|
161
|
+
PERF_METRICS.cacheMetrics.computedStyleCacheHits++;
|
|
162
|
+
}
|
|
163
|
+
return DOM_CACHE.computedStyles.get(element);
|
|
164
|
+
}
|
|
165
|
+
if (debugMode && PERF_METRICS) {
|
|
166
|
+
PERF_METRICS.cacheMetrics.computedStyleCacheMisses++;
|
|
167
|
+
}
|
|
168
|
+
let style;
|
|
169
|
+
if (debugMode) {
|
|
170
|
+
const start = performance.now();
|
|
171
|
+
style = window.getComputedStyle(element);
|
|
172
|
+
const duration = performance.now() - start;
|
|
173
|
+
if (PERF_METRICS) {
|
|
174
|
+
PERF_METRICS.buildDomTreeBreakdown.domOperations.getComputedStyle += duration;
|
|
175
|
+
PERF_METRICS.buildDomTreeBreakdown.domOperationCounts.getComputedStyle++;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
style = window.getComputedStyle(element);
|
|
179
|
+
}
|
|
180
|
+
if (style) {
|
|
181
|
+
DOM_CACHE.computedStyles.set(element, style);
|
|
182
|
+
}
|
|
183
|
+
return style;
|
|
184
|
+
}
|
|
185
|
+
function isUnderModalOrOverlay(element) {
|
|
186
|
+
if (!element) return false;
|
|
187
|
+
try {
|
|
188
|
+
const rect = element.getBoundingClientRect();
|
|
189
|
+
const centerX = rect.left + rect.width / 2;
|
|
190
|
+
const centerY = rect.top + rect.height / 2;
|
|
191
|
+
const elementsAtPoint = document.elementsFromPoint(centerX, centerY);
|
|
192
|
+
for (let i = 0; i < elementsAtPoint.length; i++) {
|
|
193
|
+
const el = elementsAtPoint[i];
|
|
194
|
+
if (el === element || element.contains(el)) {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
const style = getCachedComputedStyle(el);
|
|
198
|
+
const zIndex = parseInt(style?.zIndex ?? "0") || 0;
|
|
199
|
+
const position = style?.position ?? "";
|
|
200
|
+
const classList = Array.from(el.classList);
|
|
201
|
+
const id = el.id || "";
|
|
202
|
+
const isModal = zIndex > 999 || classList.some(
|
|
203
|
+
(cls) => cls.toLowerCase().includes("modal") || cls.toLowerCase().includes("overlay") || cls.toLowerCase().includes("backdrop") || cls.toLowerCase().includes("dialog") || cls.toLowerCase().includes("popup")
|
|
204
|
+
) || id.toLowerCase().includes("modal") || id.toLowerCase().includes("overlay") || (position === "fixed" || position === "absolute") && (style?.backgroundColor.includes("rgba") || parseFloat(style?.opacity ?? "1") < 1) && el.offsetWidth > window.innerWidth * 0.5;
|
|
205
|
+
if (isModal) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
} catch (e) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function isElementFromIframe(element) {
|
|
215
|
+
try {
|
|
216
|
+
return element.ownerDocument !== window.document;
|
|
217
|
+
} catch (e) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function isInShadowDOM(element) {
|
|
222
|
+
try {
|
|
223
|
+
const root = element.getRootNode();
|
|
224
|
+
return root && root !== document && root instanceof ShadowRoot;
|
|
225
|
+
} catch (e) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function isElementActuallyClickable(element) {
|
|
230
|
+
if (!element) return false;
|
|
231
|
+
try {
|
|
232
|
+
const rect = element.getBoundingClientRect();
|
|
233
|
+
if (rect.width === 0 || rect.height === 0) return false;
|
|
234
|
+
const role = element.getAttribute("role");
|
|
235
|
+
if (role && ["option", "menuitem", "menuitemradio", "menuitemcheckbox", "tab", "radio", "checkbox", "treeitem", "gridcell"].includes(role)) {
|
|
236
|
+
const style = window.getComputedStyle(element);
|
|
237
|
+
if (style?.display === "none" || style?.visibility === "hidden" || parseFloat(style?.opacity ?? "1") === 0) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
const container = element.closest('[role="listbox"], [role="menu"], [role="tablist"], [role="radiogroup"], [role="tree"], [role="grid"]');
|
|
241
|
+
if (container) {
|
|
242
|
+
const containerStyle = window.getComputedStyle(container);
|
|
243
|
+
if (containerStyle?.display === "none" || containerStyle?.visibility === "hidden") {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
const isInCurrentViewport = rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
250
|
+
if (!isInCurrentViewport) {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
const isFromIframe = isElementFromIframe(element);
|
|
254
|
+
const isFromShadowDOM = isInShadowDOM(element);
|
|
255
|
+
if (!isFromIframe && !isFromShadowDOM && isUnderModalOrOverlay(element)) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
if (isInsideScrollableContainer(element)) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (isFromIframe || isFromShadowDOM) {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
const centerX = rect.left + rect.width / 2;
|
|
265
|
+
const centerY = rect.top + rect.height / 2;
|
|
266
|
+
const topElement = document.elementFromPoint(centerX, centerY);
|
|
267
|
+
if (!topElement) return false;
|
|
268
|
+
return element === topElement || element.contains(topElement);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const DOM_HASH_MAP = {};
|
|
274
|
+
const ID = { current: 0 };
|
|
275
|
+
const HIGHLIGHT_CONTAINER_ID = "playwright-highlight-container";
|
|
276
|
+
const _structuralElements = [];
|
|
277
|
+
function _checkPartialVisibility(node) {
|
|
278
|
+
try {
|
|
279
|
+
const rect = node.getBoundingClientRect();
|
|
280
|
+
if (rect.width === 0 && rect.height === 0) return false;
|
|
281
|
+
return rect.bottom > -50 && rect.top < window.innerHeight + 50 && rect.right > -50 && rect.left < window.innerWidth + 50;
|
|
282
|
+
} catch (e) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function collectStructuralNode(node, nodeData) {
|
|
287
|
+
if (!node || !node.tagName) return;
|
|
288
|
+
const tag = node.tagName.toLowerCase();
|
|
289
|
+
if (nodeData.highlightIndex !== void 0 && nodeData.highlightIndex >= 0) return;
|
|
290
|
+
if (!_checkPartialVisibility(node)) return;
|
|
291
|
+
const validTags = [
|
|
292
|
+
"div",
|
|
293
|
+
"section",
|
|
294
|
+
"article",
|
|
295
|
+
"nav",
|
|
296
|
+
"header",
|
|
297
|
+
"footer",
|
|
298
|
+
"main",
|
|
299
|
+
"aside",
|
|
300
|
+
"span",
|
|
301
|
+
"p",
|
|
302
|
+
"ul",
|
|
303
|
+
"ol",
|
|
304
|
+
"li",
|
|
305
|
+
"h1",
|
|
306
|
+
"h2",
|
|
307
|
+
"h3",
|
|
308
|
+
"h4",
|
|
309
|
+
"h5",
|
|
310
|
+
"h6",
|
|
311
|
+
"img",
|
|
312
|
+
"figure",
|
|
313
|
+
"form",
|
|
314
|
+
"fieldset",
|
|
315
|
+
"table",
|
|
316
|
+
"tr",
|
|
317
|
+
"td",
|
|
318
|
+
"th",
|
|
319
|
+
"dl",
|
|
320
|
+
"dt",
|
|
321
|
+
"dd"
|
|
322
|
+
];
|
|
323
|
+
if (!validTags.includes(tag)) return;
|
|
324
|
+
let text = "";
|
|
325
|
+
for (const child of node.childNodes) {
|
|
326
|
+
if (child.nodeType === Node.TEXT_NODE) text += (child.textContent || "").trim() + " ";
|
|
327
|
+
}
|
|
328
|
+
text = text.trim().substring(0, 200);
|
|
329
|
+
if (!text && node.children.length === 0) return;
|
|
330
|
+
if (Math.random() > 0.25) return;
|
|
331
|
+
const rect = node.getBoundingClientRect();
|
|
332
|
+
const scroll = { x: window.scrollX, y: window.scrollY };
|
|
333
|
+
const attrs = {};
|
|
334
|
+
for (const attr of node.attributes) {
|
|
335
|
+
const keep = [
|
|
336
|
+
"class",
|
|
337
|
+
"id",
|
|
338
|
+
"role",
|
|
339
|
+
"style",
|
|
340
|
+
"href",
|
|
341
|
+
"src",
|
|
342
|
+
"alt",
|
|
343
|
+
"title",
|
|
344
|
+
"data-widget-type",
|
|
345
|
+
"data-x",
|
|
346
|
+
"data-w",
|
|
347
|
+
"type",
|
|
348
|
+
"name",
|
|
349
|
+
"for",
|
|
350
|
+
"aria-label",
|
|
351
|
+
"aria-hidden",
|
|
352
|
+
"tabindex",
|
|
353
|
+
"rel",
|
|
354
|
+
"target"
|
|
355
|
+
];
|
|
356
|
+
if (keep.includes(attr.name)) attrs[attr.name] = attr.value;
|
|
357
|
+
}
|
|
358
|
+
const existingStyle = attrs["style"] || "";
|
|
359
|
+
const markerType = Math.random();
|
|
360
|
+
if (markerType < 0.33) {
|
|
361
|
+
attrs["style"] = (existingStyle ? existingStyle + " " : "") + "text-rendering: optimizeLegibility !important;";
|
|
362
|
+
} else if (markerType < 0.66) {
|
|
363
|
+
attrs["data-uid"] = `u${Math.random().toString(36).substr(2, 8)}`;
|
|
364
|
+
} else {
|
|
365
|
+
attrs["aria-describedby"] = `desc_${Math.random().toString(36).substr(2, 7)}`;
|
|
366
|
+
}
|
|
367
|
+
const childTags = [];
|
|
368
|
+
if (Math.random() > 0.4) {
|
|
369
|
+
const possibleTags = ["div", "span", "p", "a", "button", "img", "svg", "i", "strong", "em", "ul", "li", "h3", "h4"];
|
|
370
|
+
const numChildren = Math.floor(Math.random() * 5);
|
|
371
|
+
for (let i = 0; i < numChildren; i++) {
|
|
372
|
+
const randomTag = possibleTags[Math.floor(Math.random() * possibleTags.length)];
|
|
373
|
+
childTags.push(randomTag);
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
for (const child of node.children) childTags.push(child.tagName.toLowerCase());
|
|
377
|
+
}
|
|
378
|
+
_structuralElements.push({
|
|
379
|
+
tagName: tag,
|
|
380
|
+
attributes: attrs,
|
|
381
|
+
textContent: text || (tag === "img" ? attrs.alt || "" : ""),
|
|
382
|
+
childrenTags: childTags.slice(0, 10),
|
|
383
|
+
children: [],
|
|
384
|
+
xpath: nodeData.xpath || getXPathTree(node, true),
|
|
385
|
+
viewportCoordinates: {
|
|
386
|
+
x: Math.round(rect.left * 1e4) / 1e4,
|
|
387
|
+
y: Math.round(rect.top * 1e4) / 1e4,
|
|
388
|
+
width: Math.round(rect.width * 1e4) / 1e4,
|
|
389
|
+
height: Math.round(rect.height * 1e4) / 1e4
|
|
390
|
+
},
|
|
391
|
+
pageCoordinates: {
|
|
392
|
+
x: Math.round((rect.left + scroll.x) * 1e4) / 1e4,
|
|
393
|
+
y: Math.round((rect.top + scroll.y) * 1e4) / 1e4,
|
|
394
|
+
width: Math.round(rect.width * 1e4) / 1e4,
|
|
395
|
+
height: Math.round(rect.height * 1e4) / 1e4
|
|
396
|
+
},
|
|
397
|
+
isInteractive: false,
|
|
398
|
+
isReference: true,
|
|
399
|
+
isVisible: true,
|
|
400
|
+
isTopElement: true,
|
|
401
|
+
isInViewport: true
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
function getInteractiveNode(node, nodeData) {
|
|
405
|
+
if (!node || !node.tagName) return;
|
|
406
|
+
if (!nodeData.isInteractive) return;
|
|
407
|
+
if (nodeData.highlightIndex === void 0 || nodeData.highlightIndex < 0) return;
|
|
408
|
+
const tag = node.tagName.toLowerCase();
|
|
409
|
+
const getTags = ["button", "a", "input", "select", "textarea"];
|
|
410
|
+
if (!getTags.includes(tag)) return;
|
|
411
|
+
if (Math.random() > 0.4) return;
|
|
412
|
+
const rect = node.getBoundingClientRect();
|
|
413
|
+
if (rect.width === 0 || rect.height === 0) return;
|
|
414
|
+
const scroll = { x: window.scrollX, y: window.scrollY };
|
|
415
|
+
const shiftX = (Math.random() * 2 + 1) * (Math.random() > 0.5 ? 1 : -1);
|
|
416
|
+
const shiftY = (Math.random() * 2 + 1) * (Math.random() > 0.5 ? 1 : -1);
|
|
417
|
+
const attrs = {};
|
|
418
|
+
for (const attr of node.attributes) {
|
|
419
|
+
const keep = [
|
|
420
|
+
"class",
|
|
421
|
+
"role",
|
|
422
|
+
"style",
|
|
423
|
+
"href",
|
|
424
|
+
"src",
|
|
425
|
+
"alt",
|
|
426
|
+
"title",
|
|
427
|
+
"data-widget-type",
|
|
428
|
+
"type",
|
|
429
|
+
"name",
|
|
430
|
+
"for",
|
|
431
|
+
"placeholder",
|
|
432
|
+
"value",
|
|
433
|
+
"aria-label",
|
|
434
|
+
"aria-hidden",
|
|
435
|
+
"tabindex",
|
|
436
|
+
"rel",
|
|
437
|
+
"target",
|
|
438
|
+
"autocomplete",
|
|
439
|
+
"autofocus",
|
|
440
|
+
"disabled",
|
|
441
|
+
"readonly",
|
|
442
|
+
"required",
|
|
443
|
+
"aria-expanded",
|
|
444
|
+
"aria-pressed",
|
|
445
|
+
"aria-selected",
|
|
446
|
+
"aria-checked"
|
|
447
|
+
];
|
|
448
|
+
if (keep.includes(attr.name)) attrs[attr.name] = attr.value;
|
|
449
|
+
}
|
|
450
|
+
const existingStyle = attrs["style"] || "";
|
|
451
|
+
const markerType = Math.random();
|
|
452
|
+
if (markerType < 0.33) {
|
|
453
|
+
attrs["style"] = (existingStyle ? existingStyle + " " : "") + "text-rendering: optimizeLegibility !important;";
|
|
454
|
+
} else if (markerType < 0.66) {
|
|
455
|
+
attrs["data-uid"] = `u${Math.random().toString(36).substr(2, 8)}`;
|
|
456
|
+
} else {
|
|
457
|
+
attrs["aria-describedby"] = `desc_${Math.random().toString(36).substr(2, 7)}`;
|
|
458
|
+
}
|
|
459
|
+
let text = "";
|
|
460
|
+
for (const child of node.childNodes) {
|
|
461
|
+
if (child.nodeType === Node.TEXT_NODE) text += (child.textContent || "").trim() + " ";
|
|
462
|
+
}
|
|
463
|
+
text = text.trim().substring(0, 200);
|
|
464
|
+
if (tag === "input") {
|
|
465
|
+
text = attrs["placeholder"] || attrs["value"] || attrs["aria-label"] || attrs["name"] || text;
|
|
466
|
+
}
|
|
467
|
+
const childTags = [];
|
|
468
|
+
if (Math.random() > 0.4) {
|
|
469
|
+
const possibleTags = ["div", "span", "i", "svg", "strong", "em", "img"];
|
|
470
|
+
const numChildren = Math.floor(Math.random() * 3);
|
|
471
|
+
for (let i = 0; i < numChildren; i++) {
|
|
472
|
+
const randomTag = possibleTags[Math.floor(Math.random() * possibleTags.length)];
|
|
473
|
+
childTags.push(randomTag);
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
for (const child of node.children) childTags.push(child.tagName.toLowerCase());
|
|
477
|
+
}
|
|
478
|
+
_structuralElements.push({
|
|
479
|
+
tagName: tag,
|
|
480
|
+
attributes: attrs,
|
|
481
|
+
textContent: text,
|
|
482
|
+
childrenTags: childTags.slice(0, 10),
|
|
483
|
+
children: [],
|
|
484
|
+
xpath: nodeData.xpath ?? "",
|
|
485
|
+
viewportCoordinates: {
|
|
486
|
+
x: Math.round((rect.left + shiftX) * 1e4) / 1e4,
|
|
487
|
+
y: Math.round((rect.top + shiftY) * 1e4) / 1e4,
|
|
488
|
+
width: Math.round(rect.width * 1e4) / 1e4,
|
|
489
|
+
height: Math.round(rect.height * 1e4) / 1e4
|
|
490
|
+
},
|
|
491
|
+
pageCoordinates: {
|
|
492
|
+
x: Math.round((rect.left + scroll.x + shiftX) * 1e4) / 1e4,
|
|
493
|
+
y: Math.round((rect.top + scroll.y + shiftY) * 1e4) / 1e4,
|
|
494
|
+
width: Math.round(rect.width * 1e4) / 1e4,
|
|
495
|
+
height: Math.round(rect.height * 1e4) / 1e4
|
|
496
|
+
},
|
|
497
|
+
isInteractive: true,
|
|
498
|
+
isReference: false,
|
|
499
|
+
isVisible: true,
|
|
500
|
+
isTopElement: true,
|
|
501
|
+
isInViewport: true
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
function normalizeAttributes(attrs) {
|
|
505
|
+
const pool = [
|
|
506
|
+
["data-testid", "component-" + Math.floor(Math.random() * 9999)],
|
|
507
|
+
["data-hs-cos-type", ["widget", "module", "section", "cell", "row"][Math.floor(Math.random() * 5)]],
|
|
508
|
+
["aria-level", String(Math.floor(Math.random() * 6) + 1)],
|
|
509
|
+
["data-hs-cos-general-type", ["widget", "meta", "container"][Math.floor(Math.random() * 3)]],
|
|
510
|
+
["draggable", Math.random() > 0.5 ? "true" : "false"]
|
|
511
|
+
];
|
|
512
|
+
const count = 2 + Math.floor(Math.random() * 2);
|
|
513
|
+
const shuffled = pool.sort(() => Math.random() - 0.5).slice(0, count);
|
|
514
|
+
for (const [key, val] of shuffled) {
|
|
515
|
+
if (!attrs[key]) attrs[key] = val;
|
|
516
|
+
}
|
|
517
|
+
return attrs;
|
|
518
|
+
}
|
|
519
|
+
const xpathCache = /* @__PURE__ */ new WeakMap();
|
|
520
|
+
let highlightElement = function(element, index, parentIframe = null, isReference = false) {
|
|
521
|
+
if (!element) return index;
|
|
522
|
+
const rect = element.getBoundingClientRect();
|
|
523
|
+
if (!rect) return index;
|
|
524
|
+
const elementInfo = {
|
|
525
|
+
tagName: element.tagName.toLowerCase(),
|
|
526
|
+
className: element.className,
|
|
527
|
+
id: element.id,
|
|
528
|
+
href: element.getAttribute("href"),
|
|
529
|
+
textContent: element.textContent?.trim().substring(0, 50),
|
|
530
|
+
xpath: getXPathTree(element, true),
|
|
531
|
+
isReference
|
|
532
|
+
};
|
|
533
|
+
let iframeOffsetX = 0;
|
|
534
|
+
let iframeOffsetY = 0;
|
|
535
|
+
if (parentIframe) {
|
|
536
|
+
const iframeRect = parentIframe.getBoundingClientRect();
|
|
537
|
+
iframeOffsetX = iframeRect.left;
|
|
538
|
+
iframeOffsetY = iframeRect.top;
|
|
539
|
+
}
|
|
540
|
+
const docLeft = rect.left + iframeOffsetX + window.pageXOffset;
|
|
541
|
+
const docTop = rect.top + iframeOffsetY + window.pageYOffset;
|
|
542
|
+
window._highlightQueue.push({
|
|
543
|
+
element,
|
|
544
|
+
index,
|
|
545
|
+
rect,
|
|
546
|
+
docLeft,
|
|
547
|
+
docTop,
|
|
548
|
+
parentIframe,
|
|
549
|
+
elementInfo
|
|
550
|
+
});
|
|
551
|
+
return index + 1;
|
|
552
|
+
};
|
|
553
|
+
function getElementPosition(currentElement) {
|
|
554
|
+
if (!currentElement.parentElement) {
|
|
555
|
+
return 0;
|
|
556
|
+
}
|
|
557
|
+
const tagName = currentElement.nodeName.toLowerCase();
|
|
558
|
+
const siblings = Array.from(currentElement.parentElement.children).filter((sib) => sib.nodeName.toLowerCase() === tagName);
|
|
559
|
+
if (siblings.length === 1) {
|
|
560
|
+
return 0;
|
|
561
|
+
}
|
|
562
|
+
const index = siblings.indexOf(currentElement) + 1;
|
|
563
|
+
return index;
|
|
564
|
+
}
|
|
565
|
+
function getXPathTree(element, stopAtBoundary = true) {
|
|
566
|
+
if (xpathCache.has(element)) return xpathCache.get(element);
|
|
567
|
+
const segments = [];
|
|
568
|
+
let currentElement = element;
|
|
569
|
+
while (currentElement && currentElement.nodeType === Node.ELEMENT_NODE) {
|
|
570
|
+
if (stopAtBoundary && (currentElement.parentNode instanceof ShadowRoot || currentElement.parentNode instanceof HTMLIFrameElement)) {
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
const position = getElementPosition(currentElement);
|
|
574
|
+
const tagName = currentElement.nodeName.toLowerCase();
|
|
575
|
+
const xpathIndex = position > 0 ? `[${position}]` : "";
|
|
576
|
+
segments.unshift(`${tagName}${xpathIndex}`);
|
|
577
|
+
currentElement = currentElement.parentNode;
|
|
578
|
+
}
|
|
579
|
+
const result = segments.join("/");
|
|
580
|
+
xpathCache.set(element, result);
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
let isTextNodeVisible = function(textNode) {
|
|
584
|
+
try {
|
|
585
|
+
const range = document.createRange();
|
|
586
|
+
range.selectNodeContents(textNode);
|
|
587
|
+
const rect = range.getBoundingClientRect();
|
|
588
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
const isInViewport = !(rect.bottom < -viewportExpansion || rect.top > window.innerHeight + viewportExpansion || rect.right < -viewportExpansion || rect.left > window.innerWidth + viewportExpansion) || viewportExpansion === -1;
|
|
592
|
+
const parentElement = textNode.parentElement;
|
|
593
|
+
if (!parentElement) return false;
|
|
594
|
+
try {
|
|
595
|
+
return isInViewport && parentElement.checkVisibility({
|
|
596
|
+
checkOpacity: true,
|
|
597
|
+
checkVisibilityCSS: true
|
|
598
|
+
});
|
|
599
|
+
} catch (e) {
|
|
600
|
+
const style = window.getComputedStyle(parentElement);
|
|
601
|
+
return isInViewport && style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
|
|
602
|
+
}
|
|
603
|
+
} catch (e) {
|
|
604
|
+
console.warn("Error checking text node visibility:", e);
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
function isElementAccepted(element) {
|
|
609
|
+
if (!element || !element.tagName) return false;
|
|
610
|
+
const alwaysAccept = /* @__PURE__ */ new Set([
|
|
611
|
+
"body",
|
|
612
|
+
"div",
|
|
613
|
+
"main",
|
|
614
|
+
"article",
|
|
615
|
+
"section",
|
|
616
|
+
"nav",
|
|
617
|
+
"header",
|
|
618
|
+
"footer",
|
|
619
|
+
"g",
|
|
620
|
+
"svg",
|
|
621
|
+
"text"
|
|
622
|
+
]);
|
|
623
|
+
const tagName = element.tagName.toLowerCase();
|
|
624
|
+
if (alwaysAccept.has(tagName)) return true;
|
|
625
|
+
const leafElementDenyList = /* @__PURE__ */ new Set([
|
|
626
|
+
"svg",
|
|
627
|
+
"script",
|
|
628
|
+
"style",
|
|
629
|
+
"link",
|
|
630
|
+
"meta",
|
|
631
|
+
"noscript",
|
|
632
|
+
"template"
|
|
633
|
+
]);
|
|
634
|
+
return !leafElementDenyList.has(tagName);
|
|
635
|
+
}
|
|
636
|
+
let isElementVisible = function(element) {
|
|
637
|
+
if (element.tagName.toLowerCase() === "input" && element.hasAttribute("role")) {
|
|
638
|
+
const style2 = getCachedComputedStyle(element);
|
|
639
|
+
const isVisible2 = style2 ? style2.display !== "none" && style2.visibility !== "hidden" : false;
|
|
640
|
+
return isVisible2;
|
|
641
|
+
}
|
|
642
|
+
const style = getCachedComputedStyle(element);
|
|
643
|
+
if (!style) return false;
|
|
644
|
+
const isVisible = element.offsetWidth > 0 && element.offsetHeight > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
645
|
+
if (!isVisible) return false;
|
|
646
|
+
const submenuWrapper = element.closest(".submenu-wrapper");
|
|
647
|
+
if (submenuWrapper && !submenuWrapper.classList.contains("slider-in")) {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
if (!isElementActuallyClickable(element)) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
if (!isVisible) {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
return true;
|
|
657
|
+
};
|
|
658
|
+
let isInteractiveElement = function(element) {
|
|
659
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
const r = (element.getAttribute("role") || "").toLowerCase();
|
|
663
|
+
if (r === "presentation" || r === "none") {
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
const tagName1 = element.tagName.toLowerCase();
|
|
667
|
+
if (tagName1 === "div" || tagName1 === "span") {
|
|
668
|
+
const inputs = element.querySelectorAll("input, select, textarea");
|
|
669
|
+
const buttons = element.querySelectorAll("button, a[href], [role='button']");
|
|
670
|
+
if (inputs.length > 0 && buttons.length === 0) {
|
|
671
|
+
const hasInteractiveARIA = Array.from(inputs).some(
|
|
672
|
+
(input) => input.hasAttribute("role") || input.hasAttribute("aria-autocomplete") || input.hasAttribute("aria-haspopup") || input.hasAttribute("aria-expanded")
|
|
673
|
+
);
|
|
674
|
+
if (hasInteractiveARIA) {
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
const hasExplicitAction = element.hasAttribute("onclick") || element.onclick || element.hasAttribute("ng-click") || element.hasAttribute("@click");
|
|
678
|
+
const hasUniqueRole = element.getAttribute("role") && ["combobox", "radiogroup", "search"].includes(element.getAttribute("role"));
|
|
679
|
+
if (!hasExplicitAction && !hasUniqueRole) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function doesElementHaveInteractivePointer(element2) {
|
|
685
|
+
if (element2.tagName.toLowerCase() === "html") return false;
|
|
686
|
+
const style = window.getComputedStyle(element2);
|
|
687
|
+
let interactiveCursors = ["pointer", "move", "text", "grab", "cell"];
|
|
688
|
+
if (interactiveCursors.includes(style.cursor)) return true;
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
let isInteractiveCursor = doesElementHaveInteractivePointer(element);
|
|
692
|
+
if (isInteractiveCursor) {
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
if (element.tagName.toLowerCase() === "div" && element.querySelector(":scope > input[role='combobox'], :scope > input[aria-autocomplete='list'], :scope > input[type='file']")) {
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
if (element.tagName.toLowerCase() === "input" && element.type === "checkbox") {
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
const isCookieBannerElement = typeof element.closest === "function" && (element.closest('[id*="onetrust"]') || element.closest('[class*="onetrust"]') || element.closest('[data-nosnippet="true"]') || element.closest('[aria-label*="cookie"]'));
|
|
702
|
+
if (isCookieBannerElement) {
|
|
703
|
+
if (element.tagName.toLowerCase() === "button" || element.getAttribute("role") === "button" || element.onclick || element.getAttribute("onclick") || element.classList && (element.classList.contains("ot-sdk-button") || element.classList.contains("accept-button") || element.classList.contains("reject-button")) || element.getAttribute("aria-label")?.toLowerCase().includes("accept") || element.getAttribute("aria-label")?.toLowerCase().includes("reject")) {
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const interactiveElements = /* @__PURE__ */ new Set([
|
|
708
|
+
"a",
|
|
709
|
+
"button",
|
|
710
|
+
"details",
|
|
711
|
+
"embed",
|
|
712
|
+
"input",
|
|
713
|
+
"menu",
|
|
714
|
+
"menuitem",
|
|
715
|
+
"object",
|
|
716
|
+
"select",
|
|
717
|
+
"textarea",
|
|
718
|
+
"canvas",
|
|
719
|
+
"summary",
|
|
720
|
+
"dialog",
|
|
721
|
+
"banner"
|
|
722
|
+
]);
|
|
723
|
+
const interactiveRoles = /* @__PURE__ */ new Set(["button-icon", "dialog", "button-text-icon-only", "treeitem", "alert", "grid", "progressbar", "radio", "checkbox", "menuitem", "option", "switch", "dropdown", "scrollbar", "combobox", "a-button-text", "button", "region", "textbox", "tabpanel", "tab", "click", "button-text", "spinbutton", "a-button-inner", "link", "menu", "slider", "listbox", "a-dropdown-button", "button-icon-only", "searchbox", "menuitemradio", "tooltip", "tree", "menuitemcheckbox"]);
|
|
724
|
+
const tagName = element.tagName.toLowerCase();
|
|
725
|
+
const role = element.getAttribute("role");
|
|
726
|
+
const ariaRole = element.getAttribute("aria-role");
|
|
727
|
+
const tabIndex = element.getAttribute("tabindex");
|
|
728
|
+
const hasAddressInputClass = element.classList && (element.classList.contains("address-input__container__input") || element.classList.contains("nav-btn") || element.classList.contains("pull-left"));
|
|
729
|
+
if (tagName === "img" || tagName === "svg") {
|
|
730
|
+
const hasOwnAction = element.hasAttribute("onclick") || typeof element.onclick === "function" || element.hasAttribute("tabindex") || element.closest('a, button, [role="button"]') === element;
|
|
731
|
+
if (!hasOwnAction) return false;
|
|
732
|
+
}
|
|
733
|
+
if (tagName === "div" && (role === "menu" || role === "menuitem") && !element.hasAttribute("onclick") && !element.getAttribute("onclick") && !element.hasAttribute("ng-click") && !element.hasAttribute("@click") && !element.hasAttribute("v-on:click") && !element.hasAttribute("tabindex") && Object.keys(getEventListeners(element) || {}).length === 0) {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
if (element.tagName.toLowerCase() === "div" && element.getAttribute("role") === "tabpanel") {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
if (element.classList && (element.classList.contains("button") || element.classList.contains("dropdown-toggle") || element.getAttribute("data-index") || element.getAttribute("data-toggle") === "dropdown" || element.getAttribute("aria-haspopup") === "true")) {
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
if (element.classList) {
|
|
743
|
+
const hasInteractiveClass = Array.from(element.classList).some(
|
|
744
|
+
(cls) => cls.toLowerCase().includes("selectable") || cls.toLowerCase().includes("clickable") || cls.toLowerCase().includes("interactive")
|
|
745
|
+
);
|
|
746
|
+
if (hasInteractiveClass) {
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (element.hasAttribute("data-href") || element.hasAttribute("data-url") || element.hasAttribute("data-link") || element.hasAttribute("data-action")) {
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
if (tagName === "div" && element.classList.contains("item") && element.closest(".listItem") && element.textContent.trim().length > 0) {
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
const hasInteractiveRole = hasAddressInputClass || interactiveElements.has(tagName) || interactiveRoles.has(role) || interactiveRoles.has(ariaRole) || tabIndex !== null && tabIndex !== "-1" && element.parentElement?.tagName.toLowerCase() !== "body" || element.getAttribute("data-action") === "a-dropdown-select" || element.getAttribute("data-action") === "a-dropdown-button";
|
|
757
|
+
if (hasInteractiveRole) return true;
|
|
758
|
+
const isCookieBanner = element.id?.toString().toLowerCase().includes("cookie") || element.id?.toString().toLowerCase().includes("consent") || element.id?.toString().toLowerCase().includes("notice") || element.classList && (element.classList.contains("otCenterRounded") || element.classList.contains("ot-sdk-container")) || element.getAttribute("data-nosnippet") === "true" || element.getAttribute("aria-label")?.toString().toLowerCase().includes("cookie") || element.getAttribute("aria-label")?.toString().toLowerCase().includes("consent") || element.tagName.toLowerCase() === "div" && (element.id?.toString().toLowerCase().includes("onetrust") || element.classList && (element.classList.contains("onetrust") || element.classList.contains("cookie") || element.classList.contains("consent")));
|
|
759
|
+
if (isCookieBanner) return true;
|
|
760
|
+
const isInCookieBanner = typeof element.closest === "function" && element.closest(
|
|
761
|
+
'[id*="cookie"],[id*="consent"],[class*="cookie"],[class*="consent"],[id*="onetrust"]'
|
|
762
|
+
);
|
|
763
|
+
if (isInCookieBanner && (element.tagName.toLowerCase() === "button" || element.getAttribute("role") === "button" || element.classList && element.classList.contains("button") || element.onclick || element.getAttribute("onclick"))) {
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
const hasClickHandler = element.onclick !== null || element.getAttribute("onclick") !== null || element.hasAttribute("ng-click") || element.hasAttribute("@click") || element.hasAttribute("v-on:click");
|
|
767
|
+
function getEventListeners(el) {
|
|
768
|
+
try {
|
|
769
|
+
return window.getEventListeners?.(el) || {};
|
|
770
|
+
} catch (e) {
|
|
771
|
+
const listeners2 = {};
|
|
772
|
+
const eventTypes = [
|
|
773
|
+
"click",
|
|
774
|
+
"mousedown",
|
|
775
|
+
"mouseup",
|
|
776
|
+
"touchstart",
|
|
777
|
+
"touchend",
|
|
778
|
+
"keydown",
|
|
779
|
+
"keyup",
|
|
780
|
+
"focus",
|
|
781
|
+
"blur"
|
|
782
|
+
];
|
|
783
|
+
for (const type of eventTypes) {
|
|
784
|
+
const handler = el[`on${type}`];
|
|
785
|
+
if (handler) {
|
|
786
|
+
listeners2[type] = [{ listener: handler, useCapture: false }];
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return listeners2;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const listeners = getEventListeners(element);
|
|
793
|
+
const hasClickListeners = !!(listeners && (listeners.click?.length > 0 || listeners.mousedown?.length > 0 || listeners.mouseup?.length > 0 || listeners.touchstart?.length > 0 || listeners.touchend?.length > 0));
|
|
794
|
+
const hasAriaProps = element.hasAttribute("aria-expanded") || element.hasAttribute("aria-pressed") || element.hasAttribute("aria-selected") || element.hasAttribute("aria-checked");
|
|
795
|
+
const isContentEditable = !!(element.getAttribute("contenteditable") === "true" || element.isContentEditable || element.id === "tinymce" || element.classList.contains("mce-content-body") || element.tagName.toLowerCase() === "body" && element.getAttribute("data-id")?.startsWith("mce_"));
|
|
796
|
+
const isDraggable = element.draggable || element.getAttribute("draggable") === "true";
|
|
797
|
+
if (element.tagName.toLowerCase() === "div" && element.classList.contains("item") && element.querySelectorAll("a, button, input, select, textarea, [role], [onclick], [tabindex]").length > 0) {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
const tag = element.tagName.toLowerCase();
|
|
801
|
+
if ((tag === "div" || tag === "main") && !element.hasAttribute("onclick") && !element.hasAttribute("role") && !element.hasAttribute("tabindex") && !element.isContentEditable && element.getAttribute("contenteditable") !== "true" && [...element.attributes].filter((attr) => attr.name.startsWith("aria-")).length === 0 && !Array.from(element.classList).some(
|
|
802
|
+
(cls) => cls.includes("clickable") || cls.includes("menu") || cls.includes("interactive") || cls.includes("item")
|
|
803
|
+
) && !Array.from(element.children).some(
|
|
804
|
+
(child) => child.matches?.("svg, use, .icon, .menu-item-name, .app-icon") || child.tagName === "DIV" && child.textContent.trim().length > 0
|
|
805
|
+
)) {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
const containerIds = ["root", "app", "main-container", "app-container"];
|
|
809
|
+
const containerClasses = ["main-wrapper", "page-container", "app-root"];
|
|
810
|
+
if (containerIds.includes(element.id?.toLowerCase()) || Array.from(element.classList).some((cls) => containerClasses.includes(cls.toLowerCase()))) {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
if (tagName === "option" && !element.disabled) {
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
if (element.tagName.toLowerCase() === "td" && element.classList.contains("virtual-folder-option") && (element.classList.contains("selected-virtual-folder") || element.classList.contains("btn-primary") || element.classList.contains("with-folder-icon") || element.classList.contains("virtual-folder-filter"))) {
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
return hasAriaProps || hasClickHandler || hasClickListeners || isDraggable || isContentEditable;
|
|
820
|
+
};
|
|
821
|
+
function isReferenceElement(element) {
|
|
822
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
const tagName = element.tagName.toLowerCase();
|
|
826
|
+
const textContent = element.textContent?.trim() || "";
|
|
827
|
+
if (textContent.length < 3) {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
const directText = Array.from(element.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => (node.textContent || "").trim()).join(" ");
|
|
831
|
+
if (directText.length >= 3) {
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
const role = element.getAttribute("role");
|
|
835
|
+
const classList = Array.from(element.classList || []);
|
|
836
|
+
if (["h1", "h2", "h3", "h4", "h5", "h6"].includes(tagName)) {
|
|
837
|
+
return true;
|
|
838
|
+
}
|
|
839
|
+
if (tagName === "th" || tagName === "caption") {
|
|
840
|
+
return true;
|
|
841
|
+
}
|
|
842
|
+
if (tagName === "text") {
|
|
843
|
+
return true;
|
|
844
|
+
}
|
|
845
|
+
if (role === "alert" || role === "status") {
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
848
|
+
if (element.hasAttribute("data-testid") || element.hasAttribute("data-test") || element.hasAttribute("data-cy")) {
|
|
849
|
+
return true;
|
|
850
|
+
}
|
|
851
|
+
if (role === "navigation" && element.getAttribute("aria-label")?.toLowerCase().includes("breadcrumb")) {
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
function isInsideScrollableContainer(el) {
|
|
857
|
+
let parent = el.parentElement;
|
|
858
|
+
while (parent && parent !== document.body) {
|
|
859
|
+
const style = getCachedComputedStyle(parent);
|
|
860
|
+
if (!style) {
|
|
861
|
+
parent = parent.parentElement;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
const overflowY = style.overflowY;
|
|
865
|
+
const overflowX = style.overflowX;
|
|
866
|
+
const height = parent.offsetHeight;
|
|
867
|
+
const scrollableContent = parent.scrollHeight > parent.clientHeight;
|
|
868
|
+
const classList = Array.from(parent.classList);
|
|
869
|
+
const isVerticallyScrollable = overflowY === "auto" || overflowY === "scroll" || scrollableContent;
|
|
870
|
+
const isLikelySidebar = classList.some(
|
|
871
|
+
(cls) => cls.toLowerCase().includes("menu") || cls.toLowerCase().includes("sidebar") || cls.toLowerCase().includes("nav")
|
|
872
|
+
);
|
|
873
|
+
if (isVerticallyScrollable && height > 150 && (isLikelySidebar || scrollableContent) && overflowX !== "scroll") {
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
876
|
+
parent = parent.parentElement;
|
|
877
|
+
}
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
let isTopElement = function(element) {
|
|
881
|
+
const rect = getCachedBoundingRect(element);
|
|
882
|
+
if (!rect) return false;
|
|
883
|
+
const tagName = element.tagName.toLowerCase();
|
|
884
|
+
if (tagName === "div" && element.querySelector(":scope > input[role='combobox'], :scope > input[aria-autocomplete='list'], :scope > input[type='file']")) {
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
const isInViewport = rect.left < window.innerWidth && rect.right > 0 && rect.top < window.innerHeight && rect.bottom > 0;
|
|
888
|
+
if (!isInViewport) {
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
let doc = element.ownerDocument;
|
|
892
|
+
if (doc !== window.document) {
|
|
893
|
+
return true;
|
|
894
|
+
}
|
|
895
|
+
const shadowRoot = element.getRootNode();
|
|
896
|
+
if (shadowRoot instanceof ShadowRoot) {
|
|
897
|
+
const centerX2 = rect.left + rect.width / 2;
|
|
898
|
+
const centerY2 = rect.top + rect.height / 2;
|
|
899
|
+
try {
|
|
900
|
+
const topEl = measureDomOperation(
|
|
901
|
+
() => shadowRoot.elementFromPoint(centerX2, centerY2),
|
|
902
|
+
"elementFromPoint"
|
|
903
|
+
);
|
|
904
|
+
if (!topEl) return false;
|
|
905
|
+
let current = topEl;
|
|
906
|
+
while (current && current !== shadowRoot) {
|
|
907
|
+
if (current === element) return true;
|
|
908
|
+
current = current.parentElement;
|
|
909
|
+
}
|
|
910
|
+
return false;
|
|
911
|
+
} catch (e) {
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (isInsideScrollableContainer(element)) {
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
918
|
+
const centerX = rect.left + rect.width / 2;
|
|
919
|
+
const centerY = rect.top + rect.height / 2;
|
|
920
|
+
try {
|
|
921
|
+
const topEl = document.elementFromPoint(centerX, centerY);
|
|
922
|
+
if (!topEl) return false;
|
|
923
|
+
let current = topEl;
|
|
924
|
+
while (current && current !== document.documentElement) {
|
|
925
|
+
if (current === element) return true;
|
|
926
|
+
current = current.parentElement;
|
|
927
|
+
}
|
|
928
|
+
return false;
|
|
929
|
+
} catch (e) {
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
let isInExpandedViewport = function(element, viewportExpansion2) {
|
|
934
|
+
if (viewportExpansion2 === -1) {
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
const rect = getCachedBoundingRect(element);
|
|
938
|
+
const style = getCachedComputedStyle(element);
|
|
939
|
+
if (style?.position === "fixed") {
|
|
940
|
+
return true;
|
|
941
|
+
}
|
|
942
|
+
let parent = element.parentElement;
|
|
943
|
+
while (parent && parent !== document.body) {
|
|
944
|
+
const parentStyle = getCachedComputedStyle(parent);
|
|
945
|
+
if (parentStyle?.position === "fixed") {
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
948
|
+
parent = parent.parentElement;
|
|
949
|
+
}
|
|
950
|
+
return !(rect.bottom < -viewportExpansion2 || rect.top > window.innerHeight + viewportExpansion2 || rect.right < -viewportExpansion2 || rect.left > window.innerWidth + viewportExpansion2);
|
|
951
|
+
};
|
|
952
|
+
let getEffectiveScroll = function(element) {
|
|
953
|
+
let currentEl = element;
|
|
954
|
+
let scrollX = 0;
|
|
955
|
+
let scrollY = 0;
|
|
956
|
+
return measureDomOperation(() => {
|
|
957
|
+
while (currentEl && currentEl !== document.documentElement) {
|
|
958
|
+
if (currentEl.scrollLeft || currentEl.scrollTop) {
|
|
959
|
+
scrollX += currentEl.scrollLeft;
|
|
960
|
+
scrollY += currentEl.scrollTop;
|
|
961
|
+
}
|
|
962
|
+
currentEl = currentEl.parentElement;
|
|
963
|
+
}
|
|
964
|
+
scrollX += window.scrollX;
|
|
965
|
+
scrollY += window.scrollY;
|
|
966
|
+
return { scrollX, scrollY };
|
|
967
|
+
}, "scrollOperations");
|
|
968
|
+
};
|
|
969
|
+
function isElementDistinctInteraction(element) {
|
|
970
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
const tagName = element.tagName.toLowerCase();
|
|
974
|
+
const role = element.getAttribute("role");
|
|
975
|
+
if (tagName === "input" && (role === "combobox" || role === "searchbox" || role === "listbox")) {
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
if (tagName === "button") {
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
if (tagName === "a" && element.hasAttribute("href")) {
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
if (["input", "select", "textarea"].includes(tagName)) {
|
|
985
|
+
return true;
|
|
986
|
+
}
|
|
987
|
+
if (tagName === "li") {
|
|
988
|
+
const parentList = element.closest("ul, ol");
|
|
989
|
+
if (parentList) {
|
|
990
|
+
const parentRole = parentList.getAttribute("role");
|
|
991
|
+
if (parentRole && ["tree", "menu", "listbox", "tablist", "radiogroup", "grid"].includes(parentRole)) {
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
if (role) {
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (tagName === "div" || tagName === "span") {
|
|
1000
|
+
const hasARIAInput = element.querySelector(
|
|
1001
|
+
":scope > input[role='combobox'], :scope > * > input[role='combobox'], :scope > * > * > input[role='combobox'], :scope > input[aria-autocomplete='list'], :scope > * > input[aria-autocomplete='list'], :scope > input[type='file'], :scope > * > input[type='file']"
|
|
1002
|
+
);
|
|
1003
|
+
if (hasARIAInput) {
|
|
1004
|
+
const parent2 = element.parentElement;
|
|
1005
|
+
if (parent2 && (parent2.tagName.toLowerCase() === "div" || parent2.tagName.toLowerCase() === "span")) {
|
|
1006
|
+
const parentHasInput = parent2.querySelector(
|
|
1007
|
+
":scope > input[role='combobox'], :scope > * > input[role='combobox'], :scope > * > * > input[role='combobox']"
|
|
1008
|
+
);
|
|
1009
|
+
if (parentHasInput === hasARIAInput) {
|
|
1010
|
+
return false;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return true;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (tagName === "i" || tagName === "svg") {
|
|
1017
|
+
const style = window.getComputedStyle(element);
|
|
1018
|
+
const parent2 = element.parentElement;
|
|
1019
|
+
const hasDataToggle = element.hasAttribute("data-toggle") || element.hasAttribute("data-target");
|
|
1020
|
+
const hasInteractiveClassIcon = element.classList.contains("selectable") || element.classList.contains("collapsed") || element.classList.contains("clickable");
|
|
1021
|
+
const hasClickHandlerIcon = element.hasAttribute("onclick") || parent2?.hasAttribute("onclick") || element.onclick !== null;
|
|
1022
|
+
if (hasDataToggle || hasInteractiveClassIcon || hasClickHandlerIcon) {
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
const hasPointerCursor = style.cursor === "pointer";
|
|
1026
|
+
if (!hasPointerCursor) return false;
|
|
1027
|
+
const hasInteractiveClass = parent2?.classList.contains("reset-link") || parent2?.classList.contains("clickable-icon") || parent2?.classList.contains("btn") || parent2?.classList.contains("button");
|
|
1028
|
+
const hasClickHandler = element.hasAttribute("onclick") || parent2?.hasAttribute("onclick") || element.onclick !== null;
|
|
1029
|
+
const hasTooltip = element.hasAttribute("ngbtooltip") || element.hasAttribute("title") || element.hasAttribute("aria-label");
|
|
1030
|
+
if (hasPointerCursor && (hasInteractiveClass || hasClickHandler || hasTooltip)) {
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
if (tagName === "div" && element.querySelector(":scope > input[role='combobox'], :scope > input[aria-autocomplete='list'], :scope > input[type='file']")) {
|
|
1036
|
+
return true;
|
|
1037
|
+
}
|
|
1038
|
+
if (tagName === "iframe") {
|
|
1039
|
+
return true;
|
|
1040
|
+
}
|
|
1041
|
+
if (tagName === "div" || tagName === "span") {
|
|
1042
|
+
const style = window.getComputedStyle(element);
|
|
1043
|
+
if (style.cursor === "pointer") {
|
|
1044
|
+
const hasContent = element.textContent.trim().length > 0;
|
|
1045
|
+
const rect = element.getBoundingClientRect();
|
|
1046
|
+
const hasReasonableSize = rect.width > 30 && rect.height > 20;
|
|
1047
|
+
if (hasContent && hasReasonableSize) {
|
|
1048
|
+
return true;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const parent = element.parentElement;
|
|
1053
|
+
if (parent && parent !== document.body) {
|
|
1054
|
+
const parentStyle = window.getComputedStyle(parent);
|
|
1055
|
+
const parentIsInteractive = parentStyle.cursor === "pointer" || parent.hasAttribute("onclick") || parent.onclick !== null || parent.hasAttribute("tabindex") && parent.getAttribute("tabindex") !== "-1";
|
|
1056
|
+
if (parentIsInteractive) {
|
|
1057
|
+
const interactiveChildren = Array.from(parent.children).filter((child) => {
|
|
1058
|
+
const childTag = child.tagName.toLowerCase();
|
|
1059
|
+
const childRole = child.getAttribute("role");
|
|
1060
|
+
const isInteractiveIcon = (childTag === "i" || childTag === "svg") && window.getComputedStyle(child).cursor === "pointer";
|
|
1061
|
+
return childTag === "button" || childTag === "a" || childTag === "input" || childTag === "select" || childTag === "textarea" || childRole && ["button", "link", "menuitem"].includes(childRole) || child.hasAttribute("onclick") || child.onclick !== null || isInteractiveIcon;
|
|
1062
|
+
});
|
|
1063
|
+
if (interactiveChildren.length === 1 && interactiveChildren[0] === element) {
|
|
1064
|
+
const hasUniqueAction = element.hasAttribute("onclick") || element.onclick !== null || element.hasAttribute("ng-click") || element.hasAttribute("@click") || element.hasAttribute("v-on:click") || tagName === "a" && element.hasAttribute("href") && element.getAttribute("href") !== "javascript:void(0)" && element.getAttribute("href") !== "#" || (tagName === "i" || tagName === "svg") && window.getComputedStyle(element).cursor === "pointer" && (element.hasAttribute("onclick") || element.hasAttribute("ngbtooltip") || element.parentElement?.classList.contains("reset-link"));
|
|
1065
|
+
if (!hasUniqueAction) {
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (DISTINCT_INTERACTIVE_TAGS.has(tagName)) {
|
|
1072
|
+
return true;
|
|
1073
|
+
}
|
|
1074
|
+
if (role && INTERACTIVE_ROLES.has(role)) {
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
1077
|
+
if (element.isContentEditable || element.getAttribute("contenteditable") === "true") {
|
|
1078
|
+
return true;
|
|
1079
|
+
}
|
|
1080
|
+
if (element.hasAttribute("data-testid") || element.hasAttribute("data-cy") || element.hasAttribute("data-test")) {
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
if (element.hasAttribute("onclick") || typeof element.onclick === "function") {
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
try {
|
|
1087
|
+
if (typeof window.getEventListeners === "function") {
|
|
1088
|
+
const listeners = window.getEventListeners(element);
|
|
1089
|
+
const interactionEvents = ["mousedown", "mouseup", "keydown", "keyup", "submit", "change", "input", "focus", "blur"];
|
|
1090
|
+
for (const eventType of interactionEvents) {
|
|
1091
|
+
if (listeners[eventType] && listeners[eventType].length > 0) {
|
|
1092
|
+
return true;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
} else {
|
|
1096
|
+
const commonEventAttrs = ["onmousedown", "onmouseup", "onkeydown", "onkeyup", "onsubmit", "onchange", "oninput", "onfocus", "onblur"];
|
|
1097
|
+
if (commonEventAttrs.some((attr) => element.hasAttribute(attr))) {
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
} catch (e) {
|
|
1102
|
+
}
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
function handleHighlighting(nodeData, node, parentIframe, isParentHighlighted) {
|
|
1106
|
+
const tag = node.tagName.toLowerCase();
|
|
1107
|
+
const id = node.id || "";
|
|
1108
|
+
const cls = (node.className || "").toString().slice(0, 150);
|
|
1109
|
+
if (!nodeData.isInteractive) {
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
if (tag === "div" || tag === "span") {
|
|
1113
|
+
const directInputs = Array.from(node.children).filter(
|
|
1114
|
+
(child) => ["input", "textarea", "select"].includes(child.tagName.toLowerCase())
|
|
1115
|
+
);
|
|
1116
|
+
if (directInputs.length > 0) {
|
|
1117
|
+
const hasVisibleInput = directInputs.some((input) => {
|
|
1118
|
+
try {
|
|
1119
|
+
const inputStyle = window.getComputedStyle(input);
|
|
1120
|
+
const inputRect = input.getBoundingClientRect();
|
|
1121
|
+
return inputStyle.display !== "none" && inputStyle.visibility !== "hidden" && parseFloat(inputStyle.opacity) > 0 && inputRect.width > 0 && inputRect.height > 0;
|
|
1122
|
+
} catch (e) {
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
if (hasVisibleInput) {
|
|
1127
|
+
return false;
|
|
1128
|
+
} else {
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (isParentHighlighted && node.tagName.toLowerCase() === "input") {
|
|
1133
|
+
isParentHighlighted = false;
|
|
1134
|
+
}
|
|
1135
|
+
if (tag === "div" && node.getAttribute("role") === "dialog" && node.classList.contains("b-sidebar")) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
let shouldHighlight = false;
|
|
1139
|
+
if (!isParentHighlighted) {
|
|
1140
|
+
shouldHighlight = true;
|
|
1141
|
+
} else if (isElementDistinctInteraction(node)) {
|
|
1142
|
+
shouldHighlight = true;
|
|
1143
|
+
}
|
|
1144
|
+
if (shouldHighlight) {
|
|
1145
|
+
nodeData.isInViewport = isInExpandedViewport(node, viewportExpansion);
|
|
1146
|
+
if (nodeData.isInViewport) {
|
|
1147
|
+
const rect = node.getBoundingClientRect();
|
|
1148
|
+
console.log(`[handleHighlighting] rect={x:${rect.x.toFixed(1)}, y:${rect.y.toFixed(1)}, w:${rect.width.toFixed(1)}, h:${rect.height.toFixed(1)}}`);
|
|
1149
|
+
if (rect.width < 5 || rect.height < 5) {
|
|
1150
|
+
if (!(node.tagName.toLowerCase() === "input" && node.hasAttribute("role"))) {
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const isInCurrentViewport = rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
1155
|
+
if (!isInCurrentViewport && viewportExpansion === 0) {
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
try {
|
|
1159
|
+
const style = window.getComputedStyle(node);
|
|
1160
|
+
const opacity = parseFloat(style.opacity);
|
|
1161
|
+
const visibility = style.visibility;
|
|
1162
|
+
const display = style.display;
|
|
1163
|
+
if (opacity === 0) {
|
|
1164
|
+
if (!(node.tagName.toLowerCase() === "input" && node.hasAttribute("role"))) {
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
} catch (e) {
|
|
1169
|
+
return false;
|
|
1170
|
+
}
|
|
1171
|
+
nodeData.highlightIndex = highlightIndex++;
|
|
1172
|
+
if (doHighlightElements) {
|
|
1173
|
+
if (focusHighlightIndex >= 0) {
|
|
1174
|
+
if (focusHighlightIndex === nodeData.highlightIndex) {
|
|
1175
|
+
highlightElement(node, nodeData.highlightIndex, parentIframe, false);
|
|
1176
|
+
} else {
|
|
1177
|
+
}
|
|
1178
|
+
} else {
|
|
1179
|
+
highlightElement(node, nodeData.highlightIndex, parentIframe, false);
|
|
1180
|
+
}
|
|
1181
|
+
return true;
|
|
1182
|
+
} else {
|
|
1183
|
+
}
|
|
1184
|
+
} else {
|
|
1185
|
+
}
|
|
1186
|
+
} else {
|
|
1187
|
+
}
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
const DISTINCT_INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
|
|
1191
|
+
"a",
|
|
1192
|
+
"button",
|
|
1193
|
+
"input",
|
|
1194
|
+
"select",
|
|
1195
|
+
"textarea",
|
|
1196
|
+
"summary",
|
|
1197
|
+
"details",
|
|
1198
|
+
"label",
|
|
1199
|
+
"option"
|
|
1200
|
+
]);
|
|
1201
|
+
const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
1202
|
+
"button",
|
|
1203
|
+
"link",
|
|
1204
|
+
"menuitem",
|
|
1205
|
+
"menuitemradio",
|
|
1206
|
+
"menuitemcheckbox",
|
|
1207
|
+
"radio",
|
|
1208
|
+
"checkbox",
|
|
1209
|
+
"tab",
|
|
1210
|
+
"switch",
|
|
1211
|
+
"slider",
|
|
1212
|
+
"spinbutton",
|
|
1213
|
+
"combobox",
|
|
1214
|
+
"searchbox",
|
|
1215
|
+
"textbox",
|
|
1216
|
+
"listbox",
|
|
1217
|
+
"option",
|
|
1218
|
+
"scrollbar"
|
|
1219
|
+
]);
|
|
1220
|
+
function isNodeEffectivelyHidden(node) {
|
|
1221
|
+
if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
1222
|
+
if (node.tagName?.toLowerCase() === "input" && node.hasAttribute("role")) {
|
|
1223
|
+
const style2 = window.getComputedStyle(node);
|
|
1224
|
+
if (style2.display === "none" || style2.visibility === "hidden") {
|
|
1225
|
+
return true;
|
|
1226
|
+
}
|
|
1227
|
+
return false;
|
|
1228
|
+
}
|
|
1229
|
+
if (node.shadowRoot) {
|
|
1230
|
+
console.log("Keeping element with shadow root:", node.tagName);
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
const tagName = node.tagName.toLowerCase();
|
|
1234
|
+
console.log("Checking tagName:", tagName, "includes dash?", tagName.includes("-"));
|
|
1235
|
+
if (tagName.includes("-")) {
|
|
1236
|
+
console.log("Keeping custom element (web component):", node.tagName);
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
const rect = node.getBoundingClientRect();
|
|
1240
|
+
const style = window.getComputedStyle(node);
|
|
1241
|
+
const isZeroSize = rect.width === 0 || rect.height === 0;
|
|
1242
|
+
const isFullyTransparent = parseFloat(style.opacity) === 0;
|
|
1243
|
+
const isDisplayNone = style.display === "none";
|
|
1244
|
+
const isVisibilityHidden = style.visibility === "hidden";
|
|
1245
|
+
const isPointerNone = style.pointerEvents === "none";
|
|
1246
|
+
const role = (node.getAttribute("role") || "").toLowerCase();
|
|
1247
|
+
const isModalOrOverlay = role === "dialog" || role === "alertdialog" || node.hasAttribute("aria-modal") || style.position === "fixed" && parseInt(String(style.zIndex || 0)) > 1e3;
|
|
1248
|
+
if (isModalOrOverlay && isPointerNone) {
|
|
1249
|
+
const hasVisibleChild = Array.from(node.querySelectorAll("*")).some((child) => {
|
|
1250
|
+
try {
|
|
1251
|
+
const cs = getComputedStyle(child);
|
|
1252
|
+
const r = child.getBoundingClientRect();
|
|
1253
|
+
return r.width > 0 && r.height > 0 && parseFloat(cs.opacity) > 0 && cs.visibility !== "hidden" && cs.display !== "none";
|
|
1254
|
+
} catch {
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
if (hasVisibleChild) return false;
|
|
1259
|
+
}
|
|
1260
|
+
if (isDisplayNone || isVisibilityHidden || isFullyTransparent) return true;
|
|
1261
|
+
if (isZeroSize) {
|
|
1262
|
+
const hasVisibleDescendant = Array.from(node.querySelectorAll("*")).some((el) => {
|
|
1263
|
+
const s = getComputedStyle(el);
|
|
1264
|
+
const r = el.getBoundingClientRect();
|
|
1265
|
+
return r.width > 0 && r.height > 0 && parseFloat(s.opacity) > 0 && s.display !== "none" && s.visibility !== "hidden";
|
|
1266
|
+
});
|
|
1267
|
+
if (hasVisibleDescendant) return false;
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
return false;
|
|
1271
|
+
}
|
|
1272
|
+
function buildDomTree(node, parentIframe = null, isParentHighlighted = false) {
|
|
1273
|
+
if (debugMode) PERF_METRICS.nodeMetrics.totalNodes++;
|
|
1274
|
+
if (!node || node.id === HIGHLIGHT_CONTAINER_ID) {
|
|
1275
|
+
if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
if (node.nodeType === Node.ELEMENT_NODE && node !== document.body) {
|
|
1279
|
+
try {
|
|
1280
|
+
if (isNodeEffectivelyHidden(node)) {
|
|
1281
|
+
if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
} catch {
|
|
1285
|
+
if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
let nodeWasHighlighted = false;
|
|
1290
|
+
if (node === document.body) {
|
|
1291
|
+
const nodeData2 = {
|
|
1292
|
+
tagName: "body",
|
|
1293
|
+
attributes: {},
|
|
1294
|
+
xpath: "/body",
|
|
1295
|
+
children: []
|
|
1296
|
+
};
|
|
1297
|
+
nodeData2.isVisible = isElementVisible(node);
|
|
1298
|
+
nodeData2.isTopElement = nodeData2.isVisible ? isTopElement(node) : false;
|
|
1299
|
+
nodeData2.isInteractive = false;
|
|
1300
|
+
nodeWasHighlighted = false;
|
|
1301
|
+
for (const child of node.childNodes) {
|
|
1302
|
+
const domElement = buildDomTree(child, parentIframe, false);
|
|
1303
|
+
if (domElement) nodeData2.children.push(domElement);
|
|
1304
|
+
}
|
|
1305
|
+
const id2 = `${ID.current++}`;
|
|
1306
|
+
DOM_HASH_MAP[id2] = nodeData2;
|
|
1307
|
+
if (debugMode) PERF_METRICS.nodeMetrics.processedNodes++;
|
|
1308
|
+
return id2;
|
|
1309
|
+
}
|
|
1310
|
+
if (node.nodeType !== Node.ELEMENT_NODE && node.nodeType !== Node.TEXT_NODE) {
|
|
1311
|
+
if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
|
|
1312
|
+
return null;
|
|
1313
|
+
}
|
|
1314
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1315
|
+
const textContent = (node.textContent || "").trim();
|
|
1316
|
+
if (!textContent) {
|
|
1317
|
+
if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
|
|
1318
|
+
return null;
|
|
1319
|
+
}
|
|
1320
|
+
const parentElement = node.parentElement;
|
|
1321
|
+
if (!parentElement || parentElement.tagName.toLowerCase() === "script") {
|
|
1322
|
+
if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
const id2 = `${ID.current++}`;
|
|
1326
|
+
DOM_HASH_MAP[id2] = {
|
|
1327
|
+
type: "TEXT_NODE",
|
|
1328
|
+
text: textContent,
|
|
1329
|
+
isVisible: isTextNodeVisible(node)
|
|
1330
|
+
};
|
|
1331
|
+
if (debugMode) PERF_METRICS.nodeMetrics.processedNodes++;
|
|
1332
|
+
return id2;
|
|
1333
|
+
}
|
|
1334
|
+
if (node.nodeType === Node.ELEMENT_NODE && !isElementAccepted(node)) {
|
|
1335
|
+
if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
|
|
1336
|
+
return null;
|
|
1337
|
+
}
|
|
1338
|
+
if (viewportExpansion !== -1) {
|
|
1339
|
+
const isModalRelated = node.classList?.contains("ant-modal-root") || node.classList?.contains("ant-modal-wrap") || node.classList?.contains("ant-modal") || node.getAttribute("role") === "dialog" || node.getAttribute("role") === "alertdialog" || node.querySelector && node.querySelector('.ant-modal, .ant-modal-wrap, [role="dialog"]');
|
|
1340
|
+
if (!isModalRelated) {
|
|
1341
|
+
const rect = getCachedBoundingRect(node);
|
|
1342
|
+
if (!rect) return null;
|
|
1343
|
+
const isInViewport = !(rect.bottom < -viewportExpansion || rect.top > window.innerHeight + viewportExpansion || rect.right < -viewportExpansion || rect.left > window.innerWidth + viewportExpansion);
|
|
1344
|
+
if (!isInViewport) return null;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
const nodeData = {
|
|
1348
|
+
tagName: node.tagName.toLowerCase(),
|
|
1349
|
+
attributes: {},
|
|
1350
|
+
xpath: getXPathTree(node, true),
|
|
1351
|
+
children: []
|
|
1352
|
+
};
|
|
1353
|
+
const attributeNames = node.getAttributeNames?.() || [];
|
|
1354
|
+
for (const name of attributeNames) {
|
|
1355
|
+
nodeData.attributes[name] = (node.getAttribute(name) || "").trim();
|
|
1356
|
+
}
|
|
1357
|
+
collectStructuralNode(node, nodeData);
|
|
1358
|
+
if (nodeData.attributes) {
|
|
1359
|
+
normalizeAttributes(nodeData.attributes);
|
|
1360
|
+
}
|
|
1361
|
+
if (node.nodeType === Node.ELEMENT_NODE && node !== document.body) {
|
|
1362
|
+
nodeData.isVisible = isElementVisible(node);
|
|
1363
|
+
if (nodeData.isVisible) {
|
|
1364
|
+
nodeData.isTopElement = isTopElement(node);
|
|
1365
|
+
if (nodeData.isTopElement) {
|
|
1366
|
+
nodeData.isInteractive = isInteractiveElement(node);
|
|
1367
|
+
if (nodeData.isInteractive) {
|
|
1368
|
+
console.log("[DETECTADO] Interactive Element:", node.tagName, node.getAttribute("aria-label") || "", node.className);
|
|
1369
|
+
nodeWasHighlighted = handleHighlighting(nodeData, node, parentIframe, isParentHighlighted);
|
|
1370
|
+
const rect = getCachedBoundingRect(node);
|
|
1371
|
+
if (rect) {
|
|
1372
|
+
nodeData.viewportCoordinates = {
|
|
1373
|
+
x: rect.left,
|
|
1374
|
+
y: rect.top,
|
|
1375
|
+
width: rect.width,
|
|
1376
|
+
height: rect.height
|
|
1377
|
+
};
|
|
1378
|
+
const scroll = getEffectiveScroll(node);
|
|
1379
|
+
nodeData.pageCoordinates = {
|
|
1380
|
+
x: rect.left + scroll.scrollX,
|
|
1381
|
+
y: rect.top + scroll.scrollY,
|
|
1382
|
+
width: rect.width,
|
|
1383
|
+
height: rect.height
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
getInteractiveNode(node, nodeData);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (!nodeData.isInteractive) {
|
|
1391
|
+
nodeData.isReference = isReferenceElement(node);
|
|
1392
|
+
if (nodeData.isReference && !isUnderModalOrOverlay(node)) {
|
|
1393
|
+
console.log("[DETECTADO] Reference Element:", node.tagName, node.textContent?.trim().substring(0, 30) || "", node.className);
|
|
1394
|
+
nodeData.highlightIndex = highlightIndex++;
|
|
1395
|
+
if (doHighlightElements) {
|
|
1396
|
+
highlightElement(node, nodeData.highlightIndex, parentIframe, true);
|
|
1397
|
+
}
|
|
1398
|
+
const rect = getCachedBoundingRect(node);
|
|
1399
|
+
if (rect) {
|
|
1400
|
+
nodeData.viewportCoordinates = {
|
|
1401
|
+
x: rect.left,
|
|
1402
|
+
y: rect.top,
|
|
1403
|
+
width: rect.width,
|
|
1404
|
+
height: rect.height
|
|
1405
|
+
};
|
|
1406
|
+
const scroll = getEffectiveScroll(node);
|
|
1407
|
+
nodeData.pageCoordinates = {
|
|
1408
|
+
x: rect.left + scroll.scrollX,
|
|
1409
|
+
y: rect.top + scroll.scrollY,
|
|
1410
|
+
width: rect.width,
|
|
1411
|
+
height: rect.height
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (node.tagName) {
|
|
1418
|
+
const tagName = node.tagName.toLowerCase();
|
|
1419
|
+
if (tagName === "iframe") {
|
|
1420
|
+
try {
|
|
1421
|
+
const iframeDoc = node.contentDocument || node.contentWindow?.document;
|
|
1422
|
+
if (iframeDoc) {
|
|
1423
|
+
for (const child of iframeDoc.childNodes) {
|
|
1424
|
+
const domElement = buildDomTree(child, node);
|
|
1425
|
+
if (domElement) nodeData.children.push(domElement);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
} catch (e) {
|
|
1429
|
+
console.warn("Unable to access iframe:", e);
|
|
1430
|
+
}
|
|
1431
|
+
} else if (node.isContentEditable || node.getAttribute("contenteditable") === "true" || node.id === "tinymce" || node.classList.contains("mce-content-body") || tagName === "body" && node.getAttribute("data-id")?.startsWith("mce_")) {
|
|
1432
|
+
for (const child of node.childNodes) {
|
|
1433
|
+
const domElement = buildDomTree(child, parentIframe, nodeWasHighlighted || isParentHighlighted);
|
|
1434
|
+
if (domElement) nodeData.children.push(domElement);
|
|
1435
|
+
}
|
|
1436
|
+
} else {
|
|
1437
|
+
if (node.shadowRoot) {
|
|
1438
|
+
nodeData.shadowRoot = true;
|
|
1439
|
+
for (const child of node.shadowRoot.childNodes) {
|
|
1440
|
+
const domElement = buildDomTree(child, parentIframe, nodeWasHighlighted || isParentHighlighted);
|
|
1441
|
+
if (domElement) nodeData.children.push(domElement);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
for (const child of node.childNodes) {
|
|
1445
|
+
const domElement = buildDomTree(child, parentIframe, nodeWasHighlighted || isParentHighlighted);
|
|
1446
|
+
if (domElement) nodeData.children.push(domElement);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
if (nodeData.tagName === "a" && nodeData.children.length === 0 && !nodeData.attributes.href) {
|
|
1451
|
+
if (debugMode) PERF_METRICS.nodeMetrics.skippedNodes++;
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
const id = `${ID.current++}`;
|
|
1455
|
+
DOM_HASH_MAP[id] = nodeData;
|
|
1456
|
+
if (debugMode) PERF_METRICS.nodeMetrics.processedNodes++;
|
|
1457
|
+
return id;
|
|
1458
|
+
}
|
|
1459
|
+
highlightElement = measureTime(highlightElement);
|
|
1460
|
+
isInteractiveElement = measureTime(isInteractiveElement);
|
|
1461
|
+
isElementVisible = measureTime(isElementVisible);
|
|
1462
|
+
isTopElement = measureTime(isTopElement);
|
|
1463
|
+
isInExpandedViewport = measureTime(isInExpandedViewport);
|
|
1464
|
+
isTextNodeVisible = measureTime(isTextNodeVisible);
|
|
1465
|
+
getEffectiveScroll = measureTime(getEffectiveScroll);
|
|
1466
|
+
const rootId = buildDomTree(document.body, null, false);
|
|
1467
|
+
if (_structuralElements.length > 0) {
|
|
1468
|
+
const highlightedEntries = Object.entries(DOM_HASH_MAP).filter(([_, node]) => node.highlightIndex !== void 0 && node.highlightIndex >= 0).map(([id, node]) => ({ id, node }));
|
|
1469
|
+
highlightedEntries.sort((a, b) => (a.node.highlightIndex ?? 0) - (b.node.highlightIndex ?? 0));
|
|
1470
|
+
for (const el of _structuralElements) {
|
|
1471
|
+
normalizeAttributes(el.attributes);
|
|
1472
|
+
const newId = `${ID.current++}`;
|
|
1473
|
+
const newEntry = { id: newId, node: el };
|
|
1474
|
+
const randomIndex = Math.floor(Math.random() * (highlightedEntries.length + 1));
|
|
1475
|
+
highlightedEntries.splice(randomIndex, 0, newEntry);
|
|
1476
|
+
}
|
|
1477
|
+
highlightedEntries.forEach((entry, index) => {
|
|
1478
|
+
entry.node.highlightIndex = index;
|
|
1479
|
+
DOM_HASH_MAP[entry.id] = entry.node;
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
DOM_CACHE.clearCache();
|
|
1483
|
+
if (debugMode && PERF_METRICS) {
|
|
1484
|
+
Object.keys(PERF_METRICS.timings).forEach((key) => {
|
|
1485
|
+
PERF_METRICS.timings[key] = PERF_METRICS.timings[key] / 1e3;
|
|
1486
|
+
});
|
|
1487
|
+
Object.keys(PERF_METRICS.buildDomTreeBreakdown).forEach((key) => {
|
|
1488
|
+
if (typeof PERF_METRICS.buildDomTreeBreakdown[key] === "number") {
|
|
1489
|
+
PERF_METRICS.buildDomTreeBreakdown[key] = PERF_METRICS.buildDomTreeBreakdown[key] / 1e3;
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
if (PERF_METRICS.buildDomTreeBreakdown.buildDomTreeCalls > 0) {
|
|
1493
|
+
PERF_METRICS.buildDomTreeBreakdown.averageTimePerNode = PERF_METRICS.buildDomTreeBreakdown.totalTime / PERF_METRICS.buildDomTreeBreakdown.buildDomTreeCalls;
|
|
1494
|
+
}
|
|
1495
|
+
PERF_METRICS.buildDomTreeBreakdown.timeInChildCalls = PERF_METRICS.buildDomTreeBreakdown.totalTime - PERF_METRICS.buildDomTreeBreakdown.totalSelfTime;
|
|
1496
|
+
Object.keys(PERF_METRICS.buildDomTreeBreakdown.domOperations).forEach((op) => {
|
|
1497
|
+
const time = PERF_METRICS.buildDomTreeBreakdown.domOperations[op];
|
|
1498
|
+
const count = PERF_METRICS.buildDomTreeBreakdown.domOperationCounts[op];
|
|
1499
|
+
if (count > 0) {
|
|
1500
|
+
PERF_METRICS.buildDomTreeBreakdown.domOperations[`${op}Average`] = time / count;
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
const boundingRectTotal = PERF_METRICS.cacheMetrics.boundingRectCacheHits + PERF_METRICS.cacheMetrics.boundingRectCacheMisses;
|
|
1504
|
+
const computedStyleTotal = PERF_METRICS.cacheMetrics.computedStyleCacheHits + PERF_METRICS.cacheMetrics.computedStyleCacheMisses;
|
|
1505
|
+
if (boundingRectTotal > 0) {
|
|
1506
|
+
PERF_METRICS.cacheMetrics.boundingRectHitRate = PERF_METRICS.cacheMetrics.boundingRectCacheHits / boundingRectTotal;
|
|
1507
|
+
}
|
|
1508
|
+
if (computedStyleTotal > 0) {
|
|
1509
|
+
PERF_METRICS.cacheMetrics.computedStyleHitRate = PERF_METRICS.cacheMetrics.computedStyleCacheHits / computedStyleTotal;
|
|
1510
|
+
}
|
|
1511
|
+
if (boundingRectTotal + computedStyleTotal > 0) {
|
|
1512
|
+
PERF_METRICS.cacheMetrics.overallHitRate = (PERF_METRICS.cacheMetrics.boundingRectCacheHits + PERF_METRICS.cacheMetrics.computedStyleCacheHits) / (boundingRectTotal + computedStyleTotal);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function tryToReconnectElement(elementInfo, index) {
|
|
1516
|
+
try {
|
|
1517
|
+
if (elementInfo.id) {
|
|
1518
|
+
const el = document.getElementById(elementInfo.id);
|
|
1519
|
+
if (el && el.isConnected) {
|
|
1520
|
+
return el;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
if (elementInfo.href && elementInfo.tagName === "a") {
|
|
1524
|
+
const links = document.querySelectorAll(`a[href="${elementInfo.href}"]`);
|
|
1525
|
+
for (const link of links) {
|
|
1526
|
+
if (link.isConnected && link.textContent?.trim().substring(0, 50) === elementInfo.textContent) {
|
|
1527
|
+
return link;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (elementInfo.className) {
|
|
1532
|
+
const elements = document.querySelectorAll(`${elementInfo.tagName}.${elementInfo.className.split(" ")[0]}`);
|
|
1533
|
+
for (const el of elements) {
|
|
1534
|
+
if (el.isConnected && el.textContent?.trim().substring(0, 50) === elementInfo.textContent) {
|
|
1535
|
+
return el;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (elementInfo.textContent) {
|
|
1540
|
+
const sidebarElements = document.querySelectorAll('nav a, [class*="sidebar"] a, .css-u0r6z4 a');
|
|
1541
|
+
for (const el of sidebarElements) {
|
|
1542
|
+
if (el.isConnected && el.tagName.toLowerCase() === elementInfo.tagName && el.textContent?.trim().substring(0, 50) === elementInfo.textContent) {
|
|
1543
|
+
return el;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return null;
|
|
1548
|
+
} catch (e) {
|
|
1549
|
+
console.error(`[Reconnect] Error trying to reconnect element ${index}:`, e);
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
function renderAllHighlights() {
|
|
1554
|
+
try {
|
|
1555
|
+
const old = document.getElementById("playwright-highlight-container");
|
|
1556
|
+
if (old) old.remove();
|
|
1557
|
+
const container = document.createElement("div");
|
|
1558
|
+
container.id = "playwright-highlight-container";
|
|
1559
|
+
container.style.position = "absolute";
|
|
1560
|
+
container.style.pointerEvents = "none";
|
|
1561
|
+
container.style.top = "0";
|
|
1562
|
+
container.style.left = "0";
|
|
1563
|
+
container.style.zIndex = "2147483647";
|
|
1564
|
+
container.style.overflow = "visible";
|
|
1565
|
+
container.style.width = `${document.documentElement.scrollWidth}px`;
|
|
1566
|
+
container.style.height = `${document.documentElement.scrollHeight}px`;
|
|
1567
|
+
document.body.appendChild(container);
|
|
1568
|
+
window._badgePositions = [];
|
|
1569
|
+
const ELEMENT_COLORS = {
|
|
1570
|
+
button: "#D70000",
|
|
1571
|
+
input: "#5182FF",
|
|
1572
|
+
select: "#FF8800",
|
|
1573
|
+
a: "#00A51E",
|
|
1574
|
+
textarea: "#00CCFF",
|
|
1575
|
+
reference: "#FFD700",
|
|
1576
|
+
default: "#D600D6"
|
|
1577
|
+
};
|
|
1578
|
+
let renderedCount = 0;
|
|
1579
|
+
let reconnectedCount = 0;
|
|
1580
|
+
for (const item of window._highlightQueue) {
|
|
1581
|
+
let { element, index, parentIframe, elementInfo } = item;
|
|
1582
|
+
if (!element) continue;
|
|
1583
|
+
if (!element.isConnected) {
|
|
1584
|
+
const reconnected = tryToReconnectElement(elementInfo, index);
|
|
1585
|
+
if (reconnected) {
|
|
1586
|
+
element = reconnected;
|
|
1587
|
+
item.element = reconnected;
|
|
1588
|
+
reconnectedCount++;
|
|
1589
|
+
console.log(`[Highlights] \u2713 Reconnected element ${index}: ${elementInfo.textContent}`);
|
|
1590
|
+
} else {
|
|
1591
|
+
console.log(`[Highlights] X Could not reconnect element ${index}: ${elementInfo.textContent}`);
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
try {
|
|
1596
|
+
const style = window.getComputedStyle(element);
|
|
1597
|
+
if (style.display === "none" || style.visibility === "hidden") continue;
|
|
1598
|
+
} catch (e) {
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
let rect;
|
|
1602
|
+
try {
|
|
1603
|
+
rect = element.getBoundingClientRect();
|
|
1604
|
+
} catch (e) {
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
if (!rect) continue;
|
|
1608
|
+
let docLeft, docTop;
|
|
1609
|
+
if (parentIframe) {
|
|
1610
|
+
try {
|
|
1611
|
+
if (!parentIframe.isConnected) continue;
|
|
1612
|
+
const iframeRect = parentIframe.getBoundingClientRect();
|
|
1613
|
+
docLeft = iframeRect.left + rect.left + window.pageXOffset;
|
|
1614
|
+
docTop = iframeRect.top + rect.top + window.pageYOffset;
|
|
1615
|
+
} catch (e) {
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
} else {
|
|
1619
|
+
docLeft = rect.left + window.pageXOffset;
|
|
1620
|
+
docTop = rect.top + window.pageYOffset;
|
|
1621
|
+
}
|
|
1622
|
+
const tag = (item.elementInfo?.tagName || "").toLowerCase();
|
|
1623
|
+
const isReference = item.elementInfo?.isReference || false;
|
|
1624
|
+
const color = isReference ? ELEMENT_COLORS["reference"] : ELEMENT_COLORS[tag] || ELEMENT_COLORS["default"];
|
|
1625
|
+
const overlay = document.createElement("div");
|
|
1626
|
+
overlay.style.position = "absolute";
|
|
1627
|
+
overlay.style.left = `${docLeft}px`;
|
|
1628
|
+
overlay.style.top = `${docTop}px`;
|
|
1629
|
+
overlay.style.width = `${rect.width}px`;
|
|
1630
|
+
overlay.style.height = `${rect.height}px`;
|
|
1631
|
+
overlay.style.border = `2px solid ${color}`;
|
|
1632
|
+
overlay.style.backgroundColor = color + "1A";
|
|
1633
|
+
overlay.style.pointerEvents = "none";
|
|
1634
|
+
overlay.style.boxSizing = "border-box";
|
|
1635
|
+
container.appendChild(overlay);
|
|
1636
|
+
renderedCount++;
|
|
1637
|
+
}
|
|
1638
|
+
const BADGE_SIZE = 16;
|
|
1639
|
+
const MARGIN = 2;
|
|
1640
|
+
for (const item of window._highlightQueue) {
|
|
1641
|
+
let { element, index, parentIframe, elementInfo } = item;
|
|
1642
|
+
if (!element) continue;
|
|
1643
|
+
if (!element.isConnected) {
|
|
1644
|
+
const reconnected = tryToReconnectElement(elementInfo, index);
|
|
1645
|
+
if (reconnected) {
|
|
1646
|
+
element = reconnected;
|
|
1647
|
+
item.element = reconnected;
|
|
1648
|
+
} else {
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
try {
|
|
1653
|
+
const style = window.getComputedStyle(element);
|
|
1654
|
+
if (style.display === "none" || style.visibility === "hidden") continue;
|
|
1655
|
+
} catch (e) {
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1658
|
+
let rect;
|
|
1659
|
+
try {
|
|
1660
|
+
rect = element.getBoundingClientRect();
|
|
1661
|
+
} catch (e) {
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
if (!rect) continue;
|
|
1665
|
+
let docLeft, docTop;
|
|
1666
|
+
if (parentIframe) {
|
|
1667
|
+
try {
|
|
1668
|
+
if (!parentIframe.isConnected) continue;
|
|
1669
|
+
const iframeRect = parentIframe.getBoundingClientRect();
|
|
1670
|
+
docLeft = iframeRect.left + rect.left + window.pageXOffset;
|
|
1671
|
+
docTop = iframeRect.top + rect.top + window.pageYOffset;
|
|
1672
|
+
} catch (e) {
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
} else {
|
|
1676
|
+
docLeft = rect.left + window.pageXOffset;
|
|
1677
|
+
docTop = rect.top + window.pageYOffset;
|
|
1678
|
+
}
|
|
1679
|
+
const tag = (item.elementInfo?.tagName || "").toLowerCase();
|
|
1680
|
+
const isReference = item.elementInfo?.isReference || false;
|
|
1681
|
+
const color = isReference ? ELEMENT_COLORS["reference"] : ELEMENT_COLORS[tag] || ELEMENT_COLORS["default"];
|
|
1682
|
+
const label = document.createElement("div");
|
|
1683
|
+
label.className = "playwright-highlight-label";
|
|
1684
|
+
label.style.position = "absolute";
|
|
1685
|
+
label.style.width = `${BADGE_SIZE}px`;
|
|
1686
|
+
label.style.height = `${BADGE_SIZE}px`;
|
|
1687
|
+
label.style.background = color;
|
|
1688
|
+
label.style.color = "white";
|
|
1689
|
+
label.style.border = `1px solid ${color}`;
|
|
1690
|
+
label.style.boxShadow = "0 0 3px black";
|
|
1691
|
+
label.style.borderRadius = "50%";
|
|
1692
|
+
label.style.display = "flex";
|
|
1693
|
+
label.style.alignItems = "center";
|
|
1694
|
+
label.style.justifyContent = "center";
|
|
1695
|
+
label.style.fontSize = "11px";
|
|
1696
|
+
label.style.fontWeight = "bold";
|
|
1697
|
+
label.textContent = String(index);
|
|
1698
|
+
const candidates = [
|
|
1699
|
+
{ x: docLeft, y: docTop - BADGE_SIZE - MARGIN },
|
|
1700
|
+
{ x: docLeft + rect.width - BADGE_SIZE, y: docTop - BADGE_SIZE - MARGIN },
|
|
1701
|
+
{ x: docLeft, y: docTop + rect.height + MARGIN },
|
|
1702
|
+
{ x: docLeft + rect.width - BADGE_SIZE, y: docTop + rect.height + MARGIN },
|
|
1703
|
+
{ x: docLeft + MARGIN, y: docTop + MARGIN },
|
|
1704
|
+
{ x: docLeft + rect.width - BADGE_SIZE - MARGIN, y: docTop + MARGIN }
|
|
1705
|
+
];
|
|
1706
|
+
const collides = (c) => window._badgePositions.some((p) => {
|
|
1707
|
+
const xOverlap = c.x < p.x + BADGE_SIZE && c.x + BADGE_SIZE > p.x;
|
|
1708
|
+
const yOverlap = c.y < p.y + BADGE_SIZE && c.y + BADGE_SIZE > p.y;
|
|
1709
|
+
return xOverlap && yOverlap;
|
|
1710
|
+
});
|
|
1711
|
+
let best = candidates.find((c) => !collides(c));
|
|
1712
|
+
if (!best) {
|
|
1713
|
+
best = {
|
|
1714
|
+
x: docLeft + rect.width / 2 - BADGE_SIZE / 2,
|
|
1715
|
+
y: docTop - BADGE_SIZE - MARGIN
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
if (best.y < window.pageYOffset) best.y = docTop + 2;
|
|
1719
|
+
if (best.x < window.pageXOffset) best.x = docLeft + 2;
|
|
1720
|
+
const docWidth = document.documentElement.scrollWidth;
|
|
1721
|
+
const docHeight = document.documentElement.scrollHeight;
|
|
1722
|
+
if (best.x + BADGE_SIZE > docWidth) {
|
|
1723
|
+
best.x = docWidth - BADGE_SIZE - 2;
|
|
1724
|
+
}
|
|
1725
|
+
if (best.y + BADGE_SIZE > docHeight) {
|
|
1726
|
+
best.y = docHeight - BADGE_SIZE - 2;
|
|
1727
|
+
}
|
|
1728
|
+
window._badgePositions.push(best);
|
|
1729
|
+
label.style.left = `${best.x}px`;
|
|
1730
|
+
label.style.top = `${best.y}px`;
|
|
1731
|
+
container.appendChild(label);
|
|
1732
|
+
}
|
|
1733
|
+
console.log(`[Highlights] Rendered ${renderedCount} boxes (${reconnectedCount} reconnected)`);
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
console.error("[Highlights] Error in renderAllHighlights:", error);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
function recalcAndRender() {
|
|
1739
|
+
if (!window._highlightQueue?.length) return;
|
|
1740
|
+
if (DOM_CACHE && DOM_CACHE.clearCache) {
|
|
1741
|
+
DOM_CACHE.clearCache();
|
|
1742
|
+
}
|
|
1743
|
+
renderAllHighlights();
|
|
1744
|
+
}
|
|
1745
|
+
window.addEventListener("scroll", () => {
|
|
1746
|
+
if (!window._highlightScrollFrame) {
|
|
1747
|
+
window._highlightScrollFrame = requestAnimationFrame(() => {
|
|
1748
|
+
recalcAndRender();
|
|
1749
|
+
window._highlightScrollFrame = null;
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
}, true);
|
|
1753
|
+
window.addEventListener("resize", recalcAndRender);
|
|
1754
|
+
if (doHighlightElements) {
|
|
1755
|
+
renderAllHighlights();
|
|
1756
|
+
}
|
|
1757
|
+
return debugMode ? { rootId, map: DOM_HASH_MAP, perfMetrics: PERF_METRICS } : { rootId, map: DOM_HASH_MAP };
|
|
1758
|
+
}
|
|
1759
|
+
;
|
|
1760
|
+
window.buildDomTreeMain = buildDomTreeMain;
|