@reportforge/playwright-pdf 0.2.2 → 0.4.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 +24 -0
- package/README.md +6 -0
- package/dist/cli/export-feedback.js +87 -0
- package/dist/demo/cli.js +11912 -0
- package/dist/index.d.mts +31 -2
- package/dist/index.d.ts +31 -2
- package/dist/index.js +503 -116
- package/dist/index.mjs +580 -193
- package/dist/templates/layouts/detailed.hbs +2 -0
- package/dist/templates/layouts/executive.hbs +2 -0
- package/dist/templates/partials/analysis-oneliner.hbs +3 -0
- package/dist/templates/partials/failure-analysis.hbs +42 -0
- package/dist/templates/styles/base.css +3 -0
- package/dist/templates/styles/detailed.css +21 -0
- package/package.json +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
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.3.1] — 2026-06-09
|
|
7
|
+
|
|
8
|
+
### Security
|
|
9
|
+
|
|
10
|
+
- **License gate hardened in `PdfGenerator`** — JWT verification is now enforced at the PDF generator boundary, not only in the reporter entry point. Real licenses require a valid Ed25519-signed JWT (verified against the bundled public key) with a non-expired `exp` claim checked directly from the JWT payload.
|
|
11
|
+
- **`DEMO_LICENSE_INFO` no longer exported** — the demo fixture constant is now module-private; not accessible via deep import from the published package.
|
|
12
|
+
- **Compile-time demo flag** — the demo CLI bypass (`reportforge-demo`) is gated on a tsup `define` constant (`__RF_IS_DEMO__`) baked into `dist/demo/cli.js` at build time. The main library (`dist/pdf/PdfGenerator.js`) has the flag set to `false` and cannot be bypassed by constructing a `LicenseInfo` with `source: 'demo'` at runtime.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## [0.3.0] — 2026-06-08
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`reportforge-demo` instant demo report** — run `npx @reportforge/playwright-pdf reportforge-demo` to generate a realistic sample PDF in under 60 seconds with no license key required. Supports `--template=minimal|detailed|executive` and `--output=<path>`. Lets evaluators see the exact report ReportForge produces before subscribing.
|
|
21
|
+
- **`reportforge-demo` bin wired in `package.json`** — available immediately after `npm install`; no global install required via `npx`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `src/collector/stats-utils.ts` (new) — `statsFromTests` and `aggregateStats` extracted from `SuiteWalker` and `ShardMerger` to a shared utility; eliminates 3-way duplication.
|
|
26
|
+
- GitHub Actions pinned to commit SHAs (supply-chain hardening).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
6
30
|
## [0.0.1] — 2026-05-26
|
|
7
31
|
|
|
8
32
|
### Changed
|
package/README.md
CHANGED
|
@@ -51,10 +51,13 @@ Three templates, one reporter. Every PDF is fully offline — fonts, charts, and
|
|
|
51
51
|
- **Self-contained PDFs** — screenshots, fonts, logos all embedded; share or archive with confidence
|
|
52
52
|
- **Custom branding** — logo, primary/accent colours, watermark, PDF password encryption
|
|
53
53
|
- **Dynamic filenames** — `{date}`, `{branch}`, `{status}` tokens in the output path
|
|
54
|
+
- **Instant demo report** — `npx @reportforge/playwright-pdf reportforge-demo` generates a realistic sample PDF before subscribing — no license key required
|
|
54
55
|
- **CI/CD snippets** — GitHub Actions, GitLab CI, Bitbucket Pipelines, Jenkins, Azure DevOps
|
|
55
56
|
- **Shard merging** — combine N Playwright JSON shard reports into one PDF via `shardResults`; accepts glob patterns or explicit paths
|
|
56
57
|
- **Notifications** — post pass/fail summaries to Slack, Teams, Discord, or email after each run; configurable trigger (`always` / `failure` / `success`) per channel; email supports optional PDF attachment via `attachPdf`
|
|
57
58
|
- **Flakiness trend** — top-N flakiest tests table with per-test dot sparkline across stored history runs in the `detailed` template; `flakinessTopN` option (default 5, `0` disables)
|
|
59
|
+
- **Offline failure analysis** — classifies failures into 7 root-cause categories, clusters them, and renders a breakdown in the `detailed` PDF (one-liner in `executive`). No network, no AI service. The classifier model auto-updates offline over the existing license-refresh channel — each delivered model is Ed25519-signed and verified against the bundled public key, with the bundled model always the floor; set `failureAnalysis.autoUpdateModel: false` to pin the bundled model.
|
|
60
|
+
- `reportforge-export-feedback` — exports locally-collected unrecognized failures (tokenized, local-only) for optional manual sharing.
|
|
58
61
|
- **Hybrid licensing** — one short key unlocks it; reports keep rendering offline via a cached JWT between refreshes
|
|
59
62
|
|
|
60
63
|
---
|
|
@@ -88,6 +91,8 @@ npx playwright test
|
|
|
88
91
|
# → reports/2026-05-01-main-failed.pdf
|
|
89
92
|
```
|
|
90
93
|
|
|
94
|
+
> **See it first.** Run `npx @reportforge/playwright-pdf reportforge-demo` to generate a realistic sample PDF in under 60 seconds — no license key required. Supports `--template=minimal|detailed|executive` and `--output=<path>`.
|
|
95
|
+
|
|
91
96
|
> **License required.** Start a **[7-day free trial at reportforge.org/pricing](https://reportforge.org/pricing)** — card required, no charge until day 8. Paste the resulting `RFSU-…` key into `RF_LICENSE_KEY`. Without a valid license the reporter logs a warning and skips PDF generation; your Playwright tests still run normally.
|
|
92
97
|
|
|
93
98
|
---
|
|
@@ -187,6 +192,7 @@ All options are the second element of the reporter tuple.
|
|
|
187
192
|
| `maxFileSizeMb` | `number` | `8` | Soft cap on the final PDF size in MB. If exceeded, the report is re-rendered once with the `'max'` compression preset. |
|
|
188
193
|
| `shardResults` | `string \| string[]` | — | Glob or path array of Playwright JSON shard report files to merge into one PDF. See the Shard Merging docs. |
|
|
189
194
|
| `notify` | `object` | — | Notification channels: `slack`, `teams`, `discord` (each `{ url, enabled, on }`), `email` (`{ to, enabled, on, attachPdf }`). See the Notifications docs. |
|
|
195
|
+
| `failureAnalysis` | `object` | `{ enabled: true }` | Offline failure root-cause analysis (embedded Naive Bayes; no runtime network). Fields: `enabled` (default `true`), `maxClusters` (default `10`), `minStrength` (`weak`\|`moderate`\|`strong`, default `weak`), `maxFailuresToAnalyse` (default `500`), `collectUnclassified` (default `true`), `autoUpdateModel` (default `true`). See the Failure Analysis docs. |
|
|
190
196
|
| `historyFile` | `string` | `~/.reportforge/{key}/history.json` | Path to the history JSON file. Relative paths resolve from `cwd`. Enables pass-rate trending charts in the `detailed` template. |
|
|
191
197
|
| `historySize` | `number` | `10` | Maximum number of test runs to keep in the history file (integer ≥ 2). Older runs are pruned on append. |
|
|
192
198
|
| `showTrend` | `boolean` | `true` | Show the pass-rate sparkline and delta badge in the `detailed` template. Set to `false` to disable history tracking entirely. |
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/cli/export-feedback.ts
|
|
32
|
+
var export_feedback_exports = {};
|
|
33
|
+
__export(export_feedback_exports, {
|
|
34
|
+
exportFeedback: () => exportFeedback
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(export_feedback_exports);
|
|
37
|
+
var import_node_fs = __toESM(require("fs"));
|
|
38
|
+
var import_node_os = __toESM(require("os"));
|
|
39
|
+
var import_node_path = __toESM(require("path"));
|
|
40
|
+
|
|
41
|
+
// src/analysis/feedback-csv.ts
|
|
42
|
+
var FEEDBACK_HEADER = "category,margin,text";
|
|
43
|
+
function parseFeedbackRows(content) {
|
|
44
|
+
return content.trim().split("\n").slice(1).filter(Boolean).map((line) => {
|
|
45
|
+
const parts = line.split(",");
|
|
46
|
+
return { category: parts[0], margin: parts[1], text: parts.slice(2).join(","), raw: line };
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/cli/export-feedback.ts
|
|
51
|
+
function exportFeedback(deps = {}) {
|
|
52
|
+
const home = deps.home ?? import_node_os.default.homedir();
|
|
53
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
54
|
+
const root = import_node_path.default.join(home, ".reportforge");
|
|
55
|
+
const rows = [];
|
|
56
|
+
if (import_node_fs.default.existsSync(root)) {
|
|
57
|
+
for (const entry of import_node_fs.default.readdirSync(root)) {
|
|
58
|
+
const file = import_node_path.default.join(root, entry, "unclassified.csv");
|
|
59
|
+
if (!import_node_fs.default.existsSync(file)) continue;
|
|
60
|
+
for (const r of parseFeedbackRows(import_node_fs.default.readFileSync(file, "utf8"))) {
|
|
61
|
+
rows.push(r.raw);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (rows.length === 0) return { rowCount: 0, outFile: null };
|
|
66
|
+
const outFile = import_node_path.default.join(cwd, "reportforge-feedback.csv");
|
|
67
|
+
import_node_fs.default.writeFileSync(outFile, `${FEEDBACK_HEADER}
|
|
68
|
+
${rows.join("\n")}
|
|
69
|
+
`, "utf8");
|
|
70
|
+
return { rowCount: rows.length, outFile };
|
|
71
|
+
}
|
|
72
|
+
if (process.argv[1] && process.argv[1].endsWith("export-feedback.js")) {
|
|
73
|
+
const res = exportFeedback();
|
|
74
|
+
if (res.rowCount === 0) {
|
|
75
|
+
process.stdout.write("No locally collected failures found (~/.reportforge/*/unclassified.csv).\n");
|
|
76
|
+
} else {
|
|
77
|
+
process.stdout.write(
|
|
78
|
+
`Saved ${res.rowCount} row(s) to ${res.outFile} - tokenized + redacted, LOCAL only.
|
|
79
|
+
Review it before sharing; there is no upload.
|
|
80
|
+
`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
85
|
+
0 && (module.exports = {
|
|
86
|
+
exportFeedback
|
|
87
|
+
});
|