@reportforge/playwright-pdf 0.14.2 → 0.15.0
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/CHANGELOG.md +20 -0
- package/README.md +2 -2
- package/dist/cli/index.js +9 -3
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +9 -3
- package/dist/index.mjs +9 -3
- package/dist/templates/partials/charts.hbs +1 -1
- package/dist/templates/partials/release-gate.hbs +4 -2
- package/dist/templates/partials/suite-breakdown.hbs +1 -1
- package/dist/templates/partials/watermark.hbs +40 -1
- package/dist/templates/styles/base.css +25 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## [0.15.0] - 2026-07-07
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- **Suite Breakdown now always opens on a fresh page** in every template (`minimal`, `detailed`, `executive`), instead of starting mid-page after whatever section precedes it.
|
|
11
|
+
- **Watermark now stamps from the Suite Breakdown page to the end of the report.** The cover, summary, and chart pages stay clean — a diagonal stamp behind the doughnut/trend charts hurt readability on exactly the pages built to be glanced at. The copies anchor inside the suite-breakdown section (its forced page break makes the section's top edge an exact page boundary), so every copy stays page-centred without estimating print pagination; custom templates and `sections` overrides without that section fall back to whole-document stamping. Also fixes a latent overshoot where the page-count pad could paint a blank trailing page containing only a watermark.
|
|
12
|
+
- **Watermark opacity raised from 4% to 10%** — visible as a stamp rather than barely-there, still light enough to keep text underneath legible.
|
|
13
|
+
- **Release Recommendation no longer says "fix before shipping."** A failure isn't necessarily a product defect, so the HOLD banner now reads: "Review each failure — it may be a product defect or an automation script issue — and verify manually before shipping." The timed-out variant matches ("often an environment or automation issue").
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [0.14.3] - 2026-07-06
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Watermark now shows on every page instead of only one.** Chromium's print-to-PDF doesn't repeat `position: fixed` elements per page (only paged-media renderers like Prince/WeasyPrint do), so a single watermark div only ever printed once for the whole document. An inline script now measures the rendered page height and stamps one `position: absolute` copy per page. Also changed from `z-index: -1` to painting above content — at 4% opacity it doesn't affect legibility, and the negative z-index made it invisible on any page dense enough to have no background gaps.
|
|
22
|
+
- **Executive report now leads with the pass-rate/suite-results charts, trend chart second.** Previously the trend chart was promoted above the doughnut/bar row; both now render in the same order as the other templates, landing on the same page as the summary stats.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
6
26
|
## [0.14.2] - 2026-07-06
|
|
7
27
|
|
|
8
28
|
### Fixed
|
package/README.md
CHANGED
|
@@ -183,7 +183,7 @@ All options are the second element of the reporter tuple.
|
|
|
183
183
|
| `logo` | `string` | n/a | Path to a logo image (PNG, JPG, or SVG) to embed in the report header. Supports absolute and relative paths. |
|
|
184
184
|
| `primaryColor` | `string` | `'#CC785C'` | Primary brand colour (3-, 6-, or 8-digit hex). Used for headers and accent bars. |
|
|
185
185
|
| `accentColor` | `string` | `'#3F7D58'` | Accent brand colour (hex). Used for highlights and badges. |
|
|
186
|
-
| `watermark` | `string` | n/a | Text to render as a diagonal watermark overlay
|
|
186
|
+
| `watermark` | `string` | n/a | Text to render as a diagonal watermark overlay from the Suite Breakdown page onward (summary/chart pages stay clean), e.g. `'CONFIDENTIAL'` or `'DRAFT'`. |
|
|
187
187
|
| `pdfPassword` | `string` | n/a | Password-protect the generated PDF. Requires `qpdf` installed on the system. |
|
|
188
188
|
| `reportTitle` | `string` | `'Playwright Test Report'` | Custom title for the report cover page and running header. |
|
|
189
189
|
| `projectName` | `string` | from `package.json` | Project or application name. Inferred from the `package.json` `name` field if absent. |
|
|
@@ -668,7 +668,7 @@ Full documentation at [reportforge.org/docs](https://reportforge.org/docs):
|
|
|
668
668
|
- [Report Sections](https://reportforge.org/docs/configuration#report-sections): add or remove sections per template
|
|
669
669
|
- [License & offline behaviour](https://reportforge.org/docs/licensing): JWT cache, machine cap, cancellation
|
|
670
670
|
- [Troubleshooting](https://reportforge.org/docs/troubleshooting): common issues and debug flags
|
|
671
|
-
-
|
|
671
|
+
- [Changelog](https://reportforge.org/changelog): version history and release notes
|
|
672
672
|
|
|
673
673
|
---
|
|
674
674
|
|
package/dist/cli/index.js
CHANGED
|
@@ -6590,7 +6590,7 @@ function deriveOptions(s) {
|
|
|
6590
6590
|
showFullFailures: s.fullFailures
|
|
6591
6591
|
};
|
|
6592
6592
|
}
|
|
6593
|
-
var import_handlebars, import_fs, import_path, SLOW_MEDIAN_FACTOR, TemplateEngine;
|
|
6593
|
+
var import_handlebars, import_fs, import_path, A4_HEIGHT_MM, PAGE_MARGIN_Y_MM, PX_PER_MM, PAGE_CONTENT_HEIGHT_PX, SLOW_MEDIAN_FACTOR, TemplateEngine;
|
|
6594
6594
|
var init_engine = __esm({
|
|
6595
6595
|
"src/templates/engine.ts"() {
|
|
6596
6596
|
"use strict";
|
|
@@ -6602,6 +6602,10 @@ var init_engine = __esm({
|
|
|
6602
6602
|
init_duration();
|
|
6603
6603
|
init_SummaryWriter();
|
|
6604
6604
|
init_sections();
|
|
6605
|
+
A4_HEIGHT_MM = 297;
|
|
6606
|
+
PAGE_MARGIN_Y_MM = 16;
|
|
6607
|
+
PX_PER_MM = 96 / 25.4;
|
|
6608
|
+
PAGE_CONTENT_HEIGHT_PX = (A4_HEIGHT_MM - 2 * PAGE_MARGIN_Y_MM) * PX_PER_MM;
|
|
6605
6609
|
SLOW_MEDIAN_FACTOR = 2;
|
|
6606
6610
|
TemplateEngine = class {
|
|
6607
6611
|
constructor() {
|
|
@@ -6661,7 +6665,8 @@ var init_engine = __esm({
|
|
|
6661
6665
|
slowTests: slow.ranked,
|
|
6662
6666
|
baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
|
|
6663
6667
|
templateCSS: "",
|
|
6664
|
-
chartjsScript: this.buildChartjsScript()
|
|
6668
|
+
chartjsScript: this.buildChartjsScript(),
|
|
6669
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
6665
6670
|
};
|
|
6666
6671
|
}
|
|
6667
6672
|
buildContext(data, template, userSections, slowTestThreshold) {
|
|
@@ -6684,7 +6689,8 @@ var init_engine = __esm({
|
|
|
6684
6689
|
slowTests: slow.ranked,
|
|
6685
6690
|
baseCSS,
|
|
6686
6691
|
templateCSS,
|
|
6687
|
-
chartjsScript: this.buildChartjsScript()
|
|
6692
|
+
chartjsScript: this.buildChartjsScript(),
|
|
6693
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
6688
6694
|
};
|
|
6689
6695
|
}
|
|
6690
6696
|
buildChartjsScript() {
|
package/dist/index.d.mts
CHANGED
|
@@ -67,7 +67,8 @@ interface ReporterOptions {
|
|
|
67
67
|
*/
|
|
68
68
|
accentColor?: string;
|
|
69
69
|
/**
|
|
70
|
-
* Text to render as a diagonal watermark overlay
|
|
70
|
+
* Text to render as a diagonal watermark overlay, stamped from the Suite
|
|
71
|
+
* Breakdown page to the end of the report (summary and chart pages stay clean).
|
|
71
72
|
* Example: 'CONFIDENTIAL', 'DRAFT'
|
|
72
73
|
*/
|
|
73
74
|
watermark?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -67,7 +67,8 @@ interface ReporterOptions {
|
|
|
67
67
|
*/
|
|
68
68
|
accentColor?: string;
|
|
69
69
|
/**
|
|
70
|
-
* Text to render as a diagonal watermark overlay
|
|
70
|
+
* Text to render as a diagonal watermark overlay, stamped from the Suite
|
|
71
|
+
* Breakdown page to the end of the report (summary and chart pages stay clean).
|
|
71
72
|
* Example: 'CONFIDENTIAL', 'DRAFT'
|
|
72
73
|
*/
|
|
73
74
|
watermark?: string;
|
package/dist/index.js
CHANGED
|
@@ -12365,6 +12365,10 @@ function formatDuration(ms) {
|
|
|
12365
12365
|
function jsonForInlineScript(value) {
|
|
12366
12366
|
return JSON.stringify(value).replace(/<\//g, "<\\/");
|
|
12367
12367
|
}
|
|
12368
|
+
var A4_HEIGHT_MM = 297;
|
|
12369
|
+
var PAGE_MARGIN_Y_MM = 16;
|
|
12370
|
+
var PX_PER_MM = 96 / 25.4;
|
|
12371
|
+
var PAGE_CONTENT_HEIGHT_PX = (A4_HEIGHT_MM - 2 * PAGE_MARGIN_Y_MM) * PX_PER_MM;
|
|
12368
12372
|
function isUnknownValue(v) {
|
|
12369
12373
|
return !v || v === "unknown";
|
|
12370
12374
|
}
|
|
@@ -12438,7 +12442,8 @@ var TemplateEngine = class {
|
|
|
12438
12442
|
slowTests: slow.ranked,
|
|
12439
12443
|
baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
|
|
12440
12444
|
templateCSS: "",
|
|
12441
|
-
chartjsScript: this.buildChartjsScript()
|
|
12445
|
+
chartjsScript: this.buildChartjsScript(),
|
|
12446
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
12442
12447
|
};
|
|
12443
12448
|
}
|
|
12444
12449
|
buildContext(data, template, userSections, slowTestThreshold) {
|
|
@@ -12461,7 +12466,8 @@ var TemplateEngine = class {
|
|
|
12461
12466
|
slowTests: slow.ranked,
|
|
12462
12467
|
baseCSS,
|
|
12463
12468
|
templateCSS,
|
|
12464
|
-
chartjsScript: this.buildChartjsScript()
|
|
12469
|
+
chartjsScript: this.buildChartjsScript(),
|
|
12470
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
12465
12471
|
};
|
|
12466
12472
|
}
|
|
12467
12473
|
buildChartjsScript() {
|
|
@@ -14056,7 +14062,7 @@ var PdfReporter = class {
|
|
|
14056
14062
|
this.liveConsole = false;
|
|
14057
14063
|
this.options = parseOptions(rawOptions);
|
|
14058
14064
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14059
|
-
const version = true ? "0.
|
|
14065
|
+
const version = true ? "0.15.0" : "0.x";
|
|
14060
14066
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
14061
14067
|
this.licenseClient = new LicenseClient({
|
|
14062
14068
|
licenseKey: this.options.licenseKey,
|
package/dist/index.mjs
CHANGED
|
@@ -12366,6 +12366,10 @@ function formatDuration(ms) {
|
|
|
12366
12366
|
function jsonForInlineScript(value) {
|
|
12367
12367
|
return JSON.stringify(value).replace(/<\//g, "<\\/");
|
|
12368
12368
|
}
|
|
12369
|
+
var A4_HEIGHT_MM = 297;
|
|
12370
|
+
var PAGE_MARGIN_Y_MM = 16;
|
|
12371
|
+
var PX_PER_MM = 96 / 25.4;
|
|
12372
|
+
var PAGE_CONTENT_HEIGHT_PX = (A4_HEIGHT_MM - 2 * PAGE_MARGIN_Y_MM) * PX_PER_MM;
|
|
12369
12373
|
function isUnknownValue(v) {
|
|
12370
12374
|
return !v || v === "unknown";
|
|
12371
12375
|
}
|
|
@@ -12439,7 +12443,8 @@ var TemplateEngine = class {
|
|
|
12439
12443
|
slowTests: slow.ranked,
|
|
12440
12444
|
baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
|
|
12441
12445
|
templateCSS: "",
|
|
12442
|
-
chartjsScript: this.buildChartjsScript()
|
|
12446
|
+
chartjsScript: this.buildChartjsScript(),
|
|
12447
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
12443
12448
|
};
|
|
12444
12449
|
}
|
|
12445
12450
|
buildContext(data, template, userSections, slowTestThreshold) {
|
|
@@ -12462,7 +12467,8 @@ var TemplateEngine = class {
|
|
|
12462
12467
|
slowTests: slow.ranked,
|
|
12463
12468
|
baseCSS,
|
|
12464
12469
|
templateCSS,
|
|
12465
|
-
chartjsScript: this.buildChartjsScript()
|
|
12470
|
+
chartjsScript: this.buildChartjsScript(),
|
|
12471
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
12466
12472
|
};
|
|
12467
12473
|
}
|
|
12468
12474
|
buildChartjsScript() {
|
|
@@ -14057,7 +14063,7 @@ var PdfReporter = class {
|
|
|
14057
14063
|
this.liveConsole = false;
|
|
14058
14064
|
this.options = parseOptions(rawOptions);
|
|
14059
14065
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14060
|
-
const version = true ? "0.
|
|
14066
|
+
const version = true ? "0.15.0" : "0.x";
|
|
14061
14067
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
14062
14068
|
this.licenseClient = new LicenseClient({
|
|
14063
14069
|
licenseKey: this.options.licenseKey,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{{#if options.showCharts}}
|
|
2
2
|
{{#if options.heroTrend}}
|
|
3
|
-
{{> chart-trend-card options=options}}
|
|
4
3
|
{{> chart-summary-grid options=options}}
|
|
4
|
+
{{> chart-trend-card options=options}}
|
|
5
5
|
{{else}}
|
|
6
6
|
{{> chart-summary-grid options=options}}
|
|
7
7
|
{{> chart-trend-card options=options}}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
HOLD — {{stats.failed}} FAILURE{{#if (gt stats.failed 1)}}S{{/if}}
|
|
8
8
|
</div>
|
|
9
9
|
<div class="release-gate__detail">
|
|
10
|
-
{{stats.failed}} of {{stats.total}} tests failed. Review
|
|
10
|
+
{{stats.failed}} of {{stats.total}} tests failed. Review each failure — it may be a
|
|
11
|
+
product defect or an automation script issue — and verify manually before shipping.
|
|
11
12
|
{{#if stats.flaky}} · {{stats.flaky}} flaky (additional risk).{{/if}}
|
|
12
13
|
</div>
|
|
13
14
|
</div>
|
|
@@ -26,7 +27,8 @@
|
|
|
26
27
|
HOLD — {{stats.timedOut}} TIMED OUT
|
|
27
28
|
</div>
|
|
28
29
|
<div class="release-gate__detail">
|
|
29
|
-
{{stats.timedOut}} tests exceeded timeout
|
|
30
|
+
{{stats.timedOut}} tests exceeded timeout — often an environment or automation
|
|
31
|
+
issue. Review and verify manually before shipping.
|
|
30
32
|
</div>
|
|
31
33
|
</div>
|
|
32
34
|
<div class="release-gate__meta">
|
|
@@ -1,3 +1,42 @@
|
|
|
1
1
|
{{#if meta.branding.watermark}}
|
|
2
|
-
<
|
|
2
|
+
<script>
|
|
3
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
4
|
+
// Chromium's print-to-PDF doesn't repeat position:fixed elements per page, so a
|
|
5
|
+
// single watermark div only ever prints on one page. Instead, measure the fully
|
|
6
|
+
// parsed document (this listener fires after all content above is in the DOM)
|
|
7
|
+
// and stamp one absolutely-positioned copy per page slot.
|
|
8
|
+
//
|
|
9
|
+
// Copies start at the suite breakdown, not page 1, so the summary/chart pages
|
|
10
|
+
// stay clean. That section opens on a forced page break (.section--suite-breakdown
|
|
11
|
+
// in base.css), which makes its top edge an exact page boundary — appending the
|
|
12
|
+
// copies INSIDE it keeps each one page-centred even when print pagination pushes
|
|
13
|
+
// in-flow content down, because the copies travel with the section rather than
|
|
14
|
+
// being estimated against shifted content. When the section is absent (sections
|
|
15
|
+
// override, custom templates), fall back to stamping the whole document.
|
|
16
|
+
var text = {{{safeJsonData meta.branding.watermark}}};
|
|
17
|
+
var pageHeightPx = {{watermarkPageHeightPx}};
|
|
18
|
+
var anchor = document.querySelector('.section--suite-breakdown');
|
|
19
|
+
var host = anchor || document.body;
|
|
20
|
+
var startPx = anchor ? anchor.offsetTop : 0;
|
|
21
|
+
// Copy count: ceil() of the measured span, NEVER padded in the anchor case — the
|
|
22
|
+
// anchor origin is page-aligned, so ceil is exact for unshifted content and every
|
|
23
|
+
// copy lands inside it (a padded copy would paint a blank trailing page with a
|
|
24
|
+
// lone watermark). Print pagination can still push content one page further than
|
|
25
|
+
// the screen measurement predicts (cards/rows refuse to split mid-way); in the
|
|
26
|
+
// anchor case that at worst leaves the final spill-over page unstamped, which is
|
|
27
|
+
// the cheaper failure. The body fallback keeps the +1: whole-document slack
|
|
28
|
+
// (cover break + every section) reliably exceeds a page, so there the pad fills
|
|
29
|
+
// a real page instead of minting an empty one.
|
|
30
|
+
var extra = anchor ? 0 : 1;
|
|
31
|
+
var pageCount = Math.min(500, Math.max(1, Math.ceil((document.body.scrollHeight - startPx) / pageHeightPx) + extra));
|
|
32
|
+
for (var i = 0; i < pageCount; i++) {
|
|
33
|
+
var el = document.createElement('div');
|
|
34
|
+
el.className = 'watermark';
|
|
35
|
+
el.setAttribute('aria-hidden', 'true');
|
|
36
|
+
el.style.top = (i * pageHeightPx + pageHeightPx / 2) + 'px';
|
|
37
|
+
el.textContent = text;
|
|
38
|
+
host.appendChild(el);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
</script>
|
|
3
42
|
{{/if}}
|
|
@@ -43,8 +43,9 @@ html, body {
|
|
|
43
43
|
color: var(--text-1);
|
|
44
44
|
background: var(--bg);
|
|
45
45
|
}
|
|
46
|
-
/*
|
|
47
|
-
|
|
46
|
+
/* Positioned so it's the containing block for the watermark's `position: absolute`
|
|
47
|
+
fallback copies (see watermark.hbs) — without this they'd position relative to
|
|
48
|
+
the document root. */
|
|
48
49
|
body { position: relative; z-index: 0; }
|
|
49
50
|
|
|
50
51
|
/* ── Typography ─────────────────────────────────────────────────────────── */
|
|
@@ -142,17 +143,37 @@ tr:last-child td { border-bottom: none; }
|
|
|
142
143
|
.severity--low { color: var(--text-3); }
|
|
143
144
|
|
|
144
145
|
/* ── Watermark ──────────────────────────────────────────────────────────── */
|
|
146
|
+
/* `top` is set inline per copy (see watermark.hbs) — Chromium's print-to-PDF
|
|
147
|
+
doesn't repeat `position: fixed` elements per page, so one copy is stamped
|
|
148
|
+
at each page's vertical centre in document-flow coordinates instead.
|
|
149
|
+
Copies live inside .section--suite-breakdown (its forced page break makes its
|
|
150
|
+
top edge a page boundary) and run from there to the end of the report — the
|
|
151
|
+
summary/chart pages stay unstamped. body hosts the copies only as a fallback
|
|
152
|
+
when that section is absent.
|
|
153
|
+
z-index is high (not negative) so it paints over cards/badges too — at
|
|
154
|
+
10% opacity it stays light enough not to hurt legibility, and a negative
|
|
155
|
+
z-index made it invisible on any page dense enough to have no gaps in the
|
|
156
|
+
content. */
|
|
145
157
|
.watermark {
|
|
146
|
-
position:
|
|
158
|
+
position: absolute; left: 50%;
|
|
147
159
|
transform: translate(-50%, -50%) rotate(-30deg);
|
|
148
160
|
font-family: 'Source Serif Pro', Georgia, serif; font-size: 60px; font-weight: 700;
|
|
149
|
-
color: rgba(31,31,28,0.
|
|
161
|
+
color: rgba(31,31,28,0.10); pointer-events: none; z-index: 999;
|
|
150
162
|
white-space: nowrap; letter-spacing: 8px; text-transform: uppercase;
|
|
151
163
|
user-select: none;
|
|
152
164
|
}
|
|
153
165
|
|
|
154
166
|
/* ── Page breaks ────────────────────────────────────────────────────────── */
|
|
155
167
|
.no-break { page-break-inside: avoid; }
|
|
168
|
+
/* Suite breakdown always opens on a fresh page (every template) — and the forced
|
|
169
|
+
break puts its top edge exactly on a page boundary, which watermark.hbs relies
|
|
170
|
+
on to start the per-page watermark copies here. position:relative makes it
|
|
171
|
+
their containing block; no z-index, so the copies keep painting in the body
|
|
172
|
+
stacking context (above the sections that follow this one too). */
|
|
173
|
+
.section--suite-breakdown {
|
|
174
|
+
break-before: page; page-break-before: always;
|
|
175
|
+
position: relative;
|
|
176
|
+
}
|
|
156
177
|
/* Keep a failed test row glued to the START of its inline failure detail so the
|
|
157
178
|
title never orphans above it. The detail card itself may break internally (its
|
|
158
179
|
small pieces are individually break-protected), so a tall screenshot flows to the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reportforge/playwright-pdf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Playwright Test reporter that generates designed PDF reports: minimal, detailed, and executive templates with CI/CD integrations",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ReportForge",
|