@reportforge/playwright-pdf 0.13.2 → 0.14.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 +22 -0
- package/README.md +3 -2
- package/dist/cli/index.js +28 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +28 -2
- package/dist/index.mjs +28 -2
- package/dist/templates/layouts/detailed.hbs +2 -0
- package/dist/templates/layouts/executive.hbs +4 -2
- package/dist/templates/layouts/minimal.hbs +2 -0
- package/dist/templates/partials/brief-band.hbs +6 -0
- package/dist/templates/partials/chart-summary-grid.hbs +18 -0
- package/dist/templates/partials/chart-trend-card.hbs +16 -0
- package/dist/templates/partials/charts.hbs +19 -34
- package/dist/templates/partials/executive-hero-summary.hbs +66 -0
- package/dist/templates/styles/base.css +22 -0
- package/dist/templates/styles/executive.css +31 -47
- package/dist/templates/styles/minimal.css +9 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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.14.1] - 2026-07-06
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Executive report's pass-rate doughnut centre text no longer overlaps the ring.** The `executive` template renders the doughnut at 150px (vs 186px on `detailed`/`minimal`), but the centre-text plugin drew a fixed 28px/9px label sized for the larger canvas. Text now scales off the doughnut's actual inner radius, so it fits cleanly at any canvas size.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [0.14.0] - 2026-07-06
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Executive report leads with a plain-language brief and a hero pass-rate stat**: a new tinted brief band composes the trend delta and root-cause tally into one sentence (e.g. "Pass rate climbed 4 points to 92%... 3 root causes remain: 2 assertion, 1 timeout"), replacing the 6-card KPI grid with a single hero pass-rate number and a compact secondary stat row. The trend chart is promoted above the smaller pass-rate/suite-results charts. `detailed` and `minimal` templates are unchanged.
|
|
19
|
+
- **New `sections.briefBand` option** to toggle the brief band on any template (on by default for `executive`).
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Flaky doughnut slice now matches the `--flaky` design token** instead of a stray hardcoded color.
|
|
24
|
+
- **`minimal` template's header no longer uses a side-stripe border**, now consistent with the design system's top-accent-bar convention used everywhere else.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
6
28
|
## [0.13.2] - 2026-07-04
|
|
7
29
|
|
|
8
30
|
### Fixed
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Three templates, one reporter. Every PDF is fully offline: fonts, charts, and sc
|
|
|
37
37
|
<tr>
|
|
38
38
|
<td align="center"><sub>KPI dashboard · suite breakdown · failure stack traces · embedded screenshots</sub></td>
|
|
39
39
|
<td align="center"><sub>Everything in minimal <strong>+ charts · requirements matrix · defect log · full environment</strong></sub></td>
|
|
40
|
-
<td align="center"><sub>Cover page ·
|
|
40
|
+
<td align="center"><sub>Cover page · plain-language brief · hero pass-rate stat · trend chart · failure summary <em>(no stack traces)</em></sub></td>
|
|
41
41
|
</tr>
|
|
42
42
|
</table>
|
|
43
43
|
|
|
@@ -61,6 +61,7 @@ Three templates, one reporter. Every PDF is fully offline: fonts, charts, and sc
|
|
|
61
61
|
- `npx @reportforge/playwright-pdf export-feedback`: exports locally-collected unrecognized failures (tokenized, local-only) for optional manual sharing.
|
|
62
62
|
- **Rich execution capture**: opt-in "Steps to Reproduce" outline (a nested, copy-pasteable Markdown list of your `test.step`s and assertions; add `apiSteps` for the full click/fill/check trail), Node console tail, and trace/video links under each failed test in the defect log. Read straight from the Playwright step tree: no fixtures, no test-code changes. Enable via `capture` (`steps` / `apiSteps` / `console` / `evidence`); off by default.
|
|
63
63
|
- **Configurable sections**: add or remove any report section per template via `sections`.
|
|
64
|
+
- **Executive report brief band**: leads with a plain-language brief (trend + root causes) and one hero pass-rate number instead of a flat KPI grid
|
|
64
65
|
- **Hybrid licensing**: one short key unlocks it; reports keep rendering offline via a cached JWT between refreshes
|
|
65
66
|
|
|
66
67
|
---
|
|
@@ -204,7 +205,7 @@ All options are the second element of the reporter tuple.
|
|
|
204
205
|
| `flakinessTopN` | `number` | `5` | Maximum flaky tests to show in the flakiness table (`detailed` template). Set to `0` to disable the table entirely. |
|
|
205
206
|
| `slowTestThreshold` | `number` | `10` | Minimum timed-test count before `SLOW` badges appear in the suite breakdown: below this, every test is trivially the "slowest" so badges stay hidden. On larger runs only genuine outliers (at least 2× the median test duration) are badged, so the badge stays rare. Set to `0` to drop the run-size gate (outliers still required). |
|
|
206
207
|
| `templatePath` | `string \| string[]` | n/a | Path to a custom Handlebars (`.hbs`) template file. Takes precedence over `template`. Pass an array to generate one PDF per custom template. See the Custom Templates docs. |
|
|
207
|
-
| `sections` | `object` | per-template | Override which report sections appear, on top of the chosen template's defaults. Flat keys are the baseline for every chosen template; per-template keys (`minimal`\|`detailed`\|`executive`) override per template. Block toggles: `coverPage`, `analysisOneliner`, `releaseGate`, `summary`, `charts`, `trend`, `requirementsMatrix`, `ciEnvironment`, `suiteBreakdown`, `failureDeepDive`, `failureAnalysis`, `slowTests`, `defectLog`. Display modifiers: `passRate`, `fullEnvironment`, `retries`, `fullFailures`, `stackTraces`. See the Report Sections docs. |
|
|
208
|
+
| `sections` | `object` | per-template | Override which report sections appear, on top of the chosen template's defaults. Flat keys are the baseline for every chosen template; per-template keys (`minimal`\|`detailed`\|`executive`) override per template. Block toggles: `coverPage`, `analysisOneliner`, `releaseGate`, `summary`, `charts`, `trend`, `requirementsMatrix`, `ciEnvironment`, `suiteBreakdown`, `failureDeepDive`, `failureAnalysis`, `slowTests`, `defectLog`, `briefBand`. Display modifiers: `passRate`, `fullEnvironment`, `retries`, `fullFailures`, `stackTraces`. See the Report Sections docs. |
|
|
208
209
|
<!-- AUTOGEN:options-table END -->
|
|
209
210
|
|
|
210
211
|
Full reference: [reportforge.org/docs/configuration](https://reportforge.org/docs/configuration).
|
package/dist/cli/index.js
CHANGED
|
@@ -6388,6 +6388,25 @@ function buildOneLiner(clusters) {
|
|
|
6388
6388
|
function dominantCategory(clusters) {
|
|
6389
6389
|
return clusters[0]?.category ?? "unknown";
|
|
6390
6390
|
}
|
|
6391
|
+
function trendClause(passRate, delta) {
|
|
6392
|
+
if (delta === 0) return `Pass rate held steady at ${passRate}%.`;
|
|
6393
|
+
const verb = delta > 0 ? "climbed" : "dropped";
|
|
6394
|
+
const abs = Math.abs(delta);
|
|
6395
|
+
const points = abs === 1 ? "point" : "points";
|
|
6396
|
+
return `Pass rate ${verb} ${abs} ${points} to ${passRate}% since the last run.`;
|
|
6397
|
+
}
|
|
6398
|
+
function causesClause(analysis) {
|
|
6399
|
+
const [head, ...tallyParts] = buildOneLiner(analysis.clusters).split(" \xB7 ");
|
|
6400
|
+
const verb = analysis.clusters.length === 1 ? "remains" : "remain";
|
|
6401
|
+
return `${head} ${verb}: ${tallyParts.join(", ")}.`;
|
|
6402
|
+
}
|
|
6403
|
+
function buildBriefSentence(stats, analysis, trend) {
|
|
6404
|
+
if (trend && trend.delta !== null) {
|
|
6405
|
+
const trendSentence = trendClause(stats.passRate, trend.delta);
|
|
6406
|
+
return analysis && analysis.clusters.length > 0 ? `${trendSentence} ${causesClause(analysis)}` : trendSentence;
|
|
6407
|
+
}
|
|
6408
|
+
return analysis && analysis.clusters.length > 0 ? causesClause(analysis) : void 0;
|
|
6409
|
+
}
|
|
6391
6410
|
var CATEGORY_LABEL, SHORT_LABEL;
|
|
6392
6411
|
var init_SummaryWriter = __esm({
|
|
6393
6412
|
"src/analysis/SummaryWriter.ts"() {
|
|
@@ -6457,6 +6476,7 @@ var init_sections = __esm({
|
|
|
6457
6476
|
"failureAnalysis",
|
|
6458
6477
|
"slowTests",
|
|
6459
6478
|
"defectLog",
|
|
6479
|
+
"briefBand",
|
|
6460
6480
|
// display modifiers
|
|
6461
6481
|
"passRate",
|
|
6462
6482
|
"fullEnvironment",
|
|
@@ -6479,6 +6499,7 @@ var init_sections = __esm({
|
|
|
6479
6499
|
failureAnalysis: false,
|
|
6480
6500
|
slowTests: false,
|
|
6481
6501
|
defectLog: false,
|
|
6502
|
+
briefBand: false,
|
|
6482
6503
|
passRate: true,
|
|
6483
6504
|
fullEnvironment: false,
|
|
6484
6505
|
retries: true,
|
|
@@ -6499,6 +6520,7 @@ var init_sections = __esm({
|
|
|
6499
6520
|
failureAnalysis: true,
|
|
6500
6521
|
slowTests: true,
|
|
6501
6522
|
defectLog: true,
|
|
6523
|
+
briefBand: false,
|
|
6502
6524
|
passRate: true,
|
|
6503
6525
|
fullEnvironment: true,
|
|
6504
6526
|
retries: true,
|
|
@@ -6507,7 +6529,9 @@ var init_sections = __esm({
|
|
|
6507
6529
|
},
|
|
6508
6530
|
executive: {
|
|
6509
6531
|
coverPage: true,
|
|
6510
|
-
|
|
6532
|
+
// Now off: its content (root-cause tally) is subsumed by briefBand below,
|
|
6533
|
+
// which also adds the trend delta. Showing both would say it twice.
|
|
6534
|
+
analysisOneliner: false,
|
|
6511
6535
|
releaseGate: true,
|
|
6512
6536
|
summary: true,
|
|
6513
6537
|
charts: true,
|
|
@@ -6522,6 +6546,7 @@ var init_sections = __esm({
|
|
|
6522
6546
|
failureAnalysis: false,
|
|
6523
6547
|
slowTests: true,
|
|
6524
6548
|
defectLog: false,
|
|
6549
|
+
briefBand: true,
|
|
6525
6550
|
passRate: true,
|
|
6526
6551
|
fullEnvironment: false,
|
|
6527
6552
|
retries: false,
|
|
@@ -12502,6 +12527,7 @@ async function run() {
|
|
|
12502
12527
|
const options = parseOptions({ template, outputFile, failureAnalysis: { collectUnclassified: false } });
|
|
12503
12528
|
const generator = new PdfGenerator();
|
|
12504
12529
|
runFailureAnalysis(reportData, options, process.cwd());
|
|
12530
|
+
reportData.briefSentence = buildBriefSentence(reportData.stats, reportData.analysis, reportData.charts.trend);
|
|
12505
12531
|
const paths = await generator.generateAll(reportData, options, DEMO_LICENSE_INFO2);
|
|
12506
12532
|
if (paths.length > 0) {
|
|
12507
12533
|
console.log(`
|
|
@@ -12542,6 +12568,7 @@ var init_cli = __esm({
|
|
|
12542
12568
|
init_schema();
|
|
12543
12569
|
init_types2();
|
|
12544
12570
|
init_analysis();
|
|
12571
|
+
init_SummaryWriter();
|
|
12545
12572
|
DEMO_LICENSE_INFO2 = {
|
|
12546
12573
|
plan: "subscription",
|
|
12547
12574
|
source: "demo",
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Reporter, FullConfig, Suite, TestCase, TestResult, TestStep, FullResult } from '@playwright/test/reporter';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Canonical section catalog.
|
|
4
|
+
* Canonical section catalog. 14 block toggles (section present/absent) followed
|
|
5
5
|
* by 5 display modifiers (tweak a section that is on). Single source of truth —
|
|
6
6
|
* the Zod schema in src/config/schema.ts builds its flag shape from this array.
|
|
7
7
|
*/
|
|
8
|
-
declare const SECTION_KEYS: readonly ["coverPage", "analysisOneliner", "releaseGate", "summary", "charts", "trend", "requirementsMatrix", "ciEnvironment", "suiteBreakdown", "failureDeepDive", "failureAnalysis", "slowTests", "defectLog", "passRate", "fullEnvironment", "retries", "fullFailures", "stackTraces"];
|
|
8
|
+
declare const SECTION_KEYS: readonly ["coverPage", "analysisOneliner", "releaseGate", "summary", "charts", "trend", "requirementsMatrix", "ciEnvironment", "suiteBreakdown", "failureDeepDive", "failureAnalysis", "slowTests", "defectLog", "briefBand", "passRate", "fullEnvironment", "retries", "fullFailures", "stackTraces"];
|
|
9
9
|
type SectionKey = (typeof SECTION_KEYS)[number];
|
|
10
10
|
/** A partial map of section flags — used both as the flat baseline and per-template. */
|
|
11
11
|
type SectionFlags = Partial<Record<SectionKey, boolean>>;
|
|
@@ -41,7 +41,7 @@ interface ReporterOptions {
|
|
|
41
41
|
*
|
|
42
42
|
* - minimal: clean white layout, developer-focused, fast
|
|
43
43
|
* - detailed: full QA report with defect log and requirements traceability
|
|
44
|
-
* - executive: cover page,
|
|
44
|
+
* - executive: cover page, plain-language brief, hero pass-rate stat, charts for stakeholders
|
|
45
45
|
* @default 'minimal'
|
|
46
46
|
*/
|
|
47
47
|
template?: TemplateId | TemplateId[];
|
|
@@ -332,7 +332,7 @@ interface ReporterOptions {
|
|
|
332
332
|
*
|
|
333
333
|
* Block toggles: `coverPage`, `analysisOneliner`, `releaseGate`, `summary`,
|
|
334
334
|
* `charts`, `trend`, `requirementsMatrix`, `ciEnvironment`, `suiteBreakdown`,
|
|
335
|
-
* `failureDeepDive`, `failureAnalysis`, `slowTests`, `defectLog`.
|
|
335
|
+
* `failureDeepDive`, `failureAnalysis`, `slowTests`, `defectLog`, `briefBand`.
|
|
336
336
|
* Display modifiers: `passRate`, `fullEnvironment`, `retries`, `fullFailures`,
|
|
337
337
|
* `stackTraces`.
|
|
338
338
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Reporter, FullConfig, Suite, TestCase, TestResult, TestStep, FullResult } from '@playwright/test/reporter';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Canonical section catalog.
|
|
4
|
+
* Canonical section catalog. 14 block toggles (section present/absent) followed
|
|
5
5
|
* by 5 display modifiers (tweak a section that is on). Single source of truth —
|
|
6
6
|
* the Zod schema in src/config/schema.ts builds its flag shape from this array.
|
|
7
7
|
*/
|
|
8
|
-
declare const SECTION_KEYS: readonly ["coverPage", "analysisOneliner", "releaseGate", "summary", "charts", "trend", "requirementsMatrix", "ciEnvironment", "suiteBreakdown", "failureDeepDive", "failureAnalysis", "slowTests", "defectLog", "passRate", "fullEnvironment", "retries", "fullFailures", "stackTraces"];
|
|
8
|
+
declare const SECTION_KEYS: readonly ["coverPage", "analysisOneliner", "releaseGate", "summary", "charts", "trend", "requirementsMatrix", "ciEnvironment", "suiteBreakdown", "failureDeepDive", "failureAnalysis", "slowTests", "defectLog", "briefBand", "passRate", "fullEnvironment", "retries", "fullFailures", "stackTraces"];
|
|
9
9
|
type SectionKey = (typeof SECTION_KEYS)[number];
|
|
10
10
|
/** A partial map of section flags — used both as the flat baseline and per-template. */
|
|
11
11
|
type SectionFlags = Partial<Record<SectionKey, boolean>>;
|
|
@@ -41,7 +41,7 @@ interface ReporterOptions {
|
|
|
41
41
|
*
|
|
42
42
|
* - minimal: clean white layout, developer-focused, fast
|
|
43
43
|
* - detailed: full QA report with defect log and requirements traceability
|
|
44
|
-
* - executive: cover page,
|
|
44
|
+
* - executive: cover page, plain-language brief, hero pass-rate stat, charts for stakeholders
|
|
45
45
|
* @default 'minimal'
|
|
46
46
|
*/
|
|
47
47
|
template?: TemplateId | TemplateId[];
|
|
@@ -332,7 +332,7 @@ interface ReporterOptions {
|
|
|
332
332
|
*
|
|
333
333
|
* Block toggles: `coverPage`, `analysisOneliner`, `releaseGate`, `summary`,
|
|
334
334
|
* `charts`, `trend`, `requirementsMatrix`, `ciEnvironment`, `suiteBreakdown`,
|
|
335
|
-
* `failureDeepDive`, `failureAnalysis`, `slowTests`, `defectLog`.
|
|
335
|
+
* `failureDeepDive`, `failureAnalysis`, `slowTests`, `defectLog`, `briefBand`.
|
|
336
336
|
* Display modifiers: `passRate`, `fullEnvironment`, `retries`, `fullFailures`,
|
|
337
337
|
* `stackTraces`.
|
|
338
338
|
*
|
package/dist/index.js
CHANGED
|
@@ -10350,6 +10350,7 @@ var SECTION_KEYS = [
|
|
|
10350
10350
|
"failureAnalysis",
|
|
10351
10351
|
"slowTests",
|
|
10352
10352
|
"defectLog",
|
|
10353
|
+
"briefBand",
|
|
10353
10354
|
// display modifiers
|
|
10354
10355
|
"passRate",
|
|
10355
10356
|
"fullEnvironment",
|
|
@@ -10372,6 +10373,7 @@ var SECTION_DEFAULTS = {
|
|
|
10372
10373
|
failureAnalysis: false,
|
|
10373
10374
|
slowTests: false,
|
|
10374
10375
|
defectLog: false,
|
|
10376
|
+
briefBand: false,
|
|
10375
10377
|
passRate: true,
|
|
10376
10378
|
fullEnvironment: false,
|
|
10377
10379
|
retries: true,
|
|
@@ -10392,6 +10394,7 @@ var SECTION_DEFAULTS = {
|
|
|
10392
10394
|
failureAnalysis: true,
|
|
10393
10395
|
slowTests: true,
|
|
10394
10396
|
defectLog: true,
|
|
10397
|
+
briefBand: false,
|
|
10395
10398
|
passRate: true,
|
|
10396
10399
|
fullEnvironment: true,
|
|
10397
10400
|
retries: true,
|
|
@@ -10400,7 +10403,9 @@ var SECTION_DEFAULTS = {
|
|
|
10400
10403
|
},
|
|
10401
10404
|
executive: {
|
|
10402
10405
|
coverPage: true,
|
|
10403
|
-
|
|
10406
|
+
// Now off: its content (root-cause tally) is subsumed by briefBand below,
|
|
10407
|
+
// which also adds the trend delta. Showing both would say it twice.
|
|
10408
|
+
analysisOneliner: false,
|
|
10404
10409
|
releaseGate: true,
|
|
10405
10410
|
summary: true,
|
|
10406
10411
|
charts: true,
|
|
@@ -10415,6 +10420,7 @@ var SECTION_DEFAULTS = {
|
|
|
10415
10420
|
failureAnalysis: false,
|
|
10416
10421
|
slowTests: true,
|
|
10417
10422
|
defectLog: false,
|
|
10423
|
+
briefBand: true,
|
|
10418
10424
|
passRate: true,
|
|
10419
10425
|
fullEnvironment: false,
|
|
10420
10426
|
retries: false,
|
|
@@ -11298,6 +11304,25 @@ function buildOneLiner(clusters) {
|
|
|
11298
11304
|
function dominantCategory(clusters) {
|
|
11299
11305
|
return clusters[0]?.category ?? "unknown";
|
|
11300
11306
|
}
|
|
11307
|
+
function trendClause(passRate, delta) {
|
|
11308
|
+
if (delta === 0) return `Pass rate held steady at ${passRate}%.`;
|
|
11309
|
+
const verb = delta > 0 ? "climbed" : "dropped";
|
|
11310
|
+
const abs = Math.abs(delta);
|
|
11311
|
+
const points = abs === 1 ? "point" : "points";
|
|
11312
|
+
return `Pass rate ${verb} ${abs} ${points} to ${passRate}% since the last run.`;
|
|
11313
|
+
}
|
|
11314
|
+
function causesClause(analysis) {
|
|
11315
|
+
const [head, ...tallyParts] = buildOneLiner(analysis.clusters).split(" \xB7 ");
|
|
11316
|
+
const verb = analysis.clusters.length === 1 ? "remains" : "remain";
|
|
11317
|
+
return `${head} ${verb}: ${tallyParts.join(", ")}.`;
|
|
11318
|
+
}
|
|
11319
|
+
function buildBriefSentence(stats, analysis, trend) {
|
|
11320
|
+
if (trend && trend.delta !== null) {
|
|
11321
|
+
const trendSentence = trendClause(stats.passRate, trend.delta);
|
|
11322
|
+
return analysis && analysis.clusters.length > 0 ? `${trendSentence} ${causesClause(analysis)}` : trendSentence;
|
|
11323
|
+
}
|
|
11324
|
+
return analysis && analysis.clusters.length > 0 ? causesClause(analysis) : void 0;
|
|
11325
|
+
}
|
|
11301
11326
|
|
|
11302
11327
|
// src/analysis/UnclassifiedCollector.ts
|
|
11303
11328
|
init_cjs_shims();
|
|
@@ -14031,7 +14056,7 @@ var PdfReporter = class {
|
|
|
14031
14056
|
this.liveConsole = false;
|
|
14032
14057
|
this.options = parseOptions(rawOptions);
|
|
14033
14058
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14034
|
-
const version = true ? "0.
|
|
14059
|
+
const version = true ? "0.14.1" : "0.x";
|
|
14035
14060
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
14036
14061
|
this.licenseClient = new LicenseClient({
|
|
14037
14062
|
licenseKey: this.options.licenseKey,
|
|
@@ -14115,6 +14140,7 @@ var PdfReporter = class {
|
|
|
14115
14140
|
}
|
|
14116
14141
|
const reportData = this._buildReportData(collected, licenseInfo);
|
|
14117
14142
|
runFailureAnalysis(reportData, this.options, this.cwd);
|
|
14143
|
+
reportData.briefSentence = buildBriefSentence(reportData.stats, reportData.analysis, reportData.charts.trend);
|
|
14118
14144
|
let pdfPaths = [];
|
|
14119
14145
|
try {
|
|
14120
14146
|
pdfPaths = await this.pdfGenerator.generateAll(reportData, this.options, licenseInfo);
|
package/dist/index.mjs
CHANGED
|
@@ -10351,6 +10351,7 @@ var SECTION_KEYS = [
|
|
|
10351
10351
|
"failureAnalysis",
|
|
10352
10352
|
"slowTests",
|
|
10353
10353
|
"defectLog",
|
|
10354
|
+
"briefBand",
|
|
10354
10355
|
// display modifiers
|
|
10355
10356
|
"passRate",
|
|
10356
10357
|
"fullEnvironment",
|
|
@@ -10373,6 +10374,7 @@ var SECTION_DEFAULTS = {
|
|
|
10373
10374
|
failureAnalysis: false,
|
|
10374
10375
|
slowTests: false,
|
|
10375
10376
|
defectLog: false,
|
|
10377
|
+
briefBand: false,
|
|
10376
10378
|
passRate: true,
|
|
10377
10379
|
fullEnvironment: false,
|
|
10378
10380
|
retries: true,
|
|
@@ -10393,6 +10395,7 @@ var SECTION_DEFAULTS = {
|
|
|
10393
10395
|
failureAnalysis: true,
|
|
10394
10396
|
slowTests: true,
|
|
10395
10397
|
defectLog: true,
|
|
10398
|
+
briefBand: false,
|
|
10396
10399
|
passRate: true,
|
|
10397
10400
|
fullEnvironment: true,
|
|
10398
10401
|
retries: true,
|
|
@@ -10401,7 +10404,9 @@ var SECTION_DEFAULTS = {
|
|
|
10401
10404
|
},
|
|
10402
10405
|
executive: {
|
|
10403
10406
|
coverPage: true,
|
|
10404
|
-
|
|
10407
|
+
// Now off: its content (root-cause tally) is subsumed by briefBand below,
|
|
10408
|
+
// which also adds the trend delta. Showing both would say it twice.
|
|
10409
|
+
analysisOneliner: false,
|
|
10405
10410
|
releaseGate: true,
|
|
10406
10411
|
summary: true,
|
|
10407
10412
|
charts: true,
|
|
@@ -10416,6 +10421,7 @@ var SECTION_DEFAULTS = {
|
|
|
10416
10421
|
failureAnalysis: false,
|
|
10417
10422
|
slowTests: true,
|
|
10418
10423
|
defectLog: false,
|
|
10424
|
+
briefBand: true,
|
|
10419
10425
|
passRate: true,
|
|
10420
10426
|
fullEnvironment: false,
|
|
10421
10427
|
retries: false,
|
|
@@ -11299,6 +11305,25 @@ function buildOneLiner(clusters) {
|
|
|
11299
11305
|
function dominantCategory(clusters) {
|
|
11300
11306
|
return clusters[0]?.category ?? "unknown";
|
|
11301
11307
|
}
|
|
11308
|
+
function trendClause(passRate, delta) {
|
|
11309
|
+
if (delta === 0) return `Pass rate held steady at ${passRate}%.`;
|
|
11310
|
+
const verb = delta > 0 ? "climbed" : "dropped";
|
|
11311
|
+
const abs = Math.abs(delta);
|
|
11312
|
+
const points = abs === 1 ? "point" : "points";
|
|
11313
|
+
return `Pass rate ${verb} ${abs} ${points} to ${passRate}% since the last run.`;
|
|
11314
|
+
}
|
|
11315
|
+
function causesClause(analysis) {
|
|
11316
|
+
const [head, ...tallyParts] = buildOneLiner(analysis.clusters).split(" \xB7 ");
|
|
11317
|
+
const verb = analysis.clusters.length === 1 ? "remains" : "remain";
|
|
11318
|
+
return `${head} ${verb}: ${tallyParts.join(", ")}.`;
|
|
11319
|
+
}
|
|
11320
|
+
function buildBriefSentence(stats, analysis, trend) {
|
|
11321
|
+
if (trend && trend.delta !== null) {
|
|
11322
|
+
const trendSentence = trendClause(stats.passRate, trend.delta);
|
|
11323
|
+
return analysis && analysis.clusters.length > 0 ? `${trendSentence} ${causesClause(analysis)}` : trendSentence;
|
|
11324
|
+
}
|
|
11325
|
+
return analysis && analysis.clusters.length > 0 ? causesClause(analysis) : void 0;
|
|
11326
|
+
}
|
|
11302
11327
|
|
|
11303
11328
|
// src/analysis/UnclassifiedCollector.ts
|
|
11304
11329
|
init_esm_shims();
|
|
@@ -14032,7 +14057,7 @@ var PdfReporter = class {
|
|
|
14032
14057
|
this.liveConsole = false;
|
|
14033
14058
|
this.options = parseOptions(rawOptions);
|
|
14034
14059
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14035
|
-
const version = true ? "0.
|
|
14060
|
+
const version = true ? "0.14.1" : "0.x";
|
|
14036
14061
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
14037
14062
|
this.licenseClient = new LicenseClient({
|
|
14038
14063
|
licenseKey: this.options.licenseKey,
|
|
@@ -14116,6 +14141,7 @@ var PdfReporter = class {
|
|
|
14116
14141
|
}
|
|
14117
14142
|
const reportData = this._buildReportData(collected, licenseInfo);
|
|
14118
14143
|
runFailureAnalysis(reportData, this.options, this.cwd);
|
|
14144
|
+
reportData.briefSentence = buildBriefSentence(reportData.stats, reportData.analysis, reportData.charts.trend);
|
|
14119
14145
|
let pdfPaths = [];
|
|
14120
14146
|
try {
|
|
14121
14147
|
pdfPaths = await this.pdfGenerator.generateAll(reportData, this.options, licenseInfo);
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
{{#if sections.releaseGate}}{{> release-gate}}{{/if}}
|
|
20
20
|
|
|
21
|
+
{{#if sections.briefBand}}{{> brief-band}}{{/if}}
|
|
22
|
+
|
|
21
23
|
{{#if sections.summary}}{{> executive-summary options=(obj showPassRate=sections.passRate releaseGateShown=sections.releaseGate) }}{{/if}}
|
|
22
24
|
|
|
23
25
|
{{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend) }}{{/if}}
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
{{#if sections.releaseGate}}{{> release-gate}}{{/if}}
|
|
20
20
|
|
|
21
|
-
{{#if sections.
|
|
21
|
+
{{#if sections.briefBand}}{{> brief-band}}{{/if}}
|
|
22
22
|
|
|
23
|
-
{{#if sections.
|
|
23
|
+
{{#if sections.summary}}{{> executive-hero-summary options=(obj showPassRate=sections.passRate releaseGateShown=sections.releaseGate) }}{{/if}}
|
|
24
|
+
|
|
25
|
+
{{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend heroTrend=true) }}{{/if}}
|
|
24
26
|
|
|
25
27
|
{{#if sections.requirementsMatrix}}{{> requirements-matrix }}{{/if}}
|
|
26
28
|
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
{{#if sections.releaseGate}}{{> release-gate}}{{/if}}
|
|
20
20
|
|
|
21
|
+
{{#if sections.briefBand}}{{> brief-band}}{{/if}}
|
|
22
|
+
|
|
21
23
|
{{#if sections.summary}}{{> executive-summary options=(obj showPassRate=sections.passRate releaseGateShown=sections.releaseGate) }}{{/if}}
|
|
22
24
|
|
|
23
25
|
{{#if sections.charts}}{{> charts options=(obj showCharts=true showTrend=sections.trend) }}{{/if}}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<div class="section charts-grid{{#if options.heroTrend}} charts-grid--secondary{{/if}}">
|
|
2
|
+
<div class="chart-card">
|
|
3
|
+
<h3>Pass Rate</h3>
|
|
4
|
+
{{#if options.heroTrend}}
|
|
5
|
+
<canvas id="passRateChart" width="150" height="150"></canvas>
|
|
6
|
+
{{else}}
|
|
7
|
+
<canvas id="passRateChart" width="186" height="186"></canvas>
|
|
8
|
+
{{/if}}
|
|
9
|
+
</div>
|
|
10
|
+
<div class="chart-card">
|
|
11
|
+
<h3>Suite Results</h3>
|
|
12
|
+
{{#if options.heroTrend}}
|
|
13
|
+
<canvas id="suitesBarChart" width="300" height="150"></canvas>
|
|
14
|
+
{{else}}
|
|
15
|
+
<canvas id="suitesBarChart" width="390" height="186"></canvas>
|
|
16
|
+
{{/if}}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{{#if options.showTrend}}
|
|
2
|
+
{{#if charts.trend}}
|
|
3
|
+
<div class="trend-card">
|
|
4
|
+
<div class="trend-card__header">
|
|
5
|
+
<span class="trend-card__title">Pass rate trend — last {{charts.trend.entries.length}} runs</span>
|
|
6
|
+
{{#if charts.trend.delta}}
|
|
7
|
+
<span class="delta {{#if (gt charts.trend.delta 0)}}delta-up{{else}}delta-down{{/if}}">
|
|
8
|
+
{{#if (gt charts.trend.delta 0)}}↑{{else}}↓{{/if}} {{absFixed charts.trend.delta}}% vs previous run
|
|
9
|
+
</span>
|
|
10
|
+
{{/if}}
|
|
11
|
+
</div>
|
|
12
|
+
<canvas id="trendChart" width="680" height="160"></canvas>
|
|
13
|
+
<div class="trend-run-history" id="run-history-table"></div>
|
|
14
|
+
</div>
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{/if}}
|
|
@@ -1,33 +1,10 @@
|
|
|
1
1
|
{{#if options.showCharts}}
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<h3>Suite Results</h3>
|
|
9
|
-
<canvas id="suitesBarChart" width="390" height="186"></canvas>
|
|
10
|
-
</div>
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
{{!-- Trend card: detailed + executive + custom templates. Minimal has showTrend=false. --}}
|
|
14
|
-
{{#if options.showTrend}}
|
|
15
|
-
{{#if charts.trend}}
|
|
16
|
-
<div class="trend-card">
|
|
17
|
-
<div class="trend-card__header">
|
|
18
|
-
<span class="trend-card__title">Pass rate trend — last {{charts.trend.entries.length}} runs</span>
|
|
19
|
-
{{!-- Delta badge: falsy guard handles null (< 2 entries) and 0 (identical runs). --}}
|
|
20
|
-
{{#if charts.trend.delta}}
|
|
21
|
-
<span class="delta {{#if (gt charts.trend.delta 0)}}delta-up{{else}}delta-down{{/if}}">
|
|
22
|
-
{{#if (gt charts.trend.delta 0)}}↑{{else}}↓{{/if}} {{absFixed charts.trend.delta}}% vs previous run
|
|
23
|
-
</span>
|
|
24
|
-
{{/if}}
|
|
25
|
-
</div>
|
|
26
|
-
<canvas id="trendChart" width="680" height="160"></canvas>
|
|
27
|
-
{{!-- Run history table is populated by JS below; placeholder must exist before script runs. --}}
|
|
28
|
-
<div class="trend-run-history" id="run-history-table"></div>
|
|
29
|
-
</div>
|
|
30
|
-
{{/if}}
|
|
2
|
+
{{#if options.heroTrend}}
|
|
3
|
+
{{> chart-trend-card options=options}}
|
|
4
|
+
{{> chart-summary-grid options=options}}
|
|
5
|
+
{{else}}
|
|
6
|
+
{{> chart-summary-grid options=options}}
|
|
7
|
+
{{> chart-trend-card options=options}}
|
|
31
8
|
{{/if}}
|
|
32
9
|
|
|
33
10
|
{{#if options.showTrend}}
|
|
@@ -88,17 +65,25 @@
|
|
|
88
65
|
var ctx = chart.ctx;
|
|
89
66
|
var cx = (chart.chartArea.left + chart.chartArea.right) / 2;
|
|
90
67
|
var cy = (chart.chartArea.top + chart.chartArea.bottom) / 2;
|
|
68
|
+
/* Size the centre text off the doughnut's actual hole, not a fixed px
|
|
69
|
+
value — the executive template's 150px canvas has a smaller inner
|
|
70
|
+
radius than the 186px canvas used elsewhere, and fixed 28px/9px text
|
|
71
|
+
overran the hole into the ring on the smaller size. */
|
|
72
|
+
var arc = chart.getDatasetMeta(0).data[0];
|
|
73
|
+
var innerRadius = (arc && arc.innerRadius) ? arc.innerRadius : 50;
|
|
74
|
+
var mainSize = Math.max(15, Math.round(innerRadius * 0.56));
|
|
75
|
+
var subSize = Math.max(7, Math.round(innerRadius * 0.175));
|
|
91
76
|
ctx.save();
|
|
92
77
|
ctx.textAlign = 'center';
|
|
93
78
|
ctx.textBaseline = 'middle';
|
|
94
79
|
/* large percentage */
|
|
95
|
-
ctx.font = '700
|
|
80
|
+
ctx.font = '700 ' + mainSize + 'px "Source Serif Pro", Georgia, serif';
|
|
96
81
|
ctx.fillStyle = '#1F1F1C';
|
|
97
|
-
ctx.fillText(passRate + '%', cx, cy -
|
|
82
|
+
ctx.fillText(passRate + '%', cx, cy - mainSize * 0.24);
|
|
98
83
|
/* label below */
|
|
99
|
-
ctx.font = '600
|
|
84
|
+
ctx.font = '600 ' + subSize + 'px Inter, sans-serif';
|
|
100
85
|
ctx.fillStyle = '#8C8B82';
|
|
101
|
-
ctx.fillText('PASS RATE', cx, cy +
|
|
86
|
+
ctx.fillText('PASS RATE', cx, cy + mainSize * 0.46);
|
|
102
87
|
ctx.restore();
|
|
103
88
|
}
|
|
104
89
|
};
|
|
@@ -112,7 +97,7 @@
|
|
|
112
97
|
labels: ['Passed', 'Failed', 'Timed Out', 'Skipped', 'Flaky'],
|
|
113
98
|
datasets: [{
|
|
114
99
|
data: [passed, failed, timedOut, skipped, flaky],
|
|
115
|
-
backgroundColor: ['#3F7D58', '#C3553C', '#B5803A', '#7A786E', '#
|
|
100
|
+
backgroundColor: ['#3F7D58', '#C3553C', '#B5803A', '#7A786E', '#CA8A04'],
|
|
116
101
|
borderColor: ['#FAF9F5', '#FAF9F5', '#FAF9F5', '#FAF9F5', '#FAF9F5'],
|
|
117
102
|
borderWidth: 2,
|
|
118
103
|
hoverOffset: 4
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<div class="section">
|
|
2
|
+
{{#unless options.releaseGateShown}}
|
|
3
|
+
<span class="verdict verdict--{{stats.verdict}}">
|
|
4
|
+
{{verdictLabel stats.verdict}}
|
|
5
|
+
</span>
|
|
6
|
+
{{/unless}}
|
|
7
|
+
|
|
8
|
+
<div class="hero-summary">
|
|
9
|
+
<div class="hero-summary__value">{{stats.passRate}}<span class="hero-summary__pct">%</span></div>
|
|
10
|
+
<div class="hero-summary__label">Pass Rate</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="hero-stat-row">
|
|
14
|
+
<div class="hero-stat">
|
|
15
|
+
<span class="hero-stat__value">{{stats.total}}</span>
|
|
16
|
+
<span class="hero-stat__label">Total</span>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="hero-stat{{#unless stats.failed}} hero-stat--zero{{/unless}}">
|
|
19
|
+
<span class="hero-stat__value" style="color:var(--fail);">{{stats.failed}}</span>
|
|
20
|
+
<span class="hero-stat__label">Failed</span>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="hero-stat{{#unless stats.timedOut}} hero-stat--zero{{/unless}}">
|
|
23
|
+
<span class="hero-stat__value" style="color:var(--timeout);">{{stats.timedOut}}</span>
|
|
24
|
+
<span class="hero-stat__label">Timed Out</span>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="hero-stat{{#unless stats.skipped}} hero-stat--zero{{/unless}}">
|
|
27
|
+
<span class="hero-stat__value" style="color:var(--skip);">{{stats.skipped}}</span>
|
|
28
|
+
<span class="hero-stat__label">Skipped</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="hero-stat{{#unless stats.flaky}} hero-stat--zero{{/unless}}">
|
|
31
|
+
<span class="hero-stat__value" style="color:var(--flaky);">{{stats.flaky}}</span>
|
|
32
|
+
<span class="hero-stat__label">Flaky</span>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="summary-row">
|
|
37
|
+
<div class="summary-row__item">
|
|
38
|
+
<span class="summary-row__label">Duration</span>
|
|
39
|
+
<span class="summary-row__value">{{formatDuration stats.duration}}</span>
|
|
40
|
+
</div>
|
|
41
|
+
{{#unless (or options.showPassRate @root.sections.charts)}}
|
|
42
|
+
<div class="summary-row__item">
|
|
43
|
+
<span class="summary-row__label">Pass Rate</span>
|
|
44
|
+
<span class="summary-row__value" style="color:var(--pass);">{{stats.passRate}}%</span>
|
|
45
|
+
</div>
|
|
46
|
+
{{/unless}}
|
|
47
|
+
<div class="summary-row__item">
|
|
48
|
+
<span class="summary-row__label">Generated</span>
|
|
49
|
+
<span class="summary-row__value">{{formatDate meta.generatedAt}}</span>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="summary-row__item">
|
|
52
|
+
<span class="summary-row__label">Project</span>
|
|
53
|
+
<span class="summary-row__value">{{meta.projectName}}</span>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{{#if stats.flaky}}
|
|
58
|
+
<div class="flaky-callout">
|
|
59
|
+
<span class="flaky-callout__icon">⚠</span>
|
|
60
|
+
<div class="flaky-callout__body">
|
|
61
|
+
<span class="flaky-callout__title">{{stats.flaky}} Flaky Test{{#if (gt stats.flaky 1)}}s{{/if}} Detected</span>
|
|
62
|
+
<span class="flaky-callout__text">These tests passed on retry. Results may vary across re-runs. Investigate before shipping to production.</span>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
{{/if}}
|
|
66
|
+
</div>
|
|
@@ -635,6 +635,28 @@ code {
|
|
|
635
635
|
|
|
636
636
|
/* These section styles ship to every template so any section can be toggled in styled; ~a few KB of unused CSS in templates where the section is off is an accepted cost. */
|
|
637
637
|
|
|
638
|
+
/* ── Brief band — the executive lede sentence, shared so any template can
|
|
639
|
+
toggle briefBand on. No border/side-stripe (deliberately distinct from the
|
|
640
|
+
card recipe used everywhere else); a tinted surface instead. ── */
|
|
641
|
+
.brief-band {
|
|
642
|
+
display: flex; align-items: flex-start; gap: 14px;
|
|
643
|
+
background: var(--surface-2);
|
|
644
|
+
padding: 18px 22px 20px;
|
|
645
|
+
border-radius: 6px;
|
|
646
|
+
margin-bottom: 22px;
|
|
647
|
+
}
|
|
648
|
+
.brief-band__quote {
|
|
649
|
+
font-family: 'Source Serif Pro', Georgia, serif;
|
|
650
|
+
font-size: 40px; font-weight: 700;
|
|
651
|
+
color: var(--accent); opacity: .5; line-height: .6;
|
|
652
|
+
flex-shrink: 0; margin-top: 8px;
|
|
653
|
+
}
|
|
654
|
+
.brief-band__text {
|
|
655
|
+
font-family: 'Source Serif Pro', Georgia, serif;
|
|
656
|
+
font-style: italic; font-size: 15px;
|
|
657
|
+
color: var(--text-1); line-height: 1.55;
|
|
658
|
+
}
|
|
659
|
+
|
|
638
660
|
/* Charts grid + cards (available to any template via sections.charts) */
|
|
639
661
|
.charts-grid {
|
|
640
662
|
display: grid; grid-template-columns: 210px 1fr;
|
|
@@ -28,53 +28,6 @@
|
|
|
28
28
|
font-weight: 500; letter-spacing: 0.4px;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/* ── KPI dashboard ─────────────────────────────────────────────────────── */
|
|
32
|
-
.kpi-strip {
|
|
33
|
-
display: grid; grid-template-columns: repeat(6, 1fr);
|
|
34
|
-
gap: 10px; margin-bottom: 22px;
|
|
35
|
-
}
|
|
36
|
-
.kpi-card {
|
|
37
|
-
background: var(--surface);
|
|
38
|
-
border: 1px solid var(--border-2);
|
|
39
|
-
border-radius: 6px; padding: 18px 12px; text-align: center;
|
|
40
|
-
position: relative; overflow: hidden;
|
|
41
|
-
}
|
|
42
|
-
.kpi-card::before {
|
|
43
|
-
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
|
|
44
|
-
-webkit-print-color-adjust: exact;
|
|
45
|
-
print-color-adjust: exact;
|
|
46
|
-
}
|
|
47
|
-
.kpi-card--total::before { background: var(--accent); }
|
|
48
|
-
.kpi-card--pass::before { background: var(--pass); }
|
|
49
|
-
.kpi-card--fail::before { background: var(--fail); }
|
|
50
|
-
.kpi-card--timedout::before { background: var(--timeout); }
|
|
51
|
-
.kpi-card--skip::before { background: var(--skip); }
|
|
52
|
-
.kpi-card--flaky::before { background: var(--flaky); }
|
|
53
|
-
|
|
54
|
-
.kpi-card__value {
|
|
55
|
-
font-family: 'Source Serif Pro', Georgia, serif;
|
|
56
|
-
font-size: 32px; font-weight: 700; line-height: 1;
|
|
57
|
-
margin-top: 6px;
|
|
58
|
-
letter-spacing: -0.5px;
|
|
59
|
-
color: var(--text-1);
|
|
60
|
-
}
|
|
61
|
-
.kpi-card--total .kpi-card__value { color: var(--info); }
|
|
62
|
-
.kpi-card--pass .kpi-card__value { color: var(--pass); }
|
|
63
|
-
.kpi-card--fail .kpi-card__value { color: var(--fail); }
|
|
64
|
-
.kpi-card--timedout .kpi-card__value { color: var(--timeout); }
|
|
65
|
-
.kpi-card--skip .kpi-card__value { color: var(--skip); }
|
|
66
|
-
.kpi-card--flaky .kpi-card__value { color: var(--flaky); }
|
|
67
|
-
.kpi-card__label {
|
|
68
|
-
font-family: 'Inter', system-ui, sans-serif; font-size: 8px;
|
|
69
|
-
font-weight: 600; letter-spacing: 1.2px; text-transform: uppercase;
|
|
70
|
-
color: var(--text-3); margin-top: 8px;
|
|
71
|
-
}
|
|
72
|
-
.kpi-card__rate {
|
|
73
|
-
font-size: 12px; font-weight: 600; color: var(--pass);
|
|
74
|
-
margin-top: 5px;
|
|
75
|
-
font-family: 'Inter', system-ui, sans-serif;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
31
|
/* ── Verdict badge ───────────────────────────────────────────────────────── */
|
|
79
32
|
.verdict {
|
|
80
33
|
display: inline-flex; align-items: center; gap: 8px;
|
|
@@ -131,6 +84,37 @@
|
|
|
131
84
|
.chart-card h3 {
|
|
132
85
|
letter-spacing: 1.3px;
|
|
133
86
|
}
|
|
87
|
+
.charts-grid--secondary {
|
|
88
|
+
grid-template-columns: 1fr 1fr;
|
|
89
|
+
margin-top: 4px;
|
|
90
|
+
}
|
|
134
91
|
|
|
135
92
|
/* Executive failures now render inline in the suite breakdown (compact, no
|
|
136
93
|
screenshots) — card styles live in base.css. */
|
|
94
|
+
|
|
95
|
+
/* ── Hero summary — executive-exclusive replacement for the 6-card KPI strip ── */
|
|
96
|
+
.hero-summary { text-align: center; padding: 18px 0 2px; }
|
|
97
|
+
.hero-summary__value {
|
|
98
|
+
font-family: 'Source Serif Pro', Georgia, serif;
|
|
99
|
+
font-size: 68px; font-weight: 700; color: var(--pass);
|
|
100
|
+
letter-spacing: -1.5px; line-height: 1;
|
|
101
|
+
}
|
|
102
|
+
.hero-summary__pct { font-size: 32px; }
|
|
103
|
+
.hero-summary__label {
|
|
104
|
+
font-family: 'Inter', system-ui, sans-serif; font-size: 10px; font-weight: 600;
|
|
105
|
+
letter-spacing: 1.4px; text-transform: uppercase; color: var(--text-3); margin-top: 6px;
|
|
106
|
+
}
|
|
107
|
+
.hero-stat-row { display: flex; justify-content: center; gap: 0; margin: 18px 0 24px; }
|
|
108
|
+
.hero-stat {
|
|
109
|
+
display: flex; flex-direction: column; align-items: center;
|
|
110
|
+
padding: 0 22px; border-right: 1px solid var(--border);
|
|
111
|
+
}
|
|
112
|
+
.hero-stat:last-child { border-right: none; }
|
|
113
|
+
.hero-stat--zero { opacity: .45; }
|
|
114
|
+
.hero-stat__value {
|
|
115
|
+
font-family: 'Source Serif Pro', Georgia, serif; font-size: 20px; font-weight: 700; color: var(--text-1);
|
|
116
|
+
}
|
|
117
|
+
.hero-stat__label {
|
|
118
|
+
display: block; font-family: 'Inter', system-ui, sans-serif; font-size: 8px; font-weight: 600;
|
|
119
|
+
letter-spacing: 1px; text-transform: uppercase; color: var(--text-3); margin-top: 4px;
|
|
120
|
+
}
|
|
@@ -6,9 +6,17 @@
|
|
|
6
6
|
padding: 20px 22px;
|
|
7
7
|
background: var(--surface);
|
|
8
8
|
border: 1px solid var(--border-2);
|
|
9
|
-
border-left: 3px solid var(--primary, #CC785C);
|
|
10
9
|
border-radius: 6px;
|
|
11
10
|
margin-bottom: 22px;
|
|
11
|
+
position: relative;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
}
|
|
14
|
+
.report-header::before {
|
|
15
|
+
content: '';
|
|
16
|
+
position: absolute; top: 0; left: 0; right: 0; height: 3px;
|
|
17
|
+
background: var(--accent);
|
|
18
|
+
-webkit-print-color-adjust: exact;
|
|
19
|
+
print-color-adjust: exact;
|
|
12
20
|
}
|
|
13
21
|
.report-header__logo { height: 36px; width: auto; }
|
|
14
22
|
.report-header__title {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reportforge/playwright-pdf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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",
|