testlens-playwright-reporter 0.2.4 → 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;
@@ -483,26 +494,20 @@ export class TestLensReporter implements Reporter {
483
494
  for (const attachment of attachments) {
484
495
  if (attachment.path) {
485
496
  try {
486
- // Preserve original file extension from path (important for traces which must be .zip)
487
- const originalExt = path.extname(attachment.path);
488
- const fileName = attachment.name.includes(originalExt)
489
- ? attachment.name
490
- : attachment.name + originalExt;
491
-
492
497
  // Upload to S3 first
493
- const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
498
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
494
499
 
495
500
  // Skip if upload failed or file was too large
496
501
  if (!s3Data) {
497
- console.log(`⏭️ Skipping artifact ${fileName} - upload failed or file too large`);
502
+ console.log(`⏭️ Skipping artifact ${attachment.name} - upload failed or file too large`);
498
503
  continue;
499
504
  }
500
505
 
501
506
  const artifactData = {
502
507
  testId,
503
- type: this.getArtifactType(fileName),
508
+ type: this.getArtifactType(attachment.name),
504
509
  path: attachment.path,
505
- name: fileName,
510
+ name: attachment.name,
506
511
  contentType: attachment.contentType,
507
512
  fileSize: this.getFileSize(attachment.path),
508
513
  storageType: 's3',
@@ -711,7 +716,7 @@ export class TestLensReporter implements Reporter {
711
716
  testRunId: this.runId,
712
717
  testId: testId,
713
718
  fileName: fileName,
714
- fileType: this.getContentType(fileName),
719
+ fileType: await this.getContentType(fileName),
715
720
  fileSize: fileSize,
716
721
  artifactType: this.getArtifactType(fileName)
717
722
  }, {
@@ -757,7 +762,7 @@ export class TestLensReporter implements Reporter {
757
762
  testId: testId,
758
763
  s3Key: s3Key,
759
764
  fileName: fileName,
760
- fileType: this.getContentType(fileName),
765
+ fileType: await this.getContentType(fileName),
761
766
  fileSize: fileSize,
762
767
  artifactType: this.getArtifactType(fileName)
763
768
  }, {
@@ -801,10 +806,35 @@ export class TestLensReporter implements Reporter {
801
806
  }
802
807
  }
803
808
 
804
- private getContentType(fileName: string): string {
809
+ private async getContentType(fileName: string): Promise<string> {
805
810
  const ext = path.extname(fileName).toLowerCase();
806
- const mimeType = mime.default.getType(ext) || 'application/octet-stream';
807
- 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';
808
838
  }
809
839
 
810
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 = {
@@ -352,23 +361,18 @@ class TestLensReporter {
352
361
  for (const attachment of attachments) {
353
362
  if (attachment.path) {
354
363
  try {
355
- // Preserve original file extension from path (important for traces which must be .zip)
356
- const originalExt = path.extname(attachment.path);
357
- const fileName = attachment.name.includes(originalExt)
358
- ? attachment.name
359
- : attachment.name + originalExt;
360
364
  // Upload to S3 first
361
- const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
365
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
362
366
  // Skip if upload failed or file was too large
363
367
  if (!s3Data) {
364
- console.log(`⏭️ Skipping artifact ${fileName} - upload failed or file too large`);
368
+ console.log(`⏭️ Skipping artifact ${attachment.name} - upload failed or file too large`);
365
369
  continue;
366
370
  }
367
371
  const artifactData = {
368
372
  testId,
369
- type: this.getArtifactType(fileName),
373
+ type: this.getArtifactType(attachment.name),
370
374
  path: attachment.path,
371
- name: fileName,
375
+ name: attachment.name,
372
376
  contentType: attachment.contentType,
373
377
  fileSize: this.getFileSize(attachment.path),
374
378
  storageType: 's3',
@@ -555,7 +559,7 @@ class TestLensReporter {
555
559
  testRunId: this.runId,
556
560
  testId: testId,
557
561
  fileName: fileName,
558
- fileType: this.getContentType(fileName),
562
+ fileType: await this.getContentType(fileName),
559
563
  fileSize: fileSize,
560
564
  artifactType: this.getArtifactType(fileName)
561
565
  }, {
@@ -593,7 +597,7 @@ class TestLensReporter {
593
597
  testId: testId,
594
598
  s3Key: s3Key,
595
599
  fileName: fileName,
596
- fileType: this.getContentType(fileName),
600
+ fileType: await this.getContentType(fileName),
597
601
  fileSize: fileSize,
598
602
  artifactType: this.getArtifactType(fileName)
599
603
  }, {
@@ -637,10 +641,36 @@ class TestLensReporter {
637
641
  return null;
638
642
  }
639
643
  }
640
- getContentType(fileName) {
644
+ async getContentType(fileName) {
641
645
  const ext = path.extname(fileName).toLowerCase();
642
- const mimeType = mime.default.getType(ext) || 'application/octet-stream';
643
- 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';
644
674
  }
645
675
  generateS3Key(runId, testId, fileName) {
646
676
  const date = new Date().toISOString().slice(0, 10);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testlens-playwright-reporter",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Universal Playwright reporter for TestLens - works with both TypeScript and JavaScript projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
package/CHANGELOG.md DELETED
@@ -1,23 +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.4] - 2025-11-18
6
-
7
- ### Fixed
8
- - **TypeScript source updated**: Applied trace file extension preservation fix to TypeScript source (`index.ts`) and rebuilt `lib/index.js` to ensure both TypeScript and JavaScript implementations have the fix.
9
-
10
- ### Fixed
11
- - **Package entry point**: Changed main entry point from `lib/index.js` to `index.js` to use the correct implementation with trace file extension preservation fix.
12
-
13
- ## [0.2.3] - 2025-11-18
14
-
15
- ### Fixed
16
- - **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.
17
-
18
- ## [0.2.2] - Previous release
19
-
20
- ### Features
21
- - Direct S3 uploads with presigned URLs
22
- - Asynchronous artifact processing
23
- - Enhanced error handling and retry logic