@arghajit/playwright-pulse-report 0.1.0 → 0.1.1

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
@@ -3,14 +3,25 @@
3
3
 
4
4
  This project provides both a custom Playwright reporter and a Next.js web dashboard to visualize your Playwright test results, now with support for **Playwright sharding** and an option for a **standalone HTML report**.
5
5
 
6
+ ## Screenshots
7
+
8
+ ### Desktop View [Click on Images to View full Image]
9
+ <a href="https://postimg.cc/180cym6c" target="_blank"><img src="https://i.postimg.cc/180cym6c/Users-arghajitsingha-Downloads-pulse-report-1-playwright-pulse-static-report-html.png" alt="Users-arghajitsingha-Downloads-pulse-report-1-playwright-pulse-static-report-html"/></a><br/><br/>
10
+ <a href="https://postimg.cc/V5TFRHmM" target="_blank"><img src="https://i.postimg.cc/V5TFRHmM/Users-arghajitsingha-Downloads-pulse-report-1-playwright-pulse-static-report-html-1.png" alt="Users-arghajitsingha-Downloads-pulse-report-1-playwright-pulse-static-report-html-1"/></a><br/><br/>
11
+ <a href="https://postimg.cc/XXTwFGkk" target="_blank"><img src="https://i.postimg.cc/XXTwFGkk/Users-arghajitsingha-Downloads-pulse-report-1-playwright-pulse-static-report-html-2.png" alt="Users-arghajitsingha-Downloads-pulse-report-1-playwright-pulse-static-report-html-2"/></a><br/><br/>
12
+
13
+ ### Mobile View [Click on Images to View full Image]
14
+ <a href="https://postimg.cc/CzJBLR5N" target="_blank"><img src="https://i.postimg.cc/CzJBLR5N/127-0-0-1-5500-pulse-report-output-playwright-pulse-static-report-html-i-Phone-14-Pro-Max.png" alt="127-0-0-1-5500-pulse-report-output-playwright-pulse-static-report-html-i-Phone-14-Pro-Max"/></a><br/><br/>
15
+ <a href="https://postimg.cc/G8YTczT8" target="_blank"><img src="https://i.postimg.cc/G8YTczT8/127-0-0-1-5500-pulse-report-output-playwright-pulse-static-report-html-i-Phone-14-Pro-Max-1.png" alt="127-0-0-1-5500-pulse-report-output-playwright-pulse-static-report-html-i-Phone-14-Pro-Max-1"/></a><br/><br/>
16
+
6
17
  ## How it Works
7
18
 
8
19
  1. **Reporter (`playwright-pulse-reporter.ts`):**
9
20
  * A custom reporter that collects detailed results during your Playwright test run.
10
21
  * **Sharding Support:** If tests are sharded, each shard process writes its results to a temporary file (`.pulse-shard-results-*.json`) in the specified output directory. The main reporter process then merges these files upon completion.
11
- 2. **JSON Output:** On completion (`onEnd`), the reporter writes all collected (and potentially merged) data into a single `playwright-pulse-report.json` file in your project's specified output directory (defaults to `pulse-report-output` in the project root).
22
+ 2. **JSON Output:** On completion (`onEnd`), the reporter writes all collected (and potentially merged) data into a single `playwright-pulse-report.json` file in your project's specified output directory (defaults to `pulse-report` in the project root).
12
23
  3. **Next.js Dashboard (Option 2 - Interactive):** A web application built with Next.js that reads the final `playwright-pulse-report.json` file from the *dashboard project's root* and presents the test results in a dynamic, interactive dashboard interface.
13
- 4. **Standalone HTML Report (Option 1 - Static):** A script (`generate-static-report.mjs`) that reads the `playwright-pulse-report.json` from the *Playwright project's output directory* (e.g., `pulse-report-output`) and generates a single, self-contained `playwright-pulse-static-report.html` file in the *same directory*. This static report mimics the key information and layout of the dashboard, including:
24
+ 4. **Standalone HTML Report (Option 1 - Static):** A script (`generate-static-report.mjs`) that reads the `playwright-pulse-report.json` from the *Playwright project's output directory* (e.g., `pulse-report`) and generates a single, self-contained `playwright-pulse-static-report.html` file in the *same directory*. This static report mimics the key information and layout of the dashboard, including:
14
25
  * Run summary metrics (Total, Passed, Failed, Skipped, Duration).
