@arghajit/dummy 0.1.2-beta-4 → 0.1.2
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.
|
@@ -20,14 +20,8 @@ export declare class PlaywrightPulseReporter implements Reporter {
|
|
|
20
20
|
private getBrowserDetails;
|
|
21
21
|
private processStep;
|
|
22
22
|
onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
|
|
23
|
-
private _getBaseTestId;
|
|
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
23
|
private _getFinalizedResults;
|
|
24
|
+
private _getStatusOrder;
|
|
31
25
|
onError(error: any): void;
|
|
32
26
|
private _getEnvDetails;
|
|
33
27
|
private _writeShardResults;
|
|
@@ -40,10 +40,8 @@ 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 (status === "passed" && retryCount > 0) {
|
|
44
|
-
return "flaky";
|
|
45
|
-
}
|
|
46
43
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
|
|
44
|
+
// If expected to fail but passed, it's flaky
|
|
47
45
|
if (status === "passed")
|
|
48
46
|
return "flaky";
|
|
49
47
|
return "failed";
|
|
@@ -51,6 +49,10 @@ const convertStatus = (status, testCase, retryCount = 0) => {
|
|
|
51
49
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "skipped") {
|
|
52
50
|
return "skipped";
|
|
53
51
|
}
|
|
52
|
+
// If a test passes on a retry, it's considered flaky
|
|
53
|
+
if (status === "passed" && retryCount > 0) {
|
|
54
|
+
return "flaky";
|
|
55
|
+
}
|
|
54
56
|
switch (status) {
|
|
55
57
|
case "passed":
|
|
56
58
|
return "passed";
|
|
@@ -69,12 +71,11 @@ const INDIVIDUAL_REPORTS_SUBDIR = "pulse-results";
|
|
|
69
71
|
class PlaywrightPulseReporter {
|
|
70
72
|
constructor(options = {}) {
|
|
71
73
|
var _a, _b, _c;
|
|
72
|
-
// This will now store all individual run attempts for all tests using our new local type.
|
|
73
74
|
this.results = [];
|
|
74
75
|
this.baseOutputFile = "playwright-pulse-report.json";
|
|
75
76
|
this.isSharded = false;
|
|
76
77
|
this.shardIndex = undefined;
|
|
77
|
-
this.currentRunId = "";
|
|
78
|
+
this.currentRunId = ""; // Added to store the overall run ID
|
|
78
79
|
this.options = options;
|
|
79
80
|
this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
|
|
80
81
|
this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
|
|
@@ -89,6 +90,7 @@ class PlaywrightPulseReporter {
|
|
|
89
90
|
this.config = config;
|
|
90
91
|
this.suite = suite;
|
|
91
92
|
this.runStartTime = Date.now();
|
|
93
|
+
// Generate the overall runId once at the beginning
|
|
92
94
|
this.currentRunId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
|
|
93
95
|
const configDir = this.config.rootDir;
|
|
94
96
|
const configFileDir = this.config.configFile
|
|
@@ -114,7 +116,9 @@ class PlaywrightPulseReporter {
|
|
|
114
116
|
})
|
|
115
117
|
.catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
|
|
116
118
|
}
|
|
117
|
-
onTestBegin(test) {
|
|
119
|
+
onTestBegin(test) {
|
|
120
|
+
// console.log(`Starting test: ${test.title}`); // Removed for brevity in final output
|
|
121
|
+
}
|
|
118
122
|
getBrowserDetails(test) {
|
|
119
123
|
var _a, _b, _c, _d;
|
|
120
124
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
@@ -165,7 +169,8 @@ class PlaywrightPulseReporter {
|
|
|
165
169
|
}
|
|
166
170
|
return finalString.trim();
|
|
167
171
|
}
|
|
168
|
-
async processStep(step, testId, browserDetails, testCase, retryCount = 0
|
|
172
|
+
async processStep(step, testId, browserDetails, testCase, retryCount = 0 // Pass retryCount to convertStatus for steps
|
|
173
|
+
) {
|
|
169
174
|
var _a, _b, _c, _d;
|
|
170
175
|
let stepStatus = "passed";
|
|
171
176
|
let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
|
|
@@ -173,6 +178,7 @@ class PlaywrightPulseReporter {
|
|
|
173
178
|
stepStatus = "skipped";
|
|
174
179
|
}
|
|
175
180
|
else {
|
|
181
|
+
// Use the extended convertStatus
|
|
176
182
|
stepStatus = convertStatus(step.error ? "failed" : "passed", testCase, retryCount);
|
|
177
183
|
}
|
|
178
184
|
const duration = step.duration;
|
|
@@ -206,13 +212,15 @@ class PlaywrightPulseReporter {
|
|
|
206
212
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
207
213
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
208
214
|
const browserDetails = this.getBrowserDetails(test);
|
|
215
|
+
// Use the extended convertStatus, passing result.retry
|
|
209
216
|
const testStatus = convertStatus(result.status, test, result.retry);
|
|
210
217
|
const startTime = new Date(result.startTime);
|
|
211
218
|
const endTime = new Date(startTime.getTime() + result.duration);
|
|
212
219
|
const processAllSteps = async (steps) => {
|
|
213
220
|
let processed = [];
|
|
214
221
|
for (const step of steps) {
|
|
215
|
-
const processedStep = await this.processStep(step, test.id, browserDetails, test, result.retry
|
|
222
|
+
const processedStep = await this.processStep(step, test.id, browserDetails, test, result.retry // Pass retryCount to processStep
|
|
223
|
+
);
|
|
216
224
|
processed.push(processedStep);
|
|
217
225
|
if (step.steps && step.steps.length > 0) {
|
|
218
226
|
processedStep.steps = await processAllSteps(step.steps);
|
|
@@ -244,11 +252,11 @@ class PlaywrightPulseReporter {
|
|
|
244
252
|
? JSON.stringify(this.config.metadata)
|
|
245
253
|
: undefined,
|
|
246
254
|
};
|
|
247
|
-
//
|
|
248
|
-
const testIdWithRunCounter = `${test.id}
|
|
255
|
+
// Modify test.id for retries
|
|
256
|
+
const testIdWithRunCounter = result.retry > 0 ? `${test.id}-${result.retry}` : test.id;
|
|
249
257
|
const pulseResult = {
|
|
250
|
-
id: testIdWithRunCounter,
|
|
251
|
-
runId: this.currentRunId,
|
|
258
|
+
id: testIdWithRunCounter, // Use the modified ID
|
|
259
|
+
runId: this.currentRunId, // Assign the overall run ID
|
|
252
260
|
name: test.titlePath().join(" > "),
|
|
253
261
|
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",
|
|
254
262
|
status: testStatus,
|
|
@@ -256,8 +264,8 @@ class PlaywrightPulseReporter {
|
|
|
256
264
|
startTime: startTime,
|
|
257
265
|
endTime: endTime,
|
|
258
266
|
browser: browserDetails,
|
|
259
|
-
retries: result.retry,
|
|
260
|
-
runCounter: result.retry,
|
|
267
|
+
retries: result.retry, // This remains the Playwright retry count (0 for first run, 1 for first retry, etc.)
|
|
268
|
+
runCounter: result.retry, // This is your 'runCounter'
|
|
261
269
|
steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
|
|
262
270
|
errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
|
|
263
271
|
stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
|
|
@@ -276,6 +284,7 @@ class PlaywrightPulseReporter {
|
|
|
276
284
|
if (!attachment.path)
|
|
277
285
|
continue;
|
|
278
286
|
try {
|
|
287
|
+
// Use the new testIdWithRunCounter for the subfolder
|
|
279
288
|
const testSubfolder = testIdWithRunCounter.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
280
289
|
const safeAttachmentName = path
|
|
281
290
|
.basename(attachment.path)
|
|
@@ -308,10 +317,34 @@ class PlaywrightPulseReporter {
|
|
|
308
317
|
}
|
|
309
318
|
this.results.push(pulseResult);
|
|
310
319
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
320
|
+
_getFinalizedResults(allResults) {
|
|
321
|
+
const finalResultsMap = new Map();
|
|
322
|
+
for (const result of allResults) {
|
|
323
|
+
// The key for de-duplication should now be the base test ID (without the run counter suffix)
|
|
324
|
+
// This ensures that all runs of a single logical test are considered together.
|
|
325
|
+
const baseTestId = result.id.split("-").slice(0, -1).join("-"); // Remove '-${runCounter}'
|
|
326
|
+
const existing = finalResultsMap.get(baseTestId);
|
|
327
|
+
// We want to keep the "most successful" run for the final report.
|
|
328
|
+
// Priority: passed > flaky > failed > skipped.
|
|
329
|
+
// If statuses are equal, prefer the one with higher retry count (latest attempt).
|
|
330
|
+
if (!existing) {
|
|
331
|
+
finalResultsMap.set(baseTestId, result);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
const currentStatusOrder = this._getStatusOrder(result.status);
|
|
335
|
+
const existingStatusOrder = this._getStatusOrder(existing.status);
|
|
336
|
+
if (currentStatusOrder < existingStatusOrder) {
|
|
337
|
+
// Current result is "better" (e.g., passed over failed)
|
|
338
|
+
finalResultsMap.set(baseTestId, result);
|
|
339
|
+
}
|
|
340
|
+
else if (currentStatusOrder === existingStatusOrder &&
|
|
341
|
+
result.retries > existing.retries) {
|
|
342
|
+
// Same status, but current is a later retry, so prefer it
|
|
343
|
+
finalResultsMap.set(baseTestId, result);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return Array.from(finalResultsMap.values());
|
|
315
348
|
}
|
|
316
349
|
_getStatusOrder(status) {
|
|
317
350
|
switch (status) {
|
|
@@ -324,47 +357,9 @@ class PlaywrightPulseReporter {
|
|
|
324
357
|
case "skipped":
|
|
325
358
|
return 4;
|
|
326
359
|
default:
|
|
327
|
-
return 99;
|
|
360
|
+
return 99; // Unknown status
|
|
328
361
|
}
|
|
329
362
|
}
|
|
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
|
-
}
|
|
368
363
|
onError(error) {
|
|
369
364
|
var _a;
|
|
370
365
|
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);
|
|
@@ -399,7 +394,7 @@ class PlaywrightPulseReporter {
|
|
|
399
394
|
}
|
|
400
395
|
}
|
|
401
396
|
async _mergeShardResults(finalRunData) {
|
|
402
|
-
let allShardRawResults = [];
|
|
397
|
+
let allShardRawResults = []; // Store raw results before final de-duplication
|
|
403
398
|
const totalShards = this.config.shard ? this.config.shard.total : 1;
|
|
404
399
|
for (let i = 0; i < totalShards; i++) {
|
|
405
400
|
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
|
|
@@ -417,10 +412,13 @@ class PlaywrightPulseReporter {
|
|
|
417
412
|
}
|
|
418
413
|
}
|
|
419
414
|
}
|
|
415
|
+
// Apply _getFinalizedResults after all raw shard results are collected
|
|
420
416
|
const finalResultsList = this._getFinalizedResults(allShardRawResults);
|
|
417
|
+
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
421
418
|
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
422
419
|
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
423
420
|
finalRunData.skipped = finalResultsList.filter((r) => r.status === "skipped").length;
|
|
421
|
+
// Add flaky count
|
|
424
422
|
finalRunData.flaky = finalResultsList.filter((r) => r.status === "flaky").length;
|
|
425
423
|
finalRunData.totalTests = finalResultsList.length;
|
|
426
424
|
const reviveDates = (key, value) => {
|
|
@@ -468,39 +466,36 @@ class PlaywrightPulseReporter {
|
|
|
468
466
|
await this._writeShardResults();
|
|
469
467
|
return;
|
|
470
468
|
}
|
|
471
|
-
|
|
472
|
-
//
|
|
473
|
-
const
|
|
474
|
-
// Use your existing logic ONLY to get the final statuses for the summary counts.
|
|
475
|
-
const summaryResults = this._getFinalizedResults(this.results);
|
|
469
|
+
// `this.results` now contains all individual run attempts.
|
|
470
|
+
// _getFinalizedResults will select the "best" run for each logical test.
|
|
471
|
+
const finalResults = this._getFinalizedResults(this.results);
|
|
476
472
|
const runEndTime = Date.now();
|
|
477
473
|
const duration = runEndTime - this.runStartTime;
|
|
474
|
+
// Use the stored overall runId
|
|
478
475
|
const runId = this.currentRunId;
|
|
479
476
|
const environmentDetails = this._getEnvDetails();
|
|
480
477
|
const runData = {
|
|
481
478
|
id: runId,
|
|
482
479
|
timestamp: new Date(this.runStartTime),
|
|
483
|
-
totalTests:
|
|
484
|
-
passed:
|
|
485
|
-
failed:
|
|
486
|
-
skipped:
|
|
487
|
-
flaky:
|
|
480
|
+
totalTests: finalResults.length,
|
|
481
|
+
passed: finalResults.filter((r) => r.status === "passed").length,
|
|
482
|
+
failed: finalResults.filter((r) => r.status === "failed").length,
|
|
483
|
+
skipped: finalResults.filter((r) => r.status === "skipped").length,
|
|
484
|
+
flaky: finalResults.filter((r) => r.status === "flaky").length, // Add flaky count
|
|
488
485
|
duration,
|
|
489
486
|
environment: environmentDetails,
|
|
490
487
|
};
|
|
491
|
-
//
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
results: allAttempts, // <<< USE THE FULL LIST HERE
|
|
495
|
-
metadata: { generatedAt: new Date().toISOString() },
|
|
496
|
-
};
|
|
488
|
+
// Ensure all final results have the correct overall runId
|
|
489
|
+
finalResults.forEach((r) => (r.runId = runId));
|
|
490
|
+
let finalReport = undefined;
|
|
497
491
|
if (this.isSharded) {
|
|
492
|
+
// _mergeShardResults will now perform the final de-duplication across shards
|
|
498
493
|
finalReport = await this._mergeShardResults(runData);
|
|
499
494
|
}
|
|
500
495
|
else {
|
|
501
496
|
finalReport = {
|
|
502
497
|
run: runData,
|
|
503
|
-
results:
|
|
498
|
+
results: finalResults, // Use the de-duplicated results for a non-sharded run
|
|
504
499
|
metadata: { generatedAt: new Date().toISOString() },
|
|
505
500
|
};
|
|
506
501
|
}
|
|
@@ -574,6 +569,8 @@ class PlaywrightPulseReporter {
|
|
|
574
569
|
const allResultsFromAllFiles = [];
|
|
575
570
|
let latestTimestamp = new Date(0);
|
|
576
571
|
let lastRunEnvironment = undefined;
|
|
572
|
+
// We can't simply sum durations across merged files, as the tests might overlap.
|
|
573
|
+
// The final duration will be derived from the range of start/end times in the final results.
|
|
577
574
|
let earliestStartTime = Date.now();
|
|
578
575
|
let latestEndTime = 0;
|
|
579
576
|
for (const file of reportFiles) {
|
|
@@ -581,26 +578,24 @@ class PlaywrightPulseReporter {
|
|
|
581
578
|
try {
|
|
582
579
|
const content = await fs.readFile(filePath, "utf-8");
|
|
583
580
|
const json = JSON.parse(content);
|
|
584
|
-
|
|
585
|
-
|
|
581
|
+
if (json.run) {
|
|
582
|
+
const runTimestamp = new Date(json.run.timestamp);
|
|
583
|
+
if (runTimestamp > latestTimestamp) {
|
|
584
|
+
latestTimestamp = runTimestamp;
|
|
585
|
+
lastRunEnvironment = json.run.environment || undefined;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
586
588
|
if (json.results) {
|
|
587
|
-
json.results
|
|
588
|
-
// Check if the TestResult has a 'runs' array (new format)
|
|
589
|
-
if ("runs" in testResult && Array.isArray(testResult.runs)) {
|
|
590
|
-
allResultsFromAllFiles.push(...testResult.runs);
|
|
591
|
-
}
|
|
592
|
-
else {
|
|
593
|
-
// This is the old format (single run). We'll treat it as a single attempt.
|
|
594
|
-
allResultsFromAllFiles.push(testResult); // Cast to any to get properties
|
|
595
|
-
}
|
|
596
|
-
});
|
|
589
|
+
allResultsFromAllFiles.push(...json.results);
|
|
597
590
|
}
|
|
598
591
|
}
|
|
599
592
|
catch (err) {
|
|
600
593
|
console.warn(`Pulse Reporter: Could not parse report file ${filePath}. Skipping. Error: ${err.message}`);
|
|
601
594
|
}
|
|
602
595
|
}
|
|
596
|
+
// De-duplicate the results from ALL merged files using the helper function
|
|
603
597
|
const finalMergedResults = this._getFinalizedResults(allResultsFromAllFiles);
|
|
598
|
+
// Calculate overall duration from the earliest start and latest end of the final merged results
|
|
604
599
|
for (const res of finalMergedResults) {
|
|
605
600
|
if (res.startTime.getTime() < earliestStartTime)
|
|
606
601
|
earliestStartTime = res.startTime.getTime();
|
|
@@ -616,7 +611,7 @@ class PlaywrightPulseReporter {
|
|
|
616
611
|
passed: finalMergedResults.filter((r) => r.status === "passed").length,
|
|
617
612
|
failed: finalMergedResults.filter((r) => r.status === "failed").length,
|
|
618
613
|
skipped: finalMergedResults.filter((r) => r.status === "skipped").length,
|
|
619
|
-
flaky: finalMergedResults.filter((r) => r.status === "flaky").length,
|
|
614
|
+
flaky: finalMergedResults.filter((r) => r.status === "flaky").length, // Add flaky count
|
|
620
615
|
duration: totalDuration,
|
|
621
616
|
};
|
|
622
617
|
const finalReport = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arghajit/dummy",
|
|
3
|
-
"version": "0.1.2
|
|
3
|
+
"version": "0.1.2",
|
|
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,8 +50,7 @@
|
|
|
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"
|
|
54
|
-
"deploy": "npm publish --access -public"
|
|
53
|
+
"generate-trend": "node ./scripts/generate-trend.mjs"
|
|
55
54
|
},
|
|
56
55
|
"dependencies": {
|
|
57
56
|
"archiver": "^7.0.1",
|