testlens-playwright-reporter 0.2.6 → 0.2.7

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
@@ -11,6 +11,10 @@ export interface TestLensReporterConfig {
11
11
  enableGitInfo?: boolean;
12
12
  /** Enable artifact processing */
13
13
  enableArtifacts?: boolean;
14
+ /** Enable video capture - defaults to true */
15
+ enableVideo?: boolean;
16
+ /** Enable screenshot capture - defaults to true */
17
+ enableScreenshot?: boolean;
14
18
  /** Batch size for API requests */
15
19
  batchSize?: number;
16
20
  /** Flush interval in milliseconds */
@@ -36,6 +40,10 @@ export interface TestLensReporterOptions {
36
40
  enableGitInfo?: boolean;
37
41
  /** Enable artifact processing */
38
42
  enableArtifacts?: boolean;
43
+ /** Enable video capture - defaults to true */
44
+ enableVideo?: boolean;
45
+ /** Enable screenshot capture - defaults to true */
46
+ enableScreenshot?: boolean;
39
47
  /** Batch size for API requests */
40
48
  batchSize?: number;
41
49
  /** Flush interval in milliseconds */
package/index.js CHANGED
@@ -27,6 +27,8 @@ class TestLensReporter {
27
27
  enableRealTimeStream: options.enableRealTimeStream !== undefined ? options.enableRealTimeStream : true,
28
28
  enableGitInfo: options.enableGitInfo !== undefined ? options.enableGitInfo : true,
29
29
  enableArtifacts: options.enableArtifacts !== undefined ? options.enableArtifacts : true,
30
+ enableVideo: options.enableVideo !== undefined ? options.enableVideo : true, // Default to true, override from config
31
+ enableScreenshot: options.enableScreenshot !== undefined ? options.enableScreenshot : true, // Default to true, override from config
30
32
  batchSize: options.batchSize || 10,
31
33
  flushInterval: options.flushInterval || 5000,
32
34
  retryAttempts: options.retryAttempts !== undefined ? options.retryAttempts : 0,
@@ -316,8 +318,10 @@ class TestLensReporter {
316
318
  // Calculate final stats
317
319
  const totalTests = Array.from(this.testMap.values()).length;
318
320
  const passedTests = Array.from(this.testMap.values()).filter(t => t.status === 'passed').length;
321
+ // failedTests already includes timedOut tests since normalizeTestStatus converts 'timedOut' to 'failed'
319
322
  const failedTests = Array.from(this.testMap.values()).filter(t => t.status === 'failed').length;
320
323
  const skippedTests = Array.from(this.testMap.values()).filter(t => t.status === 'skipped').length;
324
+ // Track timedOut separately for reporting purposes only (not for count)
321
325
  const timedOutTests = Array.from(this.testMap.values()).filter(t => t.originalStatus === 'timedOut').length;
322
326
 
323
327
  // Normalize run status - if there are timeouts, treat run as failed
@@ -333,9 +337,9 @@ class TestLensReporter {
333
337
  ...this.runMetadata,
334
338
  totalTests,
335
339
  passedTests,
336
- failedTests: failedTests + timedOutTests, // Include timeouts in failed count
340
+ failedTests, // Already includes timedOut tests (normalized to 'failed')
337
341
  skippedTests,
338
- timedOutTests,
342
+ timedOutTests, // For informational purposes
339
343
  status: normalizedRunStatus
340
344
  }
341
345
  });
@@ -345,7 +349,7 @@ class TestLensReporter {
345
349
  await new Promise(resolve => setTimeout(resolve, 10000));
346
350
 
347
351
  console.log(`📊 TestLens Report completed - Run ID: ${this.runId}`);
348
- console.log(`🎯 Results: ${passedTests} passed, ${failedTests + timedOutTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
352
+ console.log(`🎯 Results: ${passedTests} passed, ${failedTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
349
353
  }
350
354
 
351
355
  async sendToApi(payload) {
@@ -386,17 +390,70 @@ class TestLensReporter {
386
390
  for (let i = 0; i < attachments.length; i++) {
387
391
  const attachment = attachments[i];
388
392
  if (attachment.path) {
393
+ // Check if attachment should be processed based on config
394
+ const artifactType = this.getArtifactType(attachment.name);
395
+ const isVideo = artifactType === 'video' || (attachment.contentType && attachment.contentType.startsWith('video/'));
396
+ const isScreenshot = artifactType === 'screenshot' || (attachment.contentType && attachment.contentType.startsWith('image/'));
397
+
398
+ // Skip video if disabled in config
399
+ if (isVideo && !this.config.enableVideo) {
400
+ console.log(`⏭️ Skipping video artifact ${attachment.name} - video capture disabled in config`);
401
+ continue;
402
+ }
403
+
404
+ // Skip screenshot if disabled in config
405
+ if (isScreenshot && !this.config.enableScreenshot) {
406
+ console.log(`⏭️ Skipping screenshot artifact ${attachment.name} - screenshot capture disabled in config`);
407
+ continue;
408
+ }
409
+
389
410
  try {
390
411
  console.log(`📤 Processing ${attachment.name} asynchronously...`);
391
412
 
413
+ // Determine proper filename with extension
414
+ // Playwright attachment.name often doesn't have extension, so we need to derive it
415
+ let fileName = attachment.name;
416
+ const existingExt = path.extname(fileName);
417
+
418
+ if (!existingExt) {
419
+ // Get extension from the actual file path
420
+ const pathExt = path.extname(attachment.path);
421
+ if (pathExt) {
422
+ fileName = `${fileName}${pathExt}`;
423
+ } else if (attachment.contentType) {
424
+ // Fallback: derive extension from contentType
425
+ const mimeToExt = {
426
+ 'image/png': '.png',
427
+ 'image/jpeg': '.jpg',
428
+ 'image/gif': '.gif',
429
+ 'image/webp': '.webp',
430
+ 'video/webm': '.webm',
431
+ 'video/mp4': '.mp4',
432
+ 'application/zip': '.zip',
433
+ 'application/json': '.json',
434
+ 'text/plain': '.txt'
435
+ };
436
+ const ext = mimeToExt[attachment.contentType];
437
+ if (ext) {
438
+ fileName = `${fileName}${ext}`;
439
+ }
440
+ }
441
+ }
442
+
392
443
  // Upload to S3 first
393
- const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
444
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
445
+
446
+ // Skip if upload failed or file was too large
447
+ if (!s3Data) {
448
+ console.log(`⏭️ Skipping artifact ${attachment.name} - upload failed or file too large`);
449
+ continue;
450
+ }
394
451
 
395
452
  const artifactData = {
396
453
  testId,
397
454
  type: this.getArtifactType(attachment.name),
398
455
  path: attachment.path,
399
- name: attachment.name,
456
+ name: fileName,
400
457
  contentType: attachment.contentType,
401
458
  fileSize: this.getFileSize(attachment.path),
402
459
  storageType: 's3',
@@ -412,7 +469,7 @@ class TestLensReporter {
412
469
  artifact: artifactData
413
470
  });
414
471
 
415
- console.log(`📎 Processed artifact: ${attachment.name} (uploaded to S3)`);
472
+ console.log(`📎 Processed artifact: ${fileName} (uploaded to S3)`);
416
473
  } catch (error) {
417
474
  console.error(`❌ Failed to process ${attachment.name}:`, error.message);
418
475
  }
package/index.ts CHANGED
@@ -32,6 +32,10 @@ export interface TestLensReporterConfig {
32
32
  enableGitInfo?: boolean;
33
33
  /** Enable artifact processing */
34
34
  enableArtifacts?: boolean;
35
+ /** Enable video capture - defaults to true */
36
+ enableVideo?: boolean;
37
+ /** Enable screenshot capture - defaults to true */
38
+ enableScreenshot?: boolean;
35
39
  /** Batch size for API requests */
36
40
  batchSize?: number;
37
41
  /** Flush interval in milliseconds */
@@ -57,6 +61,10 @@ export interface TestLensReporterOptions {
57
61
  enableGitInfo?: boolean;
58
62
  /** Enable artifact processing */
59
63
  enableArtifacts?: boolean;
64
+ /** Enable video capture - defaults to true */
65
+ enableVideo?: boolean;
66
+ /** Enable screenshot capture - defaults to true */
67
+ enableScreenshot?: boolean;
60
68
  /** Batch size for API requests */
61
69
  batchSize?: number;
62
70
  /** Flush interval in milliseconds */
@@ -156,6 +164,8 @@ export class TestLensReporter implements Reporter {
156
164
  enableRealTimeStream: options.enableRealTimeStream !== undefined ? options.enableRealTimeStream : true,
157
165
  enableGitInfo: options.enableGitInfo !== undefined ? options.enableGitInfo : true,
158
166
  enableArtifacts: options.enableArtifacts !== undefined ? options.enableArtifacts : true,
167
+ enableVideo: options.enableVideo !== undefined ? options.enableVideo : true, // Default to true, override from config
168
+ enableScreenshot: options.enableScreenshot !== undefined ? options.enableScreenshot : true, // Default to true, override from config
159
169
  batchSize: options.batchSize || 10,
160
170
  flushInterval: options.flushInterval || 5000,
161
171
  retryAttempts: options.retryAttempts !== undefined ? options.retryAttempts : 0,
@@ -445,8 +455,10 @@ export class TestLensReporter implements Reporter {
445
455
  // Calculate final stats
446
456
  const totalTests = Array.from(this.testMap.values()).length;
447
457
  const passedTests = Array.from(this.testMap.values()).filter(t => t.status === 'passed').length;
458
+ // failedTests already includes timedOut tests since normalizeTestStatus converts 'timedOut' to 'failed'
448
459
  const failedTests = Array.from(this.testMap.values()).filter(t => t.status === 'failed').length;
449
460
  const skippedTests = Array.from(this.testMap.values()).filter(t => t.status === 'skipped').length;
461
+ // Track timedOut separately for reporting purposes only (not for count)
450
462
  const timedOutTests = Array.from(this.testMap.values()).filter(t => t.originalStatus === 'timedOut').length;
451
463
 
452
464
  // Normalize run status - if there are timeouts, treat run as failed
@@ -462,15 +474,15 @@ export class TestLensReporter implements Reporter {
462
474
  ...this.runMetadata,
463
475
  totalTests,
464
476
  passedTests,
465
- failedTests: failedTests + timedOutTests, // Include timeouts in failed count
477
+ failedTests, // Already includes timedOut tests (normalized to 'failed')
466
478
  skippedTests,
467
- timedOutTests,
479
+ timedOutTests, // For informational purposes
468
480
  status: normalizedRunStatus
469
481
  }
470
482
  });
471
483
 
472
484
  console.log(`📊 TestLens Report completed - Run ID: ${this.runId}`);
473
- console.log(`🎯 Results: ${passedTests} passed, ${failedTests + timedOutTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
485
+ console.log(`🎯 Results: ${passedTests} passed, ${failedTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
474
486
  }
475
487
 
476
488
  private async sendToApi(payload: any): Promise<void> {
@@ -500,9 +512,56 @@ export class TestLensReporter implements Reporter {
500
512
 
501
513
  for (const attachment of attachments) {
502
514
  if (attachment.path) {
515
+ // Check if attachment should be processed based on config
516
+ const artifactType = this.getArtifactType(attachment.name);
517
+ const isVideo = artifactType === 'video' || attachment.contentType?.startsWith('video/');
518
+ const isScreenshot = artifactType === 'screenshot' || attachment.contentType?.startsWith('image/');
519
+
520
+ // Skip video if disabled in config
521
+ if (isVideo && !this.config.enableVideo) {
522
+ console.log(`⏭️ Skipping video artifact ${attachment.name} - video capture disabled in config`);
523
+ continue;
524
+ }
525
+
526
+ // Skip screenshot if disabled in config
527
+ if (isScreenshot && !this.config.enableScreenshot) {
528
+ console.log(`⏭️ Skipping screenshot artifact ${attachment.name} - screenshot capture disabled in config`);
529
+ continue;
530
+ }
531
+
503
532
  try {
533
+ // Determine proper filename with extension
534
+ // Playwright attachment.name often doesn't have extension, so we need to derive it
535
+ let fileName = attachment.name;
536
+ const existingExt = path.extname(fileName);
537
+
538
+ if (!existingExt) {
539
+ // Get extension from the actual file path
540
+ const pathExt = path.extname(attachment.path);
541
+ if (pathExt) {
542
+ fileName = `${fileName}${pathExt}`;
543
+ } else if (attachment.contentType) {
544
+ // Fallback: derive extension from contentType
545
+ const mimeToExt: Record<string, string> = {
546
+ 'image/png': '.png',
547
+ 'image/jpeg': '.jpg',
548
+ 'image/gif': '.gif',
549
+ 'image/webp': '.webp',
550
+ 'video/webm': '.webm',
551
+ 'video/mp4': '.mp4',
552
+ 'application/zip': '.zip',
553
+ 'application/json': '.json',
554
+ 'text/plain': '.txt'
555
+ };
556
+ const ext = mimeToExt[attachment.contentType];
557
+ if (ext) {
558
+ fileName = `${fileName}${ext}`;
559
+ }
560
+ }
561
+ }
562
+
504
563
  // Upload to S3 first
505
- const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
564
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
506
565
 
507
566
  // Skip if upload failed or file was too large
508
567
  if (!s3Data) {
@@ -514,7 +573,7 @@ export class TestLensReporter implements Reporter {
514
573
  testId,
515
574
  type: this.getArtifactType(attachment.name),
516
575
  path: attachment.path,
517
- name: attachment.name,
576
+ name: fileName,
518
577
  contentType: attachment.contentType,
519
578
  fileSize: this.getFileSize(attachment.path),
520
579
  storageType: 's3',
@@ -530,7 +589,7 @@ export class TestLensReporter implements Reporter {
530
589
  artifact: artifactData
531
590
  });
532
591
 
533
- console.log(`📎 Processed artifact: ${attachment.name} (uploaded to S3)`);
592
+ console.log(`📎 Processed artifact: ${fileName} (uploaded to S3)`);
534
593
  } catch (error) {
535
594
  console.error(`❌ Failed to process artifact ${attachment.name}:`, (error as Error).message);
536
595
  }
package/lib/index.js CHANGED
@@ -63,6 +63,8 @@ class TestLensReporter {
63
63
  enableRealTimeStream: options.enableRealTimeStream !== undefined ? options.enableRealTimeStream : true,
64
64
  enableGitInfo: options.enableGitInfo !== undefined ? options.enableGitInfo : true,
65
65
  enableArtifacts: options.enableArtifacts !== undefined ? options.enableArtifacts : true,
66
+ enableVideo: options.enableVideo !== undefined ? options.enableVideo : true, // Default to true, override from config
67
+ enableScreenshot: options.enableScreenshot !== undefined ? options.enableScreenshot : true, // Default to true, override from config
66
68
  batchSize: options.batchSize || 10,
67
69
  flushInterval: options.flushInterval || 5000,
68
70
  retryAttempts: options.retryAttempts !== undefined ? options.retryAttempts : 0,
@@ -320,8 +322,10 @@ class TestLensReporter {
320
322
  // Calculate final stats
321
323
  const totalTests = Array.from(this.testMap.values()).length;
322
324
  const passedTests = Array.from(this.testMap.values()).filter(t => t.status === 'passed').length;
325
+ // failedTests already includes timedOut tests since normalizeTestStatus converts 'timedOut' to 'failed'
323
326
  const failedTests = Array.from(this.testMap.values()).filter(t => t.status === 'failed').length;
324
327
  const skippedTests = Array.from(this.testMap.values()).filter(t => t.status === 'skipped').length;
328
+ // Track timedOut separately for reporting purposes only (not for count)
325
329
  const timedOutTests = Array.from(this.testMap.values()).filter(t => t.originalStatus === 'timedOut').length;
326
330
  // Normalize run status - if there are timeouts, treat run as failed
327
331
  const hasTimeouts = timedOutTests > 0;
@@ -335,14 +339,14 @@ class TestLensReporter {
335
339
  ...this.runMetadata,
336
340
  totalTests,
337
341
  passedTests,
338
- failedTests: failedTests + timedOutTests, // Include timeouts in failed count
342
+ failedTests, // Already includes timedOut tests (normalized to 'failed')
339
343
  skippedTests,
340
- timedOutTests,
344
+ timedOutTests, // For informational purposes
341
345
  status: normalizedRunStatus
342
346
  }
343
347
  });
344
348
  console.log(`📊 TestLens Report completed - Run ID: ${this.runId}`);
345
- console.log(`🎯 Results: ${passedTests} passed, ${failedTests + timedOutTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
349
+ console.log(`🎯 Results: ${passedTests} passed, ${failedTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
346
350
  }
347
351
  async sendToApi(payload) {
348
352
  try {
@@ -369,9 +373,52 @@ class TestLensReporter {
369
373
  const attachments = result.attachments;
370
374
  for (const attachment of attachments) {
371
375
  if (attachment.path) {
376
+ // Check if attachment should be processed based on config
377
+ const artifactType = this.getArtifactType(attachment.name);
378
+ const isVideo = artifactType === 'video' || attachment.contentType?.startsWith('video/');
379
+ const isScreenshot = artifactType === 'screenshot' || attachment.contentType?.startsWith('image/');
380
+ // Skip video if disabled in config
381
+ if (isVideo && !this.config.enableVideo) {
382
+ console.log(`⏭️ Skipping video artifact ${attachment.name} - video capture disabled in config`);
383
+ continue;
384
+ }
385
+ // Skip screenshot if disabled in config
386
+ if (isScreenshot && !this.config.enableScreenshot) {
387
+ console.log(`⏭️ Skipping screenshot artifact ${attachment.name} - screenshot capture disabled in config`);
388
+ continue;
389
+ }
372
390
  try {
391
+ // Determine proper filename with extension
392
+ // Playwright attachment.name often doesn't have extension, so we need to derive it
393
+ let fileName = attachment.name;
394
+ const existingExt = path.extname(fileName);
395
+ if (!existingExt) {
396
+ // Get extension from the actual file path
397
+ const pathExt = path.extname(attachment.path);
398
+ if (pathExt) {
399
+ fileName = `${fileName}${pathExt}`;
400
+ }
401
+ else if (attachment.contentType) {
402
+ // Fallback: derive extension from contentType
403
+ const mimeToExt = {
404
+ 'image/png': '.png',
405
+ 'image/jpeg': '.jpg',
406
+ 'image/gif': '.gif',
407
+ 'image/webp': '.webp',
408
+ 'video/webm': '.webm',
409
+ 'video/mp4': '.mp4',
410
+ 'application/zip': '.zip',
411
+ 'application/json': '.json',
412
+ 'text/plain': '.txt'
413
+ };
414
+ const ext = mimeToExt[attachment.contentType];
415
+ if (ext) {
416
+ fileName = `${fileName}${ext}`;
417
+ }
418
+ }
419
+ }
373
420
  // Upload to S3 first
374
- const s3Data = await this.uploadArtifactToS3(attachment.path, testId, attachment.name);
421
+ const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
375
422
  // Skip if upload failed or file was too large
376
423
  if (!s3Data) {
377
424
  console.log(`⏭️ Skipping artifact ${attachment.name} - upload failed or file too large`);
@@ -381,7 +428,7 @@ class TestLensReporter {
381
428
  testId,
382
429
  type: this.getArtifactType(attachment.name),
383
430
  path: attachment.path,
384
- name: attachment.name,
431
+ name: fileName,
385
432
  contentType: attachment.contentType,
386
433
  fileSize: this.getFileSize(attachment.path),
387
434
  storageType: 's3',
@@ -395,7 +442,7 @@ class TestLensReporter {
395
442
  timestamp: new Date().toISOString(),
396
443
  artifact: artifactData
397
444
  });
398
- console.log(`📎 Processed artifact: ${attachment.name} (uploaded to S3)`);
445
+ console.log(`📎 Processed artifact: ${fileName} (uploaded to S3)`);
399
446
  }
400
447
  catch (error) {
401
448
  console.error(`❌ Failed to process artifact ${attachment.name}:`, error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testlens-playwright-reporter",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
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",