@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 +111 -14
- package/dist/playwright-pulse-reporter.js +2 -2
- package/dist/reporter/attachment-utils.js +92 -54
- package/dist/reporter/playwright-pulse-reporter.js +10 -2
- package/dist/reporter/reporter/playwright-pulse-reporter.js +45 -27
- package/package.json +3 -2
- package/scripts/generate-static-report.mjs +2 -5
- 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,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
|
|
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
|
+
```
|
|
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
|
-
|
|
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,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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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/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.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
|
|
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}`);
|