testlens-playwright-reporter 0.1.2 → 0.1.5
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 +89 -32
- package/index.ts +94 -35
- package/lib/index.js +84 -32
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -56,12 +56,12 @@ class TestLensReporter {
|
|
|
56
56
|
'Content-Type': 'application/json',
|
|
57
57
|
...(this.config.apiKey && { 'X-API-Key': this.config.apiKey }),
|
|
58
58
|
},
|
|
59
|
-
// Enhanced SSL handling
|
|
59
|
+
// Enhanced SSL handling with flexible TLS configuration
|
|
60
60
|
httpsAgent: new https.Agent({
|
|
61
61
|
rejectUnauthorized: rejectUnauthorized,
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
// Allow any TLS version for better compatibility
|
|
63
|
+
minVersion: 'TLSv1.2',
|
|
64
|
+
maxVersion: 'TLSv1.3'
|
|
65
65
|
})
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -589,51 +589,108 @@ class TestLensReporter {
|
|
|
589
589
|
|
|
590
590
|
async uploadArtifactToS3(filePath, testId, fileName) {
|
|
591
591
|
try {
|
|
592
|
-
//
|
|
592
|
+
// Check file size first
|
|
593
|
+
const fileSize = this.getFileSize(filePath);
|
|
594
|
+
const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);
|
|
595
|
+
|
|
596
|
+
console.log(`📤 Uploading ${fileName} (${fileSizeMB}MB) directly to S3...`);
|
|
597
|
+
|
|
593
598
|
const baseUrl = this.config.apiEndpoint.replace('/api/v1/webhook/playwright', '');
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
599
|
+
|
|
600
|
+
// Step 1: Request pre-signed URL from server
|
|
601
|
+
const presignedUrlEndpoint = `${baseUrl}/api/v1/artifacts/public/presigned-url`;
|
|
602
|
+
const presignedResponse = await this.axiosInstance.post(presignedUrlEndpoint, {
|
|
603
|
+
apiKey: this.config.apiKey,
|
|
604
|
+
testRunId: this.runId,
|
|
605
|
+
testId: testId,
|
|
606
|
+
fileName: fileName,
|
|
607
|
+
fileType: this.getContentType(fileName),
|
|
608
|
+
fileSize: fileSize,
|
|
609
|
+
artifactType: this.getArtifactType(fileName)
|
|
610
|
+
}, {
|
|
611
|
+
timeout: 10000 // Quick timeout for metadata request
|
|
607
612
|
});
|
|
608
613
|
|
|
609
|
-
|
|
614
|
+
if (!presignedResponse.data.success) {
|
|
615
|
+
throw new Error(`Failed to get presigned URL: ${presignedResponse.data.error || 'Unknown error'}`);
|
|
616
|
+
}
|
|
610
617
|
|
|
611
|
-
|
|
612
|
-
|
|
618
|
+
const { uploadUrl, s3Key, metadata } = presignedResponse.data;
|
|
619
|
+
|
|
620
|
+
// Step 2: Upload directly to S3 using presigned URL
|
|
621
|
+
console.log(`⬆️ Uploading ${fileName} directly to S3 (bypass server)...`);
|
|
622
|
+
|
|
623
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
624
|
+
|
|
625
|
+
// IMPORTANT: When using presigned URLs, we MUST include exactly the headers that were signed
|
|
626
|
+
// The backend signs with ServerSideEncryption:'AES256', so we must send that header
|
|
627
|
+
// AWS presigned URLs are very strict about header matching
|
|
628
|
+
const uploadResponse = await axios.put(uploadUrl, fileBuffer, {
|
|
613
629
|
headers: {
|
|
614
|
-
|
|
615
|
-
// Override content-type to let form-data set it
|
|
616
|
-
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
|
|
630
|
+
'x-amz-server-side-encryption': 'AES256'
|
|
617
631
|
},
|
|
618
|
-
|
|
632
|
+
maxContentLength: Infinity,
|
|
633
|
+
maxBodyLength: Infinity,
|
|
634
|
+
timeout: Math.max(300000, Math.ceil(fileSize / (1024 * 1024)) * 5000), // 5s per MB, min 5 minutes
|
|
635
|
+
// Don't use custom HTTPS agent for S3 uploads
|
|
636
|
+
httpsAgent: undefined
|
|
619
637
|
});
|
|
620
638
|
|
|
621
|
-
if (
|
|
622
|
-
|
|
623
|
-
|
|
639
|
+
if (uploadResponse.status !== 200) {
|
|
640
|
+
throw new Error(`S3 upload failed with status ${uploadResponse.status}`);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
console.log(`✅ S3 direct upload completed for ${fileName}`);
|
|
644
|
+
|
|
645
|
+
// Step 3: Confirm upload with server to save metadata
|
|
646
|
+
const confirmEndpoint = `${baseUrl}/api/v1/artifacts/public/confirm-upload`;
|
|
647
|
+
const confirmResponse = await this.axiosInstance.post(confirmEndpoint, {
|
|
648
|
+
apiKey: this.config.apiKey,
|
|
649
|
+
testRunId: this.runId,
|
|
650
|
+
testId: testId,
|
|
651
|
+
s3Key: s3Key,
|
|
652
|
+
fileName: fileName,
|
|
653
|
+
fileType: this.getContentType(fileName),
|
|
654
|
+
fileSize: fileSize,
|
|
655
|
+
artifactType: this.getArtifactType(fileName)
|
|
656
|
+
}, {
|
|
657
|
+
timeout: 10000
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
if (confirmResponse.status === 201 && confirmResponse.data.success) {
|
|
661
|
+
const artifact = confirmResponse.data.artifact;
|
|
662
|
+
console.log(`✅ Upload confirmed and saved to database`);
|
|
624
663
|
return {
|
|
625
|
-
key:
|
|
664
|
+
key: s3Key,
|
|
626
665
|
url: artifact.s3Url,
|
|
627
666
|
presignedUrl: artifact.presignedUrl,
|
|
628
667
|
fileSize: artifact.fileSize,
|
|
629
668
|
contentType: artifact.contentType
|
|
630
669
|
};
|
|
631
670
|
} else {
|
|
632
|
-
throw new Error(`Upload failed: ${
|
|
671
|
+
throw new Error(`Upload confirmation failed: ${confirmResponse.data.error || 'Unknown error'}`);
|
|
633
672
|
}
|
|
634
673
|
} catch (error) {
|
|
635
|
-
|
|
636
|
-
|
|
674
|
+
// Better error messages for common issues
|
|
675
|
+
let errorMsg = error.message;
|
|
676
|
+
|
|
677
|
+
if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
|
|
678
|
+
errorMsg = `Upload timeout - file may be too large or connection is slow`;
|
|
679
|
+
} else if (error.response?.status === 413) {
|
|
680
|
+
errorMsg = `File too large (413) - server rejected the upload`;
|
|
681
|
+
} else if (error.response?.status === 400) {
|
|
682
|
+
errorMsg = `Bad request (400) - ${error.response.data?.error || 'check file format'}`;
|
|
683
|
+
} else if (error.response?.status === 403) {
|
|
684
|
+
errorMsg = `Access denied (403) - presigned URL may have expired`;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
console.error(`❌ Failed to upload ${fileName} to S3:`, errorMsg);
|
|
688
|
+
if (error.response?.data) {
|
|
689
|
+
console.error('Error details:', error.response.data);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Don't throw, just return null to continue with other artifacts
|
|
693
|
+
return null;
|
|
637
694
|
}
|
|
638
695
|
}
|
|
639
696
|
|
package/index.ts
CHANGED
|
@@ -185,12 +185,12 @@ export class TestLensReporter implements Reporter {
|
|
|
185
185
|
'Content-Type': 'application/json',
|
|
186
186
|
...(this.config.apiKey && { 'X-API-Key': this.config.apiKey }),
|
|
187
187
|
},
|
|
188
|
-
// Enhanced SSL handling
|
|
188
|
+
// Enhanced SSL handling with flexible TLS configuration
|
|
189
189
|
httpsAgent: new https.Agent({
|
|
190
190
|
rejectUnauthorized: rejectUnauthorized,
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
// Allow any TLS version for better compatibility
|
|
192
|
+
minVersion: 'TLSv1.2',
|
|
193
|
+
maxVersion: 'TLSv1.3'
|
|
194
194
|
})
|
|
195
195
|
});
|
|
196
196
|
|
|
@@ -472,6 +472,12 @@ export class TestLensReporter implements Reporter {
|
|
|
472
472
|
// Upload to S3 first
|
|
473
473
|
const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
|
|
474
474
|
|
|
475
|
+
// Skip if upload failed or file was too large
|
|
476
|
+
if (!s3Data) {
|
|
477
|
+
console.log(`⏭️ Skipping artifact ${attachment.name} - upload failed or file too large`);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
|
|
475
481
|
const artifactData = {
|
|
476
482
|
testId,
|
|
477
483
|
type: this.getArtifactType(attachment.name),
|
|
@@ -668,44 +674,79 @@ export class TestLensReporter implements Reporter {
|
|
|
668
674
|
}
|
|
669
675
|
|
|
670
676
|
|
|
671
|
-
private async uploadArtifactToS3(filePath: string, testId: string, fileName: string): Promise<{ key: string; url: string; presignedUrl: string; fileSize: number; contentType: string }> {
|
|
677
|
+
private async uploadArtifactToS3(filePath: string, testId: string, fileName: string): Promise<{ key: string; url: string; presignedUrl: string; fileSize: number; contentType: string } | null> {
|
|
672
678
|
try {
|
|
673
|
-
//
|
|
679
|
+
// Check file size first
|
|
680
|
+
const fileSize = this.getFileSize(filePath);
|
|
681
|
+
const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);
|
|
682
|
+
|
|
683
|
+
console.log(`📤 Uploading ${fileName} (${fileSizeMB}MB) directly to S3...`);
|
|
684
|
+
|
|
674
685
|
const baseUrl = this.config.apiEndpoint.replace('/api/v1/webhook/playwright', '');
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
686
|
+
|
|
687
|
+
// Step 1: Request pre-signed URL from server
|
|
688
|
+
const presignedUrlEndpoint = `${baseUrl}/api/v1/artifacts/public/presigned-url`;
|
|
689
|
+
const presignedResponse = await this.axiosInstance.post(presignedUrlEndpoint, {
|
|
690
|
+
apiKey: this.config.apiKey,
|
|
691
|
+
testRunId: this.runId,
|
|
692
|
+
testId: testId,
|
|
693
|
+
fileName: fileName,
|
|
694
|
+
fileType: this.getContentType(fileName),
|
|
695
|
+
fileSize: fileSize,
|
|
696
|
+
artifactType: this.getArtifactType(fileName)
|
|
697
|
+
}, {
|
|
698
|
+
timeout: 10000 // Quick timeout for metadata request
|
|
688
699
|
});
|
|
689
700
|
|
|
690
|
-
|
|
701
|
+
if (!presignedResponse.data.success) {
|
|
702
|
+
throw new Error(`Failed to get presigned URL: ${presignedResponse.data.error || 'Unknown error'}`);
|
|
703
|
+
}
|
|
691
704
|
|
|
692
|
-
|
|
693
|
-
|
|
705
|
+
const { uploadUrl, s3Key, metadata } = presignedResponse.data;
|
|
706
|
+
|
|
707
|
+
// Step 2: Upload directly to S3 using presigned URL
|
|
708
|
+
console.log(`⬆️ Uploading ${fileName} directly to S3 (bypass server)...`);
|
|
709
|
+
|
|
710
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
711
|
+
|
|
712
|
+
// IMPORTANT: When using presigned URLs, we MUST include exactly the headers that were signed
|
|
713
|
+
// The backend signs with ServerSideEncryption:'AES256', so we must send that header
|
|
714
|
+
// AWS presigned URLs are very strict about header matching
|
|
715
|
+
const uploadResponse = await axios.put(uploadUrl, fileBuffer, {
|
|
694
716
|
headers: {
|
|
695
|
-
|
|
696
|
-
// Override content-type to let form-data set it
|
|
697
|
-
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
|
|
717
|
+
'x-amz-server-side-encryption': 'AES256'
|
|
698
718
|
},
|
|
699
|
-
|
|
719
|
+
maxContentLength: Infinity,
|
|
720
|
+
maxBodyLength: Infinity,
|
|
721
|
+
timeout: Math.max(300000, Math.ceil(fileSize / (1024 * 1024)) * 5000), // 5s per MB, min 5 minutes
|
|
722
|
+
// Don't use custom HTTPS agent for S3 uploads
|
|
723
|
+
httpsAgent: undefined
|
|
700
724
|
});
|
|
701
725
|
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
726
|
+
if (uploadResponse.status !== 200) {
|
|
727
|
+
throw new Error(`S3 upload failed with status ${uploadResponse.status}`);
|
|
728
|
+
}
|
|
705
729
|
|
|
706
|
-
|
|
707
|
-
|
|
730
|
+
console.log(`✅ S3 direct upload completed for ${fileName}`);
|
|
731
|
+
|
|
732
|
+
// Step 3: Confirm upload with server to save metadata
|
|
733
|
+
const confirmEndpoint = `${baseUrl}/api/v1/artifacts/public/confirm-upload`;
|
|
734
|
+
const confirmResponse = await this.axiosInstance.post(confirmEndpoint, {
|
|
735
|
+
apiKey: this.config.apiKey,
|
|
736
|
+
testRunId: this.runId,
|
|
737
|
+
testId: testId,
|
|
738
|
+
s3Key: s3Key,
|
|
739
|
+
fileName: fileName,
|
|
740
|
+
fileType: this.getContentType(fileName),
|
|
741
|
+
fileSize: fileSize,
|
|
742
|
+
artifactType: this.getArtifactType(fileName)
|
|
743
|
+
}, {
|
|
744
|
+
timeout: 10000
|
|
745
|
+
});
|
|
708
746
|
|
|
747
|
+
if (confirmResponse.status === 201 && confirmResponse.data.success) {
|
|
748
|
+
const artifact = confirmResponse.data.artifact;
|
|
749
|
+
console.log(`✅ Upload confirmed and saved to database`);
|
|
709
750
|
return {
|
|
710
751
|
key: s3Key,
|
|
711
752
|
url: artifact.s3Url,
|
|
@@ -714,11 +755,29 @@ export class TestLensReporter implements Reporter {
|
|
|
714
755
|
contentType: artifact.contentType
|
|
715
756
|
};
|
|
716
757
|
} else {
|
|
717
|
-
throw new Error(`Upload failed: ${
|
|
758
|
+
throw new Error(`Upload confirmation failed: ${confirmResponse.data.error || 'Unknown error'}`);
|
|
718
759
|
}
|
|
719
|
-
} catch (error) {
|
|
720
|
-
|
|
721
|
-
|
|
760
|
+
} catch (error: any) {
|
|
761
|
+
// Better error messages for common issues
|
|
762
|
+
let errorMsg = error.message;
|
|
763
|
+
|
|
764
|
+
if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
|
|
765
|
+
errorMsg = `Upload timeout - file may be too large or connection is slow`;
|
|
766
|
+
} else if (error.response?.status === 413) {
|
|
767
|
+
errorMsg = `File too large (413) - server rejected the upload`;
|
|
768
|
+
} else if (error.response?.status === 400) {
|
|
769
|
+
errorMsg = `Bad request (400) - ${error.response.data?.error || 'check file format'}`;
|
|
770
|
+
} else if (error.response?.status === 403) {
|
|
771
|
+
errorMsg = `Access denied (403) - presigned URL may have expired`;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
console.error(`❌ Failed to upload ${fileName} to S3:`, errorMsg);
|
|
775
|
+
if (error.response?.data) {
|
|
776
|
+
console.error('Error details:', error.response.data);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Don't throw, just return null to continue with other artifacts
|
|
780
|
+
return null;
|
|
722
781
|
}
|
|
723
782
|
}
|
|
724
783
|
|
package/lib/index.js
CHANGED
|
@@ -45,7 +45,6 @@ const https = __importStar(require("https"));
|
|
|
45
45
|
const axios_1 = __importDefault(require("axios"));
|
|
46
46
|
const child_process_1 = require("child_process");
|
|
47
47
|
const mime = __importStar(require("mime"));
|
|
48
|
-
const form_data_1 = __importDefault(require("form-data"));
|
|
49
48
|
class TestLensReporter {
|
|
50
49
|
constructor(options) {
|
|
51
50
|
this.config = {
|
|
@@ -93,12 +92,12 @@ class TestLensReporter {
|
|
|
93
92
|
'Content-Type': 'application/json',
|
|
94
93
|
...(this.config.apiKey && { 'X-API-Key': this.config.apiKey }),
|
|
95
94
|
},
|
|
96
|
-
// Enhanced SSL handling
|
|
95
|
+
// Enhanced SSL handling with flexible TLS configuration
|
|
97
96
|
httpsAgent: new https.Agent({
|
|
98
97
|
rejectUnauthorized: rejectUnauthorized,
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
// Allow any TLS version for better compatibility
|
|
99
|
+
minVersion: 'TLSv1.2',
|
|
100
|
+
maxVersion: 'TLSv1.3'
|
|
102
101
|
})
|
|
103
102
|
});
|
|
104
103
|
// Add retry interceptor
|
|
@@ -342,6 +341,11 @@ class TestLensReporter {
|
|
|
342
341
|
try {
|
|
343
342
|
// Upload to S3 first
|
|
344
343
|
const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
|
|
344
|
+
// Skip if upload failed or file was too large
|
|
345
|
+
if (!s3Data) {
|
|
346
|
+
console.log(`⏭️ Skipping artifact ${attachment.name} - upload failed or file too large`);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
345
349
|
const artifactData = {
|
|
346
350
|
testId,
|
|
347
351
|
type: this.getArtifactType(attachment.name),
|
|
@@ -521,35 +525,65 @@ class TestLensReporter {
|
|
|
521
525
|
}
|
|
522
526
|
async uploadArtifactToS3(filePath, testId, fileName) {
|
|
523
527
|
try {
|
|
524
|
-
//
|
|
528
|
+
// Check file size first
|
|
529
|
+
const fileSize = this.getFileSize(filePath);
|
|
530
|
+
const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);
|
|
531
|
+
console.log(`📤 Uploading ${fileName} (${fileSizeMB}MB) directly to S3...`);
|
|
525
532
|
const baseUrl = this.config.apiEndpoint.replace('/api/v1/webhook/playwright', '');
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
533
|
+
// Step 1: Request pre-signed URL from server
|
|
534
|
+
const presignedUrlEndpoint = `${baseUrl}/api/v1/artifacts/public/presigned-url`;
|
|
535
|
+
const presignedResponse = await this.axiosInstance.post(presignedUrlEndpoint, {
|
|
536
|
+
apiKey: this.config.apiKey,
|
|
537
|
+
testRunId: this.runId,
|
|
538
|
+
testId: testId,
|
|
539
|
+
fileName: fileName,
|
|
540
|
+
fileType: this.getContentType(fileName),
|
|
541
|
+
fileSize: fileSize,
|
|
542
|
+
artifactType: this.getArtifactType(fileName)
|
|
543
|
+
}, {
|
|
544
|
+
timeout: 10000 // Quick timeout for metadata request
|
|
537
545
|
});
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
546
|
+
if (!presignedResponse.data.success) {
|
|
547
|
+
throw new Error(`Failed to get presigned URL: ${presignedResponse.data.error || 'Unknown error'}`);
|
|
548
|
+
}
|
|
549
|
+
const { uploadUrl, s3Key, metadata } = presignedResponse.data;
|
|
550
|
+
// Step 2: Upload directly to S3 using presigned URL
|
|
551
|
+
console.log(`⬆️ Uploading ${fileName} directly to S3 (bypass server)...`);
|
|
552
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
553
|
+
// IMPORTANT: When using presigned URLs, we MUST include exactly the headers that were signed
|
|
554
|
+
// The backend signs with ServerSideEncryption:'AES256', so we must send that header
|
|
555
|
+
// AWS presigned URLs are very strict about header matching
|
|
556
|
+
const uploadResponse = await axios_1.default.put(uploadUrl, fileBuffer, {
|
|
541
557
|
headers: {
|
|
542
|
-
|
|
543
|
-
// Override content-type to let form-data set it
|
|
544
|
-
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
|
|
558
|
+
'x-amz-server-side-encryption': 'AES256'
|
|
545
559
|
},
|
|
546
|
-
|
|
560
|
+
maxContentLength: Infinity,
|
|
561
|
+
maxBodyLength: Infinity,
|
|
562
|
+
timeout: Math.max(300000, Math.ceil(fileSize / (1024 * 1024)) * 5000), // 5s per MB, min 5 minutes
|
|
563
|
+
// Don't use custom HTTPS agent for S3 uploads
|
|
564
|
+
httpsAgent: undefined
|
|
547
565
|
});
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
566
|
+
if (uploadResponse.status !== 200) {
|
|
567
|
+
throw new Error(`S3 upload failed with status ${uploadResponse.status}`);
|
|
568
|
+
}
|
|
569
|
+
console.log(`✅ S3 direct upload completed for ${fileName}`);
|
|
570
|
+
// Step 3: Confirm upload with server to save metadata
|
|
571
|
+
const confirmEndpoint = `${baseUrl}/api/v1/artifacts/public/confirm-upload`;
|
|
572
|
+
const confirmResponse = await this.axiosInstance.post(confirmEndpoint, {
|
|
573
|
+
apiKey: this.config.apiKey,
|
|
574
|
+
testRunId: this.runId,
|
|
575
|
+
testId: testId,
|
|
576
|
+
s3Key: s3Key,
|
|
577
|
+
fileName: fileName,
|
|
578
|
+
fileType: this.getContentType(fileName),
|
|
579
|
+
fileSize: fileSize,
|
|
580
|
+
artifactType: this.getArtifactType(fileName)
|
|
581
|
+
}, {
|
|
582
|
+
timeout: 10000
|
|
583
|
+
});
|
|
584
|
+
if (confirmResponse.status === 201 && confirmResponse.data.success) {
|
|
585
|
+
const artifact = confirmResponse.data.artifact;
|
|
586
|
+
console.log(`✅ Upload confirmed and saved to database`);
|
|
553
587
|
return {
|
|
554
588
|
key: s3Key,
|
|
555
589
|
url: artifact.s3Url,
|
|
@@ -559,12 +593,30 @@ class TestLensReporter {
|
|
|
559
593
|
};
|
|
560
594
|
}
|
|
561
595
|
else {
|
|
562
|
-
throw new Error(`Upload failed: ${
|
|
596
|
+
throw new Error(`Upload confirmation failed: ${confirmResponse.data.error || 'Unknown error'}`);
|
|
563
597
|
}
|
|
564
598
|
}
|
|
565
599
|
catch (error) {
|
|
566
|
-
|
|
567
|
-
|
|
600
|
+
// Better error messages for common issues
|
|
601
|
+
let errorMsg = error.message;
|
|
602
|
+
if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
|
|
603
|
+
errorMsg = `Upload timeout - file may be too large or connection is slow`;
|
|
604
|
+
}
|
|
605
|
+
else if (error.response?.status === 413) {
|
|
606
|
+
errorMsg = `File too large (413) - server rejected the upload`;
|
|
607
|
+
}
|
|
608
|
+
else if (error.response?.status === 400) {
|
|
609
|
+
errorMsg = `Bad request (400) - ${error.response.data?.error || 'check file format'}`;
|
|
610
|
+
}
|
|
611
|
+
else if (error.response?.status === 403) {
|
|
612
|
+
errorMsg = `Access denied (403) - presigned URL may have expired`;
|
|
613
|
+
}
|
|
614
|
+
console.error(`❌ Failed to upload ${fileName} to S3:`, errorMsg);
|
|
615
|
+
if (error.response?.data) {
|
|
616
|
+
console.error('Error details:', error.response.data);
|
|
617
|
+
}
|
|
618
|
+
// Don't throw, just return null to continue with other artifacts
|
|
619
|
+
return null;
|
|
568
620
|
}
|
|
569
621
|
}
|
|
570
622
|
getContentType(fileName) {
|
package/package.json
CHANGED