deflake 1.0.8 โ†’ 1.0.12

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 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,56 @@ 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
+ const projectName = path.basename(process.cwd());
292
+
293
+ // Updated regex to be more flexible with arrows and spaces, and support .cy files
294
+ const testMatch = logText.match(/^\s*\d+\)\s+\[.*?\]\s+.+?\s+(.*?\.(?:spec|cy)\.(?:ts|js)):(\d+):(\d+)/m);
277
295
  if (testMatch) {
278
296
  loc.specFile = testMatch[1];
279
297
  loc.testLine = testMatch[2];
280
298
  }
281
299
 
282
- const stackRegex = /at\s+(?:.*? \()?((?:[a-zA-Z]:\\|[\/~]|\.?\.\/|[\w_\-]+\/).*?):(\d+):(\d+)\)?/g;
300
+ // Stack Trace Regex - Modified to handle Cypress URLs and webpack paths
301
+ const stackRegex = /at\s+(?:.*? \()?((?:https?:\/\/.*?\/|webpack:\/\/|[\/~\\]|\.?\.\/|[\w_\-]+\/).*?):(\d+):(\d+)\)?/g;
283
302
  let match;
284
303
  let foundRoot = false;
285
304
 
286
305
  while ((match = stackRegex.exec(logText)) !== null) {
287
- const file = match[1];
306
+ let file = match[1];
288
307
  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);
308
+
309
+ // Clean Cypress/Browser URLs to just the relative path if possible
310
+ if (file.includes('__cypress/runner')) continue;
311
+
312
+ if (file.includes('webpack:///')) {
313
+ file = file.split('webpack:///')[1];
314
+ } else if (file.includes('webpack://')) {
315
+ file = file.split('webpack://')[1];
316
+ }
317
+
318
+ // Strip project name if it's the first segment (common in Cypress/Webpack logs)
319
+ // e.g. "cypress-poc/cypress/e2e/api/auth.api.cy.ts" -> "cypress/e2e/api/auth.api.cy.ts"
320
+ // Also handle "./" prefix before project name
321
+ file = file.replace(/^\.\//, '');
322
+ if (file.startsWith(projectName + '/')) {
323
+ file = file.substring(projectName.length + 1);
324
+ }
325
+ file = file.replace(/^\.\//, '');
326
+
327
+ if (!foundRoot && !file.includes('node_modules') && !file.includes('cypress_runner')) {
328
+ loc.rootFile = file.split(/[/\\]/).pop();
329
+ loc.fullRootPath = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file);
292
330
  loc.rootLine = line;
293
331
  foundRoot = true;
294
332
  }
333
+
295
334
  if (loc.specFile && file.endsWith(loc.specFile)) {
296
335
  loc.stepLine = line;
297
336
  }
298
337
  }
299
338
 
300
339
  // 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.'))) {
340
+ if (!loc.specFile && loc.rootFile && (loc.rootFile.includes('.spec.') || loc.rootFile.includes('.test.') || loc.rootFile.includes('.cy.'))) {
302
341
  loc.specFile = loc.rootFile;
303
342
  loc.testLine = loc.rootLine;
304
343
  }
@@ -307,18 +346,20 @@ function extractFailureLocation(logText) {
307
346
  return null;
308
347
  }
309
348
 
