reporting-labs 0.6.10 → 0.6.12

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/README.md CHANGED
@@ -305,8 +305,6 @@ Every option is optional. `npx reporting-labs init` writes them all, with commen
305
305
  | `project` | – | `{ name, version, team, url }` shown under the title |
306
306
  | `metadata` | `{}` | Chips in the header, e.g. `{ env: 'staging', build: '#1842' }`. `build` labels the run in the trend; in CI the run number is used when it is not set. The environment name is also read from the process environment and wins over `env` here: `ENV`, `TEST_ENV`, `APP_ENV`, `TARGET_ENV` and friends, or any variable ending in `_ENV` / `_ENVIRONMENT` (`OPENCART_ENV`), so a config that says `local` still labels CI reports `dev`, `qa`, `stage` |
307
307
  | `envVar` | – | Name of the variable that holds the environment name, when the detection cannot guess it |
308
-
309
- **Runtime overrides.** `REPORTING_LABS_METADATA_<KEY>` sets a header chip from the environment (`REPORTING_LABS_METADATA_ENV=qa`, `REPORTING_LABS_METADATA_RELEASE=2.3`) and `REPORTING_LABS_TITLE`, `_THEME`, `_PALETTE`, `_ACCENT`, `_LOGO` the matching option; they win over the config. The env chip resolves in this order: `REPORTING_LABS_METADATA_ENV`, the variable `envVar` names, the conventional names (`ENV`, `TEST_ENV`, `APP_ENV`, `TARGET_ENV`, `CI_ENVIRONMENT_NAME`, anything ending in `_ENV`), then `metadata.env` in the config.
310
308
  | `env` | – | Extra rows on the Environment card |
311
309
  | `links` | `{}` | Turn meta keys into links. `{id}` is replaced by the value. An object `{ url, display }` builds the URL from several fields of an object passed to `meta()`, see below |
312
310
  | `maskKeys` | `[]` | Extra keys to mask as `****` |
@@ -330,11 +328,14 @@ Every option is optional. `npx reporting-labs init` writes them all, with commen
330
328
  | `embedVideos` | `false` | Videos inside the HTML too (bigger file, no folder issues) |
331
329
  | `emitJson` | `true` | Also write `report.json` alongside `index.html` (used by `merge`) |
332
330
  | `jsonFile` | `'report.json'` | File name of the JSON blob |
331
+ | `pdf` | `true` | Also write `report.pdf` (light theme, print-ready), printed by Playwright's Chromium or an installed Chrome / Edge, whatever browser the tests ran on. `false` turns it off; `{ file: 'run.pdf' }` renames it; `{ chromePath }` (or `CHROME_PATH`) names the browser. The Export PDF button in the report works either way |
333
332
  | `embedFonts` | `true` | Bundle the fonts (~140 KB) so it looks the same offline |
334
333
  | `announce` | `true` | Print the report path after the run |
335
334
  | `open` | `'on-failure'` | Open the report in the browser after the run: `'on-failure'`, `'always'` or `'never'`. Never opens in CI |
336
335
  | `warnMissingMeta` | `true` | After the run, list the tests that have no `meta()` in the console, so nobody on the team forgets |
337
336
 
337
+ **Runtime overrides.** `REPORTING_LABS_METADATA_<KEY>` sets a header chip from the environment (`REPORTING_LABS_METADATA_ENV=qa`, `REPORTING_LABS_METADATA_RELEASE=2.3`) and `REPORTING_LABS_TITLE`, `_THEME`, `_PALETTE`, `_ACCENT`, `_LOGO` the matching option; they win over the config. The env chip resolves in this order: `REPORTING_LABS_METADATA_ENV`, the variable `envVar` names, the conventional names (`ENV`, `TEST_ENV`, `APP_ENV`, `TARGET_ENV`, `CI_ENVIRONMENT_NAME`, anything ending in `_ENV`), then `metadata.env` in the config.
338
+
338
339
  If your reporter list differs between CI and local, add the same line to both:
339
340
 
