@arghajit/dummy 0.1.0 → 0.1.1
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.
- package/README.md +136 -77
- package/dist/reporter/attachment-utils.js +41 -33
- package/dist/reporter/playwright-pulse-reporter.d.ts +5 -0
- package/dist/reporter/playwright-pulse-reporter.js +269 -145
- package/dist/reporter/tsconfig.reporter.tsbuildinfo +1 -0
- package/dist/types/index.d.ts +26 -2
- package/package.json +17 -5
- package/scripts/generate-email-report.mjs +714 -0
- package/scripts/generate-report.mjs +3034 -0
- package/scripts/generate-static-report.mjs +2186 -1285
- package/scripts/generate-trend.mjs +1 -1
- package/scripts/merge-pulse-report.js +1 -0
- package/scripts/{sendReport.js → sendReport.mjs} +143 -76
- package/screenshots/127-0-0-1-5500-pulse-report-output-playwright-pulse-static-report-html-i-Phone-14-Pro-Max-1.png +0 -0
- package/screenshots/127-0-0-1-5500-pulse-report-output-playwright-pulse-static-report-html-i-Phone-14-Pro-Max.png +0 -0
- package/screenshots/Email-report.jpg +0 -0
- package/screenshots/Users-arghajitsingha-Downloads-pulse-report-1-playwright-pulse-static-report-html-1.png +0 -0
- package/screenshots/Users-arghajitsingha-Downloads-pulse-report-1-playwright-pulse-static-report-html-2.png +0 -0
- package/screenshots/Users-arghajitsingha-Downloads-pulse-report-1-playwright-pulse-static-report-html.png +0 -0
- package/screenshots/image.png +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// input_file_0.ts
|
|
3
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
3
|
if (k2 === undefined) k2 = k;
|
|
5
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -38,7 +37,8 @@ exports.PlaywrightPulseReporter = void 0;
|
|
|
38
37
|
const fs = __importStar(require("fs/promises"));
|
|
39
38
|
const path = __importStar(require("path"));
|
|
40
39
|
const crypto_1 = require("crypto");
|
|
41
|
-
const
|
|
40
|
+
const ua_parser_js_1 = require("ua-parser-js");
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
42
|
const convertStatus = (status, testCase) => {
|
|
43
43
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
|
|
44
44
|
return "failed";
|
|
@@ -60,9 +60,10 @@ const convertStatus = (status, testCase) => {
|
|
|
60
60
|
};
|
|
61
61
|
const TEMP_SHARD_FILE_PREFIX = ".pulse-shard-results-";
|
|
62
62
|
const ATTACHMENTS_SUBDIR = "attachments";
|
|
63
|
+
const INDIVIDUAL_REPORTS_SUBDIR = "pulse-results";
|
|
63
64
|
class PlaywrightPulseReporter {
|
|
64
65
|
constructor(options = {}) {
|
|
65
|
-
var _a, _b;
|
|
66
|
+
var _a, _b, _c;
|
|
66
67
|
this.results = [];
|
|
67
68
|
this.baseOutputFile = "playwright-pulse-report.json";
|
|
68
69
|
this.isSharded = false;
|
|
@@ -71,6 +72,7 @@ class PlaywrightPulseReporter {
|
|
|
71
72
|
this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
|
|
72
73
|
this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
|
|
73
74
|
this.attachmentsDir = path.join(this.outputDir, ATTACHMENTS_SUBDIR);
|
|
75
|
+
this.resetOnEachRun = (_c = options.resetOnEachRun) !== null && _c !== void 0 ? _c : true;
|
|
74
76
|
}
|
|
75
77
|
printsToStdio() {
|
|
76
78
|
return this.shardIndex === undefined || this.shardIndex === 0;
|
|
@@ -94,7 +96,7 @@ class PlaywrightPulseReporter {
|
|
|
94
96
|
: undefined;
|
|
95
97
|
this._ensureDirExists(this.outputDir)
|
|
96
98
|
.then(() => {
|
|
97
|
-
if (this.
|
|
99
|
+
if (this.printsToStdio()) {
|
|
98
100
|
console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Pulse outputting to ${this.outputDir}`);
|
|
99
101
|
if (this.shardIndex === undefined ||
|
|
100
102
|
(this.isSharded && this.shardIndex === 0)) {
|
|
@@ -105,9 +107,59 @@ class PlaywrightPulseReporter {
|
|
|
105
107
|
.catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
|
|
106
108
|
}
|
|
107
109
|
onTestBegin(test) {
|
|
108
|
-
|
|
110
|
+
console.log(`Starting test: ${test.title}`);
|
|
109
111
|
}
|
|
110
|
-
|
|
112
|
+
getBrowserDetails(test) {
|
|
113
|
+
var _a, _b, _c, _d;
|
|
114
|
+
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
115
|
+
const projectConfig = project === null || project === void 0 ? void 0 : project.use;
|
|
116
|
+
const userAgent = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.userAgent;
|
|
117
|
+
const configuredBrowserType = (_b = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.browserName) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
118
|
+
const parser = new ua_parser_js_1.UAParser(userAgent);
|
|
119
|
+
const result = parser.getResult();
|
|
120
|
+
let browserName = result.browser.name;
|
|
121
|
+
const browserVersion = result.browser.version
|
|
122
|
+
? ` v${result.browser.version.split(".")[0]}`
|
|
123
|
+
: "";
|
|
124
|
+
const osName = result.os.name ? ` on ${result.os.name}` : "";
|
|
125
|
+
const osVersion = result.os.version
|
|
126
|
+
? ` ${result.os.version.split(".")[0]}`
|
|
127
|
+
: "";
|
|
128
|
+
const deviceType = result.device.type;
|
|
129
|
+
let finalString;
|
|
130
|
+
if (browserName === undefined) {
|
|
131
|
+
browserName = configuredBrowserType;
|
|
132
|
+
finalString = `${browserName}`;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
if (deviceType === "mobile" || deviceType === "tablet") {
|
|
136
|
+
if ((_c = result.os.name) === null || _c === void 0 ? void 0 : _c.toLowerCase().includes("android")) {
|
|
137
|
+
if (browserName.toLowerCase().includes("chrome"))
|
|
138
|
+
browserName = "Chrome Mobile";
|
|
139
|
+
else if (browserName.toLowerCase().includes("firefox"))
|
|
140
|
+
browserName = "Firefox Mobile";
|
|
141
|
+
else if (result.engine.name === "Blink" && !result.browser.name)
|
|
142
|
+
browserName = "Android WebView";
|
|
143
|
+
else if (browserName &&
|
|
144
|
+
!browserName.toLowerCase().includes("mobile")) {
|
|
145
|
+
// Keep it as is
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
browserName = "Android Browser";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else if ((_d = result.os.name) === null || _d === void 0 ? void 0 : _d.toLowerCase().includes("ios")) {
|
|
152
|
+
browserName = "Mobile Safari";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (browserName === "Electron") {
|
|
156
|
+
browserName = "Electron App";
|
|
157
|
+
}
|
|
158
|
+
finalString = `${browserName}${browserVersion}${osName}${osVersion}`;
|
|
159
|
+
}
|
|
160
|
+
return finalString.trim();
|
|
161
|
+
}
|
|
162
|
+
async processStep(step, testId, browserDetails, testCase) {
|
|
111
163
|
var _a, _b, _c, _d;
|
|
112
164
|
let stepStatus = "passed";
|
|
113
165
|
let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
|
|
@@ -124,15 +176,14 @@ class PlaywrightPulseReporter {
|
|
|
124
176
|
if (step.location) {
|
|
125
177
|
codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
|
|
126
178
|
}
|
|
127
|
-
let stepTitle = step.title;
|
|
128
179
|
return {
|
|
129
180
|
id: `${testId}_step_${startTime.toISOString()}-${duration}-${(0, crypto_1.randomUUID)()}`,
|
|
130
|
-
title:
|
|
181
|
+
title: step.title,
|
|
131
182
|
status: stepStatus,
|
|
132
183
|
duration: duration,
|
|
133
184
|
startTime: startTime,
|
|
134
185
|
endTime: endTime,
|
|
135
|
-
browser:
|
|
186
|
+
browser: browserDetails,
|
|
136
187
|
errorMessage: errorMessage,
|
|
137
188
|
stackTrace: ((_d = step.error) === null || _d === void 0 ? void 0 : _d.stack) || undefined,
|
|
138
189
|
codeLocation: codeLocation || undefined,
|
|
@@ -146,21 +197,16 @@ class PlaywrightPulseReporter {
|
|
|
146
197
|
};
|
|
147
198
|
}
|
|
148
199
|
async onTestEnd(test, result) {
|
|
149
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
200
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
150
201
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
151
|
-
const
|
|
202
|
+
const browserDetails = this.getBrowserDetails(test);
|
|
152
203
|
const testStatus = convertStatus(result.status, test);
|
|
153
204
|
const startTime = new Date(result.startTime);
|
|
154
205
|
const endTime = new Date(startTime.getTime() + result.duration);
|
|
155
|
-
const testIdForFiles = test.id ||
|
|
156
|
-
`${test
|
|
157
|
-
.titlePath()
|
|
158
|
-
.join("_")
|
|
159
|
-
.replace(/[^a-zA-Z0-9]/g, "_")}_${startTime.getTime()}`;
|
|
160
206
|
const processAllSteps = async (steps) => {
|
|
161
207
|
let processed = [];
|
|
162
208
|
for (const step of steps) {
|
|
163
|
-
const processedStep = await this.processStep(step,
|
|
209
|
+
const processedStep = await this.processStep(step, test.id, browserDetails, test);
|
|
164
210
|
processed.push(processedStep);
|
|
165
211
|
if (step.steps && step.steps.length > 0) {
|
|
166
212
|
processedStep.steps = await processAllSteps(step.steps);
|
|
@@ -170,7 +216,7 @@ class PlaywrightPulseReporter {
|
|
|
170
216
|
};
|
|
171
217
|
let codeSnippet = undefined;
|
|
172
218
|
try {
|
|
173
|
-
if (((
|
|
219
|
+
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)) {
|
|
174
220
|
const relativePath = path.relative(this.config.rootDir, test.location.file);
|
|
175
221
|
codeSnippet = `Test defined at: ${relativePath}:${test.location.line}:${test.location.column}`;
|
|
176
222
|
}
|
|
@@ -178,56 +224,91 @@ class PlaywrightPulseReporter {
|
|
|
178
224
|
catch (e) {
|
|
179
225
|
console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
|
|
180
226
|
}
|
|
181
|
-
const stdoutMessages =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
227
|
+
const stdoutMessages = result.stdout.map((item) => typeof item === "string" ? item : item.toString());
|
|
228
|
+
const stderrMessages = result.stderr.map((item) => typeof item === "string" ? item : item.toString());
|
|
229
|
+
const maxWorkers = this.config.workers;
|
|
230
|
+
let mappedWorkerId = result.workerIndex === -1
|
|
231
|
+
? -1
|
|
232
|
+
: (result.workerIndex % (maxWorkers > 0 ? maxWorkers : 1)) + 1;
|
|
233
|
+
const testSpecificData = {
|
|
234
|
+
workerId: mappedWorkerId,
|
|
235
|
+
totalWorkers: maxWorkers,
|
|
236
|
+
configFile: this.config.configFile,
|
|
237
|
+
metadata: this.config.metadata
|
|
238
|
+
? JSON.stringify(this.config.metadata)
|
|
239
|
+
: undefined,
|
|
240
|
+
};
|
|
194
241
|
const pulseResult = {
|
|
195
|
-
id:
|
|
242
|
+
id: test.id,
|
|
196
243
|
runId: "TBD",
|
|
197
244
|
name: test.titlePath().join(" > "),
|
|
198
|
-
suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((
|
|
245
|
+
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",
|
|
199
246
|
status: testStatus,
|
|
200
247
|
duration: result.duration,
|
|
201
248
|
startTime: startTime,
|
|
202
249
|
endTime: endTime,
|
|
203
|
-
browser:
|
|
250
|
+
browser: browserDetails,
|
|
204
251
|
retries: result.retry,
|
|
205
|
-
steps: ((
|
|
206
|
-
errorMessage: (
|
|
207
|
-
stackTrace: (
|
|
252
|
+
steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
|
|
253
|
+
errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
|
|
254
|
+
stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
|
|
255
|
+
snippet: (_j = result.error) === null || _j === void 0 ? void 0 : _j.snippet,
|
|
208
256
|
codeSnippet: codeSnippet,
|
|
209
257
|
tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
|
|
210
258
|
screenshots: [],
|
|
211
|
-
videoPath:
|
|
259
|
+
videoPath: [],
|
|
212
260
|
tracePath: undefined,
|
|
261
|
+
attachments: [],
|
|
213
262
|
stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
|
|
214
263
|
stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
|
|
264
|
+
...testSpecificData,
|
|
215
265
|
};
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
266
|
+
for (const [index, attachment] of result.attachments.entries()) {
|
|
267
|
+
if (!attachment.path)
|
|
268
|
+
continue;
|
|
269
|
+
try {
|
|
270
|
+
const testSubfolder = test.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
271
|
+
const safeAttachmentName = path
|
|
272
|
+
.basename(attachment.path)
|
|
273
|
+
.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
274
|
+
const uniqueFileName = `${index}-${Date.now()}-${safeAttachmentName}`;
|
|
275
|
+
const relativeDestPath = path.join(ATTACHMENTS_SUBDIR, testSubfolder, uniqueFileName);
|
|
276
|
+
const absoluteDestPath = path.join(this.outputDir, relativeDestPath);
|
|
277
|
+
await this._ensureDirExists(path.dirname(absoluteDestPath));
|
|
278
|
+
await fs.copyFile(attachment.path, absoluteDestPath);
|
|
279
|
+
if (attachment.contentType.startsWith("image/")) {
|
|
280
|
+
(_k = pulseResult.screenshots) === null || _k === void 0 ? void 0 : _k.push(relativeDestPath);
|
|
281
|
+
}
|
|
282
|
+
else if (attachment.contentType.startsWith("video/")) {
|
|
283
|
+
(_l = pulseResult.videoPath) === null || _l === void 0 ? void 0 : _l.push(relativeDestPath);
|
|
284
|
+
}
|
|
285
|
+
else if (attachment.name === "trace") {
|
|
286
|
+
pulseResult.tracePath = relativeDestPath;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
(_m = pulseResult.attachments) === null || _m === void 0 ? void 0 : _m.push({
|
|
290
|
+
name: attachment.name,
|
|
291
|
+
path: relativeDestPath,
|
|
292
|
+
contentType: attachment.contentType,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
console.error(`Pulse Reporter: Failed to process attachment "${attachment.name}" for test ${pulseResult.name}. Error: ${err.message}`);
|
|
226
298
|
}
|
|
227
299
|
}
|
|
228
|
-
|
|
229
|
-
|
|
300
|
+
this.results.push(pulseResult);
|
|
301
|
+
}
|
|
302
|
+
_getFinalizedResults(allResults) {
|
|
303
|
+
const finalResultsMap = new Map();
|
|
304
|
+
for (const result of allResults) {
|
|
305
|
+
const existing = finalResultsMap.get(result.id);
|
|
306
|
+
// Keep the result with the highest retry attempt for each test ID
|
|
307
|
+
if (!existing || result.retries >= existing.retries) {
|
|
308
|
+
finalResultsMap.set(result.id, result);
|
|
309
|
+
}
|
|
230
310
|
}
|
|
311
|
+
return Array.from(finalResultsMap.values());
|
|
231
312
|
}
|
|
232
313
|
onError(error) {
|
|
233
314
|
var _a;
|
|
@@ -236,6 +317,20 @@ class PlaywrightPulseReporter {
|
|
|
236
317
|
console.error(error.stack);
|
|
237
318
|
}
|
|
238
319
|
}
|
|
320
|
+
_getEnvDetails() {
|
|
321
|
+
return {
|
|
322
|
+
host: os.hostname(),
|
|
323
|
+
os: `${os.platform()} ${os.release()}`,
|
|
324
|
+
cpu: {
|
|
325
|
+
model: os.cpus()[0] ? os.cpus()[0].model : "N/A",
|
|
326
|
+
cores: os.cpus().length,
|
|
327
|
+
},
|
|
328
|
+
memory: `${(os.totalmem() / 1024 ** 3).toFixed(2)}GB`,
|
|
329
|
+
node: process.version,
|
|
330
|
+
v8: process.versions.v8,
|
|
331
|
+
cwd: process.cwd(),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
239
334
|
async _writeShardResults() {
|
|
240
335
|
if (this.shardIndex === undefined) {
|
|
241
336
|
return;
|
|
@@ -268,14 +363,7 @@ class PlaywrightPulseReporter {
|
|
|
268
363
|
}
|
|
269
364
|
}
|
|
270
365
|
}
|
|
271
|
-
|
|
272
|
-
for (const result of allShardProcessedResults) {
|
|
273
|
-
const existing = finalUniqueResultsMap.get(result.id);
|
|
274
|
-
if (!existing || result.retries >= existing.retries) {
|
|
275
|
-
finalUniqueResultsMap.set(result.id, result);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
const finalResultsList = Array.from(finalUniqueResultsMap.values());
|
|
366
|
+
const finalResultsList = this._getFinalizedResults(allShardProcessedResults);
|
|
279
367
|
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
280
368
|
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
281
369
|
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
@@ -322,131 +410,167 @@ class PlaywrightPulseReporter {
|
|
|
322
410
|
}
|
|
323
411
|
}
|
|
324
412
|
async onEnd(result) {
|
|
325
|
-
var _a, _b, _c;
|
|
326
413
|
if (this.shardIndex !== undefined) {
|
|
327
414
|
await this._writeShardResults();
|
|
328
415
|
return;
|
|
329
416
|
}
|
|
417
|
+
// De-duplicate and handle retries here, in a safe, single-threaded context.
|
|
418
|
+
const finalResults = this._getFinalizedResults(this.results);
|
|
330
419
|
const runEndTime = Date.now();
|
|
331
420
|
const duration = runEndTime - this.runStartTime;
|
|
332
|
-
const runId = `run-${this.runStartTime}
|
|
421
|
+
const runId = `run-${this.runStartTime}-581d5ad8-ce75-4ca5-94a6-ed29c466c815`;
|
|
422
|
+
const environmentDetails = this._getEnvDetails();
|
|
333
423
|
const runData = {
|
|
334
424
|
id: runId,
|
|
335
425
|
timestamp: new Date(this.runStartTime),
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
426
|
+
// Use the length of the de-duplicated array for all counts
|
|
427
|
+
totalTests: finalResults.length,
|
|
428
|
+
passed: finalResults.filter((r) => r.status === "passed").length,
|
|
429
|
+
failed: finalResults.filter((r) => r.status === "failed").length,
|
|
430
|
+
skipped: finalResults.filter((r) => r.status === "skipped").length,
|
|
340
431
|
duration,
|
|
432
|
+
environment: environmentDetails,
|
|
341
433
|
};
|
|
342
|
-
|
|
434
|
+
finalResults.forEach((r) => (r.runId = runId));
|
|
435
|
+
let finalReport = undefined;
|
|
343
436
|
if (this.isSharded) {
|
|
437
|
+
// The _mergeShardResults method will handle its own de-duplication
|
|
344
438
|
finalReport = await this._mergeShardResults(runData);
|
|
345
439
|
}
|
|
346
440
|
else {
|
|
347
|
-
this.results.forEach((r) => (r.runId = runId));
|
|
348
|
-
runData.passed = this.results.filter((r) => r.status === "passed").length;
|
|
349
|
-
runData.failed = this.results.filter((r) => r.status === "failed").length;
|
|
350
|
-
runData.skipped = this.results.filter((r) => r.status === "skipped").length;
|
|
351
|
-
runData.totalTests = this.results.length;
|
|
352
|
-
const reviveDates = (key, value) => {
|
|
353
|
-
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
354
|
-
if (typeof value === "string" && isoDateRegex.test(value)) {
|
|
355
|
-
const date = new Date(value);
|
|
356
|
-
return !isNaN(date.getTime()) ? date : value;
|
|
357
|
-
}
|
|
358
|
-
return value;
|
|
359
|
-
};
|
|
360
|
-
const properlyTypedResults = JSON.parse(JSON.stringify(this.results), reviveDates);
|
|
361
441
|
finalReport = {
|
|
362
442
|
run: runData,
|
|
363
|
-
results
|
|
443
|
+
// Use the de-duplicated results
|
|
444
|
+
results: finalResults,
|
|
364
445
|
metadata: { generatedAt: new Date().toISOString() },
|
|
365
446
|
};
|
|
366
447
|
}
|
|
367
448
|
if (!finalReport) {
|
|
368
449
|
console.error("PlaywrightPulseReporter: CRITICAL - finalReport object was not generated. Cannot create summary.");
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
console.log(errorSummary);
|
|
381
|
-
}
|
|
382
|
-
const errorReport = {
|
|
383
|
-
run: {
|
|
384
|
-
id: runId,
|
|
385
|
-
timestamp: new Date(this.runStartTime),
|
|
386
|
-
totalTests: 0,
|
|
387
|
-
passed: 0,
|
|
388
|
-
failed: 0,
|
|
389
|
-
skipped: 0,
|
|
390
|
-
duration: duration,
|
|
391
|
-
},
|
|
392
|
-
results: [],
|
|
393
|
-
metadata: {
|
|
394
|
-
generatedAt: new Date().toISOString(),
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
const finalOutputPathOnError = path.join(this.outputDir, this.baseOutputFile);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const jsonReplacer = (key, value) => {
|
|
453
|
+
if (value instanceof Date)
|
|
454
|
+
return value.toISOString();
|
|
455
|
+
if (typeof value === "bigint")
|
|
456
|
+
return value.toString();
|
|
457
|
+
return value;
|
|
458
|
+
};
|
|
459
|
+
if (this.resetOnEachRun) {
|
|
460
|
+
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
398
461
|
try {
|
|
399
462
|
await this._ensureDirExists(this.outputDir);
|
|
400
|
-
await fs.writeFile(
|
|
401
|
-
|
|
463
|
+
await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, jsonReplacer, 2));
|
|
464
|
+
if (this.printsToStdio()) {
|
|
465
|
+
console.log(`PlaywrightPulseReporter: JSON report written to ${finalOutputPath}`);
|
|
466
|
+
}
|
|
402
467
|
}
|
|
403
|
-
catch (
|
|
404
|
-
console.error(`
|
|
468
|
+
catch (error) {
|
|
469
|
+
console.error(`Pulse Reporter: Failed to write final JSON report to ${finalOutputPath}. Error: ${error.message}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
// Logic for appending/merging reports
|
|
474
|
+
const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
|
|
475
|
+
const individualReportPath = path.join(pulseResultsDir, `playwright-pulse-report-${Date.now()}.json`);
|
|
476
|
+
try {
|
|
477
|
+
await this._ensureDirExists(pulseResultsDir);
|
|
478
|
+
await fs.writeFile(individualReportPath, JSON.stringify(finalReport, jsonReplacer, 2));
|
|
479
|
+
if (this.printsToStdio()) {
|
|
480
|
+
console.log(`PlaywrightPulseReporter: Individual run report for merging written to ${individualReportPath}`);
|
|
481
|
+
}
|
|
482
|
+
await this._mergeAllRunReports();
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
console.error(`Pulse Reporter: Failed to write or merge report. Error: ${error.message}`);
|
|
405
486
|
}
|
|
406
|
-
return;
|
|
407
487
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
? "failed"
|
|
411
|
-
: ((_b = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.totalTests) !== null && _b !== void 0 ? _b : 0) === 0 && result.status !== "passed"
|
|
412
|
-
? result.status === "interrupted"
|
|
413
|
-
? "interrupted"
|
|
414
|
-
: "no tests or error"
|
|
415
|
-
: "passed";
|
|
416
|
-
const summary = `
|
|
417
|
-
PlaywrightPulseReporter: Run Finished
|
|
418
|
-
-----------------------------------------
|
|
419
|
-
Overall Status: ${finalRunStatus.toUpperCase()}
|
|
420
|
-
Total Tests: ${(reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.totalTests) || 0}
|
|
421
|
-
Passed: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.passed}
|
|
422
|
-
Failed: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.failed}
|
|
423
|
-
Skipped: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.skipped}
|
|
424
|
-
Duration: ${(((_c = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.duration) !== null && _c !== void 0 ? _c : 0) / 1000).toFixed(2)}s
|
|
425
|
-
-----------------------------------------`;
|
|
426
|
-
if (this.printsToStdio()) {
|
|
427
|
-
console.log(summary);
|
|
488
|
+
if (this.isSharded) {
|
|
489
|
+
await this._cleanupTemporaryFiles();
|
|
428
490
|
}
|
|
491
|
+
}
|
|
492
|
+
async _mergeAllRunReports() {
|
|
493
|
+
const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
|
|
429
494
|
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
495
|
+
let reportFiles;
|
|
496
|
+
try {
|
|
497
|
+
const allFiles = await fs.readdir(pulseResultsDir);
|
|
498
|
+
reportFiles = allFiles.filter((file) => file.startsWith("playwright-pulse-report-") && file.endsWith(".json"));
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
if (error.code === "ENOENT") {
|
|
502
|
+
if (this.printsToStdio()) {
|
|
503
|
+
console.log(`Pulse Reporter: No individual reports directory found at ${pulseResultsDir}. Skipping merge.`);
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
console.error(`Pulse Reporter: Error reading report directory ${pulseResultsDir}:`, error);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (reportFiles.length === 0) {
|
|
511
|
+
if (this.printsToStdio()) {
|
|
512
|
+
console.log("Pulse Reporter: No matching JSON report files found to merge.");
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const allResultsFromAllFiles = [];
|
|
517
|
+
let latestTimestamp = new Date(0);
|
|
518
|
+
let lastRunEnvironment = undefined;
|
|
519
|
+
let totalDuration = 0;
|
|
520
|
+
for (const file of reportFiles) {
|
|
521
|
+
const filePath = path.join(pulseResultsDir, file);
|
|
522
|
+
try {
|
|
523
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
524
|
+
const json = JSON.parse(content);
|
|
525
|
+
if (json.run) {
|
|
526
|
+
const runTimestamp = new Date(json.run.timestamp);
|
|
527
|
+
if (runTimestamp > latestTimestamp) {
|
|
528
|
+
latestTimestamp = runTimestamp;
|
|
529
|
+
lastRunEnvironment = json.run.environment || undefined;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (json.results) {
|
|
533
|
+
allResultsFromAllFiles.push(...json.results);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
catch (err) {
|
|
537
|
+
console.warn(`Pulse Reporter: Could not parse report file ${filePath}. Skipping. Error: ${err.message}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// De-duplicate the results from ALL merged files using the helper function
|
|
541
|
+
const finalMergedResults = this._getFinalizedResults(allResultsFromAllFiles);
|
|
542
|
+
// Sum the duration from the final, de-duplicated list of tests
|
|
543
|
+
totalDuration = finalMergedResults.reduce((acc, r) => acc + (r.duration || 0), 0);
|
|
544
|
+
const combinedRun = {
|
|
545
|
+
id: `merged-${Date.now()}`,
|
|
546
|
+
timestamp: latestTimestamp,
|
|
547
|
+
environment: lastRunEnvironment,
|
|
548
|
+
// Recalculate counts based on the truly final, de-duplicated list
|
|
549
|
+
totalTests: finalMergedResults.length,
|
|
550
|
+
passed: finalMergedResults.filter((r) => r.status === "passed").length,
|
|
551
|
+
failed: finalMergedResults.filter((r) => r.status === "failed").length,
|
|
552
|
+
skipped: finalMergedResults.filter((r) => r.status === "skipped").length,
|
|
553
|
+
duration: totalDuration,
|
|
554
|
+
};
|
|
555
|
+
const finalReport = {
|
|
556
|
+
run: combinedRun,
|
|
557
|
+
results: finalMergedResults, // Use the de-duplicated list
|
|
558
|
+
metadata: {
|
|
559
|
+
generatedAt: new Date().toISOString(),
|
|
560
|
+
},
|
|
561
|
+
};
|
|
430
562
|
try {
|
|
431
|
-
await this._ensureDirExists(this.outputDir);
|
|
432
563
|
await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
|
|
433
564
|
if (value instanceof Date)
|
|
434
565
|
return value.toISOString();
|
|
435
|
-
if (typeof value === "bigint")
|
|
436
|
-
return value.toString();
|
|
437
566
|
return value;
|
|
438
567
|
}, 2));
|
|
439
568
|
if (this.printsToStdio()) {
|
|
440
|
-
console.log(`PlaywrightPulseReporter:
|
|
569
|
+
console.log(`PlaywrightPulseReporter: ✅ Merged report with ${finalMergedResults.length} total results saved to ${finalOutputPath}`);
|
|
441
570
|
}
|
|
442
571
|
}
|
|
443
|
-
catch (
|
|
444
|
-
console.error(`Pulse Reporter: Failed to write final
|
|
445
|
-
}
|
|
446
|
-
finally {
|
|
447
|
-
if (this.isSharded) {
|
|
448
|
-
await this._cleanupTemporaryFiles();
|
|
449
|
-
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
console.error(`Pulse Reporter: Failed to write final merged report to ${finalOutputPath}. Error: ${err.message}`);
|
|
450
574
|
}
|
|
451
575
|
}
|
|
452
576
|
}
|