deflake 1.2.27 → 1.2.29

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 +102 -17
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -46,11 +46,11 @@ async function main() {
46
46
  console.log(`šŸ” Detected Framework: ${C.GREEN}${fw.toUpperCase()}${C.RESET}`);
47
47
 
48
48
  if (commandToRun) {
49
- const { code } = await runNative(commandToRun);
49
+ const { code, output } = await runNative(commandToRun);
50
50
  if (code !== 0) {
51
51
  console.log(`\nšŸ”“ Command failed with code ${code}. Activating DeFlake...`);
52
52
  const artifacts = detectFailures();
53
- const applied = await analyzeAndFix(artifacts, client, argv);
53
+ const applied = await analyzeAndFix(artifacts, client, argv, output);
54
54
  if (applied > 0 && argv.fix) {
55
55
  console.log(`\n${C.BRIGHT}šŸ’‰ Fixes applied. Re-running tests to verify...${C.RESET}`);
56
56
  await runNative(commandToRun);
@@ -63,12 +63,23 @@ async function main() {
63
63
  async function runNative(fullCommand) {
64
64
  return new Promise((resolve) => {
65
65
  console.log(`šŸš€ Running command: ${C.CYAN}${fullCommand}${C.RESET}`);
66
+ let capturedOutput = '';
66
67
  const child = spawn(fullCommand, {
67
68
  shell: true,
68
- stdio: 'inherit',
69
+ stdio: ['inherit', 'pipe', 'pipe'],
69
70
  env: { ...process.env, PLAYWRIGHT_HTML_OPEN: 'never' }
70
71
  });
71
- child.on('close', (code) => resolve({ code }));
72
+ child.stdout.on('data', (data) => {
73
+ const text = data.toString();
74
+ process.stdout.write(text);
75
+ capturedOutput += text;
76
+ });
77
+ child.stderr.on('data', (data) => {
78
+ const text = data.toString();
79
+ process.stderr.write(text);
80
+ capturedOutput += text;
81
+ });
82
+ child.on('close', (code) => resolve({ code, output: capturedOutput }));
72
83
  });
73
84
  }
74
85
 
@@ -99,7 +110,7 @@ function detectFailures() {
99
110
  return results;
100
111
  }
101
112
 
102
- async function analyzeAndFix(artifacts, client, argv) {
113
+ async function analyzeAndFix(artifacts, client, argv, capturedOutput = '') {
103
114
  if (artifacts.length === 0) {
104
115
  console.log(` ${C.YELLOW}āš ļø No failure artifacts detected. Run 'npx deflake doctor' to check permissions.${C.RESET}`);
105
116
  return 0;
@@ -111,20 +122,30 @@ async function analyzeAndFix(artifacts, client, argv) {
111
122
  console.log(`${C.CYAN}━━━ [${i+1}/${artifacts.length}] ${art.name} ━━━${C.RESET}`);
112
123
  try {
113
124
  const content = fs.readFileSync(art.htmlPath, 'utf8');
114
- const loc = extractLoc(content);
125
+
126
+ // SMART Location Extraction: try multiple sources
127
+ let loc = extractLoc(content);
128
+ let locSource = 'error-context.md';
129
+
130
+ if (!loc && capturedOutput) {
131
+ // Extract the relevant section from captured output for this artifact
132
+ const relevantOutput = extractRelevantOutput(capturedOutput, art.name);
133
+ loc = extractLoc(relevantOutput || capturedOutput);
134
+ locSource = 'console output';
135
+ }
115
136
 
116
137
  // Step 1: Location extraction
117
138
  if (loc) {
118
- console.log(` ${C.GRAY}šŸ“ Location:${C.RESET} ${path.basename(loc.path)}:${loc.line}`);
139
+ console.log(` ${C.GRAY}šŸ“ Location:${C.RESET} ${path.basename(loc.path)}:${loc.line} ${C.GRAY}(from ${locSource})${C.RESET}`);
119
140
  } else {
120
- console.log(` ${C.YELLOW}šŸ“ Location: Could not extract (regex mismatch)${C.RESET}`);
141
+ console.log(` ${C.YELLOW}šŸ“ Location: Could not extract from any source${C.RESET}`);
121
142
  }
122
143
 
123
144
  // Step 2: Source code reading
124
145
  const source = loc && fs.existsSync(loc.path) ? fs.readFileSync(loc.path, 'utf8') : null;
125
146
  console.log(` ${C.GRAY}šŸ“„ Source:${C.RESET} ${source ? 'Loaded (' + source.split('\n').length + ' lines)' : C.YELLOW + 'Not available' + C.RESET}`);
126
147
 
127
- // Step 3: API call
148
+ // Step 3: API call — send BOTH error-context AND console output for richer diagnosis
128
149
  console.log(` ${C.GRAY}🌐 Calling DeFlake API...${C.RESET}`);
129
150
  const res = await client.heal(null, art.htmlPath, loc, source, argv.fix);
130
151
 
@@ -141,12 +162,25 @@ async function analyzeAndFix(artifacts, client, argv) {
141
162
  console.log(` ${C.GRAY}→ ${p.action} at line ${p.line}: ${p.new_line?.substring(0, 70)}${C.RESET}`);
142
163
  }
143
164
  if (argv.fix) {
144
- console.log(` ${C.BRIGHT}šŸ’‰ Applying patches...${C.RESET}`);
145
- if (await applyFix(res, loc)) {
146
- count++;
147
- console.log(` ${C.GREEN}${C.BRIGHT}āœ… Fix applied to ${path.basename(loc.path)}:${loc.line}${C.RESET}`);
165
+ // Use loc from patches if our extraction failed
166
+ let fixLoc = loc;
167
+ if (!fixLoc && parsed.patches[0]?.file) {
168
+ const patchFile = path.resolve(parsed.patches[0].file);
169
+ if (fs.existsSync(patchFile)) {
170
+ fixLoc = { path: patchFile, line: parsed.patches[0].line };
171
+ console.log(` ${C.GRAY}šŸ“ Using patch target:${C.RESET} ${path.basename(patchFile)}`);
172
+ }
173
+ }
174
+ if (fixLoc) {
175
+ console.log(` ${C.BRIGHT}šŸ’‰ Applying patches...${C.RESET}`);
176
+ if (await applyFix(res, fixLoc)) {
177
+ count++;
178
+ console.log(` ${C.GREEN}${C.BRIGHT}āœ… Fix applied to ${path.basename(fixLoc.path)}:${fixLoc.line}${C.RESET}`);
179
+ } else {
180
+ console.log(` ${C.YELLOW}āš ļø Patches could not be applied (see details above)${C.RESET}`);
181
+ }
148
182
  } else {
149
- console.log(` ${C.YELLOW}āš ļø Patches could not be applied (see details above)${C.RESET}`);
183
+ console.log(` ${C.YELLOW}āš ļø Cannot apply: no target file detected${C.RESET}`);
150
184
  }
151
185
  } else {
152
186
  console.log(` ${C.YELLOW}ā„¹ļø Use --fix flag to auto-apply these patches${C.RESET}`);
@@ -221,10 +255,61 @@ async function applyFix(res, loc) {
221
255
  }
222
256
  }
223
257
 
258
+ function extractRelevantOutput(fullOutput, artifactName) {
259
+ // Extract error sections from captured console output
260
+ // Covers: Playwright, Cypress, WebdriverIO stack traces
261
+ const lines = fullOutput.split('\n');
262
+ let relevant = [];
263
+ let capturing = false;
264
+
265
+ for (const line of lines) {
266
+ if (line.includes('Error:') || line.includes('at ') || line.includes('Failing:')) {
267
+ capturing = true;
268
+ }
269
+ if (capturing) {
270
+ relevant.push(line);
271
+ if (line.includes('Error Context:') || (relevant.length > 30 && line.trim() === '')) {
272
+ capturing = false;
273
+ }
274
+ }
275
+ }
276
+ return relevant.join('\n');
277
+ }
278
+
224
279
  function extractLoc(text) {
225
- // Support .ts, .js, .cy.ts, .cy.js, .spec.ts, .spec.js
226
- const m = text.match(/at\s+(.*?\.(?:cy\.)?(?:spec\.)?(?:ts|js)):(\d+):(\d+)/);
227
- return m ? { path: path.resolve(m[1]), line: parseInt(m[2]) } : null;
280
+ if (!text) return null;
281
+
282
+ // === MULTI-FRAMEWORK LOCATION EXTRACTION ===
283
+ // Supports: Playwright, Cypress, WebdriverIO, Jest, Mocha
284
+
285
+ // Strategy 1: Absolute path inside parentheses (most precise)
286
+ // Format: at FunctionName (/absolute/path/file.ts:75:35)
287
+ let m = text.match(/\(([^()]*?[\\/][^()]+\.(?:ts|js|tsx|jsx|mjs|cjs)):(\d+)(?::(\d+))?\)/);
288
+ if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
289
+
290
+ // Strategy 2: Absolute path after "at" keyword (no parens)
291
+ // Format: at /absolute/path/file.ts:75:35
292
+ m = text.match(/at\s+(\/[^\s]+\.(?:ts|js|tsx|jsx)):(\d+)(?::(\d+))?/);
293
+ if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
294
+
295
+ // Strategy 3: Relative path after "at" keyword
296
+ // Format: at ../pages/file.ts:75
297
+ m = text.match(/at\s+(\.\.?\/[^\s]+\.(?:ts|js|tsx|jsx)):(\d+)(?::(\d+))?/);
298
+ if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
299
+
300
+ // Strategy 4: Playwright "at file:line" in stack (single line match)
301
+ m = text.match(/^\s+at\s+(.*?\.(?:ts|js)):(\d+)$/m);
302
+ if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
303
+
304
+ // Strategy 5: Cypress — at Context.<anonymous> (path)
305
+ m = text.match(/Context\.<anonymous>\s*\(([^)]+\.(?:ts|js|cy\.ts|cy\.js)):(\d+):(\d+)\)/);
306
+ if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
307
+
308
+ // Strategy 6: Generic — any path-like string with /dir/file.ts:line
309
+ m = text.match(/((?:[\w@.-]+\/)+[\w.-]+\.(?:ts|js|tsx|jsx)):(\d+)/);
310
+ if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
311
+
312
+ return null;
228
313
  }
229
314
 
230
315
  async function runDoctor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deflake",
3
- "version": "1.2.27",
3
+ "version": "1.2.29",
4
4
  "description": "AI-powered self-healing tool for Playwright, Cypress, and WebdriverIO tests.",
5
5
  "main": "client.js",
6
6
  "bin": {