@arghajit/dummy 0.1.0-beta-6 → 0.1.0-beta-8
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,7 @@ 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
43
|
const convertStatus = (status, testCase) => {
|
|
44
44
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
|
|
45
45
|
return "failed";
|
|
@@ -87,14 +87,13 @@ class PlaywrightPulseReporter {
|
|
|
87
87
|
: configDir;
|
|
88
88
|
this.outputDir = path.resolve(configFileDir, (_a = this.options.outputDir) !== null && _a !== void 0 ? _a : "pulse-report");
|
|
89
89
|
this.attachmentsDir = path.resolve(this.outputDir, ATTACHMENTS_SUBDIR);
|
|
90
|
-
this.options.outputDir = this.outputDir;
|
|
90
|
+
this.options.outputDir = this.outputDir;
|
|
91
91
|
const totalShards = this.config.shard ? this.config.shard.total : 1;
|
|
92
92
|
this.isSharded = totalShards > 1;
|
|
93
93
|
this.shardIndex = this.config.shard
|
|
94
94
|
? this.config.shard.current - 1
|
|
95
95
|
: undefined;
|
|
96
96
|
this._ensureDirExists(this.outputDir)
|
|
97
|
-
.then(() => this._ensureDirExists(this.attachmentsDir)) // Also ensure attachmentsDir exists
|
|
98
97
|
.then(() => {
|
|
99
98
|
if (this.shardIndex === undefined || this.shardIndex === 0) {
|
|
100
99
|
console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Pulse outputting to ${this.outputDir}`);
|
|
@@ -107,11 +106,89 @@ class PlaywrightPulseReporter {
|
|
|
107
106
|
.catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
|
|
108
107
|
}
|
|
109
108
|
onTestBegin(test) {
|
|
110
|
-
|
|
109
|
+
console.log(`Starting test: ${test.title}`);
|
|
111
110
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
getBrowserDetails(test) {
|
|
112
|
+
var _a, _b, _c, _d;
|
|
113
|
+
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project(); // project() can return undefined if not in a project context
|
|
114
|
+
const projectConfig = project === null || project === void 0 ? void 0 : project.use; // This is where options like userAgent, defaultBrowserType are
|
|
115
|
+
const userAgent = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.userAgent;
|
|
116
|
+
const configuredBrowserType = (_b = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.defaultBrowserType) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
117
|
+
// --- DEBUG LOGS (Uncomment if needed for diagnosing) ---
|
|
118
|
+
// console.log(`[PulseReporter DEBUG] Project: ${test.info().project.name}`);
|
|
119
|
+
// console.log(`[PulseReporter DEBUG] Configured Browser Type: "${configuredBrowserType}"`);
|
|
120
|
+
// console.log(`[PulseReporter DEBUG] User Agent for UAParser: "${userAgent}"`);
|
|
121
|
+
// --- END DEBUG LOGS ---
|
|
122
|
+
// if (!userAgent) {
|
|
123
|
+
// // Fallback if no user agent is available
|
|
124
|
+
// return configuredBrowserType
|
|
125
|
+
// ? configuredBrowserType.charAt(0).toUpperCase() +
|
|
126
|
+
// configuredBrowserType.slice(1)
|
|
127
|
+
// : "Unknown Browser";
|
|
128
|
+
// }
|
|
129
|
+
// try {
|
|
130
|
+
const parser = new ua_parser_js_1.UAParser(userAgent);
|
|
131
|
+
const result = parser.getResult();
|
|
132
|
+
// --- DEBUG LOGS (Uncomment if needed for diagnosing) ---
|
|
133
|
+
// console.log("[PulseReporter DEBUG] UAParser Result:", JSON.stringify(result, null, 2));
|
|
134
|
+
// --- END DEBUG LOGS ---
|
|
135
|
+
let browserName = result.browser.name;
|
|
136
|
+
const browserVersion = result.browser.version
|
|
137
|
+
? ` v${result.browser.version.split(".")[0]}`
|
|
138
|
+
: ""; // Major version
|
|
139
|
+
const osName = result.os.name ? ` on ${result.os.name}` : "";
|
|
140
|
+
const osVersion = result.os.version
|
|
141
|
+
? ` ${result.os.version.split(".")[0]}`
|
|
142
|
+
: ""; // Major version
|
|
143
|
+
const deviceType = result.device.type; // "mobile", "tablet", etc.
|
|
144
|
+
// If UAParser couldn't determine browser name, fallback to configured type
|
|
145
|
+
if (!browserName) {
|
|
146
|
+
browserName = configuredBrowserType
|
|
147
|
+
? configuredBrowserType.charAt(0).toUpperCase() +
|
|
148
|
+
configuredBrowserType.slice(1)
|
|
149
|
+
: "Unknown";
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Specific refinements for mobile based on parsed OS and device type
|
|
153
|
+
if (deviceType === "mobile" || deviceType === "tablet") {
|
|
154
|
+
if ((_c = result.os.name) === null || _c === void 0 ? void 0 : _c.toLowerCase().includes("android")) {
|
|
155
|
+
if (browserName.toLowerCase().includes("chrome"))
|
|
156
|
+
browserName = "Chrome Mobile";
|
|
157
|
+
else if (browserName.toLowerCase().includes("firefox"))
|
|
158
|
+
browserName = "Firefox Mobile";
|
|
159
|
+
else if (result.engine.name === "Blink" && !result.browser.name)
|
|
160
|
+
browserName = "Android WebView";
|
|
161
|
+
else if (browserName &&
|
|
162
|
+
!browserName.toLowerCase().includes("mobile")) {
|
|
163
|
+
// Keep it as is, e.g. "Samsung Browser" is specific enough
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
browserName = "Android Browser"; // default for android if not specific
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if ((_d = result.os.name) === null || _d === void 0 ? void 0 : _d.toLowerCase().includes("ios")) {
|
|
170
|
+
browserName = "Mobile Safari";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else if (browserName === "Electron") {
|
|
174
|
+
browserName = "Electron App";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
let finalString = `${browserName}${browserVersion}${osName}${osVersion}`;
|
|
178
|
+
return finalString.trim() || "Unknown Browser";
|
|
179
|
+
// } catch (error) {
|
|
180
|
+
// console.warn(
|
|
181
|
+
// `Pulse Reporter: Error parsing User-Agent string "${userAgent}":`,
|
|
182
|
+
// error
|
|
183
|
+
// );
|
|
184
|
+
// return configuredBrowserType
|
|
185
|
+
// ? configuredBrowserType.charAt(0).toUpperCase() +
|
|
186
|
+
// configuredBrowserType.slice(1)
|
|
187
|
+
// : "Unknown Browser";
|
|
188
|
+
// }
|
|
189
|
+
}
|
|
190
|
+
async processStep(step, testId, browserDetails, testCase) {
|
|
191
|
+
var _a, _b, _c, _d;
|
|
115
192
|
let stepStatus = "passed";
|
|
116
193
|
let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
|
|
117
194
|
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 +201,7 @@ class PlaywrightPulseReporter {
|
|
|
124
201
|
const startTime = new Date(step.startTime);
|
|
125
202
|
const endTime = new Date(startTime.getTime() + Math.max(0, duration));
|
|
126
203
|
let codeLocation = "";
|
|
127
|
-
if (
|
|
128
|
-
// Check if file path exists
|
|
204
|
+
if (step.location) {
|
|
129
205
|
codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
|
|
130
206
|
}
|
|
131
207
|
let stepTitle = step.title;
|
|
@@ -136,9 +212,9 @@ class PlaywrightPulseReporter {
|
|
|
136
212
|
duration: duration,
|
|
137
213
|
startTime: startTime,
|
|
138
214
|
endTime: endTime,
|
|
139
|
-
browser:
|
|
215
|
+
browser: browserDetails,
|
|
140
216
|
errorMessage: errorMessage,
|
|
141
|
-
stackTrace: ((
|
|
217
|
+
stackTrace: ((_d = step.error) === null || _d === void 0 ? void 0 : _d.stack) || undefined,
|
|
142
218
|
codeLocation: codeLocation || undefined,
|
|
143
219
|
isHook: step.category === "hook",
|
|
144
220
|
hookType: step.category === "hook"
|
|
@@ -146,103 +222,17 @@ class PlaywrightPulseReporter {
|
|
|
146
222
|
? "before"
|
|
147
223
|
: "after"
|
|
148
224
|
: undefined,
|
|
149
|
-
steps: [],
|
|
225
|
+
steps: [],
|
|
150
226
|
};
|
|
151
227
|
}
|
|
152
|
-
getBrowserInfo(test) {
|
|
153
|
-
var _a, _b, _c, _d;
|
|
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 = (_d = project === null || project === void 0 ? void 0 : project.use) === null || _d === void 0 ? void 0 : _d.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
228
|
async onTestEnd(test, result) {
|
|
239
|
-
var _a, _b, _c, _d, _e, _f, _g, _h
|
|
229
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
240
230
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
241
|
-
const
|
|
231
|
+
const browserDetails = this.getBrowserDetails(test);
|
|
242
232
|
const testStatus = convertStatus(result.status, test);
|
|
243
233
|
const startTime = new Date(result.startTime);
|
|
244
234
|
const endTime = new Date(startTime.getTime() + result.duration);
|
|
245
|
-
const testIdForFiles = test.id ||
|
|
235
|
+
const testIdForFiles = test.id ||
|
|
246
236
|
`${test
|
|
247
237
|
.titlePath()
|
|
248
238
|
.join("_")
|
|
@@ -250,54 +240,60 @@ class PlaywrightPulseReporter {
|
|
|
250
240
|
const processAllSteps = async (steps) => {
|
|
251
241
|
let processed = [];
|
|
252
242
|
for (const step of steps) {
|
|
253
|
-
const processedStep = await this.processStep(step, testIdForFiles,
|
|
254
|
-
test);
|
|
243
|
+
const processedStep = await this.processStep(step, testIdForFiles, browserDetails, test);
|
|
255
244
|
processed.push(processedStep);
|
|
256
245
|
if (step.steps && step.steps.length > 0) {
|
|
257
|
-
processedStep.steps = await processAllSteps(step.steps);
|
|
246
|
+
processedStep.steps = await processAllSteps(step.steps);
|
|
258
247
|
}
|
|
259
248
|
}
|
|
260
249
|
return processed;
|
|
261
250
|
};
|
|
262
251
|
let codeSnippet = undefined;
|
|
263
252
|
try {
|
|
264
|
-
if (((_b = test.location) === null || _b === void 0 ? void 0 : _b.file) &&
|
|
265
|
-
((_c = test.location) === null || _c === void 0 ? void 0 : _c.line) !== undefined &&
|
|
266
|
-
((_d = test.location) === null || _d === void 0 ? void 0 : _d.column) !== undefined) {
|
|
253
|
+
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)) {
|
|
267
254
|
const relativePath = path.relative(this.config.rootDir, test.location.file);
|
|
268
255
|
codeSnippet = `Test defined at: ${relativePath}:${test.location.line}:${test.location.column}`;
|
|
269
256
|
}
|
|
270
257
|
}
|
|
271
258
|
catch (e) {
|
|
272
|
-
|
|
259
|
+
console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
|
|
273
260
|
}
|
|
274
|
-
const stdoutMessages =
|
|
275
|
-
|
|
276
|
-
|
|
261
|
+
const stdoutMessages = [];
|
|
262
|
+
if (result.stdout && result.stdout.length > 0) {
|
|
263
|
+
result.stdout.forEach((item) => {
|
|
264
|
+
stdoutMessages.push(typeof item === "string" ? item : item.toString());
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
const stderrMessages = [];
|
|
268
|
+
if (result.stderr && result.stderr.length > 0) {
|
|
269
|
+
result.stderr.forEach((item) => {
|
|
270
|
+
stderrMessages.push(typeof item === "string" ? item : item.toString());
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
const uniqueTestId = test.id;
|
|
277
274
|
const pulseResult = {
|
|
278
275
|
id: uniqueTestId,
|
|
279
|
-
runId: "TBD",
|
|
276
|
+
runId: "TBD",
|
|
280
277
|
name: test.titlePath().join(" > "),
|
|
281
|
-
suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((
|
|
278
|
+
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",
|
|
282
279
|
status: testStatus,
|
|
283
280
|
duration: result.duration,
|
|
284
281
|
startTime: startTime,
|
|
285
282
|
endTime: endTime,
|
|
286
|
-
browser:
|
|
283
|
+
browser: browserDetails,
|
|
287
284
|
retries: result.retry,
|
|
288
|
-
steps: ((
|
|
289
|
-
errorMessage: (
|
|
290
|
-
stackTrace: (
|
|
285
|
+
steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
|
|
286
|
+
errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
|
|
287
|
+
stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
|
|
291
288
|
codeSnippet: codeSnippet,
|
|
292
289
|
tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
|
|
293
|
-
screenshots: [],
|
|
294
|
-
videoPath: undefined,
|
|
295
|
-
tracePath: undefined,
|
|
290
|
+
screenshots: [],
|
|
291
|
+
videoPath: undefined,
|
|
292
|
+
tracePath: undefined,
|
|
296
293
|
stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
|
|
297
294
|
stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
|
|
298
295
|
};
|
|
299
296
|
try {
|
|
300
|
-
// IMPORTANT: attachFiles logic
|
|
301
297
|
(0, attachment_utils_1.attachFiles)(testIdForFiles, result, pulseResult, this.options);
|
|
302
298
|
}
|
|
303
299
|
catch (attachError) {
|
|
@@ -332,28 +328,27 @@ class PlaywrightPulseReporter {
|
|
|
332
328
|
console.error(`Pulse Reporter: Shard ${this.shardIndex} failed to write temporary results to ${tempFilePath}`, error);
|
|
333
329
|
}
|
|
334
330
|
}
|
|
335
|
-
async _mergeShardResults(finalRunData
|
|
336
|
-
) {
|
|
337
|
-
var _a, _b;
|
|
331
|
+
async _mergeShardResults(finalRunData) {
|
|
338
332
|
let allShardProcessedResults = [];
|
|
339
|
-
const totalShards =
|
|
333
|
+
const totalShards = this.config.shard ? this.config.shard.total : 1;
|
|
340
334
|
for (let i = 0; i < totalShards; i++) {
|
|
341
335
|
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
|
|
342
336
|
try {
|
|
343
337
|
const content = await fs.readFile(tempFilePath, "utf-8");
|
|
344
|
-
const shardResults = JSON.parse(content);
|
|
345
|
-
allShardProcessedResults
|
|
338
|
+
const shardResults = JSON.parse(content);
|
|
339
|
+
allShardProcessedResults =
|
|
340
|
+
allShardProcessedResults.concat(shardResults);
|
|
346
341
|
}
|
|
347
342
|
catch (error) {
|
|
348
343
|
if ((error === null || error === void 0 ? void 0 : error.code) === "ENOENT") {
|
|
349
|
-
|
|
344
|
+
console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}. This might be normal if a shard had no tests or failed early.`);
|
|
350
345
|
}
|
|
351
346
|
else {
|
|
352
347
|
console.error(`Pulse Reporter: Could not read/parse results from shard ${i} (${tempFilePath}). Error:`, error);
|
|
353
348
|
}
|
|
354
349
|
}
|
|
355
350
|
}
|
|
356
|
-
|
|
351
|
+
let finalUniqueResultsMap = new Map();
|
|
357
352
|
for (const result of allShardProcessedResults) {
|
|
358
353
|
const existing = finalUniqueResultsMap.get(result.id);
|
|
359
354
|
if (!existing || result.retries >= existing.retries) {
|
|
@@ -361,15 +356,23 @@ class PlaywrightPulseReporter {
|
|
|
361
356
|
}
|
|
362
357
|
}
|
|
363
358
|
const finalResultsList = Array.from(finalUniqueResultsMap.values());
|
|
364
|
-
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
365
|
-
// Update the passed finalRunData object with aggregated stats
|
|
359
|
+
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
366
360
|
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
367
361
|
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
368
362
|
finalRunData.skipped = finalResultsList.filter((r) => r.status === "skipped").length;
|
|
369
363
|
finalRunData.totalTests = finalResultsList.length;
|
|
364
|
+
const reviveDates = (key, value) => {
|
|
365
|
+
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
366
|
+
if (typeof value === "string" && isoDateRegex.test(value)) {
|
|
367
|
+
const date = new Date(value);
|
|
368
|
+
return !isNaN(date.getTime()) ? date : value;
|
|
369
|
+
}
|
|
370
|
+
return value;
|
|
371
|
+
};
|
|
372
|
+
const properlyTypedResults = JSON.parse(JSON.stringify(finalResultsList), reviveDates);
|
|
370
373
|
return {
|
|
371
|
-
run: finalRunData,
|
|
372
|
-
results:
|
|
374
|
+
run: finalRunData,
|
|
375
|
+
results: properlyTypedResults,
|
|
373
376
|
metadata: { generatedAt: new Date().toISOString() },
|
|
374
377
|
};
|
|
375
378
|
}
|
|
@@ -383,7 +386,7 @@ class PlaywrightPulseReporter {
|
|
|
383
386
|
}
|
|
384
387
|
catch (error) {
|
|
385
388
|
if ((error === null || error === void 0 ? void 0 : error.code) !== "ENOENT") {
|
|
386
|
-
|
|
389
|
+
console.warn("Pulse Reporter: Warning during cleanup of temporary files:", error.message);
|
|
387
390
|
}
|
|
388
391
|
}
|
|
389
392
|
}
|
|
@@ -399,7 +402,7 @@ class PlaywrightPulseReporter {
|
|
|
399
402
|
}
|
|
400
403
|
}
|
|
401
404
|
async onEnd(result) {
|
|
402
|
-
var _a, _b;
|
|
405
|
+
var _a, _b, _c;
|
|
403
406
|
if (this.shardIndex !== undefined) {
|
|
404
407
|
await this._writeShardResults();
|
|
405
408
|
return;
|
|
@@ -408,18 +411,16 @@ class PlaywrightPulseReporter {
|
|
|
408
411
|
const duration = runEndTime - this.runStartTime;
|
|
409
412
|
const runId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
|
|
410
413
|
const runData = {
|
|
411
|
-
// This is the single source of truth for current run's data
|
|
412
414
|
id: runId,
|
|
413
|
-
timestamp: new Date(this.runStartTime),
|
|
415
|
+
timestamp: new Date(this.runStartTime),
|
|
414
416
|
totalTests: 0,
|
|
415
417
|
passed: 0,
|
|
416
418
|
failed: 0,
|
|
417
419
|
skipped: 0,
|
|
418
420
|
duration,
|
|
419
421
|
};
|
|
420
|
-
let finalReport;
|
|
422
|
+
let finalReport = undefined; // Initialize as undefined
|
|
421
423
|
if (this.isSharded) {
|
|
422
|
-
// _mergeShardResults will populate the runData object passed to it
|
|
423
424
|
finalReport = await this._mergeShardResults(runData);
|
|
424
425
|
}
|
|
425
426
|
else {
|
|
@@ -428,18 +429,37 @@ class PlaywrightPulseReporter {
|
|
|
428
429
|
runData.failed = this.results.filter((r) => r.status === "failed").length;
|
|
429
430
|
runData.skipped = this.results.filter((r) => r.status === "skipped").length;
|
|
430
431
|
runData.totalTests = this.results.length;
|
|
432
|
+
const reviveDates = (key, value) => {
|
|
433
|
+
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
434
|
+
if (typeof value === "string" && isoDateRegex.test(value)) {
|
|
435
|
+
const date = new Date(value);
|
|
436
|
+
return !isNaN(date.getTime()) ? date : value;
|
|
437
|
+
}
|
|
438
|
+
return value;
|
|
439
|
+
};
|
|
440
|
+
const properlyTypedResults = JSON.parse(JSON.stringify(this.results), reviveDates);
|
|
431
441
|
finalReport = {
|
|
432
|
-
run: runData,
|
|
433
|
-
results:
|
|
442
|
+
run: runData,
|
|
443
|
+
results: properlyTypedResults,
|
|
434
444
|
metadata: { generatedAt: new Date().toISOString() },
|
|
435
445
|
};
|
|
436
446
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
447
|
+
if (!finalReport) {
|
|
448
|
+
console.error("PlaywrightPulseReporter: CRITICAL - finalReport object was not generated. Cannot create summary.");
|
|
449
|
+
const errorSummary = `
|
|
450
|
+
PlaywrightPulseReporter: Run Finished
|
|
451
|
+
-----------------------------------------
|
|
452
|
+
Overall Status: ERROR (Report data missing)
|
|
453
|
+
Total Tests: N/A
|
|
454
|
+
Passed: N/A
|
|
455
|
+
Failed: N/A
|
|
456
|
+
Skipped: N/A
|
|
457
|
+
Duration: N/A
|
|
458
|
+
-----------------------------------------`;
|
|
459
|
+
if (this.printsToStdio()) {
|
|
460
|
+
console.log(errorSummary);
|
|
461
|
+
}
|
|
462
|
+
const errorReport = {
|
|
443
463
|
run: {
|
|
444
464
|
id: runId,
|
|
445
465
|
timestamp: new Date(this.runStartTime),
|
|
@@ -447,30 +467,28 @@ class PlaywrightPulseReporter {
|
|
|
447
467
|
passed: 0,
|
|
448
468
|
failed: 0,
|
|
449
469
|
skipped: 0,
|
|
450
|
-
duration,
|
|
470
|
+
duration: duration,
|
|
451
471
|
},
|
|
452
472
|
results: [],
|
|
453
473
|
metadata: {
|
|
454
474
|
generatedAt: new Date().toISOString(),
|
|
455
475
|
},
|
|
456
476
|
};
|
|
477
|
+
const finalOutputPathOnError = path.join(this.outputDir, this.baseOutputFile);
|
|
457
478
|
try {
|
|
458
|
-
const errorPath = path.join(this.outputDir, this.baseOutputFile);
|
|
459
479
|
await this._ensureDirExists(this.outputDir);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
console.warn(`PlaywrightPulseReporter: Wrote a minimal error report to ${errorPath}.`);
|
|
480
|
+
await fs.writeFile(finalOutputPathOnError, JSON.stringify(errorReport, null, 2));
|
|
481
|
+
console.warn(`PlaywrightPulseReporter: Wrote an error report to ${finalOutputPathOnError} as finalReport was missing.`);
|
|
463
482
|
}
|
|
464
|
-
catch (
|
|
465
|
-
console.error(
|
|
483
|
+
catch (writeError) {
|
|
484
|
+
console.error(`PlaywrightPulseReporter: Failed to write error report: ${writeError.message}`);
|
|
466
485
|
}
|
|
467
486
|
return;
|
|
468
487
|
}
|
|
469
|
-
// At this point, finalReport.run is guaranteed to be populated by either _mergeShardResults or the non-sharded path.
|
|
470
488
|
const reportRunData = finalReport.run;
|
|
471
|
-
const finalRunStatus = ((_a = reportRunData.failed) !== null && _a !== void 0 ? _a : 0) > 0
|
|
489
|
+
const finalRunStatus = ((_a = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.failed) !== null && _a !== void 0 ? _a : 0) > 0
|
|
472
490
|
? "failed"
|
|
473
|
-
: ((_b = reportRunData.totalTests) !== null && _b !== void 0 ? _b : 0) === 0 && result.status !== "passed"
|
|
491
|
+
: ((_b = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.totalTests) !== null && _b !== void 0 ? _b : 0) === 0 && result.status !== "passed"
|
|
474
492
|
? result.status === "interrupted"
|
|
475
493
|
? "interrupted"
|
|
476
494
|
: "no tests or error"
|
|
@@ -479,11 +497,11 @@ class PlaywrightPulseReporter {
|
|
|
479
497
|
PlaywrightPulseReporter: Run Finished
|
|
480
498
|
-----------------------------------------
|
|
481
499
|
Overall Status: ${finalRunStatus.toUpperCase()}
|
|
482
|
-
Total Tests: ${reportRunData.totalTests}
|
|
483
|
-
Passed: ${reportRunData.passed}
|
|
484
|
-
Failed: ${reportRunData.failed}
|
|
485
|
-
Skipped: ${reportRunData.skipped}
|
|
486
|
-
Duration: ${(reportRunData.duration / 1000).toFixed(2)}s
|
|
500
|
+
Total Tests: ${(reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.totalTests) || 0}
|
|
501
|
+
Passed: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.passed}
|
|
502
|
+
Failed: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.failed}
|
|
503
|
+
Skipped: ${reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.skipped}
|
|
504
|
+
Duration: ${(((_c = reportRunData === null || reportRunData === void 0 ? void 0 : reportRunData.duration) !== null && _c !== void 0 ? _c : 0) / 1000).toFixed(2)}s
|
|
487
505
|
-----------------------------------------`;
|
|
488
506
|
if (this.printsToStdio()) {
|
|
489
507
|
console.log(summary);
|
|
@@ -491,11 +509,11 @@ PlaywrightPulseReporter: Run Finished
|
|
|
491
509
|
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
492
510
|
try {
|
|
493
511
|
await this._ensureDirExists(this.outputDir);
|
|
494
|
-
// Custom replacer for JSON.stringify to handle Date objects correctly
|
|
495
512
|
await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
|
|
496
|
-
if (value instanceof Date)
|
|
513
|
+
if (value instanceof Date)
|
|
497
514
|
return value.toISOString();
|
|
498
|
-
|
|
515
|
+
if (typeof value === "bigint")
|
|
516
|
+
return value.toString();
|
|
499
517
|
return value;
|
|
500
518
|
}, 2));
|
|
501
519
|
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-8",
|
|
5
5
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"playwright",
|
|
@@ -45,25 +45,65 @@
|
|
|
45
45
|
"report:email": "node ./scripts/sendReport.js"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
+
"@genkit-ai/googleai": "^1.6.2",
|
|
49
|
+
"@genkit-ai/next": "^1.6.2",
|
|
50
|
+
"@hookform/resolvers": "^4.1.3",
|
|
51
|
+
"@radix-ui/react-accordion": "^1.2.3",
|
|
52
|
+
"@radix-ui/react-alert-dialog": "^1.1.6",
|
|
53
|
+
"@radix-ui/react-avatar": "^1.1.3",
|
|
54
|
+
"@radix-ui/react-checkbox": "^1.1.4",
|
|
55
|
+
"@radix-ui/react-dialog": "^1.1.6",
|
|
56
|
+
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
|
57
|
+
"@radix-ui/react-label": "^2.1.2",
|
|
58
|
+
"@radix-ui/react-menubar": "^1.1.6",
|
|
59
|
+
"@radix-ui/react-popover": "^1.1.6",
|
|
60
|
+
"@radix-ui/react-progress": "^1.1.2",
|
|
61
|
+
"@radix-ui/react-radio-group": "^1.2.3",
|
|
62
|
+
"@radix-ui/react-scroll-area": "^1.2.3",
|
|
63
|
+
"@radix-ui/react-select": "^2.1.6",
|
|
64
|
+
"@radix-ui/react-separator": "^1.1.2",
|
|
65
|
+
"@radix-ui/react-slider": "^1.2.3",
|
|
66
|
+
"@radix-ui/react-slot": "^1.1.2",
|
|
67
|
+
"@radix-ui/react-switch": "^1.1.3",
|
|
68
|
+
"@radix-ui/react-tabs": "^1.1.3",
|
|
69
|
+
"@radix-ui/react-toast": "^1.2.6",
|
|
70
|
+
"@radix-ui/react-tooltip": "^1.1.8",
|
|
71
|
+
"@tanstack-query-firebase/react": "^1.0.5",
|
|
72
|
+
"@tanstack/react-query": "^5.66.0",
|
|
48
73
|
"archiver": "^7.0.1",
|
|
49
74
|
"class-variance-authority": "^0.7.1",
|
|
50
75
|
"clsx": "^2.1.1",
|
|
51
76
|
"d3": "^7.9.0",
|
|
52
77
|
"date-fns": "^3.6.0",
|
|
53
78
|
"dotenv": "^16.5.0",
|
|
79
|
+
"firebase": "^11.3.0",
|
|
80
|
+
"genkit": "^1.6.2",
|
|
54
81
|
"highcharts": "^12.2.0",
|
|
55
82
|
"jsdom": "^26.1.0",
|
|
56
83
|
"lucide-react": "^0.475.0",
|
|
84
|
+
"next": "15.2.3",
|
|
57
85
|
"nodemailer": "^7.0.3",
|
|
58
86
|
"patch-package": "^8.0.0",
|
|
87
|
+
"react": "^18.3.1",
|
|
88
|
+
"react-day-picker": "^8.10.1",
|
|
89
|
+
"react-dom": "^18.3.1",
|
|
90
|
+
"react-hook-form": "^7.54.2",
|
|
59
91
|
"recharts": "^2.15.1",
|
|
92
|
+
"tailwind-merge": "^3.0.1",
|
|
93
|
+
"tailwindcss-animate": "^1.0.7",
|
|
60
94
|
"ua-parser-js": "^2.0.3",
|
|
61
95
|
"zod": "^3.24.2"
|
|
62
96
|
},
|
|
63
97
|
"devDependencies": {
|
|
64
98
|
"@types/node": "^20",
|
|
99
|
+
"@types/react": "^18",
|
|
100
|
+
"@types/react-dom": "^18",
|
|
65
101
|
"@types/ua-parser-js": "^0.7.39",
|
|
66
102
|
"eslint": "9.25.1",
|
|
103
|
+
"eslint-config-next": "15.3.1",
|
|
104
|
+
"genkit-cli": "^1.6.1",
|
|
105
|
+
"postcss": "^8",
|
|
106
|
+
"tailwindcss": "^3.4.1",
|
|
67
107
|
"typescript": "^5"
|
|
68
108
|
},
|
|
69
109
|
"engines": {
|