reporting-labs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/bin/cli.js +32 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +15 -0
- package/dist/mask.d.ts +9 -0
- package/dist/mask.js +51 -0
- package/dist/meta.d.ts +43 -0
- package/dist/meta.js +70 -0
- package/dist/reporter.d.ts +37 -0
- package/dist/reporter.js +504 -0
- package/dist/template.d.ts +4 -0
- package/dist/template.js +1383 -0
- package/dist/types.d.ts +281 -0
- package/dist/types.js +2 -0
- package/fonts/mono-400.woff2 +0 -0
- package/fonts/mono-500.woff2 +0 -0
- package/fonts/sans-400.woff2 +0 -0
- package/fonts/sans-500.woff2 +0 -0
- package/fonts/sans-600.woff2 +0 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Naveen Khunteta (Naveen Automation Labs)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<p align="center"><img src="https://raw.githubusercontent.com/naveenanimation20/reporting-labs/main/assets/logo-wordmark.svg" alt="reportingLabs" width="320"></p>
|
|
2
|
+
|
|
3
|
+
# reportingLabs
|
|
4
|
+
|
|
5
|
+
**One HTML file that tells you what broke, who owns it, and whether it is new.**
|
|
6
|
+
|
|
7
|
+
reportingLabs turns a test run into a single, self-contained HTML report. No server, no upload, no dashboard to log into. Open the file, or attach it to a CI job, an email or a Slack message.
|
|
8
|
+
|
|
9
|
+
It is runner-agnostic by design: the report is built from a plain JSON model that adapters feed. The **Playwright adapter ships today**; WebdriverIO, Cypress and Jest/Vitest adapters are on the roadmap.
|
|
10
|
+
|
|
11
|
+
<p align="center"><img src="https://raw.githubusercontent.com/naveenanimation20/reporting-labs/main/docs/overview.png" alt="Overview page of a reportingLabs report" width="900"></p>
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
**1. Install**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i -D reporting-labs
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**2. Add the reporter to `playwright.config.ts`**
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
reporter: [['reporting-labs', { title: 'My app – regression' }]],
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**3. Run your tests and open the report**
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx playwright test
|
|
33
|
+
open reporting-labs/index.html
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That is all. Everything below is optional.
|
|
37
|
+
|
|
38
|
+
## A tour of the report
|
|
39
|
+
|
|
40
|
+
### Overview: the state of the run in one screen
|
|
41
|
+
|
|
42
|
+
- **Tiles**: pass rate with a ring and the change from the previous run, then passed / failed / flaky / skipped. Click a tile to see those tests.
|
|
43
|
+
- **Run strip**: every test as one cell, in run order. Hover for the name, click to open.
|
|
44
|
+
- **Needs attention**: failures ranked by priority and severity, with the spec file, the owner, and whether the failure is new or has been failing since a given build.
|
|
45
|
+
- **Failure clusters**: failures grouped by error message, so 30 red tests with one root cause read as one problem.
|
|
46
|
+
- **Breakdown**: stacked bars per priority, severity, feature, owner, spec file, project or tag. Click a row to filter the test list. With more than one project you also get a feature × project heatmap.
|
|
47
|
+
- **Slowest tests** and **Got slower** (tests that took 2× longer than last run), **Flakiest tests**, **Skipped** (with reasons), **Environment** and the **Trend** across runs.
|
|
48
|
+
|
|
49
|
+
<p align="center"><img src="https://raw.githubusercontent.com/naveenanimation20/reporting-labs/main/docs/heatmap.png" alt="Breakdown card with the feature by project heatmap" width="900"></p>
|
|
50
|
+
|
|
51
|
+
<p align="center"><img src="https://raw.githubusercontent.com/naveenanimation20/reporting-labs/main/docs/trend.png" alt="Trend chart with hover tooltip" width="900"></p>
|
|
52
|
+
|
|
53
|
+
### Failures: everything you need to triage
|
|
54
|
+
|
|
55
|
+
- **By owner**: who to ping, with failed and flaky counts. Click an owner to filter.
|
|
56
|
+
- **Download CSV / JSON**: the failed and flaky tests with title, spec, project, priority, owner, ticket, attempts, duration, first error line and new/known status. Ready for Jira or a sheet.
|
|
57
|
+
- **Copy summary**: a Slack/Teams-ready message with top failures, owners, ticket keys and an owner breakdown.
|
|
58
|
+
- The table shows every failed or flaky test with its history over the last runs as dots.
|
|
59
|
+
|
|
60
|
+
<p align="center"><img src="https://raw.githubusercontent.com/naveenanimation20/reporting-labs/main/docs/failures.png" alt="Failures page" width="900"></p>
|
|
61
|
+
|
|
62
|
+
### Test detail: the error, the steps, the evidence
|
|
63
|
+
|
|
64
|
+
- **Expected vs received** side by side with the difference highlighted. Object diffs are colored line by line.
|
|
65
|
+
- Error **location** (linked to VS Code), Playwright's **code snippet**, full message and stack.
|
|
66
|
+
- **Steps** with a bar per step showing its share of the test time. Given/When/Then titles are styled as Gherkin.
|
|
67
|
+
- Retries as tabs, screenshots inline (click to zoom), videos, traces, console output, logs, test data and API calls.
|
|
68
|
+
- **Open in VS Code** jumps to the failing line.
|
|
69
|
+
|
|
70
|
+
<p align="center"><img src="https://raw.githubusercontent.com/naveenanimation20/reporting-labs/main/docs/test-detail.png" alt="Test detail with expected vs received diff and step bars" width="900"></p>
|
|
71
|
+
|
|
72
|
+
### Timeline: how the run used its workers
|
|
73
|
+
|
|
74
|
+
<p align="center"><img src="https://raw.githubusercontent.com/naveenanimation20/reporting-labs/main/docs/timeline.png" alt="Timeline by worker" width="900"></p>
|
|
75
|
+
|
|
76
|
+
## Make the report smarter: tag your tests
|
|
77
|
+
|
|
78
|
+
One line per test gives you priority ranking, owner rollups, feature breakdowns and Jira links.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { meta } from 'reporting-labs';
|
|
82
|
+
|
|
83
|
+
test('completes purchase', async ({ page }) => {
|
|
84
|
+
meta({ priority: 'P0', severity: 'blocker', owner: 'naveen', feature: 'payment', story: 'SHOP-250' });
|
|
85
|
+
// ...
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Tags work too: `{ tag: ['@P1', '@severity:critical', '@owner:priya'] }`. Plain annotations (`test.info().annotations.push({ type: 'priority', description: 'P1' })`) are picked up as well.
|
|
90
|
+
|
|
91
|
+
Turn story, epic or issue keys into links:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
links: { story: 'https://acme.atlassian.net/browse/{id}', epic: 'https://acme.atlassian.net/browse/{id}' }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Logs, test data and API calls
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { log, testData, api, recordApi } from 'reporting-labs';
|
|
101
|
+
|
|
102
|
+
test('creates an order', async ({ request }) => {
|
|
103
|
+
await log('starting with an empty cart'); // timestamped log line
|
|
104
|
+
|
|
105
|
+
await testData({ user: 'naveen@x.com', password: 'S3cret' }, 'Login'); // object → key/value block
|
|
106
|
+
await testData(rowsFromExcelOrJson, 'Coupons'); // array of objects → table
|
|
107
|
+
await testData(fs.readFileSync('data/users.csv', 'utf8'), 'users.csv'); // CSV string → table
|
|
108
|
+
|
|
109
|
+
const res = await recordApi('POST', '/v1/orders', { headers, data },
|
|
110
|
+
() => request.post('/v1/orders', { headers, data })); // request + response panel
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Passwords, tokens, API keys, `Authorization` / `Cookie` headers, JWTs and `Bearer …` values are masked as `****` everywhere. Add your own keys with `maskKeys: ['otp', 'pan']`.
|
|
115
|
+
|
|
116
|
+
## Run history: new vs known, flaky, slower
|
|
117
|
+
|
|
118
|
+
The reporter keeps `reporting-labs.history.json` next to your config (last 30 runs by default). Commit it, or cache it in CI, and the report starts answering the questions you ask first:
|
|
119
|
+
|
|
120
|
+
| Question | Where it shows |
|
|
121
|
+
|---|---|
|
|
122
|
+
| Did this break just now, or has it been red for days? | `new this run` / `failing since #1838` on every failure; the Failed tile splits the count |
|
|
123
|
+
| Which tests are flaky? | Last-10-runs dots on every failure, plus the Flakiest tests card |
|
|
124
|
+
| What got slower? | Got slower tab on the Slowest card (2× slower than last run) |
|
|
125
|
+
| Are we trending up or down? | Trend chart with pass rate, fail rate and duration per run, hover for details |
|
|
126
|
+
|
|
127
|
+
The first run has nothing to compare with; these cards fill in from the second run.
|
|
128
|
+
|
|
129
|
+
## Screenshots, videos, traces
|
|
130
|
+
|
|
131
|
+
Nothing extra to do. Use your runner's own settings and the report picks the attachments up:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
use: {
|
|
135
|
+
screenshot: 'only-on-failure', // shown inline, click to zoom
|
|
136
|
+
video: 'retain-on-failure', // inline player (copied to ./assets, or embedded with embedVideos: true)
|
|
137
|
+
trace: 'on-first-retry', // trace card with download and how to open it
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`toHaveScreenshot` failures get a visual comparison viewer: slider, side by side, and diff. Anything you attach with `test.info().attach()` shows up too: images inline, text/JSON as a code block, everything else as a download.
|
|
142
|
+
|
|
143
|
+
## What the report covers
|
|
144
|
+
|
|
145
|
+
Every outcome Playwright can produce is shown, not just pass/fail:
|
|
146
|
+
|
|
147
|
+
- passed, failed, flaky (passed on retry), skipped with the `test.skip` / `test.fixme` reason, timed out with the exceeded timeout, interrupted
|
|
148
|
+
- `test.fail()` tests that fail as expected count as passed with an "Expected failure" badge; one that unexpectedly passes is reported as failed with a note
|
|
149
|
+
- errors outside tests (a spec that throws at load, global setup, a worker crash) get their own card at the top of the overview
|
|
150
|
+
- interrupted runs and global timeouts show a banner with how many tests did not finish
|
|
151
|
+
- shard and worker count in the Environment card, together with Playwright and Node versions, OS, browsers, the CI job link (GitHub Actions, GitLab, Jenkins, CircleCI, Azure, Bitbucket) and the git commit
|
|
152
|
+
|
|
153
|
+
## Options
|
|
154
|
+
|
|
155
|
+
All options are optional. Pass them as the second element of the reporter tuple.
|
|
156
|
+
|
|
157
|
+
| Option | Default | What it does |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `title` | `'Test report'` | Report title in the header |
|
|
160
|
+
| `logo` | – | Path or data URI of your logo, shown next to the title |
|
|
161
|
+
| `project` | – | `{ name, version, team, url }` shown under the title |
|
|
162
|
+
| `metadata` | `{}` | Key/value chips in the header, e.g. `{ env: 'staging', build: '#1842' }`. `build` labels the run in history; in CI the run number is used when it is not set |
|
|
163
|
+
| `env` | – | Extra rows for the Environment card, e.g. `{ 'App build': '2.4.0-rc3' }` |
|
|
164
|
+
| `dimensions` | `['priority','severity','feature','owner']` | Meta keys that get charts and filters |
|
|
165
|
+
| `dimensionOrder` | P0…P4, blocker…trivial | Sort order per dimension, e.g. `{ severity: ['blocker','critical','major','minor'] }` |
|
|
166
|
+
| `links` | `{}` | URL templates per meta key, `{id}` is replaced by the value |
|
|
167
|
+
| `maskKeys` | `[]` | Extra keys to mask in logs, data and API panels |
|
|
168
|
+
| `widgets` | all on | Hide cards: `{ tags: false, timeline: false, flaky: false, environment: false, skipped: false, ... }` |
|
|
169
|
+
| `sections` | `[]` | Extra HTML sections below the summary, e.g. release notes |
|
|
170
|
+
| `history` | `{ enabled: true, keep: 30 }` | Run history file; `file` sets a custom path |
|
|
171
|
+
| `palette` | `'lab'` | `'lab'` (blue), `'ocean'`, `'ember'`, `'mono'`. Viewers can switch in the header |
|
|
172
|
+
| `accent` | palette accent | Override the accent with your brand color |
|
|
173
|
+
| `theme` | `'auto'` | `'light'`, `'dark'` or follow the OS |
|
|
174
|
+
| `customCss` | `''` | CSS appended to the report |
|
|
175
|
+
| `editorLinks` | `true` locally, `false` in CI | "Open in VS Code" links |
|
|
176
|
+
| `bdd` | auto | Style Given/When/Then steps as Gherkin |
|
|
177
|
+
| `outputFolder` | `'reporting-labs'` | Where the report and copied assets go |
|
|
178
|
+
| `outputFile` | `'index.html'` | Report file name |
|
|
179
|
+
| `embedAttachments` | `true` | Inline screenshots as base64 (single file) |
|
|
180
|
+
| `embedLimit` | 2 MB | Larger attachments are copied to `./assets` instead |
|
|
181
|
+
| `embedVideos` | `false` | Inline videos too (big file) |
|
|
182
|
+
| `embedFonts` | `true` | Bundle IBM Plex (~140 KB) so the report looks the same offline |
|
|
183
|
+
| `announce` | `true` | Print the report path after the run |
|
|
184
|
+
|
|
185
|
+
A fuller example:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
reporter: [['reporting-labs', {
|
|
189
|
+
title: 'ShopLite – nightly regression',
|
|
190
|
+
project: { name: 'ShopLite Web', version: '2.4.0', team: 'QA Platform' },
|
|
191
|
+
metadata: { env: 'staging', branch: process.env.GIT_BRANCH ?? 'main', build: process.env.BUILD_ID ?? 'local' },
|
|
192
|
+
links: { story: 'https://acme.atlassian.net/browse/{id}' },
|
|
193
|
+
sections: [{ title: 'Release notes', html: '<p>Checkout v2 at 50% rollout.</p>' }],
|
|
194
|
+
}]]
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`npx reporting-labs init` writes a starter `reporting-labs.config.ts`.
|
|
198
|
+
|
|
199
|
+
## Running in CI
|
|
200
|
+
|
|
201
|
+
The report is a plain file written next to your tests, so it works anywhere `npx playwright test` runs: locally, GitHub Actions, GitLab, Jenkins, CircleCI, Azure Pipelines, Bitbucket. Nothing phones home and no fonts are fetched, so it also works in locked-down networks.
|
|
202
|
+
|
|
203
|
+
What happens automatically in CI:
|
|
204
|
+
|
|
205
|
+
- The Environment card links the CI job and the commit (GitHub Actions, GitLab, Jenkins, CircleCI, Azure, Bitbucket are detected from their environment variables).
|
|
206
|
+
- The run is labelled with the CI run number in the history and the trend chart, unless you set `metadata.build` yourself.
|
|
207
|
+
- "Open in VS Code" links are off when the `CI` variable is set, because they would point at the runner's paths. Set `editorLinks: true` to force them.
|
|
208
|
+
|
|
209
|
+
Two things to set up:
|
|
210
|
+
|
|
211
|
+
1. **Publish the report.** Upload `reporting-labs/` as a build artifact (or archive it in Jenkins). Screenshots and fonts are inside `index.html`; videos and large files sit in `reporting-labs/assets/`.
|
|
212
|
+
2. **Keep the history.** `reporting-labs.history.json` is what powers the trend, new vs known failures, flaky history and duration regressions. On GitHub Actions restore and save it with `actions/cache`; on Jenkins the workspace usually persists on its own.
|
|
213
|
+
|
|
214
|
+
Ready-to-copy samples: [docs/ci/github-actions.yml](https://github.com/naveenanimation20/reporting-labs/blob/main/docs/ci/github-actions.yml) and [docs/ci/Jenkinsfile](https://github.com/naveenanimation20/reporting-labs/blob/main/docs/ci/Jenkinsfile).
|
|
215
|
+
|
|
216
|
+
```yaml
|
|
217
|
+
# GitHub Actions, the two steps that matter
|
|
218
|
+
- uses: actions/cache@v4
|
|
219
|
+
with:
|
|
220
|
+
path: reporting-labs.history.json
|
|
221
|
+
key: reporting-labs-history-${{ github.ref_name }}-${{ github.run_id }}
|
|
222
|
+
restore-keys: reporting-labs-history-${{ github.ref_name }}-
|
|
223
|
+
- uses: actions/upload-artifact@v4
|
|
224
|
+
if: always()
|
|
225
|
+
with:
|
|
226
|
+
name: test-report
|
|
227
|
+
path: reporting-labs/
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Jenkins HTML Publisher note.** Jenkins' default Content-Security-Policy blocks inline JavaScript, so a single-file report shows up blank inside Jenkins (Playwright's own HTML report has the same issue). Either download the archived artifact and open it locally, or have an admin relax the policy in the script console: `System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")`.
|
|
231
|
+
|
|
232
|
+
## Good to know
|
|
233
|
+
|
|
234
|
+
- **Single file.** Screenshots and fonts are embedded, so `index.html` works from a mail attachment or a CI artifact. Videos and large files go to `./assets` next to it.
|
|
235
|
+
- **Keyboard.** `j` / `k` next and previous test, `f` failed only, `/` search, `1`–`5` switch views, `Esc` close.
|
|
236
|
+
- **Print.** A print stylesheet is included for PDF export.
|
|
237
|
+
- **Themes.** Light and dark follow the OS; the toggle in the header remembers the choice.
|
|
238
|
+
|
|
239
|
+
## Roadmap
|
|
240
|
+
|
|
241
|
+
- WebdriverIO, Cypress, Jest/Vitest, JUnit XML adapters via the shared JSON schema
|
|
242
|
+
- AI summary and failure clustering (bring your own API key)
|
|
243
|
+
- Hosted history dashboard across branches/projects
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT © Naveen Automation Labs
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const [cmd] = process.argv.slice(2);
|
|
5
|
+
|
|
6
|
+
if (cmd === 'init') {
|
|
7
|
+
const file = path.resolve('reporting-labs.config.ts');
|
|
8
|
+
if (fs.existsSync(file)) { console.log('reporting-labs.config.ts already exists.'); process.exit(0); }
|
|
9
|
+
fs.writeFileSync(file, `import type { ReportingLabsOptions } from 'reporting-labs';
|
|
10
|
+
|
|
11
|
+
const config: ReportingLabsOptions = {
|
|
12
|
+
title: 'My app – regression',
|
|
13
|
+
// logo: 'https://example.com/logo.svg',
|
|
14
|
+
palette: 'lab', // 'lab' (blue) | 'ocean' | 'ember' | 'mono'
|
|
15
|
+
theme: 'auto',
|
|
16
|
+
metadata: { env: process.env.TEST_ENV ?? 'local', branch: process.env.GIT_BRANCH ?? 'main' },
|
|
17
|
+
links: { story: 'https://acme.atlassian.net/browse/{id}' },
|
|
18
|
+
sections: [],
|
|
19
|
+
};
|
|
20
|
+
export default config;
|
|
21
|
+
`);
|
|
22
|
+
console.log('Created reporting-labs.config.ts');
|
|
23
|
+
console.log("Add to playwright.config.ts: reporter: [['reporting-labs', require('./reporting-labs.config').default]]");
|
|
24
|
+
} else {
|
|
25
|
+
console.log(`reporting-labs
|
|
26
|
+
|
|
27
|
+
npx reporting-labs init create a starter config file
|
|
28
|
+
|
|
29
|
+
Usage in playwright.config.ts:
|
|
30
|
+
reporter: [['reporting-labs', { title: 'My report' }]]
|
|
31
|
+
`);
|
|
32
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.recordApi = exports.api = exports.testData = exports.log = exports.meta = exports.ReportingLabsReporter = void 0;
|
|
7
|
+
const reporter_1 = __importDefault(require("./reporter"));
|
|
8
|
+
exports.ReportingLabsReporter = reporter_1.default;
|
|
9
|
+
var meta_1 = require("./meta");
|
|
10
|
+
Object.defineProperty(exports, "meta", { enumerable: true, get: function () { return meta_1.meta; } });
|
|
11
|
+
Object.defineProperty(exports, "log", { enumerable: true, get: function () { return meta_1.log; } });
|
|
12
|
+
Object.defineProperty(exports, "testData", { enumerable: true, get: function () { return meta_1.testData; } });
|
|
13
|
+
Object.defineProperty(exports, "api", { enumerable: true, get: function () { return meta_1.api; } });
|
|
14
|
+
Object.defineProperty(exports, "recordApi", { enumerable: true, get: function () { return meta_1.recordApi; } });
|
|
15
|
+
exports.default = reporter_1.default;
|
package/dist/mask.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function makeMasker(extraKeys?: string[]): {
|
|
2
|
+
mask: (v: unknown, key?: string) => unknown;
|
|
3
|
+
maskStr: (s: string) => string;
|
|
4
|
+
isSensitive: (k: string) => boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function parseCsv(text: string): {
|
|
7
|
+
columns: string[];
|
|
8
|
+
rows: string[][];
|
|
9
|
+
};
|
package/dist/mask.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeMasker = makeMasker;
|
|
4
|
+
exports.parseCsv = parseCsv;
|
|
5
|
+
const DEFAULT_KEYS = ['password', 'passwd', 'pwd', 'secret', 'token', 'apikey', 'api_key', 'api-key', 'authorization', 'auth', 'cookie', 'set-cookie', 'session', 'credential', 'private', 'ssn', 'cvv', 'card'];
|
|
6
|
+
const VALUE_PATTERNS = [
|
|
7
|
+
/\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+\/=-]{8,}/gi,
|
|
8
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}/g, // JWT
|
|
9
|
+
/\b(sk|pk|ghp|xox[abp]|AKIA)[_-]?[A-Za-z0-9]{12,}\b/g,
|
|
10
|
+
/(password|passwd|pwd|token|secret|api[_-]?key)(\s*[=:]\s*)([^\s&;,"']+)/gi,
|
|
11
|
+
];
|
|
12
|
+
function makeMasker(extraKeys = []) {
|
|
13
|
+
const keys = [...DEFAULT_KEYS, ...extraKeys.map(k => k.toLowerCase())];
|
|
14
|
+
const isSensitive = (k) => { const l = k.toLowerCase(); return keys.some(s => l === s || l.includes(s)); };
|
|
15
|
+
const maskStr = (s) => VALUE_PATTERNS.reduce((acc, re) => acc.replace(re, (m, ...g) => {
|
|
16
|
+
if (g.length >= 3 && typeof g[1] === 'string' && /[=:]/.test(g[1]))
|
|
17
|
+
return g[0] + g[1] + '****';
|
|
18
|
+
return m.split(/\s+/)[0] + ' ****';
|
|
19
|
+
}), s);
|
|
20
|
+
const mask = (v, key = '') => {
|
|
21
|
+
if (key && isSensitive(key))
|
|
22
|
+
return '****';
|
|
23
|
+
if (typeof v === 'string')
|
|
24
|
+
return maskStr(v);
|
|
25
|
+
if (Array.isArray(v))
|
|
26
|
+
return v.map(x => mask(x));
|
|
27
|
+
if (v && typeof v === 'object') {
|
|
28
|
+
const o = {};
|
|
29
|
+
for (const [k, x] of Object.entries(v))
|
|
30
|
+
o[k] = mask(x, k);
|
|
31
|
+
return o;
|
|
32
|
+
}
|
|
33
|
+
return v;
|
|
34
|
+
};
|
|
35
|
+
return { mask, maskStr, isSensitive };
|
|
36
|
+
}
|
|
37
|
+
function parseCsv(text) {
|
|
38
|
+
const lines = text.replace(/\r/g, '').split('\n').filter(l => l.trim());
|
|
39
|
+
const split = (l) => { const out = []; let cur = '', q = false; for (const c of l) {
|
|
40
|
+
if (c === '"')
|
|
41
|
+
q = !q;
|
|
42
|
+
else if (c === ',' && !q) {
|
|
43
|
+
out.push(cur);
|
|
44
|
+
cur = '';
|
|
45
|
+
}
|
|
46
|
+
else
|
|
47
|
+
cur += c;
|
|
48
|
+
} out.push(cur); return out.map(s => s.trim()); };
|
|
49
|
+
const [head, ...body] = lines.map(split);
|
|
50
|
+
return { columns: head ?? [], rows: body };
|
|
51
|
+
}
|
package/dist/meta.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ApiCall, TestMeta } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Attach report metadata to the current test. Call it first thing inside the test body.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* meta({ priority: 'P1', severity: 'critical', owner: 'naveen', feature: 'checkout', story: 'SHOP-231' });
|
|
7
|
+
*/
|
|
8
|
+
export declare function meta(values: TestMeta): void;
|
|
9
|
+
/**
|
|
10
|
+
* Add a timestamped log line to the current test. Lines containing "error"/"fail" show red, "warn" amber.
|
|
11
|
+
* Secrets like `password=...`, `Bearer ...`, JWTs are masked in the report.
|
|
12
|
+
* @example await log('cart total before coupons: $99.00');
|
|
13
|
+
*/
|
|
14
|
+
export declare function log(message: string, ...rest: unknown[]): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Attach the data used by this test.
|
|
17
|
+
* - object → key/value block
|
|
18
|
+
* - array of objects → table (rows from JSON / Excel / DB)
|
|
19
|
+
* - CSV string → table
|
|
20
|
+
* Sensitive keys (password, token, secret, apiKey, authorization, cookie...) are masked as ****.
|
|
21
|
+
* @param data the data
|
|
22
|
+
* @param name heading shown in the report (default "Test data")
|
|
23
|
+
* @example await testData({ username: 'naveen', password: 'S3cret' }, 'Login');
|
|
24
|
+
*/
|
|
25
|
+
export declare function testData(data: unknown, name?: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Record an API request/response for this test. Shows up in the test detail and the API tab.
|
|
28
|
+
* Authorization/Cookie headers, tokens and secret-looking fields are masked.
|
|
29
|
+
* @example
|
|
30
|
+
* await api({ method: 'POST', url: '/v1/orders', status: 201, duration: 138, requestBody, responseBody });
|
|
31
|
+
*/
|
|
32
|
+
export declare function api(call: ApiCall): Promise<void>;
|
|
33
|
+
/** Wrap a Playwright APIRequestContext call so it's recorded automatically. */
|
|
34
|
+
export declare function recordApi<T extends {
|
|
35
|
+
status(): number;
|
|
36
|
+
headers(): Record<string, string>;
|
|
37
|
+
text(): Promise<string>;
|
|
38
|
+
url(): string;
|
|
39
|
+
}>(method: string, url: string, options: {
|
|
40
|
+
headers?: Record<string, string>;
|
|
41
|
+
data?: unknown;
|
|
42
|
+
name?: string;
|
|
43
|
+
} | undefined, run: () => Promise<T>): Promise<T>;
|
package/dist/meta.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.meta = meta;
|
|
4
|
+
exports.log = log;
|
|
5
|
+
exports.testData = testData;
|
|
6
|
+
exports.api = api;
|
|
7
|
+
exports.recordApi = recordApi;
|
|
8
|
+
function info() {
|
|
9
|
+
// Lazy require so importing reporting-labs in playwright.config never loads the test runtime.
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
11
|
+
const { test } = require('@playwright/test');
|
|
12
|
+
return test.info();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Attach report metadata to the current test. Call it first thing inside the test body.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* meta({ priority: 'P1', severity: 'critical', owner: 'naveen', feature: 'checkout', story: 'SHOP-231' });
|
|
19
|
+
*/
|
|
20
|
+
function meta(values) {
|
|
21
|
+
const i = info();
|
|
22
|
+
for (const [k, v] of Object.entries(values)) {
|
|
23
|
+
if (v === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
i.annotations.push({ type: k.toLowerCase(), description: String(v) });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Add a timestamped log line to the current test. Lines containing "error"/"fail" show red, "warn" amber.
|
|
30
|
+
* Secrets like `password=...`, `Bearer ...`, JWTs are masked in the report.
|
|
31
|
+
* @example await log('cart total before coupons: $99.00');
|
|
32
|
+
*/
|
|
33
|
+
async function log(message, ...rest) {
|
|
34
|
+
const msg = [message, ...rest.map(r => (typeof r === 'string' ? r : JSON.stringify(r)))].join(' ');
|
|
35
|
+
await info().attach('rl:log', { body: JSON.stringify({ t: Date.now(), msg }), contentType: 'application/x-rl-log' });
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Attach the data used by this test.
|
|
39
|
+
* - object → key/value block
|
|
40
|
+
* - array of objects → table (rows from JSON / Excel / DB)
|
|
41
|
+
* - CSV string → table
|
|
42
|
+
* Sensitive keys (password, token, secret, apiKey, authorization, cookie...) are masked as ****.
|
|
43
|
+
* @param data the data
|
|
44
|
+
* @param name heading shown in the report (default "Test data")
|
|
45
|
+
* @example await testData({ username: 'naveen', password: 'S3cret' }, 'Login');
|
|
46
|
+
*/
|
|
47
|
+
async function testData(data, name = 'Test data') {
|
|
48
|
+
await info().attach(name, { body: JSON.stringify(typeof data === 'string' ? { csv: data } : data), contentType: 'application/x-rl-data' });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Record an API request/response for this test. Shows up in the test detail and the API tab.
|
|
52
|
+
* Authorization/Cookie headers, tokens and secret-looking fields are masked.
|
|
53
|
+
* @example
|
|
54
|
+
* await api({ method: 'POST', url: '/v1/orders', status: 201, duration: 138, requestBody, responseBody });
|
|
55
|
+
*/
|
|
56
|
+
async function api(call) {
|
|
57
|
+
await info().attach(call.name ?? `${call.method.toUpperCase()} ${call.url}`, { body: JSON.stringify(call), contentType: 'application/x-rl-api' });
|
|
58
|
+
}
|
|
59
|
+
/** Wrap a Playwright APIRequestContext call so it's recorded automatically. */
|
|
60
|
+
async function recordApi(method, url, options, run) {
|
|
61
|
+
const t0 = Date.now();
|
|
62
|
+
const res = await run();
|
|
63
|
+
let body = await res.text();
|
|
64
|
+
try {
|
|
65
|
+
body = JSON.parse(body);
|
|
66
|
+
}
|
|
67
|
+
catch { /* keep text */ }
|
|
68
|
+
await api({ method, url, status: res.status(), duration: Date.now() - t0, requestHeaders: options?.headers, requestBody: options?.data, responseHeaders: res.headers(), responseBody: body, name: options?.name });
|
|
69
|
+
return res;
|
|
70
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestError } from '@playwright/test/reporter';
|
|
2
|
+
import { ReportingLabsOptions } from './types';
|
|
3
|
+
export default class ReportingLabsReporter implements Reporter {
|
|
4
|
+
private options;
|
|
5
|
+
private config;
|
|
6
|
+
private suite;
|
|
7
|
+
private startTime;
|
|
8
|
+
private outDir;
|
|
9
|
+
private assetsDir;
|
|
10
|
+
private assetCounter;
|
|
11
|
+
private masker;
|
|
12
|
+
private globalErrors;
|
|
13
|
+
private globalOutput;
|
|
14
|
+
constructor(options?: ReportingLabsOptions);
|
|
15
|
+
printsToStdio(): boolean;
|
|
16
|
+
/** Errors outside tests: a spec that throws at load, global setup, a crashed worker. */
|
|
17
|
+
onError(error: TestError): void;
|
|
18
|
+
onStdOut(chunk: string | Buffer, test?: TestCase | void): void;
|
|
19
|
+
onStdErr(chunk: string | Buffer, test?: TestCase | void): void;
|
|
20
|
+
private pushOutput;
|
|
21
|
+
onBegin(config: FullConfig, suite: Suite): void;
|
|
22
|
+
onEnd(result: FullResult): Promise<void>;
|
|
23
|
+
/** Runtime facts for the Environment card: Playwright, Node, OS, browsers, CI job, git commit. */
|
|
24
|
+
private collectEnv;
|
|
25
|
+
private toDataBlock;
|
|
26
|
+
private loadHistory;
|
|
27
|
+
private dimensions;
|
|
28
|
+
/** Pull dimension values from annotations and tags. */
|
|
29
|
+
private extractMeta;
|
|
30
|
+
private rel;
|
|
31
|
+
private serializeError;
|
|
32
|
+
private outcome;
|
|
33
|
+
private titlePath;
|
|
34
|
+
private serializeResult;
|
|
35
|
+
private serializeStep;
|
|
36
|
+
private serializeAttachment;
|
|
37
|
+
}
|