@arghajit/dummy 0.1.2-beta-1 → 0.1.2-beta-3
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.
|
@@ -21,8 +21,13 @@ export declare class PlaywrightPulseReporter implements Reporter {
|
|
|
21
21
|
private processStep;
|
|
22
22
|
onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
|
|
23
23
|
private _getBaseTestId;
|
|
24
|
-
private _getFinalizedResults;
|
|
25
24
|
private _getStatusOrder;
|
|
25
|
+
/**
|
|
26
|
+
* Refactored to group all run attempts for a single logical test case.
|
|
27
|
+
* @param allAttempts An array of all individual test run attempts.
|
|
28
|
+
* @returns An array of ConsolidatedTestResult objects, where each object represents one logical test and contains an array of all its runs.
|
|
29
|
+
*/
|
|
30
|
+
private _getFinalizedResults;
|
|
26
31
|
onError(error: any): void;
|
|
27
32
|
private _getEnvDetails;
|
|
28
33
|
private _writeShardResults;
|
|
@@ -40,14 +40,10 @@ const crypto_1 = require("crypto");
|
|
|
40
40
|
const ua_parser_js_1 = require("ua-parser-js");
|
|
41
41
|
const os = __importStar(require("os"));
|
|
42
42
|
const convertStatus = (status, testCase, retryCount = 0) => {
|
|
43
|
-
// If a test passes on a retry, it's considered flaky regardless of expected status.
|
|
44
|
-
// This is the most critical check for flaky tests.
|
|
45
43
|
if (status === "passed" && retryCount > 0) {
|
|
46
44
|
return "flaky";
|
|
47
45
|
}
|
|
48
|
-
// Handle expected statuses for the final result.
|
|
49
46
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
|
|
50
|
-
// If expected to fail but passed, it's flaky.
|
|
51
47
|
if (status === "passed")
|
|
52
48
|
return "flaky";
|
|
53
49
|
return "failed";
|
|
@@ -55,7 +51,6 @@ const convertStatus = (status, testCase, retryCount = 0) => {
|
|
|
55
51
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "skipped") {
|
|
56
52
|
return "skipped";
|
|
57
53
|
}
|
|
58
|
-
// Default Playwright status mapping
|
|
59
54
|
switch (status) {
|
|
60
55
|
case "passed":
|
|
61
56
|
return "passed";
|
|
@@ -74,12 +69,12 @@ const INDIVIDUAL_REPORTS_SUBDIR = "pulse-results";
|
|
|
74
69
|
class PlaywrightPulseReporter {
|
|
75
70
|
constructor(options = {}) {
|
|
76
71
|
var _a, _b, _c;
|
|
77
|
-
// This will now store all individual run attempts for all tests.
|
|
72
|
+
// This will now store all individual run attempts for all tests using our new local type.
|
|
78
73
|
this.results = [];
|
|
79
74
|
this.baseOutputFile = "playwright-pulse-report.json";
|
|
80
75
|
this.isSharded = false;
|
|
81
76
|
this.shardIndex = undefined;
|
|
82
|
-
this.currentRunId = "";
|
|
77
|
+
this.currentRunId = "";
|
|
83
78
|
this.options = options;
|
|
84
79
|
this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
|
|
85
80
|
this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
|
|
@@ -94,7 +89,6 @@ class PlaywrightPulseReporter {
|
|
|
94
89
|
this.config = config;
|
|
95
90
|
this.suite = suite;
|
|
96
91
|
this.runStartTime = Date.now();
|
|
97
|
-
// Generate the overall runId once at the beginning
|
|
98
92
|
this.currentRunId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
|
|
99
93
|
const configDir = this.config.rootDir;
|
|
100
94
|
const configFileDir = this.config.configFile
|
|
@@ -120,9 +114,7 @@ class PlaywrightPulseReporter {
|
|
|
120
114
|
})
|
|
121
115
|
.catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
|
|
122
116
|
}
|
|
123
|
-
onTestBegin(test) {
|
|
124
|
-
// console.log(`Starting test: ${test.title}`); // Removed for brevity in final output
|
|
125
|
-
}
|
|
117
|
+
onTestBegin(test) { }
|
|
126
118
|
getBrowserDetails(test) {
|
|
127
119
|
var _a, _b, _c, _d;
|
|
128
120
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
@@ -173,8 +165,7 @@ class PlaywrightPulseReporter {
|
|
|
173
165
|
}
|
|
174
166
|
return finalString.trim();
|
|
175
167
|
}
|
|
176
|
-
async processStep(step, testId, browserDetails, testCase, retryCount = 0
|
|
177
|
-
) {
|
|
168
|
+
async processStep(step, testId, browserDetails, testCase, retryCount = 0) {
|
|
178
169
|
var _a, _b, _c, _d;
|
|
179
170
|
let stepStatus = "passed";
|
|
180
171
|
let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
|
|
@@ -182,7 +173,6 @@ class PlaywrightPulseReporter {
|
|
|
182
173
|
stepStatus = "skipped";
|
|
183
174
|
}
|
|
184
175
|
else {
|
|
185
|
-
// Use the extended convertStatus
|
|
186
176
|
stepStatus = convertStatus(step.error ? "failed" : "passed", testCase, retryCount);
|
|
187
177
|
}
|
|
188
178
|
const duration = step.duration;
|
|
@@ -216,15 +206,13 @@ class PlaywrightPulseReporter {
|
|
|
216
206
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
217
207
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
218
208
|
const browserDetails = this.getBrowserDetails(test);
|
|
219
|
-
// Use the extended convertStatus, passing result.retry
|
|
220
209
|
const testStatus = convertStatus(result.status, test, result.retry);
|
|
221
210
|
const startTime = new Date(result.startTime);
|
|
222
211
|
const endTime = new Date(startTime.getTime() + result.duration);
|
|
223
212
|
const processAllSteps = async (steps) => {
|
|
224
213
|
let processed = [];
|
|
225
214
|
for (const step of steps) {
|
|
226
|
-
const processedStep = await this.processStep(step, test.id, browserDetails, test, result.retry
|
|
227
|
-
);
|
|
215
|
+
const processedStep = await this.processStep(step, test.id, browserDetails, test, result.retry);
|
|
228
216
|
processed.push(processedStep);
|
|
229
217
|
if (step.steps && step.steps.length > 0) {
|
|
230
218
|
processedStep.steps = await processAllSteps(step.steps);
|
|
@@ -256,11 +244,11 @@ class PlaywrightPulseReporter {
|
|
|
256
244
|
? JSON.stringify(this.config.metadata)
|
|
257
245
|
: undefined,
|
|
258
246
|
};
|
|
259
|
-
// Correctly handle the ID for each run
|
|
247
|
+
// Correctly handle the ID for each run attempt.
|
|
260
248
|
const testIdWithRunCounter = `${test.id}-run-${result.retry}`;
|
|
261
249
|
const pulseResult = {
|
|
262
|
-
id: testIdWithRunCounter,
|
|
263
|
-
runId: this.currentRunId,
|
|
250
|
+
id: testIdWithRunCounter,
|
|
251
|
+
runId: this.currentRunId,
|
|
264
252
|
name: test.titlePath().join(" > "),
|
|
265
253
|
suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_e = this.config.projects[0]) === null || _e === void 0 ? void 0 : _e.name) || "Default Suite",
|
|
266
254
|
status: testStatus,
|
|
@@ -268,8 +256,8 @@ class PlaywrightPulseReporter {
|
|
|
268
256
|
startTime: startTime,
|
|
269
257
|
endTime: endTime,
|
|
270
258
|
browser: browserDetails,
|
|
271
|
-
retries: result.retry,
|
|
272
|
-
runCounter: result.retry,
|
|
259
|
+
retries: result.retry,
|
|
260
|
+
runCounter: result.retry,
|
|
273
261
|
steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
|
|
274
262
|
errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
|
|
275
263
|
stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
|
|
@@ -288,7 +276,6 @@ class PlaywrightPulseReporter {
|
|
|
288
276
|
if (!attachment.path)
|
|
289
277
|
continue;
|
|
290
278
|
try {
|
|
291
|
-
// Use the new testIdWithRunCounter for the subfolder
|
|
292
279
|
const testSubfolder = testIdWithRunCounter.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
293
280
|
const safeAttachmentName = path
|
|
294
281
|
.basename(attachment.path)
|
|
@@ -326,49 +313,6 @@ class PlaywrightPulseReporter {
|
|
|
326
313
|
const parts = testResultId.split("-run-");
|
|
327
314
|
return parts[0];
|
|
328
315
|
}
|
|
329
|
-
_getFinalizedResults(allResults) {
|
|
330
|
-
const finalResultsMap = new Map();
|
|
331
|
-
const allRunsMap = new Map();
|
|
332
|
-
// First, group all run attempts by their base test ID
|
|
333
|
-
for (const result of allResults) {
|
|
334
|
-
const baseTestId = this._getBaseTestId(result.id);
|
|
335
|
-
if (!allRunsMap.has(baseTestId)) {
|
|
336
|
-
allRunsMap.set(baseTestId, []);
|
|
337
|
-
}
|
|
338
|
-
allRunsMap.get(baseTestId).push(result);
|
|
339
|
-
}
|
|
340
|
-
// Now, iterate through the grouped runs to determine the final state
|
|
341
|
-
for (const [baseTestId, runs] of allRunsMap.entries()) {
|
|
342
|
-
let finalResult = undefined;
|
|
343
|
-
// Sort runs to process them in chronological order
|
|
344
|
-
runs.sort((a, b) => a.runCounter - b.runCounter);
|
|
345
|
-
for (const currentRun of runs) {
|
|
346
|
-
if (!finalResult) {
|
|
347
|
-
finalResult = currentRun;
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
// Compare the current run to the best result found so far
|
|
351
|
-
const currentStatusOrder = this._getStatusOrder(currentRun.status);
|
|
352
|
-
const finalStatusOrder = this._getStatusOrder(finalResult.status);
|
|
353
|
-
if (currentStatusOrder < finalStatusOrder) {
|
|
354
|
-
// Current run is "better" (e.g., passed over failed)
|
|
355
|
-
finalResult = currentRun;
|
|
356
|
-
}
|
|
357
|
-
else if (currentStatusOrder === finalStatusOrder &&
|
|
358
|
-
currentRun.retries > finalResult.retries) {
|
|
359
|
-
// Same status, but prefer the latest attempt
|
|
360
|
-
finalResult = currentRun;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (finalResult) {
|
|
365
|
-
// Ensure the ID of the final result is the base test ID for de-duplication
|
|
366
|
-
finalResult.id = baseTestId;
|
|
367
|
-
finalResultsMap.set(baseTestId, finalResult);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return Array.from(finalResultsMap.values());
|
|
371
|
-
}
|
|
372
316
|
_getStatusOrder(status) {
|
|
373
317
|
switch (status) {
|
|
374
318
|
case "passed":
|
|
@@ -380,9 +324,47 @@ class PlaywrightPulseReporter {
|
|
|
380
324
|
case "skipped":
|
|
381
325
|
return 4;
|
|
382
326
|
default:
|
|
383
|
-
return 99;
|
|
327
|
+
return 99;
|
|
384
328
|
}
|
|
385
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Refactored to group all run attempts for a single logical test case.
|
|
332
|
+
* @param allAttempts An array of all individual test run attempts.
|
|
333
|
+
* @returns An array of ConsolidatedTestResult objects, where each object represents one logical test and contains an array of all its runs.
|
|
334
|
+
*/
|
|
335
|
+
_getFinalizedResults(allAttempts) {
|
|
336
|
+
const groupedResults = new Map();
|
|
337
|
+
for (const attempt of allAttempts) {
|
|
338
|
+
const baseTestId = this._getBaseTestId(attempt.id);
|
|
339
|
+
if (!groupedResults.has(baseTestId)) {
|
|
340
|
+
groupedResults.set(baseTestId, []);
|
|
341
|
+
}
|
|
342
|
+
groupedResults.get(baseTestId).push(attempt);
|
|
343
|
+
}
|
|
344
|
+
const finalResults = [];
|
|
345
|
+
for (const [baseId, runs] of groupedResults.entries()) {
|
|
346
|
+
// Sort runs to find the best status
|
|
347
|
+
runs.sort((a, b) => this._getStatusOrder(a.status) - this._getStatusOrder(b.status));
|
|
348
|
+
const bestRun = runs[0];
|
|
349
|
+
// Calculate total duration from the earliest start to the latest end time of all runs
|
|
350
|
+
const startTimes = runs.map((run) => run.startTime.getTime());
|
|
351
|
+
const endTimes = runs.map((run) => run.endTime.getTime());
|
|
352
|
+
const overallDuration = Math.max(...endTimes) - Math.min(...startTimes);
|
|
353
|
+
finalResults.push({
|
|
354
|
+
id: baseId,
|
|
355
|
+
name: bestRun.name,
|
|
356
|
+
suiteName: bestRun.suiteName,
|
|
357
|
+
status: bestRun.status,
|
|
358
|
+
duration: overallDuration,
|
|
359
|
+
startTime: new Date(Math.min(...startTimes)),
|
|
360
|
+
endTime: new Date(Math.max(...endTimes)),
|
|
361
|
+
browser: bestRun.browser,
|
|
362
|
+
tags: bestRun.tags,
|
|
363
|
+
runs: runs.sort((a, b) => a.runCounter - b.runCounter), // Sort runs chronologically for the report
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return finalResults;
|
|
367
|
+
}
|
|
386
368
|
onError(error) {
|
|
387
369
|
var _a;
|
|
388
370
|
console.error(`PlaywrightPulseReporter: Error encountered (Shard: ${(_a = this.shardIndex) !== null && _a !== void 0 ? _a : "Main"}):`, (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
@@ -417,7 +399,7 @@ class PlaywrightPulseReporter {
|
|
|
417
399
|
}
|
|
418
400
|
}
|
|
419
401
|
async _mergeShardResults(finalRunData) {
|
|
420
|
-
let allShardRawResults = [];
|
|
402
|
+
let allShardRawResults = [];
|
|
421
403
|
const totalShards = this.config.shard ? this.config.shard.total : 1;
|
|
422
404
|
for (let i = 0; i < totalShards; i++) {
|
|
423
405
|
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
|
|
@@ -435,13 +417,10 @@ class PlaywrightPulseReporter {
|
|
|
435
417
|
}
|
|
436
418
|
}
|
|
437
419
|
}
|
|
438
|
-
// Apply _getFinalizedResults after all raw shard results are collected
|
|
439
420
|
const finalResultsList = this._getFinalizedResults(allShardRawResults);
|
|
440
|
-
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
441
421
|
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
442
422
|
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
443
423
|
finalRunData.skipped = finalResultsList.filter((r) => r.status === "skipped").length;
|
|
444
|
-
// Add flaky count
|
|
445
424
|
finalRunData.flaky = finalResultsList.filter((r) => r.status === "flaky").length;
|
|
446
425
|
finalRunData.totalTests = finalResultsList.length;
|
|
447
426
|
const reviveDates = (key, value) => {
|
|
@@ -489,12 +468,9 @@ class PlaywrightPulseReporter {
|
|
|
489
468
|
await this._writeShardResults();
|
|
490
469
|
return;
|
|
491
470
|
}
|
|
492
|
-
// Now, `this.results` contains all individual run attempts.
|
|
493
|
-
// _getFinalizedResults will select the "best" run for each logical test.
|
|
494
471
|
const finalResults = this._getFinalizedResults(this.results);
|
|
495
472
|
const runEndTime = Date.now();
|
|
496
473
|
const duration = runEndTime - this.runStartTime;
|
|
497
|
-
// Use the stored overall runId
|
|
498
474
|
const runId = this.currentRunId;
|
|
499
475
|
const environmentDetails = this._getEnvDetails();
|
|
500
476
|
const runData = {
|
|
@@ -504,21 +480,18 @@ class PlaywrightPulseReporter {
|
|
|
504
480
|
passed: finalResults.filter((r) => r.status === "passed").length,
|
|
505
481
|
failed: finalResults.filter((r) => r.status === "failed").length,
|
|
506
482
|
skipped: finalResults.filter((r) => r.status === "skipped").length,
|
|
507
|
-
flaky: finalResults.filter((r) => r.status === "flaky").length,
|
|
483
|
+
flaky: finalResults.filter((r) => r.status === "flaky").length,
|
|
508
484
|
duration,
|
|
509
485
|
environment: environmentDetails,
|
|
510
486
|
};
|
|
511
|
-
// Ensure all final results have the correct overall runId
|
|
512
|
-
finalResults.forEach((r) => (r.runId = runId));
|
|
513
487
|
let finalReport = undefined;
|
|
514
488
|
if (this.isSharded) {
|
|
515
|
-
// _mergeShardResults will now perform the final de-duplication across shards
|
|
516
489
|
finalReport = await this._mergeShardResults(runData);
|
|
517
490
|
}
|
|
518
491
|
else {
|
|
519
492
|
finalReport = {
|
|
520
493
|
run: runData,
|
|
521
|
-
results: finalResults, //
|
|
494
|
+
results: finalResults, // Cast to any to bypass the type mismatch
|
|
522
495
|
metadata: { generatedAt: new Date().toISOString() },
|
|
523
496
|
};
|
|
524
497
|
}
|
|
@@ -592,8 +565,6 @@ class PlaywrightPulseReporter {
|
|
|
592
565
|
const allResultsFromAllFiles = [];
|
|
593
566
|
let latestTimestamp = new Date(0);
|
|
594
567
|
let lastRunEnvironment = undefined;
|
|
595
|
-
// We can't simply sum durations across merged files, as the tests might overlap.
|
|
596
|
-
// The final duration will be derived from the range of start/end times in the final results.
|
|
597
568
|
let earliestStartTime = Date.now();
|
|
598
569
|
let latestEndTime = 0;
|
|
599
570
|
for (const file of reportFiles) {
|
|
@@ -601,24 +572,26 @@ class PlaywrightPulseReporter {
|
|
|
601
572
|
try {
|
|
602
573
|
const content = await fs.readFile(filePath, "utf-8");
|
|
603
574
|
const json = JSON.parse(content);
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (runTimestamp > latestTimestamp) {
|
|
607
|
-
latestTimestamp = runTimestamp;
|
|
608
|
-
lastRunEnvironment = json.run.environment || undefined;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
575
|
+
// This is the tricky part. We need to handle both old and new report formats.
|
|
576
|
+
// Assuming the `results` array might contain the old, single-run objects or the new, consolidated ones.
|
|
611
577
|
if (json.results) {
|
|
612
|
-
|
|
578
|
+
json.results.forEach((testResult) => {
|
|
579
|
+
// Check if the TestResult has a 'runs' array (new format)
|
|
580
|
+
if ("runs" in testResult && Array.isArray(testResult.runs)) {
|
|
581
|
+
allResultsFromAllFiles.push(...testResult.runs);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
// This is the old format (single run). We'll treat it as a single attempt.
|
|
585
|
+
allResultsFromAllFiles.push(testResult); // Cast to any to get properties
|
|
586
|
+
}
|
|
587
|
+
});
|
|
613
588
|
}
|
|
614
589
|
}
|
|
615
590
|
catch (err) {
|
|
616
591
|
console.warn(`Pulse Reporter: Could not parse report file ${filePath}. Skipping. Error: ${err.message}`);
|
|
617
592
|
}
|
|
618
593
|
}
|
|
619
|
-
// De-duplicate the results from ALL merged files using the helper function
|
|
620
594
|
const finalMergedResults = this._getFinalizedResults(allResultsFromAllFiles);
|
|
621
|
-
// Calculate overall duration from the earliest start and latest end of the final merged results
|
|
622
595
|
for (const res of finalMergedResults) {
|
|
623
596
|
if (res.startTime.getTime() < earliestStartTime)
|
|
624
597
|
earliestStartTime = res.startTime.getTime();
|
|
@@ -634,7 +607,7 @@ class PlaywrightPulseReporter {
|
|
|
634
607
|
passed: finalMergedResults.filter((r) => r.status === "passed").length,
|
|
635
608
|
failed: finalMergedResults.filter((r) => r.status === "failed").length,
|
|
636
609
|
skipped: finalMergedResults.filter((r) => r.status === "skipped").length,
|
|
637
|
-
flaky: finalMergedResults.filter((r) => r.status === "flaky").length,
|
|
610
|
+
flaky: finalMergedResults.filter((r) => r.status === "flaky").length,
|
|
638
611
|
duration: totalDuration,
|
|
639
612
|
};
|
|
640
613
|
const finalReport = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arghajit/dummy",
|
|
3
|
-
"version": "0.1.2-beta-
|
|
3
|
+
"version": "0.1.2-beta-3",
|
|
4
4
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
5
5
|
"homepage": "https://playwright-pulse-report.netlify.app/",
|
|
6
6
|
"keywords": [
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"report:merge": "node ./scripts/merge-pulse-report.js",
|
|
51
51
|
"report:email": "node ./scripts/sendReport.mjs",
|
|
52
52
|
"report:minify": "node ./scripts/generate-email-report.mjs",
|
|
53
|
-
"generate-trend": "node ./scripts/generate-trend.mjs"
|
|
53
|
+
"generate-trend": "node ./scripts/generate-trend.mjs",
|
|
54
|
+
"deploy": "npm publish --access -public"
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
56
57
|
"archiver": "^7.0.1",
|