deflake 1.0.8 → 1.0.10

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 (4) hide show
  1. package/README.md +27 -4
  2. package/cli.js +65 -25
  3. package/client.js +10 -7
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -12,24 +12,47 @@ npm install deflake --save-dev
12
12
  ## šŸ”§ Usage
13
13
 
14
14
  ### Wrapper Mode (Zero Config)
15
- Simply prepend `npx deflake` to your test run command:
15
+ Simply prepend `npx deflake` to your test run command. DeFlake will automatically wrap the execution and analyze failures.
16
16
 
17
17
  ```bash
18
18
  # Playwright
19
- export DEFLAKE_API_KEY="your-api-key"
20
19
  npx deflake npx playwright test
21
20
 
22
21
  # Cypress
23
22
  npx deflake npx cypress run
24
23
  ```
25
24
 
25
+ ### šŸš‘ Diagnostics (Doctor Mode)
26
+ Validate your environment, API key, and quota without running tests:
27
+
28
+ ```bash
29
+ npx deflake doctor
30
+ ```
31
+
26
32
  ### Manual Mode
27
- Analyze an existing error log and HTML snapshot:
33
+ Analyze an existing error log and HTML/Image snapshot:
34
+
35
+ ```bash
36
+ npx deflake --log error.log --html cypress/screenshots/my-test-failure.png
37
+ ```
38
+
39
+ ## 🌲 Cypress Integration Tips
40
+
41
+ For the best experience with Cypress, we recommend adding a standardized script to your `package.json`:
28
42
 
43
+ ```json
44
+ "scripts": {
45
+ "test:deflake": "deflake npx cypress run"
46
+ }
47
+ ```
48
+
49
+ Then run it using:
29
50
  ```bash
30
- npx deflake --log error.log --html playwright-report/index.html
51
+ npm run test:deflake
31
52
  ```
32
53
 
54
+ DeFlake automatically scans `cypress/screenshots` to find failure artifacts (images) for analysis.
55
+
33
56
  ## šŸ“„ Support & Documentation
34
57
 
35
58
  For issues, feature requests, or to get your API key, please visit our [Official Portal](https://deflake-api.up.railway.app).
package/cli.js CHANGED
@@ -125,6 +125,22 @@ function detectAllArtifacts(providedLog, providedHtml) {
125
125
  }
126
126
  }
127
127
 
128
+ // 2. Scan cypress/screenshots (Cypress default)
129
+ if (fs.existsSync('cypress/screenshots')) {
130
+ const screenshotFiles = fs.readdirSync('cypress/screenshots', { recursive: true })
131
+ .filter(f => f.endsWith('.png'))
132
+ .map(f => path.resolve('cypress/screenshots', f));
133
+
134
+ for (const screenshot of screenshotFiles) {
135
+ detected.push({
136
+ logPath: providedLog,
137
+ htmlPath: screenshot,
138
+ id: path.basename(screenshot),
139
+ name: path.basename(screenshot)
140
+ });
141
+ }
142
+ }
143
+
128
144
  // Fallback: If no granular results, check global report
129
145
  if (detected.length === 0) {
130
146
  if (fs.existsSync('playwright-report/index.html')) {
@@ -272,33 +288,44 @@ function extractFailureLocation(logText) {
272
288
  stepLine: null
273
289
  };
274
290
 
275
- // Updated regex to be more flexible with arrows and spaces
276
- const testMatch = logText.match(/^\s*\d+\)\s+\[.*?\]\s+.+?\s+(.*?.spec.ts):(\d+):(\d+)/m);
291
+ // Updated regex to be more flexible with arrows and spaces, and support .cy files
292
+ const testMatch = logText.match(/^\s*\d+\)\s+\[.*?\]\s+.+?\s+(.*?\.(?:spec|cy)\.(?:ts|js)):(\d+):(\d+)/m);
277
293
  if (testMatch) {
278
294
  loc.specFile = testMatch[1];
279
295
  loc.testLine = testMatch[2];
280
296
  }
281
297
 
