@arghajit/playwright-pulse-report 0.1.0 → 0.1.2

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,99 @@ 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
+ ```bash
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
+ ```bash
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
+
284
+ ## 🧠 Notes:
285
+
286
+ * Each shard generates its own playwright-pulse-report.json inside pulse-report/.
287
+ * Artifacts are named using the shard type (matrix.config.type).
288
+ * After the test matrix completes, reports are downloaded, renamed, and merged.
289
+ * merge-report is a custom Node.js script that combines all JSON files into one.
290
+ * generate-report can build a static HTML dashboard if needed.
291
+
292
+ ## Fixes:
293
+
294
+ ### - "0.1.1" : Added Sharding Support
295
+ ### - "0.1.2" : Fixed browser filter and Added Browser Tag in Test Suite Card
@@ -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,11 +45,11 @@ 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
48
+ const baseReportDir = config.outputDir || "pulse-report"; // Base output directory
49
49
  // Ensure attachments are relative to the main outputDir
50
50
  const attachmentsBaseDir = path.resolve(baseReportDir, ATTACHMENTS_SUBDIR); // Absolute path for FS operations
51
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
52
+ const testAttachmentsDir = path.join(attachmentsBaseDir, attachmentsSubFolder); // e.g., pulse-report/attachments/test_id_abc
53
53
  try {
54
54
  if (!fs.existsSync(testAttachmentsDir)) {
55
55
  fs.mkdirSync(testAttachmentsDir, { recursive: true });
@@ -72,7 +72,7 @@ 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 = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
76
76
  this.attachmentsDir = path.join(this.outputDir, ATTACHMENTS_SUBDIR); // Initial path, resolved fully in onBegin
77
77
  // console.log(`Pulse Reporter Init: Configured outputDir option: ${options.outputDir}, Base file: ${this.baseOutputFile}`);
78
78
  }
@@ -90,7 +90,7 @@ class PlaywrightPulseReporter {
90
90
  const configFileDir = this.config.configFile
91
91
  ? path.dirname(this.config.configFile)
92
92
  : configDir;
93
- this.outputDir = path.resolve(configFileDir, (_a = this.options.outputDir) !== null && _a !== void 0 ? _a : "pulse-report-output");
93
+ this.outputDir = path.resolve(configFileDir, (_a = this.options.outputDir) !== null && _a !== void 0 ? _a : "pulse-report");
94
94
  // Resolve attachmentsDir relative to the final outputDir
95
95
  this.attachmentsDir = path.resolve(this.outputDir, ATTACHMENTS_SUBDIR);
96
96
  // Update options with the resolved absolute path for internal use
@@ -121,7 +121,7 @@ class PlaywrightPulseReporter {
121
121
  // Optional: Log test start if needed
122
122
  // console.log(`Starting test: ${test.title}`);
123
123
  }
124
- async processStep(step, testId) {
124
+ async processStep(step, testId, browserName) {
125
125
  var _a, _b, _c, _d;
126
126
  // Determine actual step status (don't inherit from parent)
127
127
  let stepStatus = "passed";
@@ -148,6 +148,7 @@ class PlaywrightPulseReporter {
148
148
  duration: duration,
149
149
  startTime: startTime,
150
150
  endTime: endTime,
151
+ browser: browserName,
151
152
  errorMessage: errorMessage,
152
153
  stackTrace: ((_d = step.error) === null || _d === void 0 ? void 0 : _d.stack) || undefined,
153
154
  codeLocation: codeLocation || undefined,
@@ -161,7 +162,10 @@ class PlaywrightPulseReporter {
161
162
  };
162
163
  }
163
164
  async onTestEnd(test, result) {
164
- var _a, _b, _c, _d, _e, _f, _g;
165
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
166
+ // Get the most accurate browser name
167
+ const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
168
+ const browserName = ((_b = project === null || project === void 0 ? void 0 : project.use) === null || _b === void 0 ? void 0 : _b.defaultBrowserType) || "unknown";
165
169
  const testStatus = convertStatus(result.status, test);
166
170
  const startTime = new Date(result.startTime);
167
171
  const endTime = new Date(startTime.getTime() + result.duration);
@@ -175,7 +179,7 @@ class PlaywrightPulseReporter {
175
179
  const processAllSteps = async (steps, parentTestStatus) => {
176
180
  let processed = [];
177
181
  for (const step of steps) {
178
- const processedStep = await this.processStep(step, testIdForFiles);
182
+ const processedStep = await this.processStep(step, testIdForFiles, browserName);
179
183
  processed.push(processedStep);
180
184
  if (step.steps && step.steps.length > 0) {
181
185
  const nestedSteps = await processAllSteps(step.steps, processedStep.status);
@@ -188,7 +192,7 @@ class PlaywrightPulseReporter {
188
192
  // --- Extract Code Snippet ---
189
193
  let codeSnippet = undefined;
190
194
  try {
191
- if (((_a = test.location) === null || _a === void 0 ? void 0 : _a.file) && ((_b = test.location) === null || _b === void 0 ? void 0 : _b.line) && ((_c = test.location) === null || _c === void 0 ? void 0 : _c.column)) {
195
+ 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)) {
192
196
  const relativePath = path.relative(this.config.rootDir, test.location.file);
193
197
  codeSnippet = `Test defined at: ${relativePath}:${test.location.line}:${test.location.column}`;
194
198
  }
@@ -201,17 +205,18 @@ class PlaywrightPulseReporter {
201
205
  id: test.id || `${test.title}-${startTime.toISOString()}-${(0, crypto_1.randomUUID)()}`, // Use the original ID logic here
202
206
  runId: "TBD", // Will be set later
203
207
  name: test.titlePath().join(" > "),
204
- suiteName: ((_d = this.config.projects[0]) === null || _d === void 0 ? void 0 : _d.name) || "Default Suite",
208
+ suiteName: ((_f = this.config.projects[0]) === null || _f === void 0 ? void 0 : _f.name) || "Default Suite",
205
209
  status: testStatus,
206
210
  duration: result.duration,
207
211
  startTime: startTime,
208
212
  endTime: endTime,
213
+ browser: browserName,
209
214
  retries: result.retry,
210
- steps: ((_e = result.steps) === null || _e === void 0 ? void 0 : _e.length)
215
+ steps: ((_g = result.steps) === null || _g === void 0 ? void 0 : _g.length)
211
216
  ? await processAllSteps(result.steps, testStatus)
212
217
  : [],
213
- errorMessage: (_f = result.error) === null || _f === void 0 ? void 0 : _f.message,
214
- stackTrace: (_g = result.error) === null || _g === void 0 ? void 0 : _g.stack,
218
+ errorMessage: (_h = result.error) === null || _h === void 0 ? void 0 : _h.message,
219
+ stackTrace: (_j = result.error) === null || _j === void 0 ? void 0 : _j.stack,
215
220
  codeSnippet: codeSnippet,
216
221
  tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
217
222
  screenshots: [],
@@ -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?)
@@ -7,6 +7,7 @@ export interface TestStep {
7
7
  duration: number;
8
8
  startTime: Date;
9
9
  endTime: Date;
10
+ browser: string;
10
11
  errorMessage?: string;
11
12
  stackTrace?: string;
12
13
  codeLocation?: string;
@@ -29,6 +30,7 @@ export interface TestResult {
29
30
  tags?: string[];
30
31
  suiteName?: string;
31
32
  runId: string;
33
+ browser: string;
32
34
  screenshots?: string[];
33
35
  videoPath?: string;
34
36
  tracePath?: 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.0",
4
+ "version": "0.1.2",
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
 
@@ -221,7 +221,7 @@ function getSuitesData(results) {
221
221
  const suitesMap = new Map();
222
222
 
223
223
  results.forEach((test) => {
224
- const browser = test.name.split(" > ")[1]; // Extract browser (chromium/firefox/webkit)
224
+ const browser = test.browser; // Extract browser (chromium/firefox/webkit)
225
225
  const suiteName = test.suiteName;
226
226
  const key = `${suiteName}|${browser}`;
227
227
 
@@ -231,6 +231,7 @@ function getSuitesData(results) {
231
231
  name: `${suiteName} (${browser})`,
232
232
  status: test.status,
233
233
  count: 0,
234
+ browser: browser,
234
235
  });
235
236
  }
236
237
  suitesMap.get(key).count++;
@@ -267,9 +268,7 @@ function generateSuitesWidget(suitesData) {
267
268
  suite.count !== 1 ? "s" : ""
268
269
  }</span>
269
270
  </div>
270
- <span class="browser-name">${suite.name
271
- .split(" (")[1]
272
- .replace(")", "")}</span>
271
+ <span class="browser-name">${suite.browser}</span>
273
272
  </div>
274
273
  `
275
274
  )
@@ -472,8 +471,7 @@ function generateHTML(reportData) {
472
471
 
473
472
  return results
474
473
  .map((test, index) => {
475
- const browserMatch = test.name.match(/ > (\w+) > /);
476
- const browser = browserMatch ? browserMatch[1] : "unknown";
474
+ const browser = test.browser || "unknown";
477
475
  const testName = test.name.split(" > ").pop() || test.name;
478
476
 
479
477
  // Generate steps HTML recursively
@@ -1275,12 +1273,7 @@ function generateHTML(reportData) {
1275
1273
  <select id="filter-browser">
1276
1274
  <option value="">All Browsers</option>
1277
1275
  ${Array.from(
1278
- new Set(
1279
- results.map((test) => {
1280
- const match = test.name.match(/ > (\w+) > /);
1281
- return match ? match[1] : "unknown";
1282
- })
1283
- )
1276
+ new Set(results.map((test) => test.browser || "unknown"))
1284
1277
  )
1285
1278
  .map(
1286
1279
  (browser) => `
@@ -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}`);