@arghajit/dummy 0.1.0-beta-22 → 0.1.0-beta-24
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.
|
@@ -19,6 +19,7 @@ export declare class PlaywrightPulseReporter implements Reporter {
|
|
|
19
19
|
private getBrowserDetails;
|
|
20
20
|
private processStep;
|
|
21
21
|
onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
|
|
22
|
+
private _getFinalizedResults;
|
|
22
23
|
onError(error: any): void;
|
|
23
24
|
private _getEnvDetails;
|
|
24
25
|
private _writeShardResults;
|
|
@@ -26,6 +27,6 @@ export declare class PlaywrightPulseReporter implements Reporter {
|
|
|
26
27
|
private _cleanupTemporaryFiles;
|
|
27
28
|
private _ensureDirExists;
|
|
28
29
|
onEnd(result: FullResult): Promise<void>;
|
|
29
|
-
private
|
|
30
|
+
private _mergeAllRunReports;
|
|
30
31
|
}
|
|
31
32
|
export default PlaywrightPulseReporter;
|
|
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.PlaywrightPulseReporter = void 0;
|
|
37
37
|
const fs = __importStar(require("fs/promises"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
-
const fsSync = __importStar(require("fs"));
|
|
40
39
|
const crypto_1 = require("crypto");
|
|
41
40
|
const ua_parser_js_1 = require("ua-parser-js");
|
|
42
41
|
const os = __importStar(require("os"));
|
|
@@ -61,6 +60,7 @@ const convertStatus = (status, testCase) => {
|
|
|
61
60
|
};
|
|
62
61
|
const TEMP_SHARD_FILE_PREFIX = ".pulse-shard-results-";
|
|
63
62
|
const ATTACHMENTS_SUBDIR = "attachments";
|
|
63
|
+
const INDIVIDUAL_REPORTS_SUBDIR = "pulse-results";
|
|
64
64
|
class PlaywrightPulseReporter {
|
|
65
65
|
constructor(options = {}) {
|
|
66
66
|
var _a, _b, _c;
|
|
@@ -72,7 +72,7 @@ class PlaywrightPulseReporter {
|
|
|
72
72
|
this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
|
|
73
73
|
this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
|
|
74
74
|
this.attachmentsDir = path.join(this.outputDir, ATTACHMENTS_SUBDIR);
|
|
75
|
-
this.resetOnEachRun = (_c = options.resetOnEachRun) !== null && _c !== void 0 ? _c :
|
|
75
|
+
this.resetOnEachRun = (_c = options.resetOnEachRun) !== null && _c !== void 0 ? _c : true;
|
|
76
76
|
}
|
|
77
77
|
printsToStdio() {
|
|
78
78
|
return this.shardIndex === undefined || this.shardIndex === 0;
|
|
@@ -96,7 +96,7 @@ class PlaywrightPulseReporter {
|
|
|
96
96
|
: undefined;
|
|
97
97
|
this._ensureDirExists(this.outputDir)
|
|
98
98
|
.then(() => {
|
|
99
|
-
if (this.
|
|
99
|
+
if (this.printsToStdio()) {
|
|
100
100
|
console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Pulse outputting to ${this.outputDir}`);
|
|
101
101
|
if (this.shardIndex === undefined ||
|
|
102
102
|
(this.isSharded && this.shardIndex === 0)) {
|
|
@@ -262,27 +262,19 @@ class PlaywrightPulseReporter {
|
|
|
262
262
|
stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
|
|
263
263
|
...testSpecificData,
|
|
264
264
|
};
|
|
265
|
-
// --- CORRECTED ATTACHMENT PROCESSING LOGIC ---
|
|
266
265
|
for (const [index, attachment] of result.attachments.entries()) {
|
|
267
266
|
if (!attachment.path)
|
|
268
267
|
continue;
|
|
269
268
|
try {
|
|
270
|
-
// Create a sanitized, unique folder name for this specific test
|
|
271
269
|
const testSubfolder = test.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
272
|
-
// Sanitize the original attachment name to create a safe filename
|
|
273
270
|
const safeAttachmentName = path
|
|
274
271
|
.basename(attachment.path)
|
|
275
272
|
.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
276
|
-
// Create a unique filename to prevent collisions, especially in retries
|
|
277
273
|
const uniqueFileName = `${index}-${Date.now()}-${safeAttachmentName}`;
|
|
278
|
-
// This is the relative path that will be stored in the JSON report
|
|
279
274
|
const relativeDestPath = path.join(ATTACHMENTS_SUBDIR, testSubfolder, uniqueFileName);
|
|
280
|
-
// This is the absolute path used for the actual file system operation
|
|
281
275
|
const absoluteDestPath = path.join(this.outputDir, relativeDestPath);
|
|
282
|
-
// Ensure the unique, test-specific attachment directory exists
|
|
283
276
|
await this._ensureDirExists(path.dirname(absoluteDestPath));
|
|
284
277
|
await fs.copyFile(attachment.path, absoluteDestPath);
|
|
285
|
-
// Categorize the attachment based on its content type
|
|
286
278
|
if (attachment.contentType.startsWith("image/")) {
|
|
287
279
|
(_j = pulseResult.screenshots) === null || _j === void 0 ? void 0 : _j.push(relativeDestPath);
|
|
288
280
|
}
|
|
@@ -294,8 +286,8 @@ class PlaywrightPulseReporter {
|
|
|
294
286
|
}
|
|
295
287
|
else {
|
|
296
288
|
(_l = pulseResult.attachments) === null || _l === void 0 ? void 0 : _l.push({
|
|
297
|
-
name: attachment.name,
|
|
298
|
-
path: relativeDestPath,
|
|
289
|
+
name: attachment.name,
|
|
290
|
+
path: relativeDestPath,
|
|
299
291
|
contentType: attachment.contentType,
|
|
300
292
|
});
|
|
301
293
|
}
|
|
@@ -304,15 +296,18 @@ class PlaywrightPulseReporter {
|
|
|
304
296
|
console.error(`Pulse Reporter: Failed to process attachment "${attachment.name}" for test ${pulseResult.name}. Error: ${err.message}`);
|
|
305
297
|
}
|
|
306
298
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
299
|
+
this.results.push(pulseResult);
|
|
300
|
+
}
|
|
301
|
+
_getFinalizedResults(allResults) {
|
|
302
|
+
const finalResultsMap = new Map();
|
|
303
|
+
for (const result of allResults) {
|
|
304
|
+
const existing = finalResultsMap.get(result.id);
|
|
305
|
+
// Keep the result with the highest retry attempt for each test ID
|
|
306
|
+
if (!existing || result.retries >= existing.retries) {
|
|
307
|
+
finalResultsMap.set(result.id, result);
|
|
311
308
|
}
|
|
312
309
|
}
|
|
313
|
-
|
|
314
|
-
this.results.push(pulseResult);
|
|
315
|
-
}
|
|
310
|
+
return Array.from(finalResultsMap.values());
|
|
316
311
|
}
|
|
317
312
|
onError(error) {
|
|
318
313
|
var _a;
|
|
@@ -367,14 +362,7 @@ class PlaywrightPulseReporter {
|
|
|
367
362
|
}
|
|
368
363
|
}
|
|
369
364
|
}
|
|
370
|
-
|
|
371
|
-
for (const result of allShardProcessedResults) {
|
|
372
|
-
const existing = finalUniqueResultsMap.get(result.id);
|
|
373
|
-
if (!existing || result.retries >= existing.retries) {
|
|
374
|
-
finalUniqueResultsMap.set(result.id, result);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
const finalResultsList = Array.from(finalUniqueResultsMap.values());
|
|
365
|
+
const finalResultsList = this._getFinalizedResults(allShardProcessedResults);
|
|
378
366
|
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
379
367
|
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
380
368
|
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
@@ -425,6 +413,8 @@ class PlaywrightPulseReporter {
|
|
|
425
413
|
await this._writeShardResults();
|
|
426
414
|
return;
|
|
427
415
|
}
|
|
416
|
+
// De-duplicate and handle retries here, in a safe, single-threaded context.
|
|
417
|
+
const finalResults = this._getFinalizedResults(this.results);
|
|
428
418
|
const runEndTime = Date.now();
|
|
429
419
|
const duration = runEndTime - this.runStartTime;
|
|
430
420
|
const runId = `run-${this.runStartTime}-581d5ad8-ce75-4ca5-94a6-ed29c466c815`;
|
|
@@ -432,38 +422,25 @@ class PlaywrightPulseReporter {
|
|
|
432
422
|
const runData = {
|
|
433
423
|
id: runId,
|
|
434
424
|
timestamp: new Date(this.runStartTime),
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
425
|
+
// Use the length of the de-duplicated array for all counts
|
|
426
|
+
totalTests: finalResults.length,
|
|
427
|
+
passed: finalResults.filter((r) => r.status === "passed").length,
|
|
428
|
+
failed: finalResults.filter((r) => r.status === "failed").length,
|
|
429
|
+
skipped: finalResults.filter((r) => r.status === "skipped").length,
|
|
439
430
|
duration,
|
|
440
431
|
environment: environmentDetails,
|
|
441
432
|
};
|
|
433
|
+
finalResults.forEach((r) => (r.runId = runId));
|
|
442
434
|
let finalReport = undefined;
|
|
443
435
|
if (this.isSharded) {
|
|
436
|
+
// The _mergeShardResults method will handle its own de-duplication
|
|
444
437
|
finalReport = await this._mergeShardResults(runData);
|
|
445
|
-
if (finalReport && finalReport.run && !finalReport.run.environment) {
|
|
446
|
-
finalReport.run.environment = environmentDetails;
|
|
447
|
-
}
|
|
448
438
|
}
|
|
449
439
|
else {
|
|
450
|
-
this.results.forEach((r) => (r.runId = runId));
|
|
451
|
-
runData.passed = this.results.filter((r) => r.status === "passed").length;
|
|
452
|
-
runData.failed = this.results.filter((r) => r.status === "failed").length;
|
|
453
|
-
runData.skipped = this.results.filter((r) => r.status === "skipped").length;
|
|
454
|
-
runData.totalTests = this.results.length;
|
|
455
|
-
const reviveDates = (key, value) => {
|
|
456
|
-
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
457
|
-
if (typeof value === "string" && isoDateRegex.test(value)) {
|
|
458
|
-
const date = new Date(value);
|
|
459
|
-
return !isNaN(date.getTime()) ? date : value;
|
|
460
|
-
}
|
|
461
|
-
return value;
|
|
462
|
-
};
|
|
463
|
-
const properlyTypedResults = JSON.parse(JSON.stringify(this.results), reviveDates);
|
|
464
440
|
finalReport = {
|
|
465
441
|
run: runData,
|
|
466
|
-
results
|
|
442
|
+
// Use the de-duplicated results
|
|
443
|
+
results: finalResults,
|
|
467
444
|
metadata: { generatedAt: new Date().toISOString() },
|
|
468
445
|
};
|
|
469
446
|
}
|
|
@@ -471,17 +448,18 @@ class PlaywrightPulseReporter {
|
|
|
471
448
|
console.error("PlaywrightPulseReporter: CRITICAL - finalReport object was not generated. Cannot create summary.");
|
|
472
449
|
return;
|
|
473
450
|
}
|
|
474
|
-
|
|
451
|
+
const jsonReplacer = (key, value) => {
|
|
452
|
+
if (value instanceof Date)
|
|
453
|
+
return value.toISOString();
|
|
454
|
+
if (typeof value === "bigint")
|
|
455
|
+
return value.toString();
|
|
456
|
+
return value;
|
|
457
|
+
};
|
|
458
|
+
if (this.resetOnEachRun) {
|
|
475
459
|
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
476
460
|
try {
|
|
477
461
|
await this._ensureDirExists(this.outputDir);
|
|
478
|
-
await fs.writeFile(finalOutputPath, JSON.stringify(finalReport,
|
|
479
|
-
if (value instanceof Date)
|
|
480
|
-
return value.toISOString();
|
|
481
|
-
if (typeof value === "bigint")
|
|
482
|
-
return value.toString();
|
|
483
|
-
return value;
|
|
484
|
-
}, 2));
|
|
462
|
+
await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, jsonReplacer, 2));
|
|
485
463
|
if (this.printsToStdio()) {
|
|
486
464
|
console.log(`PlaywrightPulseReporter: JSON report written to ${finalOutputPath}`);
|
|
487
465
|
}
|
|
@@ -489,101 +467,109 @@ class PlaywrightPulseReporter {
|
|
|
489
467
|
catch (error) {
|
|
490
468
|
console.error(`Pulse Reporter: Failed to write final JSON report to ${finalOutputPath}. Error: ${error.message}`);
|
|
491
469
|
}
|
|
492
|
-
finally {
|
|
493
|
-
if (this.isSharded) {
|
|
494
|
-
await this._cleanupTemporaryFiles();
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
470
|
}
|
|
498
471
|
else {
|
|
499
|
-
|
|
500
|
-
const
|
|
472
|
+
// Logic for appending/merging reports
|
|
473
|
+
const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
|
|
474
|
+
const individualReportPath = path.join(pulseResultsDir, `playwright-pulse-report-${Date.now()}.json`);
|
|
501
475
|
try {
|
|
502
|
-
await this._ensureDirExists(
|
|
503
|
-
await fs.writeFile(
|
|
504
|
-
if (value instanceof Date)
|
|
505
|
-
return value.toISOString();
|
|
506
|
-
if (typeof value === "bigint")
|
|
507
|
-
return value.toString();
|
|
508
|
-
return value;
|
|
509
|
-
}, 2));
|
|
476
|
+
await this._ensureDirExists(pulseResultsDir);
|
|
477
|
+
await fs.writeFile(individualReportPath, JSON.stringify(finalReport, jsonReplacer, 2));
|
|
510
478
|
if (this.printsToStdio()) {
|
|
511
|
-
console.log(`PlaywrightPulseReporter:
|
|
479
|
+
console.log(`PlaywrightPulseReporter: Individual run report for merging written to ${individualReportPath}`);
|
|
512
480
|
}
|
|
481
|
+
await this._mergeAllRunReports();
|
|
513
482
|
}
|
|
514
483
|
catch (error) {
|
|
515
|
-
console.error(`Pulse Reporter: Failed to write
|
|
484
|
+
console.error(`Pulse Reporter: Failed to write or merge report. Error: ${error.message}`);
|
|
516
485
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
486
|
+
}
|
|
487
|
+
if (this.isSharded) {
|
|
488
|
+
await this._cleanupTemporaryFiles();
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async _mergeAllRunReports() {
|
|
492
|
+
const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
|
|
493
|
+
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
494
|
+
let reportFiles;
|
|
495
|
+
try {
|
|
496
|
+
const allFiles = await fs.readdir(pulseResultsDir);
|
|
497
|
+
reportFiles = allFiles.filter((file) => file.startsWith("playwright-pulse-report-") && file.endsWith(".json"));
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
if (error.code === "ENOENT") {
|
|
501
|
+
if (this.printsToStdio()) {
|
|
502
|
+
console.log(`Pulse Reporter: No individual reports directory found at ${pulseResultsDir}. Skipping merge.`);
|
|
520
503
|
}
|
|
521
|
-
|
|
504
|
+
return;
|
|
522
505
|
}
|
|
506
|
+
console.error(`Pulse Reporter: Error reading report directory ${pulseResultsDir}:`, error);
|
|
507
|
+
return;
|
|
523
508
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return fsSync
|
|
530
|
-
.readdirSync(dir)
|
|
531
|
-
.filter((file) => file.startsWith("playwright-pulse-report-") &&
|
|
532
|
-
file.endsWith(".json"));
|
|
509
|
+
if (reportFiles.length === 0) {
|
|
510
|
+
if (this.printsToStdio()) {
|
|
511
|
+
console.log("Pulse Reporter: No matching JSON report files found to merge.");
|
|
512
|
+
}
|
|
513
|
+
return;
|
|
533
514
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
515
|
+
const combinedRun = {
|
|
516
|
+
id: `merged-${Date.now()}`,
|
|
517
|
+
timestamp: new Date(0),
|
|
518
|
+
totalTests: 0,
|
|
519
|
+
passed: 0,
|
|
520
|
+
failed: 0,
|
|
521
|
+
skipped: 0,
|
|
522
|
+
duration: 0,
|
|
523
|
+
environment: undefined,
|
|
524
|
+
};
|
|
525
|
+
const allResults = [];
|
|
526
|
+
let latestTimestamp = new Date(0);
|
|
527
|
+
for (const file of reportFiles) {
|
|
528
|
+
const filePath = path.join(pulseResultsDir, file);
|
|
529
|
+
try {
|
|
530
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
531
|
+
const json = JSON.parse(content);
|
|
532
|
+
if (json.run) {
|
|
533
|
+
combinedRun.totalTests += json.run.totalTests || 0;
|
|
534
|
+
combinedRun.passed += json.run.passed || 0;
|
|
535
|
+
combinedRun.failed += json.run.failed || 0;
|
|
536
|
+
combinedRun.skipped += json.run.skipped || 0;
|
|
537
|
+
combinedRun.duration += json.run.duration || 0;
|
|
538
|
+
const runTimestamp = new Date(json.run.timestamp);
|
|
539
|
+
if (runTimestamp > latestTimestamp) {
|
|
540
|
+
latestTimestamp = runTimestamp;
|
|
541
|
+
combinedRun.environment = json.run.environment || undefined;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
557
544
|
if (json.results) {
|
|
558
|
-
|
|
545
|
+
allResults.push(...json.results);
|
|
559
546
|
}
|
|
560
|
-
if (run.timestamp > latestTimestamp)
|
|
561
|
-
latestTimestamp = run.timestamp;
|
|
562
|
-
if (((_a = json.metadata) === null || _a === void 0 ? void 0 : _a.generatedAt) > latestGeneratedAt)
|
|
563
|
-
latestGeneratedAt = json.metadata.generatedAt;
|
|
564
547
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
timestamp: latestTimestamp,
|
|
569
|
-
...combinedRun,
|
|
570
|
-
},
|
|
571
|
-
results: combinedResults,
|
|
572
|
-
metadata: {
|
|
573
|
-
generatedAt: latestGeneratedAt,
|
|
574
|
-
},
|
|
575
|
-
};
|
|
576
|
-
return finalJson;
|
|
548
|
+
catch (err) {
|
|
549
|
+
console.warn(`Pulse Reporter: Could not parse report file ${filePath}. Skipping. Error: ${err.message}`);
|
|
550
|
+
}
|
|
577
551
|
}
|
|
578
|
-
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
552
|
+
combinedRun.timestamp = latestTimestamp;
|
|
553
|
+
const finalReport = {
|
|
554
|
+
run: combinedRun,
|
|
555
|
+
results: allResults,
|
|
556
|
+
metadata: {
|
|
557
|
+
generatedAt: new Date().toISOString(),
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
try {
|
|
561
|
+
await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
|
|
562
|
+
if (value instanceof Date)
|
|
563
|
+
return value.toISOString();
|
|
564
|
+
return value;
|
|
565
|
+
}, 2));
|
|
566
|
+
if (this.printsToStdio()) {
|
|
567
|
+
console.log(`PlaywrightPulseReporter: ✅ Merged report with ${allResults.length} total results saved to ${finalOutputPath}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
catch (err) {
|
|
571
|
+
console.error(`Pulse Reporter: Failed to write final merged report to ${finalOutputPath}. Error: ${err.message}`);
|
|
583
572
|
}
|
|
584
|
-
const merged = mergeReports(reportFiles);
|
|
585
|
-
fsSync.writeFileSync(path.join(REPORT_DIR, OUTPUT_FILE), JSON.stringify(merged, null, 2));
|
|
586
|
-
console.log(`✅ Merged report saved as ${OUTPUT_FILE}`);
|
|
587
573
|
}
|
|
588
574
|
}
|
|
589
575
|
exports.PlaywrightPulseReporter = PlaywrightPulseReporter;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arghajit/dummy",
|
|
3
3
|
"author": "Arghajit Singha",
|
|
4
|
-
"version": "0.1.0-beta-
|
|
4
|
+
"version": "0.1.0-beta-24",
|
|
5
5
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
6
|
"homepage": "https://playwright-pulse-report.netlify.app/",
|
|
7
7
|
"keywords": [
|