349
+ // --- COLORS ---
350
+ const C = {
351
+ RESET: "\x1b[0m",
352
+ BRIGHT: "\x1b[1m",
353
+ RED: "\x1b[31m",
354
+ GREEN: "\x1b[32m",
355
+ YELLOW: "\x1b[33m",
356
+ CYAN: "\x1b[36m",
357
+ BLUE: "\x1b[34m",
358
+ GRAY: "\x1b[90m",
359
+ WHITE: "\x1b[37m"
360
+ };
361
+
310
362
  function printDetailedFix(fixText, location, sourceCode = null, isApplied = false) {
311
- const C = {
312
- RESET: "\x1b[0m",
313
- BRIGHT: "\x1b[1m",
314
- RED: "\x1b[31m",
315
- GREEN: "\x1b[32m",
316
- YELLOW: "\x1b[33m",
317
- CYAN: "\x1b[36m",
318
- BLUE: "\x1b[34m",
319
- GRAY: "\x1b[90m",
320
- WHITE: "\x1b[37m"
321
- };
322
363
 
323
364
  let fixCode = fixText;
324
365
  let explanation = null;
@@ -419,16 +460,6 @@ function printDetailedFix(fixText, location, sourceCode = null, isApplied = fals
419
460
  * Enforces tier limits and calculates deduplicated results.
420
461
  */
421
462
  async function runDoctor(argv) {
422
- const C = {
423
- RESET: "\x1b[0m",
424
- BRIGHT: "\x1b[1m",
425
- GREEN: "\x1b[32m",
426
- YELLOW: "\x1b[33m",
427
- RED: "\x1b[31m",
428
- CYAN: "\x1b[36m",
429
- GRAY: "\x1b[90m"
430
- };
431
-
432
463
  console.log(`\n${C.BRIGHT}๐Ÿ‘จโ€โš•๏ธ DeFlake Doctor - Diagnostic Tool${C.RESET}\n`);
433
464
 
434
465
  // 1. Environment Info & Version Check
@@ -577,15 +608,23 @@ async function runDoctor(argv) {
577
608
  // 5. API Connectivity
578
609
  console.log(`${C.BRIGHT}Checking Connectivity:${C.RESET}`);
579
610
  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}`);
611
+
582
612
  try {
583
613
  process.stdout.write(` โณ Pinging DeFlake API... `);
584
- const usage = await client.getUsage();
585
- if (usage) {
614
+ const result = await client.getUsage();
615
+
616
+ if (result.status === 'success') {
617
+ const usage = result.data;
586
618
  process.stdout.write(`\r โœ… API Connected! (Tier: ${C.GREEN}${usage.tier.toUpperCase()}${C.RESET}) \n`);
619
+ console.log(` Quota: ${usage.usage}/${usage.limit}`);
587
620
  } else {
588
- process.stdout.write(`\r โŒ ${C.RED}API Connectivity Failed (Invalid Key or Server Timeout)${C.RESET} \n`);
621
+ process.stdout.write(`\r โŒ ${C.RED}API Connectivity Failed${C.RESET} \n`);
622
+ if (result.code === 401 || result.code === 403) {
623
+ console.log(` Reason: API Key is invalid (Status ${result.code}).`);
624
+ } else {
625
+ console.log(` Reason: ${result.message} (Code: ${result.code || 'UNKNOWN'})`);
626
+ }
627
+ console.log(` API URL: ${client.apiUrl}`);
589
628
  }
590
629
  } catch (error) {
591
630
  process.stdout.write(`\r โŒ ${C.RED}API Connectivity Error: ${error.message}${C.RESET}\n`);
@@ -648,24 +687,18 @@ async function applySelfHealing(result) {
648
687
  }
649
688
 
650
689
  async function analyzeFailures(artifacts, fullLog, client) {
651
- const C = {
652
- RESET: "\x1b[0m",
653
- YELLOW: "\x1b[33m",
654
- RED: "\x1b[31m",
655
- GRAY: "\x1b[90m"
656
- };
657
-
658
690
  if (artifacts.length === 0) {
659
691
  console.log("โš ๏ธ No error artifacts found.");
660
692
  return;
661
693
  }
662
694
 
663
695
  // 1. Check Quota / Tier
664
- const usage = await client.getUsage();
696
+ const result = await client.getUsage();
665
697
  let limit = 100; // Default safety cap
666
698
  let tier = 'unknown';
667
699
 
668
- if (usage) {
700
+ if (result.status === 'success') {
701
+ const usage = result.data;
669
702
  tier = usage.tier || 'free';
670
703
  if (tier === 'free') limit = 5;
671
704
  else if (tier === 'pro') limit = 50;
@@ -708,21 +741,27 @@ async function analyzeFailures(artifacts, fullLog, client) {
708
741
  }
709
742
  }
710
743
 
711
- process.stdout.write(`\rโณ Analyzing ${art.name}... `);
712
- const result = await runHealer(specificLog, art.htmlPath, argv.apiUrl, art.name);
744
+ const displayName = (art.name || 'Unknown Artifact').substring(0, 40);
745
+ process.stdout.write(`\rโณ Analyzing ${displayName}... `);
713
746
 
714
- if (result && result.status === 'success') {
715
- results.push(result);
747
+ try {
748
+ const result = await runHealer(specificLog, art.htmlPath, argv.apiUrl, art.name);
749
+
750
+ if (result && result.status === 'success') {
751
+ results.push(result);
716
752
 
717
- // If --fix is enabled, apply it immediately for ALL tiers
718
- if (argv.fix) {
719
- await applySelfHealing(result);
753
+ // If --fix is enabled, apply it immediately for ALL tiers
754
+ if (argv.fix) {
755
+ await applySelfHealing(result);
756
+ }
757
+ } else {
758
+ console.log(`\n ${C.RED}โŒ Analysis failed for ${displayName}:${C.RESET} ${result?.detail || 'Check server status'}`);
720
759
  }
721
- } else {
722
- console.log(`\n ${C.RED}โŒ Analysis failed for ${art.name}:${C.RESET} ${result?.detail || 'Check server status'}`);
760
+ } catch (err) {
761
+ console.log(`\n ${C.RED}โŒ Error analyzing ${displayName}:${C.RESET} ${err.message}`);
723
762
  }
724
763
  }
725
- process.stdout.write("\rโœ… Analysis complete. \n");
764
+ console.log("\rโœ… Analysis complete. ");
726
765
 
727
766
  // GROUPING & PRINTING
728
767
  const groups = {};
@@ -736,20 +775,38 @@ async function analyzeFailures(artifacts, fullLog, client) {
736
775
  groups[key].tests.push(res.testName);
737
776
  }
738
777
 
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
- });
778
+ const sortedKeys = Object.keys(groups);
744
779
 
745
- for (const key of sortedKeys) {
746
- printDetailedFix(groups[key].fix, groups[key].location, groups[key].sourceCode, argv.fix);
780
+ if (sortedKeys.length > 0) {
781
+ for (const key of sortedKeys) {
782
+ printDetailedFix(groups[key].fix, groups[key].location, groups[key].sourceCode, argv.fix);
783
+ }
747
784
  }
748
785
 
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) {
786
+ // SUMMARY
787
+ console.log(`\n${C.BRIGHT}๐Ÿ“Š DeFlake Summary:${C.RESET}`);
788
+ console.log(` - Failures analyzed: ${batchArtifacts.length}`);
789
+ console.log(` - Fixes suggested: ${results.length}`);
790
+
791
+ try {
792
+ // Fetch updated usage for clearer reporting
793
+ const updatedUsage = await client.getUsage();
794
+ if (updatedUsage.status === 'success') {
795
+ const u = updatedUsage.data;
796
+ const remaining = u.limit - u.usage;
797
+ console.log(` - Quota remaining: ${C.GREEN}${remaining}/${u.limit}${C.RESET}`);
798
+ }
799
+ } catch (e) {
800
+ // usage fetch failed is non-critical
801
+ }
802
+
803
+ if (results.length === 0) {
752
804
  console.log(`${C.GRAY}โ„น๏ธ DeFlake analyzed the logs but couldn't find a confident fix for these errors.${C.RESET}`);
805
+ if (!fullLog) {
806
+ console.log(`${C.YELLOW}โš ๏ธ Tip: Ensure your test runner output is being captured correctly.${C.RESET}`);
807
+ }
808
+ } else if (!argv.fix) {
809
+ console.log(`\n${C.BRIGHT}๐Ÿ’ก Tip: Use ${C.CYAN}--fix${C.RESET}${C.BRIGHT} to automatically apply these suggested fixes next time.${C.RESET}`);
753
810
  }
754
811
  }
755
812
 
package/client.js CHANGED
@@ -11,6 +11,7 @@ class DeFlakeClient {
11
11
  this.apiUrl = apiUrl || process.env.DEFLAKE_API_URL || this.productionUrl;
12
12
  this.apiKey = apiKey || process.env.DEFLAKE_API_KEY;
13
13
  this.projectName = this.detectProjectName();
14
+ this.framework = this.detectFramework();
14
15
 
15
16
  if (!this.apiKey) {
16
17
  // We no longer exit here to allow diagnostic tools (like 'doctor') to run.
@@ -39,23 +40,37 @@ class DeFlakeClient {
39
40
  }
40
41
  }
41
42
 
43
+ detectFramework() {
44
+ // 1. Manual override via environment variable
45
+ if (process.env.DEFLAKE_FRAMEWORK) return process.env.DEFLAKE_FRAMEWORK.toLowerCase();
46
+
47
+ // 2. Automated detection via configuration files
48
+ if (fs.existsSync('cypress.config.js') || fs.existsSync('cypress.config.ts') || fs.existsSync('cypress')) return 'cypress';
49
+ if (fs.existsSync('playwright.config.js') || fs.existsSync('playwright.config.ts')) return 'playwright';
50
+ if (fs.existsSync('wdio.conf.js') || fs.existsSync('wdio.conf.ts')) return 'webdriverio';
51
+ return 'generic';
52
+ }
53
+
42
54
  async getUsage() {
43
55
  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');
56
+ // Robust replacement to avoid mangling host or protocol
57
+ const usageUrl = this.apiUrl.replace(/\/deflake$/, '/user/usage');
46
58
  const response = await axios.get(usageUrl, {
47
59
  headers: {
48
60
  'X-API-KEY': this.apiKey,
49
61
  'X-Project-Name': this.projectName
50
62
  }
51
63
  });
52
- return response.data;
64
+ return { status: 'success', data: response.data };
53
65
  } 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);
66
+ let message = error.message;
67
+ let code = error.code;
68
+ if (error.response) {
69
+ code = error.response.status;
70
+ message = error.response.data?.detail || error.response.statusText;
71
+ if (typeof message === 'object') message = JSON.stringify(message);
57
72
  }
58
- return null; // Silent failure for usage check
73
+ return { status: 'error', message, code };
59
74
  }
