@reportforge/playwright-pdf 0.18.0 → 0.19.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 +23 -0
- package/README.md +13 -0
- package/dist/cli/index.js +47 -26
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +103 -53
- package/dist/index.mjs +103 -53
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,29 @@
|
|
|
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.1] - 2026-07-11
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **A blank browser window no longer flashes during report generation on Windows.** Chrome's new headless mode (the only mode left in Chrome 132+) briefly uncloaks a window on some Windows/GPU combinations; the PDF renderer now parks that window far off-screen. No effect on PDF output or other platforms.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [0.19.0] - 2026-07-11
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **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.
|
|
19
|
+
- **`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.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- 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.
|
|
24
|
+
- 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.
|
|
25
|
+
- CLI output prefixes are consistent: the `demo` and `export-feedback` commands use the same `[reportforge]` prefix as the reporter.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
6
29
|
## [0.18.0] - 2026-07-11
|
|
7
30
|
|
|
8
31
|
### Changed
|
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.
|
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}`);
|
|
@@ -7228,7 +7236,7 @@ var require_lib2 = __commonJS({
|
|
|
7228
7236
|
});
|
|
7229
7237
|
|
|
7230
7238
|
// src/pdf/BrowserManager.ts
|
|
7231
|
-
var import_child_process, import_fs3, BrowserManager;
|
|
7239
|
+
var import_child_process, import_fs3, LAUNCH_ARGS, BrowserManager;
|
|
7232
7240
|
var init_BrowserManager = __esm({
|
|
7233
7241
|
"src/pdf/BrowserManager.ts"() {
|
|
7234
7242
|
"use strict";
|
|
@@ -7236,29 +7244,33 @@ var init_BrowserManager = __esm({
|
|
|
7236
7244
|
import_child_process = require("child_process");
|
|
7237
7245
|
import_fs3 = __toESM(require("fs"));
|
|
7238
7246
|
init_logger();
|
|
7247
|
+
LAUNCH_ARGS = [
|
|
7248
|
+
"--no-sandbox",
|
|
7249
|
+
"--disable-setuid-sandbox",
|
|
7250
|
+
"--disable-dev-shm-usage",
|
|
7251
|
+
"--disable-gpu",
|
|
7252
|
+
// Chrome's new headless mode (--headless=new, the only mode left in
|
|
7253
|
+
// Chrome 132+) briefly uncloaks a blank window on some Windows/GPU
|
|
7254
|
+
// combinations. Parking the window far off-screen makes that transient
|
|
7255
|
+
// invisible; harmless everywhere else and does not affect PDF output.
|
|
7256
|
+
"--window-position=-32000,-32000"
|
|
7257
|
+
];
|
|
7239
7258
|
BrowserManager = class {
|
|
7240
7259
|
async launch(executablePath) {
|
|
7241
7260
|
const puppeteer = require("puppeteer-core");
|
|
7242
7261
|
const chromePath = this.resolveChromePath(executablePath, puppeteer);
|
|
7243
7262
|
logger.info(`Launching browser: ${chromePath ?? "puppeteer default"}`);
|
|
7244
|
-
const launchArgs = [
|
|
7245
|
-
"--no-sandbox",
|
|
7246
|
-
"--disable-setuid-sandbox",
|
|
7247
|
-
"--disable-dev-shm-usage",
|
|
7248
|
-
"--disable-gpu"
|
|
7249
|
-
];
|
|
7250
7263
|
this.browser = await puppeteer.launch({
|
|
7251
7264
|
executablePath: chromePath,
|
|
7252
7265
|
headless: true,
|
|
7253
|
-
args:
|
|
7266
|
+
args: LAUNCH_ARGS
|
|
7254
7267
|
});
|
|
7255
7268
|
const page = await this.browser.newPage();
|
|
7256
7269
|
return { browser: this.browser, page };
|
|
7257
7270
|
}
|
|
7258
7271
|
async close() {
|
|
7259
7272
|
if (this.browser) {
|
|
7260
|
-
await this.browser.close().catch(() => {
|
|
7261
|
-
});
|
|
7273
|
+
await this.browser.close().catch((err) => logger.debug("browser close failed (ignored)", { error: err.message }));
|
|
7262
7274
|
this.browser = void 0;
|
|
7263
7275
|
}
|
|
7264
7276
|
}
|
|
@@ -7270,7 +7282,8 @@ var init_BrowserManager = __esm({
|
|
|
7270
7282
|
const chromeFinder = require_lib2();
|
|
7271
7283
|
const found = chromeFinder();
|
|
7272
7284
|
if (found && import_fs3.default.existsSync(found)) return found;
|
|
7273
|
-
} catch {
|
|
7285
|
+
} catch (err) {
|
|
7286
|
+
logger.debug("chrome discovery: chrome-finder failed", { error: err.message });
|
|
7274
7287
|
}
|
|
7275
7288
|
const linuxPaths = [
|
|
7276
7289
|
"/usr/bin/google-chrome",
|
|
@@ -7288,12 +7301,14 @@ var init_BrowserManager = __esm({
|
|
|
7288
7301
|
}).toString().trim().split("\n")[0];
|
|
7289
7302
|
if (found && import_fs3.default.existsSync(found)) return found;
|
|
7290
7303
|
} catch {
|
|
7304
|
+
logger.debug("chrome discovery: which lookup found nothing");
|
|
7291
7305
|
}
|
|
7292
7306
|
if (puppeteer.executablePath) {
|
|
7293
7307
|
try {
|
|
7294
7308
|
const bundled = puppeteer.executablePath();
|
|
7295
7309
|
if (bundled && import_fs3.default.existsSync(bundled)) return bundled;
|
|
7296
|
-
} catch {
|
|
7310
|
+
} catch (err) {
|
|
7311
|
+
logger.debug("chrome discovery: no puppeteer bundled executable", { error: err.message });
|
|
7297
7312
|
}
|
|
7298
7313
|
}
|
|
7299
7314
|
logger.warn(
|
|
@@ -7315,6 +7330,7 @@ var init_HtmlWriter = __esm({
|
|
|
7315
7330
|
import_os = __toESM(require("os"));
|
|
7316
7331
|
import_path2 = __toESM(require("path"));
|
|
7317
7332
|
import_crypto = __toESM(require("crypto"));
|
|
7333
|
+
init_logger();
|
|
7318
7334
|
HtmlWriter = class {
|
|
7319
7335
|
async write(html) {
|
|
7320
7336
|
const tmpDir = import_os.default.tmpdir();
|
|
@@ -7325,8 +7341,10 @@ var init_HtmlWriter = __esm({
|
|
|
7325
7341
|
}
|
|
7326
7342
|
async cleanup() {
|
|
7327
7343
|
if (this.tempPath) {
|
|
7328
|
-
|
|
7329
|
-
|
|
7344
|
+
const tempPath = this.tempPath;
|
|
7345
|
+
await import_fs4.default.promises.unlink(tempPath).catch(
|
|
7346
|
+
(err) => logger.debug("temp HTML cleanup failed (ignored)", { path: tempPath, error: err.message })
|
|
7347
|
+
);
|
|
7330
7348
|
this.tempPath = void 0;
|
|
7331
7349
|
}
|
|
7332
7350
|
}
|
|
@@ -7405,7 +7423,8 @@ var init_PdfEncryptor = __esm({
|
|
|
7405
7423
|
try {
|
|
7406
7424
|
(0, import_child_process2.execFileSync)("qpdf", ["--version"], { stdio: "pipe" });
|
|
7407
7425
|
return true;
|
|
7408
|
-
} catch {
|
|
7426
|
+
} catch (err) {
|
|
7427
|
+
logger.debug("qpdf probe failed \u2014 treating as not installed", { error: err.message });
|
|
7409
7428
|
return false;
|
|
7410
7429
|
}
|
|
7411
7430
|
}
|
|
@@ -7638,7 +7657,8 @@ var init_PdfGenerator = __esm({
|
|
|
7638
7657
|
if (CHARTJS_BUNDLE2) {
|
|
7639
7658
|
this.templateEngine.setChartjsBundle(CHARTJS_BUNDLE2);
|
|
7640
7659
|
}
|
|
7641
|
-
} catch {
|
|
7660
|
+
} catch (err) {
|
|
7661
|
+
logger.debug("chartjs bundle not available \u2014 charts disabled", { error: err.message });
|
|
7642
7662
|
}
|
|
7643
7663
|
}
|
|
7644
7664
|
/**
|
|
@@ -12085,6 +12105,7 @@ var init_schema = __esm({
|
|
|
12085
12105
|
reportTitle: external_exports.string().optional().default(DEFAULT_OPTIONS.reportTitle),
|
|
12086
12106
|
projectName: external_exports.string().optional(),
|
|
12087
12107
|
open: external_exports.boolean().optional().default(DEFAULT_OPTIONS.open),
|
|
12108
|
+
logLevel: external_exports.enum(["silent", "error", "warn", "info", "debug"]).optional().default("info"),
|
|
12088
12109
|
puppeteerExecutablePath: external_exports.string().optional(),
|
|
12089
12110
|
serverUrl: external_exports.string().url().optional(),
|
|
12090
12111
|
compressionLevel: external_exports.enum(["auto", "none", "balanced", "max"]).optional().default(DEFAULT_OPTIONS.compressionLevel),
|
|
@@ -12549,7 +12570,7 @@ function parseArgs() {
|
|
|
12549
12570
|
const rawTemplate = templateArg?.slice("--template=".length);
|
|
12550
12571
|
const rawOutput = outputArg?.slice("--output=".length);
|
|
12551
12572
|
if (rawTemplate && !VALID_TEMPLATES.has(rawTemplate)) {
|
|
12552
|
-
console.warn(`[
|
|
12573
|
+
console.warn(`[reportforge] Unknown template '${rawTemplate}', using 'detailed'. Valid: ${TEMPLATE_IDS.join(", ")}`);
|
|
12553
12574
|
}
|
|
12554
12575
|
const template = rawTemplate && VALID_TEMPLATES.has(rawTemplate) ? rawTemplate : "detailed";
|
|
12555
12576
|
const outputFile = rawOutput ?? import_path7.default.join("playwright-report", "demo-report.pdf");
|
|
@@ -12557,7 +12578,7 @@ function parseArgs() {
|
|
|
12557
12578
|
}
|
|
12558
12579
|
async function run() {
|
|
12559
12580
|
const { template, outputFile } = parseArgs();
|
|
12560
|
-
console.log("[
|
|
12581
|
+
console.log("[reportforge] Generating demo report...");
|
|
12561
12582
|
console.log(` Template : ${template}`);
|
|
12562
12583
|
console.log(` Output : ${outputFile}`);
|
|
12563
12584
|
const reportData = buildDemoReportData(template);
|
|
@@ -12568,10 +12589,10 @@ async function run() {
|
|
|
12568
12589
|
const paths = await generator.generateAll(reportData, options, DEMO_LICENSE_INFO2);
|
|
12569
12590
|
if (paths.length > 0) {
|
|
12570
12591
|
console.log(`
|
|
12571
|
-
[
|
|
12592
|
+
[reportforge] Demo report written: ${paths[0]}`);
|
|
12572
12593
|
console.log(" Open the PDF to preview exactly what ReportForge generates for your Playwright runs.");
|
|
12573
12594
|
} else {
|
|
12574
|
-
console.warn("\n[
|
|
12595
|
+
console.warn("\n[reportforge] Warning: no PDF was written \u2014 check that Chrome is available.");
|
|
12575
12596
|
process.exit(1);
|
|
12576
12597
|
}
|
|
12577
12598
|
}
|
|
@@ -12581,14 +12602,14 @@ async function runDemo() {
|
|
|
12581
12602
|
} catch (err) {
|
|
12582
12603
|
const msg = err instanceof Error ? err.message : String(err);
|
|
12583
12604
|
if (/chrome|chromium|executable|browser|launcher/i.test(msg)) {
|
|
12584
|
-
console.error("\n[
|
|
12605
|
+
console.error("\n[reportforge] Chrome not found.\n");
|
|
12585
12606
|
console.error(" Fix: set PUPPETEER_EXECUTABLE_PATH, e.g.:");
|
|
12586
12607
|
console.error(" PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable \\");
|
|
12587
12608
|
console.error(" npx @reportforge/playwright-pdf demo\n");
|
|
12588
12609
|
console.error(` Original error: ${msg}`);
|
|
12589
12610
|
} else {
|
|
12590
12611
|
console.error(`
|
|
12591
|
-
[
|
|
12612
|
+
[reportforge] Demo generation failed: ${msg}`);
|
|
12592
12613
|
if (process.env.RF_DEBUG) console.error(err);
|
|
12593
12614
|
}
|
|
12594
12615
|
process.exit(1);
|
|
@@ -12648,10 +12669,10 @@ ${rows.join("\n")}
|
|
|
12648
12669
|
function exportFeedbackCli() {
|
|
12649
12670
|
const res = exportFeedback();
|
|
12650
12671
|
if (res.rowCount === 0) {
|
|
12651
|
-
process.stdout.write("No locally collected failures found (~/.reportforge/*/unclassified.csv).\n");
|
|
12672
|
+
process.stdout.write("[reportforge] No locally collected failures found (~/.reportforge/*/unclassified.csv).\n");
|
|
12652
12673
|
} else {
|
|
12653
12674
|
process.stdout.write(
|
|
12654
|
-
`Saved ${res.rowCount} row(s) to ${res.outFile} - tokenized + redacted, LOCAL only.
|
|
12675
|
+
`[reportforge] Saved ${res.rowCount} row(s) to ${res.outFile} - tokenized + redacted, LOCAL only.
|
|
12655
12676
|
Review it before sharing; there is no upload.
|
|
12656
12677
|
`
|
|
12657
12678
|
);
|
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
|
|
|
@@ -12826,29 +12863,33 @@ var ScreenshotEmbedder = class {
|
|
|
12826
12863
|
init_cjs_shims();
|
|
12827
12864
|
var import_child_process2 = require("child_process");
|
|
12828
12865
|
var import_fs6 = __toESM(require("fs"));
|
|
12866
|
+
var LAUNCH_ARGS = [
|
|
12867
|
+
"--no-sandbox",
|
|
12868
|
+
"--disable-setuid-sandbox",
|
|
12869
|
+
"--disable-dev-shm-usage",
|
|
12870
|
+
"--disable-gpu",
|
|
12871
|
+
// Chrome's new headless mode (--headless=new, the only mode left in
|
|
12872
|
+
// Chrome 132+) briefly uncloaks a blank window on some Windows/GPU
|
|
12873
|
+
// combinations. Parking the window far off-screen makes that transient
|
|
12874
|
+
// invisible; harmless everywhere else and does not affect PDF output.
|
|
12875
|
+
"--window-position=-32000,-32000"
|
|
12876
|
+
];
|
|
12829
12877
|
var BrowserManager = class {
|
|
12830
12878
|
async launch(executablePath) {
|
|
12831
12879
|
const puppeteer = require("puppeteer-core");
|
|
12832
12880
|
const chromePath = this.resolveChromePath(executablePath, puppeteer);
|
|
12833
12881
|
logger.info(`Launching browser: ${chromePath ?? "puppeteer default"}`);
|
|
12834
|
-
const launchArgs = [
|
|
12835
|
-
"--no-sandbox",
|
|
12836
|
-
"--disable-setuid-sandbox",
|
|
12837
|
-
"--disable-dev-shm-usage",
|
|
12838
|
-
"--disable-gpu"
|
|
12839
|
-
];
|
|
12840
12882
|
this.browser = await puppeteer.launch({
|
|
12841
12883
|
executablePath: chromePath,
|
|
12842
12884
|
headless: true,
|
|
12843
|
-
args:
|
|
12885
|
+
args: LAUNCH_ARGS
|
|
12844
12886
|
});
|
|
12845
12887
|
const page = await this.browser.newPage();
|
|
12846
12888
|
return { browser: this.browser, page };
|
|
12847
12889
|
}
|
|
12848
12890
|
async close() {
|
|
12849
12891
|
if (this.browser) {
|
|
12850
|
-
await this.browser.close().catch(() => {
|
|
12851
|
-
});
|
|
12892
|
+
await this.browser.close().catch((err) => logger.debug("browser close failed (ignored)", { error: err.message }));
|
|
12852
12893
|
this.browser = void 0;
|
|
12853
12894
|
}
|
|
12854
12895
|
}
|
|
@@ -12860,7 +12901,8 @@ var BrowserManager = class {
|
|
|
12860
12901
|
const chromeFinder = require_lib2();
|
|
12861
12902
|
const found = chromeFinder();
|
|
12862
12903
|
if (found && import_fs6.default.existsSync(found)) return found;
|
|
12863
|
-
} catch {
|
|
12904
|
+
} catch (err) {
|
|
12905
|
+
logger.debug("chrome discovery: chrome-finder failed", { error: err.message });
|
|
12864
12906
|
}
|
|
12865
12907
|
const linuxPaths = [
|
|
12866
12908
|
"/usr/bin/google-chrome",
|
|
@@ -12878,12 +12920,14 @@ var BrowserManager = class {
|
|
|
12878
12920
|
}).toString().trim().split("\n")[0];
|
|
12879
12921
|
if (found && import_fs6.default.existsSync(found)) return found;
|
|
12880
12922
|
} catch {
|
|
12923
|
+
logger.debug("chrome discovery: which lookup found nothing");
|
|
12881
12924
|
}
|
|
12882
12925
|
if (puppeteer.executablePath) {
|
|
12883
12926
|
try {
|
|
12884
12927
|
const bundled = puppeteer.executablePath();
|
|
12885
12928
|
if (bundled && import_fs6.default.existsSync(bundled)) return bundled;
|
|
12886
|
-
} catch {
|
|
12929
|
+
} catch (err) {
|
|
12930
|
+
logger.debug("chrome discovery: no puppeteer bundled executable", { error: err.message });
|
|
12887
12931
|
}
|
|
12888
12932
|
}
|
|
12889
12933
|
logger.warn(
|
|
@@ -12909,8 +12953,10 @@ var HtmlWriter = class {
|
|
|
12909
12953
|
}
|
|
12910
12954
|
async cleanup() {
|
|
12911
12955
|
if (this.tempPath) {
|
|
12912
|
-
|
|
12913
|
-
|
|
12956
|
+
const tempPath = this.tempPath;
|
|
12957
|
+
await import_fs7.default.promises.unlink(tempPath).catch(
|
|
12958
|
+
(err) => logger.debug("temp HTML cleanup failed (ignored)", { path: tempPath, error: err.message })
|
|
12959
|
+
);
|
|
12914
12960
|
this.tempPath = void 0;
|
|
12915
12961
|
}
|
|
12916
12962
|
}
|
|
@@ -12975,7 +13021,8 @@ var PdfEncryptor = class {
|
|
|
12975
13021
|
try {
|
|
12976
13022
|
(0, import_child_process3.execFileSync)("qpdf", ["--version"], { stdio: "pipe" });
|
|
12977
13023
|
return true;
|
|
12978
|
-
} catch {
|
|
13024
|
+
} catch (err) {
|
|
13025
|
+
logger.debug("qpdf probe failed \u2014 treating as not installed", { error: err.message });
|
|
12979
13026
|
return false;
|
|
12980
13027
|
}
|
|
12981
13028
|
}
|
|
@@ -13132,7 +13179,8 @@ var PdfGenerator = class {
|
|
|
13132
13179
|
if (CHARTJS_BUNDLE2) {
|
|
13133
13180
|
this.templateEngine.setChartjsBundle(CHARTJS_BUNDLE2);
|
|
13134
13181
|
}
|
|
13135
|
-
} catch {
|
|
13182
|
+
} catch (err) {
|
|
13183
|
+
logger.debug("chartjs bundle not available \u2014 charts disabled", { error: err.message });
|
|
13136
13184
|
}
|
|
13137
13185
|
}
|
|
13138
13186
|
/**
|
|
@@ -14048,7 +14096,7 @@ function buildWatchUrl(serverUrl, runId, token) {
|
|
|
14048
14096
|
}
|
|
14049
14097
|
function formatLiveBanner(url) {
|
|
14050
14098
|
const label = "Live Tracker";
|
|
14051
|
-
const link = `Watch live: ${url}`;
|
|
14099
|
+
const link = `[reportforge] Watch live: ${url}`;
|
|
14052
14100
|
const width = Math.max(label.length, link.length);
|
|
14053
14101
|
const rule = "\u2500".repeat(width + 2);
|
|
14054
14102
|
return [rule, ` ${label.padEnd(width)} `, ` ${link.padEnd(width)} `, rule].join("\n");
|
|
@@ -14154,8 +14202,9 @@ var PdfReporter = class {
|
|
|
14154
14202
|
this.liveSteps = "failed";
|
|
14155
14203
|
this.liveConsole = false;
|
|
14156
14204
|
this.options = parseOptions(rawOptions);
|
|
14205
|
+
setLogLevel(this.options.logLevel);
|
|
14157
14206
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14158
|
-
this.version = true ? "0.
|
|
14207
|
+
this.version = true ? "0.19.1" : "0.x";
|
|
14159
14208
|
logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
|
|
14160
14209
|
this.licenseClient = new LicenseClient({
|
|
14161
14210
|
licenseKey: this.options.licenseKey,
|
|
@@ -14418,8 +14467,9 @@ var PdfReporter = class {
|
|
|
14418
14467
|
);
|
|
14419
14468
|
return;
|
|
14420
14469
|
}
|
|
14421
|
-
|
|
14422
|
-
|
|
14470
|
+
if (isLevelEnabled("info")) {
|
|
14471
|
+
console.log(formatLiveBanner(watchUrl));
|
|
14472
|
+
}
|
|
14423
14473
|
const isPrimaryShard = !shard || shard.current === 1;
|
|
14424
14474
|
const notify = this.options.notify;
|
|
14425
14475
|
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
|
|
|
@@ -12827,29 +12864,33 @@ var ScreenshotEmbedder = class {
|
|
|
12827
12864
|
init_esm_shims();
|
|
12828
12865
|
import { execSync as execSync2 } from "child_process";
|
|
12829
12866
|
import fs7 from "fs";
|
|
12867
|
+
var LAUNCH_ARGS = [
|
|
12868
|
+
"--no-sandbox",
|
|
12869
|
+
"--disable-setuid-sandbox",
|
|
12870
|
+
"--disable-dev-shm-usage",
|
|
12871
|
+
"--disable-gpu",
|
|
12872
|
+
// Chrome's new headless mode (--headless=new, the only mode left in
|
|
12873
|
+
// Chrome 132+) briefly uncloaks a blank window on some Windows/GPU
|
|
12874
|
+
// combinations. Parking the window far off-screen makes that transient
|
|
12875
|
+
// invisible; harmless everywhere else and does not affect PDF output.
|
|
12876
|
+
"--window-position=-32000,-32000"
|
|
12877
|
+
];
|
|
12830
12878
|
var BrowserManager = class {
|
|
12831
12879
|
async launch(executablePath) {
|
|
12832
12880
|
const puppeteer = __require("puppeteer-core");
|
|
12833
12881
|
const chromePath = this.resolveChromePath(executablePath, puppeteer);
|
|
12834
12882
|
logger.info(`Launching browser: ${chromePath ?? "puppeteer default"}`);
|
|
12835
|
-
const launchArgs = [
|
|
12836
|
-
"--no-sandbox",
|
|
12837
|
-
"--disable-setuid-sandbox",
|
|
12838
|
-
"--disable-dev-shm-usage",
|
|
12839
|
-
"--disable-gpu"
|
|
12840
|
-
];
|
|
12841
12883
|
this.browser = await puppeteer.launch({
|
|
12842
12884
|
executablePath: chromePath,
|
|
12843
12885
|
headless: true,
|
|
12844
|
-
args:
|
|
12886
|
+
args: LAUNCH_ARGS
|
|
12845
12887
|
});
|
|
12846
12888
|
const page = await this.browser.newPage();
|
|
12847
12889
|
return { browser: this.browser, page };
|
|
12848
12890
|
}
|
|
12849
12891
|
async close() {
|
|
12850
12892
|
if (this.browser) {
|
|
12851
|
-
await this.browser.close().catch(() => {
|
|
12852
|
-
});
|
|
12893
|
+
await this.browser.close().catch((err) => logger.debug("browser close failed (ignored)", { error: err.message }));
|
|
12853
12894
|
this.browser = void 0;
|
|
12854
12895
|
}
|
|
12855
12896
|
}
|
|
@@ -12861,7 +12902,8 @@ var BrowserManager = class {
|
|
|
12861
12902
|
const chromeFinder = require_lib2();
|
|
12862
12903
|
const found = chromeFinder();
|
|
12863
12904
|
if (found && fs7.existsSync(found)) return found;
|
|
12864
|
-
} catch {
|
|
12905
|
+
} catch (err) {
|
|
12906
|
+
logger.debug("chrome discovery: chrome-finder failed", { error: err.message });
|
|
12865
12907
|
}
|
|
12866
12908
|
const linuxPaths = [
|
|
12867
12909
|
"/usr/bin/google-chrome",
|
|
@@ -12879,12 +12921,14 @@ var BrowserManager = class {
|
|
|
12879
12921
|
}).toString().trim().split("\n")[0];
|
|
12880
12922
|
if (found && fs7.existsSync(found)) return found;
|
|
12881
12923
|
} catch {
|
|
12924
|
+
logger.debug("chrome discovery: which lookup found nothing");
|
|
12882
12925
|
}
|
|
12883
12926
|
if (puppeteer.executablePath) {
|
|
12884
12927
|
try {
|
|
12885
12928
|
const bundled = puppeteer.executablePath();
|
|
12886
12929
|
if (bundled && fs7.existsSync(bundled)) return bundled;
|
|
12887
|
-
} catch {
|
|
12930
|
+
} catch (err) {
|
|
12931
|
+
logger.debug("chrome discovery: no puppeteer bundled executable", { error: err.message });
|
|
12888
12932
|
}
|
|
12889
12933
|
}
|
|
12890
12934
|
logger.warn(
|
|
@@ -12910,8 +12954,10 @@ var HtmlWriter = class {
|
|
|
12910
12954
|
}
|
|
12911
12955
|
async cleanup() {
|
|
12912
12956
|
if (this.tempPath) {
|
|
12913
|
-
|
|
12914
|
-
|
|
12957
|
+
const tempPath = this.tempPath;
|
|
12958
|
+
await fs8.promises.unlink(tempPath).catch(
|
|
12959
|
+
(err) => logger.debug("temp HTML cleanup failed (ignored)", { path: tempPath, error: err.message })
|
|
12960
|
+
);
|
|
12915
12961
|
this.tempPath = void 0;
|
|
12916
12962
|
}
|
|
12917
12963
|
}
|
|
@@ -12976,7 +13022,8 @@ var PdfEncryptor = class {
|
|
|
12976
13022
|
try {
|
|
12977
13023
|
execFileSync("qpdf", ["--version"], { stdio: "pipe" });
|
|
12978
13024
|
return true;
|
|
12979
|
-
} catch {
|
|
13025
|
+
} catch (err) {
|
|
13026
|
+
logger.debug("qpdf probe failed \u2014 treating as not installed", { error: err.message });
|
|
12980
13027
|
return false;
|
|
12981
13028
|
}
|
|
12982
13029
|
}
|
|
@@ -13133,7 +13180,8 @@ var PdfGenerator = class {
|
|
|
13133
13180
|
if (CHARTJS_BUNDLE2) {
|
|
13134
13181
|
this.templateEngine.setChartjsBundle(CHARTJS_BUNDLE2);
|
|
13135
13182
|
}
|
|
13136
|
-
} catch {
|
|
13183
|
+
} catch (err) {
|
|
13184
|
+
logger.debug("chartjs bundle not available \u2014 charts disabled", { error: err.message });
|
|
13137
13185
|
}
|
|
13138
13186
|
}
|
|
13139
13187
|
/**
|
|
@@ -14049,7 +14097,7 @@ function buildWatchUrl(serverUrl, runId, token) {
|
|
|
14049
14097
|
}
|
|
14050
14098
|
function formatLiveBanner(url) {
|
|
14051
14099
|
const label = "Live Tracker";
|
|
14052
|
-
const link = `Watch live: ${url}`;
|
|
14100
|
+
const link = `[reportforge] Watch live: ${url}`;
|
|
14053
14101
|
const width = Math.max(label.length, link.length);
|
|
14054
14102
|
const rule = "\u2500".repeat(width + 2);
|
|
14055
14103
|
return [rule, ` ${label.padEnd(width)} `, ` ${link.padEnd(width)} `, rule].join("\n");
|
|
@@ -14155,8 +14203,9 @@ var PdfReporter = class {
|
|
|
14155
14203
|
this.liveSteps = "failed";
|
|
14156
14204
|
this.liveConsole = false;
|
|
14157
14205
|
this.options = parseOptions(rawOptions);
|
|
14206
|
+
setLogLevel(this.options.logLevel);
|
|
14158
14207
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14159
|
-
this.version = true ? "0.
|
|
14208
|
+
this.version = true ? "0.19.1" : "0.x";
|
|
14160
14209
|
logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
|
|
14161
14210
|
this.licenseClient = new LicenseClient({
|
|
14162
14211
|
licenseKey: this.options.licenseKey,
|
|
@@ -14419,8 +14468,9 @@ var PdfReporter = class {
|
|
|
14419
14468
|
);
|
|
14420
14469
|
return;
|
|
14421
14470
|
}
|
|
14422
|
-
|
|
14423
|
-
|
|
14471
|
+
if (isLevelEnabled("info")) {
|
|
14472
|
+
console.log(formatLiveBanner(watchUrl));
|
|
14473
|
+
}
|
|
14424
14474
|
const isPrimaryShard = !shard || shard.current === 1;
|
|
14425
14475
|
const notify = this.options.notify;
|
|
14426
14476
|
if (isPrimaryShard && notify) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reportforge/playwright-pdf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
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",
|