15
26
  * Filtering controls for test results.
16
27
  * List of individual test results with status, duration, name, suite, and errors.
@@ -42,7 +53,7 @@ import { defineConfig, devices } from '@playwright/test';
42
53
  import * as path from 'path';
43
54
 
44
55
  // Define where the final report JSON and HTML should go
45
- const PULSE_REPORT_DIR = path.resolve(__dirname, 'pulse-report-output'); // Example: a directory in your project root
56
+ const PULSE_REPORT_DIR = path.resolve(__dirname, 'pulse-report'); // Example: a directory in your project root
46
57
 
47
58
  export default defineConfig({
48
59
  // ... other configurations like projects, testDir, etc.
@@ -77,7 +88,7 @@ export default defineConfig({
77
88
  **Explanation:**
78
89
 
79
90
  * `outputDir` in the main `defineConfig`: This is where Playwright stores its own artifacts like traces and screenshots.
80
- * `outputDir` inside the `@arghajit/playwright-pulse-reporter` options: This tells *our reporter* where to save the final `playwright-pulse-report.json`. Using a dedicated directory like `pulse-report-output` is **required** for the reporter and static report generation to work correctly.
91
+ * `outputDir` inside the `@arghajit/playwright-pulse-reporter` options: This tells *our reporter* where to save the final `playwright-pulse-report.json`. Using a dedicated directory like `pulse-report` is **required** for the reporter and static report generation to work correctly.
81
92
 
82
93
  ### 3. Run Your Tests
83
94
 
@@ -89,7 +100,7 @@ npx playwright test
89
100
  # npx playwright test --project=chromium --shard=1/3
90
101
  ```
91
102
 
92
- The `@arghajit/playwright-pulse-reporter` will automatically handle sharding if Playwright is configured to use it. Upon completion, the final `playwright-pulse-report.json` will be generated in the directory you specified (e.g., `pulse-report-output`).
103
+ The `@arghajit/playwright-pulse-reporter` will automatically handle sharding if Playwright is configured to use it. Upon completion, the final `playwright-pulse-report.json` will be generated in the directory you specified (e.g., `pulse-report`).
93
104
 
94
105
  ### 4. Generate the Static HTML Report (Option 1)
95
106
 
@@ -100,8 +111,8 @@ After your tests run and `playwright-pulse-report.json` is created, you can gene
100
111
  ```bash
101
112
  npx generate-pulse-report
102
113
  ```
103
- This command executes the `scripts/generate-static-report.mjs` script included in the `@arghajit/playwright-pulse-reporter` package. It reads the `pulse-report-output/playwright-pulse-report.json` file (relative to your current directory) and creates `pulse-report-output/playwright-pulse-static-report.html`.
104
- 3. **Open the HTML file:** Open the generated `pulse-report-output/playwright-pulse-static-report.html` in your browser.
114
+ This command executes the `scripts/generate-static-report.mjs` script included in the `@arghajit/playwright-pulse-reporter` package. It reads the `pulse-report/playwright-pulse-report.json` file (relative to your current directory) and creates `pulse-report/playwright-pulse-static-report.html`.
115
+ 3. **Open the HTML file:** Open the generated `pulse-report/playwright-pulse-static-report.html` in your browser.
105
116
 
106
117
  This HTML file is self-contained and provides a detailed, interactive dashboard-like overview suitable for sharing or archiving.
107
118
 
@@ -118,10 +129,10 @@ This HTML file is self-contained and provides a detailed, interactive dashboard-
118
129
  npm install
119
130
  # or yarn install or pnpm install
120
131
  ```
121
- 3. **Copy the Report File:** Copy the `playwright-pulse-report.json` file generated by your tests (e.g., from your main project's `pulse-report-output` directory) into the **root directory** of *this dashboard project*.
132
+ 3. **Copy the Report File:** Copy the `playwright-pulse-report.json` file generated by your tests (e.g., from your main project's `pulse-report` directory) into the **root directory** of *this dashboard project*.
122
133
  ```bash
123
134
  # Example: Copying from your main project to the dashboard project directory
124
- cp ../my-playwright-project/pulse-report-output/playwright-pulse-report.json ./
135
+ cp ../my-playwright-project/pulse-report/playwright-pulse-report.json ./
125
136
  ```
126
137
  **Tip for Development:** You can also use the `sample-report.json` file included in this project for development:
127
138
  ```bash
@@ -169,9 +180,9 @@ To work on the reporter or the dashboard itself:
169
180
  ```
170
181
  5. **Test static report generation:**
171
182
  ```bash
172
- # 1. Ensure playwright-pulse-report.json exists in pulse-report-output
173
- mkdir -p pulse-report-output
174
- cp sample-report.json pulse-report-output/playwright-pulse-report.json
183
+ # 1. Ensure playwright-pulse-report.json exists in pulse-report
184
+ mkdir -p pulse-report
185
+ cp sample-report.json pulse-report/playwright-pulse-report.json
175
186
  # 2. Run the generation script directly using node (or via the bin command)
176
187
  node ./scripts/generate-static-report.mjs
177
188
  # or
@@ -186,7 +197,93 @@ To work on the reporter or the dashboard itself:
186
197
  * `src/lib/data-reader.ts`: Server-side logic for reading the JSON report file (used by Next.js dashboard).
187
198
  * `src/lib/data.ts`: Data fetching functions used by the Next.js dashboard components.
188
199
  * `src/app/`: Contains the Next.js dashboard pages and components.
189
- * `pulse-report-output/playwright-pulse-report.json`: (Generated by the reporter in the *user's project*) The primary data source.
190
- * `pulse-report-output/playwright-pulse-static-report.html`: (Generated by the script in the *user's project*) The standalone HTML report.
200
+ * `pulse-report/playwright-pulse-report.json`: (Generated by the reporter in the *user's project*) The primary data source.
201
+ * `pulse-report/playwright-pulse-static-report.html`: (Generated by the script in the *user's project*) The standalone HTML report.
191
202
  * `playwright-pulse-report.json`: (Manually copied to the *dashboard project root*) Used by the Next.js dashboard.
192
203
  * `sample-report.json`: (Included in this project) Dummy data for development/testing visualization.
204
+
205
+
206
+ ## 📦 CI/CD: Playwright Pulse Report
207
+
208
+ * This project supports Playwright test execution with Pulse Reporting in GitHub Actions. Here's how Pulse reports are managed:
209
+
210
+ ```
211
+ # Upload Pulse report from each shard (per matrix.config.type)
212
+ - name: Upload Pulse Report results
213
+ if: success() || failure()
214
+ uses: actions/upload-artifact@v4
215
+ with:
216
+ name: pulse-report
217
+ path: pulse-report/
218
+
219
+ # Download all pulse-report-* artifacts after all shards complete
220
+ - name: Download Pulse Report artifacts
221
+ uses: actions/download-artifact@v4
222
+ with:
223
+ pattern: pulse-report
224
+ path: downloaded-artifacts
225
+
226
+ # Merge all sharded JSON reports into one final output
227
+ - name: Generate Pulse Report
228
+ run: |
229
+ npm run script merge-report
230
+ npm run script generate-report
231
+
232
+ # Upload final merged report as CI artifact
233
+ - name: Upload Pulse report
234
+ uses: actions/upload-artifact@v4
235
+ with:
236
+ name: pulse-report
237
+ path: pulse-report/
238
+ ```
239
+
240
+ ## 📦 CI/CD: Playwright Pulse Report (with Sharding Support)
241
+
242
+ * This project supports sharded Playwright test execution with Pulse Reporting in GitHub Actions. Here's how Pulse reports are managed across shards:
243
+
244
+ ```
245
+ # Upload Pulse report from each shard (per matrix.config.type)
246
+ - name: Upload Pulse Report results
247
+ if: success() || failure()
248
+ uses: actions/upload-artifact@v4
249
+ with:
250
+ name: pulse-report-${{ matrix.config.type }}
251
+ path: pulse-report/
252
+
253
+ # Download all pulse-report-* artifacts after all shards complete
254
+ - name: Download Pulse Report artifacts
255
+ uses: actions/download-artifact@v4
256
+ with:
257
+ pattern: pulse-report-*
258
+ path: downloaded-artifacts
259
+
260
+ # Organize reports into a single folder and rename for merging
261
+ - name: Organize Pulse Report
262
+ run: |
263
+ mkdir -p pulse-report
264
+ for dir in downloaded-artifacts/pulse-report-*; do
265
+ config_type=$(basename "$dir" | sed 's/pulse-report-//')
266
+ cp -r "$dir/attachments" "pulse-report/attachments"
267
+ cp "$dir/playwright-pulse-report.json" "pulse-report/playwright-pulse-report-${config_type}.json"
268
+ done
269
+
270
+ # Merge all sharded JSON reports into one final output
271
+ - name: Generate Pulse Report
272
+ run: |
273
+ npm run script merge-report
274
+ npm run script generate-report
275
+
276
+ # Upload final merged report as CI artifact
277
+ - name: Upload Pulse report
278
+ uses: actions/upload-artifact@v4
279
+ with:
280
+ name: pulse-report
281
+ path: pulse-report/
282
+ ```
283
+ ## 🧠 Notes:
284
+
285
+ * Each shard generates its own playwright-pulse-report.json inside pulse-report/.
286
+ * Artifacts are named using the shard type (matrix.config.type).
287
+ * After the test matrix completes, reports are downloaded, renamed, and merged.
288
+ * merge-report is a custom Node.js script that combines all JSON files into one.
289
+ * generate-report can build a static HTML dashboard if needed.
@@ -75,8 +75,8 @@ class PlaywrightPulseReporter {
75
75
  }
76
76
  const configDir = this.config.rootDir;
77
77
  this.outputDir = this.outputDir
78
- ? path.resolve(configDir, this.outputDir)
79
- : path.resolve(configDir, "pulse-report-output");
78
+ ? path.resolve(configDir, this.outputDir)
79
+ : path.resolve(configDir, "pulse-report");
80
80
  console.log(`PlaywrightPulseReporter: Final Output dir resolved to ${this.outputDir}`);
81
81
  if (this.shardIndex === undefined) {
82
82
  // Main process
@@ -45,62 +45,100 @@ 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-output"; // 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-output/attachments/test_id_abc
53
- try {
54
- if (!fs.existsSync(testAttachmentsDir)) {
55
- fs.mkdirSync(testAttachmentsDir, { recursive: true });
56
- }
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(
53
+ attachmentsBaseDir,
54
+ attachmentsSubFolder
55
+ ); // e.g., pulse-report/attachments/test_id_abc
56
+ try {
57
+ if (!fs.existsSync(testAttachmentsDir)) {
58
+ fs.mkdirSync(testAttachmentsDir, { recursive: true });
57
59
  }
58
- catch (error) {
59
- console.error(`Pulse Reporter: Failed to create attachments directory: ${testAttachmentsDir}`, error);
60
- return; // Stop processing if directory creation fails
60
+ } catch (error) {
61
+ console.error(
62
+ `Pulse Reporter: Failed to create attachments directory: ${testAttachmentsDir}`,
63
+ error
64
+ );
65
+ return; // Stop processing if directory creation fails
66
+ }
67
+ if (!pwResult.attachments) return;
68
+ const { base64Images } = config; // Get base64 embedding option
69
+ pulseResult.screenshots = []; // Initialize screenshots array
70
+ pwResult.attachments.forEach((attachment) => {
71
+ const { contentType, name, path: attachmentPath, body } = attachment;
72
+ // Skip attachments without path or body
73
+ if (!attachmentPath && !body) {
74
+ console.warn(
75
+ `Pulse Reporter: Attachment "${name}" for test ${testId} has no path or body. Skipping.`
76
+ );
77
+ return;
61
78
  }
62
- if (!pwResult.attachments)
63
- return;
64
- const { base64Images } = config; // Get base64 embedding option
65
- pulseResult.screenshots = []; // Initialize screenshots array
66
- pwResult.attachments.forEach((attachment) => {
67
- const { contentType, name, path: attachmentPath, body } = attachment;
68
- // Skip attachments without path or body
69
- if (!attachmentPath && !body) {
70
- console.warn(`Pulse Reporter: Attachment "${name}" for test ${testId} has no path or body. Skipping.`);
71
- return;
72
- }
73
- // Determine filename
74
- const safeName = name.replace(/[^a-zA-Z0-9_.-]/g, "_"); // Sanitize original name
75
- const extension = attachmentPath
76
- ? path.extname(attachmentPath)
77
- : `.${getFileExtension(contentType)}`;
78
- const baseFilename = attachmentPath
79
- ? path.basename(attachmentPath, extension)
80
- : safeName;
81
- // Ensure unique filename within the test's attachment folder
82
- const fileName = `${baseFilename}_${Date.now()}${extension}`;
83
- // Relative path for storing in JSON (relative to baseReportDir)
84
- const relativePath = path.join(ATTACHMENTS_SUBDIR, attachmentsSubFolder, fileName);
85
- // Full path for file system operations
86
- const fullPath = path.join(testAttachmentsDir, fileName);
87
- if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("image/")) {
88
- // Handle all image types consistently
89
- handleImage(attachmentPath, body, base64Images, fullPath, relativePath, pulseResult, name);
90
- }
91
- else if (name === "video" || (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("video/"))) {
92
- handleAttachment(attachmentPath, body, fullPath, relativePath, "videoPath", pulseResult);
93
- }
94
- else if (name === "trace" || contentType === "application/zip") {
95
- // Trace files are zips
96
- handleAttachment(attachmentPath, body, fullPath, relativePath, "tracePath", pulseResult);
97
- }
98
- 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
102
- }
103
- });
79
+ // Determine filename
80
+ const safeName = name.replace(/[^a-zA-Z0-9_.-]/g, "_"); // Sanitize original name
81
+ const extension = attachmentPath
82
+ ? path.extname(attachmentPath)
83
+ : `.${getFileExtension(contentType)}`;
84
+ const baseFilename = attachmentPath
85
+ ? path.basename(attachmentPath, extension)
86
+ : safeName;
87
+ // Ensure unique filename within the test's attachment folder
88
+ const fileName = `${baseFilename}_${Date.now()}${extension}`;
89
+ // Relative path for storing in JSON (relative to baseReportDir)
90
+ const relativePath = path.join(
91
+ ATTACHMENTS_SUBDIR,
92
+ attachmentsSubFolder,
93
+ fileName
94
+ );
95
+ // Full path for file system operations
96
+ const fullPath = path.join(testAttachmentsDir, fileName);
97
+ if (
98
+ contentType === null || contentType === void 0
99
+ ? void 0
100
+ : contentType.startsWith("image/")
101
+ ) {
102
+ // Handle all image types consistently
103
+ handleImage(
104
+ attachmentPath,
105
+ body,
106
+ base64Images,
107
+ fullPath,
108
+ relativePath,
109
+ pulseResult,
110
+ name
111
+ );
112
+ } else if (
113
+ name === "video" ||
114
+ (contentType === null || contentType === void 0
115
+ ? void 0
116
+ : contentType.startsWith("video/"))
117
+ ) {
118
+ handleAttachment(
119
+ attachmentPath,
120
+ body,
121
+ fullPath,
122
+ relativePath,
123
+ "videoPath",
124
+ pulseResult
125
+ );
126
+ } else if (name === "trace" || contentType === "application/zip") {
127
+ // Trace files are zips
128
+ handleAttachment(
129
+ attachmentPath,
130
+ body,
131
+ fullPath,
132
+ relativePath,
133
+ "tracePath",
134
+ pulseResult
135
+ );
136
+ } else {
137
+ // Handle other generic attachments if needed (e.g., log files)
138
+ // console.log(`Pulse Reporter: Processing generic attachment "${name}" (Type: ${contentType}) for test ${testId}`);
139
+ // handleAttachment(attachmentPath, body, fullPath, relativePath, 'otherAttachments', pulseResult); // Example for storing other types
140
+ }
141
+ });
104
142
  }
105
143
  /**
106
144
  * Handles image attachments, either embedding as base64 or copying the file.
@@ -72,7 +72,10 @@ class PlaywrightPulseReporter {
72
72
  this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
73
73
  // Determine outputDir relative to config file or rootDir
74
74
  // The actual resolution happens in onBegin where config is available
75
- this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report-output";
75
+ this.outputDir =
76
+ (_b = options.outputDir) !== null && _b !== void 0
77
+ ? _b
78
+ : "pulse-report";
76
79
  this.attachmentsDir = path.join(this.outputDir, ATTACHMENTS_SUBDIR); // Initial path, resolved fully in onBegin
77
80
  // console.log(`Pulse Reporter Init: Configured outputDir option: ${options.outputDir}, Base file: ${this.baseOutputFile}`);
78
81
  }
@@ -90,7 +93,12 @@ class PlaywrightPulseReporter {
90
93
  const configFileDir = this.config.configFile
91
94
  ? path.dirname(this.config.configFile)
92
95
  : configDir;
93
- this.outputDir = path.resolve(configFileDir, (_a = this.options.outputDir) !== null && _a !== void 0 ? _a : "pulse-report-output");
96
+ this.outputDir = path.resolve(
97
+ configFileDir,
98
+ (_a = this.options.outputDir) !== null && _a !== void 0
99
+ ? _a
100
+ : "pulse-report"
101
+ );
94
102
  // Resolve attachmentsDir relative to the final outputDir
95
103
  this.attachmentsDir = path.resolve(this.outputDir, ATTACHMENTS_SUBDIR);
96
104
  // Update options with the resolved absolute path for internal use
@@ -76,33 +76,51 @@ class PlaywrightPulseReporter {
76
76
  return this.shardIndex === undefined || this.shardIndex === 0;
77
77
  }
78
78
  onBegin(config, suite) {
79
- this.config = config;
80
- this.suite = suite;
81
- this.runStartTime = Date.now();
82
- // Determine sharding configuration
83
- const totalShards = parseInt(process.env.PLAYWRIGHT_SHARD_TOTAL || "1", 10);
84
- this.isSharded = totalShards > 1;
85
- if (process.env.PLAYWRIGHT_SHARD_INDEX !== undefined) {
86
- this.shardIndex = parseInt(process.env.PLAYWRIGHT_SHARD_INDEX, 10);
87
- }
88
- // Resolve outputDir relative to playwright config directory if possible, otherwise use cwd
89
- // This needs the config object, so it's done in onBegin
90
- const configDir = this.config.rootDir; // Playwright config directory
91
- // Use outputDir from options if provided and resolve it relative to configDir, otherwise default
92
- this.outputDir = this.outputDir
93
- ? path.resolve(configDir, this.outputDir)
94
- : path.resolve(configDir, "pulse-report-output"); // Default to 'pulse-report-output' relative to config
95
- console.log(`PlaywrightPulseReporter: Final Output dir resolved to ${this.outputDir}`);
96
- if (this.shardIndex === undefined) {
97
- // Main process
98
- console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Outputting to ${this.outputDir}`);
99
- // Clean up any leftover temp files from previous runs in the main process
100
- this._cleanupTemporaryFiles().catch((err) => console.error("Pulse Reporter: Error cleaning up temp files:", err));
101
- }
102
- else {
103
- // Shard process
104
- console.log(`PlaywrightPulseReporter: Shard ${this.shardIndex + 1}/${totalShards} starting. Outputting temp results to ${this.outputDir}`);
105
- }
79
+ this.config = config;
80
+ this.suite = suite;
81
+ this.runStartTime = Date.now();
82
+ // Determine sharding configuration
83
+ const totalShards = parseInt(
84
+ process.env.PLAYWRIGHT_SHARD_TOTAL || "1",
85
+ 10
86
+ );
87
+ this.isSharded = totalShards > 1;
88
+ if (process.env.PLAYWRIGHT_SHARD_INDEX !== undefined) {
89
+ this.shardIndex = parseInt(process.env.PLAYWRIGHT_SHARD_INDEX, 10);
90
+ }
91
+ // Resolve outputDir relative to playwright config directory if possible, otherwise use cwd
92
+ // This needs the config object, so it's done in onBegin
93
+ const configDir = this.config.rootDir; // Playwright config directory
94
+ // Use outputDir from options if provided and resolve it relative to configDir, otherwise default
95
+ this.outputDir = this.outputDir
96
+ ? path.resolve(configDir, this.outputDir)
97
+ : path.resolve(configDir, "pulse-report"); // Default to 'pulse-report' relative to config
98
+ console.log(
99
+ `PlaywrightPulseReporter: Final Output dir resolved to ${this.outputDir}`
100
+ );
101
+ if (this.shardIndex === undefined) {
102
+ // Main process
103
+ console.log(
104
+ `PlaywrightPulseReporter: Starting test run with ${
105
+ suite.allTests().length
106
+ } tests${
107
+ this.isSharded ? ` across ${totalShards} shards` : ""
108
+ }. Outputting to ${this.outputDir}`
109
+ );
110
+ // Clean up any leftover temp files from previous runs in the main process
111
+ this._cleanupTemporaryFiles().catch((err) =>
112
+ console.error("Pulse Reporter: Error cleaning up temp files:", err)
113
+ );
114
+ } else {
115
+ // Shard process
116
+ console.log(
117
+ `PlaywrightPulseReporter: Shard ${
118
+ this.shardIndex + 1
119
+ }/${totalShards} starting. Outputting temp results to ${
120
+ this.outputDir
121
+ }`
122
+ );
123
+ }
106
124
  }
107
125
  onTestBegin(test) {
108
126
  // Optional: Log test start (maybe only in main process or first shard?)
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.0",
4
+ "version": "0.1.1",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "keywords": [
7
7
  "playwright",
@@ -21,7 +21,8 @@
21
21
  ],
22
22
  "license": "MIT",
23
23
  "bin": {
24
- "generate-pulse-report": "./scripts/generate-static-report.mjs"
24
+ "generate-pulse-report": "./scripts/generate-static-report.mjs",
25
+ "merge-pulse-report": "./scripts/merge-pulse-report.js"
25
26
  },
26
27
  "exports": {
27
28
  ".": {
@@ -21,7 +21,7 @@ try {
21
21
  }
22
22
 
23
23
  // Default configuration
24
- const DEFAULT_OUTPUT_DIR = "pulse-report-output";
24
+ const DEFAULT_OUTPUT_DIR = "pulse-report";
25
25
  const DEFAULT_JSON_FILE = "playwright-pulse-report.json";
26
26
  const DEFAULT_HTML_FILE = "playwright-pulse-static-report.html";
27
27
 
@@ -267,9 +267,6 @@ function generateSuitesWidget(suitesData) {
267
267
  suite.count !== 1 ? "s" : ""
268
268
  }</span>
269
269
  </div>
270
- <span class="browser-name">${suite.name
271
- .split(" (")[1]
272
- .replace(")", "")}</span>
273
270
  </div>
274
271
  `
275
272
  )
@@ -1536,4 +1533,4 @@ async function main() {
1536
1533
  }
1537
1534
 
1538
1535
  // Run the main function
1539
- main();
1536
+ main();
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const REPORT_DIR = "./pulse-report"; // Or change this to your reports directory
7
+ const OUTPUT_FILE = "playwright-pulse-report.json";
8
+
9
+ function getReportFiles(dir) {
10
+ return fs
11
+ .readdirSync(dir)
12
+ .filter(
13
+ (file) =>
14
+ file.startsWith("playwright-pulse-report-") && file.endsWith(".json")
15
+ );
16
+ }
17
+
18
+ function mergeReports(files) {
19
+ let combinedRun = {
20
+ totalTests: 0,
21
+ passed: 0,
22
+ failed: 0,
23
+ skipped: 0,
24
+ duration: 0,
25
+ };
26
+
27
+ let combinedResults = [];
28
+
29
+ let latestTimestamp = "";
30
+ let latestGeneratedAt = "";
31
+
32
+ for (const file of files) {
33
+ const filePath = path.join(REPORT_DIR, file);
34
+ const json = JSON.parse(fs.readFileSync(filePath, "utf-8"));
35
+
36
+ const run = json.run || {};
37
+ combinedRun.totalTests += run.totalTests || 0;
38
+ combinedRun.passed += run.passed || 0;
39
+ combinedRun.failed += run.failed || 0;
40
+ combinedRun.skipped += run.skipped || 0;
41
+ combinedRun.duration += run.duration || 0;
42
+
43
+ if (json.results) {
44
+ combinedResults.push(...json.results);
45
+ }
46
+
47
+ if (run.timestamp > latestTimestamp) latestTimestamp = run.timestamp;
48
+ if (json.metadata?.generatedAt > latestGeneratedAt)
49
+ latestGeneratedAt = json.metadata.generatedAt;
50
+ }
51
+
52
+ const finalJson = {
53
+ run: {
54
+ id: `merged-${Date.now()}`,
55
+ timestamp: latestTimestamp,
56
+ ...combinedRun,
57
+ },
58
+ results: combinedResults,
59
+ metadata: {
60
+ generatedAt: latestGeneratedAt,
61
+ },
62
+ };
63
+
64
+ return finalJson;
65
+ }
66
+
67
+ // Main execution
68
+ const reportFiles = getReportFiles(REPORT_DIR);
69
+
70
+ if (reportFiles.length === 0) {
71
+ console.log("No matching JSON report files found.");
72
+ process.exit(1);
73
+ }
74
+
75
+ const merged = mergeReports(reportFiles);
76
+
77
+ fs.writeFileSync(
78
+ path.join(REPORT_DIR, OUTPUT_FILE),
79
+ JSON.stringify(merged, null, 2)
80
+ );
81
+ console.log(`✅ Merged report saved as ${OUTPUT_FILE}`);