60
75
  }
61
76
 
@@ -66,13 +81,25 @@ class DeFlakeClient {
66
81
  if (!fs.existsSync(htmlPath)) throw new Error(`HTML file not found: ${htmlPath}`);
67
82
 
68
83
  const logContent = fs.readFileSync(logPath, 'utf8');
69
- const htmlContent = fs.readFileSync(htmlPath, 'utf8');
84
+
85
+ // Handle binary artifacts (Screenshots) vs Text artifacts (HTML/MD)
86
+ const isImage = htmlPath.toLowerCase().endsWith('.png');
87
+ let htmlContent = '';
88
+
89
+ if (isImage) {
90
+ // Read as base64 for screenshots
91
+ htmlContent = fs.readFileSync(htmlPath, 'base64');
92
+ } else {
93
+ // Read as utf-8 for HTML/MD
94
+ htmlContent = fs.readFileSync(htmlPath, 'utf8');
95
+ }
70
96
 
71
97
  const payload = {
72
98
  error_log: logContent || "",
73
99
  html_snapshot: htmlContent || "",
74
100
  failing_line: failureLocation ? `Line ${failureLocation.rootLine}` : "",
75
- source_code: sourceCode || ""
101
+ source_code: sourceCode || "",
102
+ framework: this.framework
76
103
  };
77
104
 
78
105
  const response = await axios.post(this.apiUrl, payload, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deflake",
3
- "version": "1.0.8",
3
+ "version": "1.0.12",
4
4
  "description": "AI-powered self-healing tool for Playwright, Cypress, and WebdriverIO tests.",
5
5
  "main": "client.js",
6
6
  "bin": {
@@ -0,0 +1,132 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ // Mocking dependencies to test the function in isolation
5
+ const process_cwd = () => '/Users/hugo/Documents/cypress-poc';
6
+ const path_resolve = (...args) => path.join(...args);
7
+ const path_isAbsolute = (p) => path.isAbsolute(p);
8
+
9
+ // Import the function from cli.js
10
+ // Since it's a script with side effects, we'll extract just the function or require it carefully.
11
+ // To keep it simple, I'll copy the function here for unit test, but use the real dependencies.
12
+
13
+ function extractFailureLocation(logText, cwd = '/Users/hugo/Documents/cypress-poc') {
14
+ if (!logText) return null;
15
+ const loc = {
16
+ specFile: null,
17
+ testLine: null,
18
+ rootFile: null,
19
+ fullRootPath: null,
20
+ rootLine: null,
21
+ stepLine: null
22
+ };
23
+
24
+ const projectName = path.basename(cwd);
25
+
26
+ // Updated regex to be more flexible with arrows and spaces, and support .cy files
27
+ const testMatch = logText.match(/^\s*\d+\)\s+\[.*?\]\s+.+?\s+(.*?\.(?:spec|cy)\.(?:ts|js)):(\d+):(\d+)/m);
28
+ if (testMatch) {
29
+ loc.specFile = testMatch[1];
30
+ loc.testLine = testMatch[2];
31
+ }
32
+
33
+ // Stack Trace Regex - Modified to handle Cypress URLs and webpack paths
34
+ const stackRegex = /at\s+(?:.*? \()?((?:https?:\/\/.*?\/|webpack:\/\/\/|[\/~\\]|\.?\.\/|[\w_\-]+\/).*?):(\d+):(\d+)\)?/g;
35
+ let match;
36
+ let foundRoot = false;
37
+
38
+ while ((match = stackRegex.exec(logText)) !== null) {
39
+ let file = match[1];
40
+ const line = match[2];
41
+
42
+ // Clean Cypress/Browser URLs to just the relative path if possible
43
+ if (file.includes('__cypress/runner')) continue;
44
+
45
+ if (file.includes('webpack:///')) {
46
+ file = file.split('webpack:///')[1];
47
+ } else if (file.includes('webpack://')) {
48
+ file = file.split('webpack://')[1];
49
+ }
50
+
51
+ // Strip project name if it's the first segment (common in Cypress/Webpack logs)
52
+ if (file.startsWith(projectName + '/')) {
53
+ file = file.substring(projectName.length + 1);
54
+ } else if (file.startsWith('./' + projectName + '/')) {
55
+ file = file.substring(projectName.length + 3);
56
+ }
57
+
58
+ file = file.replace(/^\.\//, '');
59
+
60
+ if (!foundRoot && !file.includes('node_modules') && !file.includes('cypress_runner')) {
61
+ loc.rootFile = file.split(/[/\\]/).pop();
62
+ loc.fullRootPath = path.isAbsolute(file) ? file : path.resolve(cwd, file);
63
+ loc.rootLine = line;
64
+ foundRoot = true;
65
+ }
66
+
67
+ if (loc.specFile && (file.endsWith(loc.specFile) || file === loc.specFile)) {
68
+ loc.stepLine = line;
69
+ }
70
+ }
71
+
72
+ // Fallback: If header regex failed but we found a root file that looks like a test
73
+ if (!loc.specFile && loc.rootFile && (loc.rootFile.includes('.spec.') || loc.rootFile.includes('.test.') || loc.rootFile.includes('.cy.'))) {
74
+ loc.specFile = loc.rootFile;
75
+ loc.testLine = loc.rootLine;
76
+ }
77
+
78
+ if (loc.specFile || loc.rootFile) return loc;
79
+ return null;
80
+ }
81
+
82
+ // TEST CASES
83
+ const testLogs = [
84
+ {
85
+ name: "Cypress Webpack Trace",
86
+ log: `API - Reqres
87
+ 1) authenticates and returns token
88
+ From Your Spec Code:
89
+ at AuthClient.login (webpack://cypress-poc/./api/clients/AuthClient.ts:17:14)
90
+ at Context.eval (webpack://cypress-poc/./cypress/e2e/api/auth.api.cy.ts:12:15)`,
91
+ expected: {
92
+ rootFile: 'AuthClient.ts',
93
+ fullRootPath: '/Users/hugo/Documents/cypress-poc/api/clients/AuthClient.ts',
94
+ rootLine: '17'
95
+ }
96
+ },
97
+ {
98
+ name: "Cypress Negative Scenario",
99
+ log: ` 1) API - Reqres negative scenarios
100
+ returns 400 when password is missing:
101
+
102
+ AssertionError: expected 403 to equal 400
103
+ at Context.eval (webpack://cypress-poc/./cypress/e2e/api/auth.negative.api.cy.ts:11:33)`,
104
+ expected: {
105
+ rootFile: 'auth.negative.api.cy.ts',
106
+ fullRootPath: '/Users/hugo/Documents/cypress-poc/cypress/e2e/api/auth.negative.api.cy.ts',
107
+ rootLine: '11'
108
+ }
109
+ }
110
+ ];
111
+
112
+ console.log("๐Ÿงช Running DeFlake Path Parsing Tests...\n");
113
+ let passed = 0;
114
+ testLogs.forEach(t => {
115
+ const result = extractFailureLocation(t.log);
116
+ console.log(`Test: ${t.name}`);
117
+ let success = true;
118
+ for (const key in t.expected) {
119
+ if (result[key] !== t.expected[key]) {
120
+ console.log(` โŒ FAIL: ${key} expected "${t.expected[key]}", got "${result[key]}"`);
121
+ success = false;
122
+ }
123
+ }
124
+ if (success) {
125
+ console.log(` โœ… PASS`);
126
+ passed++;
127
+ }
128
+ console.log("");
129
+ });
130
+
131
+ console.log(`Summary: ${passed}/${testLogs.length} tests passed.`);
132
+ if (passed < testLogs.length) process.exit(1);