@reportforge/playwright-pdf 0.14.3 → 0.15.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/CHANGELOG.md +19 -0
- package/README.md +2 -2
- package/dist/cli/index.js +15 -5
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +16 -6
- package/dist/index.mjs +16 -6
- package/dist/templates/partials/release-gate.hbs +8 -4
- package/dist/templates/partials/suite-breakdown.hbs +1 -1
- package/dist/templates/partials/watermark.hbs +23 -5
- package/dist/templates/styles/base.css +19 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
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.1] - 2026-07-07
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- **Release Recommendation copy is dash-free and data-driven.** The banner verdict reads `HOLD: N FAILURES` and the detail line now carries the per-run root-cause tally from failure analysis, e.g. "3 of 21 tests failed (2 assertion, 1 timeout). Each failure may be a product defect or an automation script issue. Review and verify manually before shipping." Falls back to the plain count when analysis is off; `flaky-by-retry` is excluded from the tally (flaky tests keep their own suffix). Timed-out variant matches.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [0.15.0] - 2026-07-07
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **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.
|
|
19
|
+
- **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.
|
|
20
|
+
- **Watermark opacity raised from 4% to 10%** — visible as a stamp rather than barely-there, still light enough to keep text underneath legible.
|
|
21
|
+
- **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").
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
6
25
|
## [0.14.3] - 2026-07-06
|
|
7
26
|
|
|
8
27
|
### 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
|
@@ -6375,16 +6375,24 @@ var init_duration = __esm({
|
|
|
6375
6375
|
});
|
|
6376
6376
|
|
|
6377
6377
|
// src/analysis/SummaryWriter.ts
|
|
6378
|
-
function
|
|
6379
|
-
if (clusters.length === 0) return "";
|
|
6378
|
+
function tallyEntries(clusters) {
|
|
6380
6379
|
const byCategory = /* @__PURE__ */ new Map();
|
|
6381
6380
|
for (const c of clusters) {
|
|
6382
6381
|
byCategory.set(c.category, (byCategory.get(c.category) ?? 0) + c.count);
|
|
6383
6382
|
}
|
|
6383
|
+
return [...byCategory.entries()].sort((a, b) => b[1] - a[1]);
|
|
6384
|
+
}
|
|
6385
|
+
function buildOneLiner(clusters) {
|
|
6386
|
+
if (clusters.length === 0) return "";
|
|
6384
6387
|
const head = `${clusters.length} root ${clusters.length === 1 ? "cause" : "causes"}`;
|
|
6385
|
-
const tally =
|
|
6388
|
+
const tally = tallyEntries(clusters).map(([cat, count]) => `${count} ${SHORT_LABEL[cat]}`);
|
|
6386
6389
|
return [head, ...tally].join(" \xB7 ");
|
|
6387
6390
|
}
|
|
6391
|
+
function buildFailureTally(analysis) {
|
|
6392
|
+
if (!analysis) return "";
|
|
6393
|
+
const failureClusters = analysis.clusters.filter((c) => c.category !== "flaky-by-retry");
|
|
6394
|
+
return tallyEntries(failureClusters).map(([cat, count]) => `${count} ${SHORT_LABEL[cat]}`).join(", ");
|
|
6395
|
+
}
|
|
6388
6396
|
function dominantCategory(clusters) {
|
|
6389
6397
|
return clusters[0]?.category ?? "unknown";
|
|
6390
6398
|
}
|
|
@@ -6666,7 +6674,8 @@ var init_engine = __esm({
|
|
|
6666
6674
|
baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
|
|
6667
6675
|
templateCSS: "",
|
|
6668
6676
|
chartjsScript: this.buildChartjsScript(),
|
|
6669
|
-
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
6677
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
|
|
6678
|
+
releaseGateTally: buildFailureTally(data.analysis)
|
|
6670
6679
|
};
|
|
6671
6680
|
}
|
|
6672
6681
|
buildContext(data, template, userSections, slowTestThreshold) {
|
|
@@ -6690,7 +6699,8 @@ var init_engine = __esm({
|
|
|
6690
6699
|
baseCSS,
|
|
6691
6700
|
templateCSS,
|
|
6692
6701
|
chartjsScript: this.buildChartjsScript(),
|
|
6693
|
-
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
6702
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
|
|
6703
|
+
releaseGateTally: buildFailureTally(data.analysis)
|
|
6694
6704
|
};
|
|
6695
6705
|
}
|
|
6696
6706
|
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
|
@@ -11291,16 +11291,24 @@ var SHORT_LABEL = {
|
|
|
11291
11291
|
"flaky-by-retry": "flaky",
|
|
11292
11292
|
unknown: "unclassified"
|
|
11293
11293
|
};
|
|
11294
|
-
function
|
|
11295
|
-
if (clusters.length === 0) return "";
|
|
11294
|
+
function tallyEntries(clusters) {
|
|
11296
11295
|
const byCategory = /* @__PURE__ */ new Map();
|
|
11297
11296
|
for (const c of clusters) {
|
|
11298
11297
|
byCategory.set(c.category, (byCategory.get(c.category) ?? 0) + c.count);
|
|
11299
11298
|
}
|
|
11299
|
+
return [...byCategory.entries()].sort((a, b) => b[1] - a[1]);
|
|
11300
|
+
}
|
|
11301
|
+
function buildOneLiner(clusters) {
|
|
11302
|
+
if (clusters.length === 0) return "";
|
|
11300
11303
|
const head = `${clusters.length} root ${clusters.length === 1 ? "cause" : "causes"}`;
|
|
11301
|
-
const tally =
|
|
11304
|
+
const tally = tallyEntries(clusters).map(([cat, count]) => `${count} ${SHORT_LABEL[cat]}`);
|
|
11302
11305
|
return [head, ...tally].join(" \xB7 ");
|
|
11303
11306
|
}
|
|
11307
|
+
function buildFailureTally(analysis) {
|
|
11308
|
+
if (!analysis) return "";
|
|
11309
|
+
const failureClusters = analysis.clusters.filter((c) => c.category !== "flaky-by-retry");
|
|
11310
|
+
return tallyEntries(failureClusters).map(([cat, count]) => `${count} ${SHORT_LABEL[cat]}`).join(", ");
|
|
11311
|
+
}
|
|
11304
11312
|
function dominantCategory(clusters) {
|
|
11305
11313
|
return clusters[0]?.category ?? "unknown";
|
|
11306
11314
|
}
|
|
@@ -12443,7 +12451,8 @@ var TemplateEngine = class {
|
|
|
12443
12451
|
baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
|
|
12444
12452
|
templateCSS: "",
|
|
12445
12453
|
chartjsScript: this.buildChartjsScript(),
|
|
12446
|
-
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
12454
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
|
|
12455
|
+
releaseGateTally: buildFailureTally(data.analysis)
|
|
12447
12456
|
};
|
|
12448
12457
|
}
|
|
12449
12458
|
buildContext(data, template, userSections, slowTestThreshold) {
|
|
@@ -12467,7 +12476,8 @@ var TemplateEngine = class {
|
|
|
12467
12476
|
baseCSS,
|
|
12468
12477
|
templateCSS,
|
|
12469
12478
|
chartjsScript: this.buildChartjsScript(),
|
|
12470
|
-
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
12479
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
|
|
12480
|
+
releaseGateTally: buildFailureTally(data.analysis)
|
|
12471
12481
|
};
|
|
12472
12482
|
}
|
|
12473
12483
|
buildChartjsScript() {
|
|
@@ -14062,7 +14072,7 @@ var PdfReporter = class {
|
|
|
14062
14072
|
this.liveConsole = false;
|
|
14063
14073
|
this.options = parseOptions(rawOptions);
|
|
14064
14074
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14065
|
-
const version = true ? "0.
|
|
14075
|
+
const version = true ? "0.15.1" : "0.x";
|
|
14066
14076
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
14067
14077
|
this.licenseClient = new LicenseClient({
|
|
14068
14078
|
licenseKey: this.options.licenseKey,
|
package/dist/index.mjs
CHANGED
|
@@ -11292,16 +11292,24 @@ var SHORT_LABEL = {
|
|
|
11292
11292
|
"flaky-by-retry": "flaky",
|
|
11293
11293
|
unknown: "unclassified"
|
|
11294
11294
|
};
|
|
11295
|
-
function
|
|
11296
|
-
if (clusters.length === 0) return "";
|
|
11295
|
+
function tallyEntries(clusters) {
|
|
11297
11296
|
const byCategory = /* @__PURE__ */ new Map();
|
|
11298
11297
|
for (const c of clusters) {
|
|
11299
11298
|
byCategory.set(c.category, (byCategory.get(c.category) ?? 0) + c.count);
|
|
11300
11299
|
}
|
|
11300
|
+
return [...byCategory.entries()].sort((a, b) => b[1] - a[1]);
|
|
11301
|
+
}
|
|
11302
|
+
function buildOneLiner(clusters) {
|
|
11303
|
+
if (clusters.length === 0) return "";
|
|
11301
11304
|
const head = `${clusters.length} root ${clusters.length === 1 ? "cause" : "causes"}`;
|
|
11302
|
-
const tally =
|
|
11305
|
+
const tally = tallyEntries(clusters).map(([cat, count]) => `${count} ${SHORT_LABEL[cat]}`);
|
|
11303
11306
|
return [head, ...tally].join(" \xB7 ");
|
|
11304
11307
|
}
|
|
11308
|
+
function buildFailureTally(analysis) {
|
|
11309
|
+
if (!analysis) return "";
|
|
11310
|
+
const failureClusters = analysis.clusters.filter((c) => c.category !== "flaky-by-retry");
|
|
11311
|
+
return tallyEntries(failureClusters).map(([cat, count]) => `${count} ${SHORT_LABEL[cat]}`).join(", ");
|
|
11312
|
+
}
|
|
11305
11313
|
function dominantCategory(clusters) {
|
|
11306
11314
|
return clusters[0]?.category ?? "unknown";
|
|
11307
11315
|
}
|
|
@@ -12444,7 +12452,8 @@ var TemplateEngine = class {
|
|
|
12444
12452
|
baseCSS: this.fontsBundleCss + "\n" + this.readStyle("base.css"),
|
|
12445
12453
|
templateCSS: "",
|
|
12446
12454
|
chartjsScript: this.buildChartjsScript(),
|
|
12447
|
-
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
12455
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
|
|
12456
|
+
releaseGateTally: buildFailureTally(data.analysis)
|
|
12448
12457
|
};
|
|
12449
12458
|
}
|
|
12450
12459
|
buildContext(data, template, userSections, slowTestThreshold) {
|
|
@@ -12468,7 +12477,8 @@ var TemplateEngine = class {
|
|
|
12468
12477
|
baseCSS,
|
|
12469
12478
|
templateCSS,
|
|
12470
12479
|
chartjsScript: this.buildChartjsScript(),
|
|
12471
|
-
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX
|
|
12480
|
+
watermarkPageHeightPx: PAGE_CONTENT_HEIGHT_PX,
|
|
12481
|
+
releaseGateTally: buildFailureTally(data.analysis)
|
|
12472
12482
|
};
|
|
12473
12483
|
}
|
|
12474
12484
|
buildChartjsScript() {
|
|
@@ -14063,7 +14073,7 @@ var PdfReporter = class {
|
|
|
14063
14073
|
this.liveConsole = false;
|
|
14064
14074
|
this.options = parseOptions(rawOptions);
|
|
14065
14075
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14066
|
-
const version = true ? "0.
|
|
14076
|
+
const version = true ? "0.15.1" : "0.x";
|
|
14067
14077
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
14068
14078
|
this.licenseClient = new LicenseClient({
|
|
14069
14079
|
licenseKey: this.options.licenseKey,
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
<div class="release-gate__body">
|
|
5
5
|
<div class="release-gate__label">Release Recommendation</div>
|
|
6
6
|
<div class="release-gate__verdict release-gate__verdict--fail">
|
|
7
|
-
HOLD
|
|
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
|
|
10
|
+
{{stats.failed}} of {{stats.total}} tests failed{{#if releaseGateTally}} ({{releaseGateTally}}){{/if}}.
|
|
11
|
+
Each failure may be a product defect or an automation script issue.
|
|
12
|
+
Review and verify manually before shipping.
|
|
11
13
|
{{#if stats.flaky}} · {{stats.flaky}} flaky (additional risk).{{/if}}
|
|
12
14
|
</div>
|
|
13
15
|
</div>
|
|
@@ -23,10 +25,12 @@
|
|
|
23
25
|
<div class="release-gate__body">
|
|
24
26
|
<div class="release-gate__label">Release Recommendation</div>
|
|
25
27
|
<div class="release-gate__verdict release-gate__verdict--fail">
|
|
26
|
-
HOLD
|
|
28
|
+
HOLD: {{stats.timedOut}} TIMED OUT
|
|
27
29
|
</div>
|
|
28
30
|
<div class="release-gate__detail">
|
|
29
|
-
{{stats.timedOut}}
|
|
31
|
+
{{stats.timedOut}} test{{#if (gt stats.timedOut 1)}}s{{/if}} exceeded timeout.
|
|
32
|
+
Timeouts are often environment or automation issues.
|
|
33
|
+
Review and verify manually before shipping.
|
|
30
34
|
</div>
|
|
31
35
|
</div>
|
|
32
36
|
<div class="release-gate__meta">
|
|
@@ -5,19 +5,37 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
5
5
|
// single watermark div only ever prints on one page. Instead, measure the fully
|
|
6
6
|
// parsed document (this listener fires after all content above is in the DOM)
|
|
7
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.
|
|
8
16
|
var text = {{{safeJsonData meta.branding.watermark}}};
|
|
9
17
|
var pageHeightPx = {{watermarkPageHeightPx}};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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));
|
|
14
32
|
for (var i = 0; i < pageCount; i++) {
|
|
15
33
|
var el = document.createElement('div');
|
|
16
34
|
el.className = 'watermark';
|
|
17
35
|
el.setAttribute('aria-hidden', 'true');
|
|
18
36
|
el.style.top = (i * pageHeightPx + pageHeightPx / 2) + 'px';
|
|
19
37
|
el.textContent = text;
|
|
20
|
-
|
|
38
|
+
host.appendChild(el);
|
|
21
39
|
}
|
|
22
40
|
});
|
|
23
41
|
</script>
|
|
@@ -44,7 +44,8 @@ html, body {
|
|
|
44
44
|
background: var(--bg);
|
|
45
45
|
}
|
|
46
46
|
/* Positioned so it's the containing block for the watermark's `position: absolute`
|
|
47
|
-
copies (see
|
|
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 ─────────────────────────────────────────────────────────── */
|
|
@@ -145,20 +146,34 @@ tr:last-child td { border-bottom: none; }
|
|
|
145
146
|
/* `top` is set inline per copy (see watermark.hbs) — Chromium's print-to-PDF
|
|
146
147
|
doesn't repeat `position: fixed` elements per page, so one copy is stamped
|
|
147
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.
|
|
148
153
|
z-index is high (not negative) so it paints over cards/badges too — at
|
|
149
|
-
|
|
150
|
-
invisible on any page dense enough to have no gaps in the
|
|
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. */
|
|
151
157
|
.watermark {
|
|
152
158
|
position: absolute; left: 50%;
|
|
153
159
|
transform: translate(-50%, -50%) rotate(-30deg);
|
|
154
160
|
font-family: 'Source Serif Pro', Georgia, serif; font-size: 60px; font-weight: 700;
|
|
155
|
-
color: rgba(31,31,28,0.
|
|
161
|
+
color: rgba(31,31,28,0.10); pointer-events: none; z-index: 999;
|
|
156
162
|
white-space: nowrap; letter-spacing: 8px; text-transform: uppercase;
|
|
157
163
|
user-select: none;
|
|
158
164
|
}
|
|
159
165
|
|
|
160
166
|
/* ── Page breaks ────────────────────────────────────────────────────────── */
|
|
161
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
|
+
}
|
|
162
177
|
/* Keep a failed test row glued to the START of its inline failure detail so the
|
|
163
178
|
title never orphans above it. The detail card itself may break internally (its
|
|
164
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.1",
|
|
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",
|