@reportforge/playwright-pdf 0.17.0 → 0.19.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 +25 -0
- package/README.md +18 -1
- package/SECURITY.md +50 -0
- package/dist/cli/index.js +34 -18
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +91 -46
- package/dist/index.mjs +91 -46
- package/package.json +16 -16
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
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.19.0] - 2026-07-11
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **New `logLevel` option** (`'silent' | 'error' | 'warn' | 'info' | 'debug'`, default `'info'`) controls how chatty the reporter's `[reportforge]` console lines are. `'debug'` prints the same diagnostics as `RF_DEBUG=1` (useful in CI where env vars are awkward to set); `'silent'` suppresses everything, including error lines. `RF_DEBUG=1` always forces debug output, overriding the option, so "set `RF_DEBUG=1` and send the log" keeps working regardless of config.
|
|
11
|
+
- **`RF_DEBUG=1` now explains previously silent failure paths**: license cache reads/writes, cached-token rejection (including exactly why the reporter re-activated instead of serving from cache), device-id persistence, Chrome discovery, and the qpdf availability probe.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- A failed write of `~/.reportforge/device.json` now logs a warning, because it means this machine may register a new license seat on every run.
|
|
16
|
+
- The live watch URL is printed once instead of twice; the banner link line now carries the `[reportforge]` prefix so it stays greppable in CI logs.
|
|
17
|
+
- CLI output prefixes are consistent: the `demo` and `export-feedback` commands use the same `[reportforge]` prefix as the reporter.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## [0.18.0] - 2026-07-11
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **Node 20 is now the minimum supported version** (was Node 18). Node 18 reached end-of-life in April 2025 and no longer receives security patches, and dependencies have begun dropping support for it. If you are still on Node 18, upgrade to Node 20 or later before taking this release. CI now tests against Node 20, 22, and 24.
|
|
26
|
+
- `puppeteer-core` is now supported up to v25 (peer range widened to `>=21 <26`). Existing v21 to v24 installs keep working, so no action is needed unless you want to upgrade.
|
|
27
|
+
- Internal dependency updates: `sharp` 0.35 (which requires Node 20), and `@types/node` 26.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
6
31
|
## [0.17.0] - 2026-07-09
|
|
7
32
|
|
|
8
33
|
### Added
|
package/README.md
CHANGED
|
@@ -189,6 +189,7 @@ All options are the second element of the reporter tuple.
|
|
|
189
189
|
| `reportTitle` | `string` | `'Playwright Test Report'` | Custom title for the report cover page and running header. |
|
|
190
190
|
| `projectName` | `string` | from `package.json` | Project or application name. Inferred from the `package.json` `name` field if absent. |
|
|
191
191
|
| `open` | `boolean` | `false` | Open the generated PDF automatically after generation. For local use only; ignored in CI. |
|
|
192
|
+
| `logLevel` | `'silent' \| 'error' \| 'warn' \| 'info' \| 'debug'` | `'info'` | Console verbosity for `[reportforge]` log lines. `'silent'` suppresses everything including errors; `'debug'` adds diagnostics. The `RF_DEBUG=1` env var always forces debug, overriding this option. |
|
|
192
193
|
| `puppeteerExecutablePath` | `string` | auto-detect | Full path to a Chrome or Chromium binary. Falls back to `PUPPETEER_EXECUTABLE_PATH` env, then system Chrome discovery. |
|
|
193
194
|
| `serverUrl` | `string` | `'https://reportforge.org'` | Base URL for the ReportForge licensing server. Override only for self-hosting or local development. |
|
|
194
195
|
| `compressionLevel` | `'auto' \| 'none' \| 'balanced' \| 'max'` | `'auto'` | Screenshot JPEG quality preset. `'auto'` picks based on failure volume; `'none'` keeps original PNGs; `'balanced'` uses JPEG q85; `'max'` uses JPEG q70. |
|
|
@@ -644,6 +645,18 @@ Set `RF_DEBUG=1` to log the full activation flow and error stack traces:
|
|
|
644
645
|
RF_DEBUG=1 npx playwright test 2>&1 | grep '\[reportforge\]'
|
|
645
646
|
```
|
|
646
647
|
|
|
648
|
+
### Controlling log output
|
|
649
|
+
|
|
650
|
+
The `logLevel` option sets how chatty the reporter's `[reportforge]` console lines are:
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
['@reportforge/playwright-pdf', { logLevel: 'warn' }] // warnings and errors only
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
- `'debug'` prints the same diagnostics as `RF_DEBUG=1` — useful in CI where env vars are awkward to set.
|
|
657
|
+
- `'silent'` suppresses everything, **including error lines**: a failed PDF then surfaces only as a missing file, because the reporter never fails your test run.
|
|
658
|
+
- `RF_DEBUG=1` always forces debug output, overriding `logLevel` — it is the support escape hatch, so "set `RF_DEBUG=1` and send the log" works no matter what the config says.
|
|
659
|
+
|
|
647
660
|
Common issues:
|
|
648
661
|
|
|
649
662
|
- **`Chrome/Chromium not found`** → install Google Chrome Stable; don't use the Ubuntu `chromium-browser` snap.
|
|
@@ -677,6 +690,10 @@ Full documentation at [reportforge.org/docs](https://reportforge.org/docs):
|
|
|
677
690
|
|
|
678
691
|
Questions and bug reports: email [support@reportforge.org](mailto:support@reportforge.org).
|
|
679
692
|
|
|
693
|
+
## Security
|
|
694
|
+
|
|
695
|
+
Found a vulnerability? Email [support@reportforge.org](mailto:support@reportforge.org) with a subject starting with `[SECURITY]` — please don't open a public issue. Details, scope, and response timelines are in [SECURITY.md](./SECURITY.md); machine-readable contact data is at [reportforge.org/.well-known/security.txt](https://reportforge.org/.well-known/security.txt).
|
|
696
|
+
|
|
680
697
|
## License
|
|
681
698
|
|
|
682
|
-
[Elastic License 2.0](./LICENSE)
|
|
699
|
+
[Elastic License 2.0](./LICENSE). The license does not allow offering the software as a hosted service, bypassing or removing the license-key check, or stripping license or copyright notices. Generating PDF reports requires an active subscription.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
ReportForge handles license keys, payment webhooks, and customer data. We take
|
|
4
|
+
vulnerability reports seriously and appreciate coordinated disclosure.
|
|
5
|
+
|
|
6
|
+
## Reporting a vulnerability
|
|
7
|
+
|
|
8
|
+
Email **[support@reportforge.org](mailto:support@reportforge.org)** with the
|
|
9
|
+
subject line starting with `[SECURITY]`.
|
|
10
|
+
|
|
11
|
+
Please include:
|
|
12
|
+
|
|
13
|
+
- A description of the issue and where it lives (npm package
|
|
14
|
+
`@reportforge/playwright-pdf`, the `reportforge.org` service, or both).
|
|
15
|
+
- Steps to reproduce, or a proof of concept.
|
|
16
|
+
- The version(s) affected, if known.
|
|
17
|
+
|
|
18
|
+
Please do **not** open a public GitHub issue for security reports, and do not
|
|
19
|
+
test against production customer data — a trial subscription gives you a full
|
|
20
|
+
legitimate environment to test the license flow.
|
|
21
|
+
|
|
22
|
+
## What to expect
|
|
23
|
+
|
|
24
|
+
- **Acknowledgement within 48 hours** of your report.
|
|
25
|
+
- A triage verdict (accepted / not a vulnerability / duplicate) within 7 days.
|
|
26
|
+
- If accepted: a fix or mitigation timeline, and credit in the release notes
|
|
27
|
+
if you want it. We ask for up to **90 days** of coordinated disclosure
|
|
28
|
+
before you publish details.
|
|
29
|
+
- We do not currently run a paid bounty program.
|
|
30
|
+
|
|
31
|
+
## Scope
|
|
32
|
+
|
|
33
|
+
In scope:
|
|
34
|
+
|
|
35
|
+
- The npm package `@reportforge/playwright-pdf` (report generation, license
|
|
36
|
+
client, bundled assets).
|
|
37
|
+
- The `reportforge.org` service: license API, dashboard, checkout, webhooks.
|
|
38
|
+
|
|
39
|
+
Out of scope:
|
|
40
|
+
|
|
41
|
+
- Denial of service / volumetric attacks.
|
|
42
|
+
- Reports about the 25-machine licensing cap working as documented.
|
|
43
|
+
- Vulnerabilities exclusively in third-party services we integrate
|
|
44
|
+
(Razorpay, Clerk, Vercel, Resend) — report those upstream.
|
|
45
|
+
|
|
46
|
+
## Supported versions
|
|
47
|
+
|
|
48
|
+
Only the latest published version of `@reportforge/playwright-pdf` receives
|
|
49
|
+
security fixes. Machine-readable contact data is published at
|
|
50
|
+
[`https://reportforge.org/.well-known/security.txt`](https://reportforge.org/.well-known/security.txt).
|
package/dist/cli/index.js
CHANGED
|
@@ -42,24 +42,32 @@ var init_cjs_shims = __esm({
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
// src/utils/logger.ts
|
|
45
|
-
|
|
45
|
+
function isLevelEnabled(level) {
|
|
46
|
+
return LEVELS[level] >= (process.env.RF_DEBUG ? LEVELS.debug : threshold);
|
|
47
|
+
}
|
|
48
|
+
var PREFIX, LEVELS, threshold, logger;
|
|
46
49
|
var init_logger = __esm({
|
|
47
50
|
"src/utils/logger.ts"() {
|
|
48
51
|
"use strict";
|
|
49
52
|
init_cjs_shims();
|
|
50
53
|
PREFIX = "[reportforge]";
|
|
54
|
+
LEVELS = { debug: 10, info: 20, warn: 30, error: 40, silent: 99 };
|
|
55
|
+
threshold = LEVELS.info;
|
|
51
56
|
logger = {
|
|
52
57
|
info(msg) {
|
|
58
|
+
if (!isLevelEnabled("info")) return;
|
|
53
59
|
console.log(`${PREFIX} ${msg}`);
|
|
54
60
|
},
|
|
55
61
|
warn(msg) {
|
|
62
|
+
if (!isLevelEnabled("warn")) return;
|
|
56
63
|
console.warn(`${PREFIX} WARN: ${msg}`);
|
|
57
64
|
},
|
|
58
65
|
error(msg) {
|
|
66
|
+
if (!isLevelEnabled("error")) return;
|
|
59
67
|
console.error(`${PREFIX} ERROR: ${msg}`);
|
|
60
68
|
},
|
|
61
69
|
debug(msg, data) {
|
|
62
|
-
if (!
|
|
70
|
+
if (!isLevelEnabled("debug")) return;
|
|
63
71
|
const suffix = data !== void 0 ? `
|
|
64
72
|
${JSON.stringify(data, null, 2)}` : "";
|
|
65
73
|
console.log(`${PREFIX} DEBUG: ${msg}${suffix}`);
|
|
@@ -7257,8 +7265,7 @@ var init_BrowserManager = __esm({
|
|
|
7257
7265
|
}
|
|
7258
7266
|
async close() {
|
|
7259
7267
|
if (this.browser) {
|
|
7260
|
-
await this.browser.close().catch(() => {
|
|
7261
|
-
});
|
|
7268
|
+
await this.browser.close().catch((err) => logger.debug("browser close failed (ignored)", { error: err.message }));
|
|
7262
7269
|
this.browser = void 0;
|
|
7263
7270
|
}
|
|
7264
7271
|
}
|
|
@@ -7270,7 +7277,8 @@ var init_BrowserManager = __esm({
|
|
|
7270
7277
|
const chromeFinder = require_lib2();
|
|
7271
7278
|
const found = chromeFinder();
|
|
7272
7279
|
if (found && import_fs3.default.existsSync(found)) return found;
|
|
7273
|
-
} catch {
|
|
7280
|
+
} catch (err) {
|
|
7281
|
+
logger.debug("chrome discovery: chrome-finder failed", { error: err.message });
|
|
7274
7282
|
}
|
|
7275
7283
|
const linuxPaths = [
|
|
7276
7284
|
"/usr/bin/google-chrome",
|
|
@@ -7288,12 +7296,14 @@ var init_BrowserManager = __esm({
|
|
|
7288
7296
|
}).toString().trim().split("\n")[0];
|
|
7289
7297
|
if (found && import_fs3.default.existsSync(found)) return found;
|
|
7290
7298
|
} catch {
|
|
7299
|
+
logger.debug("chrome discovery: which lookup found nothing");
|
|
7291
7300
|
}
|
|
7292
7301
|
if (puppeteer.executablePath) {
|
|
7293
7302
|
try {
|
|
7294
7303
|
const bundled = puppeteer.executablePath();
|
|
7295
7304
|
if (bundled && import_fs3.default.existsSync(bundled)) return bundled;
|
|
7296
|
-
} catch {
|
|
7305
|
+
} catch (err) {
|
|
7306
|
+
logger.debug("chrome discovery: no puppeteer bundled executable", { error: err.message });
|
|
7297
7307
|
}
|
|
7298
7308
|
}
|
|
7299
7309
|
logger.warn(
|
|
@@ -7315,6 +7325,7 @@ var init_HtmlWriter = __esm({
|
|
|
7315
7325
|
import_os = __toESM(require("os"));
|
|
7316
7326
|
import_path2 = __toESM(require("path"));
|
|
7317
7327
|
import_crypto = __toESM(require("crypto"));
|
|
7328
|
+
init_logger();
|
|
7318
7329
|
HtmlWriter = class {
|
|
7319
7330
|
async write(html) {
|
|
7320
7331
|
const tmpDir = import_os.default.tmpdir();
|
|
@@ -7325,8 +7336,10 @@ var init_HtmlWriter = __esm({
|
|
|
7325
7336
|
}
|
|
7326
7337
|
async cleanup() {
|
|
7327
7338
|
if (this.tempPath) {
|
|
7328
|
-
|
|
7329
|
-
|
|
7339
|
+
const tempPath = this.tempPath;
|
|
7340
|
+
await import_fs4.default.promises.unlink(tempPath).catch(
|
|
7341
|
+
(err) => logger.debug("temp HTML cleanup failed (ignored)", { path: tempPath, error: err.message })
|
|
7342
|
+
);
|
|
7330
7343
|
this.tempPath = void 0;
|
|
7331
7344
|
}
|
|
7332
7345
|
}
|
|
@@ -7405,7 +7418,8 @@ var init_PdfEncryptor = __esm({
|
|
|
7405
7418
|
try {
|
|
7406
7419
|
(0, import_child_process2.execFileSync)("qpdf", ["--version"], { stdio: "pipe" });
|
|
7407
7420
|
return true;
|
|
7408
|
-
} catch {
|
|
7421
|
+
} catch (err) {
|
|
7422
|
+
logger.debug("qpdf probe failed \u2014 treating as not installed", { error: err.message });
|
|
7409
7423
|
return false;
|
|
7410
7424
|
}
|
|
7411
7425
|
}
|
|
@@ -7638,7 +7652,8 @@ var init_PdfGenerator = __esm({
|
|
|
7638
7652
|
if (CHARTJS_BUNDLE2) {
|
|
7639
7653
|
this.templateEngine.setChartjsBundle(CHARTJS_BUNDLE2);
|
|
7640
7654
|
}
|
|
7641
|
-
} catch {
|
|
7655
|
+
} catch (err) {
|
|
7656
|
+
logger.debug("chartjs bundle not available \u2014 charts disabled", { error: err.message });
|
|
7642
7657
|
}
|
|
7643
7658
|
}
|
|
7644
7659
|
/**
|
|
@@ -12085,6 +12100,7 @@ var init_schema = __esm({
|
|
|
12085
12100
|
reportTitle: external_exports.string().optional().default(DEFAULT_OPTIONS.reportTitle),
|
|
12086
12101
|
projectName: external_exports.string().optional(),
|
|
12087
12102
|
open: external_exports.boolean().optional().default(DEFAULT_OPTIONS.open),
|
|
12103
|
+
logLevel: external_exports.enum(["silent", "error", "warn", "info", "debug"]).optional().default("info"),
|
|
12088
12104
|
puppeteerExecutablePath: external_exports.string().optional(),
|
|
12089
12105
|
serverUrl: external_exports.string().url().optional(),
|
|
12090
12106
|
compressionLevel: external_exports.enum(["auto", "none", "balanced", "max"]).optional().default(DEFAULT_OPTIONS.compressionLevel),
|
|
@@ -12549,7 +12565,7 @@ function parseArgs() {
|
|
|
12549
12565
|
const rawTemplate = templateArg?.slice("--template=".length);
|
|
12550
12566
|
const rawOutput = outputArg?.slice("--output=".length);
|
|
12551
12567
|
if (rawTemplate && !VALID_TEMPLATES.has(rawTemplate)) {
|
|
12552
|
-
console.warn(`[
|
|
12568
|
+
console.warn(`[reportforge] Unknown template '${rawTemplate}', using 'detailed'. Valid: ${TEMPLATE_IDS.join(", ")}`);
|
|
12553
12569
|
}
|
|
12554
12570
|
const template = rawTemplate && VALID_TEMPLATES.has(rawTemplate) ? rawTemplate : "detailed";
|
|
12555
12571
|
const outputFile = rawOutput ?? import_path7.default.join("playwright-report", "demo-report.pdf");
|
|
@@ -12557,7 +12573,7 @@ function parseArgs() {
|
|
|
12557
12573
|
}
|
|
12558
12574
|
async function run() {
|
|
12559
12575
|
const { template, outputFile } = parseArgs();
|
|
12560
|
-
console.log("[
|
|
12576
|
+
console.log("[reportforge] Generating demo report...");
|
|
12561
12577
|
console.log(` Template : ${template}`);
|
|
12562
12578
|
console.log(` Output : ${outputFile}`);
|
|
12563
12579
|
const reportData = buildDemoReportData(template);
|
|
@@ -12568,10 +12584,10 @@ async function run() {
|
|
|
12568
12584
|
const paths = await generator.generateAll(reportData, options, DEMO_LICENSE_INFO2);
|
|
12569
12585
|
if (paths.length > 0) {
|
|
12570
12586
|
console.log(`
|
|
12571
|
-
[
|
|
12587
|
+
[reportforge] Demo report written: ${paths[0]}`);
|
|
12572
12588
|
console.log(" Open the PDF to preview exactly what ReportForge generates for your Playwright runs.");
|
|
12573
12589
|
} else {
|
|
12574
|
-
console.warn("\n[
|
|
12590
|
+
console.warn("\n[reportforge] Warning: no PDF was written \u2014 check that Chrome is available.");
|
|
12575
12591
|
process.exit(1);
|
|
12576
12592
|
}
|
|
12577
12593
|
}
|
|
@@ -12581,14 +12597,14 @@ async function runDemo() {
|
|
|
12581
12597
|
} catch (err) {
|
|
12582
12598
|
const msg = err instanceof Error ? err.message : String(err);
|
|
12583
12599
|
if (/chrome|chromium|executable|browser|launcher/i.test(msg)) {
|
|
12584
|
-
console.error("\n[
|
|
12600
|
+
console.error("\n[reportforge] Chrome not found.\n");
|
|
12585
12601
|
console.error(" Fix: set PUPPETEER_EXECUTABLE_PATH, e.g.:");
|
|
12586
12602
|
console.error(" PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable \\");
|
|
12587
12603
|
console.error(" npx @reportforge/playwright-pdf demo\n");
|
|
12588
12604
|
console.error(` Original error: ${msg}`);
|
|
12589
12605
|
} else {
|
|
12590
12606
|
console.error(`
|
|
12591
|
-
[
|
|
12607
|
+
[reportforge] Demo generation failed: ${msg}`);
|
|
12592
12608
|
if (process.env.RF_DEBUG) console.error(err);
|
|
12593
12609
|
}
|
|
12594
12610
|
process.exit(1);
|
|
@@ -12648,10 +12664,10 @@ ${rows.join("\n")}
|
|
|
12648
12664
|
function exportFeedbackCli() {
|
|
12649
12665
|
const res = exportFeedback();
|
|
12650
12666
|
if (res.rowCount === 0) {
|
|
12651
|
-
process.stdout.write("No locally collected failures found (~/.reportforge/*/unclassified.csv).\n");
|
|
12667
|
+
process.stdout.write("[reportforge] No locally collected failures found (~/.reportforge/*/unclassified.csv).\n");
|
|
12652
12668
|
} else {
|
|
12653
12669
|
process.stdout.write(
|
|
12654
|
-
`Saved ${res.rowCount} row(s) to ${res.outFile} - tokenized + redacted, LOCAL only.
|
|
12670
|
+
`[reportforge] Saved ${res.rowCount} row(s) to ${res.outFile} - tokenized + redacted, LOCAL only.
|
|
12655
12671
|
Review it before sharing; there is no upload.
|
|
12656
12672
|
`
|
|
12657
12673
|
);
|
package/dist/index.d.mts
CHANGED
|
@@ -91,6 +91,16 @@ interface ReporterOptions {
|
|
|
91
91
|
* @default false
|
|
92
92
|
*/
|
|
93
93
|
open?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Console verbosity for reporter log lines (the `[reportforge]` prefix).
|
|
96
|
+
* `'silent'` suppresses everything, including error lines — a PDF failure
|
|
97
|
+
* then surfaces only as a missing file (the reporter never fails the test
|
|
98
|
+
* run). `'debug'` adds diagnostic detail. The `RF_DEBUG=1` environment
|
|
99
|
+
* variable always forces `'debug'`, overriding this option (support
|
|
100
|
+
* escape hatch).
|
|
101
|
+
* @default 'info'
|
|
102
|
+
*/
|
|
103
|
+
logLevel?: 'silent' | 'error' | 'warn' | 'info' | 'debug';
|
|
94
104
|
/**
|
|
95
105
|
* Full path to a Chrome/Chromium executable.
|
|
96
106
|
* Falls back to PUPPETEER_EXECUTABLE_PATH env, then system Chrome discovery.
|
package/dist/index.d.ts
CHANGED
|
@@ -91,6 +91,16 @@ interface ReporterOptions {
|
|
|
91
91
|
* @default false
|
|
92
92
|
*/
|
|
93
93
|
open?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Console verbosity for reporter log lines (the `[reportforge]` prefix).
|
|
96
|
+
* `'silent'` suppresses everything, including error lines — a PDF failure
|
|
97
|
+
* then surfaces only as a missing file (the reporter never fails the test
|
|
98
|
+
* run). `'debug'` adds diagnostic detail. The `RF_DEBUG=1` environment
|
|
99
|
+
* variable always forces `'debug'`, overriding this option (support
|
|
100
|
+
* escape hatch).
|
|
101
|
+
* @default 'info'
|
|
102
|
+
*/
|
|
103
|
+
logLevel?: 'silent' | 'error' | 'warn' | 'info' | 'debug';
|
|
94
104
|
/**
|
|
95
105
|
* Full path to a Chrome/Chromium executable.
|
|
96
106
|
* Falls back to PUPPETEER_EXECUTABLE_PATH env, then system Chrome discovery.
|
package/dist/index.js
CHANGED
|
@@ -10532,6 +10532,7 @@ var ReporterOptionsSchema = external_exports.object({
|
|
|
10532
10532
|
reportTitle: external_exports.string().optional().default(DEFAULT_OPTIONS.reportTitle),
|
|
10533
10533
|
projectName: external_exports.string().optional(),
|
|
10534
10534
|
open: external_exports.boolean().optional().default(DEFAULT_OPTIONS.open),
|
|
10535
|
+
logLevel: external_exports.enum(["silent", "error", "warn", "info", "debug"]).optional().default("info"),
|
|
10535
10536
|
puppeteerExecutablePath: external_exports.string().optional(),
|
|
10536
10537
|
serverUrl: external_exports.string().url().optional(),
|
|
10537
10538
|
compressionLevel: external_exports.enum(["auto", "none", "balanced", "max"]).optional().default(DEFAULT_OPTIONS.compressionLevel),
|
|
@@ -10668,13 +10669,48 @@ var import_fs = __toESM(require("fs"));
|
|
|
10668
10669
|
var import_path = __toESM(require("path"));
|
|
10669
10670
|
var import_os = __toESM(require("os"));
|
|
10670
10671
|
var import_crypto3 = require("crypto");
|
|
10672
|
+
|
|
10673
|
+
// src/utils/logger.ts
|
|
10674
|
+
init_cjs_shims();
|
|
10675
|
+
var PREFIX = "[reportforge]";
|
|
10676
|
+
var LEVELS = { debug: 10, info: 20, warn: 30, error: 40, silent: 99 };
|
|
10677
|
+
var threshold = LEVELS.info;
|
|
10678
|
+
function setLogLevel(level) {
|
|
10679
|
+
threshold = LEVELS[level];
|
|
10680
|
+
}
|
|
10681
|
+
function isLevelEnabled(level) {
|
|
10682
|
+
return LEVELS[level] >= (process.env.RF_DEBUG ? LEVELS.debug : threshold);
|
|
10683
|
+
}
|
|
10684
|
+
var logger = {
|
|
10685
|
+
info(msg) {
|
|
10686
|
+
if (!isLevelEnabled("info")) return;
|
|
10687
|
+
console.log(`${PREFIX} ${msg}`);
|
|
10688
|
+
},
|
|
10689
|
+
warn(msg) {
|
|
10690
|
+
if (!isLevelEnabled("warn")) return;
|
|
10691
|
+
console.warn(`${PREFIX} WARN: ${msg}`);
|
|
10692
|
+
},
|
|
10693
|
+
error(msg) {
|
|
10694
|
+
if (!isLevelEnabled("error")) return;
|
|
10695
|
+
console.error(`${PREFIX} ERROR: ${msg}`);
|
|
10696
|
+
},
|
|
10697
|
+
debug(msg, data) {
|
|
10698
|
+
if (!isLevelEnabled("debug")) return;
|
|
10699
|
+
const suffix = data !== void 0 ? `
|
|
10700
|
+
${JSON.stringify(data, null, 2)}` : "";
|
|
10701
|
+
console.log(`${PREFIX} DEBUG: ${msg}${suffix}`);
|
|
10702
|
+
}
|
|
10703
|
+
};
|
|
10704
|
+
|
|
10705
|
+
// src/license/DeviceId.ts
|
|
10671
10706
|
var DEVICE_FILE = import_path.default.join(import_os.default.homedir(), ".reportforge", "device.json");
|
|
10672
10707
|
function getStableDeviceId() {
|
|
10673
10708
|
try {
|
|
10674
10709
|
const raw = import_fs.default.readFileSync(DEVICE_FILE, "utf8");
|
|
10675
10710
|
const parsed = JSON.parse(raw);
|
|
10676
10711
|
if (parsed.id && /^[0-9a-f-]{36}$/i.test(parsed.id)) return parsed.id;
|
|
10677
|
-
} catch {
|
|
10712
|
+
} catch (err) {
|
|
10713
|
+
logger.debug("device id: no readable device.json \u2014 creating new id", { error: err.message });
|
|
10678
10714
|
}
|
|
10679
10715
|
const id = (0, import_crypto3.randomUUID)();
|
|
10680
10716
|
try {
|
|
@@ -10682,7 +10718,10 @@ function getStableDeviceId() {
|
|
|
10682
10718
|
import_fs.default.writeFileSync(DEVICE_FILE, JSON.stringify({ id, createdAt: Date.now() }), {
|
|
10683
10719
|
mode: 384
|
|
10684
10720
|
});
|
|
10685
|
-
} catch {
|
|
10721
|
+
} catch (err) {
|
|
10722
|
+
logger.warn(
|
|
10723
|
+
`Could not persist device id (~/.reportforge/device.json) \u2014 this machine may register a new seat on every run: ${err.message}`
|
|
10724
|
+
);
|
|
10686
10725
|
}
|
|
10687
10726
|
return id;
|
|
10688
10727
|
}
|
|
@@ -10709,10 +10748,13 @@ function getMachineFingerprint() {
|
|
|
10709
10748
|
label: `${ci.provider}/${ci.repo} (CI)`
|
|
10710
10749
|
};
|
|
10711
10750
|
}
|
|
10712
|
-
const hostname = safe(() => import_os2.default.hostname())
|
|
10751
|
+
const hostname = safe(() => import_os2.default.hostname());
|
|
10752
|
+
if (hostname === void 0) {
|
|
10753
|
+
logger.debug("fingerprint: os.hostname() failed \u2014 label falls back to unknown-host");
|
|
10754
|
+
}
|
|
10713
10755
|
return {
|
|
10714
10756
|
hash: sha256Hex(`dev:${getStableDeviceId()}`),
|
|
10715
|
-
label: `${hostname} (dev)`
|
|
10757
|
+
label: `${hostname ?? "unknown-host"} (dev)`
|
|
10716
10758
|
};
|
|
10717
10759
|
}
|
|
10718
10760
|
function detectCiRepo() {
|
|
@@ -10754,19 +10796,25 @@ function readCache() {
|
|
|
10754
10796
|
if (f.endsWith(".tmp")) {
|
|
10755
10797
|
try {
|
|
10756
10798
|
import_fs2.default.unlinkSync(import_path2.default.join(CACHE_DIR, f));
|
|
10757
|
-
} catch {
|
|
10799
|
+
} catch (err) {
|
|
10800
|
+
logger.debug("license cache: stale tmp cleanup failed", { file: f, error: err.message });
|
|
10758
10801
|
}
|
|
10759
10802
|
}
|
|
10760
10803
|
}
|
|
10761
|
-
} catch {
|
|
10804
|
+
} catch (err) {
|
|
10805
|
+
logger.debug("license cache: cache dir not readable (first run?)", { error: err.message });
|
|
10762
10806
|
}
|
|
10763
10807
|
try {
|
|
10764
10808
|
if (!import_fs2.default.existsSync(CACHE_FILE)) return null;
|
|
10765
10809
|
const raw = import_fs2.default.readFileSync(CACHE_FILE, "utf8");
|
|
10766
10810
|
const parsed = JSON.parse(raw);
|
|
10767
|
-
if (typeof parsed.jwt !== "string" || typeof parsed.expiresAt !== "number")
|
|
10811
|
+
if (typeof parsed.jwt !== "string" || typeof parsed.expiresAt !== "number") {
|
|
10812
|
+
logger.debug("license cache: malformed entry \u2014 ignoring");
|
|
10813
|
+
return null;
|
|
10814
|
+
}
|
|
10768
10815
|
return parsed;
|
|
10769
|
-
} catch {
|
|
10816
|
+
} catch (err) {
|
|
10817
|
+
logger.debug("license cache: read failed \u2014 treating as no cache", { error: err.message });
|
|
10770
10818
|
return null;
|
|
10771
10819
|
}
|
|
10772
10820
|
}
|
|
@@ -10776,13 +10824,15 @@ function writeCache(entry) {
|
|
|
10776
10824
|
const tmp = `${CACHE_FILE}.${process.pid}.tmp`;
|
|
10777
10825
|
import_fs2.default.writeFileSync(tmp, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
10778
10826
|
import_fs2.default.renameSync(tmp, CACHE_FILE);
|
|
10779
|
-
} catch {
|
|
10827
|
+
} catch (err) {
|
|
10828
|
+
logger.debug("license cache: write failed \u2014 will re-activate next run", { error: err.message });
|
|
10780
10829
|
}
|
|
10781
10830
|
}
|
|
10782
10831
|
function clearCache() {
|
|
10783
10832
|
try {
|
|
10784
10833
|
import_fs2.default.unlinkSync(CACHE_FILE);
|
|
10785
|
-
} catch {
|
|
10834
|
+
} catch (err) {
|
|
10835
|
+
logger.debug("license cache: clear failed", { error: err.message });
|
|
10786
10836
|
}
|
|
10787
10837
|
}
|
|
10788
10838
|
|
|
@@ -10801,27 +10851,6 @@ var import_node_crypto = require("crypto");
|
|
|
10801
10851
|
init_cjs_shims();
|
|
10802
10852
|
var MODEL = { "alpha": 1, "labels": ["assertion", "env-config", "flaky-by-retry", "locator-not-found", "navigation", "network", "timeout"], "logLikelihoods": { "assertion": { "NUM": -4.574710978503383, "assertionerror": -4.980176086611547, "be": -4.980176086611547, "cart": -4.980176086611547, "checkout": -4.980176086611547, "com": -4.980176086611547, "dashboard": -4.980176086611547, "deep": -4.980176086611547, "equal": -4.980176086611547, "equality": -4.980176086611547, "error": -4.980176086611547, "expect": -3.5938817254916566, "expected": -3.1083739097099556, "failed": -4.980176086611547, "false": -4.980176086611547, "got": -4.980176086611547, "id": -4.574710978503383, "john": -4.980176086611547, "locator": -4.574710978503383, "login": -4.980176086611547, "must": -4.980176086611547, "page": -4.980176086611547, "pattern": -4.980176086611547, "received": -3.275427994373122, "string": -4.980176086611547, "to": -4.980176086611547, "tobe": -4.980176086611547, "tobetruthy": -4.980176086611547, "toequal": -4.980176086611547, "tohavecount": -4.980176086611547, "tohavetext": -4.980176086611547, "tohavetitle": -4.980176086611547, "tohavevalue": -4.980176086611547, "truthy": -4.980176086611547, "value": -4.980176086611547 }, "env-config": { "NUM": -4.997212273764115, "api": -4.997212273764115, "at": -4.997212273764115, "back": -4.997212273764115, "base": -4.997212273764115, "baseurl": -4.997212273764115, "browsers": -4.997212273764115, "cannot": -4.997212273764115, "ci": -4.997212273764115, "config": -4.304065093204169, "directory": -4.591747165655951, "dotenv": -4.997212273764115, "enoent": -4.997212273764115, "env": -4.591747165655951, "environment": -4.591747165655951, "error": -4.08092154188996, "existent": -4.997212273764115, "failed": -4.997212273764115, "falling": -4.997212273764115, "file": -4.997212273764115, "find": -4.997212273764115, "from": -4.997212273764115, "in": -4.591747165655951, "invalid": -4.997212273764115, "is": -4.997212273764115, "json": -4.591747165655951, "key": -4.997212273764115, "load": -4.997212273764115, "missing": -4.997212273764115, "module": -4.997212273764115, "no": -4.997212273764115, "non": -4.997212273764115, "not": -4.997212273764115, "open": -4.997212273764115, "or": -4.997212273764115, "path": -4.997212273764115, "playwright": -4.591747165655951, "points": -4.997212273764115, "position": -4.997212273764115, "required": -4.997212273764115, "set": -4.997212273764115, "spec": -4.997212273764115, "such": -4.997212273764115, "syntaxerror": -4.997212273764115, "test": -4.997212273764115, "to": -4.304065093204169, "token": -4.997212273764115, "ts": -4.997212273764115, "tsconfig": -4.997212273764115, "undefined": -4.591747165655951, "unexpected": -4.997212273764115, "url": -4.997212273764115, "values": -4.997212273764115, "variable": -4.591747165655951 }, "flaky-by-retry": { "after": -4.58836306767171, "and": -4.993828175779875, "animation": -4.993828175779875, "attempt": -4.07753744390572, "between": -4.993828175779875, "blip": -4.993828175779875, "cause": -4.993828175779875, "change": -4.993828175779875, "click": -4.993828175779875, "code": -4.993828175779875, "commit": -4.993828175779875, "condition": -4.993828175779875, "dependency": -4.993828175779875, "detached": -4.993828175779875, "deterministic": -4.993828175779875, "element": -4.993828175779875, "failed": -4.300680995219929, "fails": -4.993828175779875, "flaky": -4.07753744390572, "green": -4.993828175779875, "in": -4.993828175779875, "intermittent": -4.993828175779875, "locator": -4.993828175779875, "marked": -4.993828175779875, "network": -4.993828175779875, "no": -4.993828175779875, "non": -4.993828175779875, "on": -3.895215887111765, "order": -4.993828175779875, "passed": -3.895215887111765, "quarantined": -4.993828175779875, "race": -4.993828175779875, "reproducible": -4.993828175779875, "rerun": -4.993828175779875, "retry": -4.07753744390572, "runs": -4.993828175779875, "same": -4.993828175779875, "second": -4.993828175779875, "stale": -4.993828175779875, "succeeded": -4.993828175779875, "test": -4.58836306767171, "the": -4.993828175779875, "then": -4.993828175779875, "timeout": -4.993828175779875, "unstable": -4.993828175779875, "with": -4.993828175779875, "without": -4.993828175779875 }, "locator-not-found": { "an": -4.993828175779875, "attached": -4.993828175779875, "btn": -4.993828175779875, "button": -4.58836306767171, "cart": -4.993828175779875, "checkout": -4.993828175779875, "click": -4.993828175779875, "data": -4.993828175779875, "div": -4.993828175779875, "dom": -4.993828175779875, "element": -4.07753744390572, "elements": -4.300680995219929, "error": -4.58836306767171, "find": -4.993828175779875, "for": -4.58836306767171, "found": -4.58836306767171, "getbyrole": -4.993828175779875, "getbytext": -4.993828175779875, "icon": -4.993828175779875, "id": -4.993828175779875, "is": -4.993828175779875, "locator": -4.07753744390572, "matched": -4.993828175779875, "matches": -4.993828175779875, "missing": -4.993828175779875, "mode": -4.58836306767171, "name": -4.993828175779875, "no": -4.58836306767171, "node": -4.993828175779875, "not": -4.58836306767171, "resolved": -4.58836306767171, "save": -4.993828175779875, "selector": -4.58836306767171, "strict": -4.58836306767171, "submit": -4.993828175779875, "testid": -4.993828175779875, "textcontent": -4.993828175779875, "the": -4.58836306767171, "to": -4.07753744390572, "total": -4.993828175779875, "unable": -4.993828175779875, "violation": -4.58836306767171, "waiting": -4.993828175779875, "with": -4.993828175779875, "xpath": -4.993828175779875 }, "navigation": { "NUM": -4.983606621708336, "aborted": -4.983606621708336, "another": -4.983606621708336, "back": -4.983606621708336, "because": -4.578141513600172, "by": -4.578141513600172, "checkout": -4.983606621708336, "context": -4.983606621708336, "crashed": -4.983606621708336, "dashboard": -4.983606621708336, "destroyed": -4.983606621708336, "detached": -4.983606621708336, "entry": -4.983606621708336, "err": -4.578141513600172, "exceeded": -4.983606621708336, "execution": -4.983606621708336, "failed": -4.983606621708336, "frame": -4.578141513600172, "goback": -4.983606621708336, "goto": -4.290459441148391, "history": -4.983606621708336, "home": -4.983606621708336, "interrupted": -4.578141513600172, "is": -4.983606621708336, "likely": -4.983606621708336, "login": -4.983606621708336, "many": -4.983606621708336, "maybe": -4.983606621708336, "most": -4.983606621708336, "navigate": -4.983606621708336, "navigating": -4.983606621708336, "navigation": -3.7308436532129683, "net": -4.578141513600172, "no": -4.983606621708336, "of": -4.983606621708336, "page": -3.884994333040227, "redirects": -4.983606621708336, "timeout": -4.983606621708336, "to": -3.7308436532129683, "too": -4.983606621708336, "waitforurl": -4.983606621708336, "was": -4.578141513600172 }, "network": { "NUM": -4.57126863431241, "URL": -4.283586561860629, "again": -4.976733742420574, "api": -4.976733742420574, "apirequestcontext": -4.976733742420574, "at": -4.976733742420574, "body": -4.976733742420574, "connect": -4.976733742420574, "connection": -4.57126863431241, "disconnected": -4.976733742420574, "eai": -4.976733742420574, "econnrefused": -4.976733742420574, "err": -3.8781214537524646, "error": -4.976733742420574, "failed": -4.283586561860629, "fetch": -4.976733742420574, "fetching": -4.976733742420574, "from": -4.976733742420574, "get": -4.976733742420574, "getaddrinfo": -4.976733742420574, "goto": -4.976733742420574, "hang": -4.976733742420574, "internet": -4.976733742420574, "loading": -4.976733742420574, "name": -4.976733742420574, "net": -3.8781214537524646, "not": -4.976733742420574, "on": -4.976733742420574, "out": -4.976733742420574, "page": -4.57126863431241, "products": -4.976733742420574, "protocol": -4.976733742420574, "reading": -4.976733742420574, "reason": -4.976733742420574, "refused": -4.976733742420574, "request": -4.57126863431241, "resolved": -4.976733742420574, "resource": -4.976733742420574, "response": -4.976733742420574, "route": -4.976733742420574, "socket": -4.976733742420574, "ssl": -4.976733742420574, "timed": -4.976733742420574, "to": -4.976733742420574, "up": -4.976733742420574, "upstream": -4.976733742420574, "while": -4.976733742420574 }, "timeout": { "NUM": -3.4965075614664802, "after": -5.000584958242754, "be": -4.59511985013459, "cart": -5.000584958242754, "click": -5.000584958242754, "element": -5.000584958242754, "event": -5.000584958242754, "exceeded": -3.6142905971228636, "expect": -5.000584958242754, "fill": -5.000584958242754, "for": -3.7478219897473863, "function": -5.000584958242754, "load": -5.000584958242754, "locator": -4.084294226368599, "navigation": -5.000584958242754, "of": -5.000584958242754, "out": -5.000584958242754, "page": -4.59511985013459, "running": -5.000584958242754, "selector": -5.000584958242754, "submit": -5.000584958242754, "test": -4.59511985013459, "timed": -5.000584958242754, "timeout": -3.6142905971228636, "timeouterror": -4.59511985013459, "to": -4.59511985013459, "tobevisible": -5.000584958242754, "visible": -4.59511985013459, "waitforloadstate": -5.000584958242754, "waitforselector": -5.000584958242754, "waiting": -3.7478219897473863, "while": -5.000584958242754 } }, "playwrightVersion": "1.59.1", "priors": { "assertion": -1.9636097261547143, "env-config": -1.9636097261547143, "flaky-by-retry": -1.9636097261547143, "locator-not-found": -1.9636097261547143, "navigation": -1.9636097261547143, "network": -1.9636097261547143, "timeout": -1.845826690498331 }, "totals": { "assertion": 64, "env-config": 69, "flaky-by-retry": 68, "locator-not-found": 68, "navigation": 65, "network": 63, "timeout": 70 }, "version": 1, "vocabSize": 227 };
|
|
10803
10853
|
|
|
10804
|
-
// src/utils/logger.ts
|
|
10805
|
-
init_cjs_shims();
|
|
10806
|
-
var PREFIX = "[reportforge]";
|
|
10807
|
-
var logger = {
|
|
10808
|
-
info(msg) {
|
|
10809
|
-
console.log(`${PREFIX} ${msg}`);
|
|
10810
|
-
},
|
|
10811
|
-
warn(msg) {
|
|
10812
|
-
console.warn(`${PREFIX} WARN: ${msg}`);
|
|
10813
|
-
},
|
|
10814
|
-
error(msg) {
|
|
10815
|
-
console.error(`${PREFIX} ERROR: ${msg}`);
|
|
10816
|
-
},
|
|
10817
|
-
debug(msg, data) {
|
|
10818
|
-
if (!process.env.RF_DEBUG) return;
|
|
10819
|
-
const suffix = data !== void 0 ? `
|
|
10820
|
-
${JSON.stringify(data, null, 2)}` : "";
|
|
10821
|
-
console.log(`${PREFIX} DEBUG: ${msg}${suffix}`);
|
|
10822
|
-
}
|
|
10823
|
-
};
|
|
10824
|
-
|
|
10825
10854
|
// src/analysis/ModelStore.ts
|
|
10826
10855
|
var MODEL_SIG_DOMAIN = "reportforge-model-sig:v1\n";
|
|
10827
10856
|
var testDeps = null;
|
|
@@ -11092,9 +11121,16 @@ var LicenseClient = class {
|
|
|
11092
11121
|
function verifyCache(verifier, cached, expectedFp) {
|
|
11093
11122
|
try {
|
|
11094
11123
|
const payload = verifier.verify(cached.jwt);
|
|
11095
|
-
if (payload.fp !== expectedFp)
|
|
11124
|
+
if (payload.fp !== expectedFp) {
|
|
11125
|
+
logger.debug("license cache rejected: fingerprint mismatch", {
|
|
11126
|
+
cachedFp: payload.fp.slice(0, 12),
|
|
11127
|
+
expectedFp: expectedFp.slice(0, 12)
|
|
11128
|
+
});
|
|
11129
|
+
return null;
|
|
11130
|
+
}
|
|
11096
11131
|
return payload;
|
|
11097
|
-
} catch {
|
|
11132
|
+
} catch (err) {
|
|
11133
|
+
logger.debug("license cache rejected: JWT verify failed", { error: err.message });
|
|
11098
11134
|
return null;
|
|
11099
11135
|
}
|
|
11100
11136
|
}
|
|
@@ -11150,7 +11186,8 @@ function maybePrintUpdateNotice(licenseInfo, currentVersion) {
|
|
|
11150
11186
|
logger.info(
|
|
11151
11187
|
`Update available: ${currentVersion} -> ${shown}. Run: npm i -D @reportforge/playwright-pdf@latest`
|
|
11152
11188
|
);
|
|
11153
|
-
} catch {
|
|
11189
|
+
} catch (err) {
|
|
11190
|
+
logger.debug("update notice failed (ignored)", { error: err.message });
|
|
11154
11191
|
}
|
|
11155
11192
|
}
|
|
11156
11193
|
|
|
@@ -12847,8 +12884,7 @@ var BrowserManager = class {
|
|
|
12847
12884
|
}
|
|
12848
12885
|
async close() {
|
|
12849
12886
|
if (this.browser) {
|
|
12850
|
-
await this.browser.close().catch(() => {
|
|
12851
|
-
});
|
|
12887
|
+
await this.browser.close().catch((err) => logger.debug("browser close failed (ignored)", { error: err.message }));
|
|
12852
12888
|
this.browser = void 0;
|
|
12853
12889
|
}
|
|
12854
12890
|
}
|
|
@@ -12860,7 +12896,8 @@ var BrowserManager = class {
|
|
|
12860
12896
|
const chromeFinder = require_lib2();
|
|
12861
12897
|
const found = chromeFinder();
|
|
12862
12898
|
if (found && import_fs6.default.existsSync(found)) return found;
|
|
12863
|
-
} catch {
|
|
12899
|
+
} catch (err) {
|
|
12900
|
+
logger.debug("chrome discovery: chrome-finder failed", { error: err.message });
|
|
12864
12901
|
}
|
|
12865
12902
|
const linuxPaths = [
|
|
12866
12903
|
"/usr/bin/google-chrome",
|
|
@@ -12878,12 +12915,14 @@ var BrowserManager = class {
|
|
|
12878
12915
|
}).toString().trim().split("\n")[0];
|
|
12879
12916
|
if (found && import_fs6.default.existsSync(found)) return found;
|
|
12880
12917
|
} catch {
|
|
12918
|
+
logger.debug("chrome discovery: which lookup found nothing");
|
|
12881
12919
|
}
|
|
12882
12920
|
if (puppeteer.executablePath) {
|
|
12883
12921
|
try {
|
|
12884
12922
|
const bundled = puppeteer.executablePath();
|
|
12885
12923
|
if (bundled && import_fs6.default.existsSync(bundled)) return bundled;
|
|
12886
|
-
} catch {
|
|
12924
|
+
} catch (err) {
|
|
12925
|
+
logger.debug("chrome discovery: no puppeteer bundled executable", { error: err.message });
|
|
12887
12926
|
}
|
|
12888
12927
|
}
|
|
12889
12928
|
logger.warn(
|
|
@@ -12909,8 +12948,10 @@ var HtmlWriter = class {
|
|
|
12909
12948
|
}
|
|
12910
12949
|
async cleanup() {
|
|
12911
12950
|
if (this.tempPath) {
|
|
12912
|
-
|
|
12913
|
-
|
|
12951
|
+
const tempPath = this.tempPath;
|
|
12952
|
+
await import_fs7.default.promises.unlink(tempPath).catch(
|
|
12953
|
+
(err) => logger.debug("temp HTML cleanup failed (ignored)", { path: tempPath, error: err.message })
|
|
12954
|
+
);
|
|
12914
12955
|
this.tempPath = void 0;
|
|
12915
12956
|
}
|
|
12916
12957
|
}
|
|
@@ -12975,7 +13016,8 @@ var PdfEncryptor = class {
|
|
|
12975
13016
|
try {
|
|
12976
13017
|
(0, import_child_process3.execFileSync)("qpdf", ["--version"], { stdio: "pipe" });
|
|
12977
13018
|
return true;
|
|
12978
|
-
} catch {
|
|
13019
|
+
} catch (err) {
|
|
13020
|
+
logger.debug("qpdf probe failed \u2014 treating as not installed", { error: err.message });
|
|
12979
13021
|
return false;
|
|
12980
13022
|
}
|
|
12981
13023
|
}
|
|
@@ -13132,7 +13174,8 @@ var PdfGenerator = class {
|
|
|
13132
13174
|
if (CHARTJS_BUNDLE2) {
|
|
13133
13175
|
this.templateEngine.setChartjsBundle(CHARTJS_BUNDLE2);
|
|
13134
13176
|
}
|
|
13135
|
-
} catch {
|
|
13177
|
+
} catch (err) {
|
|
13178
|
+
logger.debug("chartjs bundle not available \u2014 charts disabled", { error: err.message });
|
|
13136
13179
|
}
|
|
13137
13180
|
}
|
|
13138
13181
|
/**
|
|
@@ -14048,7 +14091,7 @@ function buildWatchUrl(serverUrl, runId, token) {
|
|
|
14048
14091
|
}
|
|
14049
14092
|
function formatLiveBanner(url) {
|
|
14050
14093
|
const label = "Live Tracker";
|
|
14051
|
-
const link = `Watch live: ${url}`;
|
|
14094
|
+
const link = `[reportforge] Watch live: ${url}`;
|
|
14052
14095
|
const width = Math.max(label.length, link.length);
|
|
14053
14096
|
const rule = "\u2500".repeat(width + 2);
|
|
14054
14097
|
return [rule, ` ${label.padEnd(width)} `, ` ${link.padEnd(width)} `, rule].join("\n");
|
|
@@ -14154,8 +14197,9 @@ var PdfReporter = class {
|
|
|
14154
14197
|
this.liveSteps = "failed";
|
|
14155
14198
|
this.liveConsole = false;
|
|
14156
14199
|
this.options = parseOptions(rawOptions);
|
|
14200
|
+
setLogLevel(this.options.logLevel);
|
|
14157
14201
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14158
|
-
this.version = true ? "0.
|
|
14202
|
+
this.version = true ? "0.19.0" : "0.x";
|
|
14159
14203
|
logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
|
|
14160
14204
|
this.licenseClient = new LicenseClient({
|
|
14161
14205
|
licenseKey: this.options.licenseKey,
|
|
@@ -14418,8 +14462,9 @@ var PdfReporter = class {
|
|
|
14418
14462
|
);
|
|
14419
14463
|
return;
|
|
14420
14464
|
}
|
|
14421
|
-
|
|
14422
|
-
|
|
14465
|
+
if (isLevelEnabled("info")) {
|
|
14466
|
+
console.log(formatLiveBanner(watchUrl));
|
|
14467
|
+
}
|
|
14423
14468
|
const isPrimaryShard = !shard || shard.current === 1;
|
|
14424
14469
|
const notify = this.options.notify;
|
|
14425
14470
|
if (isPrimaryShard && notify) {
|
package/dist/index.mjs
CHANGED
|
@@ -10533,6 +10533,7 @@ var ReporterOptionsSchema = external_exports.object({
|
|
|
10533
10533
|
reportTitle: external_exports.string().optional().default(DEFAULT_OPTIONS.reportTitle),
|
|
10534
10534
|
projectName: external_exports.string().optional(),
|
|
10535
10535
|
open: external_exports.boolean().optional().default(DEFAULT_OPTIONS.open),
|
|
10536
|
+
logLevel: external_exports.enum(["silent", "error", "warn", "info", "debug"]).optional().default("info"),
|
|
10536
10537
|
puppeteerExecutablePath: external_exports.string().optional(),
|
|
10537
10538
|
serverUrl: external_exports.string().url().optional(),
|
|
10538
10539
|
compressionLevel: external_exports.enum(["auto", "none", "balanced", "max"]).optional().default(DEFAULT_OPTIONS.compressionLevel),
|
|
@@ -10669,13 +10670,48 @@ import fs from "fs";
|
|
|
10669
10670
|
import path2 from "path";
|
|
10670
10671
|
import os from "os";
|
|
10671
10672
|
import { randomUUID } from "crypto";
|
|
10673
|
+
|
|
10674
|
+
// src/utils/logger.ts
|
|
10675
|
+
init_esm_shims();
|
|
10676
|
+
var PREFIX = "[reportforge]";
|
|
10677
|
+
var LEVELS = { debug: 10, info: 20, warn: 30, error: 40, silent: 99 };
|
|
10678
|
+
var threshold = LEVELS.info;
|
|
10679
|
+
function setLogLevel(level) {
|
|
10680
|
+
threshold = LEVELS[level];
|
|
10681
|
+
}
|
|
10682
|
+
function isLevelEnabled(level) {
|
|
10683
|
+
return LEVELS[level] >= (process.env.RF_DEBUG ? LEVELS.debug : threshold);
|
|
10684
|
+
}
|
|
10685
|
+
var logger = {
|
|
10686
|
+
info(msg) {
|
|
10687
|
+
if (!isLevelEnabled("info")) return;
|
|
10688
|
+
console.log(`${PREFIX} ${msg}`);
|
|
10689
|
+
},
|
|
10690
|
+
warn(msg) {
|
|
10691
|
+
if (!isLevelEnabled("warn")) return;
|
|
10692
|
+
console.warn(`${PREFIX} WARN: ${msg}`);
|
|
10693
|
+
},
|
|
10694
|
+
error(msg) {
|
|
10695
|
+
if (!isLevelEnabled("error")) return;
|
|
10696
|
+
console.error(`${PREFIX} ERROR: ${msg}`);
|
|
10697
|
+
},
|
|
10698
|
+
debug(msg, data) {
|
|
10699
|
+
if (!isLevelEnabled("debug")) return;
|
|
10700
|
+
const suffix = data !== void 0 ? `
|
|
10701
|
+
${JSON.stringify(data, null, 2)}` : "";
|
|
10702
|
+
console.log(`${PREFIX} DEBUG: ${msg}${suffix}`);
|
|
10703
|
+
}
|
|
10704
|
+
};
|
|
10705
|
+
|
|
10706
|
+
// src/license/DeviceId.ts
|
|
10672
10707
|
var DEVICE_FILE = path2.join(os.homedir(), ".reportforge", "device.json");
|
|
10673
10708
|
function getStableDeviceId() {
|
|
10674
10709
|
try {
|
|
10675
10710
|
const raw = fs.readFileSync(DEVICE_FILE, "utf8");
|
|
10676
10711
|
const parsed = JSON.parse(raw);
|
|
10677
10712
|
if (parsed.id && /^[0-9a-f-]{36}$/i.test(parsed.id)) return parsed.id;
|
|
10678
|
-
} catch {
|
|
10713
|
+
} catch (err) {
|
|
10714
|
+
logger.debug("device id: no readable device.json \u2014 creating new id", { error: err.message });
|
|
10679
10715
|
}
|
|
10680
10716
|
const id = randomUUID();
|
|
10681
10717
|
try {
|
|
@@ -10683,7 +10719,10 @@ function getStableDeviceId() {
|
|
|
10683
10719
|
fs.writeFileSync(DEVICE_FILE, JSON.stringify({ id, createdAt: Date.now() }), {
|
|
10684
10720
|
mode: 384
|
|
10685
10721
|
});
|
|
10686
|
-
} catch {
|
|
10722
|
+
} catch (err) {
|
|
10723
|
+
logger.warn(
|
|
10724
|
+
`Could not persist device id (~/.reportforge/device.json) \u2014 this machine may register a new seat on every run: ${err.message}`
|
|
10725
|
+
);
|
|
10687
10726
|
}
|
|
10688
10727
|
return id;
|
|
10689
10728
|
}
|
|
@@ -10710,10 +10749,13 @@ function getMachineFingerprint() {
|
|
|
10710
10749
|
label: `${ci.provider}/${ci.repo} (CI)`
|
|
10711
10750
|
};
|
|
10712
10751
|
}
|
|
10713
|
-
const hostname = safe(() => os2.hostname())
|
|
10752
|
+
const hostname = safe(() => os2.hostname());
|
|
10753
|
+
if (hostname === void 0) {
|
|
10754
|
+
logger.debug("fingerprint: os.hostname() failed \u2014 label falls back to unknown-host");
|
|
10755
|
+
}
|
|
10714
10756
|
return {
|
|
10715
10757
|
hash: sha256Hex(`dev:${getStableDeviceId()}`),
|
|
10716
|
-
label: `${hostname} (dev)`
|
|
10758
|
+
label: `${hostname ?? "unknown-host"} (dev)`
|
|
10717
10759
|
};
|
|
10718
10760
|
}
|
|
10719
10761
|
function detectCiRepo() {
|
|
@@ -10755,19 +10797,25 @@ function readCache() {
|
|
|
10755
10797
|
if (f.endsWith(".tmp")) {
|
|
10756
10798
|
try {
|
|
10757
10799
|
fs2.unlinkSync(path3.join(CACHE_DIR, f));
|
|
10758
|
-
} catch {
|
|
10800
|
+
} catch (err) {
|
|
10801
|
+
logger.debug("license cache: stale tmp cleanup failed", { file: f, error: err.message });
|
|
10759
10802
|
}
|
|
10760
10803
|
}
|
|
10761
10804
|
}
|
|
10762
|
-
} catch {
|
|
10805
|
+
} catch (err) {
|
|
10806
|
+
logger.debug("license cache: cache dir not readable (first run?)", { error: err.message });
|
|
10763
10807
|
}
|
|
10764
10808
|
try {
|
|
10765
10809
|
if (!fs2.existsSync(CACHE_FILE)) return null;
|
|
10766
10810
|
const raw = fs2.readFileSync(CACHE_FILE, "utf8");
|
|
10767
10811
|
const parsed = JSON.parse(raw);
|
|
10768
|
-
if (typeof parsed.jwt !== "string" || typeof parsed.expiresAt !== "number")
|
|
10812
|
+
if (typeof parsed.jwt !== "string" || typeof parsed.expiresAt !== "number") {
|
|
10813
|
+
logger.debug("license cache: malformed entry \u2014 ignoring");
|
|
10814
|
+
return null;
|
|
10815
|
+
}
|
|
10769
10816
|
return parsed;
|
|
10770
|
-
} catch {
|
|
10817
|
+
} catch (err) {
|
|
10818
|
+
logger.debug("license cache: read failed \u2014 treating as no cache", { error: err.message });
|
|
10771
10819
|
return null;
|
|
10772
10820
|
}
|
|
10773
10821
|
}
|
|
@@ -10777,13 +10825,15 @@ function writeCache(entry) {
|
|
|
10777
10825
|
const tmp = `${CACHE_FILE}.${process.pid}.tmp`;
|
|
10778
10826
|
fs2.writeFileSync(tmp, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
10779
10827
|
fs2.renameSync(tmp, CACHE_FILE);
|
|
10780
|
-
} catch {
|
|
10828
|
+
} catch (err) {
|
|
10829
|
+
logger.debug("license cache: write failed \u2014 will re-activate next run", { error: err.message });
|
|
10781
10830
|
}
|
|
10782
10831
|
}
|
|
10783
10832
|
function clearCache() {
|
|
10784
10833
|
try {
|
|
10785
10834
|
fs2.unlinkSync(CACHE_FILE);
|
|
10786
|
-
} catch {
|
|
10835
|
+
} catch (err) {
|
|
10836
|
+
logger.debug("license cache: clear failed", { error: err.message });
|
|
10787
10837
|
}
|
|
10788
10838
|
}
|
|
10789
10839
|
|
|
@@ -10802,27 +10852,6 @@ import { createPublicKey as createPublicKey2, verify as verify2 } from "crypto";
|
|
|
10802
10852
|
init_esm_shims();
|
|
10803
10853
|
var MODEL = { "alpha": 1, "labels": ["assertion", "env-config", "flaky-by-retry", "locator-not-found", "navigation", "network", "timeout"], "logLikelihoods": { "assertion": { "NUM": -4.574710978503383, "assertionerror": -4.980176086611547, "be": -4.980176086611547, "cart": -4.980176086611547, "checkout": -4.980176086611547, "com": -4.980176086611547, "dashboard": -4.980176086611547, "deep": -4.980176086611547, "equal": -4.980176086611547, "equality": -4.980176086611547, "error": -4.980176086611547, "expect": -3.5938817254916566, "expected": -3.1083739097099556, "failed": -4.980176086611547, "false": -4.980176086611547, "got": -4.980176086611547, "id": -4.574710978503383, "john": -4.980176086611547, "locator": -4.574710978503383, "login": -4.980176086611547, "must": -4.980176086611547, "page": -4.980176086611547, "pattern": -4.980176086611547, "received": -3.275427994373122, "string": -4.980176086611547, "to": -4.980176086611547, "tobe": -4.980176086611547, "tobetruthy": -4.980176086611547, "toequal": -4.980176086611547, "tohavecount": -4.980176086611547, "tohavetext": -4.980176086611547, "tohavetitle": -4.980176086611547, "tohavevalue": -4.980176086611547, "truthy": -4.980176086611547, "value": -4.980176086611547 }, "env-config": { "NUM": -4.997212273764115, "api": -4.997212273764115, "at": -4.997212273764115, "back": -4.997212273764115, "base": -4.997212273764115, "baseurl": -4.997212273764115, "browsers": -4.997212273764115, "cannot": -4.997212273764115, "ci": -4.997212273764115, "config": -4.304065093204169, "directory": -4.591747165655951, "dotenv": -4.997212273764115, "enoent": -4.997212273764115, "env": -4.591747165655951, "environment": -4.591747165655951, "error": -4.08092154188996, "existent": -4.997212273764115, "failed": -4.997212273764115, "falling": -4.997212273764115, "file": -4.997212273764115, "find": -4.997212273764115, "from": -4.997212273764115, "in": -4.591747165655951, "invalid": -4.997212273764115, "is": -4.997212273764115, "json": -4.591747165655951, "key": -4.997212273764115, "load": -4.997212273764115, "missing": -4.997212273764115, "module": -4.997212273764115, "no": -4.997212273764115, "non": -4.997212273764115, "not": -4.997212273764115, "open": -4.997212273764115, "or": -4.997212273764115, "path": -4.997212273764115, "playwright": -4.591747165655951, "points": -4.997212273764115, "position": -4.997212273764115, "required": -4.997212273764115, "set": -4.997212273764115, "spec": -4.997212273764115, "such": -4.997212273764115, "syntaxerror": -4.997212273764115, "test": -4.997212273764115, "to": -4.304065093204169, "token": -4.997212273764115, "ts": -4.997212273764115, "tsconfig": -4.997212273764115, "undefined": -4.591747165655951, "unexpected": -4.997212273764115, "url": -4.997212273764115, "values": -4.997212273764115, "variable": -4.591747165655951 }, "flaky-by-retry": { "after": -4.58836306767171, "and": -4.993828175779875, "animation": -4.993828175779875, "attempt": -4.07753744390572, "between": -4.993828175779875, "blip": -4.993828175779875, "cause": -4.993828175779875, "change": -4.993828175779875, "click": -4.993828175779875, "code": -4.993828175779875, "commit": -4.993828175779875, "condition": -4.993828175779875, "dependency": -4.993828175779875, "detached": -4.993828175779875, "deterministic": -4.993828175779875, "element": -4.993828175779875, "failed": -4.300680995219929, "fails": -4.993828175779875, "flaky": -4.07753744390572, "green": -4.993828175779875, "in": -4.993828175779875, "intermittent": -4.993828175779875, "locator": -4.993828175779875, "marked": -4.993828175779875, "network": -4.993828175779875, "no": -4.993828175779875, "non": -4.993828175779875, "on": -3.895215887111765, "order": -4.993828175779875, "passed": -3.895215887111765, "quarantined": -4.993828175779875, "race": -4.993828175779875, "reproducible": -4.993828175779875, "rerun": -4.993828175779875, "retry": -4.07753744390572, "runs": -4.993828175779875, "same": -4.993828175779875, "second": -4.993828175779875, "stale": -4.993828175779875, "succeeded": -4.993828175779875, "test": -4.58836306767171, "the": -4.993828175779875, "then": -4.993828175779875, "timeout": -4.993828175779875, "unstable": -4.993828175779875, "with": -4.993828175779875, "without": -4.993828175779875 }, "locator-not-found": { "an": -4.993828175779875, "attached": -4.993828175779875, "btn": -4.993828175779875, "button": -4.58836306767171, "cart": -4.993828175779875, "checkout": -4.993828175779875, "click": -4.993828175779875, "data": -4.993828175779875, "div": -4.993828175779875, "dom": -4.993828175779875, "element": -4.07753744390572, "elements": -4.300680995219929, "error": -4.58836306767171, "find": -4.993828175779875, "for": -4.58836306767171, "found": -4.58836306767171, "getbyrole": -4.993828175779875, "getbytext": -4.993828175779875, "icon": -4.993828175779875, "id": -4.993828175779875, "is": -4.993828175779875, "locator": -4.07753744390572, "matched": -4.993828175779875, "matches": -4.993828175779875, "missing": -4.993828175779875, "mode": -4.58836306767171, "name": -4.993828175779875, "no": -4.58836306767171, "node": -4.993828175779875, "not": -4.58836306767171, "resolved": -4.58836306767171, "save": -4.993828175779875, "selector": -4.58836306767171, "strict": -4.58836306767171, "submit": -4.993828175779875, "testid": -4.993828175779875, "textcontent": -4.993828175779875, "the": -4.58836306767171, "to": -4.07753744390572, "total": -4.993828175779875, "unable": -4.993828175779875, "violation": -4.58836306767171, "waiting": -4.993828175779875, "with": -4.993828175779875, "xpath": -4.993828175779875 }, "navigation": { "NUM": -4.983606621708336, "aborted": -4.983606621708336, "another": -4.983606621708336, "back": -4.983606621708336, "because": -4.578141513600172, "by": -4.578141513600172, "checkout": -4.983606621708336, "context": -4.983606621708336, "crashed": -4.983606621708336, "dashboard": -4.983606621708336, "destroyed": -4.983606621708336, "detached": -4.983606621708336, "entry": -4.983606621708336, "err": -4.578141513600172, "exceeded": -4.983606621708336, "execution": -4.983606621708336, "failed": -4.983606621708336, "frame": -4.578141513600172, "goback": -4.983606621708336, "goto": -4.290459441148391, "history": -4.983606621708336, "home": -4.983606621708336, "interrupted": -4.578141513600172, "is": -4.983606621708336, "likely": -4.983606621708336, "login": -4.983606621708336, "many": -4.983606621708336, "maybe": -4.983606621708336, "most": -4.983606621708336, "navigate": -4.983606621708336, "navigating": -4.983606621708336, "navigation": -3.7308436532129683, "net": -4.578141513600172, "no": -4.983606621708336, "of": -4.983606621708336, "page": -3.884994333040227, "redirects": -4.983606621708336, "timeout": -4.983606621708336, "to": -3.7308436532129683, "too": -4.983606621708336, "waitforurl": -4.983606621708336, "was": -4.578141513600172 }, "network": { "NUM": -4.57126863431241, "URL": -4.283586561860629, "again": -4.976733742420574, "api": -4.976733742420574, "apirequestcontext": -4.976733742420574, "at": -4.976733742420574, "body": -4.976733742420574, "connect": -4.976733742420574, "connection": -4.57126863431241, "disconnected": -4.976733742420574, "eai": -4.976733742420574, "econnrefused": -4.976733742420574, "err": -3.8781214537524646, "error": -4.976733742420574, "failed": -4.283586561860629, "fetch": -4.976733742420574, "fetching": -4.976733742420574, "from": -4.976733742420574, "get": -4.976733742420574, "getaddrinfo": -4.976733742420574, "goto": -4.976733742420574, "hang": -4.976733742420574, "internet": -4.976733742420574, "loading": -4.976733742420574, "name": -4.976733742420574, "net": -3.8781214537524646, "not": -4.976733742420574, "on": -4.976733742420574, "out": -4.976733742420574, "page": -4.57126863431241, "products": -4.976733742420574, "protocol": -4.976733742420574, "reading": -4.976733742420574, "reason": -4.976733742420574, "refused": -4.976733742420574, "request": -4.57126863431241, "resolved": -4.976733742420574, "resource": -4.976733742420574, "response": -4.976733742420574, "route": -4.976733742420574, "socket": -4.976733742420574, "ssl": -4.976733742420574, "timed": -4.976733742420574, "to": -4.976733742420574, "up": -4.976733742420574, "upstream": -4.976733742420574, "while": -4.976733742420574 }, "timeout": { "NUM": -3.4965075614664802, "after": -5.000584958242754, "be": -4.59511985013459, "cart": -5.000584958242754, "click": -5.000584958242754, "element": -5.000584958242754, "event": -5.000584958242754, "exceeded": -3.6142905971228636, "expect": -5.000584958242754, "fill": -5.000584958242754, "for": -3.7478219897473863, "function": -5.000584958242754, "load": -5.000584958242754, "locator": -4.084294226368599, "navigation": -5.000584958242754, "of": -5.000584958242754, "out": -5.000584958242754, "page": -4.59511985013459, "running": -5.000584958242754, "selector": -5.000584958242754, "submit": -5.000584958242754, "test": -4.59511985013459, "timed": -5.000584958242754, "timeout": -3.6142905971228636, "timeouterror": -4.59511985013459, "to": -4.59511985013459, "tobevisible": -5.000584958242754, "visible": -4.59511985013459, "waitforloadstate": -5.000584958242754, "waitforselector": -5.000584958242754, "waiting": -3.7478219897473863, "while": -5.000584958242754 } }, "playwrightVersion": "1.59.1", "priors": { "assertion": -1.9636097261547143, "env-config": -1.9636097261547143, "flaky-by-retry": -1.9636097261547143, "locator-not-found": -1.9636097261547143, "navigation": -1.9636097261547143, "network": -1.9636097261547143, "timeout": -1.845826690498331 }, "totals": { "assertion": 64, "env-config": 69, "flaky-by-retry": 68, "locator-not-found": 68, "navigation": 65, "network": 63, "timeout": 70 }, "version": 1, "vocabSize": 227 };
|
|
10804
10854
|
|
|
10805
|
-
// src/utils/logger.ts
|
|
10806
|
-
init_esm_shims();
|
|
10807
|
-
var PREFIX = "[reportforge]";
|
|
10808
|
-
var logger = {
|
|
10809
|
-
info(msg) {
|
|
10810
|
-
console.log(`${PREFIX} ${msg}`);
|
|
10811
|
-
},
|
|
10812
|
-
warn(msg) {
|
|
10813
|
-
console.warn(`${PREFIX} WARN: ${msg}`);
|
|
10814
|
-
},
|
|
10815
|
-
error(msg) {
|
|
10816
|
-
console.error(`${PREFIX} ERROR: ${msg}`);
|
|
10817
|
-
},
|
|
10818
|
-
debug(msg, data) {
|
|
10819
|
-
if (!process.env.RF_DEBUG) return;
|
|
10820
|
-
const suffix = data !== void 0 ? `
|
|
10821
|
-
${JSON.stringify(data, null, 2)}` : "";
|
|
10822
|
-
console.log(`${PREFIX} DEBUG: ${msg}${suffix}`);
|
|
10823
|
-
}
|
|
10824
|
-
};
|
|
10825
|
-
|
|
10826
10855
|
// src/analysis/ModelStore.ts
|
|
10827
10856
|
var MODEL_SIG_DOMAIN = "reportforge-model-sig:v1\n";
|
|
10828
10857
|
var testDeps = null;
|
|
@@ -11093,9 +11122,16 @@ var LicenseClient = class {
|
|
|
11093
11122
|
function verifyCache(verifier, cached, expectedFp) {
|
|
11094
11123
|
try {
|
|
11095
11124
|
const payload = verifier.verify(cached.jwt);
|
|
11096
|
-
if (payload.fp !== expectedFp)
|
|
11125
|
+
if (payload.fp !== expectedFp) {
|
|
11126
|
+
logger.debug("license cache rejected: fingerprint mismatch", {
|
|
11127
|
+
cachedFp: payload.fp.slice(0, 12),
|
|
11128
|
+
expectedFp: expectedFp.slice(0, 12)
|
|
11129
|
+
});
|
|
11130
|
+
return null;
|
|
11131
|
+
}
|
|
11097
11132
|
return payload;
|
|
11098
|
-
} catch {
|
|
11133
|
+
} catch (err) {
|
|
11134
|
+
logger.debug("license cache rejected: JWT verify failed", { error: err.message });
|
|
11099
11135
|
return null;
|
|
11100
11136
|
}
|
|
11101
11137
|
}
|
|
@@ -11151,7 +11187,8 @@ function maybePrintUpdateNotice(licenseInfo, currentVersion) {
|
|
|
11151
11187
|
logger.info(
|
|
11152
11188
|
`Update available: ${currentVersion} -> ${shown}. Run: npm i -D @reportforge/playwright-pdf@latest`
|
|
11153
11189
|
);
|
|
11154
|
-
} catch {
|
|
11190
|
+
} catch (err) {
|
|
11191
|
+
logger.debug("update notice failed (ignored)", { error: err.message });
|
|
11155
11192
|
}
|
|
11156
11193
|
}
|
|
11157
11194
|
|
|
@@ -12848,8 +12885,7 @@ var BrowserManager = class {
|
|
|
12848
12885
|
}
|
|
12849
12886
|
async close() {
|
|
12850
12887
|
if (this.browser) {
|
|
12851
|
-
await this.browser.close().catch(() => {
|
|
12852
|
-
});
|
|
12888
|
+
await this.browser.close().catch((err) => logger.debug("browser close failed (ignored)", { error: err.message }));
|
|
12853
12889
|
this.browser = void 0;
|
|
12854
12890
|
}
|
|
12855
12891
|
}
|
|
@@ -12861,7 +12897,8 @@ var BrowserManager = class {
|
|
|
12861
12897
|
const chromeFinder = require_lib2();
|
|
12862
12898
|
const found = chromeFinder();
|
|
12863
12899
|
if (found && fs7.existsSync(found)) return found;
|
|
12864
|
-
} catch {
|
|
12900
|
+
} catch (err) {
|
|
12901
|
+
logger.debug("chrome discovery: chrome-finder failed", { error: err.message });
|
|
12865
12902
|
}
|
|
12866
12903
|
const linuxPaths = [
|
|
12867
12904
|
"/usr/bin/google-chrome",
|
|
@@ -12879,12 +12916,14 @@ var BrowserManager = class {
|
|
|
12879
12916
|
}).toString().trim().split("\n")[0];
|
|
12880
12917
|
if (found && fs7.existsSync(found)) return found;
|
|
12881
12918
|
} catch {
|
|
12919
|
+
logger.debug("chrome discovery: which lookup found nothing");
|
|
12882
12920
|
}
|
|
12883
12921
|
if (puppeteer.executablePath) {
|
|
12884
12922
|
try {
|
|
12885
12923
|
const bundled = puppeteer.executablePath();
|
|
12886
12924
|
if (bundled && fs7.existsSync(bundled)) return bundled;
|
|
12887
|
-
} catch {
|
|
12925
|
+
} catch (err) {
|
|
12926
|
+
logger.debug("chrome discovery: no puppeteer bundled executable", { error: err.message });
|
|
12888
12927
|
}
|
|
12889
12928
|
}
|
|
12890
12929
|
logger.warn(
|
|
@@ -12910,8 +12949,10 @@ var HtmlWriter = class {
|
|
|
12910
12949
|
}
|
|
12911
12950
|
async cleanup() {
|
|
12912
12951
|
if (this.tempPath) {
|
|
12913
|
-
|
|
12914
|
-
|
|
12952
|
+
const tempPath = this.tempPath;
|
|
12953
|
+
await fs8.promises.unlink(tempPath).catch(
|
|
12954
|
+
(err) => logger.debug("temp HTML cleanup failed (ignored)", { path: tempPath, error: err.message })
|
|
12955
|
+
);
|
|
12915
12956
|
this.tempPath = void 0;
|
|
12916
12957
|
}
|
|
12917
12958
|
}
|
|
@@ -12976,7 +13017,8 @@ var PdfEncryptor = class {
|
|
|
12976
13017
|
try {
|
|
12977
13018
|
execFileSync("qpdf", ["--version"], { stdio: "pipe" });
|
|
12978
13019
|
return true;
|
|
12979
|
-
} catch {
|
|
13020
|
+
} catch (err) {
|
|
13021
|
+
logger.debug("qpdf probe failed \u2014 treating as not installed", { error: err.message });
|
|
12980
13022
|
return false;
|
|
12981
13023
|
}
|
|
12982
13024
|
}
|
|
@@ -13133,7 +13175,8 @@ var PdfGenerator = class {
|
|
|
13133
13175
|
if (CHARTJS_BUNDLE2) {
|
|
13134
13176
|
this.templateEngine.setChartjsBundle(CHARTJS_BUNDLE2);
|
|
13135
13177
|
}
|
|
13136
|
-
} catch {
|
|
13178
|
+
} catch (err) {
|
|
13179
|
+
logger.debug("chartjs bundle not available \u2014 charts disabled", { error: err.message });
|
|
13137
13180
|
}
|
|
13138
13181
|
}
|
|
13139
13182
|
/**
|
|
@@ -14049,7 +14092,7 @@ function buildWatchUrl(serverUrl, runId, token) {
|
|
|
14049
14092
|
}
|
|
14050
14093
|
function formatLiveBanner(url) {
|
|
14051
14094
|
const label = "Live Tracker";
|
|
14052
|
-
const link = `Watch live: ${url}`;
|
|
14095
|
+
const link = `[reportforge] Watch live: ${url}`;
|
|
14053
14096
|
const width = Math.max(label.length, link.length);
|
|
14054
14097
|
const rule = "\u2500".repeat(width + 2);
|
|
14055
14098
|
return [rule, ` ${label.padEnd(width)} `, ` ${link.padEnd(width)} `, rule].join("\n");
|
|
@@ -14155,8 +14198,9 @@ var PdfReporter = class {
|
|
|
14155
14198
|
this.liveSteps = "failed";
|
|
14156
14199
|
this.liveConsole = false;
|
|
14157
14200
|
this.options = parseOptions(rawOptions);
|
|
14201
|
+
setLogLevel(this.options.logLevel);
|
|
14158
14202
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14159
|
-
this.version = true ? "0.
|
|
14203
|
+
this.version = true ? "0.19.0" : "0.x";
|
|
14160
14204
|
logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
|
|
14161
14205
|
this.licenseClient = new LicenseClient({
|
|
14162
14206
|
licenseKey: this.options.licenseKey,
|
|
@@ -14419,8 +14463,9 @@ var PdfReporter = class {
|
|
|
14419
14463
|
);
|
|
14420
14464
|
return;
|
|
14421
14465
|
}
|
|
14422
|
-
|
|
14423
|
-
|
|
14466
|
+
if (isLevelEnabled("info")) {
|
|
14467
|
+
console.log(formatLiveBanner(watchUrl));
|
|
14468
|
+
}
|
|
14424
14469
|
const isPrimaryShard = !shard || shard.current === 1;
|
|
14425
14470
|
const notify = this.options.notify;
|
|
14426
14471
|
if (isPrimaryShard && notify) {
|
package/package.json
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reportforge/playwright-pdf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Playwright Test reporter that generates designed PDF reports: minimal, detailed, and executive templates with CI/CD integrations",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"author": "ReportForge",
|
|
7
7
|
"homepage": "https://reportforge.org",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "https://github.com/muralidharan92/reportforge"
|
|
11
|
-
},
|
|
12
8
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/muralidharan92/reportforge/issues",
|
|
14
9
|
"email": "support@reportforge.org"
|
|
15
10
|
},
|
|
16
11
|
"keywords": [
|
|
@@ -30,7 +25,11 @@
|
|
|
30
25
|
"pdf-generator",
|
|
31
26
|
"ci",
|
|
32
27
|
"puppeteer",
|
|
33
|
-
"html-to-pdf"
|
|
28
|
+
"html-to-pdf",
|
|
29
|
+
"reportforge",
|
|
30
|
+
"reportforge-playwright-pdf",
|
|
31
|
+
"report",
|
|
32
|
+
"forge"
|
|
34
33
|
],
|
|
35
34
|
"main": "./dist/index.js",
|
|
36
35
|
"module": "./dist/index.mjs",
|
|
@@ -53,7 +52,8 @@
|
|
|
53
52
|
"ci-templates",
|
|
54
53
|
"README.md",
|
|
55
54
|
"CHANGELOG.md",
|
|
56
|
-
"LICENSE"
|
|
55
|
+
"LICENSE",
|
|
56
|
+
"SECURITY.md"
|
|
57
57
|
],
|
|
58
58
|
"scripts": {
|
|
59
59
|
"prebuild": "npm run bundle-chartjs && npm run bundle-fonts && npm run bundle-pubkey && npm run bundle-model && npm run precompile-templates",
|
|
@@ -82,14 +82,14 @@
|
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
84
|
"@playwright/test": ">=1.40.0",
|
|
85
|
-
"puppeteer-core": ">=21.0.0 <
|
|
85
|
+
"puppeteer-core": ">=21.0.0 <26.0.0"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"chrome-finder": "^1.0.7",
|
|
89
89
|
"framer-motion": "^12.40.0",
|
|
90
90
|
"glob": "^13.0.6",
|
|
91
91
|
"handlebars": "^4.7.8",
|
|
92
|
-
"sharp": "^0.
|
|
92
|
+
"sharp": "^0.35.3",
|
|
93
93
|
"zod": "^3.23.8"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
@@ -98,20 +98,20 @@
|
|
|
98
98
|
"@fontsource/source-serif-pro": "^5.2.5",
|
|
99
99
|
"@playwright/test": "^1.49.0",
|
|
100
100
|
"@types/handlebars": "^4.1.0",
|
|
101
|
-
"@types/node": "^
|
|
101
|
+
"@types/node": "^26.1.1",
|
|
102
102
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
103
103
|
"@typescript-eslint/parser": "^8.0.0",
|
|
104
|
-
"@vitest/coverage-v8": "^
|
|
104
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
105
105
|
"chart.js": "^4.4.0",
|
|
106
106
|
"eslint": "^9.0.0",
|
|
107
107
|
"prettier": "^3.3.0",
|
|
108
|
-
"puppeteer-core": "^
|
|
108
|
+
"puppeteer-core": "^25.3.0",
|
|
109
109
|
"tsup": "^8.3.0",
|
|
110
110
|
"tsx": "^4.19.0",
|
|
111
|
-
"typescript": "^5.
|
|
112
|
-
"vitest": "^
|
|
111
|
+
"typescript": "^5.9.3",
|
|
112
|
+
"vitest": "^4.1.10"
|
|
113
113
|
},
|
|
114
114
|
"engines": {
|
|
115
|
-
"node": ">=
|
|
115
|
+
"node": ">=20.0.0"
|
|
116
116
|
}
|
|
117
117
|
}
|