testlens-playwright-reporter 0.2.5 â 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 +10 -2
- package/index.js +72 -8
- package/index.ts +76 -10
- package/lib/index.js +64 -8
- package/package.json +1 -1
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 */
|
|
@@ -55,8 +63,8 @@ export interface GitInfo {
|
|
|
55
63
|
commit: string;
|
|
56
64
|
shortCommit: string;
|
|
57
65
|
author: string;
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
message: string;
|
|
67
|
+
timestamp: string;
|
|
60
68
|
isDirty: boolean;
|
|
61
69
|
remoteName: string;
|
|
62
70
|
remoteUrl: string;
|
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,
|
|
@@ -147,6 +149,13 @@ class TestLensReporter {
|
|
|
147
149
|
// Collect Git information if enabled
|
|
148
150
|
if (this.config.enableGitInfo) {
|
|
149
151
|
this.runMetadata.gitInfo = await this.collectGitInfo();
|
|
152
|
+
if (this.runMetadata.gitInfo) {
|
|
153
|
+
console.log(`đĻ Git info collected: branch=${this.runMetadata.gitInfo.branch}, commit=${this.runMetadata.gitInfo.shortCommit}, author=${this.runMetadata.gitInfo.author}`);
|
|
154
|
+
} else {
|
|
155
|
+
console.log(`â ī¸ Git info collection returned null - not in a git repository or git not available`);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
console.log(`âšī¸ Git info collection disabled (enableGitInfo: false)`);
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
// Add shard information if available
|
|
@@ -309,8 +318,10 @@ class TestLensReporter {
|
|
|
309
318
|
// Calculate final stats
|
|
310
319
|
const totalTests = Array.from(this.testMap.values()).length;
|
|
311
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'
|
|
312
322
|
const failedTests = Array.from(this.testMap.values()).filter(t => t.status === 'failed').length;
|
|
313
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)
|
|
314
325
|
const timedOutTests = Array.from(this.testMap.values()).filter(t => t.originalStatus === 'timedOut').length;
|
|
315
326
|
|
|
316
327
|
// Normalize run status - if there are timeouts, treat run as failed
|
|
@@ -326,9 +337,9 @@ class TestLensReporter {
|
|
|
326
337
|
...this.runMetadata,
|
|
327
338
|
totalTests,
|
|
328
339
|
passedTests,
|
|
329
|
-
failedTests
|
|
340
|
+
failedTests, // Already includes timedOut tests (normalized to 'failed')
|
|
330
341
|
skippedTests,
|
|
331
|
-
timedOutTests,
|
|
342
|
+
timedOutTests, // For informational purposes
|
|
332
343
|
status: normalizedRunStatus
|
|
333
344
|
}
|
|
334
345
|
});
|
|
@@ -338,7 +349,7 @@ class TestLensReporter {
|
|
|
338
349
|
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
339
350
|
|
|
340
351
|
console.log(`đ TestLens Report completed - Run ID: ${this.runId}`);
|
|
341
|
-
console.log(`đ¯ Results: ${passedTests} passed, ${failedTests
|
|
352
|
+
console.log(`đ¯ Results: ${passedTests} passed, ${failedTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
|
|
342
353
|
}
|
|
343
354
|
|
|
344
355
|
async sendToApi(payload) {
|
|
@@ -379,17 +390,70 @@ class TestLensReporter {
|
|
|
379
390
|
for (let i = 0; i < attachments.length; i++) {
|
|
380
391
|
const attachment = attachments[i];
|
|
381
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
|
+
|
|
382
410
|
try {
|
|
383
411
|
console.log(`đ¤ Processing ${attachment.name} asynchronously...`);
|
|
384
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
|
+
|
|
385
443
|
// Upload to S3 first
|
|
386
|
-
const s3Data = await this.uploadArtifactToS3(attachment.path, testId,
|
|
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
|
+
}
|
|
387
451
|
|
|
388
452
|
const artifactData = {
|
|
389
453
|
testId,
|
|
390
454
|
type: this.getArtifactType(attachment.name),
|
|
391
455
|
path: attachment.path,
|
|
392
|
-
name:
|
|
456
|
+
name: fileName,
|
|
393
457
|
contentType: attachment.contentType,
|
|
394
458
|
fileSize: this.getFileSize(attachment.path),
|
|
395
459
|
storageType: 's3',
|
|
@@ -405,7 +469,7 @@ class TestLensReporter {
|
|
|
405
469
|
artifact: artifactData
|
|
406
470
|
});
|
|
407
471
|
|
|
408
|
-
console.log(`đ Processed artifact: ${
|
|
472
|
+
console.log(`đ Processed artifact: ${fileName} (uploaded to S3)`);
|
|
409
473
|
} catch (error) {
|
|
410
474
|
console.error(`â Failed to process ${attachment.name}:`, error.message);
|
|
411
475
|
}
|
|
@@ -543,8 +607,8 @@ class TestLensReporter {
|
|
|
543
607
|
commit,
|
|
544
608
|
shortCommit,
|
|
545
609
|
author,
|
|
546
|
-
commitMessage,
|
|
547
|
-
commitTimestamp,
|
|
610
|
+
message: commitMessage,
|
|
611
|
+
timestamp: commitTimestamp,
|
|
548
612
|
isDirty,
|
|
549
613
|
remoteName,
|
|
550
614
|
remoteUrl
|
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 */
|
|
@@ -76,8 +84,8 @@ export interface GitInfo {
|
|
|
76
84
|
commit: string;
|
|
77
85
|
shortCommit: string;
|
|
78
86
|
author: string;
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
message: string;
|
|
88
|
+
timestamp: string;
|
|
81
89
|
isDirty: boolean;
|
|
82
90
|
remoteName: string;
|
|
83
91
|
remoteUrl: string;
|
|
@@ -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,
|
|
@@ -276,6 +286,13 @@ export class TestLensReporter implements Reporter {
|
|
|
276
286
|
// Collect Git information if enabled
|
|
277
287
|
if (this.config.enableGitInfo) {
|
|
278
288
|
this.runMetadata.gitInfo = await this.collectGitInfo();
|
|
289
|
+
if (this.runMetadata.gitInfo) {
|
|
290
|
+
console.log(`đĻ Git info collected: branch=${this.runMetadata.gitInfo.branch}, commit=${this.runMetadata.gitInfo.shortCommit}, author=${this.runMetadata.gitInfo.author}`);
|
|
291
|
+
} else {
|
|
292
|
+
console.log(`â ī¸ Git info collection returned null - not in a git repository or git not available`);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
console.log(`âšī¸ Git info collection disabled (enableGitInfo: false)`);
|
|
279
296
|
}
|
|
280
297
|
|
|
281
298
|
// Add shard information if available
|
|
@@ -438,8 +455,10 @@ export class TestLensReporter implements Reporter {
|
|
|
438
455
|
// Calculate final stats
|
|
439
456
|
const totalTests = Array.from(this.testMap.values()).length;
|
|
440
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'
|
|
441
459
|
const failedTests = Array.from(this.testMap.values()).filter(t => t.status === 'failed').length;
|
|
442
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)
|
|
443
462
|
const timedOutTests = Array.from(this.testMap.values()).filter(t => t.originalStatus === 'timedOut').length;
|
|
444
463
|
|
|
445
464
|
// Normalize run status - if there are timeouts, treat run as failed
|
|
@@ -455,15 +474,15 @@ export class TestLensReporter implements Reporter {
|
|
|
455
474
|
...this.runMetadata,
|
|
456
475
|
totalTests,
|
|
457
476
|
passedTests,
|
|
458
|
-
failedTests
|
|
477
|
+
failedTests, // Already includes timedOut tests (normalized to 'failed')
|
|
459
478
|
skippedTests,
|
|
460
|
-
timedOutTests,
|
|
479
|
+
timedOutTests, // For informational purposes
|
|
461
480
|
status: normalizedRunStatus
|
|
462
481
|
}
|
|
463
482
|
});
|
|
464
483
|
|
|
465
484
|
console.log(`đ TestLens Report completed - Run ID: ${this.runId}`);
|
|
466
|
-
console.log(`đ¯ Results: ${passedTests} passed, ${failedTests
|
|
485
|
+
console.log(`đ¯ Results: ${passedTests} passed, ${failedTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
|
|
467
486
|
}
|
|
468
487
|
|
|
469
488
|
private async sendToApi(payload: any): Promise<void> {
|
|
@@ -493,9 +512,56 @@ export class TestLensReporter implements Reporter {
|
|
|
493
512
|
|
|
494
513
|
for (const attachment of attachments) {
|
|
495
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
|
+
|
|
496
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
|
+
|
|
497
563
|
// Upload to S3 first
|
|
498
|
-
const s3Data = await this.uploadArtifactToS3(attachment.path, testId,
|
|
564
|
+
const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
|
|
499
565
|
|
|
500
566
|
// Skip if upload failed or file was too large
|
|
501
567
|
if (!s3Data) {
|
|
@@ -507,7 +573,7 @@ export class TestLensReporter implements Reporter {
|
|
|
507
573
|
testId,
|
|
508
574
|
type: this.getArtifactType(attachment.name),
|
|
509
575
|
path: attachment.path,
|
|
510
|
-
name:
|
|
576
|
+
name: fileName,
|
|
511
577
|
contentType: attachment.contentType,
|
|
512
578
|
fileSize: this.getFileSize(attachment.path),
|
|
513
579
|
storageType: 's3',
|
|
@@ -523,7 +589,7 @@ export class TestLensReporter implements Reporter {
|
|
|
523
589
|
artifact: artifactData
|
|
524
590
|
});
|
|
525
591
|
|
|
526
|
-
console.log(`đ Processed artifact: ${
|
|
592
|
+
console.log(`đ Processed artifact: ${fileName} (uploaded to S3)`);
|
|
527
593
|
} catch (error) {
|
|
528
594
|
console.error(`â Failed to process artifact ${attachment.name}:`, (error as Error).message);
|
|
529
595
|
}
|
|
@@ -657,8 +723,8 @@ export class TestLensReporter implements Reporter {
|
|
|
657
723
|
commit,
|
|
658
724
|
shortCommit,
|
|
659
725
|
author,
|
|
660
|
-
commitMessage,
|
|
661
|
-
commitTimestamp,
|
|
726
|
+
message: commitMessage,
|
|
727
|
+
timestamp: commitTimestamp,
|
|
662
728
|
isDirty,
|
|
663
729
|
remoteName,
|
|
664
730
|
remoteUrl
|
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,
|
|
@@ -168,6 +170,15 @@ class TestLensReporter {
|
|
|
168
170
|
// Collect Git information if enabled
|
|
169
171
|
if (this.config.enableGitInfo) {
|
|
170
172
|
this.runMetadata.gitInfo = await this.collectGitInfo();
|
|
173
|
+
if (this.runMetadata.gitInfo) {
|
|
174
|
+
console.log(`đĻ Git info collected: branch=${this.runMetadata.gitInfo.branch}, commit=${this.runMetadata.gitInfo.shortCommit}, author=${this.runMetadata.gitInfo.author}`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.log(`â ī¸ Git info collection returned null - not in a git repository or git not available`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log(`âšī¸ Git info collection disabled (enableGitInfo: false)`);
|
|
171
182
|
}
|
|
172
183
|
// Add shard information if available
|
|
173
184
|
if (config.shard) {
|
|
@@ -311,8 +322,10 @@ class TestLensReporter {
|
|
|
311
322
|
// Calculate final stats
|
|
312
323
|
const totalTests = Array.from(this.testMap.values()).length;
|
|
313
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'
|
|
314
326
|
const failedTests = Array.from(this.testMap.values()).filter(t => t.status === 'failed').length;
|
|
315
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)
|
|
316
329
|
const timedOutTests = Array.from(this.testMap.values()).filter(t => t.originalStatus === 'timedOut').length;
|
|
317
330
|
// Normalize run status - if there are timeouts, treat run as failed
|
|
318
331
|
const hasTimeouts = timedOutTests > 0;
|
|
@@ -326,14 +339,14 @@ class TestLensReporter {
|
|
|
326
339
|
...this.runMetadata,
|
|
327
340
|
totalTests,
|
|
328
341
|
passedTests,
|
|
329
|
-
failedTests
|
|
342
|
+
failedTests, // Already includes timedOut tests (normalized to 'failed')
|
|
330
343
|
skippedTests,
|
|
331
|
-
timedOutTests,
|
|
344
|
+
timedOutTests, // For informational purposes
|
|
332
345
|
status: normalizedRunStatus
|
|
333
346
|
}
|
|
334
347
|
});
|
|
335
348
|
console.log(`đ TestLens Report completed - Run ID: ${this.runId}`);
|
|
336
|
-
console.log(`đ¯ Results: ${passedTests} passed, ${failedTests
|
|
349
|
+
console.log(`đ¯ Results: ${passedTests} passed, ${failedTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
|
|
337
350
|
}
|
|
338
351
|
async sendToApi(payload) {
|
|
339
352
|
try {
|
|
@@ -360,9 +373,52 @@ class TestLensReporter {
|
|
|
360
373
|
const attachments = result.attachments;
|
|
361
374
|
for (const attachment of attachments) {
|
|
362
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
|
+
}
|
|
363
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
|
+
}
|
|
364
420
|
// Upload to S3 first
|
|
365
|
-
const s3Data = await this.uploadArtifactToS3(attachment.path, testId,
|
|
421
|
+
const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName);
|
|
366
422
|
// Skip if upload failed or file was too large
|
|
367
423
|
if (!s3Data) {
|
|
368
424
|
console.log(`âī¸ Skipping artifact ${attachment.name} - upload failed or file too large`);
|
|
@@ -372,7 +428,7 @@ class TestLensReporter {
|
|
|
372
428
|
testId,
|
|
373
429
|
type: this.getArtifactType(attachment.name),
|
|
374
430
|
path: attachment.path,
|
|
375
|
-
name:
|
|
431
|
+
name: fileName,
|
|
376
432
|
contentType: attachment.contentType,
|
|
377
433
|
fileSize: this.getFileSize(attachment.path),
|
|
378
434
|
storageType: 's3',
|
|
@@ -386,7 +442,7 @@ class TestLensReporter {
|
|
|
386
442
|
timestamp: new Date().toISOString(),
|
|
387
443
|
artifact: artifactData
|
|
388
444
|
});
|
|
389
|
-
console.log(`đ Processed artifact: ${
|
|
445
|
+
console.log(`đ Processed artifact: ${fileName} (uploaded to S3)`);
|
|
390
446
|
}
|
|
391
447
|
catch (error) {
|
|
392
448
|
console.error(`â Failed to process artifact ${attachment.name}:`, error.message);
|
|
@@ -507,8 +563,8 @@ class TestLensReporter {
|
|
|
507
563
|
commit,
|
|
508
564
|
shortCommit,
|
|
509
565
|
author,
|
|
510
|
-
commitMessage,
|
|
511
|
-
commitTimestamp,
|
|
566
|
+
message: commitMessage,
|
|
567
|
+
timestamp: commitTimestamp,
|
|
512
568
|
isDirty,
|
|
513
569
|
remoteName,
|
|
514
570
|
remoteUrl
|
package/package.json
CHANGED