reporting-labs 0.6.0 → 0.6.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/README.md CHANGED
@@ -267,7 +267,7 @@ Legend: ✅ built in · 🟡 possible with manual setup or extra config · ❌ n
267
267
  | Secrets masked | ❌ | 🟡 parameter masking | ✅ automatic |
268
268
  | CSV / JSON export of failures, Slack summary | ❌ | ❌ | ✅ |
269
269
  | Timeline by worker | ❌ | ✅ | ✅ |
270
- | Combine shards / several runs | ✅ blob + `merge-reports` | ✅ Launches | ❌ on the roadmap |
270
+ | Combine shards / several runs | ✅ blob + `merge-reports` | ✅ Launches | ✅ `reporting-labs merge` |
271
271
  | Frameworks beyond Playwright | ❌ | ✅ many languages | ❌ on the roadmap |
272
272
  | Free and open source | ✅ | ✅ (Allure TestOps is a separate paid product) | ✅ MIT |
273
273
 
@@ -352,39 +352,100 @@ Ready-to-copy samples: [github-actions.yml](https://github.com/naveenautomationl
352
352
 
353
353
  ### Split your run across shards, then merge into one report
354
354
 
355
- Big test suite? Run it faster by splitting it into shards. Each shard is a separate CI job, all running at the same time. When they finish, join them into one report.
355
+ Big test suite? Split it into shards so they run in parallel, then join every shard's report into one HTML at the end. Works on your laptop and on any CI.
356
356
 
357
- **How it works:**
357
+ **What happens under the hood.** Each shard runs the reporter and writes its own `reporting-labs/` folder with `index.html` plus a small `report.json` next to it. The `merge` command reads every shard's `report.json`, combines them into one dataset, copies attachments into per-shard subfolders so nothing overwrites, and renders one HTML with all tests, one Trend chart, one Failure Clusters view, and a Shards row in the Environment card.
358
358
 
359
- 1. Each shard runs your tests. Each one writes its own `reporting-labs/index.html` and a small `report.json` next to it. Save each shard's folder as a CI artifact.
360
- 2. In one last job, download all the shard folders and run one command:
359
+ Nothing extra to install and no config change needed. The reporter writes `report.json` on every run out of the box.
360
+
361
+ #### Try it on your laptop first
362
+
363
+ Three shards, run one after another (on a single machine you cannot really run them in parallel, but this proves the merge works end to end):
364
+
365
+ ```bash
366
+ # Delete leftovers from any previous run
367
+ rm -rf all-shards merged
368
+
369
+ # Run each shard and move its folder aside so the next shard does not overwrite it
370
+ mkdir -p all-shards
371
+ npx playwright test --shard=1/3 && mv reporting-labs all-shards/s1
372
+ npx playwright test --shard=2/3 && mv reporting-labs all-shards/s2
373
+ npx playwright test --shard=3/3 && mv reporting-labs all-shards/s3
374
+
375
+ # One command combines them
376
+ npx reporting-labs merge all-shards -o merged
377
+
378
+ # Open it
379
+ open merged/index.html # macOS
380
+ # start merged/index.html # Windows
381
+ # xdg-open merged/index.html # Linux
382
+ ```
383
+
384
+ Look for this line in each `npx playwright test` output:
385
+
386
+ ```
387
+ reporting-labs: report written to reporting-labs/index.html
388
+ ```
389
+
390
+ That line proves the reporter ran and the folder exists to move. If you do not see it, your `playwright.config.ts` is not wiring `reporting-labs` as a reporter yet — see the [Quick start](#quick-start-2-minutes).
391
+
392
+ **Custom output folder?** If your config has `outputFolder: 'my-report'`, use that name in the move step:
361
393
 
362
394
  ```bash
363
- npx reporting-labs merge ./all-shards -o merged/
395
+ mv my-report all-shards/s1
364
396
  ```
365
397
 
366
- You get one report with:
398
+ **One-liner for a real parallel run on your laptop.** Runs three shards at once, then merges when all three finish:
399
+
400
+ ```bash
401
+ rm -rf all-shards merged && mkdir -p all-shards
402
+ (npx playwright test --shard=1/3 && mv reporting-labs all-shards/s1) &
403
+ (npx playwright test --shard=2/3 && mv reporting-labs all-shards/s2) &
404
+ (npx playwright test --shard=3/3 && mv reporting-labs all-shards/s3) &
405
+ wait
406
+ npx reporting-labs merge all-shards -o merged
407
+ open merged/index.html
408
+ ```
367
409
 
368
- - All tests from every shard in one list, sorted by priority
369
- - Combined pass / fail / flaky numbers on top
370
- - One Trend chart, one Environment card, one Failure Clusters view
371
- - Screenshots and videos kept in `merged/assets/shard-1-of-4/`, `merged/assets/shard-2-of-4/`, so nothing overwrites
410
+ #### GitHub Actions
372
411
 
373
- **GitHub Actions example** (replace `npx playwright test` with your own command):
412
+ Each shard runs on its own runner in parallel, then a final `merge` job downloads every shard's report and combines them. Copy-paste as `.github/workflows/tests.yml` in your repo:
374
413
 
375
414
  ```yaml
415
+ name: Playwright tests
416
+
417
+ on:
418
+ push:
419
+ branches: [main]
420
+ pull_request:
421
+
376
422
  jobs:
377
423
  test:
378
- strategy:
379
- matrix: { shard: [1, 2, 3, 4] }
424
+ name: shard ${{ matrix.shard }}/4
380
425
  runs-on: ubuntu-latest
426
+ strategy:
427
+ fail-fast: false
428
+ matrix:
429
+ shard: [1, 2, 3, 4]
381
430
  steps:
382
431
  - uses: actions/checkout@v4
383
432
  - uses: actions/setup-node@v4
384
433
  with: { node-version: 20, cache: npm }
385
434
  - run: npm ci
386
435
  - run: npx playwright install --with-deps
436
+
437
+ # Keep the history file across runs so the Trend and new-vs-known
438
+ # failures build up over time. Each shard reads the same file.
439
+ - uses: actions/cache@v4
440
+ with:
441
+ path: reporting-labs.history.json
442
+ key: reporting-labs-history-${{ github.ref_name }}-${{ github.run_id }}
443
+ restore-keys: |
444
+ reporting-labs-history-${{ github.ref_name }}-
445
+
387
446
  - run: npx playwright test --shard=${{ matrix.shard }}/4
447
+
448
+ # Save this shard's report so the merge job can pick it up.
388
449
  - uses: actions/upload-artifact@v4
389
450
  if: always()
390
451
  with:
@@ -393,6 +454,7 @@ jobs:
393
454
  retention-days: 30
394
455
 
395
456
  merge:
457
+ name: merge shards into one report
396
458
  if: always()
397
459
  needs: test
398
460
  runs-on: ubuntu-latest
@@ -401,17 +463,91 @@ jobs:
401
463
  - uses: actions/setup-node@v4
402
464
  with: { node-version: 20, cache: npm }
403
465
  - run: npm ci
466
+
467
+ # Downloads every 'report-shard-N' artifact under ./all-shards/report-shard-N/
404
468
  - uses: actions/download-artifact@v4
405
- with: { path: all-shards, pattern: 'report-shard-*' }
406
- - run: npx reporting-labs merge all-shards -o merged/
469
+ with:
470
+ path: all-shards
471
+ pattern: report-shard-*
472
+
473
+ - run: npx reporting-labs merge all-shards -o merged
474
+
475
+ # This is the artifact your team downloads and opens.
407
476
  - uses: actions/upload-artifact@v4
408
- with: { name: merged-report, path: merged/, retention-days: 30 }
477
+ with:
478
+ name: merged-report
479
+ path: merged/
480
+ retention-days: 30
409
481
  ```
410
482
 
411
- **Notes:**
483
+ Download the **merged-report** artifact from the run's summary page and open `index.html` locally.
484
+
485
+ #### Jenkins
486
+
487
+ Four shards run in parallel via a matrix pipeline, then a final stage merges. Copy-paste as `Jenkinsfile`:
488
+
489
+ ```groovy
490
+ pipeline {
491
+ agent any
492
+ options { timestamps() }
493
+
494
+ stages {
495
+ stage('Install') {
496
+ steps {
497
+ sh 'npm ci'
498
+ sh 'npx playwright install --with-deps'
499
+ }
500
+ }
501
+
502
+ stage('Run shards in parallel') {
503
+ matrix {
504
+ axes {
505
+ axis { name 'SHARD'; values '1', '2', '3', '4' }
506
+ }
507
+ stages {
508
+ stage('Test') {
509
+ steps {
510
+ sh "npx playwright test --shard=${SHARD}/4"
511
+ // Save each shard's report so the merge stage can unpack it later.
512
+ stash name: "report-shard-${SHARD}", includes: 'reporting-labs/**'
513
+ }
514
+ }
515
+ }
516
+ }
517
+ }
518
+
519
+ stage('Merge into one report') {
520
+ steps {
521
+ sh 'rm -rf all-shards merged && mkdir -p all-shards'
522
+ script {
523
+ ['1', '2', '3', '4'].each { s ->
524
+ dir("all-shards/shard-${s}") { unstash "report-shard-${s}" }
525
+ }
526
+ }
527
+ // Unstash lands the folder as all-shards/shard-N/reporting-labs/... — flatten it.
528
+ sh '''
529
+ for d in all-shards/shard-*; do
530
+ mv "$d/reporting-labs/"* "$d/"
531
+ rmdir "$d/reporting-labs"
532
+ done
533
+ '''
534
+ sh 'npx reporting-labs merge all-shards -o merged'
535
+ archiveArtifacts artifacts: 'merged/**', allowEmptyArchive: false
536
+ }
537
+ }
538
+ }
539
+ }
540
+ ```
541
+
542
+ Jenkins blocks inline JavaScript inside the HTML Publisher by default (Playwright's own HTML report has the same limitation), so the fastest way to view the merged report is to download the `merged/` folder from the build's artifacts and open `index.html` locally. If a Jenkins admin can relax the policy in the script console with `System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")`, you can also add a `publishHTML` step to open the report right from the build page.
543
+
544
+ #### Troubleshooting
412
545
 
413
- - The reporter always writes `report.json` alongside `index.html`, so `merge` just works. Turn it off with `emitJson: false` if you do not want it.
414
- - Not using shards? Ignore this section. The single-run report keeps working exactly as before.
546
+ - **`mv: reporting-labs: No such file or directory`.** The reporter did not run. Check `playwright.config.ts`: `reporter: [['list'], ['reporting-labs', reportingLabs]]`. Without that line, `npx playwright test` does not create the `reporting-labs/` folder.
547
+ - **A shard reports "0 tests".** Playwright shards by spec file by default, so if you have fewer spec files than shards, some shards will be empty. Reduce the shard count, split large specs, or upgrade to Playwright 1.51+ and set `shardingMode: 'round-robin'` for per-test sharding. The merge still works either way.
548
+ - **The Trend chart looks off after merging.** All shards read the same history file at startup, so the current run is recorded once by whichever shard writes last. On CI, cache the history file (see the GitHub Actions example above) so future runs pick it up.
549
+ - **`merge` says "no report.json under ...".** The folder you pointed at does not contain a shard's report. `merge` accepts either individual shard folders (each with `report.json`) or one parent folder that has many shards as subfolders. Check that `report.json` actually exists inside — the reporter writes it on every run unless you set `emitJson: false`.
550
+ - **Attachments broken in the merged report.** Screenshots are usually embedded inline as base64, so they always work. Videos and traces live in `merged/assets/shard-N-of-M/` — the merge rewrites paths so they resolve correctly. If a video does not play, open the merged folder locally (not from a Jenkins URL that strips inline JS).
415
551
 
416
552
  ### Slack, email, Teams: use your CI's own integration
417
553
 
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ // Emit dist/template.html — the same HTML shell renderHtml produces, but with
3
+ // well-known placeholders for the fields any language port (Java today, more
4
+ // later) needs to inject at render time:
5
+ //
6
+ // __RL_DATA__ — the report data JSON (the only required one)
7
+ // __RL_ACCENT_CSS__ — a :root{--accent:...} block, or empty
8
+ // __RL_CUSTOM_CSS__ — extra CSS the caller wants appended, or empty
9
+ //
10
+ // The placeholders are seeded with distinctive sentinels and then rewritten,
11
+ // so the surrounding CSS/HTML stays exactly what renderHtml produces.
12
+ //
13
+ // Runs AFTER `tsc` (dist/template.js has to exist).
14
+
15
+ 'use strict';
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const { renderHtml } = require('../dist/template');
20
+
21
+ const ACCENT_SENTINEL = '__RL_ACCENT_SENTINEL_1a56db_2026__';
22
+ const CUSTOM_SENTINEL = '/*__RL_CUSTOM_CSS_SENTINEL_2026__*/';
23
+
24
+ const seed = {
25
+ title: '',
26
+ generatedAt: 0,
27
+ startTime: 0,
28
+ duration: 0,
29
+ metadata: {},
30
+ projects: [],
31
+ workers: 1,
32
+ stats: { total: 0, passed: 0, failed: 0, flaky: 0, skipped: 0 },
33
+ tests: [],
34
+ history: [],
35
+ bdd: false,
36
+ rootDir: '',
37
+ env: [],
38
+ runStatus: 'passed',
39
+ globalErrors: [],
40
+ globalOutput: [],
41
+ options: {
42
+ theme: 'auto',
43
+ palette: 'lab',
44
+ embedFonts: true,
45
+ sections: [],
46
+ widgets: {
47
+ overviewCards: true,
48
+ breakdown: true,
49
+ needsAttention: true,
50
+ failureClusters: true,
51
+ trend: true,
52
+ slowest: true,
53
+ env: true,
54
+ },
55
+ dimensions: ['priority', 'severity', 'owner', 'feature'],
56
+ dimensionOrder: {},
57
+ links: {},
58
+ // Seed accent + customCss with sentinels so we know exactly where they
59
+ // ended up in the rendered HTML.
60
+ accent: ACCENT_SENTINEL,
61
+ customCss: CUSTOM_SENTINEL,
62
+ editorLinks: true,
63
+ },
64
+ };
65
+
66
+ let html = renderHtml(seed);
67
+
68
+ // Data payload placeholder.
69
+ const dataRe = /<script id="rl-data" type="application\/json">[^<]*<\/script>/;
70
+ if (!dataRe.test(html)) {
71
+ console.error('build-template: could not find rl-data script tag');
72
+ process.exit(1);
73
+ }
74
+ html = html.replace(
75
+ dataRe,
76
+ '<script id="rl-data" type="application/json">__RL_DATA__</script>'
77
+ );
78
+
79
+ // Accent + customCss placeholders. renderHtml renders:
80
+ // `:root{--accent:${accent}!important}` when accent is truthy,
81
+ // then appends customCss verbatim.
82
+ // Replace those sentinel-anchored spans with named placeholders that hold
83
+ // nothing by default; the port fills them in at render time.
84
+ const accentBlock = `:root{--accent:${ACCENT_SENTINEL}!important}`;
85
+ if (!html.includes(accentBlock)) {
86
+ console.error('build-template: accent sentinel not found in rendered HTML');
87
+ process.exit(1);
88
+ }
89
+ html = html.replace(accentBlock, '__RL_ACCENT_CSS__');
90
+
91
+ if (!html.includes(CUSTOM_SENTINEL)) {
92
+ console.error('build-template: customCss sentinel not found in rendered HTML');
93
+ process.exit(1);
94
+ }
95
+ html = html.replace(CUSTOM_SENTINEL, '__RL_CUSTOM_CSS__');
96
+
97
+ const out = path.join(__dirname, '..', 'dist', 'template.html');
98
+ fs.writeFileSync(out, html);
99
+
100
+ const sizeKb = (Buffer.byteLength(html) / 1024).toFixed(1);
101
+ console.log(`build-template: wrote ${out} (${sizeKb} KB)`);