testlens-playwright-reporter 0.2.3 → 0.2.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 +46 -34
- package/index.ts +42 -6
- package/lib/index.js +41 -6
- package/package.json +2 -2
- package/CHANGELOG.md +0 -15
package/index.js
CHANGED
|
@@ -6,12 +6,23 @@ const path = require('path');
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const https = require('https');
|
|
8
8
|
const axios = require('axios');
|
|
9
|
-
const mime = require('mime');
|
|
10
9
|
const FormData = require('form-data');
|
|
10
|
+
|
|
11
|
+
// Lazy-load mime module to support ESM
|
|
12
|
+
let mimeModule = null;
|
|
13
|
+
async function getMime() {
|
|
14
|
+
if (!mimeModule) {
|
|
15
|
+
const imported = await import('mime');
|
|
16
|
+
// Handle both default export and named exports
|
|
17
|
+
mimeModule = imported.default || imported;
|
|
18
|
+
}
|
|
19
|
+
return mimeModule;
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
class TestLensReporter {
|
|
12
23
|
constructor(options = {}) {
|
|
13
24
|
this.config = {
|
|
14
|
-
apiEndpoint: 'https://testlens.qa-path.com/api/v1/webhook/playwright',
|
|
25
|
+
apiEndpoint: options.apiEndpoint || 'https://testlens.qa-path.com/api/v1/webhook/playwright',
|
|
15
26
|
apiKey: options.apiKey, // API key must come from config file
|
|
16
27
|
enableRealTimeStream: options.enableRealTimeStream !== undefined ? options.enableRealTimeStream : true,
|
|
17
28
|
enableGitInfo: options.enableGitInfo !== undefined ? options.enableGitInfo : true,
|
|
@@ -371,20 +382,14 @@ class TestLensReporter {
|
|
|
371
382
|
try {
|
|
372
383
|
console.log(`📤 Processing ${attachment.name} asynchronously...`);
|
|
373
384
|
|
|
374
|
-
// Preserve original file extension from path (important for traces which must be .zip)
|
|
375
|
-
const originalExt = path.extname(attachment.path);
|
|
376
|
-
const fileName = attachment.name.includes(originalExt)
|
|
377
|
-
? attachment.name
|
|
378
|
-
: attachment.name + originalExt;
|
|
379
|
-
|
|
380
385
|
// Upload to S3 first
|
|
381
|
-
const s3Data = await this.uploadArtifactToS3(attachment.path, testId,
|
|
386
|
+
const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
|
|
382
387
|
|
|
383
388
|
const artifactData = {
|
|
384
389
|
testId,
|
|
385
|
-
type: this.getArtifactType(
|
|
390
|
+
type: this.getArtifactType(attachment.name),
|
|
386
391
|
path: attachment.path,
|
|
387
|
-
name:
|
|
392
|
+
name: attachment.name,
|
|
388
393
|
contentType: attachment.contentType,
|
|
389
394
|
fileSize: this.getFileSize(attachment.path),
|
|
390
395
|
storageType: 's3',
|
|
@@ -579,24 +584,6 @@ class TestLensReporter {
|
|
|
579
584
|
return `${test.location.file}:${test.location.line}:${cleanTitle}`;
|
|
580
585
|
}
|
|
581
586
|
|
|
582
|
-
getContentType(ext) {
|
|
583
|
-
const contentTypes = {
|
|
584
|
-
'.mp4': 'video/mp4',
|
|
585
|
-
'.webm': 'video/webm',
|
|
586
|
-
'.png': 'image/png',
|
|
587
|
-
'.jpg': 'image/jpeg',
|
|
588
|
-
'.jpeg': 'image/jpeg',
|
|
589
|
-
'.gif': 'image/gif',
|
|
590
|
-
'.json': 'application/json',
|
|
591
|
-
'.txt': 'text/plain',
|
|
592
|
-
'.html': 'text/html',
|
|
593
|
-
'.xml': 'application/xml',
|
|
594
|
-
'.zip': 'application/zip',
|
|
595
|
-
'.pdf': 'application/pdf'
|
|
596
|
-
};
|
|
597
|
-
return contentTypes[ext] || 'application/octet-stream';
|
|
598
|
-
}
|
|
599
|
-
|
|
600
587
|
getFileSize(filePath) {
|
|
601
588
|
try {
|
|
602
589
|
const stats = fs.statSync(filePath);
|
|
@@ -624,7 +611,7 @@ class TestLensReporter {
|
|
|
624
611
|
testRunId: this.runId,
|
|
625
612
|
testId: testId,
|
|
626
613
|
fileName: fileName,
|
|
627
|
-
fileType: this.getContentType(fileName),
|
|
614
|
+
fileType: await this.getContentType(fileName),
|
|
628
615
|
fileSize: fileSize,
|
|
629
616
|
artifactType: this.getArtifactType(fileName)
|
|
630
617
|
}, {
|
|
@@ -670,7 +657,7 @@ class TestLensReporter {
|
|
|
670
657
|
testId: testId,
|
|
671
658
|
s3Key: s3Key,
|
|
672
659
|
fileName: fileName,
|
|
673
|
-
fileType: this.getContentType(fileName),
|
|
660
|
+
fileType: await this.getContentType(fileName),
|
|
674
661
|
fileSize: fileSize,
|
|
675
662
|
artifactType: this.getArtifactType(fileName)
|
|
676
663
|
}, {
|
|
@@ -714,10 +701,35 @@ class TestLensReporter {
|
|
|
714
701
|
}
|
|
715
702
|
}
|
|
716
703
|
|
|
717
|
-
getContentType(fileName) {
|
|
704
|
+
async getContentType(fileName) {
|
|
718
705
|
const ext = path.extname(fileName).toLowerCase();
|
|
719
|
-
|
|
720
|
-
|
|
706
|
+
try {
|
|
707
|
+
const mime = await getMime();
|
|
708
|
+
// Try different ways to access getType method
|
|
709
|
+
const getType = mime.getType || mime.default?.getType;
|
|
710
|
+
if (typeof getType === 'function') {
|
|
711
|
+
const mimeType = getType.call(mime, ext) || getType.call(mime.default, ext);
|
|
712
|
+
return mimeType || 'application/octet-stream';
|
|
713
|
+
}
|
|
714
|
+
} catch (error) {
|
|
715
|
+
console.warn(`Failed to get MIME type for ${fileName}:`, error.message);
|
|
716
|
+
}
|
|
717
|
+
// Fallback to basic content type mapping
|
|
718
|
+
const contentTypes = {
|
|
719
|
+
'.mp4': 'video/mp4',
|
|
720
|
+
'.webm': 'video/webm',
|
|
721
|
+
'.png': 'image/png',
|
|
722
|
+
'.jpg': 'image/jpeg',
|
|
723
|
+
'.jpeg': 'image/jpeg',
|
|
724
|
+
'.gif': 'image/gif',
|
|
725
|
+
'.json': 'application/json',
|
|
726
|
+
'.txt': 'text/plain',
|
|
727
|
+
'.html': 'text/html',
|
|
728
|
+
'.xml': 'application/xml',
|
|
729
|
+
'.zip': 'application/zip',
|
|
730
|
+
'.pdf': 'application/pdf'
|
|
731
|
+
};
|
|
732
|
+
return contentTypes[ext] || 'application/octet-stream';
|
|
721
733
|
}
|
|
722
734
|
}
|
|
723
735
|
|
package/index.ts
CHANGED
|
@@ -8,8 +8,19 @@ import * as https from 'https';
|
|
|
8
8
|
import axios, { AxiosInstance } from 'axios';
|
|
9
9
|
import type { Reporter, TestCase, TestResult, FullConfig, Suite } from '@playwright/test/reporter';
|
|
10
10
|
import { execSync } from 'child_process';
|
|
11
|
-
import * as mime from 'mime';
|
|
12
11
|
import FormData from 'form-data';
|
|
12
|
+
|
|
13
|
+
// Lazy-load mime module to support ESM
|
|
14
|
+
let mimeModule: any = null;
|
|
15
|
+
async function getMime() {
|
|
16
|
+
if (!mimeModule) {
|
|
17
|
+
const imported = await import('mime');
|
|
18
|
+
// Handle both default export and named exports
|
|
19
|
+
mimeModule = imported.default || imported;
|
|
20
|
+
}
|
|
21
|
+
return mimeModule;
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
export interface TestLensReporterConfig {
|
|
14
25
|
/** TestLens API endpoint URL */
|
|
15
26
|
apiEndpoint?: string;
|
|
@@ -705,7 +716,7 @@ export class TestLensReporter implements Reporter {
|
|
|
705
716
|
testRunId: this.runId,
|
|
706
717
|
testId: testId,
|
|
707
718
|
fileName: fileName,
|
|
708
|
-
fileType: this.getContentType(fileName),
|
|
719
|
+
fileType: await this.getContentType(fileName),
|
|
709
720
|
fileSize: fileSize,
|
|
710
721
|
artifactType: this.getArtifactType(fileName)
|
|
711
722
|
}, {
|
|
@@ -751,7 +762,7 @@ export class TestLensReporter implements Reporter {
|
|
|
751
762
|
testId: testId,
|
|
752
763
|
s3Key: s3Key,
|
|
753
764
|
fileName: fileName,
|
|
754
|
-
fileType: this.getContentType(fileName),
|
|
765
|
+
fileType: await this.getContentType(fileName),
|
|
755
766
|
fileSize: fileSize,
|
|
756
767
|
artifactType: this.getArtifactType(fileName)
|
|
757
768
|
}, {
|
|
@@ -795,10 +806,35 @@ export class TestLensReporter implements Reporter {
|
|
|
795
806
|
}
|
|
796
807
|
}
|
|
797
808
|
|
|
798
|
-
private getContentType(fileName: string): string {
|
|
809
|
+
private async getContentType(fileName: string): Promise<string> {
|
|
799
810
|
const ext = path.extname(fileName).toLowerCase();
|
|
800
|
-
|
|
801
|
-
|
|
811
|
+
try {
|
|
812
|
+
const mime = await getMime();
|
|
813
|
+
// Try different ways to access getType method
|
|
814
|
+
const getType = mime.getType || mime.default?.getType;
|
|
815
|
+
if (typeof getType === 'function') {
|
|
816
|
+
const mimeType = getType.call(mime, ext) || getType.call(mime.default, ext);
|
|
817
|
+
return mimeType || 'application/octet-stream';
|
|
818
|
+
}
|
|
819
|
+
} catch (error: any) {
|
|
820
|
+
console.warn(`Failed to get MIME type for ${fileName}:`, error.message);
|
|
821
|
+
}
|
|
822
|
+
// Fallback to basic content type mapping
|
|
823
|
+
const contentTypes: Record<string, string> = {
|
|
824
|
+
'.mp4': 'video/mp4',
|
|
825
|
+
'.webm': 'video/webm',
|
|
826
|
+
'.png': 'image/png',
|
|
827
|
+
'.jpg': 'image/jpeg',
|
|
828
|
+
'.jpeg': 'image/jpeg',
|
|
829
|
+
'.gif': 'image/gif',
|
|
830
|
+
'.json': 'application/json',
|
|
831
|
+
'.txt': 'text/plain',
|
|
832
|
+
'.html': 'text/html',
|
|
833
|
+
'.xml': 'application/xml',
|
|
834
|
+
'.zip': 'application/zip',
|
|
835
|
+
'.pdf': 'application/pdf'
|
|
836
|
+
};
|
|
837
|
+
return contentTypes[ext] || 'application/octet-stream';
|
|
802
838
|
}
|
|
803
839
|
|
|
804
840
|
private generateS3Key(runId: string, testId: string, fileName: string): string {
|
package/lib/index.js
CHANGED
|
@@ -45,7 +45,16 @@ const fs = __importStar(require("fs"));
|
|
|
45
45
|
const https = __importStar(require("https"));
|
|
46
46
|
const axios_1 = __importDefault(require("axios"));
|
|
47
47
|
const child_process_1 = require("child_process");
|
|
48
|
-
|
|
48
|
+
// Lazy-load mime module to support ESM
|
|
49
|
+
let mimeModule = null;
|
|
50
|
+
async function getMime() {
|
|
51
|
+
if (!mimeModule) {
|
|
52
|
+
const imported = await Promise.resolve().then(() => __importStar(require('mime')));
|
|
53
|
+
// Handle both default export and named exports
|
|
54
|
+
mimeModule = imported.default || imported;
|
|
55
|
+
}
|
|
56
|
+
return mimeModule;
|
|
57
|
+
}
|
|
49
58
|
class TestLensReporter {
|
|
50
59
|
constructor(options) {
|
|
51
60
|
this.config = {
|
|
@@ -550,7 +559,7 @@ class TestLensReporter {
|
|
|
550
559
|
testRunId: this.runId,
|
|
551
560
|
testId: testId,
|
|
552
561
|
fileName: fileName,
|
|
553
|
-
fileType: this.getContentType(fileName),
|
|
562
|
+
fileType: await this.getContentType(fileName),
|
|
554
563
|
fileSize: fileSize,
|
|
555
564
|
artifactType: this.getArtifactType(fileName)
|
|
556
565
|
}, {
|
|
@@ -588,7 +597,7 @@ class TestLensReporter {
|
|
|
588
597
|
testId: testId,
|
|
589
598
|
s3Key: s3Key,
|
|
590
599
|
fileName: fileName,
|
|
591
|
-
fileType: this.getContentType(fileName),
|
|
600
|
+
fileType: await this.getContentType(fileName),
|
|
592
601
|
fileSize: fileSize,
|
|
593
602
|
artifactType: this.getArtifactType(fileName)
|
|
594
603
|
}, {
|
|
@@ -632,10 +641,36 @@ class TestLensReporter {
|
|
|
632
641
|
return null;
|
|
633
642
|
}
|
|
634
643
|
}
|
|
635
|
-
getContentType(fileName) {
|
|
644
|
+
async getContentType(fileName) {
|
|
636
645
|
const ext = path.extname(fileName).toLowerCase();
|
|
637
|
-
|
|
638
|
-
|
|
646
|
+
try {
|
|
647
|
+
const mime = await getMime();
|
|
648
|
+
// Try different ways to access getType method
|
|
649
|
+
const getType = mime.getType || mime.default?.getType;
|
|
650
|
+
if (typeof getType === 'function') {
|
|
651
|
+
const mimeType = getType.call(mime, ext) || getType.call(mime.default, ext);
|
|
652
|
+
return mimeType || 'application/octet-stream';
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
console.warn(`Failed to get MIME type for ${fileName}:`, error.message);
|
|
657
|
+
}
|
|
658
|
+
// Fallback to basic content type mapping
|
|
659
|
+
const contentTypes = {
|
|
660
|
+
'.mp4': 'video/mp4',
|
|
661
|
+
'.webm': 'video/webm',
|
|
662
|
+
'.png': 'image/png',
|
|
663
|
+
'.jpg': 'image/jpeg',
|
|
664
|
+
'.jpeg': 'image/jpeg',
|
|
665
|
+
'.gif': 'image/gif',
|
|
666
|
+
'.json': 'application/json',
|
|
667
|
+
'.txt': 'text/plain',
|
|
668
|
+
'.html': 'text/html',
|
|
669
|
+
'.xml': 'application/xml',
|
|
670
|
+
'.zip': 'application/zip',
|
|
671
|
+
'.pdf': 'application/pdf'
|
|
672
|
+
};
|
|
673
|
+
return contentTypes[ext] || 'application/octet-stream';
|
|
639
674
|
}
|
|
640
675
|
generateS3Key(runId, testId, fileName) {
|
|
641
676
|
const date = new Date().toISOString().slice(0, 10);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testlens-playwright-reporter",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Universal Playwright reporter for TestLens - works with both TypeScript and JavaScript projects",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"index.js",
|
package/CHANGELOG.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to the testlens-playwright-reporter package will be documented in this file.
|
|
4
|
-
|
|
5
|
-
## [0.2.3] - 2025-11-18
|
|
6
|
-
|
|
7
|
-
### Fixed
|
|
8
|
-
- **Trace file extension preservation**: Fixed issue where Playwright trace files were being uploaded to S3 without their `.zip` extension, causing "Could not load trace" errors in Playwright Trace Viewer. The reporter now preserves the original file extension from `attachment.path` when uploading artifacts, ensuring trace files maintain their `.zip` format required by the viewer.
|
|
9
|
-
|
|
10
|
-
## [0.2.2] - Previous release
|
|
11
|
-
|
|
12
|
-
### Features
|
|
13
|
-
- Direct S3 uploads with presigned URLs
|
|
14
|
-
- Asynchronous artifact processing
|
|
15
|
-
- Enhanced error handling and retry logic
|