@reportforge/playwright-pdf 0.3.1 → 0.4.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 +8 -0
- package/README.md +3 -0
- package/dist/cli/export-feedback.js +87 -0
- package/dist/demo/cli.js +413 -59
- package/dist/index.d.mts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +461 -68
- package/dist/index.mjs +538 -145
- 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 +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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.4.0] — 2026-06-12
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **Failure analysis** — every failed test is classified into one of seven root-cause categories (assertion, timeout, selector/locator, network, and more) and failures that share a cause are grouped into clusters, rendered as a ranked breakdown in the `detailed` template. Classification runs fully offline — no network call, no AI service. The classifier model refreshes from the licensing server in the background (offline-tolerant; the bundled model is always the floor), with optional privacy-preserving local feedback collection. See `docs/advanced/failure-analysis`.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
6
14
|
## [0.3.1] — 2026-06-09
|
|
7
15
|
|
|
8
16
|
### Security
|
package/README.md
CHANGED
|
@@ -56,6 +56,8 @@ Three templates, one reporter. Every PDF is fully offline — fonts, charts, and
|
|
|
56
56
|
- **Shard merging** — combine N Playwright JSON shard reports into one PDF via `shardResults`; accepts glob patterns or explicit paths
|
|
57
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`
|
|
58
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** — sorts failures into 7 root-cause buckets and clusters them, right inside the `detailed` PDF (one-liner in `executive`). No network, no AI service — a small embedded classifier that updates itself safely over your existing license refresh; pin it any time. Full details at [reportforge.org/docs/advanced/failure-analysis](https://reportforge.org/docs/advanced/failure-analysis).
|
|
60
|
+
- `reportforge-export-feedback` — exports locally-collected unrecognized failures (tokenized, local-only) for optional manual sharing.
|
|
59
61
|
- **Hybrid licensing** — one short key unlocks it; reports keep rendering offline via a cached JWT between refreshes
|
|
60
62
|
|
|
61
63
|
---
|
|
@@ -190,6 +192,7 @@ All options are the second element of the reporter tuple.
|
|
|
190
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. |
|
|
191
193
|
| `shardResults` | `string \| string[]` | — | Glob or path array of Playwright JSON shard report files to merge into one PDF. See the Shard Merging docs. |
|
|
192
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. |
|
|
193
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. |
|
|
194
197
|
| `historySize` | `number` | `10` | Maximum number of test runs to keep in the history file (integer ≥ 2). Older runs are pruned on append. |
|
|
195
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
|
+
});
|