@alternative-path/testlens-playwright-reporter 0.4.2 → 0.4.4

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/index.js CHANGED
@@ -7,7 +7,6 @@ const os = tslib_1.__importStar(require("os"));
7
7
  const path = tslib_1.__importStar(require("path"));
8
8
  const fs = tslib_1.__importStar(require("fs"));
9
9
  const https = tslib_1.__importStar(require("https"));
10
- const tls = tslib_1.__importStar(require("tls"));
11
10
  const axios_1 = tslib_1.__importDefault(require("axios"));
12
11
  const child_process_1 = require("child_process");
13
12
  // Lazy-load mime module to support ESM
@@ -21,94 +20,6 @@ async function getMime() {
21
20
  return mimeModule;
22
21
  }
23
22
  class TestLensReporter {
24
- /**
25
- * Get bundled CA certificates for TestLens
26
- * Combines custom CA bundle with Node.js root certificates
27
- * This ensures SSL works with both testlens.qa-path.com and other HTTPS endpoints
28
- */
29
- static getBundledCaCertificates() {
30
- const allCerts = [];
31
- // First, add our bundled TestLens CA certificate chain
32
- try {
33
- const certPath = path.join(__dirname, 'testlens-ca-bundle.pem');
34
- if (fs.existsSync(certPath)) {
35
- const certData = fs.readFileSync(certPath, 'utf8');
36
- // Split the bundle into individual certificates
37
- const certs = certData.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g);
38
- if (certs && certs.length > 0) {
39
- const buffers = certs.map(cert => Buffer.from(cert, 'utf8'));
40
- allCerts.push(...buffers);
41
- if (process.env.DEBUG) {
42
- console.log(`✓ Loaded bundled TestLens CA certificates (${buffers.length} certificate(s))`);
43
- }
44
- }
45
- }
46
- }
47
- catch (error) {
48
- if (process.env.DEBUG) {
49
- console.log('⚠️ Bundled CA certificate not available');
50
- }
51
- }
52
- // Then, add Node.js built-in root certificates for general SSL support
53
- try {
54
- if (tls.rootCertificates && Array.isArray(tls.rootCertificates) && tls.rootCertificates.length > 0) {
55
- const rootCerts = tls.rootCertificates.map(cert => Buffer.from(cert, 'utf8'));
56
- allCerts.push(...rootCerts);
57
- if (process.env.DEBUG) {
58
- console.log(`✓ Added Node.js built-in root certificates (${rootCerts.length} certificates)`);
59
- }
60
- }
61
- }
62
- catch (error) {
63
- if (process.env.DEBUG) {
64
- console.log('⚠️ Node.js built-in root certificates not available');
65
- }
66
- }
67
- return allCerts.length > 0 ? allCerts : undefined;
68
- }
69
- /**
70
- * Automatically detect and load system CA certificates
71
- * Uses bundled certificates as primary source, falls back to system certificates
72
- */
73
- static getSystemCaCertificates() {
74
- // First, try to use our bundled certificates (Node.js rootCertificates)
75
- const bundledCerts = TestLensReporter.getBundledCaCertificates();
76
- if (bundledCerts && bundledCerts.length > 0) {
77
- return bundledCerts;
78
- }
79
- // Fallback: Try to load from file system (for older Node.js versions or special cases)
80
- const platform = os.platform();
81
- const certificates = [];
82
- const certPaths = [];
83
- if (platform === 'darwin') {
84
- // macOS: Common certificate locations
85
- certPaths.push('/etc/ssl/cert.pem', '/usr/local/etc/openssl/cert.pem', '/opt/homebrew/etc/openssl/cert.pem', '/System/Library/OpenSSL/certs/cert.pem');
86
- }
87
- else {
88
- // Linux and other Unix-like systems
89
- certPaths.push('/etc/ssl/certs/ca-certificates.crt', '/etc/ssl/certs/ca-bundle.crt', '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/ca-bundle.pem', '/usr/share/ssl/certs/ca-bundle.crt', '/usr/local/share/certs/ca-root-nss.crt', '/etc/ca-certificates/extracted/tls-ca-bundle.pem');
90
- }
91
- // Try to load certificates from common locations
92
- for (const certPath of certPaths) {
93
- try {
94
- if (certPath && fs.existsSync(certPath)) {
95
- const certData = fs.readFileSync(certPath);
96
- certificates.push(certData);
97
- // Only log in debug mode to avoid noise
98
- if (process.env.DEBUG) {
99
- console.log(`✓ Loaded CA certificates from: ${certPath}`);
100
- }
101
- }
102
- }
103
- catch (error) {
104
- // Silently continue if a certificate file can't be read
105
- // This is expected as not all paths will exist on every system
106
- }
107
- }
108
- // Return undefined if no certificates found (let Node.js use defaults)
109
- // Return certificates array if we found any
110
- return certificates.length > 0 ? certificates : undefined;
111
- }
112
23
  /**
113
24
  * Parse custom metadata from environment variables
114
25
  * Checks for common metadata environment variables
@@ -149,6 +60,7 @@ class TestLensReporter {
149
60
  constructor(options) {
150
61
  this.runCreationFailed = false; // Track if run creation failed due to limits
151
62
  this.cliArgs = {}; // Store CLI args separately
63
+ this.pendingUploads = new Set(); // Track pending artifact uploads
152
64
  // Parse custom CLI arguments
153
65
  const customArgs = TestLensReporter.parseCustomArgs();
154
66
  this.cliArgs = customArgs; // Store CLI args separately for later use
@@ -186,46 +98,6 @@ class TestLensReporter {
186
98
  }
187
99
  // Determine SSL validation behavior
188
100
  let rejectUnauthorized = true; // Default to secure
189
- let ca = undefined;
190
- // Use bundled CA certificates as primary source (Node.js rootCertificates)
191
- // This ensures consistent behavior across all platforms
192
- const bundledCerts = TestLensReporter.getBundledCaCertificates();
193
- // Load custom CA certificate if explicitly provided (for advanced users)
194
- // Custom certificates will be combined with bundled certificates
195
- if (this.config.caCertificate) {
196
- try {
197
- if (fs.existsSync(this.config.caCertificate)) {
198
- const customCert = fs.readFileSync(this.config.caCertificate);
199
- // Combine bundled certs with custom cert
200
- if (bundledCerts && Array.isArray(bundledCerts) && bundledCerts.length > 0) {
201
- ca = [...bundledCerts, customCert];
202
- console.log(`✓ Using bundled CA certificates + custom certificate: ${this.config.caCertificate}`);
203
- }
204
- else {
205
- ca = customCert;
206
- console.log(`✓ Using custom CA certificate: ${this.config.caCertificate}`);
207
- }
208
- }
209
- else {
210
- console.warn(`⚠️ CA certificate file not found: ${this.config.caCertificate}`);
211
- // Fall back to bundled certs if custom cert not found
212
- ca = bundledCerts || undefined;
213
- }
214
- }
215
- catch (error) {
216
- console.warn(`⚠️ Failed to read CA certificate file: ${this.config.caCertificate}`, error.message);
217
- // Fall back to bundled certs if custom cert read fails
218
- ca = bundledCerts || undefined;
219
- }
220
- }
221
- else {
222
- // Use bundled certificates as primary source
223
- // This works reliably across all platforms (Windows, macOS, Linux)
224
- ca = bundledCerts || undefined;
225
- if (ca && process.env.DEBUG) {
226
- console.log(`✓ Using bundled CA certificates (${Array.isArray(ca) ? ca.length : 1} certificates)`);
227
- }
228
- }
229
101
  // Check various ways SSL validation can be disabled (in order of precedence)
230
102
  if (this.config.ignoreSslErrors) {
231
103
  // Explicit configuration option
@@ -243,24 +115,6 @@ class TestLensReporter {
243
115
  console.log('⚠️ SSL certificate validation disabled via NODE_TLS_REJECT_UNAUTHORIZED environment variable');
244
116
  }
245
117
  // Set up axios instance with retry logic and enhanced SSL handling
246
- const httpsAgentOptions = {
247
- rejectUnauthorized: rejectUnauthorized,
248
- // Allow any TLS version for better compatibility
249
- minVersion: 'TLSv1.2',
250
- maxVersion: 'TLSv1.3'
251
- };
252
- // Add CA certificates if available
253
- // On Windows, ca will be undefined to let Node.js use Windows certificate store automatically
254
- // On Unix systems, ca will contain certificates if found, or undefined to use Node.js defaults
255
- if (ca !== undefined) {
256
- if (Array.isArray(ca) && ca.length > 0) {
257
- httpsAgentOptions.ca = ca;
258
- }
259
- else if (typeof ca === 'string' || Buffer.isBuffer(ca)) {
260
- httpsAgentOptions.ca = ca;
261
- }
262
- // If ca is undefined, we don't set it, allowing Node.js to use its default certificate store
263
- }
264
118
  this.axiosInstance = axios_1.default.create({
265
119
  baseURL: this.config.apiEndpoint,
266
120
  timeout: this.config.timeout,
@@ -269,7 +123,12 @@ class TestLensReporter {
269
123
  ...(this.config.apiKey && { 'X-API-Key': this.config.apiKey }),
270
124
  },
271
125
  // Enhanced SSL handling with flexible TLS configuration
272
- httpsAgent: new https.Agent(httpsAgentOptions)
126
+ httpsAgent: new https.Agent({
127
+ rejectUnauthorized: rejectUnauthorized,
128
+ // Allow any TLS version for better compatibility
129
+ minVersion: 'TLSv1.2',
130
+ maxVersion: 'TLSv1.3'
131
+ })
273
132
  });
274
133
  // Add retry interceptor
275
134
  this.axiosInstance.interceptors.response.use((response) => response, async (error) => {
@@ -308,7 +167,8 @@ class TestLensReporter {
308
167
  browser: 'multiple',
309
168
  os: `${os.type()} ${os.release()}`,
310
169
  playwrightVersion: this.getPlaywrightVersion(),
311
- nodeVersion: process.version
170
+ nodeVersion: process.version,
171
+ testlensVersion: this.getTestLensVersion()
312
172
  };
313
173
  // Add custom metadata if provided
314
174
  if (this.config.customMetadata && Object.keys(this.config.customMetadata).length > 0) {
@@ -331,6 +191,15 @@ class TestLensReporter {
331
191
  return 'unknown';
332
192
  }
333
193
  }
194
+ getTestLensVersion() {
195
+ try {
196
+ const testlensPackage = require('./package.json');
197
+ return testlensPackage.version;
198
+ }
199
+ catch (error) {
200
+ return 'unknown';
201
+ }
202
+ }
334
203
  normalizeTestStatus(status) {
335
204
  // Treat timeout as failed for consistency with analytics
336
205
  if (status === 'timedOut') {
@@ -607,23 +476,20 @@ class TestLensReporter {
607
476
  }
608
477
  return testError;
609
478
  });
610
- // Only send testEnd event after final retry attempt
611
- // If test passed or this is the last retry, send the event
612
- const isFinalAttempt = result.status === 'passed' || result.status === 'skipped' || result.retry >= test.retries;
613
- if (isFinalAttempt) {
614
- console.log(`[TestLens] Sending testEnd - testId: ${testData.id}, status: ${testData.status}, originalStatus: ${testData.originalStatus}`);
615
- // Send test end event to API and get response
616
- const testEndResponse = await this.sendToApi({
617
- type: 'testEnd',
618
- runId: this.runId,
619
- timestamp: new Date().toISOString(),
620
- test: testData
621
- });
622
- // Handle artifacts (test case is now guaranteed to be in database)
623
- if (this.config.enableArtifacts) {
624
- // Pass test case DB ID if available for faster lookups
625
- await this.processArtifacts(testId, result, testEndResponse?.testCaseId);
626
- }
479
+ // Send testEnd event for all tests, regardless of status
480
+ // This ensures tests that are interrupted or have unexpected statuses are properly recorded
481
+ console.log(`[TestLens] Sending testEnd - testId: ${testData.id}, status: ${testData.status}, originalStatus: ${testData.originalStatus}`);
482
+ // Send test end event to API and get response
483
+ const testEndResponse = await this.sendToApi({
484
+ type: 'testEnd',
485
+ runId: this.runId,
486
+ timestamp: new Date().toISOString(),
487
+ test: testData
488
+ });
489
+ // Handle artifacts (test case is now guaranteed to be in database)
490
+ if (this.config.enableArtifacts) {
491
+ // Pass test case DB ID if available for faster lookups
492
+ await this.processArtifacts(testId, result, testEndResponse?.testCaseId);
627
493
  }
628
494
  }
629
495
  // Update spec status
@@ -639,12 +505,33 @@ class TestLensReporter {
639
505
  specData.status = 'skipped';
640
506
  }
641
507
  // Check if all tests in spec are complete
508
+ // Only consider tests that were actually executed (have testData)
642
509
  const remainingTests = test.parent.tests.filter((t) => {
643
510
  const tId = this.getTestId(t);
644
511
  const tData = this.testMap.get(tId);
645
- return !tData || !tData.endTime;
512
+ // If testData exists but no endTime, it's still running
513
+ return tData && !tData.endTime;
646
514
  });
647
515
  if (remainingTests.length === 0) {
516
+ // Determine final spec status based on all executed tests
517
+ const executedTests = test.parent.tests
518
+ .map((t) => {
519
+ const tId = this.getTestId(t);
520
+ return this.testMap.get(tId);
521
+ })
522
+ .filter((tData) => !!tData);
523
+ if (executedTests.length > 0) {
524
+ const allTestStatuses = executedTests.map(tData => tData.status);
525
+ if (allTestStatuses.every(status => status === 'passed')) {
526
+ specData.status = 'passed';
527
+ }
528
+ else if (allTestStatuses.some(status => status === 'failed')) {
529
+ specData.status = 'failed';
530
+ }
531
+ else if (allTestStatuses.every(status => status === 'skipped')) {
532
+ specData.status = 'skipped';
533
+ }
534
+ }
648
535
  // Aggregate tags from all tests in this spec
649
536
  const allTags = new Set();
650
537
  test.parent.tests.forEach((t) => {
@@ -672,6 +559,17 @@ class TestLensReporter {
672
559
  async onEnd(result) {
673
560
  this.runMetadata.endTime = new Date().toISOString();
674
561
  this.runMetadata.duration = Date.now() - new Date(this.runMetadata.startTime).getTime();
562
+ // Wait for all pending artifact uploads to complete before sending runEnd
563
+ if (this.pendingUploads.size > 0) {
564
+ console.log(`⏳ Waiting for ${this.pendingUploads.size} artifact upload(s) to complete...`);
565
+ try {
566
+ await Promise.all(Array.from(this.pendingUploads));
567
+ console.log(`✅ All artifact uploads completed`);
568
+ }
569
+ catch (error) {
570
+ console.error(`⚠️ Some artifact uploads failed, continuing with runEnd`);
571
+ }
572
+ }
675
573
  // Calculate final stats
676
574
  const totalTests = Array.from(this.testMap.values()).length;
677
575
  const passedTests = Array.from(this.testMap.values()).filter(t => t.status === 'passed').length;
@@ -789,68 +687,17 @@ class TestLensReporter {
789
687
  console.error(`❌ Authentication failed: ${errorData?.error || 'Invalid API key'}`);
790
688
  }
791
689
  }
792
- else {
793
- // Check for SSL certificate errors
794
- const isSslError = error?.code === 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY' ||
795
- error?.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' ||
796
- error?.code === 'CERT_HAS_EXPIRED' ||
797
- error?.code === 'SELF_SIGNED_CERT_IN_CHAIN' ||
798
- error?.message?.includes('certificate') ||
799
- error?.message?.includes('SSL') ||
800
- error?.message?.includes('TLS');
801
- if (isSslError && status !== 403) {
802
- console.error('\n' + '='.repeat(80));
803
- console.error('❌ SSL Certificate Error');
804
- console.error('='.repeat(80));
805
- console.error('');
806
- console.error(`Failed to send ${payload.type} event to TestLens due to SSL certificate issue.`);
807
- console.error('');
808
- console.error('The reporter automatically attempts to detect and use system CA certificates.');
809
- console.error('If this error persists, it may indicate:');
810
- console.error(' - Missing or outdated system CA certificates');
811
- console.error(' - Corporate proxy with custom CA certificate');
812
- console.error(' - Incomplete certificate chain from the server');
813
- console.error('');
814
- console.error('Error details:');
815
- console.error(` Code: ${error?.code || 'Unknown'}`);
816
- console.error(` Message: ${error?.message || 'Unknown error'}`);
817
- console.error('');
818
- console.error('Possible solutions:');
819
- console.error('');
820
- console.error('1. Update your system\'s CA certificate store:');
821
- console.error(' - Windows: Update Windows root certificates via Windows Update');
822
- console.error(' - macOS: Run: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain <certificate.pem>');
823
- console.error(' - Linux: Update ca-certificates package: sudo apt-get update && sudo apt-get install ca-certificates');
824
- console.error('');
825
- console.error('2. If you have a custom CA certificate file (e.g., from corporate proxy),');
826
- console.error(' you can specify it in your config (optional):');
827
- console.error(' reporter: [');
828
- console.error(' [\'@testlens/playwright-reporter\', {');
829
- console.error(' caCertificate: \'/path/to/your/ca-certificate.pem\'');
830
- console.error(' }]');
831
- console.error(' ]');
832
- console.error('');
833
- console.error('3. Contact your network administrator if you\'re behind a corporate proxy');
834
- console.error(' that uses a custom CA certificate.');
835
- console.error('');
836
- console.error('⚠️ WARNING: Setting NODE_TLS_REJECT_UNAUTHORIZED=0 disables SSL verification');
837
- console.error(' and is insecure. Only use this as a last resort in development.');
838
- console.error('');
839
- console.error('='.repeat(80));
840
- console.error('');
841
- }
842
- else if (status !== 403) {
843
- // Log other errors (but not 403 which we handled above)
844
- console.error(`❌ Failed to send ${payload.type} event to TestLens:`, {
845
- message: error?.message || 'Unknown error',
846
- status: status,
847
- statusText: error?.response?.statusText,
848
- data: errorData,
849
- code: error?.code,
850
- url: error?.config?.url,
851
- method: error?.config?.method
852
- });
853
- }
690
+ else if (status !== 403) {
691
+ // Log other errors (but not 403 which we handled above)
692
+ console.error(`❌ Failed to send ${payload.type} event to TestLens:`, {
693
+ message: error?.message || 'Unknown error',
694
+ status: status,
695
+ statusText: error?.response?.statusText,
696
+ data: errorData,
697
+ code: error?.code,
698
+ url: error?.config?.url,
699
+ method: error?.config?.method
700
+ });
854
701
  }
855
702
  // Don't throw error to avoid breaking test execution
856
703
  }
@@ -908,34 +755,53 @@ class TestLensReporter {
908
755
  }
909
756
  }
910
757
  // Upload to S3 first (pass DB ID if available for faster lookup)
911
- const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName, testCaseDbId);
912
- // Skip if upload failed or file was too large
913
- if (!s3Data) {
914
- console.log(`⏭️ [Test: ${testId.substring(0, 8)}...] Skipping artifact ${attachment.name} - upload failed or file too large`);
915
- continue;
916
- }
917
- const artifactData = {
918
- testId,
919
- type: this.getArtifactType(attachment.name),
920
- path: attachment.path,
921
- name: fileName,
922
- contentType: attachment.contentType,
923
- fileSize: this.getFileSize(attachment.path),
924
- storageType: 's3',
925
- s3Key: s3Data.key,
926
- s3Url: s3Data.url
927
- };
928
- // Send artifact data to API
929
- await this.sendToApi({
930
- type: 'artifact',
931
- runId: this.runId,
932
- timestamp: new Date().toISOString(),
933
- artifact: artifactData
758
+ // Create upload promise that we can track
759
+ const uploadPromise = Promise.resolve().then(async () => {
760
+ try {
761
+ if (!attachment.path) {
762
+ console.log(`⏭️ [Test: ${testId.substring(0, 8)}...] Skipping artifact ${attachment.name} - no file path`);
763
+ return;
764
+ }
765
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName, testCaseDbId);
766
+ // Skip if upload failed or file was too large
767
+ if (!s3Data) {
768
+ console.log(`⏭️ [Test: ${testId.substring(0, 8)}...] Skipping artifact ${attachment.name} - upload failed or file too large`);
769
+ return;
770
+ }
771
+ const artifactData = {
772
+ testId,
773
+ type: this.getArtifactType(attachment.name),
774
+ path: attachment.path,
775
+ name: fileName,
776
+ contentType: attachment.contentType,
777
+ fileSize: this.getFileSize(attachment.path),
778
+ storageType: 's3',
779
+ s3Key: s3Data.key,
780
+ s3Url: s3Data.url
781
+ };
782
+ // Send artifact data to API
783
+ await this.sendToApi({
784
+ type: 'artifact',
785
+ runId: this.runId,
786
+ timestamp: new Date().toISOString(),
787
+ artifact: artifactData
788
+ });
789
+ console.log(`📎 [Test: ${testId.substring(0, 8)}...] Processed artifact: ${fileName}`);
790
+ }
791
+ catch (error) {
792
+ console.error(`❌ [Test: ${testId.substring(0, 8)}...] Failed to process artifact ${attachment.name}:`, error.message);
793
+ }
794
+ });
795
+ // Track this upload and ensure cleanup on completion
796
+ this.pendingUploads.add(uploadPromise);
797
+ uploadPromise.finally(() => {
798
+ this.pendingUploads.delete(uploadPromise);
934
799
  });
935
- console.log(`📎 [Test: ${testId.substring(0, 8)}...] Processed artifact: ${fileName}`);
800
+ // Don't await here - let uploads happen in parallel
801
+ // They will be awaited in onEnd
936
802
  }
937
803
  catch (error) {
938
- console.error(`❌ [Test: ${testId.substring(0, 8)}...] Failed to process artifact ${attachment.name}:`, error.message);
804
+ console.error(`❌ [Test: ${testId.substring(0, 8)}...] Failed to setup artifact upload ${attachment.name}:`, error.message);
939
805
  }
940
806
  }
941
807
  }
@@ -953,8 +819,13 @@ class TestLensReporter {
953
819
  describe: block.describe // parent describe block name
954
820
  }));
955
821
  // Send to dedicated spec code blocks API endpoint
956
- const baseUrl = this.config.apiEndpoint.replace('/webhook/playwright', '');
957
- const specEndpoint = `${baseUrl}/webhook/playwright/spec-code-blocks`;
822
+ // Extract base URL - handle both full and partial endpoint patterns
823
+ let baseUrl = this.config.apiEndpoint.replace('/api/v1/webhook/playwright', '');
824
+ if (baseUrl === this.config.apiEndpoint) {
825
+ // Fallback: try alternative pattern if main pattern didn't match
826
+ baseUrl = this.config.apiEndpoint.replace('/webhook/playwright', '');
827
+ }
828
+ const specEndpoint = `${baseUrl}/api/v1/webhook/playwright/spec-code-blocks`;
958
829
  await this.axiosInstance.post(specEndpoint, {
959
830
  filePath: path.relative(process.cwd(), specPath),
960
831
  codeBlocks,
@@ -969,58 +840,7 @@ class TestLensReporter {
969
840
  console.log(`ℹ️ Spec code blocks already exist for: ${path.basename(specPath)} (skipped)`);
970
841
  return;
971
842
  }
972
- // Check for SSL certificate errors
973
- const isSslError = error?.code === 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY' ||
974
- error?.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' ||
975
- error?.code === 'CERT_HAS_EXPIRED' ||
976
- error?.code === 'SELF_SIGNED_CERT_IN_CHAIN' ||
977
- error?.message?.includes('certificate') ||
978
- error?.message?.includes('SSL') ||
979
- error?.message?.includes('TLS');
980
- if (isSslError) {
981
- console.error('\n' + '='.repeat(80));
982
- console.error('❌ SSL Certificate Error');
983
- console.error('='.repeat(80));
984
- console.error('');
985
- console.error('Failed to send spec code blocks due to SSL certificate issue.');
986
- console.error('');
987
- console.error('The reporter automatically attempts to detect and use system CA certificates.');
988
- console.error('If this error persists, it may indicate:');
989
- console.error(' - Missing or outdated system CA certificates');
990
- console.error(' - Corporate proxy with custom CA certificate');
991
- console.error(' - Incomplete certificate chain from the server');
992
- console.error('');
993
- console.error('Error details:');
994
- console.error(` Code: ${error?.code || 'Unknown'}`);
995
- console.error(` Message: ${error?.message || 'Unknown error'}`);
996
- console.error('');
997
- console.error('Possible solutions:');
998
- console.error('');
999
- console.error('1. Update your system\'s CA certificate store:');
1000
- console.error(' - Windows: Update Windows root certificates via Windows Update');
1001
- console.error(' - macOS: Run: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain <certificate.pem>');
1002
- console.error(' - Linux: Update ca-certificates package: sudo apt-get update && sudo apt-get install ca-certificates');
1003
- console.error('');
1004
- console.error('2. If you have a custom CA certificate file (e.g., from corporate proxy),');
1005
- console.error(' you can specify it in your config (optional):');
1006
- console.error(' reporter: [');
1007
- console.error(' [\'@testlens/playwright-reporter\', {');
1008
- console.error(' caCertificate: \'/path/to/your/ca-certificate.pem\'');
1009
- console.error(' }]');
1010
- console.error(' ]');
1011
- console.error('');
1012
- console.error('3. Contact your network administrator if you\'re behind a corporate proxy');
1013
- console.error(' that uses a custom CA certificate.');
1014
- console.error('');
1015
- console.error('⚠️ WARNING: Setting NODE_TLS_REJECT_UNAUTHORIZED=0 disables SSL verification');
1016
- console.error(' and is insecure. Only use this as a last resort in development.');
1017
- console.error('');
1018
- console.error('='.repeat(80));
1019
- console.error('');
1020
- }
1021
- else {
1022
- console.error('Failed to send spec code blocks:', errorData || error?.message || 'Unknown error');
1023
- }
843
+ console.error('Failed to send spec code blocks:', errorData || error?.message || 'Unknown error');
1024
844
  }
1025
845
  }
1026
846
  extractTestBlocks(filePath) {
@@ -1211,8 +1031,8 @@ class TestLensReporter {
1211
1031
  maxContentLength: Infinity,
1212
1032
  maxBodyLength: Infinity,
1213
1033
  timeout: Math.max(600000, Math.ceil(fileSize / (1024 * 1024)) * 10000), // 10s per MB, min 10 minutes - increased for large trace files
1214
- // Use the same HTTPS agent with CA certificates for S3 uploads
1215
- httpsAgent: this.axiosInstance.defaults.httpsAgent
1034
+ // Don't use custom HTTPS agent for S3 uploads
1035
+ httpsAgent: undefined
1216
1036
  });
1217
1037
  if (uploadResponse.status !== 200) {
1218
1038
  throw new Error(`S3 upload failed with status ${uploadResponse.status}`);
@@ -1258,7 +1078,7 @@ class TestLensReporter {
1258
1078
  const errorData = error?.response?.data;
1259
1079
  if (errorData?.error === 'trial_expired' || errorData?.error === 'subscription_inactive' ||
1260
1080
  errorData?.error === 'test_cases_limit_reached' || errorData?.error === 'test_runs_limit_reached') {
1261
- console.error('\\n' + '='.repeat(80));
1081
+ console.error('\n' + '='.repeat(80));
1262
1082
  if (errorData?.error === 'test_cases_limit_reached') {
1263
1083
  console.error('❌ TESTLENS ERROR: Test Cases Limit Reached');
1264
1084
  }
@@ -1337,24 +1157,8 @@ class TestLensReporter {
1337
1157
  };
1338
1158
  return contentTypes[ext] || 'application/octet-stream';
1339
1159
  }
1340
- generateS3Key(runId, testId, fileName) {
1341
- const date = new Date().toISOString().slice(0, 10);
1342
- const safeTestId = this.sanitizeForS3(testId);
1343
- const safeFileName = this.sanitizeForS3(fileName);
1344
- const ext = path.extname(fileName);
1345
- const baseName = path.basename(fileName, ext);
1346
- return `test-artifacts/${date}/${runId}/${safeTestId}/${safeFileName}${ext}`;
1347
- }
1348
- sanitizeForS3(value) {
1349
- return value
1350
- .replace(/[\/:*?"<>|]/g, '-')
1351
- .replace(/[-\u001f\u007f]/g, '-')
1352
- .replace(/[^-~]/g, '-')
1353
- .replace(/\s+/g, '-')
1354
- .replace(/[_]/g, '-')
1355
- .replace(/-+/g, '-')
1356
- .replace(/^-|-$/g, '');
1357
- }
1160
+ // Note: S3 key generation and sanitization are handled server-side
1161
+ // generateS3Key() and sanitizeForS3() methods removed as they were not used
1358
1162
  getFileSize(filePath) {
1359
1163
  try {
1360
1164
  const stats = fs.statSync(filePath);