deflake 1.2.43 → 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.
Files changed (2) hide show
  1. package/cli.js +111 -2
  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: API call send BOTH error-context AND console error block for richer diagnosis
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, relevantOutput);
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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deflake",
3
- "version": "1.2.43",
3
+ "version": "1.2.45",
4
4
  "description": "AI-powered self-healing tool for Playwright, Cypress, and WebdriverIO tests.",
5
5
  "main": "client.js",
6
6
  "bin": {