@arghajit/dummy 0.3.34 → 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
|
-
|
|
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(
|
|
131
|
+
const fsSync = require("fs");
|
|
124
132
|
if (!fsSync.existsSync(filePath)) {
|
|
125
|
-
return
|
|
133
|
+
return "";
|
|
126
134
|
}
|
|
127
|
-
const content = fsSync.readFileSync(filePath,
|
|
128
|
-
const lines = content.split(
|
|
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,9 +234,19 @@ 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);
|
|
249
|
+
const uniqueTestId = `${(project === null || project === void 0 ? void 0 : project.name) || "default"}-${test.id}`;
|
|
232
250
|
// Captured outcome from Playwright
|
|
233
251
|
const outcome = test.outcome();
|
|
234
252
|
// Calculate final status based on the last result (Last-Run-Wins)
|
|
@@ -239,15 +257,15 @@ class PlaywrightPulseReporter {
|
|
|
239
257
|
// Existing behavior: fail if flaky (implied by user request "existing status field should remain failed")
|
|
240
258
|
// If outcome is flaky, status should be 'failed' to indicate initial failure, but final_status is 'passed'
|
|
241
259
|
let testStatus = finalStatus;
|
|
242
|
-
if (outcome ===
|
|
243
|
-
testStatus =
|
|
260
|
+
if (outcome === "flaky") {
|
|
261
|
+
testStatus = "flaky";
|
|
244
262
|
}
|
|
245
263
|
const startTime = new Date(result.startTime);
|
|
246
264
|
const endTime = new Date(startTime.getTime() + result.duration);
|
|
247
265
|
const processAllSteps = async (steps) => {
|
|
248
266
|
let processed = [];
|
|
249
267
|
for (const step of steps) {
|
|
250
|
-
const processedStep = await this.processStep(step,
|
|
268
|
+
const processedStep = await this.processStep(step, uniqueTestId, browserDetails, test);
|
|
251
269
|
processed.push(processedStep);
|
|
252
270
|
if (step.steps && step.steps.length > 0) {
|
|
253
271
|
processedStep.steps = await processAllSteps(step.steps);
|
|
@@ -255,7 +273,7 @@ class PlaywrightPulseReporter {
|
|
|
255
273
|
}
|
|
256
274
|
return processed;
|
|
257
275
|
};
|
|
258
|
-
let codeSnippet =
|
|
276
|
+
let codeSnippet = "";
|
|
259
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)) {
|
|
260
278
|
codeSnippet = this.extractCodeSnippet(test.location.file, test.location.line, test.location.column);
|
|
261
279
|
}
|
|
@@ -284,7 +302,7 @@ class PlaywrightPulseReporter {
|
|
|
284
302
|
: undefined,
|
|
285
303
|
};
|
|
286
304
|
const pulseResult = {
|
|
287
|
-
id:
|
|
305
|
+
id: uniqueTestId,
|
|
288
306
|
runId: "TBD",
|
|
289
307
|
describe: describeBlockName,
|
|
290
308
|
spec_file: specFileName,
|
|
@@ -318,7 +336,7 @@ class PlaywrightPulseReporter {
|
|
|
318
336
|
if (!attachment.path)
|
|
319
337
|
continue;
|
|
320
338
|
try {
|
|
321
|
-
const testSubfolder =
|
|
339
|
+
const testSubfolder = uniqueTestId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
322
340
|
const safeAttachmentName = path
|
|
323
341
|
.basename(attachment.path)
|
|
324
342
|
.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
@@ -487,6 +505,39 @@ class PlaywrightPulseReporter {
|
|
|
487
505
|
}
|
|
488
506
|
}
|
|
489
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
|
+
}
|
|
490
541
|
async _ensureDirExists(dirPath) {
|
|
491
542
|
try {
|
|
492
543
|
await fs.mkdir(dirPath, { recursive: true });
|
|
@@ -499,6 +550,11 @@ class PlaywrightPulseReporter {
|
|
|
499
550
|
}
|
|
500
551
|
}
|
|
501
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);
|
|
502
558
|
if (this.shardIndex !== undefined) {
|
|
503
559
|
await this._writeShardResults();
|
|
504
560
|
return;
|
|
@@ -659,6 +715,16 @@ class PlaywrightPulseReporter {
|
|
|
659
715
|
if (this.printsToStdio()) {
|
|
660
716
|
console.log(`PlaywrightPulseReporter: ✅ Merged report with ${finalMergedResults.length} total results saved to ${finalOutputPath}`);
|
|
661
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
|
+
}
|
|
662
728
|
}
|
|
663
729
|
catch (err) {
|
|
664
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.
|
|
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": {
|