@arghajit/dummy 0.1.0-beta-14 → 0.1.0-beta-16
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 +43 -18
- package/dist/reporter/playwright-pulse-reporter.js +11 -4
- package/package.json +3 -2
- package/scripts/generate-email-report.mjs +148 -10
- package/scripts/generate-report.mjs +2277 -0
- package/scripts/generate-static-report.mjs +462 -3
- package/scripts/merge-pulse-report.js +1 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Playwright Pluse Report
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-
|
|
4
|
+
_The ultimate Playwright reporter — Interactive dashboard with historical trend analytics, CI/CD-ready standalone HTML reports, and sharding support for scalable test execution._
|
|
5
5
|
|
|
6
6
|
## [Live Demo](https://pulse-report.netlify.app/)
|
|
7
7
|
|
|
@@ -58,10 +58,12 @@
|
|
|
58
58
|
## 🛠️ How It Works
|
|
59
59
|
|
|
60
60
|
1. **Reporter Collection**:
|
|
61
|
+
|
|
61
62
|
- Custom reporter collects detailed results during test execution
|
|
62
63
|
- Handles sharding by merging `.pulse-shard-results-*.json` files
|
|
63
64
|
|
|
64
65
|
2. **JSON Output**:
|
|
66
|
+
|
|
65
67
|
- Generates comprehensive `playwright-pulse-report.json`
|
|
66
68
|
|
|
67
69
|
3. **Visualization Options**:
|
|
@@ -84,18 +86,20 @@ pnpm add @arghajit/playwright-pulse-report@latest --save-dev
|
|
|
84
86
|
|
|
85
87
|
```typescript
|
|
86
88
|
// playwright.config.ts
|
|
87
|
-
import { defineConfig } from
|
|
88
|
-
import * as path from
|
|
89
|
+
import { defineConfig } from "@playwright/test";
|
|
90
|
+
import * as path from "path";
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
const PULSE_REPORT_DIR = path.resolve(__dirname, 'pulse-report');
|
|
92
|
+
const PULSE_REPORT_DIR = path.resolve(__dirname, "pulse-report");
|
|
92
93
|
|
|
93
94
|
export default defineConfig({
|
|
94
95
|
reporter: [
|
|
95
|
-
[
|
|
96
|
-
[
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
["list"],
|
|
97
|
+
[
|
|
98
|
+
"@arghajit/playwright-pulse-report",
|
|
99
|
+
{
|
|
100
|
+
outputDir: PULSE_REPORT_DIR,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
99
103
|
],
|
|
100
104
|
// Other configurations...
|
|
101
105
|
});
|
|
@@ -112,9 +116,11 @@ npx send-email # Sends email report
|
|
|
112
116
|
|
|
113
117
|
## 📊 Report Options
|
|
114
118
|
|
|
115
|
-
### Option 1: Static HTML Report
|
|
119
|
+
### Option 1: Static HTML Report (Embedded Attachments)
|
|
116
120
|
|
|
117
121
|
```bash
|
|
122
|
+
npm run generate-pulse-report
|
|
123
|
+
or,
|
|
118
124
|
npx generate-pulse-report
|
|
119
125
|
```
|
|
120
126
|
|
|
@@ -122,7 +128,20 @@ npx generate-pulse-report
|
|
|
122
128
|
- Self-contained, no server required
|
|
123
129
|
- Preserves all dashboard functionality
|
|
124
130
|
|
|
125
|
-
### Option 2:
|
|
131
|
+
### Option 2: HTML Report (Attachment-based)
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm run generate-report
|
|
135
|
+
or,
|
|
136
|
+
npx generate-report
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- Generates playwright-pulse-report.html
|
|
140
|
+
- Loads screenshots and images dynamically from the attachments/ directory
|
|
141
|
+
- Produces a lighter HTML file with faster initial load
|
|
142
|
+
- Requires attachments/ directory to be present when viewing the report
|
|
143
|
+
|
|
144
|
+
### Option 3: Email Report
|
|
126
145
|
|
|
127
146
|
1. Configure `.env`:
|
|
128
147
|
|
|
@@ -138,7 +157,7 @@ npx generate-pulse-report
|
|
|
138
157
|
npx send-email
|
|
139
158
|
```
|
|
140
159
|
|
|
141
|
-
|
|
160
|
+
NOTE: The email will be send with a light-weight html file, which can be opened in mail preview application.
|
|
142
161
|
|
|
143
162
|
## 🤖 AI Analysis
|
|
144
163
|
|
|
@@ -173,7 +192,7 @@ The dashboard includes AI-powered test analysis that provides:
|
|
|
173
192
|
- name: Generate Pulse Report
|
|
174
193
|
run: |
|
|
175
194
|
npm run script merge-report
|
|
176
|
-
npm run
|
|
195
|
+
npm run generate-report [or, npm run generate-pulse-report]
|
|
177
196
|
|
|
178
197
|
# Upload final merged report as CI artifact
|
|
179
198
|
- name: Upload Pulse report
|
|
@@ -213,8 +232,8 @@ The dashboard includes AI-powered test analysis that provides:
|
|
|
213
232
|
# Merge all sharded JSON reports into one final output
|
|
214
233
|
- name: Generate Pulse Report
|
|
215
234
|
run: |
|
|
216
|
-
npm run
|
|
217
|
-
npm run
|
|
235
|
+
npm run merge-report
|
|
236
|
+
npm run generate-report [or, npm run generate-pulse-report]
|
|
218
237
|
|
|
219
238
|
# Upload final merged report as CI artifact
|
|
220
239
|
- name: Upload Pulse report
|
|
@@ -222,18 +241,18 @@ The dashboard includes AI-powered test analysis that provides:
|
|
|
222
241
|
with:
|
|
223
242
|
name: pulse-report
|
|
224
243
|
path: pulse-report/
|
|
225
|
-
|
|
226
244
|
```
|
|
227
245
|
|
|
228
246
|
## 🧠 Notes
|
|
229
247
|
|
|
248
|
+
- <strong>`npm run generate-report` generates a HTML report ( screenshots/images will be taken in realtime from 'attachments/' directory ).</strong>
|
|
249
|
+
- <strong>`npm run generate-pulse-report` generates a fully self-contained static HTML report( All screenshots and images are embedded directly into the HTML using base64 encoding, which simplifies distribution but may result in larger file sizes and longer load times ).</strong>
|
|
230
250
|
- Each shard generates its own playwright-pulse-report.json inside pulse-report/.
|
|
231
251
|
- Artifacts are named using the shard type (matrix.config.type).
|
|
232
252
|
- After the test matrix completes, reports are downloaded, renamed, and merged.
|
|
233
253
|
- merge-report is a custom Node.js script that combines all JSON files into one.
|
|
234
|
-
- generate-report can build a static HTML dashboard if needed.
|
|
235
254
|
|
|
236
|
-
## 
|
|
237
256
|
|
|
238
257
|
### 🚀 **Upgrade Now**
|
|
239
258
|
|
|
@@ -247,4 +266,10 @@ For issues or feature requests, please [Contact Me](mailto:arghajitsingha47@gmai
|
|
|
247
266
|
|
|
248
267
|
---
|
|
249
268
|
|
|
269
|
+
## 🙌🏼 Thank you
|
|
270
|
+
|
|
271
|
+
Special Thanks to [@suman-vishwakarma](https://www.linkedin.com/in/suman-vishwakarma-426108185/) and [@sagnik-ghosh](https://www.linkedin.com/in/sagnikghosh99/), for continuous UAT feedback.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
250
275
|
<div align="center">Made by Arghajit Singha | MIT Licensed</div>
|
|
@@ -245,10 +245,17 @@ class PlaywrightPulseReporter {
|
|
|
245
245
|
});
|
|
246
246
|
}
|
|
247
247
|
const uniqueTestId = test.id;
|
|
248
|
-
// ---
|
|
248
|
+
// --- REFINED THIS SECTION for testData ---
|
|
249
|
+
const maxWorkers = this.config.workers;
|
|
250
|
+
// Use the modulo operator to map the unique workerIndex to a concurrency "slot".
|
|
251
|
+
// This ensures workerId is always between 0 and (maxWorkers - 1).
|
|
252
|
+
// Added a safeguard to fall back to the raw index if maxWorkers isn't a positive number.
|
|
253
|
+
const mappedWorkerId = maxWorkers && maxWorkers > 0
|
|
254
|
+
? result.workerIndex % maxWorkers
|
|
255
|
+
: result.workerIndex;
|
|
249
256
|
const testSpecificData = {
|
|
250
|
-
workerId:
|
|
251
|
-
totalWorkers:
|
|
257
|
+
workerId: mappedWorkerId,
|
|
258
|
+
totalWorkers: maxWorkers,
|
|
252
259
|
configFile: this.config.configFile,
|
|
253
260
|
metadata: this.config.metadata
|
|
254
261
|
? JSON.stringify(this.config.metadata)
|
|
@@ -275,7 +282,7 @@ class PlaywrightPulseReporter {
|
|
|
275
282
|
tracePath: undefined,
|
|
276
283
|
stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
|
|
277
284
|
stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
|
|
278
|
-
// ---
|
|
285
|
+
// --- UPDATED THESE LINES from testSpecificData ---
|
|
279
286
|
...testSpecificData,
|
|
280
287
|
};
|
|
281
288
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arghajit/dummy",
|
|
3
3
|
"author": "Arghajit Singha",
|
|
4
|
-
"version": "0.1.0-beta-
|
|
4
|
+
"version": "0.1.0-beta-16",
|
|
5
5
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"playwright",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"bin": {
|
|
28
28
|
"generate-pulse-report": "./scripts/generate-static-report.mjs",
|
|
29
|
+
"generate-report": "./scripts/generate-report.mjs",
|
|
29
30
|
"merge-pulse-report": "./scripts/merge-pulse-report.js",
|
|
30
31
|
"send-email": "./scripts/sendReport.mjs",
|
|
31
32
|
"generate-trend": "./scripts/generate-trend.mjs",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"typecheck": "tsc --noEmit",
|
|
43
44
|
"prepublishOnly": "npm run build:reporter",
|
|
44
45
|
"report:static": "node ./scripts/generate-static-report.mjs",
|
|
46
|
+
"report:generate": "node ./scripts/generate-report.mjs",
|
|
45
47
|
"report:merge": "node ./scripts/merge-pulse-report.js",
|
|
46
48
|
"report:email": "node ./scripts/sendReport.mjs",
|
|
47
49
|
"report:minify": "node ./scripts/generate-email-report.mjs"
|
|
@@ -67,7 +69,6 @@
|
|
|
67
69
|
"@types/node": "^20",
|
|
68
70
|
"@types/ua-parser-js": "^0.7.39",
|
|
69
71
|
"eslint": "9.25.1",
|
|
70
|
-
"pretty-ansi": "^3.0.0",
|
|
71
72
|
"typescript": "^5"
|
|
72
73
|
},
|
|
73
74
|
"engines": {
|
|
@@ -177,9 +177,11 @@ function generateMinifiedHTML(reportData) {
|
|
|
177
177
|
};
|
|
178
178
|
|
|
179
179
|
const testsByBrowser = new Map();
|
|
180
|
+
const allBrowsers = new Set();
|
|
180
181
|
if (results && results.length > 0) {
|
|
181
182
|
results.forEach((test) => {
|
|
182
183
|
const browser = test.browser || "unknown";
|
|
184
|
+
allBrowsers.add(browser);
|
|
183
185
|
if (!testsByBrowser.has(browser)) {
|
|
184
186
|
testsByBrowser.set(browser, []);
|
|
185
187
|
}
|
|
@@ -195,7 +197,9 @@ function generateMinifiedHTML(reportData) {
|
|
|
195
197
|
let html = "";
|
|
196
198
|
testsByBrowser.forEach((tests, browser) => {
|
|
197
199
|
html += `
|
|
198
|
-
<div class="browser-section"
|
|
200
|
+
<div class="browser-section" data-browser-group="${sanitizeHTML(
|
|
201
|
+
browser.toLowerCase()
|
|
202
|
+
)}">
|
|
199
203
|
<h2 class="browser-title">${sanitizeHTML(capitalize(browser))}</h2>
|
|
200
204
|
<ul class="test-list">
|
|
201
205
|
`;
|
|
@@ -204,7 +208,12 @@ function generateMinifiedHTML(reportData) {
|
|
|
204
208
|
const testTitle =
|
|
205
209
|
testFileParts[testFileParts.length - 1] || "Unnamed Test";
|
|
206
210
|
html += `
|
|
207
|
-
<li class="test-item ${getStatusClass(test.status)}"
|
|
211
|
+
<li class="test-item ${getStatusClass(test.status)}"
|
|
212
|
+
data-test-name-min="${sanitizeHTML(testTitle.toLowerCase())}"
|
|
213
|
+
data-status-min="${sanitizeHTML(
|
|
214
|
+
String(test.status).toLowerCase()
|
|
215
|
+
)}"
|
|
216
|
+
data-browser-min="${sanitizeHTML(browser.toLowerCase())}">
|
|
208
217
|
<span class="test-status-icon">${getStatusIcon(
|
|
209
218
|
test.status
|
|
210
219
|
)}</span>
|
|
@@ -343,6 +352,43 @@ function generateMinifiedHTML(reportData) {
|
|
|
343
352
|
padding-bottom: 10px;
|
|
344
353
|
border-bottom: 2px solid var(--secondary-color);
|
|
345
354
|
}
|
|
355
|
+
|
|
356
|
+
/* Filters Section */
|
|
357
|
+
.filters-section {
|
|
358
|
+
display: flex;
|
|
359
|
+
flex-wrap: wrap;
|
|
360
|
+
gap: 15px;
|
|
361
|
+
margin-bottom: 20px;
|
|
362
|
+
padding: 15px;
|
|
363
|
+
background-color: var(--light-gray-color);
|
|
364
|
+
border-radius: var(--border-radius);
|
|
365
|
+
border: 1px solid var(--border-color);
|
|
366
|
+
}
|
|
367
|
+
.filters-section input[type="text"],
|
|
368
|
+
.filters-section select {
|
|
369
|
+
padding: 8px 12px;
|
|
370
|
+
border: 1px solid var(--medium-gray-color);
|
|
371
|
+
border-radius: 4px;
|
|
372
|
+
font-size: 0.95em;
|
|
373
|
+
flex-grow: 1;
|
|
374
|
+
}
|
|
375
|
+
.filters-section select {
|
|
376
|
+
min-width: 150px;
|
|
377
|
+
}
|
|
378
|
+
.filters-section button {
|
|
379
|
+
padding: 8px 15px;
|
|
380
|
+
font-size: 0.95em;
|
|
381
|
+
background-color: var(--secondary-color);
|
|
382
|
+
color: white;
|
|
383
|
+
border: none;
|
|
384
|
+
border-radius: 4px;
|
|
385
|
+
cursor: pointer;
|
|
386
|
+
transition: background-color 0.2s ease;
|
|
387
|
+
}
|
|
388
|
+
.filters-section button:hover {
|
|
389
|
+
background-color: var(--primary-color);
|
|
390
|
+
}
|
|
391
|
+
|
|
346
392
|
.browser-section {
|
|
347
393
|
margin-bottom: 25px;
|
|
348
394
|
}
|
|
@@ -365,7 +411,7 @@ function generateMinifiedHTML(reportData) {
|
|
|
365
411
|
border: 1px solid var(--border-color);
|
|
366
412
|
border-radius: var(--border-radius);
|
|
367
413
|
background-color: #fff;
|
|
368
|
-
transition: background-color 0.2s ease;
|
|
414
|
+
transition: background-color 0.2s ease, display 0.3s ease-out;
|
|
369
415
|
}
|
|
370
416
|
.test-item:hover {
|
|
371
417
|
background-color: var(--light-gray-color);
|
|
@@ -424,10 +470,11 @@ function generateMinifiedHTML(reportData) {
|
|
|
424
470
|
.report-header { flex-direction: column; align-items: flex-start; gap: 10px; }
|
|
425
471
|
.report-header h1 { font-size: 1.5em; }
|
|
426
472
|
.run-info { text-align: left; }
|
|
427
|
-
.summary-stats { grid-template-columns: 1fr 1fr; }
|
|
473
|
+
.summary-stats { grid-template-columns: 1fr 1fr; }
|
|
474
|
+
.filters-section { flex-direction: column; }
|
|
428
475
|
}
|
|
429
476
|
@media (max-width: 480px) {
|
|
430
|
-
.summary-stats { grid-template-columns: 1fr; }
|
|
477
|
+
.summary-stats { grid-template-columns: 1fr; }
|
|
431
478
|
}
|
|
432
479
|
</style>
|
|
433
480
|
</head>
|
|
@@ -471,6 +518,30 @@ function generateMinifiedHTML(reportData) {
|
|
|
471
518
|
|
|
472
519
|
<section class="test-results-section">
|
|
473
520
|
<h1 class="section-title">Test Case Summary</h1>
|
|
521
|
+
|
|
522
|
+
<div class="filters-section">
|
|
523
|
+
<input type="text" id="filter-min-name" placeholder="Search by test name...">
|
|
524
|
+
<select id="filter-min-status">
|
|
525
|
+
<option value="">All Statuses</option>
|
|
526
|
+
<option value="passed">Passed</option>
|
|
527
|
+
<option value="failed">Failed</option>
|
|
528
|
+
<option value="skipped">Skipped</option>
|
|
529
|
+
<option value="unknown">Unknown</option>
|
|
530
|
+
</select>
|
|
531
|
+
<select id="filter-min-browser">
|
|
532
|
+
<option value="">All Browsers</option>
|
|
533
|
+
${Array.from(allBrowsers)
|
|
534
|
+
.map(
|
|
535
|
+
(browser) =>
|
|
536
|
+
`<option value="${sanitizeHTML(
|
|
537
|
+
browser.toLowerCase()
|
|
538
|
+
)}">${sanitizeHTML(capitalize(browser))}</option>`
|
|
539
|
+
)
|
|
540
|
+
.join("")}
|
|
541
|
+
</select>
|
|
542
|
+
<button id="clear-min-filters">Clear Filters</button>
|
|
543
|
+
</div>
|
|
544
|
+
|
|
474
545
|
${generateTestListHTML()}
|
|
475
546
|
</section>
|
|
476
547
|
|
|
@@ -485,16 +556,83 @@ function generateMinifiedHTML(reportData) {
|
|
|
485
556
|
</footer>
|
|
486
557
|
</div>
|
|
487
558
|
<script>
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
559
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
560
|
+
const nameFilterMin = document.getElementById('filter-min-name');
|
|
561
|
+
const statusFilterMin = document.getElementById('filter-min-status');
|
|
562
|
+
const browserFilterMin = document.getElementById('filter-min-browser');
|
|
563
|
+
const clearMinFiltersBtn = document.getElementById('clear-min-filters');
|
|
564
|
+
const testItemsMin = document.querySelectorAll('.test-results-section .test-item');
|
|
565
|
+
const browserSections = document.querySelectorAll('.test-results-section .browser-section');
|
|
566
|
+
|
|
567
|
+
function filterMinifiedTests() {
|
|
568
|
+
const nameValue = nameFilterMin.value.toLowerCase();
|
|
569
|
+
const statusValue = statusFilterMin.value;
|
|
570
|
+
const browserValue = browserFilterMin.value;
|
|
571
|
+
let anyBrowserSectionVisible = false;
|
|
572
|
+
|
|
573
|
+
browserSections.forEach(section => {
|
|
574
|
+
let sectionHasVisibleTests = false;
|
|
575
|
+
const testsInThisSection = section.querySelectorAll('.test-item');
|
|
576
|
+
|
|
577
|
+
testsInThisSection.forEach(testItem => {
|
|
578
|
+
const testName = testItem.getAttribute('data-test-name-min');
|
|
579
|
+
const testStatus = testItem.getAttribute('data-status-min');
|
|
580
|
+
const testBrowser = testItem.getAttribute('data-browser-min');
|
|
581
|
+
|
|
582
|
+
const nameMatch = testName.includes(nameValue);
|
|
583
|
+
const statusMatch = !statusValue || testStatus === statusValue;
|
|
584
|
+
const browserMatch = !browserValue || testBrowser === browserValue;
|
|
585
|
+
|
|
586
|
+
if (nameMatch && statusMatch && browserMatch) {
|
|
587
|
+
testItem.style.display = 'flex';
|
|
588
|
+
sectionHasVisibleTests = true;
|
|
589
|
+
anyBrowserSectionVisible = true;
|
|
590
|
+
} else {
|
|
591
|
+
testItem.style.display = 'none';
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
// Hide browser section if no tests match OR if a specific browser is selected and it's not this one
|
|
595
|
+
if (!sectionHasVisibleTests || (browserValue && section.getAttribute('data-browser-group') !== browserValue)) {
|
|
596
|
+
section.style.display = 'none';
|
|
597
|
+
} else {
|
|
598
|
+
section.style.display = '';
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// Show "no tests" message if all sections are hidden
|
|
603
|
+
const noTestsMessage = document.querySelector('.test-results-section .no-tests');
|
|
604
|
+
if (noTestsMessage) {
|
|
605
|
+
noTestsMessage.style.display = anyBrowserSectionVisible ? 'none' : 'block';
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (nameFilterMin) nameFilterMin.addEventListener('input', filterMinifiedTests);
|
|
611
|
+
if (statusFilterMin) statusFilterMin.addEventListener('change', filterMinifiedTests);
|
|
612
|
+
if (browserFilterMin) browserFilterMin.addEventListener('change', filterMinifiedTests);
|
|
613
|
+
|
|
614
|
+
if (clearMinFiltersBtn) {
|
|
615
|
+
clearMinFiltersBtn.addEventListener('click', () => {
|
|
616
|
+
nameFilterMin.value = '';
|
|
617
|
+
statusFilterMin.value = '';
|
|
618
|
+
browserFilterMin.value = '';
|
|
619
|
+
filterMinifiedTests();
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
// Initial filter call in case of pre-filled values (though unlikely here)
|
|
623
|
+
if (testItemsMin.length > 0) { // Only filter if there are items
|
|
624
|
+
filterMinifiedTests();
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Fallback helper functions (though ideally not needed client-side for this minified report)
|
|
491
629
|
if (typeof formatDuration === 'undefined') {
|
|
492
|
-
function formatDuration(ms) {
|
|
630
|
+
function formatDuration(ms) {
|
|
493
631
|
if (ms === undefined || ms === null || ms < 0) return "0.0s";
|
|
494
632
|
return (ms / 1000).toFixed(1) + "s";
|
|
495
633
|
}
|
|
496
634
|
}
|
|
497
|
-
if (typeof formatDate === 'undefined') {
|
|
635
|
+
if (typeof formatDate === 'undefined') {
|
|
498
636
|
function formatDate(dateStrOrDate) {
|
|
499
637
|
if (!dateStrOrDate) return "N/A";
|
|
500
638
|
try {
|