@reportforge/playwright-pdf 0.12.0 → 0.13.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 +114 -106
- package/README.md +86 -83
- package/dist/cli/index.js +5 -2
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +56 -6
- package/dist/index.mjs +52 -2
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @reportforge/playwright-pdf
|
|
2
2
|
|
|
3
|
-
> Playwright PDF reporter
|
|
3
|
+
> Playwright PDF reporter: generate branded PDF reports from [Playwright Test](https://playwright.dev/) results. Three templates (minimal, detailed, executive) for developers, QA teams, and stakeholders. CI-ready, self-contained, with embedded screenshots and charts.
|
|
4
4
|
|
|
5
5
|
Drop it into any Playwright project and get a PDF report on every CI run. No changes to your tests.
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ Drop it into any Playwright project and get a PDF report on every CI run. No cha
|
|
|
21
21
|
|
|
22
22
|
## Report Gallery
|
|
23
23
|
|
|
24
|
-
Three templates, one reporter. Every PDF is fully offline
|
|
24
|
+
Three templates, one reporter. Every PDF is fully offline: fonts, charts, and screenshots are embedded.
|
|
25
25
|
|
|
26
26
|
<table>
|
|
27
27
|
<tr>
|
|
@@ -45,23 +45,23 @@ Three templates, one reporter. Every PDF is fully offline — fonts, charts, and
|
|
|
45
45
|
|
|
46
46
|
## Features
|
|
47
47
|
|
|
48
|
-
- **3 templates
|
|
49
|
-
- **Detailed reports
|
|
50
|
-
- **Embedded Chart.js
|
|
51
|
-
- **Self-contained PDFs
|
|
52
|
-
- **Custom branding
|
|
53
|
-
- **Dynamic filenames
|
|
54
|
-
- **Instant demo report
|
|
55
|
-
- **CI/CD snippets
|
|
56
|
-
- **Live runs
|
|
57
|
-
- **Shard merging
|
|
58
|
-
- **Notifications
|
|
59
|
-
- **Flakiness trend
|
|
60
|
-
- **Offline failure analysis
|
|
61
|
-
- `npx @reportforge/playwright-pdf export-feedback
|
|
62
|
-
- **Rich execution capture
|
|
63
|
-
- **Configurable sections
|
|
64
|
-
- **Hybrid licensing
|
|
48
|
+
- **3 templates**: `minimal` (developer), `detailed` (QA team), `executive` (stakeholders)
|
|
49
|
+
- **Detailed reports**: KPI dashboard, suite breakdown with inline failure detail (error, screenshot, root-cause chip) under each failed test, CI/CD environment
|
|
50
|
+
- **Embedded Chart.js**: pass-rate doughnut + per-suite bar chart, bundled inline (no CDN)
|
|
51
|
+
- **Self-contained PDFs**: screenshots, fonts, logos all embedded; share or archive with confidence
|
|
52
|
+
- **Custom branding**: logo, primary/accent colours, watermark, PDF password encryption
|
|
53
|
+
- **Dynamic filenames**: `{date}`, `{branch}`, `{status}` tokens in the output path
|
|
54
|
+
- **Instant demo report**: `npx @reportforge/playwright-pdf demo` generates a realistic sample PDF before subscribing, no license key required
|
|
55
|
+
- **CI/CD snippets**: GitHub Actions, GitLab CI, Bitbucket Pipelines, Jenkins, Azure DevOps
|
|
56
|
+
- **Live runs**: opt-in `live` streams per-test progress to an unguessable watch link printed at run start; all shards of one CI run converge on a single live dashboard while tests execute. The PDF is still produced at the end, unchanged
|
|
57
|
+
- **Shard merging**: combine N Playwright JSON shard reports into one PDF via `shardResults`; accepts glob patterns or explicit paths
|
|
58
|
+
- **Notifications**: post pass/fail summaries to Slack, Teams, Discord, or email after each run; configurable trigger (`always` / `failure` / `success`) per channel; email and Discord support optional PDF attachment via `attachPdf`
|
|
59
|
+
- **Flakiness trend**: top-N flakiest tests table with per-test dot sparkline across stored history runs in the `detailed` template; `flakinessTopN` option (default 5, `0` disables)
|
|
60
|
+
- **Offline failure analysis**: sorts each failure into one of 7 root-cause buckets and renders a root-cause chip under each failed test in the `detailed` PDF (dominant-cause one-liner in `executive`). No network, no AI service: a small embedded classifier that updates over your existing license refresh; pin it any time. Full details at [reportforge.org/docs/advanced/failure-analysis](https://reportforge.org/docs/advanced/failure-analysis).
|
|
61
|
+
- `npx @reportforge/playwright-pdf export-feedback`: exports locally-collected unrecognized failures (tokenized, local-only) for optional manual sharing.
|
|
62
|
+
- **Rich execution capture**: opt-in "Steps to Reproduce" outline (a nested, copy-pasteable Markdown list of your `test.step`s and assertions; add `apiSteps` for the full click/fill/check trail), Node console tail, and trace/video links under each failed test in the defect log. Read straight from the Playwright step tree: no fixtures, no test-code changes. Enable via `capture` (`steps` / `apiSteps` / `console` / `evidence`); off by default.
|
|
63
|
+
- **Configurable sections**: add or remove any report section per template via `sections`.
|
|
64
|
+
- **Hybrid licensing**: one short key unlocks it; reports keep rendering offline via a cached JWT between refreshes
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
@@ -87,16 +87,16 @@ export default defineConfig({
|
|
|
87
87
|
});
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
`defineReporterConfig` is an optional typed identity helper
|
|
90
|
+
`defineReporterConfig` is an optional typed identity helper: it gives you IntelliSense and type-checking on the options object without a separate `ReporterOptions` import. You can also pass a plain object literal if you prefer.
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
93
|
npx playwright test
|
|
94
94
|
# → reports/2026-05-01-main-failed.pdf
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
> **See it first.** Run `npx @reportforge/playwright-pdf demo` to generate a realistic sample PDF in under 60 seconds
|
|
97
|
+
> **See it first.** Run `npx @reportforge/playwright-pdf demo` to generate a realistic sample PDF in under 60 seconds, no license key required. Supports `--template=minimal|detailed|executive` and `--output=<path>`.
|
|
98
98
|
|
|
99
|
-
> **License required.** Start a **[7-day free trial at reportforge.org/pricing](https://reportforge.org/pricing)
|
|
99
|
+
> **License required.** Start a **[7-day free trial at reportforge.org/pricing](https://reportforge.org/pricing)**: card required, no charge until day 8. Paste the resulting `RFSU-…` key into `RF_LICENSE_KEY`. Without a valid license the reporter logs a warning and skips PDF generation; your Playwright tests still run normally.
|
|
100
100
|
|
|
101
101
|
---
|
|
102
102
|
|
|
@@ -132,7 +132,7 @@ sudo apt-get update -qq && sudo apt-get install -y google-chrome-stable
|
|
|
132
132
|
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
> **Note:** Do not use the `chromium-browser` apt package
|
|
135
|
+
> **Note:** Do not use the `chromium-browser` apt package: it installs as a snap on Ubuntu and Puppeteer cannot drive it.
|
|
136
136
|
|
|
137
137
|
**macOS**
|
|
138
138
|
|
|
@@ -173,37 +173,37 @@ Currency is auto-detected by region; toggle on the pricing page.
|
|
|
173
173
|
|
|
174
174
|
All options are the second element of the reporter tuple.
|
|
175
175
|
|
|
176
|
-
<!-- AUTOGEN:options-table START
|
|
176
|
+
<!-- AUTOGEN:options-table START: edit scripts/docs/options-meta.ts, then run npm run docs:gen -->
|
|
177
177
|
| Option | Type | Default | Description |
|
|
178
178
|
|---|---|---|---|
|
|
179
179
|
| `outputFile` | `string` | `'playwright-report/{date}-report.pdf'` | Output path. Supports tokens: `{date}`, `{datetime}`, `{branch}`, `{status}`, `{total}`, `{passed}`, `{failed}`, `{project}`. Directory is created if absent. |
|
|
180
|
-
| `template` | `'minimal' \| 'detailed' \| 'executive'` or array | `'minimal'` | Template(s) to generate. Pass an array to produce one PDF per template in one run
|
|
180
|
+
| `template` | `'minimal' \| 'detailed' \| 'executive'` or array | `'minimal'` | Template(s) to generate. Pass an array to produce one PDF per template in one run: the template name is appended to each output filename automatically (e.g. `report-detailed.pdf`). |
|
|
181
181
|
| `licenseKey` | `string` | env `RF_LICENSE_KEY` | Subscription key (format `RFSU-…`). Falls back to the `RF_LICENSE_KEY` environment variable. PDF generation is skipped when absent or invalid. |
|
|
182
|
-
| `logo` | `string` |
|
|
182
|
+
| `logo` | `string` | n/a | Path to a logo image (PNG, JPG, or SVG) to embed in the report header. Supports absolute and relative paths. |
|
|
183
183
|
| `primaryColor` | `string` | `'#CC785C'` | Primary brand colour (3-, 6-, or 8-digit hex). Used for headers and accent bars. |
|
|
184
184
|
| `accentColor` | `string` | `'#3F7D58'` | Accent brand colour (hex). Used for highlights and badges. |
|
|
185
|
-
| `watermark` | `string` |
|
|
186
|
-
| `pdfPassword` | `string` |
|
|
185
|
+
| `watermark` | `string` | n/a | Text to render as a diagonal watermark overlay on every page, e.g. `'CONFIDENTIAL'` or `'DRAFT'`. |
|
|
186
|
+
| `pdfPassword` | `string` | n/a | Password-protect the generated PDF. Requires `qpdf` installed on the system. |
|
|
187
187
|
| `reportTitle` | `string` | `'Playwright Test Report'` | Custom title for the report cover page and running header. |
|
|
188
188
|
| `projectName` | `string` | from `package.json` | Project or application name. Inferred from the `package.json` `name` field if absent. |
|
|
189
|
-
| `open` | `boolean` | `false` | Open the generated PDF automatically after generation. For local use only
|
|
189
|
+
| `open` | `boolean` | `false` | Open the generated PDF automatically after generation. For local use only; ignored in CI. |
|
|
190
190
|
| `puppeteerExecutablePath` | `string` | auto-detect | Full path to a Chrome or Chromium binary. Falls back to `PUPPETEER_EXECUTABLE_PATH` env, then system Chrome discovery. |
|
|
191
191
|
| `serverUrl` | `string` | `'https://reportforge.org'` | Base URL for the ReportForge licensing server. Override only for self-hosting or local development. |
|
|
192
192
|
| `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. |
|
|
193
|
-
| `includeScreenshots` | `boolean` | `true` | Embed Playwright screenshots in the PDF. Set to `false` to omit images
|
|
193
|
+
| `includeScreenshots` | `boolean` | `true` | Embed Playwright screenshots in the PDF. Set to `false` to omit images; useful for exec-audience reports or reducing file size. |
|
|
194
194
|
| `maxInlineFailures` | `number` | derived from `compressionLevel` | Cap on failure entries rendered inline in the PDF. Overflow is written to a sibling `{basename}-failures.json` sidecar file. |
|
|
195
195
|
| `maxFileSizeMb` | `number` | `8` | Soft cap on the final PDF size in MB. If exceeded, the report is re-rendered once with the `'max'` compression preset. |
|
|
196
|
-
| `shardResults` | `string \| string[]` |
|
|
197
|
-
| `notify` | `object` |
|
|
196
|
+
| `shardResults` | `string \| string[]` | n/a | Glob or path array of Playwright JSON shard report files to merge into one PDF. See the Shard Merging docs. |
|
|
197
|
+
| `notify` | `object` | n/a | Notification channels: `slack`, `teams` (each `{ url, enabled, on }`), `discord` (`{ url, enabled, on, attachPdf }`), `email` (`{ to, enabled, on, attachPdf }`). See the Notifications docs. |
|
|
198
198
|
| `failureAnalysis` | `object` | `{ enabled: true }` | Offline failure root-cause analysis (embedded Naive Bayes; no runtime network). Fields: `enabled` (default `true`), `maxClusters` (default `10`), `minStrength` (`weak`\|`moderate`\|`strong`, default `weak`), `maxFailuresToAnalyse` (default `500`), `collectUnclassified` (default `true`), `autoUpdateModel` (default `true`). See the Failure Analysis docs. |
|
|
199
|
-
| `capture` | `object` | `{}` (off) | Opt-in rich execution capture for the defect log (reporter-side; no fixtures, no test-code changes). Fields: `steps` (a copy-pasteable "Steps to Reproduce" outline built from the Playwright step tree
|
|
200
|
-
| `live` | `object` | `{}` (off) | Opt-in live test-execution streaming. When `enabled`, the reporter prints an unguessable watch link to the CI logs at run start and streams per-test progress to the hosted dashboard while tests run
|
|
199
|
+
| `capture` | `object` | `{}` (off) | Opt-in rich execution capture for the defect log (reporter-side; no fixtures, no test-code changes). Fields: `steps` (a copy-pasteable "Steps to Reproduce" outline built from the Playwright step tree; each row is a Markdown list line indented under its parent `test.step`, default `false`), `apiSteps` (include every top-level `pw:api` action (click/fill/check/goto) in the outline for a full action trail; without it the outline shows only `test.step` intent + `expect` assertions, default `false`), `console` (Node stdout/stderr tail, default `false`), `evidence` (trace/video file links, default `false`), `maxSteps` (default `50`), `maxConsoleLines` (default `50`). Renders in the Defect Log section of the `detailed` template. |
|
|
200
|
+
| `live` | `object` | `{}` (off) | Opt-in live test-execution streaming. When `enabled`, the reporter prints an unguessable watch link to the CI logs at run start and streams per-test progress to the hosted dashboard while tests run; all shards of one CI run converge on a single live view. Fields: `enabled` (default `false`), `runId` (override the auto-derived run id), `serverUrl` (override the streaming server), `steps` (`none`\|`failed`\|`intent`\|`all`, default `failed`; `intent` shows only your `test.step` names plus any failing step), `console` (stream stdout/stderr tails, default `false`), `flushMs` (batch debounce 500–10000, default `2000`). The PDF at the end of the run is unaffected. Requires an active subscription with the `live` entitlement. See the Live Runs docs. |
|
|
201
201
|
| `historyFile` | `string` | `~/.reportforge/{key}/history.json` | Path to the history JSON file. Relative paths resolve from `cwd`. Enables pass-rate trending charts in the `detailed` template. |
|
|
202
202
|
| `historySize` | `number` | `10` | Maximum number of test runs to keep in the history file (integer ≥ 2). Older runs are pruned on append. |
|
|
203
203
|
| `showTrend` | `boolean` | `true` | Show the pass-rate sparkline and delta badge in the `detailed` template. Set to `false` to disable history tracking entirely. |
|
|
204
204
|
| `flakinessTopN` | `number` | `5` | Maximum flaky tests to show in the flakiness table (`detailed` template). Set to `0` to disable the table entirely. |
|
|
205
|
-
| `slowTestThreshold` | `number` | `10` | Minimum timed-test count before `SLOW` badges appear in the suite breakdown
|
|
206
|
-
| `templatePath` | `string \| string[]` |
|
|
205
|
+
| `slowTestThreshold` | `number` | `10` | Minimum timed-test count before `SLOW` badges appear in the suite breakdown: below this, every test is trivially the "slowest" so badges stay hidden. On larger runs only genuine outliers (at least 2× the median test duration) are badged, so the badge stays rare. Set to `0` to drop the run-size gate (outliers still required). |
|
|
206
|
+
| `templatePath` | `string \| string[]` | n/a | Path to a custom Handlebars (`.hbs`) template file. Takes precedence over `template`. Pass an array to generate one PDF per custom template. See the Custom Templates docs. |
|
|
207
207
|
| `sections` | `object` | per-template | Override which report sections appear, on top of the chosen template's defaults. Flat keys are the baseline for every chosen template; per-template keys (`minimal`\|`detailed`\|`executive`) override per template. Block toggles: `coverPage`, `analysisOneliner`, `releaseGate`, `summary`, `charts`, `trend`, `requirementsMatrix`, `ciEnvironment`, `suiteBreakdown`, `failureDeepDive`, `failureAnalysis`, `slowTests`, `defectLog`. Display modifiers: `passRate`, `fullEnvironment`, `retries`, `fullFailures`, `stackTraces`. See the Report Sections docs. |
|
|
208
208
|
<!-- AUTOGEN:options-table END -->
|
|
209
209
|
|
|
@@ -211,9 +211,9 @@ Full reference: [reportforge.org/docs/configuration](https://reportforge.org/doc
|
|
|
211
211
|
|
|
212
212
|
### Live Runs
|
|
213
213
|
|
|
214
|
-
Watch a run as it happens. With `live` enabled, the reporter prints an unguessable watch link
|
|
214
|
+
Watch a run as it happens. With `live` enabled, the reporter prints an unguessable watch link (boxed under a `Live Tracker` heading) to your CI logs at run start and streams per-test progress to a hosted dashboard while tests execute. All shards of one CI run converge on a single live view. Each test shows its steps and assertions as sub-lines beneath its row: each step once, as it settles, indented under its parent `test.step` in source order (Playwright's hooks, fixtures, and teardown are filtered out). The PDF is still generated at the end, unchanged.
|
|
215
215
|
|
|
216
|
-
> The watch link is entitlement-gated. If you enable `live` but see no `Live Tracker` line, the reporter logs why (missing `RF_LICENSE_KEY`, or a cached token without the `live` entitlement
|
|
216
|
+
> The watch link is entitlement-gated. If you enable `live` but see no `Live Tracker` line, the reporter logs why (missing `RF_LICENSE_KEY`, or a cached token without the `live` entitlement; delete `~/.reportforge/license.json` to force re-activation).
|
|
217
217
|
|
|
218
218
|
```typescript
|
|
219
219
|
// playwright.config.ts
|
|
@@ -223,20 +223,20 @@ reporter: [
|
|
|
223
223
|
live: {
|
|
224
224
|
enabled: true,
|
|
225
225
|
steps: 'failed', // 'none' | 'failed' | 'intent' | 'all'
|
|
226
|
-
// console: false, // stream stdout/stderr tails (off by default
|
|
226
|
+
// console: false, // stream stdout/stderr tails (off by default; see note)
|
|
227
227
|
// flushMs: 2000, // batch debounce (500–10000)
|
|
228
228
|
},
|
|
229
229
|
}],
|
|
230
230
|
],
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
`steps: 'intent'` shows only your `test.step` names (plus any failing step)
|
|
233
|
+
`steps: 'intent'` shows only your `test.step` names (plus any failing step): the cleanest, most readable trail. `'all'` adds every action and assertion beneath its parent step; `'failed'` (default) keeps intent + assertions + failures.
|
|
234
234
|
|
|
235
|
-
Sharded runs need no extra config
|
|
235
|
+
Sharded runs need no extra config: the run id is derived from the run-shared CI identifier (e.g. `GITHUB_RUN_ID`), identical across every matrix shard. Set `RF_LIVE_RUN_ID` to override when your CI is not auto-detected.
|
|
236
236
|
|
|
237
237
|
Live streaming is best-effort and entitlement-gated: if the server is unreachable the run is unaffected, and an active subscription with the `live` feature is required. If you've configured Slack/Teams/Discord notifications, the watch link is also posted to those channels when the run starts.
|
|
238
238
|
|
|
239
|
-
> **Security:** the watch link exposes test titles + statuses (and step titles when `steps` is on). Leaving `console: false` is recommended
|
|
239
|
+
> **Security:** the watch link exposes test titles + statuses (and step titles when `steps` is on). Leaving `console: false` is recommended: enabling it streams your tests' stdout/stderr to anyone holding the link. Full details at [reportforge.org/docs/advanced/live](https://reportforge.org/docs/advanced/live).
|
|
240
240
|
|
|
241
241
|
### Shard Merging
|
|
242
242
|
|
|
@@ -245,7 +245,7 @@ Combine parallel Playwright shards into one PDF without re-running any tests.
|
|
|
245
245
|
**Workflow:**
|
|
246
246
|
|
|
247
247
|
1. Run each shard with `--reporter=json` to produce a JSON output file.
|
|
248
|
-
2. In a separate step, point `shardResults` at those files
|
|
248
|
+
2. In a separate step, point `shardResults` at those files; it accepts a glob or an explicit array.
|
|
249
249
|
|
|
250
250
|
```typescript
|
|
251
251
|
// playwright.config.ts (merge step)
|
|
@@ -258,7 +258,7 @@ reporter: [
|
|
|
258
258
|
],
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
The merge step can run against a dummy test file with zero tests
|
|
261
|
+
The merge step can run against a dummy test file with zero tests: `shardResults` takes precedence over live results.
|
|
262
262
|
|
|
263
263
|
> **Note:** Timed-out tests are counted as failures in shard mode (Playwright JSON stats do not distinguish `timedOut` from `failed`).
|
|
264
264
|
|
|
@@ -282,6 +282,7 @@ notify: {
|
|
|
282
282
|
url: process.env.DISCORD_WEBHOOK_URL,
|
|
283
283
|
enabled: true,
|
|
284
284
|
on: 'failure',
|
|
285
|
+
attachPdf: true, // upload the PDF with the message (default: false)
|
|
285
286
|
},
|
|
286
287
|
email: {
|
|
287
288
|
to: ['team@company.com', 'qa@company.com'],
|
|
@@ -292,7 +293,7 @@ notify: {
|
|
|
292
293
|
},
|
|
293
294
|
```
|
|
294
295
|
|
|
295
|
-
Each channel is independent. `enabled: false` (the default) lets you store a URL without activating it
|
|
296
|
+
Each channel is independent. `enabled: false` (the default) lets you store a URL without activating it; useful for staging configs.
|
|
296
297
|
|
|
297
298
|
The `on` trigger controls when the message fires:
|
|
298
299
|
|
|
@@ -302,17 +303,19 @@ The `on` trigger controls when the message fires:
|
|
|
302
303
|
| `'failure'` | `stats.failed > 0` or run timed out / interrupted |
|
|
303
304
|
| `'success'` | All tests passed |
|
|
304
305
|
|
|
305
|
-
**Email** requires `RESEND_API_KEY` in the environment (get one at [resend.com](https://resend.com)). Sender defaults to `noreply@reportforge.org`; override with `RESEND_FROM`. Store webhook URLs and API keys as CI secrets
|
|
306
|
+
**Email** requires `RESEND_API_KEY` in the environment (get one at [resend.com](https://resend.com)). Sender defaults to `noreply@reportforge.org`; override with `RESEND_FROM`. Store webhook URLs and API keys as CI secrets; never commit them.
|
|
306
307
|
|
|
307
|
-
|
|
308
|
+
**PDF attachment** (`attachPdf: true`) is available on `email` and `discord`: Slack and Teams webhooks cannot carry file uploads. On Discord the PDF is uploaded with the message; if the upload fails or the PDF exceeds Discord's file-size cap, the summary still posts without the attachment. With multiple templates, the first PDF is attached.
|
|
309
|
+
|
|
310
|
+
The summary includes pass rate, test counts, duration, and the report filename. Notifications require a valid license and fire after PDF generation (or after a PDF failure; you still get the ping).
|
|
308
311
|
|
|
309
312
|
### Test History Trending
|
|
310
313
|
|
|
311
314
|
After each run the reporter appends a summary entry to a local JSON file and renders a pass-rate sparkline + verdict row in the `detailed` template.
|
|
312
315
|
|
|
313
|
-
**Local dev (zero config)
|
|
316
|
+
**Local dev (zero config)**: history is written automatically to `~/.reportforge/{projectKey}/history.json`. The sparkline appears once two or more runs have been recorded.
|
|
314
317
|
|
|
315
|
-
**CI with ephemeral runners
|
|
318
|
+
**CI with ephemeral runners**: set `historyFile` to a project-relative path and cache it between runs:
|
|
316
319
|
|
|
317
320
|
```typescript
|
|
318
321
|
// playwright.config.ts
|
|
@@ -328,10 +331,10 @@ reporter: [['@reportforge/playwright-pdf', {
|
|
|
328
331
|
with:
|
|
329
332
|
path: .reportforge/history.json
|
|
330
333
|
key: reportforge-history-${{ github.ref }}
|
|
331
|
-
# Omit restore-keys
|
|
334
|
+
# Omit restore-keys; cross-branch fallback causes misleading sparklines on PRs
|
|
332
335
|
```
|
|
333
336
|
|
|
334
|
-
**Monorepo
|
|
337
|
+
**Monorepo**: each package needs a distinct `historyFile` (e.g. `.reportforge/api-history.json`, `.reportforge/web-history.json`) so runs do not overwrite each other.
|
|
335
338
|
|
|
336
339
|
### Flakiness Trend Table
|
|
337
340
|
|
|
@@ -340,11 +343,11 @@ The `detailed` template includes a **Top flaky tests** table showing which tests
|
|
|
340
343
|
```typescript
|
|
341
344
|
reporter: [['@reportforge/playwright-pdf', {
|
|
342
345
|
template: 'detailed',
|
|
343
|
-
flakinessTopN: 5, // default
|
|
346
|
+
flakinessTopN: 5, // default: show top 5; set 0 to disable
|
|
344
347
|
}]]
|
|
345
348
|
```
|
|
346
349
|
|
|
347
|
-
The table is gated behind `showTrend: true` (the default) and appears automatically once history entries are present. Runs written before the flakiness feature was added are excluded from the denominator
|
|
350
|
+
The table is gated behind `showTrend: true` (the default) and appears automatically once history entries are present. Runs written before the flakiness feature was added are excluded from the denominator; the table fills in correctly as newer runs accumulate. Set `flakinessTopN: 0` to hide the table entirely.
|
|
348
351
|
|
|
349
352
|
### Filename tokens
|
|
350
353
|
|
|
@@ -358,21 +361,21 @@ The table is gated behind `showTrend: true` (the default) and appears automatica
|
|
|
358
361
|
|
|
359
362
|
## Templates
|
|
360
363
|
|
|
361
|
-
### `minimal
|
|
364
|
+
### `minimal`: Developer-focused
|
|
362
365
|
|
|
363
366
|
Clean layout, KPI numbers, hierarchical test table, failure details with stack traces and embedded screenshots. Fast to generate. Good for local development and PR checks.
|
|
364
367
|
|
|
365
|
-
### `detailed
|
|
368
|
+
### `detailed`: QA Team
|
|
366
369
|
|
|
367
370
|
Everything in `minimal`, **plus** Chart.js pass-rate + suite-results charts, a numbered defect log, a requirements traceability matrix derived from `@TAG` annotations, and the full CI/CD environment table.
|
|
368
371
|
|
|
369
|
-
### `executive
|
|
372
|
+
### `executive`: Stakeholder presentations
|
|
370
373
|
|
|
371
|
-
Full-page cover with verdict + KPI strip, followed by a compact dashboard with charts and a failures summary (titles only
|
|
374
|
+
Full-page cover with verdict + KPI strip, followed by a compact dashboard with charts and a failures summary (titles only, **no raw stack traces**). Best for sprint reports and management dashboards.
|
|
372
375
|
|
|
373
376
|
### Shared across all three
|
|
374
377
|
|
|
375
|
-
Every report leads with a **release-gate** ship/hold banner derived from the run verdict, surfaces **timed-out** tests as their own KPI card and pass-rate chart slice, and lists failures **most-severe first** (with severity-coloured card borders in `minimal` and `detailed`). Pages **pack compactly
|
|
378
|
+
Every report leads with a **release-gate** ship/hold banner derived from the run verdict, surfaces **timed-out** tests as their own KPI card and pass-rate chart slice, and lists failures **most-severe first** (with severity-coloured card borders in `minimal` and `detailed`). Pages **pack compactly**: large sections flow and break between rows instead of each starting on a fresh page. The Suite Results chart breaks a single-file run down by **describe block** so it never collapses to one bar. Requirements coverage bars are coloured by threshold (red <50%, amber <80%, green ≥80%).
|
|
376
379
|
|
|
377
380
|
### Generating multiple templates in one run
|
|
378
381
|
|
|
@@ -394,11 +397,11 @@ reporter: [
|
|
|
394
397
|
# → reports/2026-04-28-report-executive.pdf
|
|
395
398
|
```
|
|
396
399
|
|
|
397
|
-
One license check, one data-collection pass
|
|
400
|
+
One license check, one data-collection pass: faster than multiple reporter instances. Duplicate entries are silently deduplicated.
|
|
398
401
|
|
|
399
402
|
### Report Sections
|
|
400
403
|
|
|
401
|
-
Each built-in template ships a curated set of sections. The `sections` option lets you **add a section a template hides or remove one it shows
|
|
404
|
+
Each built-in template ships a curated set of sections. The `sections` option lets you **add a section a template hides or remove one it shows, per template**, without writing a custom template.
|
|
402
405
|
|
|
403
406
|
```ts
|
|
404
407
|
reporter: [['@reportforge/playwright-pdf', {
|
|
@@ -413,7 +416,7 @@ reporter: [['@reportforge/playwright-pdf', {
|
|
|
413
416
|
|
|
414
417
|
**Resolution order** (lowest → highest): the template's defaults → flat keys (baseline for all chosen templates) → per-template keys (`minimal` / `detailed` / `executive`). An unknown key throws a configuration error (typo-safe).
|
|
415
418
|
|
|
416
|
-
#### Block toggles
|
|
419
|
+
#### Block toggles: which sections appear
|
|
417
420
|
|
|
418
421
|
| Key | Renders |
|
|
419
422
|
|---|---|
|
|
@@ -431,7 +434,7 @@ reporter: [['@reportforge/playwright-pdf', {
|
|
|
431
434
|
| `slowTests` | `SLOW` badge on the duration-ranked slowest tests, shown inline in the breakdown |
|
|
432
435
|
| `defectLog` | `DEF-####` numbered failure table (severity, duration) + opt-in repro detail (`capture`) |
|
|
433
436
|
|
|
434
|
-
#### Display modifiers
|
|
437
|
+
#### Display modifiers: tune a section that's on
|
|
435
438
|
|
|
436
439
|
| Key | Effect |
|
|
437
440
|
|---|---|
|
|
@@ -468,11 +471,11 @@ Omit `sections` entirely and each template renders exactly this (✓ = on):
|
|
|
468
471
|
|
|
469
472
|
#### Dependencies & gotchas
|
|
470
473
|
|
|
471
|
-
- **`trend` needs `charts`.** The trend line, run-history, and flakiness table live inside the charts block, so `trend: true` does nothing with `charts: false` (it's coerced off). It also needs history data
|
|
472
|
-
- **Inline failure detail needs `suiteBreakdown`.** `failureDeepDive` (failure detail), `failureAnalysis` (root-cause chip), and `slowTests` (`SLOW` badge) now render *inside* the suite breakdown, so they show nothing when `suiteBreakdown` is off. The chip (`failureAnalysis`) also needs `failureDeepDive
|
|
473
|
-
- **Data-gated sections
|
|
474
|
-
- **Render-layer only.** `sections` controls what's drawn, never what's collected
|
|
475
|
-
- **Custom templates.** `sections` applies to the built-in templates; a custom `templatePath` controls its own layout (every section available) and ignores `sections
|
|
474
|
+
- **`trend` needs `charts`.** The trend line, run-history, and flakiness table live inside the charts block, so `trend: true` does nothing with `charts: false` (it's coerced off). It also needs history data; keep the top-level `showTrend` option on.
|
|
475
|
+
- **Inline failure detail needs `suiteBreakdown`.** `failureDeepDive` (failure detail), `failureAnalysis` (root-cause chip), and `slowTests` (`SLOW` badge) now render *inside* the suite breakdown, so they show nothing when `suiteBreakdown` is off. The chip (`failureAnalysis`) also needs `failureDeepDive`: it lives inside the failure detail, so it's coerced off without it.
|
|
476
|
+
- **Data-gated sections**: `failureAnalysis`, `requirementsMatrix`, `slowTests`, `defectLog` render only when there's matching data (classified failures, tagged tests, a meaningful slow set, failures). Toggling them on with no data shows nothing.
|
|
477
|
+
- **Render-layer only.** `sections` controls what's drawn, never what's collected; the data switches stay separate: `showTrend` (history), `flakinessTopN` (flaky-table size), `failureAnalysis.enabled` (classifier), `includeScreenshots` (images).
|
|
478
|
+
- **Custom templates.** `sections` applies to the built-in templates; a custom `templatePath` controls its own layout (every section available) and ignores `sections`. You'll get a warning if both are set.
|
|
476
479
|
|
|
477
480
|
---
|
|
478
481
|
|
|
@@ -497,7 +500,7 @@ Full workflows for all four CI providers: [reportforge.org/docs/ci-cd](https://r
|
|
|
497
500
|
path: reports/*.pdf
|
|
498
501
|
```
|
|
499
502
|
|
|
500
|
-
### GitHub Actions
|
|
503
|
+
### GitHub Actions: Sharded runs
|
|
501
504
|
|
|
502
505
|
Run shards in parallel with `--reporter=json`, then merge all JSON files into one PDF:
|
|
503
506
|
|
|
@@ -540,7 +543,7 @@ jobs:
|
|
|
540
543
|
with: { name: playwright-pdf-report, path: reports/*.pdf }
|
|
541
544
|
```
|
|
542
545
|
|
|
543
|
-
In `playwright.config.ts` set `shardResults: process.env.SHARD_RESULTS_GLOB`. `tests/dummy.spec.ts` can be a single skipped test
|
|
546
|
+
In `playwright.config.ts` set `shardResults: process.env.SHARD_RESULTS_GLOB`. `tests/dummy.spec.ts` can be a single skipped test: live results are ignored when `shardResults` is set.
|
|
544
547
|
|
|
545
548
|
### GitLab CI
|
|
546
549
|
|
|
@@ -603,11 +606,11 @@ ReportForge ships a **hybrid offline-first** model:
|
|
|
603
606
|
|
|
604
607
|
1. Paste your `RFSU-…` key into the config once (or set `RF_LICENSE_KEY`).
|
|
605
608
|
2. On first run the reporter activates against the license server, receives a short-lived **Ed25519-signed JWT**, and caches it at `~/.reportforge/license.json`.
|
|
606
|
-
3. Every subsequent run is **fully offline
|
|
609
|
+
3. Every subsequent run is **fully offline**: the cached JWT is verified locally against a public key bundled into the npm package. No network call.
|
|
607
610
|
4. When the cache has less than 24 hours left, the reporter refreshes it in the background.
|
|
608
611
|
5. Each subscription allows up to **25 active machines** in any rolling 30-day window. Machines that haven't run in 30 days are pruned automatically.
|
|
609
612
|
|
|
610
|
-
Cancel the subscription and the reporter keeps working until the end of the current billing period, then stops generating PDFs until you restart. **A license issue never aborts your test run
|
|
613
|
+
Cancel the subscription and the reporter keeps working until the end of the current billing period, then stops generating PDFs until you restart. **A license issue never aborts your test run**: the reporter logs a warning and skips PDF generation; your Playwright tests still pass.
|
|
611
614
|
|
|
612
615
|
```bash
|
|
613
616
|
export RF_LICENSE_KEY=RFSU-XXXX-XXXX-XXXX-XXXX
|
|
@@ -654,17 +657,17 @@ Full guide: [reportforge.org/docs/troubleshooting](https://reportforge.org/docs/
|
|
|
654
657
|
|
|
655
658
|
Full documentation at [reportforge.org/docs](https://reportforge.org/docs):
|
|
656
659
|
|
|
657
|
-
- [Quick start](https://reportforge.org/docs/quickstart)
|
|
658
|
-
- [Chrome setup](https://reportforge.org/docs/running-tests#chrome-setup)
|
|
659
|
-
- [CI/CD integration](https://reportforge.org/docs/ci-cd)
|
|
660
|
-
- [Configuration reference](https://reportforge.org/docs/configuration)
|
|
661
|
-
- [Filename tokens](https://reportforge.org/docs/configuration#tokens)
|
|
662
|
-
- [Templates](https://reportforge.org/docs/templates)
|
|
663
|
-
- [Live runs](https://reportforge.org/docs/advanced/live)
|
|
664
|
-
- [Report Sections](https://reportforge.org/docs/configuration#report-sections)
|
|
665
|
-
- [License & offline behaviour](https://reportforge.org/docs/licensing)
|
|
666
|
-
- [Troubleshooting](https://reportforge.org/docs/troubleshooting)
|
|
667
|
-
- **Changelog
|
|
660
|
+
- [Quick start](https://reportforge.org/docs/quickstart): install, configure, activate
|
|
661
|
+
- [Chrome setup](https://reportforge.org/docs/running-tests#chrome-setup): Windows, Linux, macOS
|
|
662
|
+
- [CI/CD integration](https://reportforge.org/docs/ci-cd): GitHub Actions, GitLab CI, Bitbucket Pipelines, Jenkins, Azure DevOps
|
|
663
|
+
- [Configuration reference](https://reportforge.org/docs/configuration): all options
|
|
664
|
+
- [Filename tokens](https://reportforge.org/docs/configuration#tokens): `{date}`, `{branch}`, `{status}`
|
|
665
|
+
- [Templates](https://reportforge.org/docs/templates): minimal, detailed, executive
|
|
666
|
+
- [Live runs](https://reportforge.org/docs/advanced/live): stream per-test progress to a shared watch link
|
|
667
|
+
- [Report Sections](https://reportforge.org/docs/configuration#report-sections): add or remove sections per template
|
|
668
|
+
- [License & offline behaviour](https://reportforge.org/docs/licensing): JWT cache, machine cap, cancellation
|
|
669
|
+
- [Troubleshooting](https://reportforge.org/docs/troubleshooting): common issues and debug flags
|
|
670
|
+
- **Changelog**: [CHANGELOG.md](./CHANGELOG.md)
|
|
668
671
|
|
|
669
672
|
---
|
|
670
673
|
|
|
@@ -674,4 +677,4 @@ Questions and bug reports: email [support@reportforge.org](mailto:support@report
|
|
|
674
677
|
|
|
675
678
|
## License
|
|
676
679
|
|
|
677
|
-
MIT
|
|
680
|
+
MIT. See [LICENSE](./LICENSE).
|
package/dist/cli/index.js
CHANGED
|
@@ -11921,7 +11921,7 @@ ${issues}`);
|
|
|
11921
11921
|
}
|
|
11922
11922
|
return result.data;
|
|
11923
11923
|
}
|
|
11924
|
-
var hexColor, channelConfigSchema, emailChannelConfigSchema, notifyConfigSchema, failureAnalysisConfigSchema, captureConfigSchema, liveConfigSchema, sectionFlagsShape, sectionFlagsSchema, sectionsSchema, ReporterOptionsSchema;
|
|
11924
|
+
var hexColor, channelConfigSchema, discordChannelConfigSchema, emailChannelConfigSchema, notifyConfigSchema, failureAnalysisConfigSchema, captureConfigSchema, liveConfigSchema, sectionFlagsShape, sectionFlagsSchema, sectionsSchema, ReporterOptionsSchema;
|
|
11925
11925
|
var init_schema = __esm({
|
|
11926
11926
|
"src/config/schema.ts"() {
|
|
11927
11927
|
"use strict";
|
|
@@ -11938,6 +11938,9 @@ var init_schema = __esm({
|
|
|
11938
11938
|
on: external_exports.enum(["always", "failure", "success"]).default("always"),
|
|
11939
11939
|
enabled: external_exports.boolean().default(false)
|
|
11940
11940
|
});
|
|
11941
|
+
discordChannelConfigSchema = channelConfigSchema.extend({
|
|
11942
|
+
attachPdf: external_exports.boolean().default(false)
|
|
11943
|
+
});
|
|
11941
11944
|
emailChannelConfigSchema = external_exports.object({
|
|
11942
11945
|
to: external_exports.array(external_exports.string().email('Each "to" entry must be a valid email')).min(1, '"to" must have at least one address'),
|
|
11943
11946
|
on: external_exports.enum(["always", "failure", "success"]).default("always"),
|
|
@@ -11947,7 +11950,7 @@ var init_schema = __esm({
|
|
|
11947
11950
|
notifyConfigSchema = external_exports.object({
|
|
11948
11951
|
slack: channelConfigSchema.optional(),
|
|
11949
11952
|
teams: channelConfigSchema.optional(),
|
|
11950
|
-
discord:
|
|
11953
|
+
discord: discordChannelConfigSchema.optional(),
|
|
11951
11954
|
email: emailChannelConfigSchema.optional()
|
|
11952
11955
|
}).optional();
|
|
11953
11956
|
failureAnalysisConfigSchema = external_exports.object({
|
package/dist/index.d.mts
CHANGED
|
@@ -158,7 +158,12 @@ interface ReporterOptions {
|
|
|
158
158
|
* `on` controls the trigger: `'always'` (default), `'failure'`, or `'success'`.
|
|
159
159
|
*
|
|
160
160
|
* - `slack` / `teams` / `discord`: webhook URL channels
|
|
161
|
+
* - `discord`: `attachPdf: true` uploads the PDF with the message (falls back
|
|
162
|
+
* to the summary alone if the upload fails or the PDF exceeds Discord's cap)
|
|
161
163
|
* - `email`: sends via Resend; `to` is required; `attachPdf: true` attaches the PDF
|
|
164
|
+
*
|
|
165
|
+
* Slack and Teams webhooks cannot carry file uploads, so `attachPdf` does not
|
|
166
|
+
* exist on those channels.
|
|
162
167
|
*/
|
|
163
168
|
notify?: {
|
|
164
169
|
slack?: {
|
|
@@ -175,6 +180,7 @@ interface ReporterOptions {
|
|
|
175
180
|
url: string;
|
|
176
181
|
on?: 'always' | 'failure' | 'success';
|
|
177
182
|
enabled?: boolean;
|
|
183
|
+
attachPdf?: boolean;
|
|
178
184
|
};
|
|
179
185
|
email?: {
|
|
180
186
|
to: string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -158,7 +158,12 @@ interface ReporterOptions {
|
|
|
158
158
|
* `on` controls the trigger: `'always'` (default), `'failure'`, or `'success'`.
|
|
159
159
|
*
|
|
160
160
|
* - `slack` / `teams` / `discord`: webhook URL channels
|
|
161
|
+
* - `discord`: `attachPdf: true` uploads the PDF with the message (falls back
|
|
162
|
+
* to the summary alone if the upload fails or the PDF exceeds Discord's cap)
|
|
161
163
|
* - `email`: sends via Resend; `to` is required; `attachPdf: true` attaches the PDF
|
|
164
|
+
*
|
|
165
|
+
* Slack and Teams webhooks cannot carry file uploads, so `attachPdf` does not
|
|
166
|
+
* exist on those channels.
|
|
162
167
|
*/
|
|
163
168
|
notify?: {
|
|
164
169
|
slack?: {
|
|
@@ -175,6 +180,7 @@ interface ReporterOptions {
|
|
|
175
180
|
url: string;
|
|
176
181
|
on?: 'always' | 'failure' | 'success';
|
|
177
182
|
enabled?: boolean;
|
|
183
|
+
attachPdf?: boolean;
|
|
178
184
|
};
|
|
179
185
|
email?: {
|
|
180
186
|
to: string[];
|
package/dist/index.js
CHANGED
|
@@ -10454,6 +10454,9 @@ var channelConfigSchema = external_exports.object({
|
|
|
10454
10454
|
on: external_exports.enum(["always", "failure", "success"]).default("always"),
|
|
10455
10455
|
enabled: external_exports.boolean().default(false)
|
|
10456
10456
|
});
|
|
10457
|
+
var discordChannelConfigSchema = channelConfigSchema.extend({
|
|
10458
|
+
attachPdf: external_exports.boolean().default(false)
|
|
10459
|
+
});
|
|
10457
10460
|
var emailChannelConfigSchema = external_exports.object({
|
|
10458
10461
|
to: external_exports.array(external_exports.string().email('Each "to" entry must be a valid email')).min(1, '"to" must have at least one address'),
|
|
10459
10462
|
on: external_exports.enum(["always", "failure", "success"]).default("always"),
|
|
@@ -10463,7 +10466,7 @@ var emailChannelConfigSchema = external_exports.object({
|
|
|
10463
10466
|
var notifyConfigSchema = external_exports.object({
|
|
10464
10467
|
slack: channelConfigSchema.optional(),
|
|
10465
10468
|
teams: channelConfigSchema.optional(),
|
|
10466
|
-
discord:
|
|
10469
|
+
discord: discordChannelConfigSchema.optional(),
|
|
10467
10470
|
email: emailChannelConfigSchema.optional()
|
|
10468
10471
|
}).optional();
|
|
10469
10472
|
var failureAnalysisConfigSchema = external_exports.object({
|
|
@@ -13199,7 +13202,7 @@ function fmtMb(bytes) {
|
|
|
13199
13202
|
}
|
|
13200
13203
|
|
|
13201
13204
|
// src/reporter.ts
|
|
13202
|
-
var
|
|
13205
|
+
var import_path13 = __toESM(require("path"));
|
|
13203
13206
|
|
|
13204
13207
|
// src/history/HistoryManager.ts
|
|
13205
13208
|
init_cjs_shims();
|
|
@@ -13280,6 +13283,8 @@ init_cjs_shims();
|
|
|
13280
13283
|
|
|
13281
13284
|
// src/notify/NotificationSender.ts
|
|
13282
13285
|
init_cjs_shims();
|
|
13286
|
+
var import_promises2 = require("fs/promises");
|
|
13287
|
+
var import_path12 = require("path");
|
|
13283
13288
|
|
|
13284
13289
|
// src/notify/formatters/slack.ts
|
|
13285
13290
|
init_cjs_shims();
|
|
@@ -13529,6 +13534,7 @@ var FORMATTERS = {
|
|
|
13529
13534
|
teams: buildTeamsPayload,
|
|
13530
13535
|
discord: buildDiscordPayload
|
|
13531
13536
|
};
|
|
13537
|
+
var DISCORD_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
13532
13538
|
var NotificationSender = class {
|
|
13533
13539
|
async sendAll(reportData, notifyConfig, pdfPaths) {
|
|
13534
13540
|
const result = { sent: [], skipped: [], failed: [] };
|
|
@@ -13541,6 +13547,9 @@ var NotificationSender = class {
|
|
|
13541
13547
|
return Promise.resolve();
|
|
13542
13548
|
}
|
|
13543
13549
|
const payload = FORMATTERS[name](reportData.stats, pdfPaths, reportTitle);
|
|
13550
|
+
if (name === "discord" && notifyConfig.discord?.attachPdf && pdfPaths.length > 0) {
|
|
13551
|
+
return this.postDiscordWithPdf(channel.url, payload, pdfPaths[0], result);
|
|
13552
|
+
}
|
|
13544
13553
|
return this.post(name, channel.url, payload, result);
|
|
13545
13554
|
})
|
|
13546
13555
|
);
|
|
@@ -13582,6 +13591,47 @@ var NotificationSender = class {
|
|
|
13582
13591
|
})
|
|
13583
13592
|
);
|
|
13584
13593
|
}
|
|
13594
|
+
/**
|
|
13595
|
+
* Posts the summary to a Discord webhook with the PDF as a multipart file
|
|
13596
|
+
* upload (`payload_json` + `files[0]`). Any problem — unreadable file, PDF
|
|
13597
|
+
* over Discord's upload cap, or a failed upload — falls back to the plain
|
|
13598
|
+
* JSON summary post so the notification still lands; only the fallback's
|
|
13599
|
+
* outcome is recorded in the result. Attaches the first PDF only (matches
|
|
13600
|
+
* the email channel's multi-template behaviour).
|
|
13601
|
+
*/
|
|
13602
|
+
async postDiscordWithPdf(url, payload, pdfPath, result) {
|
|
13603
|
+
let file;
|
|
13604
|
+
try {
|
|
13605
|
+
file = await (0, import_promises2.readFile)(pdfPath);
|
|
13606
|
+
if (file.byteLength > DISCORD_MAX_UPLOAD_BYTES) {
|
|
13607
|
+
const mb = (file.byteLength / 1024 / 1024).toFixed(1);
|
|
13608
|
+
throw new Error(`PDF is ${mb}MB \u2014 over Discord's upload limit`);
|
|
13609
|
+
}
|
|
13610
|
+
} catch (err) {
|
|
13611
|
+
logger.warn(
|
|
13612
|
+
`[notify] discord: ${err instanceof Error ? err.message : String(err)} \u2014 sending summary without the PDF`
|
|
13613
|
+
);
|
|
13614
|
+
return this.post("discord", url, payload, result);
|
|
13615
|
+
}
|
|
13616
|
+
const form = new FormData();
|
|
13617
|
+
form.append("payload_json", JSON.stringify(payload));
|
|
13618
|
+
form.append("files[0]", new Blob([new Uint8Array(file)], { type: "application/pdf" }), (0, import_path12.basename)(pdfPath));
|
|
13619
|
+
const controller = new AbortController();
|
|
13620
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
13621
|
+
try {
|
|
13622
|
+
const res = await fetch(url, { method: "POST", body: form, signal: controller.signal });
|
|
13623
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
13624
|
+
logger.info("[notify] discord: sent (PDF attached)");
|
|
13625
|
+
result.sent.push("discord");
|
|
13626
|
+
} catch (err) {
|
|
13627
|
+
logger.warn(
|
|
13628
|
+
`[notify] discord: upload failed (${err instanceof Error ? err.message : String(err)}) \u2014 retrying without the PDF`
|
|
13629
|
+
);
|
|
13630
|
+
return this.post("discord", url, payload, result);
|
|
13631
|
+
} finally {
|
|
13632
|
+
clearTimeout(timeoutId);
|
|
13633
|
+
}
|
|
13634
|
+
}
|
|
13585
13635
|
async post(name, url, payload, result) {
|
|
13586
13636
|
const controller = new AbortController();
|
|
13587
13637
|
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
@@ -13951,7 +14001,7 @@ var PdfReporter = class {
|
|
|
13951
14001
|
this.liveConsole = false;
|
|
13952
14002
|
this.options = parseOptions(rawOptions);
|
|
13953
14003
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
13954
|
-
const version = true ? "0.
|
|
14004
|
+
const version = true ? "0.13.1" : "0.x";
|
|
13955
14005
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
13956
14006
|
this.licenseClient = new LicenseClient({
|
|
13957
14007
|
licenseKey: this.options.licenseKey,
|
|
@@ -14219,7 +14269,7 @@ var PdfReporter = class {
|
|
|
14219
14269
|
}
|
|
14220
14270
|
resolveProjectName() {
|
|
14221
14271
|
try {
|
|
14222
|
-
const pkgPath =
|
|
14272
|
+
const pkgPath = import_path13.default.resolve(process.cwd(), "package.json");
|
|
14223
14273
|
const pkg = require(pkgPath);
|
|
14224
14274
|
return pkg.name ?? "Playwright Tests";
|
|
14225
14275
|
} catch {
|
|
@@ -14269,9 +14319,9 @@ function deriveVerdict(stats) {
|
|
|
14269
14319
|
}
|
|
14270
14320
|
function resolveHistoryPath(options, cwd) {
|
|
14271
14321
|
if (options.historyFile) {
|
|
14272
|
-
return
|
|
14322
|
+
return import_path13.default.isAbsolute(options.historyFile) ? options.historyFile : import_path13.default.resolve(cwd, options.historyFile);
|
|
14273
14323
|
}
|
|
14274
|
-
return
|
|
14324
|
+
return import_path13.default.join(resolveProjectDir(options, cwd), "history.json");
|
|
14275
14325
|
}
|
|
14276
14326
|
function flattenSuiteNodes(suites) {
|
|
14277
14327
|
return suites.flatMap((s) => [s, ...flattenSuiteNodes(s.suites)]);
|