@arghajit/playwright-pulse-report 0.1.5 → 0.2.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.
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ // input_file_0.ts
2
3
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
4
  if (k2 === undefined) k2 = k;
4
5
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -39,11 +40,9 @@ const path = __importStar(require("path"));
39
40
  const crypto_1 = require("crypto");
40
41
  const attachment_utils_1 = require("./attachment-utils"); // Use relative path
41
42
  const convertStatus = (status, testCase) => {
42
- // Special case: test was expected to fail (test.fail())
43
43
  if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
44
- return status === "failed" ? "passed" : "failed";
44
+ return status === "failed" ? "failed" : "failed";
45
45
  }
46
- // Special case: test was expected to skip (test.skip())
47
46
  if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "skipped") {
48
47
  return "skipped";
49
48
  }
@@ -60,7 +59,7 @@ const convertStatus = (status, testCase) => {
60
59
  }
61
60
  };
62
61
  const TEMP_SHARD_FILE_PREFIX = ".pulse-shard-results-";
63
- const ATTACHMENTS_SUBDIR = "attachments"; // Centralized definition
62
+ const ATTACHMENTS_SUBDIR = "attachments";
64
63
  class PlaywrightPulseReporter {
65
64
  constructor(options = {}) {
66
65
  var _a, _b;
@@ -68,13 +67,10 @@ class PlaywrightPulseReporter {
68
67
  this.baseOutputFile = "playwright-pulse-report.json";
69
68
  this.isSharded = false;
70
69
  this.shardIndex = undefined;
71
- this.options = options; // Store provided options
70
+ this.options = options;
72
71
  this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
73
- // Determine outputDir relative to config file or rootDir
74
- // The actual resolution happens in onBegin where config is available
75
72
  this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
76
- this.attachmentsDir = path.join(this.outputDir, ATTACHMENTS_SUBDIR); // Initial path, resolved fully in onBegin
77
- // console.log(`Pulse Reporter Init: Configured outputDir option: ${options.outputDir}, Base file: ${this.baseOutputFile}`);
73
+ this.attachmentsDir = path.join(this.outputDir, ATTACHMENTS_SUBDIR);
78
74
  }
79
75
  printsToStdio() {
80
76
  return this.shardIndex === undefined || this.shardIndex === 0;
@@ -84,46 +80,33 @@ class PlaywrightPulseReporter {
84
80
  this.config = config;
85
81
  this.suite = suite;
86
82
  this.runStartTime = Date.now();
87
- // --- Resolve outputDir relative to config file or rootDir ---
88
83
  const configDir = this.config.rootDir;
89
- // Use config file directory if available, otherwise rootDir
90
84
  const configFileDir = this.config.configFile
91
85
  ? path.dirname(this.config.configFile)
92
86
  : configDir;
93
87
  this.outputDir = path.resolve(configFileDir, (_a = this.options.outputDir) !== null && _a !== void 0 ? _a : "pulse-report");
94
- // Resolve attachmentsDir relative to the final outputDir
95
88
  this.attachmentsDir = path.resolve(this.outputDir, ATTACHMENTS_SUBDIR);
96
- // Update options with the resolved absolute path for internal use
97
89
  this.options.outputDir = this.outputDir;
98
- // console.log(`Pulse Reporter onBegin: Final Report Output dir resolved to ${this.outputDir}`);
99
- // console.log(`Pulse Reporter onBegin: Attachments base dir resolved to ${this.attachmentsDir}`);
100
90
  const totalShards = this.config.shard ? this.config.shard.total : 1;
101
91
  this.isSharded = totalShards > 1;
102
92
  this.shardIndex = this.config.shard
103
93
  ? this.config.shard.current - 1
104
94
  : undefined;
105
- // Ensure base output directory exists (attachments handled by attachFiles util)
106
95
  this._ensureDirExists(this.outputDir)
107
96
  .then(() => {
108
97
  if (this.shardIndex === undefined) {
109
98
  console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Pulse outputting to ${this.outputDir}`);
110
- // Clean up old shard files only in the main process
111
99
  return this._cleanupTemporaryFiles();
112
100
  }
113
- else {
114
- // console.log(`Pulse Reporter (Shard ${this.shardIndex + 1}/${totalShards}): Starting. Temp results to ${this.outputDir}`);
115
- return Promise.resolve();
116
- }
117
101
  })
118
102
  .catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
119
103
  }
120
104
  onTestBegin(test) {
121
- // Optional: Log test start if needed
122
105
  // console.log(`Starting test: ${test.title}`);
123
106
  }
124
- async processStep(step, testId, browserName) {
107
+ async processStep(step, testId, browserName, // Changed from browserName for clarity
108
+ testCase) {
125
109
  var _a, _b, _c, _d;
126
- // Determine actual step status (don't inherit from parent)
127
110
  let stepStatus = "passed";
128
111
  let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
129
112
  if ((_c = (_b = step.error) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.startsWith("Test is skipped:")) {
@@ -131,19 +114,41 @@ class PlaywrightPulseReporter {
131
114
  errorMessage = "Info: Test is skipped:";
132
115
  }
133
116
  else {
134
- stepStatus = convertStatus(step.error ? "failed" : "passed");
117
+ stepStatus = convertStatus(step.error ? "failed" : "passed", testCase);
135
118
  }
136
119
  const duration = step.duration;
137
120
  const startTime = new Date(step.startTime);
138
121
  const endTime = new Date(startTime.getTime() + Math.max(0, duration));
139
- // Capture code location if available
140
122
  let codeLocation = "";
141
123
  if (step.location) {
142
124
  codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
143
125
  }
126
+ let stepTitle = step.title;
127
+ // This logic had a 'status' variable that was not defined in this scope.
128
+ // Assuming it meant to check 'stepStatus' or 'testCase.expectedStatus' related to step.error.
129
+ // Corrected to reflect comparison with testCase if step.category is 'test'.
130
+ if (step.category === "test" && testCase) {
131
+ // If a test step (not a hook) resulted in an error, but the test was expected to fail,
132
+ // this specific logic might need refinement based on how you want to report step errors
133
+ // within a test that is expected to fail.
134
+ // The current convertStatus handles the overall testCase expectedStatus.
135
+ // For step-specific error messages when testCase.expectedStatus === 'failed':
136
+ if (testCase.expectedStatus === "failed") {
137
+ if (step.error) {
138
+ // If the step itself has an error
139
+ // errorMessage is already set from step.error.message
140
+ }
141
+ else {
142
+ // If a step within an expected-to-fail test passes, it's usually not an error for the step itself.
143
+ }
144
+ }
145
+ else if (testCase.expectedStatus === "skipped") {
146
+ // errorMessage is already set if step.error.message started with "Test is skipped:"
147
+ }
148
+ }
144
149
  return {
145
- id: `${testId}_step_${startTime.toISOString()}-${duration}-${(0, crypto_1.randomUUID)()}-581d5ad8-ce75-4ca5-94a6-ed29c466c815`,
146
- title: step.title,
150
+ id: `${testId}_step_${startTime.toISOString()}-${duration}-${(0, crypto_1.randomUUID)()}`,
151
+ title: stepTitle,
147
152
  status: stepStatus,
148
153
  duration: duration,
149
154
  startTime: startTime,
@@ -158,38 +163,39 @@ class PlaywrightPulseReporter {
158
163
  ? "before"
159
164
  : "after"
160
165
  : undefined,
161
- steps: [], // Will be populated recursively
166
+ steps: [],
162
167
  };
163
168
  }
164
169
  async onTestEnd(test, result) {
165
170
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
166
- // Get the most accurate browser name
167
171
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
172
+ // Use project.name for a user-friendly display name
168
173
  const browserName = ((_b = project === null || project === void 0 ? void 0 : project.use) === null || _b === void 0 ? void 0 : _b.defaultBrowserType) || "unknown";
174
+ // If you need the engine name (chromium, firefox, webkit)
175
+ // const browserEngineName = project?.use?.browserName || "unknown_engine";
169
176
  const testStatus = convertStatus(result.status, test);
170
177
  const startTime = new Date(result.startTime);
171
178
  const endTime = new Date(startTime.getTime() + result.duration);
172
- // Generate a slightly more robust ID for attachments, especially if test.id is missing
173
179
  const testIdForFiles = test.id ||
174
180
  `${test
175
181
  .titlePath()
176
182
  .join("_")
177
183
  .replace(/[^a-zA-Z0-9]/g, "_")}_${startTime.getTime()}`;
178
- // --- Process Steps Recursively ---
179
- const processAllSteps = async (steps, parentTestStatus) => {
184
+ const processAllSteps = async (steps
185
+ // parentTestStatus parameter was not used, removed for now.
186
+ // If needed for inherited status logic for steps, it can be re-added.
187
+ ) => {
180
188
  let processed = [];
181
189
  for (const step of steps) {
182
- const processedStep = await this.processStep(step, testIdForFiles, browserName);
190
+ const processedStep = await this.processStep(step, testIdForFiles, browserName, // Pass display name
191
+ test);
183
192
  processed.push(processedStep);
184
193
  if (step.steps && step.steps.length > 0) {
185
- const nestedSteps = await processAllSteps(step.steps, processedStep.status);
186
- // Assign nested steps correctly
187
- processedStep.steps = nestedSteps;
194
+ processedStep.steps = await processAllSteps(step.steps);
188
195
  }
189
196
  }
190
197
  return processed;
191
198
  };
192
- // --- Extract Code Snippet ---
193
199
  let codeSnippet = undefined;
194
200
  try {
195
201
  if (((_c = test.location) === null || _c === void 0 ? void 0 : _c.file) && ((_d = test.location) === null || _d === void 0 ? void 0 : _d.line) && ((_e = test.location) === null || _e === void 0 ? void 0 : _e.column)) {
@@ -200,31 +206,63 @@ class PlaywrightPulseReporter {
200
206
  catch (e) {
201
207
  console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
202
208
  }
203
- // --- Prepare Base TestResult ---
209
+ // --- Capture stdout and stderr ---
210
+ const stdoutMessages = [];
211
+ if (result.stdout && result.stdout.length > 0) {
212
+ result.stdout.forEach((item) => {
213
+ if (typeof item === "string") {
214
+ stdoutMessages.push(item);
215
+ }
216
+ else {
217
+ // If item is not a string, Playwright's typings indicate it's a Buffer (or Buffer-like).
218
+ // We must call toString() on it.
219
+ // The 'item' here is typed as 'Buffer' from the 'else' branch of '(string | Buffer)[]'
220
+ stdoutMessages.push(item.toString());
221
+ }
222
+ });
223
+ }
224
+ const stderrMessages = [];
225
+ if (result.stderr && result.stderr.length > 0) {
226
+ result.stderr.forEach((item) => {
227
+ if (typeof item === "string") {
228
+ stderrMessages.push(item);
229
+ }
230
+ else {
231
+ // If item is not a string, Playwright's typings indicate it's a Buffer (or Buffer-like).
232
+ // We must call toString() on it.
233
+ stderrMessages.push(item.toString());
234
+ }
235
+ });
236
+ }
237
+ // --- End capture stdout and stderr ---
204
238
  const pulseResult = {
205
- id: test.id || `${test.title}-${startTime.toISOString()}-${(0, crypto_1.randomUUID)()}`, // Use the original ID logic here
206
- runId: "TBD", // Will be set later
239
+ id: test.id || `${test.title}-${startTime.toISOString()}-${(0, crypto_1.randomUUID)()}`,
240
+ runId: "TBD",
207
241
  name: test.titlePath().join(" > "),
208
- suiteName: ((_f = this.config.projects[0]) === null || _f === void 0 ? void 0 : _f.name) || "Default Suite",
242
+ // Use project.name for suiteName if desired, or fallback
243
+ suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_f = this.config.projects[0]) === null || _f === void 0 ? void 0 : _f.name) || "Default Suite",
209
244
  status: testStatus,
210
245
  duration: result.duration,
211
246
  startTime: startTime,
212
247
  endTime: endTime,
213
- browser: browserName,
248
+ browser: browserName, // Use the user-friendly project name
214
249
  retries: result.retry,
215
- steps: ((_g = result.steps) === null || _g === void 0 ? void 0 : _g.length)
216
- ? await processAllSteps(result.steps, testStatus)
217
- : [],
250
+ steps: ((_g = result.steps) === null || _g === void 0 ? void 0 : _g.length) ? await processAllSteps(result.steps) : [],
218
251
  errorMessage: (_h = result.error) === null || _h === void 0 ? void 0 : _h.message,
219
252
  stackTrace: (_j = result.error) === null || _j === void 0 ? void 0 : _j.stack,
220
253
  codeSnippet: codeSnippet,
221
254
  tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
222
- screenshots: [],
255
+ screenshots: [], // Will be populated by attachFiles
223
256
  videoPath: undefined,
224
257
  tracePath: undefined,
258
+ // videoPath and tracePath might be deprecated if using the array versions above
259
+ // Depending on attachFiles implementation
260
+ // Add the captured console messages
261
+ stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
262
+ stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
225
263
  };
226
- // --- Process Attachments using the new utility ---
227
264
  try {
265
+ // Pass this.options which should contain the resolved outputDir
228
266
  (0, attachment_utils_1.attachFiles)(testIdForFiles, result, pulseResult, this.options);
229
267
  }
230
268
  catch (attachError) {
@@ -241,26 +279,18 @@ class PlaywrightPulseReporter {
241
279
  }
242
280
  async _writeShardResults() {
243
281
  if (this.shardIndex === undefined) {
244
- console.warn("Pulse Reporter: _writeShardResults called unexpectedly in main process. Skipping.");
282
+ // console.warn("Pulse Reporter: _writeShardResults called unexpectedly in main process. Skipping.");
245
283
  return;
246
284
  }
247
285
  const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${this.shardIndex}.json`);
248
286
  try {
249
- // No need to ensureDirExists here, should be done in onBegin
250
- await fs.writeFile(tempFilePath, JSON.stringify(this.results, (key, value) => {
251
- if (value instanceof Date) {
252
- return value.toISOString();
253
- }
254
- return value;
255
- }, 2));
256
- // console.log(`Pulse Reporter: Shard ${this.shardIndex} wrote ${this.results.length} results to ${tempFilePath}`);
287
+ await fs.writeFile(tempFilePath, JSON.stringify(this.results, (key, value) => (value instanceof Date ? value.toISOString() : value), 2));
257
288
  }
258
289
  catch (error) {
259
290
  console.error(`Pulse Reporter: Shard ${this.shardIndex} failed to write temporary results to ${tempFilePath}`, error);
260
291
  }
261
292
  }
262
293
  async _mergeShardResults(finalRunData) {
263
- // console.log('Pulse Reporter: Merging results from shards...');
264
294
  let allResults = [];
265
295
  const totalShards = this.config.shard ? this.config.shard.total : 1;
266
296
  for (let i = 0; i < totalShards; i++) {
@@ -270,18 +300,16 @@ class PlaywrightPulseReporter {
270
300
  const shardResults = JSON.parse(content);
271
301
  shardResults.forEach((r) => (r.runId = finalRunData.id));
272
302
  allResults = allResults.concat(shardResults);
273
- // console.log(`Pulse Reporter: Successfully merged ${shardResults.length} results from shard ${i}`);
274
303
  }
275
304
  catch (error) {
276
305
  if ((error === null || error === void 0 ? void 0 : error.code) === "ENOENT") {
277
- console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}. This might happen if shard ${i} had no tests or failed early.`);
306
+ console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}.`);
278
307
  }
279
308
  else {
280
- console.error(`Pulse Reporter: Could not read or parse results from shard ${i} (${tempFilePath}). Error:`, error);
309
+ console.error(`Pulse Reporter: Could not read/parse results from shard ${i} (${tempFilePath}). Error:`, error);
281
310
  }
282
311
  }
283
312
  }
284
- // console.log(`Pulse Reporter: Merged a total of ${allResults.length} results from ${totalShards} shards.`);
285
313
  finalRunData.passed = allResults.filter((r) => r.status === "passed").length;
286
314
  finalRunData.failed = allResults.filter((r) => r.status === "failed").length;
287
315
  finalRunData.skipped = allResults.filter((r) => r.status === "skipped").length;
@@ -290,9 +318,7 @@ class PlaywrightPulseReporter {
290
318
  const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
291
319
  if (typeof value === "string" && isoDateRegex.test(value)) {
292
320
  const date = new Date(value);
293
- if (!isNaN(date.getTime())) {
294
- return date;
295
- }
321
+ return !isNaN(date.getTime()) ? date : value;
296
322
  }
297
323
  return value;
298
324
  };
@@ -305,34 +331,27 @@ class PlaywrightPulseReporter {
305
331
  }
306
332
  async _cleanupTemporaryFiles() {
307
333
  try {
308
- // No need to ensure dir exists here if handled in onBegin
309
334
  const files = await fs.readdir(this.outputDir);
310
335
  const tempFiles = files.filter((f) => f.startsWith(TEMP_SHARD_FILE_PREFIX));
311
336
  if (tempFiles.length > 0) {
312
- // console.log(`Pulse Reporter: Cleaning up ${tempFiles.length} temporary shard files...`);
313
337
  await Promise.all(tempFiles.map((f) => fs.unlink(path.join(this.outputDir, f))));
314
338
  }
315
339
  }
316
340
  catch (error) {
317
341
  if ((error === null || error === void 0 ? void 0 : error.code) !== "ENOENT") {
318
- // Ignore if the directory doesn't exist
319
342
  console.error("Pulse Reporter: Error cleaning up temporary files:", error);
320
343
  }
321
344
  }
322
345
  }
323
- async _ensureDirExists(dirPath, clean = false) {
346
+ async _ensureDirExists(dirPath) {
347
+ // Removed 'clean' parameter as it was unused
324
348
  try {
325
- if (clean) {
326
- // console.log(`Pulse Reporter: Cleaning directory ${dirPath}...`);
327
- await fs.rm(dirPath, { recursive: true, force: true });
328
- }
329
349
  await fs.mkdir(dirPath, { recursive: true });
330
350
  }
331
351
  catch (error) {
332
- // Ignore EEXIST error if the directory already exists
333
352
  if (error.code !== "EEXIST") {
334
353
  console.error(`Pulse Reporter: Failed to ensure directory exists: ${dirPath}`, error);
335
- throw error; // Re-throw other errors
354
+ throw error;
336
355
  }
337
356
  }
338
357
  }
@@ -340,16 +359,16 @@ class PlaywrightPulseReporter {
340
359
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
341
360
  if (this.shardIndex !== undefined) {
342
361
  await this._writeShardResults();
343
- // console.log(`PlaywrightPulseReporter: Shard ${this.shardIndex + 1} finished writing results.`);
344
362
  return;
345
363
  }
346
364
  const runEndTime = Date.now();
347
365
  const duration = runEndTime - this.runStartTime;
366
+ // Consider making the UUID part truly random for each run if this ID needs to be globally unique over time
348
367
  const runId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
349
368
  const runData = {
350
369
  id: runId,
351
370
  timestamp: new Date(this.runStartTime),
352
- totalTests: 0, // Will be updated after merging/processing
371
+ totalTests: 0,
353
372
  passed: 0,
354
373
  failed: 0,
355
374
  skipped: 0,
@@ -357,22 +376,34 @@ class PlaywrightPulseReporter {
357
376
  };
358
377
  let finalReport;
359
378
  if (this.isSharded) {
360
- // console.log("Pulse Reporter: Run ended, main process merging shard results...");
361
379
  finalReport = await this._mergeShardResults(runData);
362
380
  }
363
381
  else {
364
- // console.log("Pulse Reporter: Run ended, processing results directly (no sharding)...");
365
- this.results.forEach((r) => (r.runId = runId)); // Assign runId to directly collected results
382
+ this.results.forEach((r) => (r.runId = runId));
366
383
  runData.passed = this.results.filter((r) => r.status === "passed").length;
367
384
  runData.failed = this.results.filter((r) => r.status === "failed").length;
368
385
  runData.skipped = this.results.filter((r) => r.status === "skipped").length;
369
386
  runData.totalTests = this.results.length;
370
387
  finalReport = {
371
388
  run: runData,
372
- results: this.results, // Use directly collected results
389
+ results: this.results,
373
390
  metadata: { generatedAt: new Date().toISOString() },
374
391
  };
375
392
  }
393
+ // This block seems redundant as finalReport is already assigned above.
394
+ // if (this.isSharded) {
395
+ // finalReport = await this._mergeShardResults(runData);
396
+ // } else {
397
+ // this.results.forEach((r) => (r.runId = runId));
398
+ // runData.passed = this.results.filter((r) => r.status === "passed").length;
399
+ // runData.failed = this.results.filter((r) => r.status === "failed").length;
400
+ // runData.skipped = this.results.filter((r) => r.status === "skipped").length;
401
+ // runData.totalTests = this.results.length;
402
+ // finalReport = {
403
+ // run: runData, results: this.results,
404
+ // metadata: { generatedAt: new Date().toISOString() },
405
+ // };
406
+ // }
376
407
  const finalRunStatus = ((_b = (_a = finalReport.run) === null || _a === void 0 ? void 0 : _a.failed) !== null && _b !== void 0 ? _b : 0 > 0)
377
408
  ? "failed"
378
409
  : ((_c = finalReport.run) === null || _c === void 0 ? void 0 : _c.totalTests) === 0
@@ -391,30 +422,21 @@ PlaywrightPulseReporter: Run Finished
391
422
  console.log(summary);
392
423
  const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
393
424
  try {
394
- // Ensure directory exists before writing final report
395
425
  await this._ensureDirExists(this.outputDir);
396
- // --- Write Final JSON Report ---
397
426
  await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
398
- if (value instanceof Date) {
399
- return value.toISOString(); // Ensure dates are ISO strings in JSON
400
- }
401
- // Handle potential BigInt if used elsewhere, though unlikely here
402
- if (typeof value === "bigint") {
427
+ if (value instanceof Date)
428
+ return value.toISOString();
429
+ if (typeof value === "bigint")
403
430
  return value.toString();
404
- }
405
431
  return value;
406
432
  }, 2));
407
433
  console.log(`PlaywrightPulseReporter: JSON report written to ${finalOutputPath}`);
408
- // REMOVED Static HTML Generation Call
409
- // The reporter's responsibility is now only to create the JSON file.
410
- // The user will run `npx generate-pulse-report` separately.
411
434
  }
412
435
  catch (error) {
413
436
  console.error(`Pulse Reporter: Failed to write final JSON report to ${finalOutputPath}. Error: ${error.message}`);
414
437
  }
415
438
  finally {
416
439
  if (this.isSharded) {
417
- // console.log("Pulse Reporter: Cleaning up temporary shard files...");
418
440
  await this._cleanupTemporaryFiles();
419
441
  }
420
442
  }
@@ -1,5 +1,5 @@
1
1
  import type { LucideIcon } from 'lucide-react';
2
- export type TestStatus = 'passed' | 'failed' | 'skipped';
2
+ export type TestStatus = "passed" | "failed" | "skipped" | "expected-failure" | "unexpected-success" | "explicitly-skipped";
3
3
  export interface TestStep {
4
4
  id: string;
5
5
  title: string;
@@ -34,6 +34,8 @@ export interface TestResult {
34
34
  screenshots?: string[];
35
35
  videoPath?: string;
36
36
  tracePath?: string;
37
+ stdout?: string[];
38
+ stderr?: string[];
37
39
  }
38
40
  export interface TestRun {
39
41
  id: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arghajit/playwright-pulse-report",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.1.5",
4
+ "version": "0.2.0",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "keywords": [
7
7
  "playwright",
@@ -20,13 +20,15 @@
20
20
  "types": "dist/reporter/index.d.ts",
21
21
  "files": [
22
22
  "dist",
23
+ "screenshots",
23
24
  "scripts/generate-static-report.mjs"
24
25
  ],
25
26
  "license": "MIT",
26
27
  "bin": {
27
28
  "generate-pulse-report": "./scripts/generate-static-report.mjs",
28
29
  "merge-pulse-report": "./scripts/merge-pulse-report.js",
29
- "send-email": "./scripts/sendReport.js"
30
+ "send-email": "./scripts/sendReport.js",
31
+ "generate-trend": "./scripts/generate-trend-excel.mjs"
30
32
  },
31
33
  "exports": {
32
34
  ".": {
@@ -75,15 +77,18 @@
75
77
  "@radix-ui/react-tooltip": "^1.1.8",
76
78
  "@tanstack-query-firebase/react": "^1.0.5",
77
79
  "@tanstack/react-query": "^5.66.0",
80
+ "archiver": "^7.0.1",
78
81
  "class-variance-authority": "^0.7.1",
79
82
  "clsx": "^2.1.1",
80
83
  "d3": "^7.9.0",
81
84
  "date-fns": "^3.6.0",
85
+ "dotenv": "^16.5.0",
82
86
  "firebase": "^11.3.0",
83
87
  "genkit": "^1.6.2",
84
88
  "jsdom": "^26.1.0",
85
89
  "lucide-react": "^0.475.0",
86
90
  "next": "15.2.3",
91
+ "nodemailer": "^7.0.3",
87
92
  "patch-package": "^8.0.0",
88
93
  "react": "^18.3.1",
89
94
  "react-day-picker": "^8.10.1",
@@ -92,10 +97,8 @@
92
97
  "recharts": "^2.15.1",
93
98
  "tailwind-merge": "^3.0.1",
94
99
  "tailwindcss-animate": "^1.0.7",
95
- "zod": "^3.24.2",
96
- "archiver": "^7.0.1",
97
- "dotenv": "^16.5.0",
98
- "nodemailer": "^7.0.3"
100
+ "xlsx": "^0.18.5",
101
+ "zod": "^3.24.2"
99
102
  },
100
103
  "devDependencies": {
101
104
  "@types/node": "^20",
Binary file
Binary file