@arghajit/dummy 0.3.36 → 0.3.37

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.
@@ -4,6 +4,7 @@ export declare class PlaywrightPulseReporter implements Reporter {
4
4
  private config;
5
5
  private suite;
6
6
  private results;
7
+ private _pendingTestEnds;
7
8
  private runStartTime;
8
9
  private options;
9
10
  private outputDir;
@@ -21,12 +22,28 @@ export declare class PlaywrightPulseReporter implements Reporter {
21
22
  private getBrowserDetails;
22
23
  private processStep;
23
24
  onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
25
+ private _processTestEnd;
24
26
  private _getFinalizedResults;
25
27
  onError(error: any): void;
26
28
  private _getEnvDetails;
27
29
  private _writeShardResults;
28
30
  private _mergeShardResults;
29
31
  private _cleanupTemporaryFiles;
32
+ /**
33
+ * Removes all individual run JSON files from the `pulse-results/` directory
34
+ * that were left over from previous test sessions.
35
+ *
36
+ * When `resetOnEachRun: false`, each run writes its own timestamped JSON to
37
+ * `pulse-results/` and then `_mergeAllRunReports()` merges them all. However,
38
+ * if files from *older* sessions accumulate there (e.g. because a previous run
39
+ * was interrupted before the post-merge cleanup, or because the user ran tests
40
+ * on a previous day), `_getFinalizedResults()` de-duplicates by `test.id` and
41
+ * collapses results from both sessions into a single entry — producing a
42
+ * `totalTests` count lower than the actual number of tests that ran.
43
+ *
44
+ * Cleaning up at `onBegin` time guarantees each run starts with a fresh slate.
45
+ */
46
+ private _cleanupStaleRunReports;
30
47
  private _ensureDirExists;
31
48
  onEnd(result: FullResult): Promise<void>;
32
49
  private _mergeAllRunReports;
@@ -69,6 +69,7 @@ class PlaywrightPulseReporter {
69
69
  constructor(options = {}) {
70
70
  var _a, _b, _c;
71
71
  this.results = [];
72
+ this._pendingTestEnds = [];
72
73
  this.baseOutputFile = "playwright-pulse-report.json";
73
74
  this.isSharded = false;
74
75
  this.shardIndex = undefined;
@@ -99,14 +100,21 @@ class PlaywrightPulseReporter {
99
100
  ? this.config.shard.current - 1
100
101
  : undefined;
101
102
  this._ensureDirExists(this.outputDir)
102
- .then(() => {
103
+ .then(async () => {
103
104
  if (this.printsToStdio()) {
104
105
  console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Pulse outputting to ${this.outputDir}`);
105
106
  if (this.shardIndex === undefined ||
106
107
  (this.isSharded && this.shardIndex === 0)) {
107
- return this._cleanupTemporaryFiles();
108
+ await this._cleanupTemporaryFiles();
108
109
  }
109
110
  }
111
+ // When not resetting on each run, clear stale individual run files
112
+ // from previous sessions. Without this, _mergeAllRunReports() reads
113
+ // ALL accumulated files and de-duplicates by test.id, which collapses
114
+ // results from different sessions into fewer entries than expected.
115
+ if (!this.resetOnEachRun) {
116
+ await this._cleanupStaleRunReports();
117
+ }
110
118
  })
111
119
  .catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
112
120
  }
@@ -120,19 +128,19 @@ class PlaywrightPulseReporter {
120
128
  extractCodeSnippet(filePath, targetLine, targetColumn) {
121
129
  var _a;
122
130
  try {
123
- const fsSync = require('fs');
131
+ const fsSync = require("fs");
124
132
  if (!fsSync.existsSync(filePath)) {
125
- return '';
133
+ return "";
126
134
  }
127
- const content = fsSync.readFileSync(filePath, 'utf8');
128
- const lines = content.split('\n');
135
+ const content = fsSync.readFileSync(filePath, "utf8");
136
+ const lines = content.split("\n");
129
137
  if (targetLine < 1 || targetLine > lines.length) {
130
- return '';
138
+ return "";
131
139
  }
132
- return ((_a = lines[targetLine - 1]) === null || _a === void 0 ? void 0 : _a.trim()) || '';
140
+ return ((_a = lines[targetLine - 1]) === null || _a === void 0 ? void 0 : _a.trim()) || "";
133
141
  }
134
142
  catch (e) {
135
- return '';
143
+ return "";
136
144
  }
137
145
  }
138
146
  getBrowserDetails(test) {
@@ -199,7 +207,7 @@ class PlaywrightPulseReporter {
199
207
  const startTime = new Date(step.startTime);
200
208
  const endTime = new Date(startTime.getTime() + Math.max(0, duration));
201
209
  let codeLocation = "";
202
- let codeSnippet = '';
210
+ let codeSnippet = "";
203
211
  if (step.location) {
204
212
  codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
205
213
  codeSnippet = this.extractCodeSnippet(step.location.file, step.location.line, step.location.column);
@@ -226,6 +234,15 @@ class PlaywrightPulseReporter {
226
234
  };
227
235
  }
228
236
  async onTestEnd(test, result) {
237
+ // Track this async call so onEnd() can wait for all in-flight processing
238
+ // before it reads this.results. This prevents the race condition where
239
+ // onEnd() fires before an interrupted test's onTestEnd() has finished
240
+ // its async attachment I/O and pushed to this.results.
241
+ const p = this._processTestEnd(test, result);
242
+ this._pendingTestEnds.push(p);
243
+ await p;
244
+ }
245
+ async _processTestEnd(test, result) {
229
246
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
230
247
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
231
248
  const browserDetails = this.getBrowserDetails(test);
@@ -240,8 +257,8 @@ class PlaywrightPulseReporter {
240
257
  // Existing behavior: fail if flaky (implied by user request "existing status field should remain failed")
241
258
  // If outcome is flaky, status should be 'failed' to indicate initial failure, but final_status is 'passed'
242
259
  let testStatus = finalStatus;
243
- if (outcome === 'flaky') {
244
- testStatus = 'flaky';
260
+ if (outcome === "flaky") {
261
+ testStatus = "flaky";
245
262
  }
246
263
  const startTime = new Date(result.startTime);
247
264
  const endTime = new Date(startTime.getTime() + result.duration);
@@ -256,7 +273,7 @@ class PlaywrightPulseReporter {
256
273
  }
257
274
  return processed;
258
275
  };
259
- let codeSnippet = '';
276
+ let codeSnippet = "";
260
277
  if (((_b = test.location) === null || _b === void 0 ? void 0 : _b.file) && ((_c = test.location) === null || _c === void 0 ? void 0 : _c.line) && ((_d = test.location) === null || _d === void 0 ? void 0 : _d.column)) {
261
278
  codeSnippet = this.extractCodeSnippet(test.location.file, test.location.line, test.location.column);
262
279
  }
@@ -488,6 +505,39 @@ class PlaywrightPulseReporter {
488
505
  }
489
506
  }
490
507
  }
508
+ /**
509
+ * Removes all individual run JSON files from the `pulse-results/` directory
510
+ * that were left over from previous test sessions.
511
+ *
512
+ * When `resetOnEachRun: false`, each run writes its own timestamped JSON to
513
+ * `pulse-results/` and then `_mergeAllRunReports()` merges them all. However,
514
+ * if files from *older* sessions accumulate there (e.g. because a previous run
515
+ * was interrupted before the post-merge cleanup, or because the user ran tests
516
+ * on a previous day), `_getFinalizedResults()` de-duplicates by `test.id` and
517
+ * collapses results from both sessions into a single entry — producing a
518
+ * `totalTests` count lower than the actual number of tests that ran.
519
+ *
520
+ * Cleaning up at `onBegin` time guarantees each run starts with a fresh slate.
521
+ */
522
+ async _cleanupStaleRunReports() {
523
+ const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
524
+ try {
525
+ const files = await fs.readdir(pulseResultsDir);
526
+ const staleFiles = files.filter((f) => f.startsWith("playwright-pulse-report-") && f.endsWith(".json"));
527
+ if (staleFiles.length > 0) {
528
+ await Promise.all(staleFiles.map((f) => fs.unlink(path.join(pulseResultsDir, f))));
529
+ if (this.printsToStdio()) {
530
+ console.log(`PlaywrightPulseReporter: Cleaned up ${staleFiles.length} stale run report(s) from ${pulseResultsDir}`);
531
+ }
532
+ }
533
+ }
534
+ catch (error) {
535
+ // ENOENT simply means no previous runs exist — that's fine
536
+ if ((error === null || error === void 0 ? void 0 : error.code) !== "ENOENT") {
537
+ console.warn("Pulse Reporter: Warning during cleanup of stale run reports:", error.message);
538
+ }
539
+ }
540
+ }
491
541
  async _ensureDirExists(dirPath) {
492
542
  try {
493
543
  await fs.mkdir(dirPath, { recursive: true });
@@ -500,6 +550,11 @@ class PlaywrightPulseReporter {
500
550
  }
501
551
  }
502
552
  async onEnd(result) {
553
+ // Wait for ALL in-flight onTestEnd calls to finish before reading this.results.
554
+ // This guards against Playwright calling onEnd() concurrently with (or just
555
+ // before) the last onTestEnd() finishing its async attachment I/O — which
556
+ // would cause that test to be silently dropped from the report.
557
+ await Promise.allSettled(this._pendingTestEnds);
503
558
  if (this.shardIndex !== undefined) {
504
559
  await this._writeShardResults();
505
560
  return;
@@ -660,6 +715,16 @@ class PlaywrightPulseReporter {
660
715
  if (this.printsToStdio()) {
661
716
  console.log(`PlaywrightPulseReporter: ✅ Merged report with ${finalMergedResults.length} total results saved to ${finalOutputPath}`);
662
717
  }
718
+ // Clean up the pulse-results directory after a successful merge
719
+ try {
720
+ await fs.rm(pulseResultsDir, { recursive: true, force: true });
721
+ if (this.printsToStdio()) {
722
+ console.log(`PlaywrightPulseReporter: Cleaned up individual reports directory at ${pulseResultsDir}`);
723
+ }
724
+ }
725
+ catch (cleanupErr) {
726
+ console.warn(`Pulse Reporter: Could not clean up individual reports directory. Error: ${cleanupErr.message}`);
727
+ }
663
728
  }
664
729
  catch (err) {
665
730
  console.error(`Pulse Reporter: Failed to write final merged report to ${finalOutputPath}. Error: ${err.message}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arghajit/dummy",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.3.36",
4
+ "version": "0.3.37",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "homepage": "https://arghajit47.github.io/playwright-pulse/",
7
7
  "repository": {
@@ -98,4 +98,4 @@
98
98
  "safe-buffer": "^5.2.1",
99
99
  "string_decoder": "^1.3.0"
100
100
  }
101
- }
101
+ }