@arghajit/dummy 0.1.0
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 +259 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +26 -0
- package/dist/lib/report-types.d.ts +8 -0
- package/dist/lib/report-types.js +2 -0
- package/dist/playwright-pulse-reporter.d.ts +26 -0
- package/dist/playwright-pulse-reporter.js +304 -0
- package/dist/reporter/attachment-utils.d.ts +10 -0
- package/dist/reporter/attachment-utils.js +192 -0
- package/dist/reporter/index.d.ts +5 -0
- package/dist/reporter/index.js +9 -0
- package/dist/reporter/lib/report-types.d.ts +8 -0
- package/dist/reporter/lib/report-types.js +2 -0
- package/dist/reporter/playwright-pulse-reporter.d.ts +27 -0
- package/dist/reporter/playwright-pulse-reporter.js +454 -0
- package/dist/reporter/reporter/playwright-pulse-reporter.d.ts +1 -0
- package/dist/reporter/reporter/playwright-pulse-reporter.js +398 -0
- package/dist/reporter/types/index.d.ts +52 -0
- package/dist/reporter/types/index.js +2 -0
- package/dist/types/index.d.ts +65 -0
- package/dist/types/index.js +2 -0
- package/package.json +73 -0
- 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
- package/scripts/generate-static-report.mjs +2279 -0
- package/scripts/generate-trend.mjs +165 -0
- package/scripts/merge-pulse-report.js +81 -0
- package/scripts/sendReport.js +335 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
const fs = __importStar(require("fs/promises"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
// Helper to convert Playwright status to Pulse status
|
|
48
|
+
const convertStatus = (status) => {
|
|
49
|
+
if (status === "passed")
|
|
50
|
+
return "passed";
|
|
51
|
+
if (status === "failed" || status === "timedOut" || status === "interrupted")
|
|
52
|
+
return "failed";
|
|
53
|
+
return "skipped";
|
|
54
|
+
};
|
|
55
|
+
const TEMP_SHARD_FILE_PREFIX = ".pulse-shard-results-";
|
|
56
|
+
class PlaywrightPulseReporter {
|
|
57
|
+
constructor(options = {}) {
|
|
58
|
+
var _a;
|
|
59
|
+
this.results = []; // Holds results *per process* (main or shard)
|
|
60
|
+
this.baseOutputFile = "playwright-pulse-report.json";
|
|
61
|
+
this.isSharded = false;
|
|
62
|
+
this.shardIndex = undefined;
|
|
63
|
+
this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
|
|
64
|
+
// Resolve outputDir relative to playwright config directory or cwd if not specified
|
|
65
|
+
// Ensure options.outputDir exists before trying to resolve
|
|
66
|
+
const baseDir = options.outputDir
|
|
67
|
+
? path.resolve(options.outputDir)
|
|
68
|
+
: process.cwd();
|
|
69
|
+
this.outputDir = baseDir;
|
|
70
|
+
// Note: Final resolution happens in onBegin after config is available
|
|
71
|
+
console.log(`PlaywrightPulseReporter: Initial Output dir configured to ${this.outputDir}`);
|
|
72
|
+
}
|
|
73
|
+
printsToStdio() {
|
|
74
|
+
// Prevent shard processes other than the first from printing duplicate status updates
|
|
75
|
+
// The main process (index undefined) or the first shard (index 0) can print.
|
|
76
|
+
return this.shardIndex === undefined || this.shardIndex === 0;
|
|
77
|
+
}
|
|
78
|
+
onBegin(config, suite) {
|
|
79
|
+
this.config = config;
|
|
80
|
+
this.suite = suite;
|
|
81
|
+
this.runStartTime = Date.now();
|
|
82
|
+
// Determine sharding configuration
|
|
83
|
+
const totalShards = parseInt(
|
|
84
|
+
process.env.PLAYWRIGHT_SHARD_TOTAL || "1",
|
|
85
|
+
10
|
|
86
|
+
);
|
|
87
|
+
this.isSharded = totalShards > 1;
|
|
88
|
+
if (process.env.PLAYWRIGHT_SHARD_INDEX !== undefined) {
|
|
89
|
+
this.shardIndex = parseInt(process.env.PLAYWRIGHT_SHARD_INDEX, 10);
|
|
90
|
+
}
|
|
91
|
+
// Resolve outputDir relative to playwright config directory if possible, otherwise use cwd
|
|
92
|
+
// This needs the config object, so it's done in onBegin
|
|
93
|
+
const configDir = this.config.rootDir; // Playwright config directory
|
|
94
|
+
// Use outputDir from options if provided and resolve it relative to configDir, otherwise default
|
|
95
|
+
this.outputDir = this.outputDir
|
|
96
|
+
? path.resolve(configDir, this.outputDir)
|
|
97
|
+
: path.resolve(configDir, "pulse-report"); // Default to 'pulse-report' relative to config
|
|
98
|
+
console.log(
|
|
99
|
+
`PlaywrightPulseReporter: Final Output dir resolved to ${this.outputDir}`
|
|
100
|
+
);
|
|
101
|
+
if (this.shardIndex === undefined) {
|
|
102
|
+
// Main process
|
|
103
|
+
console.log(
|
|
104
|
+
`PlaywrightPulseReporter: Starting test run with ${
|
|
105
|
+
suite.allTests().length
|
|
106
|
+
} tests${
|
|
107
|
+
this.isSharded ? ` across ${totalShards} shards` : ""
|
|
108
|
+
}. Outputting to ${this.outputDir}`
|
|
109
|
+
);
|
|
110
|
+
// Clean up any leftover temp files from previous runs in the main process
|
|
111
|
+
this._cleanupTemporaryFiles().catch((err) =>
|
|
112
|
+
console.error("Pulse Reporter: Error cleaning up temp files:", err)
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
// Shard process
|
|
116
|
+
console.log(
|
|
117
|
+
`PlaywrightPulseReporter: Shard ${
|
|
118
|
+
this.shardIndex + 1
|
|
119
|
+
}/${totalShards} starting. Outputting temp results to ${
|
|
120
|
+
this.outputDir
|
|
121
|
+
}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
onTestBegin(test) {
|
|
126
|
+
// Optional: Log test start (maybe only in main process or first shard?)
|
|
127
|
+
// if (this.printsToStdio()) {
|
|
128
|
+
// console.log(`Starting test: ${test.title}`);
|
|
129
|
+
// }
|
|
130
|
+
}
|
|
131
|
+
processStep(step, parentStatus) {
|
|
132
|
+
var _a;
|
|
133
|
+
// If parent failed or was skipped, all child steps inherit that status
|
|
134
|
+
const inherentStatus = parentStatus === "failed" || parentStatus === "skipped"
|
|
135
|
+
? parentStatus
|
|
136
|
+
: convertStatus(step.error ? "failed" : "passed");
|
|
137
|
+
const duration = step.duration;
|
|
138
|
+
const startTime = new Date(step.startTime);
|
|
139
|
+
// Ensure endTime is calculated correctly, respecting duration might be 0
|
|
140
|
+
const endTime = new Date(startTime.getTime() + Math.max(0, duration));
|
|
141
|
+
return {
|
|
142
|
+
// Create a somewhat unique ID combining title and timing details
|
|
143
|
+
id: `${step.title}-${startTime.toISOString()}-${duration}-${Math.random()
|
|
144
|
+
.toString(16)
|
|
145
|
+
.slice(2)}`, // Add random suffix for uniqueness
|
|
146
|
+
title: step.title,
|
|
147
|
+
status: inherentStatus,
|
|
148
|
+
duration: duration,
|
|
149
|
+
startTime: startTime,
|
|
150
|
+
endTime: endTime,
|
|
151
|
+
errorMessage: (_a = step.error) === null || _a === void 0 ? void 0 : _a.message,
|
|
152
|
+
// We won't embed screenshots directly, maybe paths later
|
|
153
|
+
screenshot: undefined, // Placeholder for potential future enhancement
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
onTestEnd(test, result) {
|
|
157
|
+
// This runs in each SHARD PROCESS
|
|
158
|
+
var _a, _b, _c, _d, _e;
|
|
159
|
+
const testStatus = convertStatus(result.status);
|
|
160
|
+
const startTime = new Date(result.startTime);
|
|
161
|
+
const endTime = new Date(startTime.getTime() + result.duration);
|
|
162
|
+
const processAllSteps = (steps, parentTestStatus) => {
|
|
163
|
+
let processed = [];
|
|
164
|
+
for (const step of steps) {
|
|
165
|
+
// Pass the overall test status down, as a step cannot pass if the test failed/skipped
|
|
166
|
+
const processedStep = this.processStep(step, parentTestStatus);
|
|
167
|
+
processed.push(processedStep);
|
|
168
|
+
if (step.steps.length > 0) {
|
|
169
|
+
// Use the processed step's status for its children
|
|
170
|
+
processed = processed.concat(processAllSteps(step.steps, processedStep.status));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return processed;
|
|
174
|
+
};
|
|
175
|
+
// Extract code snippet if available (experimental, might not be reliable)
|
|
176
|
+
let codeSnippet = undefined;
|
|
177
|
+
try {
|
|
178
|
+
if ((_a = test.location) === null || _a === void 0 ? void 0 : _a.file) {
|
|
179
|
+
// This requires reading the file, which might be slow or have permissions issues
|
|
180
|
+
// const fileContent = fs.readFileSync(test.location.file, 'utf-8');
|
|
181
|
+
// const lines = fileContent.split('\n');
|
|
182
|
+
// // Extract lines around the test definition (this is a rough guess)
|
|
183
|
+
// const startLine = Math.max(0, test.location.line - 5);
|
|
184
|
+
// const endLine = Math.min(lines.length, test.location.line + 10);
|
|
185
|
+
// codeSnippet = lines.slice(startLine, endLine).join('\n');
|
|
186
|
+
codeSnippet = `Test defined at: ${test.location.file}:${test.location.line}:${test.location.column}`; // Simpler placeholder
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
|
|
191
|
+
}
|
|
192
|
+
const pulseResult = {
|
|
193
|
+
id: test.id ||
|
|
194
|
+
`${test.title}-${startTime.toISOString()}-${Math.random()
|
|
195
|
+
.toString(16)
|
|
196
|
+
.slice(2)}`, // Fallback ID if test.id is missing
|
|
197
|
+
runId: "TBD", // Placeholder, will be set by main process later
|
|
198
|
+
name: test.titlePath().join(" > "), // Use full title path
|
|
199
|
+
suiteName: test.parent.title,
|
|
200
|
+
status: testStatus,
|
|
201
|
+
duration: result.duration,
|
|
202
|
+
startTime: startTime,
|
|
203
|
+
endTime: endTime,
|
|
204
|
+
retries: result.retry,
|
|
205
|
+
// Process steps recursively, passing the final test status
|
|
206
|
+
steps: processAllSteps(result.steps, testStatus),
|
|
207
|
+
errorMessage: (_b = result.error) === null || _b === void 0 ? void 0 : _b.message,
|
|
208
|
+
stackTrace: (_c = result.error) === null || _c === void 0 ? void 0 : _c.stack,
|
|
209
|
+
codeSnippet: codeSnippet,
|
|
210
|
+
// Get relative paths for attachments if possible, otherwise use absolute
|
|
211
|
+
screenshot: (_d = result.attachments.find((a) => a.name === "screenshot")) === null || _d === void 0 ? void 0 : _d.path,
|
|
212
|
+
video: (_e = result.attachments.find((a) => a.name === "video")) === null || _e === void 0 ? void 0 : _e.path,
|
|
213
|
+
tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
|
|
214
|
+
};
|
|
215
|
+
this.results.push(pulseResult);
|
|
216
|
+
// console.log(`Finished test: ${test.title} - ${result.status}`); // Optional: Log test end per shard
|
|
217
|
+
}
|
|
218
|
+
onError(error) {
|
|
219
|
+
var _a;
|
|
220
|
+
// This can run in shards or main process
|
|
221
|
+
console.error(`PlaywrightPulseReporter: Error encountered (Shard: ${(_a = this.shardIndex) !== null && _a !== void 0 ? _a : "Main"}):`, error);
|
|
222
|
+
}
|
|
223
|
+
_writeShardResults() {
|
|
224
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
225
|
+
// Writes the results gathered in *this specific shard process* to a temp file
|
|
226
|
+
if (this.shardIndex === undefined) {
|
|
227
|
+
console.warn("Pulse Reporter: _writeShardResults called in main process. Skipping.");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${this.shardIndex}.json`);
|
|
231
|
+
try {
|
|
232
|
+
yield this._ensureDirExists(this.outputDir);
|
|
233
|
+
yield fs.writeFile(tempFilePath, JSON.stringify(this.results, null, 2));
|
|
234
|
+
// console.log(`Pulse Reporter: Shard ${this.shardIndex} results written to ${tempFilePath}`);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
console.error(`Pulse Reporter: Shard ${this.shardIndex} failed to write temporary results to ${tempFilePath}`, error);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
_mergeShardResults(finalRunData) {
|
|
242
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
243
|
+
// Runs *only* in the main process to merge results from all shards
|
|
244
|
+
console.log("Pulse Reporter: Merging results from shards...");
|
|
245
|
+
let allResults = [];
|
|
246
|
+
const totalShards = parseInt(process.env.PLAYWRIGHT_SHARD_TOTAL || "1", 10);
|
|
247
|
+
for (let i = 0; i < totalShards; i++) {
|
|
248
|
+
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
|
|
249
|
+
try {
|
|
250
|
+
const content = yield fs.readFile(tempFilePath, "utf-8");
|
|
251
|
+
const shardResults = JSON.parse(content);
|
|
252
|
+
// Assign the final runId to results from this shard
|
|
253
|
+
shardResults.forEach((r) => (r.runId = finalRunData.id));
|
|
254
|
+
allResults = allResults.concat(shardResults);
|
|
255
|
+
// console.log(`Pulse Reporter: Merged ${shardResults.length} results from shard ${i}`);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (error &&
|
|
259
|
+
typeof error === "object" &&
|
|
260
|
+
"code" in error &&
|
|
261
|
+
error.code === "ENOENT") {
|
|
262
|
+
console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}. This might happen if a shard had no tests or failed early.`);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
console.warn(`Pulse Reporter: Could not read or parse results from shard ${i} (${tempFilePath}). Error: ${error}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
console.log(`Pulse Reporter: Merged a total of ${allResults.length} results from ${totalShards} shards.`);
|
|
270
|
+
// Recalculate final counts based on merged results
|
|
271
|
+
finalRunData.passed = allResults.filter((r) => r.status === "passed").length;
|
|
272
|
+
finalRunData.failed = allResults.filter((r) => r.status === "failed").length;
|
|
273
|
+
finalRunData.skipped = allResults.filter((r) => r.status === "skipped").length;
|
|
274
|
+
finalRunData.totalTests = allResults.length;
|
|
275
|
+
return {
|
|
276
|
+
run: finalRunData,
|
|
277
|
+
results: allResults,
|
|
278
|
+
metadata: { generatedAt: new Date().toISOString() },
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
_cleanupTemporaryFiles() {
|
|
283
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
284
|
+
// Runs *only* in the main process after merging or on error
|
|
285
|
+
try {
|
|
286
|
+
yield this._ensureDirExists(this.outputDir); // Ensure dir exists before reading
|
|
287
|
+
const files = yield fs.readdir(this.outputDir);
|
|
288
|
+
const tempFiles = files.filter((f) => f.startsWith(TEMP_SHARD_FILE_PREFIX));
|
|
289
|
+
if (tempFiles.length > 0) {
|
|
290
|
+
console.log(`Pulse Reporter: Cleaning up ${tempFiles.length} temporary shard files...`);
|
|
291
|
+
yield Promise.all(tempFiles.map((f) => fs.unlink(path.join(this.outputDir, f))));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
// Ignore ENOENT (directory not found) errors, log others
|
|
296
|
+
if (error &&
|
|
297
|
+
typeof error === "object" &&
|
|
298
|
+
"code" in error &&
|
|
299
|
+
error.code !== "ENOENT") {
|
|
300
|
+
console.error("Pulse Reporter: Error cleaning up temporary files:", error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
_ensureDirExists(dirPath) {
|
|
306
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
307
|
+
try {
|
|
308
|
+
yield fs.mkdir(dirPath, { recursive: true });
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
// Ignore EEXIST errors (directory already exists)
|
|
312
|
+
if (error &&
|
|
313
|
+
typeof error === "object" &&
|
|
314
|
+
"code" in error &&
|
|
315
|
+
error.code !== "EEXIST") {
|
|
316
|
+
console.error(`Pulse Reporter: Failed to ensure directory exists: ${dirPath}`, error); // Log error if mkdir fails unexpectedly
|
|
317
|
+
throw error; // Re-throw other errors
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
onEnd(result) {
|
|
323
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
324
|
+
var _a, _b, _c, _d, _e, _f;
|
|
325
|
+
if (this.shardIndex !== undefined) {
|
|
326
|
+
// This is a shard process, write its results to a temp file and exit
|
|
327
|
+
yield this._writeShardResults();
|
|
328
|
+
console.log(`PlaywrightPulseReporter: Shard ${this.shardIndex + 1} finished.`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
// ---- This is the MAIN PROCESS ----
|
|
332
|
+
const runEndTime = Date.now();
|
|
333
|
+
const duration = runEndTime - this.runStartTime;
|
|
334
|
+
// The main process result.status might not be accurate with sharding, recalculate later
|
|
335
|
+
// const runStatus = convertStatus(result.status);
|
|
336
|
+
const runId = `run-${this.runStartTime}-${Math.random()
|
|
337
|
+
.toString(16)
|
|
338
|
+
.slice(2)}`; // Add randomness to run ID for uniqueness
|
|
339
|
+
// Initial run data (counts will be recalculated after merging)
|
|
340
|
+
const runData = {
|
|
341
|
+
id: runId,
|
|
342
|
+
timestamp: new Date(this.runStartTime),
|
|
343
|
+
totalTests: 0, // Placeholder
|
|
344
|
+
passed: 0, // Placeholder
|
|
345
|
+
failed: 0, // Placeholder
|
|
346
|
+
skipped: 0, // Placeholder
|
|
347
|
+
duration,
|
|
348
|
+
};
|
|
349
|
+
let finalReport;
|
|
350
|
+
if (this.isSharded) {
|
|
351
|
+
// Merge results from all shard temp files
|
|
352
|
+
finalReport = yield this._mergeShardResults(runData);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
// No sharding, use the results gathered in this main process
|
|
356
|
+
this.results.forEach((r) => (r.runId = runId)); // Assign runId
|
|
357
|
+
runData.passed = this.results.filter((r) => r.status === "passed").length;
|
|
358
|
+
runData.failed = this.results.filter((r) => r.status === "failed").length;
|
|
359
|
+
runData.skipped = this.results.filter((r) => r.status === "skipped").length;
|
|
360
|
+
runData.totalTests = this.results.length;
|
|
361
|
+
finalReport = {
|
|
362
|
+
run: runData,
|
|
363
|
+
results: this.results,
|
|
364
|
+
metadata: { generatedAt: new Date().toISOString() },
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
// Log final summary from the main process
|
|
368
|
+
const finalRunStatus = ((_b = (_a = finalReport.run) === null || _a === void 0 ? void 0 : _a.failed) !== null && _b !== void 0 ? _b : 0 > 0) ? "failed" : "passed"; // Simplified overall status
|
|
369
|
+
console.log(`PlaywrightPulseReporter: Test run finished with overall status: ${finalRunStatus}`);
|
|
370
|
+
console.log(` Passed: ${(_c = finalReport.run) === null || _c === void 0 ? void 0 : _c.passed}, Failed: ${(_d = finalReport.run) === null || _d === void 0 ? void 0 : _d.failed}, Skipped: ${(_e = finalReport.run) === null || _e === void 0 ? void 0 : _e.skipped}`);
|
|
371
|
+
console.log(` Total tests: ${(_f = finalReport.run) === null || _f === void 0 ? void 0 : _f.totalTests}`);
|
|
372
|
+
console.log(` Total time: ${(duration / 1000).toFixed(2)}s`);
|
|
373
|
+
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
374
|
+
try {
|
|
375
|
+
yield this._ensureDirExists(this.outputDir);
|
|
376
|
+
yield fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
|
|
377
|
+
// Custom replacer to handle Date objects -> ISO strings
|
|
378
|
+
if (value instanceof Date) {
|
|
379
|
+
return value.toISOString();
|
|
380
|
+
}
|
|
381
|
+
return value;
|
|
382
|
+
}, 2));
|
|
383
|
+
console.log(`PlaywrightPulseReporter: Final report written to ${finalOutputPath}`);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
console.error(`PlaywrightPulseReporter: Failed to write final report to ${finalOutputPath}`, error);
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
// Clean up temporary shard files after merging (or if merge failed)
|
|
390
|
+
if (this.isSharded) {
|
|
391
|
+
yield this._cleanupTemporaryFiles();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Use CommonJS export for compatibility
|
|
398
|
+
module.exports = PlaywrightPulseReporter;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { LucideIcon } from 'lucide-react';
|
|
2
|
+
export type TestStatus = 'passed' | 'failed' | 'skipped';
|
|
3
|
+
export interface TestStep {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
status: TestStatus;
|
|
7
|
+
duration: number;
|
|
8
|
+
startTime: Date;
|
|
9
|
+
endTime: Date;
|
|
10
|
+
errorMessage?: string;
|
|
11
|
+
screenshot?: string;
|
|
12
|
+
videoTimestamp?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface TestResult {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
status: TestStatus;
|
|
18
|
+
duration: number;
|
|
19
|
+
startTime: Date;
|
|
20
|
+
endTime: Date;
|
|
21
|
+
retries: number;
|
|
22
|
+
steps: TestStep[];
|
|
23
|
+
errorMessage?: string;
|
|
24
|
+
stackTrace?: string;
|
|
25
|
+
codeSnippet?: string;
|
|
26
|
+
screenshot?: string;
|
|
27
|
+
video?: string;
|
|
28
|
+
tags?: string[];
|
|
29
|
+
suiteName?: string;
|
|
30
|
+
runId: string;
|
|
31
|
+
}
|
|
32
|
+
export interface TestRun {
|
|
33
|
+
id: string;
|
|
34
|
+
timestamp: Date;
|
|
35
|
+
totalTests: number;
|
|
36
|
+
passed: number;
|
|
37
|
+
failed: number;
|
|
38
|
+
skipped: number;
|
|
39
|
+
duration: number;
|
|
40
|
+
}
|
|
41
|
+
export interface TrendDataPoint {
|
|
42
|
+
date: string;
|
|
43
|
+
passed: number;
|
|
44
|
+
failed: number;
|
|
45
|
+
skipped: number;
|
|
46
|
+
}
|
|
47
|
+
export interface SummaryMetric {
|
|
48
|
+
label: string;
|
|
49
|
+
value: string | number;
|
|
50
|
+
icon: LucideIcon;
|
|
51
|
+
color?: string;
|
|
52
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { LucideIcon } from 'lucide-react';
|
|
2
|
+
export type TestStatus = "passed" | "failed" | "skipped" | "expected-failure" | "unexpected-success" | "explicitly-skipped";
|
|
3
|
+
export interface TestStep {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
status: TestStatus;
|
|
7
|
+
duration: number;
|
|
8
|
+
startTime: Date;
|
|
9
|
+
endTime: Date;
|
|
10
|
+
browser: string;
|
|
11
|
+
errorMessage?: string;
|
|
12
|
+
stackTrace?: string;
|
|
13
|
+
codeLocation?: string;
|
|
14
|
+
isHook?: boolean;
|
|
15
|
+
hookType?: "before" | "after";
|
|
16
|
+
steps?: TestStep[];
|
|
17
|
+
}
|
|
18
|
+
export interface TestResult {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
status: TestStatus;
|
|
22
|
+
duration: number;
|
|
23
|
+
startTime: Date;
|
|
24
|
+
endTime: Date;
|
|
25
|
+
retries: number;
|
|
26
|
+
steps: TestStep[];
|
|
27
|
+
errorMessage?: string;
|
|
28
|
+
stackTrace?: string;
|
|
29
|
+
codeSnippet?: string;
|
|
30
|
+
tags?: string[];
|
|
31
|
+
suiteName?: string;
|
|
32
|
+
runId: string;
|
|
33
|
+
browser: string;
|
|
34
|
+
screenshots?: string[];
|
|
35
|
+
videoPath?: string;
|
|
36
|
+
tracePath?: string;
|
|
37
|
+
stdout?: string[];
|
|
38
|
+
stderr?: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface TestRun {
|
|
41
|
+
id: string;
|
|
42
|
+
timestamp: Date;
|
|
43
|
+
totalTests: number;
|
|
44
|
+
passed: number;
|
|
45
|
+
failed: number;
|
|
46
|
+
skipped: number;
|
|
47
|
+
duration: number;
|
|
48
|
+
}
|
|
49
|
+
export interface TrendDataPoint {
|
|
50
|
+
date: string;
|
|
51
|
+
passed: number;
|
|
52
|
+
failed: number;
|
|
53
|
+
skipped: number;
|
|
54
|
+
}
|
|
55
|
+
export interface SummaryMetric {
|
|
56
|
+
label: string;
|
|
57
|
+
value: string | number;
|
|
58
|
+
icon: LucideIcon;
|
|
59
|
+
color?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface PlaywrightPulseReporterOptions {
|
|
62
|
+
outputFile?: string;
|
|
63
|
+
outputDir?: string;
|
|
64
|
+
base64Images?: boolean;
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arghajit/dummy",
|
|
3
|
+
"author": "Arghajit Singha",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"playwright",
|
|
8
|
+
"reporter",
|
|
9
|
+
"dashboard",
|
|
10
|
+
"test",
|
|
11
|
+
"reporting",
|
|
12
|
+
"nextjs",
|
|
13
|
+
"playwright-pulse",
|
|
14
|
+
"report",
|
|
15
|
+
"email-report",
|
|
16
|
+
"send-report",
|
|
17
|
+
"email"
|
|
18
|
+
],
|
|
19
|
+
"main": "dist/reporter/index.js",
|
|
20
|
+
"types": "dist/reporter/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"screenshots",
|
|
24
|
+
"scripts/generate-static-report.mjs"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"bin": {
|
|
28
|
+
"generate-pulse-report": "./scripts/generate-static-report.mjs",
|
|
29
|
+
"merge-pulse-report": "./scripts/merge-pulse-report.js",
|
|
30
|
+
"send-email": "./scripts/sendReport.js",
|
|
31
|
+
"generate-trend": "./scripts/generate-trend.mjs"
|
|
32
|
+
},
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"import": "./dist/reporter/index.js",
|
|
36
|
+
"require": "./dist/reporter/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build:reporter": "tsc -p tsconfig.reporter.json",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"prepublishOnly": "npm run build:reporter",
|
|
43
|
+
"report:static": "node ./scripts/generate-static-report.mjs",
|
|
44
|
+
"report:merge": "node ./scripts/merge-pulse-report.js",
|
|
45
|
+
"report:email": "node ./scripts/sendReport.js"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"archiver": "^7.0.1",
|
|
49
|
+
"class-variance-authority": "^0.7.1",
|
|
50
|
+
"clsx": "^2.1.1",
|
|
51
|
+
"d3": "^7.9.0",
|
|
52
|
+
"date-fns": "^3.6.0",
|
|
53
|
+
"dotenv": "^16.5.0",
|
|
54
|
+
"highcharts": "^12.2.0",
|
|
55
|
+
"jsdom": "^26.1.0",
|
|
56
|
+
"lucide-react": "^0.475.0",
|
|
57
|
+
"nodemailer": "^7.0.3",
|
|
58
|
+
"patch-package": "^8.0.0",
|
|
59
|
+
"recharts": "^2.15.1",
|
|
60
|
+
"zod": "^3.24.2"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^20",
|
|
64
|
+
"eslint": "9.25.1",
|
|
65
|
+
"typescript": "^5"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=16"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@playwright/test": ">=1.40.0"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|