@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 falseResetOnEachRun;
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 : false;
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.shardIndex === undefined || this.shardIndex === 0) {
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, // The original, human-readable name
298
- path: relativeDestPath, // The safe, relative path for linking
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
- const existingTestIndex = this.results.findIndex((r) => r.id === test.id);
308
- if (existingTestIndex !== -1) {
309
- if (pulseResult.retries >= this.results[existingTestIndex].retries) {
310
- this.results[existingTestIndex] = pulseResult;
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
- else {
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
- let finalUniqueResultsMap = new Map();
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
- totalTests: 0,
436
- passed: 0,
437
- failed: 0,
438
- skipped: 0,
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: properlyTypedResults,
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
- if (this.resetOnEachRun == true) {
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, (key, value) => {
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
- console.warn("PlaywrightPulseReporter: resetOnEachRun is set to false. The finalReport will display all the results present in '/pulse-results'.");
500
- const finalOutputPath = path.join(`${this.outputDir}/pulse-results`, `playwright-pulse-report-${Date.now()}.json`);
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(this.outputDir);
503
- await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
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: JSON report written to ${finalOutputPath}`);
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 final JSON report to ${finalOutputPath}. Error: ${error.message}`);
484
+ console.error(`Pulse Reporter: Failed to write or merge report. Error: ${error.message}`);
516
485
  }
517
- finally {
518
- if (this.isSharded) {
519
- await this._cleanupTemporaryFiles();
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
- await this.falseResetOnEachRun();
504
+ return;
522
505
  }
506
+ console.error(`Pulse Reporter: Error reading report directory ${pulseResultsDir}:`, error);
507
+ return;
523
508
  }
524
- }
525
- async falseResetOnEachRun() {
526
- const REPORT_DIR = "./pulse-report"; // Or change this to your reports directory
527
- const OUTPUT_FILE = "playwright-pulse-report.json";
528
- function getReportFiles(dir) {
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
- function mergeReports(files) {
535
- var _a;
536
- let combinedRun = {
537
- totalTests: 0,
538
- passed: 0,
539
- failed: 0,
540
- skipped: 0,
541
- duration: 0,
542
- environment: {},
543
- };
544
- let combinedResults = [];
545
- let latestTimestamp = "";
546
- let latestGeneratedAt = "";
547
- for (const file of files) {
548
- const filePath = path.join(`${REPORT_DIR}/pulse-results`, file);
549
- const json = JSON.parse(fsSync.readFileSync(filePath, "utf-8"));
550
- const run = json.run || {};
551
- combinedRun.totalTests += run.totalTests || 0;
552
- combinedRun.passed += run.passed || 0;
553
- combinedRun.failed += run.failed || 0;
554
- combinedRun.skipped += run.skipped || 0;
555
- combinedRun.duration += run.duration || 0;
556
- combinedRun.environment = run.environment || {};
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
- combinedResults.push(...json.results);
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
- const finalJson = {
566
- run: {
567
- id: `merged-${Date.now()}`,
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
- // Main execution
579
- const reportFiles = getReportFiles(REPORT_DIR);
580
- if (reportFiles.length === 0) {
581
- console.log("No matching JSON report files found.");
582
- process.exit(1);
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-22",
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": [