@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 +117 -14
- package/dist/playwright-pulse-reporter.js +2 -2
- package/dist/reporter/attachment-utils.js +2 -2
- package/dist/reporter/playwright-pulse-reporter.js +15 -10
- package/dist/reporter/reporter/playwright-pulse-reporter.js +45 -27
- package/dist/types/index.d.ts +2 -0
- package/package.json +3 -2
- package/scripts/generate-static-report.mjs +6 -13
- package/scripts/merge-pulse-report.js +81 -0
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
104
|
-
3. **Open the HTML file:** Open the generated `pulse-report
|
|
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
|
|
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
|
|
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
|
|
173
|
-
mkdir -p pulse-report
|
|
174
|
-
cp sample-report.json pulse-report
|
|
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
|
|
190
|
-
* `pulse-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
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (((
|
|
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: ((
|
|
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: ((
|
|
215
|
+
steps: ((_g = result.steps) === null || _g === void 0 ? void 0 : _g.length)
|
|
211
216
|
? await processAllSteps(result.steps, testStatus)
|
|
212
217
|
: [],
|
|
213
|
-
errorMessage: (
|
|
214
|
-
stackTrace: (
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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/dist/types/index.d.ts
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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}`);
|