282
- const stackRegex = /at\s+(?:.*? \()?((?:[a-zA-Z]:\\|[\/~]|\.?\.\/|[\w_\-]+\/).*?):(\d+):(\d+)\)?/g;
298
+ // Stack Trace Regex - Modified to handle Cypress URLs and webpack paths
299
+ const stackRegex = /at\s+(?:.*? \()?((?:https?:\/\/.*?\/|webpack:\/\/\/|[\/~\\]|\.?\.\/|[\w_\-]+\/).*?):(\d+):(\d+)\)?/g;
283
300
  let match;
284
301
  let foundRoot = false;
285
302
 
286
303
  while ((match = stackRegex.exec(logText)) !== null) {
287
- const file = match[1];
304
+ let file = match[1];
288
305
  const line = match[2];
289
- if (!foundRoot && !file.includes('node_modules')) {
290
- loc.rootFile = file.split('/').pop();
291
- loc.fullRootPath = path.resolve(process.cwd(), file);
306
+
307
+ // Clean Cypress/Browser URLs to just the relative path if possible
308
+ if (file.includes('__cypress/runner')) continue; // Skip internal runner files
309
+ if (file.includes('webpack:///')) {
310
+ file = file.split('webpack:///')[1].replace(/^\.\//, '');
311
+ } else if (file.includes('webpack://')) {
312
+ file = file.split('webpack://')[1].replace(/^\.\//, '');
313
+ }
314
+
315
+ if (!foundRoot && !file.includes('node_modules') && !file.includes('cypress_runner')) {
316
+ loc.rootFile = file.split(/[/\\]/).pop();
317
+ loc.fullRootPath = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file);
292
318
  loc.rootLine = line;
293
319
  foundRoot = true;
294
320
  }
321
+
295
322
  if (loc.specFile && file.endsWith(loc.specFile)) {
296
323
  loc.stepLine = line;
297
324
  }
298
325
  }
299
326
 
300
327
  // Fallback: If header regex failed but we found a root file that looks like a test
301
- if (!loc.specFile && loc.rootFile && (loc.rootFile.includes('.spec.') || loc.rootFile.includes('.test.'))) {
328
+ if (!loc.specFile && loc.rootFile && (loc.rootFile.includes('.spec.') || loc.rootFile.includes('.test.') || loc.rootFile.includes('.cy.'))) {
302
329
  loc.specFile = loc.rootFile;
303
330
  loc.testLine = loc.rootLine;
304
331
  }
@@ -577,15 +604,23 @@ async function runDoctor(argv) {
577
604
  // 5. API Connectivity
578
605
  console.log(`${C.BRIGHT}Checking Connectivity:${C.RESET}`);
579
606
  const client = new DeFlakeClient(argv.apiUrl, apiKey);
580
- // Debug: Show what the client is using
581
- console.log(` ${C.GRAY}(URL: ${client.apiUrl}, Key length: ${client.apiKey ? client.apiKey.length : 0})${C.RESET}`);
607
+
582
608
  try {
583
609
  process.stdout.write(` ā³ Pinging DeFlake API... `);
584
- const usage = await client.getUsage();
585
- if (usage) {
610
+ const result = await client.getUsage();
611
+
612
+ if (result.status === 'success') {
613
+ const usage = result.data;
586
614
  process.stdout.write(`\r āœ… API Connected! (Tier: ${C.GREEN}${usage.tier.toUpperCase()}${C.RESET}) \n`);
615
+ console.log(` Quota: ${usage.usage}/${usage.limit}`);
587
616
  } else {
588
- process.stdout.write(`\r āŒ ${C.RED}API Connectivity Failed (Invalid Key or Server Timeout)${C.RESET} \n`);
617
+ process.stdout.write(`\r āŒ ${C.RED}API Connectivity Failed${C.RESET} \n`);
618
+ if (result.code === 401 || result.code === 403) {
619
+ console.log(` Reason: API Key is invalid (Status ${result.code}).`);
620
+ } else {
621
+ console.log(` Reason: ${result.message} (Code: ${result.code || 'UNKNOWN'})`);
622
+ }
623
+ console.log(` API URL: ${client.apiUrl}`);
589
624
  }
590
625
  } catch (error) {
591
626
  process.stdout.write(`\r āŒ ${C.RED}API Connectivity Error: ${error.message}${C.RESET}\n`);
@@ -661,11 +696,12 @@ async function analyzeFailures(artifacts, fullLog, client) {
661
696
  }
662
697
 
663
698
  // 1. Check Quota / Tier
664
- const usage = await client.getUsage();
699
+ const result = await client.getUsage();
665
700
  let limit = 100; // Default safety cap
666
701
  let tier = 'unknown';
667
702
 
668
- if (usage) {
703
+ if (result.status === 'success') {
704
+ const usage = result.data;
669
705
  tier = usage.tier || 'free';
670
706
  if (tier === 'free') limit = 5;
671
707
  else if (tier === 'pro') limit = 50;
@@ -736,20 +772,24 @@ async function analyzeFailures(artifacts, fullLog, client) {
736
772
  groups[key].tests.push(res.testName);
737
773
  }
738
774
 
739
- const sortedKeys = Object.keys(groups).sort((a, b) => {
740
- const lineA = parseInt(groups[a].location?.rootLine) || 0;
741
- const lineB = parseInt(groups[b].location?.rootLine) || 0;
742
- return lineA - lineB;
743
- });
775
+ const sortedKeys = Object.keys(groups);
744
776
 
745
- for (const key of sortedKeys) {
746
- printDetailedFix(groups[key].fix, groups[key].location, groups[key].sourceCode, argv.fix);
777
+ if (sortedKeys.length > 0) {
778
+ for (const key of sortedKeys) {
779
+ printDetailedFix(groups[key].fix, groups[key].location, groups[key].sourceCode, argv.fix);
780
+ }
747
781
  }
748
782
 
749
- if (results.length > 0 && sortedKeys.length === 0) {
750
- console.log(`${C.YELLOW}āš ļø Note: Some suggestions were found but could not be grouped for display.${C.RESET}`);
751
- } else if (results.length === 0) {
783
+ // SUMMARY
784
+ console.log(`\nšŸ“Š DeFlake Summary:`);
785
+ console.log(` - Failures analyzed: ${batchArtifacts.length}`);
786
+ console.log(` - Fixes suggested: ${results.length}`);
787
+
788
+ if (results.length === 0) {
752
789
  console.log(`${C.GRAY}ā„¹ļø DeFlake analyzed the logs but couldn't find a confident fix for these errors.${C.RESET}`);
790
+ if (!fullLog) {
791
+ console.log(`${C.YELLOW}āš ļø Tip: Ensure your test runner output is being captured correctly.${C.RESET}`);
792
+ }
753
793
  }
754
794
  }
755
795
 
package/client.js CHANGED
@@ -41,21 +41,24 @@ class DeFlakeClient {
41
41
 
42
42
  async getUsage() {
43
43
  try {
44
- // Replace /api/deflake with /api/user/usage to avoid matching domain name
45
- const usageUrl = this.apiUrl.replace('/api/deflake', '/api/user/usage');
44
+ // Robust replacement to avoid mangling host or protocol
45
+ const usageUrl = this.apiUrl.replace(/\/deflake$/, '/user/usage');
46
46
  const response = await axios.get(usageUrl, {
47
47
  headers: {
48
48
  'X-API-KEY': this.apiKey,
49
49
  'X-Project-Name': this.projectName
50
50
  }
51
51
  });
52
- return response.data;
52
+ return { status: 'success', data: response.data };
53
53
  } catch (error) {
54
- // Log error details for debugging (only in doctor mode, based on env var)
55
- if (process.env.DEFLAKE_DEBUG) {
56
- console.error('getUsage error:', error.response?.status, error.response?.data || error.message);
54
+ let message = error.message;
55
+ let code = error.code;
56
+ if (error.response) {
57
+ code = error.response.status;
58
+ message = error.response.data?.detail || error.response.statusText;
59
+ if (typeof message === 'object') message = JSON.stringify(message);
57
60
  }
58
- return null; // Silent failure for usage check
61
+ return { status: 'error', message, code };
59
62
  }
60
63
  }
61
64
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deflake",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "AI-powered self-healing tool for Playwright, Cypress, and WebdriverIO tests.",
5
5
  "main": "client.js",
6
6
  "bin": {