@arghajit/playwright-pulse-report 0.2.0 → 0.2.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 +3 -2
- package/dist/reporter/playwright-pulse-reporter.js +115 -107
- package/package.json +3 -3
- package/scripts/generate-static-report.mjs +945 -1058
- package/scripts/generate-trend.mjs +165 -0
- package/scripts/generate-trend-excel.mjs +0 -273
package/README.md
CHANGED
|
@@ -227,20 +227,21 @@ playwright-pulse-reporter/
|
|
|
227
227
|
│ └── app/ # Next.js dashboard
|
|
228
228
|
├── scripts/
|
|
229
229
|
│ └── generate-static-report.mjs # HTML generator
|
|
230
|
+
| └── generate-trend.mjs # Generate Trends
|
|
230
231
|
| └── merge-pulse-report.mjs # merge sharded reports
|
|
231
232
|
| └── sendReport.mjs # Send email report
|
|
232
233
|
├── pulse-report/ # Generated reports
|
|
233
234
|
└── sample-report.json # Example data
|
|
234
235
|
```
|
|
235
236
|
|
|
236
|
-
## 🎉 What's New in v0.2.
|
|
237
|
+
## 🎉 What's New in v0.2.1
|
|
237
238
|
|
|
238
239
|
### ✨ **Key Improvements**
|
|
239
240
|
|
|
240
241
|
| Feature | Description |
|
|
241
242
|
|---------|-------------|
|
|
242
243
|
| **🎨 Refined UI** | Completely redesigned static HTML reports for better readability and navigation |
|
|
243
|
-
| **📊 History Trends** | Visual analytics for:<br>• Test History for last
|
|
244
|
+
| **📊 History Trends** | Visual analytics for:<br>• Test History for last 15 runs<br>• Test suite pass/fail rates<br>• Duration trends<br>• Individual test flakiness |
|
|
244
245
|
| **🛠️ Project Fixes** | Corrected project name display in test suite components |
|
|
245
246
|
|
|
246
247
|
### 🚀 **Upgrade Now**
|
|
@@ -41,7 +41,7 @@ const crypto_1 = require("crypto");
|
|
|
41
41
|
const attachment_utils_1 = require("./attachment-utils"); // Use relative path
|
|
42
42
|
const convertStatus = (status, testCase) => {
|
|
43
43
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
|
|
44
|
-
return
|
|
44
|
+
return "failed";
|
|
45
45
|
}
|
|
46
46
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "skipped") {
|
|
47
47
|
return "skipped";
|
|
@@ -94,9 +94,12 @@ class PlaywrightPulseReporter {
|
|
|
94
94
|
: undefined;
|
|
95
95
|
this._ensureDirExists(this.outputDir)
|
|
96
96
|
.then(() => {
|
|
97
|
-
if (this.shardIndex === undefined) {
|
|
97
|
+
if (this.shardIndex === undefined || this.shardIndex === 0) {
|
|
98
98
|
console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Pulse outputting to ${this.outputDir}`);
|
|
99
|
-
|
|
99
|
+
if (this.shardIndex === undefined ||
|
|
100
|
+
(this.isSharded && this.shardIndex === 0)) {
|
|
101
|
+
return this._cleanupTemporaryFiles();
|
|
102
|
+
}
|
|
100
103
|
}
|
|
101
104
|
})
|
|
102
105
|
.catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
|
|
@@ -104,14 +107,12 @@ class PlaywrightPulseReporter {
|
|
|
104
107
|
onTestBegin(test) {
|
|
105
108
|
// console.log(`Starting test: ${test.title}`);
|
|
106
109
|
}
|
|
107
|
-
async processStep(step, testId, browserName,
|
|
108
|
-
testCase) {
|
|
110
|
+
async processStep(step, testId, browserName, testCase) {
|
|
109
111
|
var _a, _b, _c, _d;
|
|
110
112
|
let stepStatus = "passed";
|
|
111
113
|
let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
|
|
112
114
|
if ((_c = (_b = step.error) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.startsWith("Test is skipped:")) {
|
|
113
115
|
stepStatus = "skipped";
|
|
114
|
-
errorMessage = "Info: Test is skipped:";
|
|
115
116
|
}
|
|
116
117
|
else {
|
|
117
118
|
stepStatus = convertStatus(step.error ? "failed" : "passed", testCase);
|
|
@@ -124,28 +125,6 @@ class PlaywrightPulseReporter {
|
|
|
124
125
|
codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
|
|
125
126
|
}
|
|
126
127
|
let stepTitle = step.title;
|
|
127
|
-
// This logic had a 'status' variable that was not defined in this scope.
|
|
128
|
-
// Assuming it meant to check 'stepStatus' or 'testCase.expectedStatus' related to step.error.
|
|
129
|
-
// Corrected to reflect comparison with testCase if step.category is 'test'.
|
|
130
|
-
if (step.category === "test" && testCase) {
|
|
131
|
-
// If a test step (not a hook) resulted in an error, but the test was expected to fail,
|
|
132
|
-
// this specific logic might need refinement based on how you want to report step errors
|
|
133
|
-
// within a test that is expected to fail.
|
|
134
|
-
// The current convertStatus handles the overall testCase expectedStatus.
|
|
135
|
-
// For step-specific error messages when testCase.expectedStatus === 'failed':
|
|
136
|
-
if (testCase.expectedStatus === "failed") {
|
|
137
|
-
if (step.error) {
|
|
138
|
-
// If the step itself has an error
|
|
139
|
-
// errorMessage is already set from step.error.message
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
// If a step within an expected-to-fail test passes, it's usually not an error for the step itself.
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
else if (testCase.expectedStatus === "skipped") {
|
|
146
|
-
// errorMessage is already set if step.error.message started with "Test is skipped:"
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
128
|
return {
|
|
150
129
|
id: `${testId}_step_${startTime.toISOString()}-${duration}-${(0, crypto_1.randomUUID)()}`,
|
|
151
130
|
title: stepTitle,
|
|
@@ -169,10 +148,7 @@ class PlaywrightPulseReporter {
|
|
|
169
148
|
async onTestEnd(test, result) {
|
|
170
149
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
171
150
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
172
|
-
|
|
173
|
-
const browserName = ((_b = project === null || project === void 0 ? void 0 : project.use) === null || _b === void 0 ? void 0 : _b.defaultBrowserType) || "unknown";
|
|
174
|
-
// If you need the engine name (chromium, firefox, webkit)
|
|
175
|
-
// const browserEngineName = project?.use?.browserName || "unknown_engine";
|
|
151
|
+
const browserName = ((_b = project === null || project === void 0 ? void 0 : project.use) === null || _b === void 0 ? void 0 : _b.defaultBrowserType) || (project === null || project === void 0 ? void 0 : project.name) || "unknown";
|
|
176
152
|
const testStatus = convertStatus(result.status, test);
|
|
177
153
|
const startTime = new Date(result.startTime);
|
|
178
154
|
const endTime = new Date(startTime.getTime() + result.duration);
|
|
@@ -181,14 +157,10 @@ class PlaywrightPulseReporter {
|
|
|
181
157
|
.titlePath()
|
|
182
158
|
.join("_")
|
|
183
159
|
.replace(/[^a-zA-Z0-9]/g, "_")}_${startTime.getTime()}`;
|
|
184
|
-
const processAllSteps = async (steps
|
|
185
|
-
// parentTestStatus parameter was not used, removed for now.
|
|
186
|
-
// If needed for inherited status logic for steps, it can be re-added.
|
|
187
|
-
) => {
|
|
160
|
+
const processAllSteps = async (steps) => {
|
|
188
161
|
let processed = [];
|
|
189
162
|
for (const step of steps) {
|
|
190
|
-
const processedStep = await this.processStep(step, testIdForFiles, browserName,
|
|
191
|
-
test);
|
|
163
|
+
const processedStep = await this.processStep(step, testIdForFiles, browserName, test);
|
|
192
164
|
processed.push(processedStep);
|
|
193
165
|
if (step.steps && step.steps.length > 0) {
|
|
194
166
|
processedStep.steps = await processAllSteps(step.steps);
|
|
@@ -206,69 +178,56 @@ class PlaywrightPulseReporter {
|
|
|
206
178
|
catch (e) {
|
|
207
179
|
console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
|
|
208
180
|
}
|
|
209
|
-
// --- Capture stdout and stderr ---
|
|
210
181
|
const stdoutMessages = [];
|
|
211
182
|
if (result.stdout && result.stdout.length > 0) {
|
|
212
183
|
result.stdout.forEach((item) => {
|
|
213
|
-
|
|
214
|
-
stdoutMessages.push(item);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
// If item is not a string, Playwright's typings indicate it's a Buffer (or Buffer-like).
|
|
218
|
-
// We must call toString() on it.
|
|
219
|
-
// The 'item' here is typed as 'Buffer' from the 'else' branch of '(string | Buffer)[]'
|
|
220
|
-
stdoutMessages.push(item.toString());
|
|
221
|
-
}
|
|
184
|
+
stdoutMessages.push(typeof item === "string" ? item : item.toString());
|
|
222
185
|
});
|
|
223
186
|
}
|
|
224
187
|
const stderrMessages = [];
|
|
225
188
|
if (result.stderr && result.stderr.length > 0) {
|
|
226
189
|
result.stderr.forEach((item) => {
|
|
227
|
-
|
|
228
|
-
stderrMessages.push(item);
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
// If item is not a string, Playwright's typings indicate it's a Buffer (or Buffer-like).
|
|
232
|
-
// We must call toString() on it.
|
|
233
|
-
stderrMessages.push(item.toString());
|
|
234
|
-
}
|
|
190
|
+
stderrMessages.push(typeof item === "string" ? item : item.toString());
|
|
235
191
|
});
|
|
236
192
|
}
|
|
237
|
-
|
|
193
|
+
const uniqueTestId = test.id;
|
|
238
194
|
const pulseResult = {
|
|
239
|
-
id:
|
|
195
|
+
id: uniqueTestId,
|
|
240
196
|
runId: "TBD",
|
|
241
197
|
name: test.titlePath().join(" > "),
|
|
242
|
-
// Use project.name for suiteName if desired, or fallback
|
|
243
198
|
suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_f = this.config.projects[0]) === null || _f === void 0 ? void 0 : _f.name) || "Default Suite",
|
|
244
199
|
status: testStatus,
|
|
245
200
|
duration: result.duration,
|
|
246
201
|
startTime: startTime,
|
|
247
202
|
endTime: endTime,
|
|
248
|
-
browser: browserName,
|
|
203
|
+
browser: browserName,
|
|
249
204
|
retries: result.retry,
|
|
250
205
|
steps: ((_g = result.steps) === null || _g === void 0 ? void 0 : _g.length) ? await processAllSteps(result.steps) : [],
|
|
251
206
|
errorMessage: (_h = result.error) === null || _h === void 0 ? void 0 : _h.message,
|
|
252
207
|
stackTrace: (_j = result.error) === null || _j === void 0 ? void 0 : _j.stack,
|
|
253
208
|
codeSnippet: codeSnippet,
|
|
254
209
|
tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
|
|
255
|
-
screenshots: [],
|
|
210
|
+
screenshots: [],
|
|
256
211
|
videoPath: undefined,
|
|
257
212
|
tracePath: undefined,
|
|
258
|
-
// videoPath and tracePath might be deprecated if using the array versions above
|
|
259
|
-
// Depending on attachFiles implementation
|
|
260
|
-
// Add the captured console messages
|
|
261
213
|
stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
|
|
262
214
|
stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
|
|
263
215
|
};
|
|
264
216
|
try {
|
|
265
|
-
// Pass this.options which should contain the resolved outputDir
|
|
266
217
|
(0, attachment_utils_1.attachFiles)(testIdForFiles, result, pulseResult, this.options);
|
|
267
218
|
}
|
|
268
219
|
catch (attachError) {
|
|
269
220
|
console.error(`Pulse Reporter: Error processing attachments for test ${pulseResult.name} (ID: ${testIdForFiles}): ${attachError.message}`);
|
|
270
221
|
}
|
|
271
|
-
this.results.
|
|
222
|
+
const existingTestIndex = this.results.findIndex((r) => r.id === uniqueTestId);
|
|
223
|
+
if (existingTestIndex !== -1) {
|
|
224
|
+
if (pulseResult.retries >= this.results[existingTestIndex].retries) {
|
|
225
|
+
this.results[existingTestIndex] = pulseResult;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
this.results.push(pulseResult);
|
|
230
|
+
}
|
|
272
231
|
}
|
|
273
232
|
onError(error) {
|
|
274
233
|
var _a;
|
|
@@ -279,7 +238,6 @@ class PlaywrightPulseReporter {
|
|
|
279
238
|
}
|
|
280
239
|
async _writeShardResults() {
|
|
281
240
|
if (this.shardIndex === undefined) {
|
|
282
|
-
// console.warn("Pulse Reporter: _writeShardResults called unexpectedly in main process. Skipping.");
|
|
283
241
|
return;
|
|
284
242
|
}
|
|
285
243
|
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${this.shardIndex}.json`);
|
|
@@ -291,29 +249,38 @@ class PlaywrightPulseReporter {
|
|
|
291
249
|
}
|
|
292
250
|
}
|
|
293
251
|
async _mergeShardResults(finalRunData) {
|
|
294
|
-
let
|
|
252
|
+
let allShardProcessedResults = [];
|
|
295
253
|
const totalShards = this.config.shard ? this.config.shard.total : 1;
|
|
296
254
|
for (let i = 0; i < totalShards; i++) {
|
|
297
255
|
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
|
|
298
256
|
try {
|
|
299
257
|
const content = await fs.readFile(tempFilePath, "utf-8");
|
|
300
258
|
const shardResults = JSON.parse(content);
|
|
301
|
-
|
|
302
|
-
|
|
259
|
+
allShardProcessedResults =
|
|
260
|
+
allShardProcessedResults.concat(shardResults);
|
|
303
261
|
}
|
|
304
262
|
catch (error) {
|
|
305
263
|
if ((error === null || error === void 0 ? void 0 : error.code) === "ENOENT") {
|
|
306
|
-
console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}.`);
|
|
264
|
+
console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}. This might be normal if a shard had no tests or failed early.`);
|
|
307
265
|
}
|
|
308
266
|
else {
|
|
309
267
|
console.error(`Pulse Reporter: Could not read/parse results from shard ${i} (${tempFilePath}). Error:`, error);
|
|
310
268
|
}
|
|
311
269
|
}
|
|
312
270
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
271
|
+
let finalUniqueResultsMap = new Map();
|
|
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());
|
|
279
|
+
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
280
|
+
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
281
|
+
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
282
|
+
finalRunData.skipped = finalResultsList.filter((r) => r.status === "skipped").length;
|
|
283
|
+
finalRunData.totalTests = finalResultsList.length;
|
|
317
284
|
const reviveDates = (key, value) => {
|
|
318
285
|
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
319
286
|
if (typeof value === "string" && isoDateRegex.test(value)) {
|
|
@@ -322,10 +289,10 @@ class PlaywrightPulseReporter {
|
|
|
322
289
|
}
|
|
323
290
|
return value;
|
|
324
291
|
};
|
|
325
|
-
const
|
|
292
|
+
const properlyTypedResults = JSON.parse(JSON.stringify(finalResultsList), reviveDates);
|
|
326
293
|
return {
|
|
327
294
|
run: finalRunData,
|
|
328
|
-
results:
|
|
295
|
+
results: properlyTypedResults,
|
|
329
296
|
metadata: { generatedAt: new Date().toISOString() },
|
|
330
297
|
};
|
|
331
298
|
}
|
|
@@ -339,12 +306,11 @@ class PlaywrightPulseReporter {
|
|
|
339
306
|
}
|
|
340
307
|
catch (error) {
|
|
341
308
|
if ((error === null || error === void 0 ? void 0 : error.code) !== "ENOENT") {
|
|
342
|
-
console.
|
|
309
|
+
console.warn("Pulse Reporter: Warning during cleanup of temporary files:", error.message);
|
|
343
310
|
}
|
|
344
311
|
}
|
|
345
312
|
}
|
|
346
313
|
async _ensureDirExists(dirPath) {
|
|
347
|
-
// Removed 'clean' parameter as it was unused
|
|
348
314
|
try {
|
|
349
315
|
await fs.mkdir(dirPath, { recursive: true });
|
|
350
316
|
}
|
|
@@ -356,14 +322,13 @@ class PlaywrightPulseReporter {
|
|
|
356
322
|
}
|
|
357
323
|
}
|
|
358
324
|
async onEnd(result) {
|
|
359
|
-
var _a, _b, _c
|
|
325
|
+
var _a, _b, _c;
|
|
360
326
|
if (this.shardIndex !== undefined) {
|
|
361
327
|
await this._writeShardResults();
|
|
362
328
|
return;
|
|
363
329
|
}
|
|
364
330
|
const runEndTime = Date.now();
|
|
365
331
|
const duration = runEndTime - this.runStartTime;
|
|
366
|
-
// Consider making the UUID part truly random for each run if this ID needs to be globally unique over time
|
|
367
332
|
const runId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
|
|
368
333
|
const runData = {
|
|
369
334
|
id: runId,
|
|
@@ -374,7 +339,7 @@ class PlaywrightPulseReporter {
|
|
|
374
339
|
skipped: 0,
|
|
375
340
|
duration,
|
|
376
341
|
};
|
|
377
|
-
let finalReport;
|
|
342
|
+
let finalReport = undefined; // Initialize as undefined
|
|
378
343
|
if (this.isSharded) {
|
|
379
344
|
finalReport = await this._mergeShardResults(runData);
|
|
380
345
|
}
|
|
@@ -384,42 +349,83 @@ class PlaywrightPulseReporter {
|
|
|
384
349
|
runData.failed = this.results.filter((r) => r.status === "failed").length;
|
|
385
350
|
runData.skipped = this.results.filter((r) => r.status === "skipped").length;
|
|
386
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);
|
|
387
361
|
finalReport = {
|
|
388
362
|
run: runData,
|
|
389
|
-
results:
|
|
363
|
+
results: properlyTypedResults,
|
|
390
364
|
metadata: { generatedAt: new Date().toISOString() },
|
|
391
365
|
};
|
|
392
366
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
367
|
+
if (!finalReport) {
|
|
368
|
+
console.error("PlaywrightPulseReporter: CRITICAL - finalReport object was not generated. Cannot create summary.");
|
|
369
|
+
const errorSummary = `
|
|
370
|
+
PlaywrightPulseReporter: Run Finished
|
|
371
|
+
-----------------------------------------
|
|
372
|
+
Overall Status: ERROR (Report data missing)
|
|
373
|
+
Total Tests: N/A
|
|
374
|
+
Passed: N/A
|
|
375
|
+
Failed: N/A
|
|
376
|
+
Skipped: N/A
|
|
377
|
+
Duration: N/A
|
|
378
|
+
-----------------------------------------`;
|
|
379
|
+
if (this.printsToStdio()) {
|
|
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);
|
|
398
|
+
try {
|
|
399
|
+
await this._ensureDirExists(this.outputDir);
|
|
400
|
+
await fs.writeFile(finalOutputPathOnError, JSON.stringify(errorReport, null, 2));
|
|
401
|
+
console.warn(`PlaywrightPulseReporter: Wrote an error report to ${finalOutputPathOnError} as finalReport was missing.`);
|
|
402
|
+
}
|
|
403
|
+
catch (writeError) {
|
|
404
|
+
console.error(`PlaywrightPulseReporter: Failed to write error report: ${writeError.message}`);
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const reportRunData = finalReport.run;
|
|
409
|
+
const finalRunStatus = ((_a = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.failed) !== null && _a !== void 0 ? _a : 0) > 0
|
|
408
410
|
? "failed"
|
|
409
|
-
: ((
|
|
410
|
-
?
|
|
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"
|
|
411
415
|
: "passed";
|
|
412
416
|
const summary = `
|
|
413
417
|
PlaywrightPulseReporter: Run Finished
|
|
414
418
|
-----------------------------------------
|
|
415
419
|
Overall Status: ${finalRunStatus.toUpperCase()}
|
|
416
|
-
Total Tests: ${(
|
|
417
|
-
Passed: ${
|
|
418
|
-
Failed: ${
|
|
419
|
-
Skipped: ${
|
|
420
|
-
Duration: ${(duration / 1000).toFixed(2)}s
|
|
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
|
|
421
425
|
-----------------------------------------`;
|
|
422
|
-
|
|
426
|
+
if (this.printsToStdio()) {
|
|
427
|
+
console.log(summary);
|
|
428
|
+
}
|
|
423
429
|
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
424
430
|
try {
|
|
425
431
|
await this._ensureDirExists(this.outputDir);
|
|
@@ -430,7 +436,9 @@ PlaywrightPulseReporter: Run Finished
|
|
|
430
436
|
return value.toString();
|
|
431
437
|
return value;
|
|
432
438
|
}, 2));
|
|
433
|
-
|
|
439
|
+
if (this.printsToStdio()) {
|
|
440
|
+
console.log(`PlaywrightPulseReporter: JSON report written to ${finalOutputPath}`);
|
|
441
|
+
}
|
|
434
442
|
}
|
|
435
443
|
catch (error) {
|
|
436
444
|
console.error(`Pulse Reporter: Failed to write final JSON report to ${finalOutputPath}. Error: ${error.message}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arghajit/playwright-pulse-report",
|
|
3
3
|
"author": "Arghajit Singha",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"playwright",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"generate-pulse-report": "./scripts/generate-static-report.mjs",
|
|
29
29
|
"merge-pulse-report": "./scripts/merge-pulse-report.js",
|
|
30
30
|
"send-email": "./scripts/sendReport.js",
|
|
31
|
-
"generate-trend": "./scripts/generate-trend
|
|
31
|
+
"generate-trend": "./scripts/generate-trend.mjs"
|
|
32
32
|
},
|
|
33
33
|
"exports": {
|
|
34
34
|
".": {
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"dotenv": "^16.5.0",
|
|
86
86
|
"firebase": "^11.3.0",
|
|
87
87
|
"genkit": "^1.6.2",
|
|
88
|
+
"highcharts": "^12.2.0",
|
|
88
89
|
"jsdom": "^26.1.0",
|
|
89
90
|
"lucide-react": "^0.475.0",
|
|
90
91
|
"next": "15.2.3",
|
|
@@ -97,7 +98,6 @@
|
|
|
97
98
|
"recharts": "^2.15.1",
|
|
98
99
|
"tailwind-merge": "^3.0.1",
|
|
99
100
|
"tailwindcss-animate": "^1.0.7",
|
|
100
|
-
"xlsx": "^0.18.5",
|
|
101
101
|
"zod": "^3.24.2"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|