340
341
  ```ts
@@ -606,7 +607,7 @@ Your Slack step can then read `results.json` for pass / fail counts and top fail
606
607
  - **Each run replaces the report.** The old report stays until the new run finishes. Archive the folder if you want to keep an old one.
607
608
  - **Videos on macOS.** If the report is in Downloads, Desktop or Documents and you open it as a file, Chrome may not be allowed to read the `assets/` folder (the player shows a clear message). Allow Chrome under System Settings → Privacy & Security → Files and Folders, move the project elsewhere, or set `embedVideos: true`.
608
609
  - **Keyboard.** `j` / `k` next and previous test, `f` failed only, `/` search, `1`–`5` switch views, `Esc` close.
609
- - **Print.** A print stylesheet is included, so "Save as PDF" works.
610
+ - **PDF.** `report.pdf` is written next to the report, and the Export PDF button in the header saves the same print-ready copy from the browser.
610
611
  - **Themes.** Light and dark follow the OS. The toggle in the header remembers your choice.
611
612
 
612
613
  ## Java teams
@@ -617,12 +618,42 @@ The same report, from Java: `dev.reportinglabs` on Maven Central, for TestNG and
617
618
 
618
619
  The same report, from Python: `pip install reporting-labs` ([PyPI](https://pypi.org/project/reporting-labs/)). A pytest plugin that turns on the moment it is installed, with zero-code support for Playwright and Selenium, and a Robot Framework listener (one row per test, keywords as steps). Source: [reporting-labs-python](https://github.com/naveenautomationlabs/reporting-labs-python). Guides: [reportinglabs.dev/get-started/python](https://reportinglabs.dev/get-started/python).
619
620
 
621
+ ## WebdriverIO
622
+
623
+ The same report, from a WebdriverIO suite (Mocha, Jasmine or Cucumber): one row per test, WebDriver commands as steps, a screenshot on failure, failure clusters and the PDF export. WebdriverIO runs a reporter per spec, so reportingLabs writes a part per runner and stitches them together in `onComplete`:
624
+
625
+ ```ts
626
+ // wdio.conf.ts
627
+ import ReportingLabsReporter, { reportingLabsComplete } from 'reporting-labs/wdio';
628
+
629
+ export const config = {
630
+ reporters: ['spec', [ReportingLabsReporter, { outputFolder: 'reporting-labs' }]],
631
+ async onComplete() {
632
+ await reportingLabsComplete({ outputFolder: 'reporting-labs', title: 'Web E2E' });
633
+ },
634
+ };
635
+ ```
636
+
637
+ Guide: [reportinglabs.dev/get-started/webdriverio](https://reportinglabs.dev/get-started/webdriverio).
638
+
620
639
  ## Roadmap
621
640
 
622
- - WebdriverIO, Cypress, Jest/Vitest and JUnit XML adapters
641
+ - Cypress, Jest/Vitest and JUnit XML adapters
623
642
  - AI summary of failures (bring your own API key)
624
643
  - Hosted history dashboard across branches and projects
625
644
 
645
+ ## Security & privacy
646
+
647
+ reportingLabs is a library that runs inside your own test run. There is no reportingLabs server, account, API key, telemetry or licence check.
648
+
649
+ - **Nothing is sent anywhere.** The reporter makes no network requests of its own; its only traffic is the traffic your tests already make. An opened report makes no external requests either: fonts, scripts and the logo are embedded, so it works offline and behind a firewall. The only exceptions are opt-in (`embedFonts: false`, a logo given as an `https://` URL) or need a click (CI, commit and issue links).
650
+ - **Everything stays on your machine:** the report folder (`reporting-labs/` by default) holds `index.html`, `report.json`, `report.pdf` and `assets/`, plus the run history `reporting-labs.history.json` next to your project. Nothing is written anywhere else. Whoever can read your test artifacts can read the report; deleting them deletes the data.
651
+ - **Secrets are masked before anything is written:** passwords, tokens, cookies, auth headers, API keys, JWTs and card numbers in logs, API bodies and headers, test data, errors and step titles. With Playwright, a value passed to `fill()` shows in the step title unless it is a known secret, so read test passwords from environment variables (masked by default) or list them in `maskValues`. The WebdriverIO reporter masks values typed into password fields.
652
+ - **Screenshots, videos and traces are not masked.** They are images and recordings of the application, so run tests against test data, or turn them off for suites that show real personal data.
653
+ - **No runtime dependencies;** `@playwright/test` is an optional peer, the one your project already has. No install or post-install scripts. MIT licensed.
654
+
655
+ Full details for security reviewers and client projects, including what is read, what is written and what to tell a client: [reportinglabs.dev/security-privacy](https://reportinglabs.dev/security-privacy). To report a vulnerability, open an issue saying you have a security report (no details) and a private channel will be arranged.
656
+
626
657
  ## License
627
658
 
628
659
  MIT © Naveen Automation Labs
package/dist/explain.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Turns a raw Playwright error message into a short, plain-language explanation.
3
3
  * Rule based, no network, no AI. The original message is always kept next to it.
4
4
  */
5
- export type ErrorKind = 'not-found' | 'ambiguous' | 'not-visible' | 'blocked' | 'disabled' | 'detached' | 'wrong-element' | 'assertion' | 'visual' | 'navigation' | 'network' | 'api' | 'test-timeout' | 'hook-timeout' | 'closed' | 'script' | 'file' | 'thrown';
5
+ export type ErrorKind = 'not-found' | 'ambiguous' | 'not-visible' | 'blocked' | 'disabled' | 'detached' | 'wrong-element' | 'assertion' | 'visual' | 'navigation' | 'network' | 'api' | 'test-timeout' | 'hook-timeout' | 'closed' | 'crashed' | 'script' | 'file' | 'thrown';
6
6
  export interface ErrorExplain {
7
7
  kind: ErrorKind;
8
8
  /** Short label for a badge, e.g. "Element not found". */
package/dist/explain.js CHANGED
@@ -21,6 +21,7 @@ const LABELS = {
21
21
  'test-timeout': 'Test timed out',
22
22
  'hook-timeout': 'Hook timed out',
23
23
  'closed': 'Browser closed early',
24
+ 'crashed': 'Browser crashed',
24
25
  'script': 'Error in test code',
25
26
  'file': 'File not found',
26
27
  'thrown': 'Test threw an error',
@@ -51,6 +52,9 @@ function explainError(raw) {
51
52
  const where = action ? ` It was stuck in ${action}` + (locator ? ` on ${locator}` : '') + '.' : '';
52
53
  return out('test-timeout', `The whole test took longer than ${secs(ms)}.${where}`, 'Find the slow step in the Steps list below. Raise timeout in playwright.config.ts only if the flow is really that long.', { timeoutMs: ms, action, locator });
53
54
  }
55
+ // ── The browser process died (memory, too many workers): infrastructure, not the app or the test ──
56
+ if (/Target crashed|Page crashed|page has crashed|Renderer process crashed|tab crashed|session deleted because of page crash|chrome not reachable|Browsing context has been discarded/i.test(msg))
57
+ return out('crashed', 'The browser crashed while the test was running.', 'Not a bug in the app or the test: the browser process died, usually from memory pressure or too many parallel workers. Re-run it; if it keeps happening, lower workers or give the machine more memory.', { action });
54
58
  // ── Strict mode ──────────────────────────────────────────────────────────
55
59
  m = msg.match(/strict mode violation: (.+?) resolved to (\d+) elements/s);
56
60
  if (m)
package/dist/pdf.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export interface PdfResult {
2
+ ok: boolean;
3
+ reason?: string;
4
+ }
5
+ /** Render the report's print layout to a PDF. Best-effort, never throws: a failure here must not fail
6
+ * the run — the HTML report and its Export PDF button remain.
7
+ * 1. the Chromium that Playwright ships, when `@playwright/test` and its browser are installed;
8
+ * 2. an installed Chrome, Edge or Chromium, headless with --print-to-pdf (the report builds its print
9
+ * layout itself on `?rl-print`). Edge ships with Windows, so this covers suites that run on Firefox
10
+ * or WebKit, and WebdriverIO projects that have no Playwright at all.
11
+ * `chromePath` (or the CHROME_PATH variable) names the browser explicitly and skips the search. */
12
+ export declare function writeReportPdf(htmlFile: string, pdfFile: string, chromePath?: string): Promise<PdfResult>;
13
+ /** Any Chromium-based browser can print the report: Chrome, Edge or Chromium. */
14
+ export declare function findChrome(): string | undefined;
package/dist/pdf.js ADDED
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.writeReportPdf = writeReportPdf;
37
+ exports.findChrome = findChrome;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const child_process_1 = require("child_process");
41
+ const url_1 = require("url");
42
+ /** Render the report's print layout to a PDF. Best-effort, never throws: a failure here must not fail
43
+ * the run — the HTML report and its Export PDF button remain.
44
+ * 1. the Chromium that Playwright ships, when `@playwright/test` and its browser are installed;
45
+ * 2. an installed Chrome, Edge or Chromium, headless with --print-to-pdf (the report builds its print
46
+ * layout itself on `?rl-print`). Edge ships with Windows, so this covers suites that run on Firefox
47
+ * or WebKit, and WebdriverIO projects that have no Playwright at all.
48
+ * `chromePath` (or the CHROME_PATH variable) names the browser explicitly and skips the search. */
49
+ async function writeReportPdf(htmlFile, pdfFile, chromePath) {
50
+ try {
51
+ fs.unlinkSync(pdfFile);
52
+ }
53
+ catch { /* none yet */ } // never leave a previous run's PDF next to this report
54
+ const explicit = chromePath || process.env.CHROME_PATH;
55
+ let firstError = '';
56
+ if (!explicit) {
57
+ try {
58
+ const { chromium } = await import('@playwright/test');
59
+ const browser = await chromium.launch();
60
+ try {
61
+ const page = await browser.newPage();
62
+ await page.goto((0, url_1.pathToFileURL)(htmlFile).href, { waitUntil: 'load' });
63
+ await page.evaluate(() => window.reportingLabsPreparePrint?.());
64
+ await page.pdf({ path: pdfFile, printBackground: true, preferCSSPageSize: true });
65
+ }
66
+ finally {
67
+ await browser.close();
68
+ }
69
+ if (written(pdfFile))
70
+ return { ok: true };
71
+ }
72
+ catch (e) {
73
+ firstError = String(e.message).split('\n')[0];
74
+ }
75
+ }
76
+ const chrome = explicit && isFile(explicit) ? explicit : findChrome();
77
+ if (!chrome) {
78
+ return { ok: false, reason: explicit ? `browser not found at ${explicit}` : 'no Chrome, Edge or Chromium found (set pdf: { chromePath }, or pdf: false to silence)' };
79
+ }
80
+ try {
81
+ (0, child_process_1.spawnSync)(chrome, ['--headless', '--no-sandbox', '--disable-gpu', '--no-pdf-header-footer',
82
+ '--virtual-time-budget=8000', '--run-all-compositor-stages-before-draw',
83
+ '--print-to-pdf=' + pdfFile, (0, url_1.pathToFileURL)(htmlFile).href + '?rl-print=1'], { stdio: 'ignore', timeout: 120000 });
84
+ }
85
+ catch (e) {
86
+ return { ok: false, reason: String(e.message).split('\n')[0] };
87
+ }
88
+ return written(pdfFile) ? { ok: true } : { ok: false, reason: firstError || `${path.basename(chrome)} did not write the PDF` };
89
+ }
90
+ function written(f) {
91
+ try {
92
+ return fs.statSync(f).size > 0;
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
98
+ function isFile(f) {
99
+ try {
100
+ return fs.statSync(f).isFile();
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ }
106
+ function which(name) {
107
+ const exts = process.platform === 'win32' ? ['.exe', ''] : [''];
108
+ for (const dir of (process.env.PATH || '').split(path.delimiter)) {
109
+ if (!dir)
110
+ continue;
111
+ for (const ext of exts) {
112
+ const p = path.join(dir, name + ext);
113
+ try {
114
+ fs.accessSync(p, fs.constants.X_OK);
115
+ if (isFile(p))
116
+ return p;
117
+ }
118
+ catch { /* next */ }
119
+ }
120
+ }
121
+ return undefined;
122
+ }
123
+ /** Any Chromium-based browser can print the report: Chrome, Edge or Chromium. */
124
+ function findChrome() {
125
+ const candidates = [];
126
+ if (process.platform === 'darwin') {
127
+ candidates.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chromium.app/Contents/MacOS/Chromium', '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge');
128
+ }
129
+ else if (process.platform === 'win32') {
130
+ for (const base of [process.env.ProgramFiles, process.env['ProgramFiles(x86)'], process.env.LOCALAPPDATA]) {
131
+ if (!base)
132
+ continue;
133
+ candidates.push(path.join(base, 'Google', 'Chrome', 'Application', 'chrome.exe'), path.join(base, 'Microsoft', 'Edge', 'Application', 'msedge.exe'));
134
+ }
135
+ }
136
+ for (const c of candidates)
137
+ if (isFile(c))
138
+ return c;
139
+ for (const name of ['google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', 'chrome',
140
+ 'microsoft-edge', 'microsoft-edge-stable', 'msedge']) {
141
+ const p = which(name);
142
+ if (p)
143
+ return p;
144
+ }
145
+ for (const c of ['/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser', '/snap/bin/chromium'])
146
+ if (isFile(c))
147
+ return c;
148
+ return undefined;
149
+ }
@@ -20,6 +20,8 @@ export default class ReportingLabsReporter implements Reporter {
20
20
  private pushOutput;
21
21
  onBegin(config: FullConfig, suite: Suite): void;
22
22
  onEnd(result: FullResult): Promise<void>;
23
+ /** report.pdf next to the HTML; see writeReportPdf for which browser prints it. Never fails the run. */
24
+ private writePdf;
23
25
  /** One short list of tests that carry no meta() at all, so the whole team keeps the report useful. */
24
26
  private printMissingMeta;
25
27
  /** Open the report in the default browser, like Playwright's HTML reporter. Never in CI. */
@@ -61,3 +63,15 @@ export default class ReportingLabsReporter implements Reporter {
61
63
  * it differently; this finds it without being told. Undefined when nothing fits.
62
64
  */
63
65
  export declare function detectEnvName(env: NodeJS.ProcessEnv, envVar?: string): string | undefined;
66
+ export declare function ciRunLabel(env: NodeJS.ProcessEnv): string | undefined;
67
+ export declare function ciLink(env: NodeJS.ProcessEnv): {
68
+ name: string;
69
+ url?: string;
70
+ } | null;
71
+ export declare function gitInfo(cwd: string, env: NodeJS.ProcessEnv): {
72
+ sha?: string;
73
+ author?: string;
74
+ subject?: string;
75
+ branch?: string;
76
+ url?: string;
77
+ };
package/dist/reporter.js CHANGED
@@ -34,6 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.detectEnvName = detectEnvName;
37
+ exports.ciRunLabel = ciRunLabel;
38
+ exports.ciLink = ciLink;
39
+ exports.gitInfo = gitInfo;
37
40
  const fs = __importStar(require("fs"));
38
41
  const path = __importStar(require("path"));
39
42
  const os = __importStar(require("os"));
@@ -41,6 +44,7 @@ const child_process_1 = require("child_process");
41
44
  const template_1 = require("./template");
42
45
  const mask_1 = require("./mask");
43
46
  const explain_1 = require("./explain");
47
+ const pdf_1 = require("./pdf");
44
48
  const DEFAULT_EMBED_LIMIT = 2 * 1024 * 1024;
45
49
  /** Attach steps created by log() and the automatic API capture; the data shows in its own section, so hide the step. */
46
50
  function isInternalAttach(s) {
@@ -240,8 +244,19 @@ class ReportingLabsReporter {
240
244
  this.printMissingMeta(tests);
241
245
  console.log('');
242
246
  }
247
+ if (this.options.pdf !== false)
248
+ await this.writePdf(file);
243
249
  this.maybeOpen(file, result);
244
250
  }
251
+ /** report.pdf next to the HTML; see writeReportPdf for which browser prints it. Never fails the run. */
252
+ async writePdf(htmlFile) {
253
+ const pdfOpt = typeof this.options.pdf === 'object' ? this.options.pdf : {};
254
+ const pdfFile = path.join(this.outDir, pdfOpt.file || 'report.pdf');
255
+ const r = await (0, pdf_1.writeReportPdf)(htmlFile, pdfFile, pdfOpt.chromePath);
256
+ if (this.options.announce === false)
257
+ return;
258
+ console.log(r.ok ? ` reporting-labs: PDF written to ${path.relative(process.cwd(), pdfFile)}\n` : ` reporting-labs: PDF skipped — ${r.reason}\n`);
259
+ }
245
260
  /** One short list of tests that carry no meta() at all, so the whole team keeps the report useful. */
246
261
  printMissingMeta(tests) {
247
262
  if (this.options.warnMissingMeta === false)