@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; // Ensure options has the resolved path
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
- // Optional: console.log(`Starting test: ${test.titlePath().join(' > ')} for project ${test.parent?.project()?.name}`);
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, browserName, // This will be the detailed browser info string
113
- testCase) {
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 ((_d = step.location) === null || _d === void 0 ? void 0 : _d.file) {
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: browserName, // Store the detailed browser string for the step
206
+ browser: browserDetails,
140
207
  errorMessage: errorMessage,
141
- stackTrace: ((_e = step.error) === null || _e === void 0 ? void 0 : _e.stack) || undefined,
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: [], // Will be populated by recursive calls in onTestEnd
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, _j, _k;
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
- // const browserDisplayInfo = this.getBrowserInfo(test);
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 || // Playwright's internal unique ID for the test case
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, browserDisplayInfo, // Pass the detailed browser info string
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); // Recursive call
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
- // console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
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 stdoutMessages = ((_e = result.stdout) === null || _e === void 0 ? void 0 : _e.map((item) => typeof item === "string" ? item : item.toString())) || [];
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", // Will be set during final report generation
267
+ runId: "TBD",
284
268
  name: test.titlePath().join(" > "),
285
- suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_g = this.config.projects[0]) === null || _g === void 0 ? void 0 : _g.name) || "Default Suite",
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: browserDisplayInfo, // Use the detailed browser string
274
+ browser: browserDetails,
291
275
  retries: result.retry,
292
- steps: ((_h = result.steps) === null || _h === void 0 ? void 0 : _h.length) ? await processAllSteps(result.steps) : [],
293
- errorMessage: (_j = result.error) === null || _j === void 0 ? void 0 : _j.message,
294
- stackTrace: (_k = result.error) === null || _k === void 0 ? void 0 : _k.stack,
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: [], // To be populated by attachFiles
298
- videoPath: undefined, // To be populated by attachFiles
299
- tracePath: undefined, // To be populated by attachFiles
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 // Pass the TestRun object to populate
340
- ) {
341
- var _a, _b;
322
+ async _mergeShardResults(finalRunData) {
342
323
  let allShardProcessedResults = [];
343
- const totalShards = (_b = (_a = this.config.shard) === null || _a === void 0 ? void 0 : _a.total) !== null && _b !== void 0 ? _b : 1;
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); // Dates are already ISO strings
349
- allShardProcessedResults.push(...shardResults);
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
- // console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}.`);
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
- const finalUniqueResultsMap = new Map();
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)); // Assign runId to each test result
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, // Contains Date object for timestamp
376
- results: finalResultsList, // Contains ISO strings for dates from shards
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
- // console.warn("Pulse Reporter: Warning during cleanup of temporary files:", error.message);
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), // Stored as Date object
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, // runData contains a Date object for timestamp
437
- results: this.results, // results contain Date objects for startTime, endTime
433
+ run: runData,
434
+ results: properlyTypedResults,
438
435
  metadata: { generatedAt: new Date().toISOString() },
439
436
  };
440
437
  }
441
- // This check should be robust now
442
- if (!finalReport ||
443
- !finalReport.run ||
444
- typeof finalReport.run.totalTests !== "number") {
445
- console.error("PlaywrightPulseReporter: CRITICAL - finalReport object or its run data was malformed. Cannot create summary.");
446
- const errorReportMinimal = {
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
- // Stringify with Date conversion for the minimal error report
465
- await fs.writeFile(errorPath, JSON.stringify(errorReportMinimal, (key, value) => value instanceof Date ? value.toISOString() : value, 2));
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 (e) {
469
- console.error("PlaywrightPulseReporter: Failed to write minimal error report.", e);
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-7",
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" : ""