@arghajit/dummy 0.1.0-beta-7 → 0.1.0-beta-9
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.
|
@@ -15,8 +15,8 @@ export declare class PlaywrightPulseReporter implements Reporter {
|
|
|
15
15
|
printsToStdio(): boolean;
|
|
16
16
|
onBegin(config: FullConfig, suite: Suite): void;
|
|
17
17
|
onTestBegin(test: TestCase): void;
|
|
18
|
+
private getBrowserDetails;
|
|
18
19
|
private processStep;
|
|
19
|
-
getBrowserInfo(test: TestCase): string;
|
|
20
20
|
onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
|
|
21
21
|
onError(error: any): void;
|
|
22
22
|
private _writeShardResults;
|
|
@@ -39,7 +39,25 @@ const fs = __importStar(require("fs/promises"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const crypto_1 = require("crypto");
|
|
41
41
|
const attachment_utils_1 = require("./attachment-utils"); // Use relative path
|
|
42
|
-
const ua_parser_js_1 = require("ua-parser-js");
|
|
42
|
+
const ua_parser_js_1 = require("ua-parser-js"); // Added UAParser import
|
|
43
|
+
// Use dynamic import for chalk as it's ESM only
|
|
44
|
+
let chalk;
|
|
45
|
+
try {
|
|
46
|
+
(async () => {
|
|
47
|
+
chalk = (await Promise.resolve().then(() => __importStar(require("chalk")))).default;
|
|
48
|
+
})();
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
console.warn("Chalk could not be imported. Using plain console logs.");
|
|
52
|
+
chalk = {
|
|
53
|
+
green: (text) => text,
|
|
54
|
+
red: (text) => text,
|
|
55
|
+
yellow: (text) => text,
|
|
56
|
+
blue: (text) => text,
|
|
57
|
+
bold: (text) => text,
|
|
58
|
+
gray: (text) => text,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
43
61
|
const convertStatus = (status, testCase) => {
|
|
44
62
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
|
|
45
63
|
return "failed";
|
|
@@ -87,14 +105,13 @@ class PlaywrightPulseReporter {
|
|
|
87
105
|
: configDir;
|
|
88
106
|
this.outputDir = path.resolve(configFileDir, (_a = this.options.outputDir) !== null && _a !== void 0 ? _a : "pulse-report");
|
|
89
107
|
this.attachmentsDir = path.resolve(this.outputDir, ATTACHMENTS_SUBDIR);
|
|
90
|
-
this.options.outputDir = this.outputDir;
|
|
108
|
+
this.options.outputDir = this.outputDir;
|
|
91
109
|
const totalShards = this.config.shard ? this.config.shard.total : 1;
|
|
92
110
|
this.isSharded = totalShards > 1;
|
|
93
111
|
this.shardIndex = this.config.shard
|
|
94
112
|
? this.config.shard.current - 1
|
|
95
113
|
: undefined;
|
|
96
114
|
this._ensureDirExists(this.outputDir)
|
|
97
|
-
.then(() => this._ensureDirExists(this.attachmentsDir)) // Also ensure attachmentsDir exists
|
|
98
115
|
.then(() => {
|
|
99
116
|
if (this.shardIndex === undefined || this.shardIndex === 0) {
|
|
100
117
|
console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Pulse outputting to ${this.outputDir}`);
|
|
@@ -107,11 +124,62 @@ class PlaywrightPulseReporter {
|
|
|
107
124
|
.catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
|
|
108
125
|
}
|
|
109
126
|
onTestBegin(test) {
|
|
110
|
-
|
|
127
|
+
console.log(`${chalk.blue("Starting test:")} ${test.title}`);
|
|
128
|
+
}
|
|
129
|
+
getBrowserDetails(test) {
|
|
130
|
+
var _a, _b, _c, _d;
|
|
131
|
+
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project(); // project() can return undefined if not in a project context
|
|
132
|
+
const projectConfig = project === null || project === void 0 ? void 0 : project.use; // This is where options like userAgent, defaultBrowserType are
|
|
133
|
+
const userAgent = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.userAgent;
|
|
134
|
+
const configuredBrowserType = (_b = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.defaultBrowserType) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
135
|
+
const parser = new ua_parser_js_1.UAParser(userAgent);
|
|
136
|
+
const result = parser.getResult();
|
|
137
|
+
let browserName = result.browser.name;
|
|
138
|
+
const browserVersion = result.browser.version
|
|
139
|
+
? ` v${result.browser.version.split(".")[0]}`
|
|
140
|
+
: ""; // Major version
|
|
141
|
+
const osName = result.os.name ? ` on ${result.os.name}` : "";
|
|
142
|
+
const osVersion = result.os.version
|
|
143
|
+
? ` ${result.os.version.split(".")[0]}`
|
|
144
|
+
: ""; // Major version
|
|
145
|
+
const deviceType = result.device.type; // "mobile", "tablet", etc.
|
|
146
|
+
let finalString;
|
|
147
|
+
// If UAParser couldn't determine browser name, fallback to configured type
|
|
148
|
+
if (browserName === undefined) {
|
|
149
|
+
browserName = configuredBrowserType;
|
|
150
|
+
finalString = `${browserName}`;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// Specific refinements for mobile based on parsed OS and device type
|
|
154
|
+
if (deviceType === "mobile" || deviceType === "tablet") {
|
|
155
|
+
if ((_c = result.os.name) === null || _c === void 0 ? void 0 : _c.toLowerCase().includes("android")) {
|
|
156
|
+
if (browserName.toLowerCase().includes("chrome"))
|
|
157
|
+
browserName = "Chrome Mobile";
|
|
158
|
+
else if (browserName.toLowerCase().includes("firefox"))
|
|
159
|
+
browserName = "Firefox Mobile";
|
|
160
|
+
else if (result.engine.name === "Blink" && !result.browser.name)
|
|
161
|
+
browserName = "Android WebView";
|
|
162
|
+
else if (browserName &&
|
|
163
|
+
!browserName.toLowerCase().includes("mobile")) {
|
|
164
|
+
// Keep it as is, e.g. "Samsung Browser" is specific enough
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
browserName = "Android Browser"; // default for android if not specific
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if ((_d = result.os.name) === null || _d === void 0 ? void 0 : _d.toLowerCase().includes("ios")) {
|
|
171
|
+
browserName = "Mobile Safari";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (browserName === "Electron") {
|
|
175
|
+
browserName = "Electron App";
|
|
176
|
+
}
|
|
177
|
+
finalString = `${browserName}${browserVersion}${osName}${osVersion}`;
|
|
178
|
+
}
|
|
179
|
+
return finalString.trim();
|
|
111
180
|
}
|
|
112
|
-
async processStep(step, testId,
|
|
113
|
-
|
|
114
|
-
var _a, _b, _c, _d, _e;
|
|
181
|
+
async processStep(step, testId, browserDetails, testCase) {
|
|
182
|
+
var _a, _b, _c, _d;
|
|
115
183
|
let stepStatus = "passed";
|
|
116
184
|
let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
|
|
117
185
|
if ((_c = (_b = step.error) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.startsWith("Test is skipped:")) {
|
|
@@ -124,8 +192,7 @@ class PlaywrightPulseReporter {
|
|
|
124
192
|
const startTime = new Date(step.startTime);
|
|
125
193
|
const endTime = new Date(startTime.getTime() + Math.max(0, duration));
|
|
126
194
|
let codeLocation = "";
|
|
127
|
-
if (
|
|
128
|
-
// Check if file path exists
|
|
195
|
+
if (step.location) {
|
|
129
196
|
codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
|
|
130
197
|
}
|
|
131
198
|
let stepTitle = step.title;
|
|
@@ -136,9 +203,9 @@ class PlaywrightPulseReporter {
|
|
|
136
203
|
duration: duration,
|
|
137
204
|
startTime: startTime,
|
|
138
205
|
endTime: endTime,
|
|
139
|
-
browser:
|
|
206
|
+
browser: browserDetails,
|
|
140
207
|
errorMessage: errorMessage,
|
|
141
|
-
stackTrace: ((
|
|
208
|
+
stackTrace: ((_d = step.error) === null || _d === void 0 ? void 0 : _d.stack) || undefined,
|
|
142
209
|
codeLocation: codeLocation || undefined,
|
|
143
210
|
isHook: step.category === "hook",
|
|
144
211
|
hookType: step.category === "hook"
|
|
@@ -146,107 +213,17 @@ class PlaywrightPulseReporter {
|
|
|
146
213
|
? "before"
|
|
147
214
|
: "after"
|
|
148
215
|
: undefined,
|
|
149
|
-
steps: [],
|
|
216
|
+
steps: [],
|
|
150
217
|
};
|
|
151
218
|
}
|
|
152
|
-
getBrowserInfo(test) {
|
|
153
|
-
var _a, _b, _c;
|
|
154
|
-
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
155
|
-
const configuredBrowserType = (_c = (_b = project === null || project === void 0 ? void 0 : project.use) === null || _b === void 0 ? void 0 : _b.defaultBrowserType) === null || _c === void 0 ? void 0 : _c.toLowerCase();
|
|
156
|
-
const userAgentString = test.info().project.use.userAgent;
|
|
157
|
-
// --- DEBUG LOGS (IMPORTANT! Check these in your console output) ---
|
|
158
|
-
console.log(`[PulseReporter DEBUG] Project: ${(project === null || project === void 0 ? void 0 : project.name) || "N/A"}`);
|
|
159
|
-
console.log(`[PulseReporter DEBUG] Configured Browser Type: "${configuredBrowserType}"`);
|
|
160
|
-
console.log(`[PulseReporter DEBUG] User Agent String for UAParser: "${userAgentString}"`);
|
|
161
|
-
// --- END DEBUG LOGS ---
|
|
162
|
-
let parsedBrowserName;
|
|
163
|
-
let parsedVersion;
|
|
164
|
-
let parsedOsName;
|
|
165
|
-
let parsedOsVersion;
|
|
166
|
-
let deviceModel;
|
|
167
|
-
let deviceType;
|
|
168
|
-
if (userAgentString) {
|
|
169
|
-
try {
|
|
170
|
-
const parser = new ua_parser_js_1.UAParser(userAgentString);
|
|
171
|
-
const uaResult = parser.getResult();
|
|
172
|
-
// --- DEBUG LOGS (IMPORTANT! Check these in your console output) ---
|
|
173
|
-
console.log("[PulseReporter DEBUG] UAParser Result:", JSON.stringify(uaResult, null, 2));
|
|
174
|
-
// --- END DEBUG LOGS ---
|
|
175
|
-
parsedBrowserName = uaResult.browser.name;
|
|
176
|
-
parsedVersion = uaResult.browser.version;
|
|
177
|
-
parsedOsName = uaResult.os.name;
|
|
178
|
-
parsedOsVersion = uaResult.os.version;
|
|
179
|
-
deviceModel = uaResult.device.model;
|
|
180
|
-
deviceType = uaResult.device.type;
|
|
181
|
-
if (deviceType === "mobile" || deviceType === "tablet") {
|
|
182
|
-
if (parsedOsName === null || parsedOsName === void 0 ? void 0 : parsedOsName.toLowerCase().includes("android")) {
|
|
183
|
-
if (parsedBrowserName === null || parsedBrowserName === void 0 ? void 0 : parsedBrowserName.toLowerCase().includes("chrome")) {
|
|
184
|
-
parsedBrowserName = "Chrome Mobile";
|
|
185
|
-
}
|
|
186
|
-
else if (parsedBrowserName === null || parsedBrowserName === void 0 ? void 0 : parsedBrowserName.toLowerCase().includes("firefox")) {
|
|
187
|
-
parsedBrowserName = "Firefox Mobile";
|
|
188
|
-
}
|
|
189
|
-
else if (uaResult.engine.name === "Blink" && !parsedBrowserName) {
|
|
190
|
-
parsedBrowserName = "Android WebView";
|
|
191
|
-
}
|
|
192
|
-
else if (parsedBrowserName) {
|
|
193
|
-
// Parsed name is likely okay
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
parsedBrowserName = "Android Browser";
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
else if (parsedOsName === null || parsedOsName === void 0 ? void 0 : parsedOsName.toLowerCase().includes("ios")) {
|
|
200
|
-
parsedBrowserName = "Mobile Safari";
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
else if (parsedBrowserName === "Electron") {
|
|
204
|
-
parsedBrowserName = "Electron App";
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
console.warn(`Pulse Reporter: Error parsing User-Agent string "${userAgentString}":`, error);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
let finalDisplayName;
|
|
212
|
-
if (parsedBrowserName) {
|
|
213
|
-
finalDisplayName = parsedBrowserName;
|
|
214
|
-
if (parsedVersion) {
|
|
215
|
-
finalDisplayName += ` v${parsedVersion.split(".")[0]}`;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
else if (configuredBrowserType && configuredBrowserType !== "unknown") {
|
|
219
|
-
finalDisplayName =
|
|
220
|
-
configuredBrowserType.charAt(0).toUpperCase() +
|
|
221
|
-
configuredBrowserType.slice(1);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
finalDisplayName = "Unknown Browser";
|
|
225
|
-
}
|
|
226
|
-
if (parsedOsName) {
|
|
227
|
-
finalDisplayName += ` on ${parsedOsName}`;
|
|
228
|
-
if (parsedOsVersion) {
|
|
229
|
-
finalDisplayName += ` ${parsedOsVersion.split(".")[0]}`;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
// Example: Append device model if it's a mobile/tablet and model exists
|
|
233
|
-
// if ((deviceType === "mobile" || deviceType === "tablet") && deviceModel && !finalDisplayName.includes(deviceModel)) {
|
|
234
|
-
// finalDisplayName += ` (${deviceModel})`;
|
|
235
|
-
// }
|
|
236
|
-
return finalDisplayName.trim();
|
|
237
|
-
}
|
|
238
219
|
async onTestEnd(test, result) {
|
|
239
|
-
var _a, _b, _c, _d, _e, _f, _g, _h
|
|
220
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
240
221
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
241
|
-
|
|
242
|
-
const ua = test.info().project.use.userAgent;
|
|
243
|
-
const parser = new ua_parser_js_1.UAParser(ua);
|
|
244
|
-
const res = parser.getResult();
|
|
245
|
-
const browserDisplayInfo = res.browser.name || "";
|
|
222
|
+
const browserDetails = this.getBrowserDetails(test);
|
|
246
223
|
const testStatus = convertStatus(result.status, test);
|
|
247
224
|
const startTime = new Date(result.startTime);
|
|
248
225
|
const endTime = new Date(startTime.getTime() + result.duration);
|
|
249
|
-
const testIdForFiles = test.id ||
|
|
226
|
+
const testIdForFiles = test.id ||
|
|
250
227
|
`${test
|
|
251
228
|
.titlePath()
|
|
252
229
|
.join("_")
|
|
@@ -254,54 +231,60 @@ class PlaywrightPulseReporter {
|
|
|
254
231
|
const processAllSteps = async (steps) => {
|
|
255
232
|
let processed = [];
|
|
256
233
|
for (const step of steps) {
|
|
257
|
-
const processedStep = await this.processStep(step, testIdForFiles,
|
|
258
|
-
test);
|
|
234
|
+
const processedStep = await this.processStep(step, testIdForFiles, browserDetails, test);
|
|
259
235
|
processed.push(processedStep);
|
|
260
236
|
if (step.steps && step.steps.length > 0) {
|
|
261
|
-
processedStep.steps = await processAllSteps(step.steps);
|
|
237
|
+
processedStep.steps = await processAllSteps(step.steps);
|
|
262
238
|
}
|
|
263
239
|
}
|
|
264
240
|
return processed;
|
|
265
241
|
};
|
|
266
242
|
let codeSnippet = undefined;
|
|
267
243
|
try {
|
|
268
|
-
if (((_b = test.location) === null || _b === void 0 ? void 0 : _b.file) &&
|
|
269
|
-
((_c = test.location) === null || _c === void 0 ? void 0 : _c.line) !== undefined &&
|
|
270
|
-
((_d = test.location) === null || _d === void 0 ? void 0 : _d.column) !== undefined) {
|
|
244
|
+
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)) {
|
|
271
245
|
const relativePath = path.relative(this.config.rootDir, test.location.file);
|
|
272
246
|
codeSnippet = `Test defined at: ${relativePath}:${test.location.line}:${test.location.column}`;
|
|
273
247
|
}
|
|
274
248
|
}
|
|
275
249
|
catch (e) {
|
|
276
|
-
|
|
250
|
+
console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
|
|
251
|
+
}
|
|
252
|
+
const stdoutMessages = [];
|
|
253
|
+
if (result.stdout && result.stdout.length > 0) {
|
|
254
|
+
result.stdout.forEach((item) => {
|
|
255
|
+
stdoutMessages.push(typeof item === "string" ? item : item.toString());
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
const stderrMessages = [];
|
|
259
|
+
if (result.stderr && result.stderr.length > 0) {
|
|
260
|
+
result.stderr.forEach((item) => {
|
|
261
|
+
stderrMessages.push(typeof item === "string" ? item : item.toString());
|
|
262
|
+
});
|
|
277
263
|
}
|
|
278
|
-
const
|
|
279
|
-
const stderrMessages = ((_f = result.stderr) === null || _f === void 0 ? void 0 : _f.map((item) => typeof item === "string" ? item : item.toString())) || [];
|
|
280
|
-
const uniqueTestId = test.id; // test.id is Playwright's unique ID for a test case instance
|
|
264
|
+
const uniqueTestId = test.id;
|
|
281
265
|
const pulseResult = {
|
|
282
266
|
id: uniqueTestId,
|
|
283
|
-
runId: "TBD",
|
|
267
|
+
runId: "TBD",
|
|
284
268
|
name: test.titlePath().join(" > "),
|
|
285
|
-
suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((
|
|
269
|
+
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",
|
|
286
270
|
status: testStatus,
|
|
287
271
|
duration: result.duration,
|
|
288
272
|
startTime: startTime,
|
|
289
273
|
endTime: endTime,
|
|
290
|
-
browser:
|
|
274
|
+
browser: browserDetails,
|
|
291
275
|
retries: result.retry,
|
|
292
|
-
steps: ((
|
|
293
|
-
errorMessage: (
|
|
294
|
-
stackTrace: (
|
|
276
|
+
steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
|
|
277
|
+
errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
|
|
278
|
+
stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
|
|
295
279
|
codeSnippet: codeSnippet,
|
|
296
280
|
tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
|
|
297
|
-
screenshots: [],
|
|
298
|
-
videoPath: undefined,
|
|
299
|
-
tracePath: undefined,
|
|
281
|
+
screenshots: [],
|
|
282
|
+
videoPath: undefined,
|
|
283
|
+
tracePath: undefined,
|
|
300
284
|
stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
|
|
301
285
|
stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
|
|
302
286
|
};
|
|
303
287
|
try {
|
|
304
|
-
// IMPORTANT: attachFiles logic
|
|
305
288
|
(0, attachment_utils_1.attachFiles)(testIdForFiles, result, pulseResult, this.options);
|
|
306
289
|
}
|
|
307
290
|
catch (attachError) {
|
|
@@ -336,28 +319,27 @@ class PlaywrightPulseReporter {
|
|
|
336
319
|
console.error(`Pulse Reporter: Shard ${this.shardIndex} failed to write temporary results to ${tempFilePath}`, error);
|
|
337
320
|
}
|
|
338
321
|
}
|
|
339
|
-
async _mergeShardResults(finalRunData
|
|
340
|
-
) {
|
|
341
|
-
var _a, _b;
|
|
322
|
+
async _mergeShardResults(finalRunData) {
|
|
342
323
|
let allShardProcessedResults = [];
|
|
343
|
-
const totalShards =
|
|
324
|
+
const totalShards = this.config.shard ? this.config.shard.total : 1;
|
|
344
325
|
for (let i = 0; i < totalShards; i++) {
|
|
345
326
|
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
|
|
346
327
|
try {
|
|
347
328
|
const content = await fs.readFile(tempFilePath, "utf-8");
|
|
348
|
-
const shardResults = JSON.parse(content);
|
|
349
|
-
allShardProcessedResults
|
|
329
|
+
const shardResults = JSON.parse(content);
|
|
330
|
+
allShardProcessedResults =
|
|
331
|
+
allShardProcessedResults.concat(shardResults);
|
|
350
332
|
}
|
|
351
333
|
catch (error) {
|
|
352
334
|
if ((error === null || error === void 0 ? void 0 : error.code) === "ENOENT") {
|
|
353
|
-
|
|
335
|
+
console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}. This might be normal if a shard had no tests or failed early.`);
|
|
354
336
|
}
|
|
355
337
|
else {
|
|
356
338
|
console.error(`Pulse Reporter: Could not read/parse results from shard ${i} (${tempFilePath}). Error:`, error);
|
|
357
339
|
}
|
|
358
340
|
}
|
|
359
341
|
}
|
|
360
|
-
|
|
342
|
+
let finalUniqueResultsMap = new Map();
|
|
361
343
|
for (const result of allShardProcessedResults) {
|
|
362
344
|
const existing = finalUniqueResultsMap.get(result.id);
|
|
363
345
|
if (!existing || result.retries >= existing.retries) {
|
|
@@ -365,15 +347,23 @@ class PlaywrightPulseReporter {
|
|
|
365
347
|
}
|
|
366
348
|
}
|
|
367
349
|
const finalResultsList = Array.from(finalUniqueResultsMap.values());
|
|
368
|
-
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
369
|
-
// Update the passed finalRunData object with aggregated stats
|
|
350
|
+
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
370
351
|
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
371
352
|
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
372
353
|
finalRunData.skipped = finalResultsList.filter((r) => r.status === "skipped").length;
|
|
373
354
|
finalRunData.totalTests = finalResultsList.length;
|
|
355
|
+
const reviveDates = (key, value) => {
|
|
356
|
+
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
357
|
+
if (typeof value === "string" && isoDateRegex.test(value)) {
|
|
358
|
+
const date = new Date(value);
|
|
359
|
+
return !isNaN(date.getTime()) ? date : value;
|
|
360
|
+
}
|
|
361
|
+
return value;
|
|
362
|
+
};
|
|
363
|
+
const properlyTypedResults = JSON.parse(JSON.stringify(finalResultsList), reviveDates);
|
|
374
364
|
return {
|
|
375
|
-
run: finalRunData,
|
|
376
|
-
results:
|
|
365
|
+
run: finalRunData,
|
|
366
|
+
results: properlyTypedResults,
|
|
377
367
|
metadata: { generatedAt: new Date().toISOString() },
|
|
378
368
|
};
|
|
379
369
|
}
|
|
@@ -387,7 +377,7 @@ class PlaywrightPulseReporter {
|
|
|
387
377
|
}
|
|
388
378
|
catch (error) {
|
|
389
379
|
if ((error === null || error === void 0 ? void 0 : error.code) !== "ENOENT") {
|
|
390
|
-
|
|
380
|
+
console.warn("Pulse Reporter: Warning during cleanup of temporary files:", error.message);
|
|
391
381
|
}
|
|
392
382
|
}
|
|
393
383
|
}
|
|
@@ -403,7 +393,7 @@ class PlaywrightPulseReporter {
|
|
|
403
393
|
}
|
|
404
394
|
}
|
|
405
395
|
async onEnd(result) {
|
|
406
|
-
var _a, _b;
|
|
396
|
+
var _a, _b, _c;
|
|
407
397
|
if (this.shardIndex !== undefined) {
|
|
408
398
|
await this._writeShardResults();
|
|
409
399
|
return;
|
|
@@ -412,18 +402,16 @@ class PlaywrightPulseReporter {
|
|
|
412
402
|
const duration = runEndTime - this.runStartTime;
|
|
413
403
|
const runId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
|
|
414
404
|
const runData = {
|
|
415
|
-
// This is the single source of truth for current run's data
|
|
416
405
|
id: runId,
|
|
417
|
-
timestamp: new Date(this.runStartTime),
|
|
406
|
+
timestamp: new Date(this.runStartTime),
|
|
418
407
|
totalTests: 0,
|
|
419
408
|
passed: 0,
|
|
420
409
|
failed: 0,
|
|
421
410
|
skipped: 0,
|
|
422
411
|
duration,
|
|
423
412
|
};
|
|
424
|
-
let finalReport;
|
|
413
|
+
let finalReport = undefined; // Initialize as undefined
|
|
425
414
|
if (this.isSharded) {
|
|
426
|
-
// _mergeShardResults will populate the runData object passed to it
|
|
427
415
|
finalReport = await this._mergeShardResults(runData);
|
|
428
416
|
}
|
|
429
417
|
else {
|
|
@@ -432,18 +420,37 @@ class PlaywrightPulseReporter {
|
|
|
432
420
|
runData.failed = this.results.filter((r) => r.status === "failed").length;
|
|
433
421
|
runData.skipped = this.results.filter((r) => r.status === "skipped").length;
|
|
434
422
|
runData.totalTests = this.results.length;
|
|
423
|
+
const reviveDates = (key, value) => {
|
|
424
|
+
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
425
|
+
if (typeof value === "string" && isoDateRegex.test(value)) {
|
|
426
|
+
const date = new Date(value);
|
|
427
|
+
return !isNaN(date.getTime()) ? date : value;
|
|
428
|
+
}
|
|
429
|
+
return value;
|
|
430
|
+
};
|
|
431
|
+
const properlyTypedResults = JSON.parse(JSON.stringify(this.results), reviveDates);
|
|
435
432
|
finalReport = {
|
|
436
|
-
run: runData,
|
|
437
|
-
results:
|
|
433
|
+
run: runData,
|
|
434
|
+
results: properlyTypedResults,
|
|
438
435
|
metadata: { generatedAt: new Date().toISOString() },
|
|
439
436
|
};
|
|
440
437
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
438
|
+
if (!finalReport) {
|
|
439
|
+
console.error("PlaywrightPulseReporter: CRITICAL - finalReport object was not generated. Cannot create summary.");
|
|
440
|
+
const errorSummary = `
|
|
441
|
+
PlaywrightPulseReporter: Run Finished
|
|
442
|
+
-----------------------------------------
|
|
443
|
+
Overall Status: ERROR (Report data missing)
|
|
444
|
+
Total Tests: N/A
|
|
445
|
+
Passed: N/A
|
|
446
|
+
Failed: N/A
|
|
447
|
+
Skipped: N/A
|
|
448
|
+
Duration: N/A
|
|
449
|
+
-----------------------------------------`;
|
|
450
|
+
if (this.printsToStdio()) {
|
|
451
|
+
console.log(errorSummary);
|
|
452
|
+
}
|
|
453
|
+
const errorReport = {
|
|
447
454
|
run: {
|
|
448
455
|
id: runId,
|
|
449
456
|
timestamp: new Date(this.runStartTime),
|
|
@@ -451,30 +458,28 @@ class PlaywrightPulseReporter {
|
|
|
451
458
|
passed: 0,
|
|
452
459
|
failed: 0,
|
|
453
460
|
skipped: 0,
|
|
454
|
-
duration,
|
|
461
|
+
duration: duration,
|
|
455
462
|
},
|
|
456
463
|
results: [],
|
|
457
464
|
metadata: {
|
|
458
465
|
generatedAt: new Date().toISOString(),
|
|
459
466
|
},
|
|
460
467
|
};
|
|
468
|
+
const finalOutputPathOnError = path.join(this.outputDir, this.baseOutputFile);
|
|
461
469
|
try {
|
|
462
|
-
const errorPath = path.join(this.outputDir, this.baseOutputFile);
|
|
463
470
|
await this._ensureDirExists(this.outputDir);
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
console.warn(`PlaywrightPulseReporter: Wrote a minimal error report to ${errorPath}.`);
|
|
471
|
+
await fs.writeFile(finalOutputPathOnError, JSON.stringify(errorReport, null, 2));
|
|
472
|
+
console.warn(`PlaywrightPulseReporter: Wrote an error report to ${finalOutputPathOnError} as finalReport was missing.`);
|
|
467
473
|
}
|
|
468
|
-
catch (
|
|
469
|
-
console.error(
|
|
474
|
+
catch (writeError) {
|
|
475
|
+
console.error(`PlaywrightPulseReporter: Failed to write error report: ${writeError.message}`);
|
|
470
476
|
}
|
|
471
477
|
return;
|
|
472
478
|
}
|
|
473
|
-
// At this point, finalReport.run is guaranteed to be populated by either _mergeShardResults or the non-sharded path.
|
|
474
479
|
const reportRunData = finalReport.run;
|
|
475
|
-
const finalRunStatus = ((_a = reportRunData.failed) !== null && _a !== void 0 ? _a : 0) > 0
|
|
480
|
+
const finalRunStatus = ((_a = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.failed) !== null && _a !== void 0 ? _a : 0) > 0
|
|
476
481
|
? "failed"
|
|
477
|
-
: ((_b = reportRunData.totalTests) !== null && _b !== void 0 ? _b : 0) === 0 && result.status !== "passed"
|
|
482
|
+
: ((_b = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.totalTests) !== null && _b !== void 0 ? _b : 0) === 0 && result.status !== "passed"
|
|
478
483
|
? result.status === "interrupted"
|
|
479
484
|
? "interrupted"
|
|
480
485
|
: "no tests or error"
|
|
@@ -483,11 +488,11 @@ class PlaywrightPulseReporter {
|
|
|
483
488
|
PlaywrightPulseReporter: Run Finished
|
|
484
489
|
-----------------------------------------
|
|
485
490
|
Overall Status: ${finalRunStatus.toUpperCase()}
|
|
486
|
-
Total Tests: ${reportRunData.totalTests}
|
|
487
|
-
Passed: ${reportRunData.passed}
|
|
488
|
-
Failed: ${reportRunData.failed}
|
|
489
|
-
Skipped: ${reportRunData.skipped}
|
|
490
|
-
Duration: ${(reportRunData.duration / 1000).toFixed(2)}s
|
|
491
|
+
Total Tests: ${(reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.totalTests) || 0}
|
|
492
|
+
Passed: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.passed}
|
|
493
|
+
Failed: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.failed}
|
|
494
|
+
Skipped: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.skipped}
|
|
495
|
+
Duration: ${(((_c = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.duration) !== null && _c !== void 0 ? _c : 0) / 1000).toFixed(2)}s
|
|
491
496
|
-----------------------------------------`;
|
|
492
497
|
if (this.printsToStdio()) {
|
|
493
498
|
console.log(summary);
|
|
@@ -495,11 +500,11 @@ PlaywrightPulseReporter: Run Finished
|
|
|
495
500
|
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
496
501
|
try {
|
|
497
502
|
await this._ensureDirExists(this.outputDir);
|
|
498
|
-
// Custom replacer for JSON.stringify to handle Date objects correctly
|
|
499
503
|
await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
|
|
500
|
-
if (value instanceof Date)
|
|
504
|
+
if (value instanceof Date)
|
|
501
505
|
return value.toISOString();
|
|
502
|
-
|
|
506
|
+
if (typeof value === "bigint")
|
|
507
|
+
return value.toString();
|
|
503
508
|
return value;
|
|
504
509
|
}, 2));
|
|
505
510
|
if (this.printsToStdio()) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arghajit/dummy",
|
|
3
3
|
"author": "Arghajit Singha",
|
|
4
|
-
"version": "0.1.0-beta-
|
|
4
|
+
"version": "0.1.0-beta-9",
|
|
5
5
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"playwright",
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"dotenv": "^16.5.0",
|
|
54
54
|
"highcharts": "^12.2.0",
|
|
55
55
|
"jsdom": "^26.1.0",
|
|
56
|
-
"lucide-react": "^0.475.0",
|
|
57
56
|
"nodemailer": "^7.0.3",
|
|
58
57
|
"patch-package": "^8.0.0",
|
|
59
58
|
"recharts": "^2.15.1",
|
|
60
59
|
"ua-parser-js": "^2.0.3",
|
|
61
|
-
"zod": "^3.24.2"
|
|
60
|
+
"zod": "^3.24.2",
|
|
61
|
+
"lucide-react": "^0.475.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/node": "^20",
|
|
@@ -874,8 +874,11 @@ function generateSuitesWidget(suitesData) {
|
|
|
874
874
|
<h3 class="suite-name" title="${sanitizeHTML(
|
|
875
875
|
suite.name
|
|
876
876
|
)} (${sanitizeHTML(suite.browser)})">${sanitizeHTML(suite.name)}</h3>
|
|
877
|
-
<span class="browser-tag">${sanitizeHTML(suite.browser)}</span>
|
|
878
877
|
</div>
|
|
878
|
+
<div>
|
|
879
|
+
🖥️
|
|
880
|
+
<span class="browser-tag">${sanitizeHTML(suite.browser)}</span>
|
|
881
|
+
</div>
|
|
879
882
|
<div class="suite-card-body">
|
|
880
883
|
<span class="test-count">${suite.count} test${
|
|
881
884
|
suite.count !== 1 ? "s" : ""
|