@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,338 @@
|
|
|
1
|
+
class DOMBaseNode {
|
|
2
|
+
is_visible;
|
|
3
|
+
parent;
|
|
4
|
+
constructor(isVisible, parent = null) {
|
|
5
|
+
this.is_visible = isVisible;
|
|
6
|
+
this.parent = parent;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
class DOMTextNode extends DOMBaseNode {
|
|
10
|
+
text;
|
|
11
|
+
type;
|
|
12
|
+
constructor(text, isVisible, parent = null) {
|
|
13
|
+
super(isVisible, parent);
|
|
14
|
+
this.text = text;
|
|
15
|
+
this.type = "TEXT_NODE";
|
|
16
|
+
}
|
|
17
|
+
has_parent_with_highlight_index() {
|
|
18
|
+
let current = this.parent;
|
|
19
|
+
while (current !== null) {
|
|
20
|
+
if (current.highlight_index !== null && current.highlight_index !== void 0) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
current = current.parent;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function convertSimpleXpathToCssSelector(xpath) {
|
|
29
|
+
if (!xpath) return "";
|
|
30
|
+
xpath = xpath.replace(/^\/+/, "");
|
|
31
|
+
const parts = xpath.split("/");
|
|
32
|
+
const cssParts = [];
|
|
33
|
+
for (let part of parts) {
|
|
34
|
+
if (!part) continue;
|
|
35
|
+
if (part.includes(":") && !part.includes("[")) {
|
|
36
|
+
const basePart = part.replace(/:/g, "\\:");
|
|
37
|
+
cssParts.push(basePart);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (part.includes("[")) {
|
|
41
|
+
let basePart = part.substring(0, part.indexOf("["));
|
|
42
|
+
if (basePart.includes(":")) {
|
|
43
|
+
basePart = basePart.replace(/:/g, "\\:");
|
|
44
|
+
}
|
|
45
|
+
const indexPart = part.substring(part.indexOf("["));
|
|
46
|
+
const indices = indexPart.split("]").filter((i) => i).map((i) => i.replace("[", ""));
|
|
47
|
+
for (const idx of indices) {
|
|
48
|
+
try {
|
|
49
|
+
if (/^\d+$/.test(idx)) {
|
|
50
|
+
const index = parseInt(idx) - 1;
|
|
51
|
+
basePart += `:nth-of-type(${index + 1})`;
|
|
52
|
+
} else if (idx === "last()") {
|
|
53
|
+
basePart += ":last-of-type";
|
|
54
|
+
} else if (idx.includes("position()") && idx.includes(">1")) {
|
|
55
|
+
basePart += ":nth-of-type(n+2)";
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
cssParts.push(basePart);
|
|
62
|
+
} else {
|
|
63
|
+
cssParts.push(part);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return cssParts.join(" > ");
|
|
67
|
+
}
|
|
68
|
+
function enhancedCssSelectorForElement(element, includeDynamicAttributes = true) {
|
|
69
|
+
try {
|
|
70
|
+
let cssSelector = convertSimpleXpathToCssSelector(element.xpath);
|
|
71
|
+
if (element.attributes.class && includeDynamicAttributes) {
|
|
72
|
+
const validClassNamePattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
|
|
73
|
+
const classes = element.attributes.class.split(/\s+/);
|
|
74
|
+
for (const className of classes) {
|
|
75
|
+
if (!className.trim()) continue;
|
|
76
|
+
if (validClassNamePattern.test(className)) {
|
|
77
|
+
cssSelector += `.${className}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const SAFE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
82
|
+
"id",
|
|
83
|
+
"name",
|
|
84
|
+
"type",
|
|
85
|
+
"placeholder",
|
|
86
|
+
"aria-label",
|
|
87
|
+
"aria-labelledby",
|
|
88
|
+
"aria-describedby",
|
|
89
|
+
"role",
|
|
90
|
+
"for",
|
|
91
|
+
"autocomplete",
|
|
92
|
+
"required",
|
|
93
|
+
"readonly",
|
|
94
|
+
"alt",
|
|
95
|
+
"title",
|
|
96
|
+
"src",
|
|
97
|
+
"href",
|
|
98
|
+
"target"
|
|
99
|
+
]);
|
|
100
|
+
if (includeDynamicAttributes) {
|
|
101
|
+
SAFE_ATTRIBUTES.add("data-id");
|
|
102
|
+
SAFE_ATTRIBUTES.add("data-qa");
|
|
103
|
+
SAFE_ATTRIBUTES.add("data-cy");
|
|
104
|
+
SAFE_ATTRIBUTES.add("data-testid");
|
|
105
|
+
}
|
|
106
|
+
for (const [attribute, value] of Object.entries(element.attributes)) {
|
|
107
|
+
if (attribute === "class") continue;
|
|
108
|
+
if (!attribute.trim()) continue;
|
|
109
|
+
if (!SAFE_ATTRIBUTES.has(attribute)) continue;
|
|
110
|
+
const safeAttribute = attribute.replace(/:/g, "\\:");
|
|
111
|
+
if (value === "") {
|
|
112
|
+
cssSelector += `[${safeAttribute}]`;
|
|
113
|
+
} else if (/["'<>`\n\r\t]/.test(value)) {
|
|
114
|
+
let processedValue = value;
|
|
115
|
+
if (processedValue.includes("\n")) {
|
|
116
|
+
processedValue = processedValue.split("\n")[0];
|
|
117
|
+
}
|
|
118
|
+
const collapsedValue = processedValue.replace(/\s+/g, " ").trim();
|
|
119
|
+
const safeValue = collapsedValue.replace(/"/g, '\\"');
|
|
120
|
+
cssSelector += `[${safeAttribute}*="${safeValue}"]`;
|
|
121
|
+
} else {
|
|
122
|
+
cssSelector += `[${safeAttribute}="${value}"]`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return cssSelector;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const tagName = element.tag_name || "*";
|
|
128
|
+
return `${tagName}[highlight_index='${element.highlight_index}']`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
class DOMElementNode extends DOMBaseNode {
|
|
132
|
+
tag_name;
|
|
133
|
+
xpath;
|
|
134
|
+
attributes;
|
|
135
|
+
children;
|
|
136
|
+
childrenTags;
|
|
137
|
+
is_interactive;
|
|
138
|
+
is_reference;
|
|
139
|
+
is_top_element;
|
|
140
|
+
is_in_viewport;
|
|
141
|
+
shadow_root;
|
|
142
|
+
highlight_index;
|
|
143
|
+
page_coordinates;
|
|
144
|
+
viewport_coordinates;
|
|
145
|
+
_css_selector;
|
|
146
|
+
constructor(data) {
|
|
147
|
+
super(data.isVisible || false, null);
|
|
148
|
+
this.tag_name = data.tagName || "";
|
|
149
|
+
this.xpath = data.xpath || "";
|
|
150
|
+
this.attributes = data.attributes || {};
|
|
151
|
+
this.children = [];
|
|
152
|
+
this.childrenTags = data.childrenTags || [];
|
|
153
|
+
this.is_interactive = data.isInteractive || false;
|
|
154
|
+
this.is_reference = data.isReference || false;
|
|
155
|
+
this.is_top_element = data.isTopElement || false;
|
|
156
|
+
this.is_in_viewport = data.isInViewport || false;
|
|
157
|
+
this.shadow_root = data.shadowRoot || false;
|
|
158
|
+
this.highlight_index = data.highlightIndex;
|
|
159
|
+
this.page_coordinates = data.pageCoordinates || null;
|
|
160
|
+
this.viewport_coordinates = data.viewportCoordinates || null;
|
|
161
|
+
this._css_selector = null;
|
|
162
|
+
}
|
|
163
|
+
get css_selector() {
|
|
164
|
+
if (this._css_selector === null) {
|
|
165
|
+
this._css_selector = enhancedCssSelectorForElement(this, true);
|
|
166
|
+
}
|
|
167
|
+
return this._css_selector;
|
|
168
|
+
}
|
|
169
|
+
get_meaningful_text() {
|
|
170
|
+
const semanticAttrs = ["value", "aria-label", "title", "placeholder", "alt", "name"];
|
|
171
|
+
for (const attr of semanticAttrs) {
|
|
172
|
+
if (this.attributes[attr]) {
|
|
173
|
+
return this.clean_text(this.attributes[attr]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const tag = this.tag_name.toLowerCase();
|
|
177
|
+
if (tag === "a") {
|
|
178
|
+
for (const child of this.children) {
|
|
179
|
+
if (!(child instanceof DOMElementNode)) continue;
|
|
180
|
+
const ctag = child.tag_name.toLowerCase();
|
|
181
|
+
if (ctag === "img") {
|
|
182
|
+
const alt = child.attributes.alt || "";
|
|
183
|
+
const title = child.attributes.title || "";
|
|
184
|
+
if (alt) return this.clean_text(alt);
|
|
185
|
+
if (title) return this.clean_text(title);
|
|
186
|
+
} else if (ctag === "svg") {
|
|
187
|
+
const ariaLabel = child.attributes["aria-label"] || "";
|
|
188
|
+
const title = child.attributes.title || "";
|
|
189
|
+
if (ariaLabel) return this.clean_text(ariaLabel);
|
|
190
|
+
if (title) return this.clean_text(title);
|
|
191
|
+
for (const g of child.children) {
|
|
192
|
+
if (g instanceof DOMElementNode && g.tag_name.toLowerCase() === "title") {
|
|
193
|
+
const titleText = this.get_all_text_from_node(g);
|
|
194
|
+
if (titleText) return this.clean_text(titleText);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (tag === "img") {
|
|
201
|
+
const alt = this.attributes.alt || "";
|
|
202
|
+
const title = this.attributes.title || "";
|
|
203
|
+
if (alt) return this.clean_text(alt);
|
|
204
|
+
if (title) return this.clean_text(title);
|
|
205
|
+
}
|
|
206
|
+
if (tag === "svg") {
|
|
207
|
+
const ariaLabel = this.attributes["aria-label"] || "";
|
|
208
|
+
const title = this.attributes.title || "";
|
|
209
|
+
if (ariaLabel) return this.clean_text(ariaLabel);
|
|
210
|
+
if (title) return this.clean_text(title);
|
|
211
|
+
for (const child of this.children) {
|
|
212
|
+
if (child instanceof DOMElementNode && child.tag_name.toLowerCase() === "title") {
|
|
213
|
+
const titleText = this.get_all_text_from_node(child);
|
|
214
|
+
if (titleText) return this.clean_text(titleText);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const textVal = this.get_all_children_text(-1).trim();
|
|
219
|
+
return textVal ? this.clean_text(textVal) : "";
|
|
220
|
+
}
|
|
221
|
+
get_all_children_text(maxDepth = 1) {
|
|
222
|
+
const textParts = [];
|
|
223
|
+
const collectText = (node, currentDepth) => {
|
|
224
|
+
if (maxDepth !== -1 && currentDepth > maxDepth) return;
|
|
225
|
+
if (node instanceof DOMTextNode) {
|
|
226
|
+
if (node.text && node.is_visible) {
|
|
227
|
+
textParts.push(node.text.trim());
|
|
228
|
+
}
|
|
229
|
+
} else if (node instanceof DOMElementNode) {
|
|
230
|
+
for (const child of node.children) {
|
|
231
|
+
collectText(child, currentDepth + 1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const children = this.children || [];
|
|
236
|
+
if (children.length === 1 && children[0] instanceof DOMElementNode) {
|
|
237
|
+
const child = children[0];
|
|
238
|
+
const tag = child.tag_name.toLowerCase();
|
|
239
|
+
if (tag === "div" || tag === "span") {
|
|
240
|
+
for (const gc of child.children) {
|
|
241
|
+
collectText(gc, 1);
|
|
242
|
+
}
|
|
243
|
+
return textParts.filter((t) => t).join(" ");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
for (const child of children) {
|
|
247
|
+
collectText(child, 1);
|
|
248
|
+
}
|
|
249
|
+
return textParts.filter((t) => t).join(" ");
|
|
250
|
+
}
|
|
251
|
+
get_all_text_from_node(node) {
|
|
252
|
+
if (node instanceof DOMTextNode) {
|
|
253
|
+
return node.text;
|
|
254
|
+
} else if (node instanceof DOMElementNode) {
|
|
255
|
+
return node.get_all_children_text(-1);
|
|
256
|
+
}
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
clean_text(text) {
|
|
260
|
+
if (!text) return "";
|
|
261
|
+
const parts = text.split(/\s+/);
|
|
262
|
+
const mid = Math.floor(parts.length / 2);
|
|
263
|
+
if (parts.length > 1) {
|
|
264
|
+
const firstHalf = parts.slice(0, mid).join(" ");
|
|
265
|
+
const secondHalf = parts.slice(mid).join(" ");
|
|
266
|
+
if (firstHalf === secondHalf) {
|
|
267
|
+
return firstHalf;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return parts.join(" ");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
class DomService {
|
|
274
|
+
async buildDomTree(page, jsCode, options = {}) {
|
|
275
|
+
const args = {
|
|
276
|
+
doHighlightElements: options.highlightElements ?? true,
|
|
277
|
+
focusHighlightIndex: options.focusElement ?? -1,
|
|
278
|
+
viewportExpansion: options.viewportExpansion ?? -1,
|
|
279
|
+
debugMode: options.debugMode ?? false
|
|
280
|
+
};
|
|
281
|
+
const evalPage = await page.evaluate(({ code, params }) => {
|
|
282
|
+
eval(code);
|
|
283
|
+
return window.buildDomTreeMain(params);
|
|
284
|
+
}, { code: jsCode, params: args });
|
|
285
|
+
return this.constructDomTree(evalPage);
|
|
286
|
+
}
|
|
287
|
+
constructDomTree(evalPage2) {
|
|
288
|
+
const jsNodeMap = evalPage2.map;
|
|
289
|
+
const jsRootId = evalPage2.rootId;
|
|
290
|
+
const selectorMap = {};
|
|
291
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
292
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
293
|
+
for (const [id, nodeData] of Object.entries(jsNodeMap)) {
|
|
294
|
+
const { node, childrenIds } = this.parseNode(nodeData);
|
|
295
|
+
if (!node) continue;
|
|
296
|
+
nodeMap.set(id, node);
|
|
297
|
+
childrenMap.set(id, childrenIds);
|
|
298
|
+
if (node instanceof DOMElementNode && node.highlight_index !== null && node.highlight_index !== void 0) {
|
|
299
|
+
selectorMap[node.highlight_index] = node;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
for (const [idStr, node] of nodeMap.entries()) {
|
|
303
|
+
if (node instanceof DOMElementNode) {
|
|
304
|
+
const childIds = childrenMap.get(idStr) || [];
|
|
305
|
+
for (const childId of childIds) {
|
|
306
|
+
const childNode = nodeMap.get(String(childId));
|
|
307
|
+
if (childNode) {
|
|
308
|
+
childNode.parent = node;
|
|
309
|
+
node.children.push(childNode);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const rootNode = nodeMap.get(String(jsRootId));
|
|
315
|
+
return { elementTree: rootNode, selectorMap };
|
|
316
|
+
}
|
|
317
|
+
parseNode(nodeData) {
|
|
318
|
+
if (!nodeData) return { node: null, childrenIds: [] };
|
|
319
|
+
if (nodeData.type === "TEXT_NODE") {
|
|
320
|
+
const textNode = new DOMTextNode(
|
|
321
|
+
nodeData.text || "",
|
|
322
|
+
nodeData.isVisible || false,
|
|
323
|
+
null
|
|
324
|
+
);
|
|
325
|
+
return { node: textNode, childrenIds: [] };
|
|
326
|
+
}
|
|
327
|
+
const elementNode = new DOMElementNode(nodeData);
|
|
328
|
+
const childrenIds = nodeData.children || [];
|
|
329
|
+
return { node: elementNode, childrenIds };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
export {
|
|
333
|
+
DOMBaseNode,
|
|
334
|
+
DOMElementNode,
|
|
335
|
+
DOMTextNode,
|
|
336
|
+
DomService
|
|
337
|
+
};
|
|
338
|
+
//# sourceMappingURL=dom-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/dom-service.ts"],"sourcesContent":["/**\n * DOM Node Classes - Port from Python views.py\n */\nimport type { Page } from '@playwright/test';\n\nexport interface Coordinates {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface DOMElementData {\n tagName?: string;\n xpath?: string;\n attributes?: Record<string, string>;\n childrenTags?: string[];\n isInteractive?: boolean;\n isReference?: boolean;\n isTopElement?: boolean;\n isInViewport?: boolean;\n isVisible?: boolean;\n shadowRoot?: boolean;\n highlightIndex?: number | null;\n pageCoordinates?: Coordinates | null;\n viewportCoordinates?: Coordinates | null;\n children?: number[];\n type?: string;\n text?: string;\n}\n\ninterface BuildDomTreeEvalResult {\n rootId: number;\n map: Record<string, DOMElementData>;\n}\n\nexport interface DomTreeResult {\n elementTree: DOMElementNode | undefined;\n selectorMap: Record<number, DOMElementNode>;\n}\n\nexport class DOMBaseNode {\n is_visible: boolean;\n parent: DOMElementNode | null;\n\n constructor(isVisible: boolean, parent: DOMElementNode | null = null) {\n this.is_visible = isVisible;\n this.parent = parent;\n }\n}\n\nexport class DOMTextNode extends DOMBaseNode {\n text: string;\n type: string;\n\n constructor(text: string, isVisible: boolean, parent: DOMElementNode | null = null) {\n super(isVisible, parent);\n this.text = text;\n this.type = 'TEXT_NODE';\n }\n\n has_parent_with_highlight_index(): boolean {\n let current: DOMElementNode | null = this.parent;\n while (current !== null) {\n if (current.highlight_index !== null && current.highlight_index !== undefined) {\n return true;\n }\n current = current.parent;\n }\n return false;\n }\n}\n\n/**\n * Port of enhanced_css_selector_for_element from Python\n */\nfunction convertSimpleXpathToCssSelector(xpath: string): string {\n if (!xpath) return '';\n\n xpath = xpath.replace(/^\\/+/, '');\n const parts = xpath.split('/');\n const cssParts: string[] = [];\n\n for (let part of parts) {\n if (!part) continue;\n\n if (part.includes(':') && !part.includes('[')) {\n const basePart = part.replace(/:/g, '\\\\:');\n cssParts.push(basePart);\n continue;\n }\n\n if (part.includes('[')) {\n let basePart = part.substring(0, part.indexOf('['));\n\n if (basePart.includes(':')) {\n basePart = basePart.replace(/:/g, '\\\\:');\n }\n\n const indexPart = part.substring(part.indexOf('['));\n const indices = indexPart.split(']').filter(i => i).map(i => i.replace('[', ''));\n\n for (const idx of indices) {\n try {\n if (/^\\d+$/.test(idx)) {\n const index = parseInt(idx) - 1;\n basePart += `:nth-of-type(${index + 1})`;\n } else if (idx === 'last()') {\n basePart += ':last-of-type';\n } else if (idx.includes('position()') && idx.includes('>1')) {\n basePart += ':nth-of-type(n+2)';\n }\n } catch (e) {\n continue;\n }\n }\n\n cssParts.push(basePart);\n } else {\n cssParts.push(part);\n }\n }\n\n return cssParts.join(' > ');\n}\n\nfunction enhancedCssSelectorForElement(element: DOMElementNode, includeDynamicAttributes = true): string {\n try {\n let cssSelector = convertSimpleXpathToCssSelector(element.xpath);\n\n if (element.attributes.class && includeDynamicAttributes) {\n const validClassNamePattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;\n const classes = element.attributes.class.split(/\\s+/);\n\n for (const className of classes) {\n if (!className.trim()) continue;\n if (validClassNamePattern.test(className)) {\n cssSelector += `.${className}`;\n }\n }\n }\n\n const SAFE_ATTRIBUTES = new Set([\n 'id', 'name', 'type', 'placeholder',\n 'aria-label', 'aria-labelledby', 'aria-describedby', 'role',\n 'for', 'autocomplete', 'required', 'readonly',\n 'alt', 'title', 'src', 'href', 'target'\n ]);\n\n if (includeDynamicAttributes) {\n SAFE_ATTRIBUTES.add('data-id');\n SAFE_ATTRIBUTES.add('data-qa');\n SAFE_ATTRIBUTES.add('data-cy');\n SAFE_ATTRIBUTES.add('data-testid');\n }\n\n for (const [attribute, value] of Object.entries(element.attributes)) {\n if (attribute === 'class') continue;\n if (!attribute.trim()) continue;\n if (!SAFE_ATTRIBUTES.has(attribute)) continue;\n\n const safeAttribute = attribute.replace(/:/g, '\\\\:');\n\n if (value === '') {\n cssSelector += `[${safeAttribute}]`;\n } else if (/[\"'<>`\\n\\r\\t]/.test(value)) {\n let processedValue = value;\n if (processedValue.includes('\\n')) {\n processedValue = processedValue.split('\\n')[0];\n }\n const collapsedValue = processedValue.replace(/\\s+/g, ' ').trim();\n const safeValue = collapsedValue.replace(/\"/g, '\\\\\"');\n cssSelector += `[${safeAttribute}*=\"${safeValue}\"]`;\n } else {\n cssSelector += `[${safeAttribute}=\"${value}\"]`;\n }\n }\n\n return cssSelector;\n\n } catch (error) {\n const tagName = element.tag_name || '*';\n return `${tagName}[highlight_index='${element.highlight_index}']`;\n }\n}\n\nexport class DOMElementNode extends DOMBaseNode {\n tag_name: string;\n xpath: string;\n attributes: Record<string, string>;\n children: (DOMElementNode | DOMTextNode)[];\n childrenTags: string[];\n is_interactive: boolean;\n is_reference: boolean;\n is_top_element: boolean;\n is_in_viewport: boolean;\n shadow_root: boolean;\n highlight_index: number | null | undefined;\n page_coordinates: Coordinates | null;\n viewport_coordinates: Coordinates | null;\n private _css_selector: string | null;\n\n constructor(data: DOMElementData) {\n super(data.isVisible || false, null);\n\n this.tag_name = data.tagName || '';\n this.xpath = data.xpath || '';\n this.attributes = data.attributes || {};\n this.children = [];\n this.childrenTags = data.childrenTags || [];\n this.is_interactive = data.isInteractive || false;\n this.is_reference = data.isReference || false;\n this.is_top_element = data.isTopElement || false;\n this.is_in_viewport = data.isInViewport || false;\n this.shadow_root = data.shadowRoot || false;\n this.highlight_index = data.highlightIndex;\n this.page_coordinates = data.pageCoordinates || null;\n this.viewport_coordinates = data.viewportCoordinates || null;\n this._css_selector = null;\n }\n\n get css_selector(): string {\n if (this._css_selector === null) {\n this._css_selector = enhancedCssSelectorForElement(this, true);\n }\n return this._css_selector;\n }\n\n get_meaningful_text(): string {\n const semanticAttrs = ['value', 'aria-label', 'title', 'placeholder', 'alt', 'name'];\n for (const attr of semanticAttrs) {\n if (this.attributes[attr]) {\n return this.clean_text(this.attributes[attr]);\n }\n }\n\n const tag = this.tag_name.toLowerCase();\n\n if (tag === 'a') {\n for (const child of this.children) {\n if (!(child instanceof DOMElementNode)) continue;\n const ctag = child.tag_name.toLowerCase();\n\n if (ctag === 'img') {\n const alt = child.attributes.alt || '';\n const title = child.attributes.title || '';\n if (alt) return this.clean_text(alt);\n if (title) return this.clean_text(title);\n } else if (ctag === 'svg') {\n const ariaLabel = child.attributes['aria-label'] || '';\n const title = child.attributes.title || '';\n if (ariaLabel) return this.clean_text(ariaLabel);\n if (title) return this.clean_text(title);\n\n for (const g of child.children) {\n if (g instanceof DOMElementNode && g.tag_name.toLowerCase() === 'title') {\n const titleText = this.get_all_text_from_node(g);\n if (titleText) return this.clean_text(titleText);\n }\n }\n }\n }\n }\n\n if (tag === 'img') {\n const alt = this.attributes.alt || '';\n const title = this.attributes.title || '';\n if (alt) return this.clean_text(alt);\n if (title) return this.clean_text(title);\n }\n\n if (tag === 'svg') {\n const ariaLabel = this.attributes['aria-label'] || '';\n const title = this.attributes.title || '';\n if (ariaLabel) return this.clean_text(ariaLabel);\n if (title) return this.clean_text(title);\n\n for (const child of this.children) {\n if (child instanceof DOMElementNode && child.tag_name.toLowerCase() === 'title') {\n const titleText = this.get_all_text_from_node(child);\n if (titleText) return this.clean_text(titleText);\n }\n }\n }\n\n const textVal = this.get_all_children_text(-1).trim();\n return textVal ? this.clean_text(textVal) : '';\n }\n\n get_all_children_text(maxDepth = 1): string {\n const textParts: string[] = [];\n\n const collectText = (node: DOMElementNode | DOMTextNode, currentDepth: number): void => {\n if (maxDepth !== -1 && currentDepth > maxDepth) return;\n\n if (node instanceof DOMTextNode) {\n if (node.text && node.is_visible) {\n textParts.push(node.text.trim());\n }\n } else if (node instanceof DOMElementNode) {\n for (const child of node.children) {\n collectText(child, currentDepth + 1);\n }\n }\n };\n\n const children = this.children || [];\n\n if (children.length === 1 && children[0] instanceof DOMElementNode) {\n const child = children[0];\n const tag = child.tag_name.toLowerCase();\n if (tag === 'div' || tag === 'span') {\n for (const gc of child.children) {\n collectText(gc, 1);\n }\n return textParts.filter(t => t).join(' ');\n }\n }\n\n for (const child of children) {\n collectText(child, 1);\n }\n\n return textParts.filter(t => t).join(' ');\n }\n\n get_all_text_from_node(node: DOMElementNode | DOMTextNode): string {\n if (node instanceof DOMTextNode) {\n return node.text;\n } else if (node instanceof DOMElementNode) {\n return node.get_all_children_text(-1);\n }\n return '';\n }\n\n clean_text(text: string): string {\n if (!text) return '';\n\n const parts = text.split(/\\s+/);\n const mid = Math.floor(parts.length / 2);\n\n if (parts.length > 1) {\n const firstHalf = parts.slice(0, mid).join(' ');\n const secondHalf = parts.slice(mid).join(' ');\n if (firstHalf === secondHalf) {\n return firstHalf;\n }\n }\n\n return parts.join(' ');\n }\n}\n\n/**\n * Port of DomService from Python service.py\n */\nexport class DomService {\n async buildDomTree(\n page: Page,\n jsCode: string,\n options: {\n highlightElements?: boolean;\n focusElement?: number;\n viewportExpansion?: number;\n debugMode?: boolean;\n } = {}\n ): Promise<DomTreeResult> {\n const args = {\n doHighlightElements: options.highlightElements ?? true,\n focusHighlightIndex: options.focusElement ?? -1,\n viewportExpansion: options.viewportExpansion ?? -1,\n debugMode: options.debugMode ?? false\n };\n\n // Execute JS in browser\n const evalPage = await page.evaluate(({ code, params }: { code: string; params: typeof args }) => {\n eval(code);\n return (window as any).buildDomTreeMain(params);\n }, { code: jsCode, params: args }) as BuildDomTreeEvalResult;\n\n return this.constructDomTree(evalPage);\n }\n\n private constructDomTree(evalPage: BuildDomTreeEvalResult): DomTreeResult {\n const jsNodeMap = evalPage.map;\n const jsRootId = evalPage.rootId;\n\n const selectorMap: Record<number, DOMElementNode> = {};\n const nodeMap = new Map<string, DOMElementNode | DOMTextNode>();\n const childrenMap = new Map<string, number[]>();\n\n for (const [id, nodeData] of Object.entries(jsNodeMap)) {\n const { node, childrenIds } = this.parseNode(nodeData);\n if (!node) continue;\n\n nodeMap.set(id, node);\n childrenMap.set(id, childrenIds);\n\n if (node instanceof DOMElementNode && node.highlight_index !== null && node.highlight_index !== undefined) {\n selectorMap[node.highlight_index] = node;\n }\n }\n\n for (const [idStr, node] of nodeMap.entries()) {\n if (node instanceof DOMElementNode) {\n const childIds = childrenMap.get(idStr) || [];\n for (const childId of childIds) {\n const childNode = nodeMap.get(String(childId));\n if (childNode) {\n childNode.parent = node;\n node.children.push(childNode);\n }\n }\n }\n }\n\n const rootNode = nodeMap.get(String(jsRootId)) as DOMElementNode | undefined;\n\n return { elementTree: rootNode, selectorMap };\n }\n\n private parseNode(nodeData: DOMElementData): { node: DOMElementNode | DOMTextNode | null; childrenIds: number[] } {\n if (!nodeData) return { node: null, childrenIds: [] };\n\n if (nodeData.type === 'TEXT_NODE') {\n const textNode = new DOMTextNode(\n nodeData.text || '',\n nodeData.isVisible || false,\n null\n );\n return { node: textNode, childrenIds: [] };\n }\n\n const elementNode = new DOMElementNode(nodeData);\n const childrenIds = nodeData.children || [];\n\n return { node: elementNode, childrenIds };\n }\n}\n"],"mappings":"AAyCO,MAAM,YAAY;AAAA,EACvB;AAAA,EACA;AAAA,EAEA,YAAY,WAAoB,SAAgC,MAAM;AACpE,SAAK,aAAa;AAClB,SAAK,SAAS;AAAA,EAChB;AACF;AAEO,MAAM,oBAAoB,YAAY;AAAA,EAC3C;AAAA,EACA;AAAA,EAEA,YAAY,MAAc,WAAoB,SAAgC,MAAM;AAClF,UAAM,WAAW,MAAM;AACvB,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,kCAA2C;AACzC,QAAI,UAAiC,KAAK;AAC1C,WAAO,YAAY,MAAM;AACvB,UAAI,QAAQ,oBAAoB,QAAQ,QAAQ,oBAAoB,QAAW;AAC7E,eAAO;AAAA,MACT;AACA,gBAAU,QAAQ;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AACF;AAKA,SAAS,gCAAgC,OAAuB;AAC9D,MAAI,CAAC,MAAO,QAAO;AAEnB,UAAQ,MAAM,QAAQ,QAAQ,EAAE;AAChC,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAM,WAAqB,CAAC;AAE5B,WAAS,QAAQ,OAAO;AACtB,QAAI,CAAC,KAAM;AAEX,QAAI,KAAK,SAAS,GAAG,KAAK,CAAC,KAAK,SAAS,GAAG,GAAG;AAC7C,YAAM,WAAW,KAAK,QAAQ,MAAM,KAAK;AACzC,eAAS,KAAK,QAAQ;AACtB;AAAA,IACF;AAEA,QAAI,KAAK,SAAS,GAAG,GAAG;AACtB,UAAI,WAAW,KAAK,UAAU,GAAG,KAAK,QAAQ,GAAG,CAAC;AAElD,UAAI,SAAS,SAAS,GAAG,GAAG;AAC1B,mBAAW,SAAS,QAAQ,MAAM,KAAK;AAAA,MACzC;AAEA,YAAM,YAAY,KAAK,UAAU,KAAK,QAAQ,GAAG,CAAC;AAClD,YAAM,UAAU,UAAU,MAAM,GAAG,EAAE,OAAO,OAAK,CAAC,EAAE,IAAI,OAAK,EAAE,QAAQ,KAAK,EAAE,CAAC;AAE/E,iBAAW,OAAO,SAAS;AACzB,YAAI;AACF,cAAI,QAAQ,KAAK,GAAG,GAAG;AACrB,kBAAM,QAAQ,SAAS,GAAG,IAAI;AAC9B,wBAAY,gBAAgB,QAAQ,CAAC;AAAA,UACvC,WAAW,QAAQ,UAAU;AAC3B,wBAAY;AAAA,UACd,WAAW,IAAI,SAAS,YAAY,KAAK,IAAI,SAAS,IAAI,GAAG;AAC3D,wBAAY;AAAA,UACd;AAAA,QACF,SAAS,GAAG;AACV;AAAA,QACF;AAAA,MACF;AAEA,eAAS,KAAK,QAAQ;AAAA,IACxB,OAAO;AACL,eAAS,KAAK,IAAI;AAAA,IACpB;AAAA,EACF;AAEA,SAAO,SAAS,KAAK,KAAK;AAC5B;AAEA,SAAS,8BAA8B,SAAyB,2BAA2B,MAAc;AACvG,MAAI;AACF,QAAI,cAAc,gCAAgC,QAAQ,KAAK;AAE/D,QAAI,QAAQ,WAAW,SAAS,0BAA0B;AACxD,YAAM,wBAAwB;AAC9B,YAAM,UAAU,QAAQ,WAAW,MAAM,MAAM,KAAK;AAEpD,iBAAW,aAAa,SAAS;AAC/B,YAAI,CAAC,UAAU,KAAK,EAAG;AACvB,YAAI,sBAAsB,KAAK,SAAS,GAAG;AACzC,yBAAe,IAAI,SAAS;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AAEA,UAAM,kBAAkB,oBAAI,IAAI;AAAA,MAC9B;AAAA,MAAM;AAAA,MAAQ;AAAA,MAAQ;AAAA,MACtB;AAAA,MAAc;AAAA,MAAmB;AAAA,MAAoB;AAAA,MACrD;AAAA,MAAO;AAAA,MAAgB;AAAA,MAAY;AAAA,MACnC;AAAA,MAAO;AAAA,MAAS;AAAA,MAAO;AAAA,MAAQ;AAAA,IACjC,CAAC;AAED,QAAI,0BAA0B;AAC5B,sBAAgB,IAAI,SAAS;AAC7B,sBAAgB,IAAI,SAAS;AAC7B,sBAAgB,IAAI,SAAS;AAC7B,sBAAgB,IAAI,aAAa;AAAA,IACnC;AAEA,eAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,QAAQ,UAAU,GAAG;AACnE,UAAI,cAAc,QAAS;AAC3B,UAAI,CAAC,UAAU,KAAK,EAAG;AACvB,UAAI,CAAC,gBAAgB,IAAI,SAAS,EAAG;AAErC,YAAM,gBAAgB,UAAU,QAAQ,MAAM,KAAK;AAEnD,UAAI,UAAU,IAAI;AAChB,uBAAe,IAAI,aAAa;AAAA,MAClC,WAAW,gBAAgB,KAAK,KAAK,GAAG;AACtC,YAAI,iBAAiB;AACrB,YAAI,eAAe,SAAS,IAAI,GAAG;AACjC,2BAAiB,eAAe,MAAM,IAAI,EAAE,CAAC;AAAA,QAC/C;AACA,cAAM,iBAAiB,eAAe,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAChE,cAAM,YAAY,eAAe,QAAQ,MAAM,KAAK;AACpD,uBAAe,IAAI,aAAa,MAAM,SAAS;AAAA,MACjD,OAAO;AACL,uBAAe,IAAI,aAAa,KAAK,KAAK;AAAA,MAC5C;AAAA,IACF;AAEA,WAAO;AAAA,EAET,SAAS,OAAO;AACd,UAAM,UAAU,QAAQ,YAAY;AACpC,WAAO,GAAG,OAAO,qBAAqB,QAAQ,eAAe;AAAA,EAC/D;AACF;AAEO,MAAM,uBAAuB,YAAY;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACQ;AAAA,EAER,YAAY,MAAsB;AAChC,UAAM,KAAK,aAAa,OAAO,IAAI;AAEnC,SAAK,WAAW,KAAK,WAAW;AAChC,SAAK,QAAQ,KAAK,SAAS;AAC3B,SAAK,aAAa,KAAK,cAAc,CAAC;AACtC,SAAK,WAAW,CAAC;AACjB,SAAK,eAAe,KAAK,gBAAgB,CAAC;AAC1C,SAAK,iBAAiB,KAAK,iBAAiB;AAC5C,SAAK,eAAe,KAAK,eAAe;AACxC,SAAK,iBAAiB,KAAK,gBAAgB;AAC3C,SAAK,iBAAiB,KAAK,gBAAgB;AAC3C,SAAK,cAAc,KAAK,cAAc;AACtC,SAAK,kBAAkB,KAAK;AAC5B,SAAK,mBAAmB,KAAK,mBAAmB;AAChD,SAAK,uBAAuB,KAAK,uBAAuB;AACxD,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,IAAI,eAAuB;AACzB,QAAI,KAAK,kBAAkB,MAAM;AAC/B,WAAK,gBAAgB,8BAA8B,MAAM,IAAI;AAAA,IAC/D;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,sBAA8B;AAC5B,UAAM,gBAAgB,CAAC,SAAS,cAAc,SAAS,eAAe,OAAO,MAAM;AACnF,eAAW,QAAQ,eAAe;AAChC,UAAI,KAAK,WAAW,IAAI,GAAG;AACzB,eAAO,KAAK,WAAW,KAAK,WAAW,IAAI,CAAC;AAAA,MAC9C;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,SAAS,YAAY;AAEtC,QAAI,QAAQ,KAAK;AACf,iBAAW,SAAS,KAAK,UAAU;AACjC,YAAI,EAAE,iBAAiB,gBAAiB;AACxC,cAAM,OAAO,MAAM,SAAS,YAAY;AAExC,YAAI,SAAS,OAAO;AAClB,gBAAM,MAAM,MAAM,WAAW,OAAO;AACpC,gBAAM,QAAQ,MAAM,WAAW,SAAS;AACxC,cAAI,IAAK,QAAO,KAAK,WAAW,GAAG;AACnC,cAAI,MAAO,QAAO,KAAK,WAAW,KAAK;AAAA,QACzC,WAAW,SAAS,OAAO;AACzB,gBAAM,YAAY,MAAM,WAAW,YAAY,KAAK;AACpD,gBAAM,QAAQ,MAAM,WAAW,SAAS;AACxC,cAAI,UAAW,QAAO,KAAK,WAAW,SAAS;AAC/C,cAAI,MAAO,QAAO,KAAK,WAAW,KAAK;AAEvC,qBAAW,KAAK,MAAM,UAAU;AAC9B,gBAAI,aAAa,kBAAkB,EAAE,SAAS,YAAY,MAAM,SAAS;AACvE,oBAAM,YAAY,KAAK,uBAAuB,CAAC;AAC/C,kBAAI,UAAW,QAAO,KAAK,WAAW,SAAS;AAAA,YACjD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,QAAQ,OAAO;AACjB,YAAM,MAAM,KAAK,WAAW,OAAO;AACnC,YAAM,QAAQ,KAAK,WAAW,SAAS;AACvC,UAAI,IAAK,QAAO,KAAK,WAAW,GAAG;AACnC,UAAI,MAAO,QAAO,KAAK,WAAW,KAAK;AAAA,IACzC;AAEA,QAAI,QAAQ,OAAO;AACjB,YAAM,YAAY,KAAK,WAAW,YAAY,KAAK;AACnD,YAAM,QAAQ,KAAK,WAAW,SAAS;AACvC,UAAI,UAAW,QAAO,KAAK,WAAW,SAAS;AAC/C,UAAI,MAAO,QAAO,KAAK,WAAW,KAAK;AAEvC,iBAAW,SAAS,KAAK,UAAU;AACjC,YAAI,iBAAiB,kBAAkB,MAAM,SAAS,YAAY,MAAM,SAAS;AAC/E,gBAAM,YAAY,KAAK,uBAAuB,KAAK;AACnD,cAAI,UAAW,QAAO,KAAK,WAAW,SAAS;AAAA,QACjD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,sBAAsB,EAAE,EAAE,KAAK;AACpD,WAAO,UAAU,KAAK,WAAW,OAAO,IAAI;AAAA,EAC9C;AAAA,EAEA,sBAAsB,WAAW,GAAW;AAC1C,UAAM,YAAsB,CAAC;AAE7B,UAAM,cAAc,CAAC,MAAoC,iBAA+B;AACtF,UAAI,aAAa,MAAM,eAAe,SAAU;AAEhD,UAAI,gBAAgB,aAAa;AAC/B,YAAI,KAAK,QAAQ,KAAK,YAAY;AAChC,oBAAU,KAAK,KAAK,KAAK,KAAK,CAAC;AAAA,QACjC;AAAA,MACF,WAAW,gBAAgB,gBAAgB;AACzC,mBAAW,SAAS,KAAK,UAAU;AACjC,sBAAY,OAAO,eAAe,CAAC;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,YAAY,CAAC;AAEnC,QAAI,SAAS,WAAW,KAAK,SAAS,CAAC,aAAa,gBAAgB;AAClE,YAAM,QAAQ,SAAS,CAAC;AACxB,YAAM,MAAM,MAAM,SAAS,YAAY;AACvC,UAAI,QAAQ,SAAS,QAAQ,QAAQ;AACnC,mBAAW,MAAM,MAAM,UAAU;AAC/B,sBAAY,IAAI,CAAC;AAAA,QACnB;AACA,eAAO,UAAU,OAAO,OAAK,CAAC,EAAE,KAAK,GAAG;AAAA,MAC1C;AAAA,IACF;AAEA,eAAW,SAAS,UAAU;AAC5B,kBAAY,OAAO,CAAC;AAAA,IACtB;AAEA,WAAO,UAAU,OAAO,OAAK,CAAC,EAAE,KAAK,GAAG;AAAA,EAC1C;AAAA,EAEA,uBAAuB,MAA4C;AACjE,QAAI,gBAAgB,aAAa;AAC/B,aAAO,KAAK;AAAA,IACd,WAAW,gBAAgB,gBAAgB;AACzC,aAAO,KAAK,sBAAsB,EAAE;AAAA,IACtC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,MAAsB;AAC/B,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,QAAQ,KAAK,MAAM,KAAK;AAC9B,UAAM,MAAM,KAAK,MAAM,MAAM,SAAS,CAAC;AAEvC,QAAI,MAAM,SAAS,GAAG;AACpB,YAAM,YAAY,MAAM,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG;AAC9C,YAAM,aAAa,MAAM,MAAM,GAAG,EAAE,KAAK,GAAG;AAC5C,UAAI,cAAc,YAAY;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO,MAAM,KAAK,GAAG;AAAA,EACvB;AACF;AAKO,MAAM,WAAW;AAAA,EACtB,MAAM,aACJ,MACA,QACA,UAKI,CAAC,GACmB;AACxB,UAAM,OAAO;AAAA,MACX,qBAAqB,QAAQ,qBAAqB;AAAA,MAClD,qBAAqB,QAAQ,gBAAgB;AAAA,MAC7C,mBAAmB,QAAQ,qBAAqB;AAAA,MAChD,WAAW,QAAQ,aAAa;AAAA,IAClC;AAGA,UAAM,WAAW,MAAM,KAAK,SAAS,CAAC,EAAE,MAAM,OAAO,MAA6C;AAChG,WAAK,IAAI;AACT,aAAQ,OAAe,iBAAiB,MAAM;AAAA,IAChD,GAAG,EAAE,MAAM,QAAQ,QAAQ,KAAK,CAAC;AAEjC,WAAO,KAAK,iBAAiB,QAAQ;AAAA,EACvC;AAAA,EAEQ,iBAAiBA,WAAiD;AACxE,UAAM,YAAYA,UAAS;AAC3B,UAAM,WAAWA,UAAS;AAE1B,UAAM,cAA8C,CAAC;AACrD,UAAM,UAAU,oBAAI,IAA0C;AAC9D,UAAM,cAAc,oBAAI,IAAsB;AAE9C,eAAW,CAAC,IAAI,QAAQ,KAAK,OAAO,QAAQ,SAAS,GAAG;AACtD,YAAM,EAAE,MAAM,YAAY,IAAI,KAAK,UAAU,QAAQ;AACrD,UAAI,CAAC,KAAM;AAEX,cAAQ,IAAI,IAAI,IAAI;AACpB,kBAAY,IAAI,IAAI,WAAW;AAE/B,UAAI,gBAAgB,kBAAkB,KAAK,oBAAoB,QAAQ,KAAK,oBAAoB,QAAW;AACzG,oBAAY,KAAK,eAAe,IAAI;AAAA,MACtC;AAAA,IACF;AAEA,eAAW,CAAC,OAAO,IAAI,KAAK,QAAQ,QAAQ,GAAG;AAC7C,UAAI,gBAAgB,gBAAgB;AAClC,cAAM,WAAW,YAAY,IAAI,KAAK,KAAK,CAAC;AAC5C,mBAAW,WAAW,UAAU;AAC9B,gBAAM,YAAY,QAAQ,IAAI,OAAO,OAAO,CAAC;AAC7C,cAAI,WAAW;AACb,sBAAU,SAAS;AACnB,iBAAK,SAAS,KAAK,SAAS;AAAA,UAC9B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,QAAQ,IAAI,OAAO,QAAQ,CAAC;AAE7C,WAAO,EAAE,aAAa,UAAU,YAAY;AAAA,EAC9C;AAAA,EAEQ,UAAU,UAAgG;AAChH,QAAI,CAAC,SAAU,QAAO,EAAE,MAAM,MAAM,aAAa,CAAC,EAAE;AAEpD,QAAI,SAAS,SAAS,aAAa;AACjC,YAAM,WAAW,IAAI;AAAA,QACnB,SAAS,QAAQ;AAAA,QACjB,SAAS,aAAa;AAAA,QACtB;AAAA,MACF;AACA,aAAO,EAAE,MAAM,UAAU,aAAa,CAAC,EAAE;AAAA,IAC3C;AAEA,UAAM,cAAc,IAAI,eAAe,QAAQ;AAC/C,UAAM,cAAc,SAAS,YAAY,CAAC;AAE1C,WAAO,EAAE,MAAM,aAAa,YAAY;AAAA,EAC1C;AACF;","names":["evalPage"]}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var failure_analyzer_exports = {};
|
|
30
|
+
__export(failure_analyzer_exports, {
|
|
31
|
+
analyzeFailure: () => analyzeFailure,
|
|
32
|
+
setupPageListeners: () => setupPageListeners
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(failure_analyzer_exports);
|
|
35
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
36
|
+
var import_path = __toESM(require("path"), 1);
|
|
37
|
+
function setupPageListeners(page) {
|
|
38
|
+
const consoleLogs = [];
|
|
39
|
+
const networkFailures = [];
|
|
40
|
+
const MAX_CONSOLE_ENTRIES = 500;
|
|
41
|
+
page.on("console", (msg) => {
|
|
42
|
+
if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;
|
|
43
|
+
const loc = msg.location();
|
|
44
|
+
consoleLogs.push({
|
|
45
|
+
type: msg.type(),
|
|
46
|
+
text: msg.text(),
|
|
47
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
48
|
+
location: {
|
|
49
|
+
url: loc.url,
|
|
50
|
+
lineNumber: loc.lineNumber,
|
|
51
|
+
columnNumber: loc.columnNumber
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
page.on("pageerror", (err) => {
|
|
56
|
+
if (consoleLogs.length >= MAX_CONSOLE_ENTRIES) return;
|
|
57
|
+
consoleLogs.push({
|
|
58
|
+
type: "pageerror",
|
|
59
|
+
text: err.message,
|
|
60
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
page.on("response", (response) => {
|
|
64
|
+
if (response.status() >= 400) {
|
|
65
|
+
networkFailures.push({
|
|
66
|
+
url: response.url(),
|
|
67
|
+
method: response.request().method(),
|
|
68
|
+
status: response.status(),
|
|
69
|
+
statusText: response.statusText(),
|
|
70
|
+
resourceType: response.request().resourceType(),
|
|
71
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
page.on("requestfailed", (request) => {
|
|
76
|
+
networkFailures.push({
|
|
77
|
+
url: request.url(),
|
|
78
|
+
method: request.method(),
|
|
79
|
+
status: 0,
|
|
80
|
+
statusText: request.failure()?.errorText ?? "Request failed",
|
|
81
|
+
resourceType: request.resourceType(),
|
|
82
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
return { consoleLogs, networkFailures };
|
|
86
|
+
}
|
|
87
|
+
async function analyzeFailure(page, testInfo, consoleLogs, networkFailures, artifacts) {
|
|
88
|
+
const error = testInfo.error ?? testInfo.errors?.[0] ?? {};
|
|
89
|
+
const errorMessage = error.message ?? "";
|
|
90
|
+
const errorStack = error.stack ?? "";
|
|
91
|
+
const now = /* @__PURE__ */ new Date();
|
|
92
|
+
const duration = testInfo.duration ?? 0;
|
|
93
|
+
const testStart = new Date(now.getTime() - duration).toISOString();
|
|
94
|
+
const failureCapture = now.toISOString();
|
|
95
|
+
const selector = extractSelector(errorMessage);
|
|
96
|
+
const action = extractAction(errorMessage);
|
|
97
|
+
const errorFirstLine = errorMessage.split("\n")[0];
|
|
98
|
+
const location = extractLocation(errorStack, testInfo);
|
|
99
|
+
const context = errorMessage;
|
|
100
|
+
let pageTitle = null;
|
|
101
|
+
try {
|
|
102
|
+
pageTitle = await page.title();
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
const isTimeout = /timeout/i.test(errorMessage);
|
|
106
|
+
const timeoutMs = extractTimeoutMs(errorMessage);
|
|
107
|
+
const snippet = generateSnippet(errorStack, testInfo);
|
|
108
|
+
const causes = extractCauseChain(error);
|
|
109
|
+
const rawErrors = testInfo.errors ?? [];
|
|
110
|
+
const errors = rawErrors.map((err) => {
|
|
111
|
+
const msg = err.message ?? "";
|
|
112
|
+
const stk = err.stack ?? "";
|
|
113
|
+
return {
|
|
114
|
+
message: msg,
|
|
115
|
+
stack: stk,
|
|
116
|
+
pw_console: generateSnippet(stk, testInfo),
|
|
117
|
+
isTimeout: /timeout/i.test(msg),
|
|
118
|
+
timeoutMs: extractTimeoutMs(msg),
|
|
119
|
+
causes: extractCauseChain(err)
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
const analysis = {
|
|
123
|
+
// Backward-compatible
|
|
124
|
+
selector,
|
|
125
|
+
action,
|
|
126
|
+
error: errorFirstLine,
|
|
127
|
+
location,
|
|
128
|
+
context,
|
|
129
|
+
// v3 enriched
|
|
130
|
+
test: {
|
|
131
|
+
title: testInfo.title,
|
|
132
|
+
titlePath: testInfo.titlePath,
|
|
133
|
+
file: testInfo.file,
|
|
134
|
+
line: testInfo.line ?? null,
|
|
135
|
+
project: testInfo.project.name,
|
|
136
|
+
retries: testInfo.project.retries,
|
|
137
|
+
retry: testInfo.retry,
|
|
138
|
+
duration,
|
|
139
|
+
status: testInfo.status,
|
|
140
|
+
expectedStatus: testInfo.expectedStatus,
|
|
141
|
+
tags: testInfo.tags ?? []
|
|
142
|
+
},
|
|
143
|
+
page: {
|
|
144
|
+
url: page.url(),
|
|
145
|
+
title: pageTitle,
|
|
146
|
+
viewport: page.viewportSize()
|
|
147
|
+
},
|
|
148
|
+
selectorDetails: extractSelectorDetails(errorMessage),
|
|
149
|
+
consoleLogs,
|
|
150
|
+
consoleErrors: consoleLogs.filter(
|
|
151
|
+
(e) => e.type === "error" || e.type === "pageerror"
|
|
152
|
+
),
|
|
153
|
+
networkFailures,
|
|
154
|
+
errorDetails: {
|
|
155
|
+
message: errorMessage,
|
|
156
|
+
stack: errorStack,
|
|
157
|
+
pw_console: snippet,
|
|
158
|
+
isTimeout,
|
|
159
|
+
timeoutMs,
|
|
160
|
+
causes
|
|
161
|
+
},
|
|
162
|
+
errors,
|
|
163
|
+
timing: {
|
|
164
|
+
testStart,
|
|
165
|
+
failureCapture,
|
|
166
|
+
duration
|
|
167
|
+
},
|
|
168
|
+
artifacts: {
|
|
169
|
+
domJsonPath: artifacts.jsonPath,
|
|
170
|
+
screenshotPath: artifacts.screenshotPath,
|
|
171
|
+
failureInfoPath: artifacts.failureInfoPath
|
|
172
|
+
},
|
|
173
|
+
_version: 3
|
|
174
|
+
};
|
|
175
|
+
import_fs.default.writeFileSync(artifacts.failureInfoPath, JSON.stringify(analysis, null, 2));
|
|
176
|
+
return analysis;
|
|
177
|
+
}
|
|
178
|
+
function generateSnippet(stack, testInfo) {
|
|
179
|
+
const escaped = testInfo.file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
180
|
+
const match = stack.match(new RegExp(`${escaped}:(\\d+):(\\d+)`));
|
|
181
|
+
if (!match) return null;
|
|
182
|
+
const lineNumber = parseInt(match[1], 10);
|
|
183
|
+
let lines;
|
|
184
|
+
try {
|
|
185
|
+
lines = import_fs.default.readFileSync(testInfo.file, "utf-8").split("\n");
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const start = Math.max(0, lineNumber - 5);
|
|
190
|
+
const end = Math.min(lines.length, lineNumber + 3);
|
|
191
|
+
const slice = lines.slice(start, end);
|
|
192
|
+
const maxLineNum = end;
|
|
193
|
+
const gutterWidth = String(maxLineNum).length;
|
|
194
|
+
return slice.map((text, i) => {
|
|
195
|
+
const num = start + i + 1;
|
|
196
|
+
const marker = num === lineNumber ? ">" : " ";
|
|
197
|
+
const padded = String(num).padStart(gutterWidth);
|
|
198
|
+
return `${marker} ${padded} | ${text}`;
|
|
199
|
+
}).join("\n");
|
|
200
|
+
}
|
|
201
|
+
function extractCauseChain(error) {
|
|
202
|
+
const causes = [];
|
|
203
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
204
|
+
let current = error;
|
|
205
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
206
|
+
if (current == null || typeof current !== "object") break;
|
|
207
|
+
const cause = current.cause;
|
|
208
|
+
if (cause == null || typeof cause !== "object") break;
|
|
209
|
+
if (seen.has(cause)) break;
|
|
210
|
+
seen.add(cause);
|
|
211
|
+
const causeErr = cause;
|
|
212
|
+
causes.push({
|
|
213
|
+
message: causeErr.message ?? String(cause),
|
|
214
|
+
stack: causeErr.stack ?? ""
|
|
215
|
+
});
|
|
216
|
+
current = cause;
|
|
217
|
+
}
|
|
218
|
+
return causes;
|
|
219
|
+
}
|
|
220
|
+
function extractTimeoutMs(msg) {
|
|
221
|
+
if (!/timeout/i.test(msg)) return null;
|
|
222
|
+
const match = msg.match(/(\d+)\s*ms/);
|
|
223
|
+
return match ? parseInt(match[1], 10) : null;
|
|
224
|
+
}
|
|
225
|
+
function extractSelector(msg) {
|
|
226
|
+
const match = msg.match(/locator\(['"](.+?)['"]\)/) || msg.match(/getByRole\(['"](.+?)['"]\)/) || msg.match(/getByText\(['"](.+?)['"]\)/);
|
|
227
|
+
return match ? match[1] : "";
|
|
228
|
+
}
|
|
229
|
+
function extractAction(msg) {
|
|
230
|
+
if (msg.includes(".click")) return "click";
|
|
231
|
+
if (msg.includes(".fill")) return "fill";
|
|
232
|
+
if (msg.includes(".type")) return "type";
|
|
233
|
+
if (msg.includes(".hover")) return "hover";
|
|
234
|
+
if (msg.includes(".check")) return "check";
|
|
235
|
+
if (msg.includes(".uncheck")) return "uncheck";
|
|
236
|
+
if (msg.includes(".select")) return "select";
|
|
237
|
+
if (msg.includes(".press")) return "press";
|
|
238
|
+
if (msg.includes(".scroll")) return "scroll";
|
|
239
|
+
if (msg.includes(".drag")) return "drag";
|
|
240
|
+
if (/timeout/i.test(msg)) return "wait";
|
|
241
|
+
return "";
|
|
242
|
+
}
|
|
243
|
+
function extractLocation(stack, testInfo) {
|
|
244
|
+
const escaped = testInfo.file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
245
|
+
const match = stack.match(new RegExp(`${escaped}:(\\d+):(\\d+)`));
|
|
246
|
+
if (match) {
|
|
247
|
+
return `${import_path.default.basename(testInfo.file)}:${match[1]}`;
|
|
248
|
+
}
|
|
249
|
+
return "";
|
|
250
|
+
}
|
|
251
|
+
function extractSelectorDetails(msg) {
|
|
252
|
+
const patterns = [
|
|
253
|
+
{ method: "locator", regex: /locator\(['"](.+?)['"]\)/ },
|
|
254
|
+
{ method: "getByRole", regex: /getByRole\(['"](.+?)['"]\)/ },
|
|
255
|
+
{ method: "getByText", regex: /getByText\(['"](.+?)['"]\)/ },
|
|
256
|
+
{ method: "getByTestId", regex: /getByTestId\(['"](.+?)['"]\)/ },
|
|
257
|
+
{ method: "getByLabel", regex: /getByLabel\(['"](.+?)['"]\)/ },
|
|
258
|
+
{ method: "getByPlaceholder", regex: /getByPlaceholder\(['"](.+?)['"]\)/ },
|
|
259
|
+
{ method: "getByAltText", regex: /getByAltText\(['"](.+?)['"]\)/ },
|
|
260
|
+
{ method: "getByTitle", regex: /getByTitle\(['"](.+?)['"]\)/ },
|
|
261
|
+
{ method: "frameLocator", regex: /frameLocator\(['"](.+?)['"]\)/ }
|
|
262
|
+
];
|
|
263
|
+
for (const { method, regex } of patterns) {
|
|
264
|
+
const match = msg.match(regex);
|
|
265
|
+
if (match) {
|
|
266
|
+
return { raw: match[0], method, value: match[1] };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
272
|
+
0 && (module.exports = {
|
|
273
|
+
analyzeFailure,
|
|
274
|
+
setupPageListeners
|
|
275
|
+
});
|
|
276
|
+
//# sourceMappingURL=failure-analyzer.cjs.map
|