@arghajit/playwright-pulse-report 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,53 +7,7 @@ _The ultimate Playwright reporter — Interactive dashboard with historical tren
7
7
 
8
8
  ## ![Features](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/features.svg)
9
9
 
10
- ## 📸 Screenshots
11
-
12
- ### 🖥️ Desktop View
13
-
14
- <div align="center" style="display: flex; gap: 20px; justify-content: center; flex-wrap: wrap;">
15
- <a href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//playwright-pulse-static-report-desktop.html.png" target="_blank"> <img src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//playwright-pulse-static-report-desktop.html.png" alt="Dashboard Overview" width="300"/>
16
- <p align="center"><strong>Dashboard Overview</strong></p>
17
- </a>
18
- <a href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//Test-run-desktop.png" target="_blank"> <img src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//Test-run-desktop.png" alt="Test Details" width="300"/>
19
- <p align="center"><strong>Test Details</strong>
20
- </p>
21
- </a>
22
- <a href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//Test-error-desktop.png" target="_blank"> <img src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//Test-run-desktop.png" alt="Test Failure Details" width="300"/>
23
- <p align="center"><strong>Test Failure Details</strong>
24
- </p>
25
- </a>
26
- <a href="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//Test-trends-desktop.png" target="_blank"> <img src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//Test-trends-desktop.png" alt="Filter View" width="300"/>
27
- <p align="center"><strong>Test Trends</strong></p>
28
- </a>
29
- </div>
30
-
31
- ### 📱 Mobile View
32
-
33
- <div align="center" style="display: flex; gap: 20px; justify-content: center; flex-wrap: wrap;">
34
-
35
- <a href="https://postimg.cc/CzJBLR5N" target="_blank">
36
- <img src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//playwright-pulse-static-report-Dashboard.html.png" alt="Mobile Dashboard Overview" width="300"/>
37
- <p align="center"><strong>Dashboard Overview</strong></p>
38
- </a>
39
-
40
- <a href="https://postimg.cc/G8YTczT8" target="_blank">
41
- <img src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//playwright-pulse-static-report_Test-results.html.png" alt="Test Details" width="300"/>
42
- <p align="center"><strong>Test Details</strong></p>
43
- </a>
44
-
45
- <a href="https://postimg.cc/G8YTczT8" target="_blank">
46
- <img src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//playwright-pulse-static-report-Trends.html.png" alt="Test Trends" width="300"/>
47
- <p align="center"><strong>Test Trends</strong></p>
48
- </a>
49
-
50
- </div>
51
-
52
- ### Email Report Example
53
-
54
- [![Email Report Template](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//Email-report-mobile-template.jpeg)](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//Email-report-mobile-template.jpeg)
55
-
56
- [![Email Report](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//pulse-email-summary.html.png)](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//pulse-email-summary.html.png)
10
+ ## **Documentation**: [Pulse Report](https://playwright-pulse-report.netlify.app/)
57
11
 
58
12
  ## Available Scripts
59
13
 
@@ -91,10 +45,6 @@ Run with `npm run <command>`
91
45
 
92
46
  ```bash
93
47
  npm install @arghajit/playwright-pulse-report@latest --save-dev
94
- # or
95
- yarn add @arghajit/playwright-pulse-report@latest --dev
96
- # or
97
- pnpm add @arghajit/playwright-pulse-report@latest --save-dev
98
48
  ```
99
49
 
100
50
  ### 2. Configure Playwright
@@ -161,8 +111,8 @@ npx generate-report
161
111
  1. Configure `.env`:
162
112
 
163
113
  ```bash
164
- SENDER_EMAIL_1=recipient1@example.com
165
- SENDER_EMAIL_2=recipient2@example.com
114
+ RECIPIENT_EMAIL_1=recipient1@example.com
115
+ RECIPIENT_EMAIL_2=recipient2@example.com
166
116
  # ... up to 5 recipients
167
117
  ```
168
118
 
@@ -300,7 +250,7 @@ npm run pulse-dashboard
300
250
 
301
251
  *(Run from project root containing `pulse-report/` directory)*
302
252
 
303
- **NPM Package**: [pulse-dashboard](https://www.npmjs.com/package/pulse-dashboard)
253
+ **NPM Package**: [playwright-pulse-report](https://www.npmjs.com/package/@arghajit/playwright-pulse-report)
304
254
 
305
255
  **Tech Stack**: Next.js, TypeScript, Tailwind CSS, Playwright
306
256
 
@@ -308,6 +258,52 @@ npm run pulse-dashboard
308
258
 
309
259
  ---
310
260
 
261
+ ## ⚙️ Advanced Configuration
262
+
263
+ ### Handling Sequential Test Runs
264
+
265
+ By default, the reporter will overwrite the `playwright-pulse-report.json` file on each new test run. This is usually what we want. However, if we run tests sequentially in the same job, like this:
266
+
267
+ ```bash
268
+ npx playwright test test1.spec.ts && npx playwright test test2.spec.ts
269
+ ```
270
+
271
+ By default, In this above scenario, the report from test1 will be lost. To solve this, you can use the resetOnEachRun option.
272
+
273
+ ```bash
274
+ // playwright.config.ts
275
+ import { defineConfig } from "@playwright/test";
276
+ import * as path from "path";
277
+
278
+ // Define where the final report JSON and HTML should go
279
+ const PULSE_REPORT_DIR = path.resolve(__dirname, "pulse-report"); // Example: a directory in your project root
280
+
281
+ export default defineConfig({
282
+ reporter: [
283
+ ["list"],
284
+ [
285
+ "@arghajit/playwright-pulse-report",
286
+ {
287
+ outputDir: PULSE_REPORT_DIR,
288
+ // Add this option
289
+ resetOnEachRun: false, // Default is true
290
+ },
291
+ ],
292
+ ],
293
+ // ...
294
+ });
295
+ ```
296
+
297
+ **How it works when resetOnEachRun: false:**
298
+
299
+ - On the first run, it saves report-1.json to a pulse-report/pulse-results directory and creates the main playwright-pulse-report.json from it.
300
+ - On the second run, it saves report-2.json to the same directory.
301
+ - It then automatically reads both report-1.json and report-2.json, merges them, and updates the main playwright-pulse-report.json with the combined results.
302
+
303
+ ***This ensures your final report is always a complete summary of all sequential test runs.***
304
+
305
+ ---
306
+
311
307
  ## 📬 Support
312
308
 
313
309
  For issues or feature requests, please [Contact Me](mailto:arghajitsingha47@gmail.com).
@@ -45,11 +45,10 @@ const ATTACHMENTS_SUBDIR = "attachments"; // Consistent subdirectory name
45
45
  * @param config The reporter configuration options.
46
46
  */
47
47
  function attachFiles(testId, pwResult, pulseResult, config) {
48
- const baseReportDir = config.outputDir || "pulse-report"; // Base output directory
49
- // Ensure attachments are relative to the main outputDir
50
- const attachmentsBaseDir = path.resolve(baseReportDir, ATTACHMENTS_SUBDIR); // Absolute path for FS operations
51
- const attachmentsSubFolder = testId.replace(/[^a-zA-Z0-9_-]/g, "_"); // Sanitize testId for folder name
52
- const testAttachmentsDir = path.join(attachmentsBaseDir, attachmentsSubFolder); // e.g., pulse-report/attachments/test_id_abc
48
+ const baseReportDir = config.outputDir || "pulse-report";
49
+ const attachmentsBaseDir = path.resolve(baseReportDir, ATTACHMENTS_SUBDIR);
50
+ const attachmentsSubFolder = testId.replace(/[^a-zA-Z0-9_-]/g, "_");
51
+ const testAttachmentsDir = path.join(attachmentsBaseDir, attachmentsSubFolder);
53
52
  try {
54
53
  if (!fs.existsSync(testAttachmentsDir)) {
55
54
  fs.mkdirSync(testAttachmentsDir, { recursive: true });
@@ -57,53 +56,49 @@ function attachFiles(testId, pwResult, pulseResult, config) {
57
56
  }
58
57
  catch (error) {
59
58
  console.error(`Pulse Reporter: Failed to create attachments directory: ${testAttachmentsDir}`, error);
60
- return; // Stop processing if directory creation fails
59
+ return;
61
60
  }
62
61
  if (!pwResult.attachments)
63
62
  return;
64
- const { base64Images } = config; // Get base64 embedding option
65
- pulseResult.screenshots = []; // Initialize screenshots array
63
+ const { base64Images } = config;
64
+ // --- MODIFICATION: Initialize all attachment arrays to prevent errors ---
65
+ pulseResult.screenshots = [];
66
+ pulseResult.videoPath = [];
67
+ pulseResult.attachments = [];
66
68
  pwResult.attachments.forEach((attachment) => {
67
69
  const { contentType, name, path: attachmentPath, body } = attachment;
68
- // Skip attachments without path or body
69
70
  if (!attachmentPath && !body) {
70
71
  console.warn(`Pulse Reporter: Attachment "${name}" for test ${testId} has no path or body. Skipping.`);
71
72
  return;
72
73
  }
73
- // Determine filename
74
- const safeName = name.replace(/[^a-zA-Z0-9_.-]/g, "_"); // Sanitize original name
74
+ const safeName = name.replace(/[^a-zA-Z0-9_.-]/g, "_");
75
75
  const extension = attachmentPath
76
76
  ? path.extname(attachmentPath)
77
77
  : `.${getFileExtension(contentType)}`;
78
78
  const baseFilename = attachmentPath
79
79
  ? path.basename(attachmentPath, extension)
80
80
  : safeName;
81
- // Ensure unique filename within the test's attachment folder
82
81
  const fileName = `${baseFilename}_${Date.now()}${extension}`;
83
- // Relative path for storing in JSON (relative to baseReportDir)
84
82
  const relativePath = path.join(ATTACHMENTS_SUBDIR, attachmentsSubFolder, fileName);
85
- // Full path for file system operations
86
83
  const fullPath = path.join(testAttachmentsDir, fileName);
87
84
  if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("image/")) {
88
- // Handle all image types consistently
89
85
  handleImage(attachmentPath, body, base64Images, fullPath, relativePath, pulseResult, name);
90
86
  }
91
87
  else if (name === "video" || (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("video/"))) {
92
- handleAttachment(attachmentPath, body, fullPath, relativePath, "videoPath", pulseResult);
88
+ handleAttachment(attachmentPath, body, fullPath, relativePath, "videoPath", pulseResult, attachment);
93
89
  }
94
90
  else if (name === "trace" || contentType === "application/zip") {
95
- // Trace files are zips
96
- handleAttachment(attachmentPath, body, fullPath, relativePath, "tracePath", pulseResult);
91
+ handleAttachment(attachmentPath, body, fullPath, relativePath, "tracePath", pulseResult, attachment);
97
92
  }
98
93
  else {
99
- // Handle other generic attachments if needed (e.g., log files)
100
- // console.log(`Pulse Reporter: Processing generic attachment "${name}" (Type: ${contentType}) for test ${testId}`);
101
- // handleAttachment(attachmentPath, body, fullPath, relativePath, 'otherAttachments', pulseResult); // Example for storing other types
94
+ // --- MODIFICATION: Enabled handling for all other file types ---
95
+ handleAttachment(attachmentPath, body, fullPath, relativePath, "attachments", pulseResult, attachment);
102
96
  }
103
97
  });
104
98
  }
105
99
  /**
106
100
  * Handles image attachments, either embedding as base64 or copying the file.
101
+ * (This function is unchanged)
107
102
  */
108
103
  function handleImage(attachmentPath, body, base64Embed, fullPath, relativePath, pulseResult, attachmentName) {
109
104
  let screenshotData = undefined;
@@ -123,14 +118,10 @@ function handleImage(attachmentPath, body, base64Embed, fullPath, relativePath,
123
118
  }
124
119
  }
125
120
  else if (body) {
126
- // Always embed if only body is available
127
121
  screenshotData = `data:image/${getFileExtension(attachmentName)};base64,${body.toString("base64")}`;
128
122
  if (!base64Embed) {
129
- // Optionally save the buffer to a file even if embedding is off,
130
- // but the primary representation will be base64.
131
123
  try {
132
124
  fs.writeFileSync(fullPath, body);
133
- // console.log(`Pulse Reporter: Saved screenshot buffer to ${fullPath}`);
134
125
  }
135
126
  catch (error) {
136
127
  console.error(`Pulse Reporter: Failed to save screenshot buffer: ${fullPath}. Error: ${error.message}`);
@@ -147,21 +138,36 @@ function handleImage(attachmentPath, body, base64Embed, fullPath, relativePath,
147
138
  /**
148
139
  * Handles non-image attachments by copying the file or writing the buffer.
149
140
  */
150
- function handleAttachment(attachmentPath, body, fullPath, relativePath, resultKey, // Add more keys if needed
151
- pulseResult) {
141
+ function handleAttachment(attachmentPath, body, fullPath, relativePath, resultKey, // MODIFIED: Added 'attachments'
142
+ pulseResult, originalAttachment // MODIFIED: Pass original attachment
143
+ ) {
144
+ var _a, _b;
152
145
  try {
153
146
  if (attachmentPath) {
154
147
  fs.copyFileSync(attachmentPath, fullPath);
155
- pulseResult[resultKey] = relativePath;
156
148
  }
157
149
  else if (body) {
158
150
  fs.writeFileSync(fullPath, body);
159
- pulseResult[resultKey] = relativePath; // Store relative path even if from buffer
151
+ }
152
+ // --- MODIFICATION: Logic to handle different properties correctly ---
153
+ switch (resultKey) {
154
+ case "videoPath":
155
+ (_a = pulseResult.videoPath) === null || _a === void 0 ? void 0 : _a.push(relativePath);
156
+ break;
157
+ case "tracePath":
158
+ pulseResult.tracePath = relativePath;
159
+ break;
160
+ case "attachments":
161
+ (_b = pulseResult.attachments) === null || _b === void 0 ? void 0 : _b.push({
162
+ name: originalAttachment.name,
163
+ path: relativePath,
164
+ contentType: originalAttachment.contentType,
165
+ });
166
+ break;
160
167
  }
161
168
  }
162
169
  catch (error) {
163
170
  console.error(`Pulse Reporter: Failed to copy/write attachment to ${fullPath}. Error: ${error.message}`);
164
- // Don't set the path in pulseResult if saving failed
165
171
  }
166
172
  }
167
173
  /**
@@ -172,8 +178,7 @@ pulseResult) {
172
178
  function getFileExtension(contentType) {
173
179
  var _a;
174
180
  if (!contentType)
175
- return "bin"; // Default binary extension
176
- // More robust mapping
181
+ return "bin";
177
182
  const extensions = {
178
183
  "image/png": "png",
179
184
  "image/jpeg": "jpg",
@@ -182,9 +187,12 @@ function getFileExtension(contentType) {
182
187
  "image/svg+xml": "svg",
183
188
  "video/webm": "webm",
184
189
  "video/mp4": "mp4",
185
- "application/zip": "zip", // For traces
190
+ "application/zip": "zip",
186
191
  "text/plain": "txt",
187
192
  "application/json": "json",
193
+ "text/html": "html",
194
+ "application/pdf": "pdf",
195
+ "text/csv": "csv",
188
196
  };
189
197
  return (extensions[contentType.toLowerCase()] ||
190
198
  ((_a = contentType.split("/")[1]) === null || _a === void 0 ? void 0 : _a.split("+")[0]) ||
@@ -11,6 +11,7 @@ export declare class PlaywrightPulseReporter implements Reporter {
11
11
  private baseOutputFile;
12
12
  private isSharded;
13
13
  private shardIndex;
14
+ private resetOnEachRun;
14
15
  constructor(options?: PlaywrightPulseReporterOptions);
15
16
  printsToStdio(): boolean;
16
17
  onBegin(config: FullConfig, suite: Suite): void;
@@ -18,6 +19,7 @@ export declare class PlaywrightPulseReporter implements Reporter {
18
19
  private getBrowserDetails;
19
20
  private processStep;
20
21
  onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
22
+ private _getFinalizedResults;
21
23
  onError(error: any): void;
22
24
  private _getEnvDetails;
23
25
  private _writeShardResults;
@@ -25,5 +27,6 @@ export declare class PlaywrightPulseReporter implements Reporter {
25
27
  private _cleanupTemporaryFiles;
26
28
  private _ensureDirExists;
27
29
  onEnd(result: FullResult): Promise<void>;
30
+ private _mergeAllRunReports;
28
31
  }
29
32
  export default PlaywrightPulseReporter;