deflake 1.2.44 → 1.2.45
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/cli.js +111 -2
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -7,6 +7,104 @@ const fs = require('fs');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const pkg = require('./package.json');
|
|
9
9
|
|
|
10
|
+
// --- AX TREE PARSER: Extract available selectors from Playwright Accessibility Tree ---
|
|
11
|
+
function parseAxTreeSelectors(axTreeContent) {
|
|
12
|
+
const selectors = [];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
const lines = axTreeContent.split('\n');
|
|
15
|
+
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
// Match: - role "name" or - role 'name'
|
|
18
|
+
// Examples:
|
|
19
|
+
// - heading "People" [level=3]
|
|
20
|
+
// - button "Add a New Employee" [ref=e144]
|
|
21
|
+
// - searchbox "Search by Name" [ref=e163]
|
|
22
|
+
// - link "Manage Employees" [ref=e128]
|
|
23
|
+
// - textbox "Leave Start Date*" [ref=e130]
|
|
24
|
+
// - tab "Basic Information" [selected]
|
|
25
|
+
// - combobox "Leave Type*"
|
|
26
|
+
|
|
27
|
+
const match = line.match(/- (heading|button|link|searchbox|textbox|combobox|tab|switch|checkbox|radio|slider)\s+["']([^"']+)["']/i);
|
|
28
|
+
if (match) {
|
|
29
|
+
const [, role, name] = match;
|
|
30
|
+
const key = `${role}:${name}`;
|
|
31
|
+
if (seen.has(key)) continue;
|
|
32
|
+
seen.add(key);
|
|
33
|
+
|
|
34
|
+
let selector;
|
|
35
|
+
switch (role.toLowerCase()) {
|
|
36
|
+
case 'heading':
|
|
37
|
+
selector = `page.getByRole('heading', { name: '${name}' })`;
|
|
38
|
+
break;
|
|
39
|
+
case 'button':
|
|
40
|
+
// Clean button names that include icon names like "user-add Add a New Employee"
|
|
41
|
+
const cleanName = name.replace(/^[a-z-]+ /i, '').trim();
|
|
42
|
+
if (cleanName !== name && cleanName.length > 2) {
|
|
43
|
+
selector = `page.getByRole('button', { name: '${cleanName}' })`;
|
|
44
|
+
} else {
|
|
45
|
+
selector = `page.getByRole('button', { name: '${name}' })`;
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case 'link':
|
|
49
|
+
const linkName = name.trim();
|
|
50
|
+
if (linkName.length > 1) {
|
|
51
|
+
selector = `page.getByRole('link', { name: '${linkName}' })`;
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
case 'searchbox':
|
|
55
|
+
selector = `page.getByPlaceholder('${name}')`;
|
|
56
|
+
break;
|
|
57
|
+
case 'textbox':
|
|
58
|
+
selector = `page.getByLabel('${name}')`;
|
|
59
|
+
break;
|
|
60
|
+
case 'combobox':
|
|
61
|
+
selector = `page.getByLabel('${name}')`;
|
|
62
|
+
break;
|
|
63
|
+
case 'tab':
|
|
64
|
+
selector = `page.getByRole('tab', { name: '${name}' })`;
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
selector = `page.getByRole('${role}', { name: '${name}' })`;
|
|
68
|
+
}
|
|
69
|
+
if (selector) selectors.push(selector);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Also extract text content from generic elements with cursor=pointer (action links)
|
|
73
|
+
const genericTextMatch = line.match(/- generic.*\[cursor=pointer\]/);
|
|
74
|
+
if (genericTextMatch) {
|
|
75
|
+
// Look for the text child in the next few lines
|
|
76
|
+
const lineIdx = lines.indexOf(line);
|
|
77
|
+
for (let j = lineIdx + 1; j < Math.min(lineIdx + 4, lines.length); j++) {
|
|
78
|
+
const textMatch = lines[j].match(/- text: (.+)/);
|
|
79
|
+
if (textMatch) {
|
|
80
|
+
const text = textMatch[1].trim();
|
|
81
|
+
if (text.length > 1 && text.length < 50) {
|
|
82
|
+
const key = `text:${text}`;
|
|
83
|
+
if (!seen.has(key)) {
|
|
84
|
+
seen.add(key);
|
|
85
|
+
selectors.push(`page.getByText('${text}', { exact: true }).first()`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Extract placeholder from textbox entries
|
|
94
|
+
const placeholderMatch = line.match(/\/placeholder: (.+)/);
|
|
95
|
+
if (placeholderMatch) {
|
|
96
|
+
const ph = placeholderMatch[1].trim();
|
|
97
|
+
const key = `placeholder:${ph}`;
|
|
98
|
+
if (!seen.has(key)) {
|
|
99
|
+
seen.add(key);
|
|
100
|
+
selectors.push(`page.getByPlaceholder('${ph}')`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return selectors;
|
|
106
|
+
}
|
|
107
|
+
|
|
10
108
|
// --- PREMIUM COLORS ---
|
|
11
109
|
const C = {
|
|
12
110
|
RESET: "\x1b[0m",
|
|
@@ -224,9 +322,20 @@ async function analyzeAndFix(artifacts, client, argv, capturedOutput = '', backu
|
|
|
224
322
|
const source = loc && fs.existsSync(loc.path) ? fs.readFileSync(loc.path, 'utf8') : null;
|
|
225
323
|
console.log(` ${C.GRAY}📄 Source:${C.RESET} ${source ? 'Loaded (' + source.split('\n').length + ' lines)' : C.YELLOW + 'Not available' + C.RESET}`);
|
|
226
324
|
|
|
227
|
-
// Step 3:
|
|
325
|
+
// Step 3: Parse AX Tree for available selectors
|
|
326
|
+
const axSelectors = parseAxTreeSelectors(content);
|
|
327
|
+
if (axSelectors.length > 0) {
|
|
328
|
+
console.log(` ${C.GRAY}🎯 Available selectors: ${axSelectors.length} elements found in AX Tree${C.RESET}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Step 4: API call — send error-context, console error block, AND available selectors
|
|
332
|
+
const selectorsContext = axSelectors.length > 0
|
|
333
|
+
? `\n\n=== AVAILABLE SELECTORS (pre-computed from page) ===\nThese selectors are VERIFIED to exist on the page. Pick from this list:\n${axSelectors.map((s, i) => `${i+1}. ${s}`).join('\n')}\n=== END AVAILABLE SELECTORS ===`
|
|
334
|
+
: '';
|
|
335
|
+
const enrichedOutput = (relevantOutput || '') + selectorsContext;
|
|
336
|
+
|
|
228
337
|
console.log(` ${C.GRAY}🌐 Calling DeFlake API...${C.RESET}`);
|
|
229
|
-
const res = await client.heal(null, art.htmlPath, loc, source, argv.fix,
|
|
338
|
+
const res = await client.heal(null, art.htmlPath, loc, source, argv.fix, enrichedOutput);
|
|
230
339
|
|
|
231
340
|
if (res && res.status === 'success') {
|
|
232
341
|
console.log(` ${C.GREEN}✅ API Response: SUCCESS${C.RESET}`);
|