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 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, fileName);
386
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
382
387
 
383
388
  const artifactData = {
384
389
  testId,
385
- type: this.getArtifactType(fileName),
390
+ type: this.getArtifactType(attachment.name),
386
391
  path: attachment.path,
387
- name: fileName,
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
- const mimeType = mime.getType(ext) || 'application/octet-stream';
720
- return mimeType;
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
- const mimeType = mime.default.getType(ext) || 'application/octet-stream';
801
- return mimeType;
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
- const mime = __importStar(require("mime"));
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
- const mimeType = mime.default.getType(ext) || 'application/octet-stream';
638
- return mimeType;
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",
3
+ "version": "0.2.5",
4
4
  "description": "Universal Playwright reporter for TestLens - works with both TypeScript and JavaScript projects",
5
- "main": "lib/index.js",
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