testlens-playwright-reporter 0.2.4 → 0.2.6

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.d.ts CHANGED
@@ -55,8 +55,8 @@ export interface GitInfo {
55
55
  commit: string;
56
56
  shortCommit: string;
57
57
  author: string;
58
- commitMessage: string;
59
- commitTimestamp: string;
58
+ message: string;
59
+ timestamp: string;
60
60
  isDirty: boolean;
61
61
  remoteName: string;
62
62
  remoteUrl: string;
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,
@@ -136,6 +147,13 @@ class TestLensReporter {
136
147
  // Collect Git information if enabled
137
148
  if (this.config.enableGitInfo) {
138
149
  this.runMetadata.gitInfo = await this.collectGitInfo();
150
+ if (this.runMetadata.gitInfo) {
151
+ console.log(`📦 Git info collected: branch=${this.runMetadata.gitInfo.branch}, commit=${this.runMetadata.gitInfo.shortCommit}, author=${this.runMetadata.gitInfo.author}`);
152
+ } else {
153
+ console.log(`⚠️ Git info collection returned null - not in a git repository or git not available`);
154
+ }
155
+ } else {
156
+ console.log(`ℹ️ Git info collection disabled (enableGitInfo: false)`);
139
157
  }
140
158
 
141
159
  // Add shard information if available
@@ -371,20 +389,14 @@ class TestLensReporter {
371
389
  try {
372
390
  console.log(`📤 Processing ${attachment.name} asynchronously...`);
373
391
 
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
392
  // Upload to S3 first
381
- const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
393
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
382
394
 
383
395
  const artifactData = {
384
396
  testId,
385
- type: this.getArtifactType(fileName),
397
+ type: this.getArtifactType(attachment.name),
386
398
  path: attachment.path,
387
- name: fileName,
399
+ name: attachment.name,
388
400
  contentType: attachment.contentType,
389
401
  fileSize: this.getFileSize(attachment.path),
390
402
  storageType: 's3',
@@ -538,8 +550,8 @@ class TestLensReporter {
538
550
  commit,
539
551
  shortCommit,
540
552
  author,
541
- commitMessage,
542
- commitTimestamp,
553
+ message: commitMessage,
554
+ timestamp: commitTimestamp,
543
555
  isDirty,
544
556
  remoteName,
545
557
  remoteUrl
@@ -579,24 +591,6 @@ class TestLensReporter {
579
591
  return `${test.location.file}:${test.location.line}:${cleanTitle}`;
580
592
  }
581
593
 
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
594
  getFileSize(filePath) {
601
595
  try {
602
596
  const stats = fs.statSync(filePath);
@@ -624,7 +618,7 @@ class TestLensReporter {
624
618
  testRunId: this.runId,
625
619
  testId: testId,
626
620
  fileName: fileName,
627
- fileType: this.getContentType(fileName),
621
+ fileType: await this.getContentType(fileName),
628
622
  fileSize: fileSize,
629
623
  artifactType: this.getArtifactType(fileName)
630
624
  }, {
@@ -670,7 +664,7 @@ class TestLensReporter {
670
664
  testId: testId,
671
665
  s3Key: s3Key,
672
666
  fileName: fileName,
673
- fileType: this.getContentType(fileName),
667
+ fileType: await this.getContentType(fileName),
674
668
  fileSize: fileSize,
675
669
  artifactType: this.getArtifactType(fileName)
676
670
  }, {
@@ -714,10 +708,35 @@ class TestLensReporter {
714
708
  }
715
709
  }
716
710
 
717
- getContentType(fileName) {
711
+ async getContentType(fileName) {
718
712
  const ext = path.extname(fileName).toLowerCase();
719
- const mimeType = mime.getType(ext) || 'application/octet-stream';
720
- return mimeType;
713
+ try {
714
+ const mime = await getMime();
715
+ // Try different ways to access getType method
716
+ const getType = mime.getType || mime.default?.getType;
717
+ if (typeof getType === 'function') {
718
+ const mimeType = getType.call(mime, ext) || getType.call(mime.default, ext);
719
+ return mimeType || 'application/octet-stream';
720
+ }
721
+ } catch (error) {
722
+ console.warn(`Failed to get MIME type for ${fileName}:`, error.message);
723
+ }
724
+ // Fallback to basic content type mapping
725
+ const contentTypes = {
726
+ '.mp4': 'video/mp4',
727
+ '.webm': 'video/webm',
728
+ '.png': 'image/png',
729
+ '.jpg': 'image/jpeg',
730
+ '.jpeg': 'image/jpeg',
731
+ '.gif': 'image/gif',
732
+ '.json': 'application/json',
733
+ '.txt': 'text/plain',
734
+ '.html': 'text/html',
735
+ '.xml': 'application/xml',
736
+ '.zip': 'application/zip',
737
+ '.pdf': 'application/pdf'
738
+ };
739
+ return contentTypes[ext] || 'application/octet-stream';
721
740
  }
722
741
  }
723
742
 
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;
@@ -65,8 +76,8 @@ export interface GitInfo {
65
76
  commit: string;
66
77
  shortCommit: string;
67
78
  author: string;
68
- commitMessage: string;
69
- commitTimestamp: string;
79
+ message: string;
80
+ timestamp: string;
70
81
  isDirty: boolean;
71
82
  remoteName: string;
72
83
  remoteUrl: string;
@@ -265,6 +276,13 @@ export class TestLensReporter implements Reporter {
265
276
  // Collect Git information if enabled
266
277
  if (this.config.enableGitInfo) {
267
278
  this.runMetadata.gitInfo = await this.collectGitInfo();
279
+ if (this.runMetadata.gitInfo) {
280
+ console.log(`📦 Git info collected: branch=${this.runMetadata.gitInfo.branch}, commit=${this.runMetadata.gitInfo.shortCommit}, author=${this.runMetadata.gitInfo.author}`);
281
+ } else {
282
+ console.log(`⚠️ Git info collection returned null - not in a git repository or git not available`);
283
+ }
284
+ } else {
285
+ console.log(`ℹ️ Git info collection disabled (enableGitInfo: false)`);
268
286
  }
269
287
 
270
288
  // Add shard information if available
@@ -483,26 +501,20 @@ export class TestLensReporter implements Reporter {
483
501
  for (const attachment of attachments) {
484
502
  if (attachment.path) {
485
503
  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
504
  // Upload to S3 first
493
- const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
505
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
494
506
 
495
507
  // Skip if upload failed or file was too large
496
508
  if (!s3Data) {
497
- console.log(`⏭️ Skipping artifact ${fileName} - upload failed or file too large`);
509
+ console.log(`⏭️ Skipping artifact ${attachment.name} - upload failed or file too large`);
498
510
  continue;
499
511
  }
500
512
 
501
513
  const artifactData = {
502
514
  testId,
503
- type: this.getArtifactType(fileName),
515
+ type: this.getArtifactType(attachment.name),
504
516
  path: attachment.path,
505
- name: fileName,
517
+ name: attachment.name,
506
518
  contentType: attachment.contentType,
507
519
  fileSize: this.getFileSize(attachment.path),
508
520
  storageType: 's3',
@@ -652,8 +664,8 @@ export class TestLensReporter implements Reporter {
652
664
  commit,
653
665
  shortCommit,
654
666
  author,
655
- commitMessage,
656
- commitTimestamp,
667
+ message: commitMessage,
668
+ timestamp: commitTimestamp,
657
669
  isDirty,
658
670
  remoteName,
659
671
  remoteUrl
@@ -711,7 +723,7 @@ export class TestLensReporter implements Reporter {
711
723
  testRunId: this.runId,
712
724
  testId: testId,
713
725
  fileName: fileName,
714
- fileType: this.getContentType(fileName),
726
+ fileType: await this.getContentType(fileName),
715
727
  fileSize: fileSize,
716
728
  artifactType: this.getArtifactType(fileName)
717
729
  }, {
@@ -757,7 +769,7 @@ export class TestLensReporter implements Reporter {
757
769
  testId: testId,
758
770
  s3Key: s3Key,
759
771
  fileName: fileName,
760
- fileType: this.getContentType(fileName),
772
+ fileType: await this.getContentType(fileName),
761
773
  fileSize: fileSize,
762
774
  artifactType: this.getArtifactType(fileName)
763
775
  }, {
@@ -801,10 +813,35 @@ export class TestLensReporter implements Reporter {
801
813
  }
802
814
  }
803
815
 
804
- private getContentType(fileName: string): string {
816
+ private async getContentType(fileName: string): Promise<string> {
805
817
  const ext = path.extname(fileName).toLowerCase();
806
- const mimeType = mime.default.getType(ext) || 'application/octet-stream';
807
- return mimeType;
818
+ try {
819
+ const mime = await getMime();
820
+ // Try different ways to access getType method
821
+ const getType = mime.getType || mime.default?.getType;
822
+ if (typeof getType === 'function') {
823
+ const mimeType = getType.call(mime, ext) || getType.call(mime.default, ext);
824
+ return mimeType || 'application/octet-stream';
825
+ }
826
+ } catch (error: any) {
827
+ console.warn(`Failed to get MIME type for ${fileName}:`, error.message);
828
+ }
829
+ // Fallback to basic content type mapping
830
+ const contentTypes: Record<string, string> = {
831
+ '.mp4': 'video/mp4',
832
+ '.webm': 'video/webm',
833
+ '.png': 'image/png',
834
+ '.jpg': 'image/jpeg',
835
+ '.jpeg': 'image/jpeg',
836
+ '.gif': 'image/gif',
837
+ '.json': 'application/json',
838
+ '.txt': 'text/plain',
839
+ '.html': 'text/html',
840
+ '.xml': 'application/xml',
841
+ '.zip': 'application/zip',
842
+ '.pdf': 'application/pdf'
843
+ };
844
+ return contentTypes[ext] || 'application/octet-stream';
808
845
  }
809
846
 
810
847
  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 = {
@@ -159,6 +168,15 @@ class TestLensReporter {
159
168
  // Collect Git information if enabled
160
169
  if (this.config.enableGitInfo) {
161
170
  this.runMetadata.gitInfo = await this.collectGitInfo();
171
+ if (this.runMetadata.gitInfo) {
172
+ console.log(`📦 Git info collected: branch=${this.runMetadata.gitInfo.branch}, commit=${this.runMetadata.gitInfo.shortCommit}, author=${this.runMetadata.gitInfo.author}`);
173
+ }
174
+ else {
175
+ console.log(`⚠️ Git info collection returned null - not in a git repository or git not available`);
176
+ }
177
+ }
178
+ else {
179
+ console.log(`ℹ️ Git info collection disabled (enableGitInfo: false)`);
162
180
  }
163
181
  // Add shard information if available
164
182
  if (config.shard) {
@@ -352,23 +370,18 @@ class TestLensReporter {
352
370
  for (const attachment of attachments) {
353
371
  if (attachment.path) {
354
372
  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
373
  // Upload to S3 first
361
- const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
374
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
362
375
  // Skip if upload failed or file was too large
363
376
  if (!s3Data) {
364
- console.log(`⏭️ Skipping artifact ${fileName} - upload failed or file too large`);
377
+ console.log(`⏭️ Skipping artifact ${attachment.name} - upload failed or file too large`);
365
378
  continue;
366
379
  }
367
380
  const artifactData = {
368
381
  testId,
369
- type: this.getArtifactType(fileName),
382
+ type: this.getArtifactType(attachment.name),
370
383
  path: attachment.path,
371
- name: fileName,
384
+ name: attachment.name,
372
385
  contentType: attachment.contentType,
373
386
  fileSize: this.getFileSize(attachment.path),
374
387
  storageType: 's3',
@@ -503,8 +516,8 @@ class TestLensReporter {
503
516
  commit,
504
517
  shortCommit,
505
518
  author,
506
- commitMessage,
507
- commitTimestamp,
519
+ message: commitMessage,
520
+ timestamp: commitTimestamp,
508
521
  isDirty,
509
522
  remoteName,
510
523
  remoteUrl
@@ -555,7 +568,7 @@ class TestLensReporter {
555
568
  testRunId: this.runId,
556
569
  testId: testId,
557
570
  fileName: fileName,
558
- fileType: this.getContentType(fileName),
571
+ fileType: await this.getContentType(fileName),
559
572
  fileSize: fileSize,
560
573
  artifactType: this.getArtifactType(fileName)
561
574
  }, {
@@ -593,7 +606,7 @@ class TestLensReporter {
593
606
  testId: testId,
594
607
  s3Key: s3Key,
595
608
  fileName: fileName,
596
- fileType: this.getContentType(fileName),
609
+ fileType: await this.getContentType(fileName),
597
610
  fileSize: fileSize,
598
611
  artifactType: this.getArtifactType(fileName)
599
612
  }, {
@@ -637,10 +650,36 @@ class TestLensReporter {
637
650
  return null;
638
651
  }
639
652
  }
640
- getContentType(fileName) {
653
+ async getContentType(fileName) {
641
654
  const ext = path.extname(fileName).toLowerCase();
642
- const mimeType = mime.default.getType(ext) || 'application/octet-stream';
643
- return mimeType;
655
+ try {
656
+ const mime = await getMime();
657
+ // Try different ways to access getType method
658
+ const getType = mime.getType || mime.default?.getType;
659
+ if (typeof getType === 'function') {
660
+ const mimeType = getType.call(mime, ext) || getType.call(mime.default, ext);
661
+ return mimeType || 'application/octet-stream';
662
+ }
663
+ }
664
+ catch (error) {
665
+ console.warn(`Failed to get MIME type for ${fileName}:`, error.message);
666
+ }
667
+ // Fallback to basic content type mapping
668
+ const contentTypes = {
669
+ '.mp4': 'video/mp4',
670
+ '.webm': 'video/webm',
671
+ '.png': 'image/png',
672
+ '.jpg': 'image/jpeg',
673
+ '.jpeg': 'image/jpeg',
674
+ '.gif': 'image/gif',
675
+ '.json': 'application/json',
676
+ '.txt': 'text/plain',
677
+ '.html': 'text/html',
678
+ '.xml': 'application/xml',
679
+ '.zip': 'application/zip',
680
+ '.pdf': 'application/pdf'
681
+ };
682
+ return contentTypes[ext] || 'application/octet-stream';
644
683
  }
645
684
  generateS3Key(runId, testId, fileName) {
646
685
  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.6",
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