@piwitests/reporter 0.26.0 → 0.27.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/README.md +20 -286
- package/dist/cli/index.js +95 -22
- package/dist/global-setup-module.js +75 -7
- package/dist/index.d.ts +35 -0
- package/dist/index.js +1473 -153
- package/dist/internal/capture/attachments.d.ts +2 -0
- package/dist/internal/capture/attachments.js +2 -0
- package/dist/internal/capture/capture-fixtures.d.ts +8 -1
- package/dist/internal/capture/capture-fixtures.js +156 -4
- package/dist/internal/capture/locator-healing.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Piwi Dashboard Reporter
|
|
2
2
|
|
|
3
|
-
A custom Playwright reporter that sends test results to a [Piwi Dashboard](https://piwitests.dev) server
|
|
3
|
+
A custom Playwright reporter that sends your test results to a [Piwi Dashboard](https://piwitests.dev) server — run history, HTML reports, traces and performance metrics, streamed live as tests execute. With the optional capture fixtures it also unlocks locator healing, slow-endpoint analysis, Web Vitals, console capture and failure-time ARIA snapshots.
|
|
4
4
|
|
|
5
|
-
📖 **[Full documentation](https://piwitests.dev/reporter)**
|
|
5
|
+
📖 **[Full documentation](https://piwitests.dev/guide/reporter)** · 🎮 **[Live demo](https://piwitests.dev/demo/)**
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -18,140 +18,27 @@ From your Playwright project, `npx @piwitests/reporter init` installs the report
|
|
|
18
18
|
npx @piwitests/reporter init --server-url http://localhost:3000 --project my-project
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Every step is idempotent (safe to re-run); a config shape it will not rewrite is reported as `manual` with the exact change to make, never mangled. Add `--dry-run` to preview or `--json` for a machine-readable plan an agent can act on. It also installs the [Piwi agent skills](https://piwitests.dev/mcp#agent-skills) so your coding agent can investigate failures, heal locators, and stabilize flaky tests. Run `npx @piwitests/reporter init --help` for all options, or wire it up by hand with the steps below.
|
|
21
|
+
Every step is idempotent (safe to re-run); a config shape it will not rewrite is reported as `manual` with the exact change to make, never mangled. Add `--dry-run` to preview or `--json` for a machine-readable plan an agent can act on. It also installs the [Piwi agent skills](https://piwitests.dev/features/mcp#agent-skills) so your coding agent can investigate failures, heal locators, and stabilize flaky tests. Run `npx @piwitests/reporter init --help` for all options, or wire it up by hand with the steps below.
|
|
22
22
|
|
|
23
23
|
> The package is `@piwitests/reporter`; its command is `piwi`. Invoke it through the package name (`npx @piwitests/reporter <command>`) so npx resolves this package — `npx piwi` would fetch an unrelated `piwi` from npm. Once the reporter is a project dependency, `npx piwi <command>` resolves the local binary and works too.
|
|
24
24
|
|
|
25
25
|
## Quick start
|
|
26
26
|
|
|
27
|
-
`wrapConfig` is the recommended setup. It injects the reporter **and** a global
|
|
28
|
-
setup step (so the run shows up as "initializing" while your `globalSetup` runs),
|
|
29
|
-
and forwards your options to that setup:
|
|
27
|
+
`wrapConfig` is the recommended setup. It injects the reporter **and** a global setup step (so the run shows up as "initializing" while your `globalSetup` runs), and defaults `use.screenshot` / `use.trace` to `'only-on-failure'` / `'retain-on-failure'` when unset so failure evidence is captured (pass `defaultCapture: false` to opt out):
|
|
30
28
|
|
|
31
29
|
```typescript
|
|
32
30
|
import { defineConfig } from '@playwright/test'
|
|
33
31
|
import { wrapConfig } from '@piwitests/reporter'
|
|
34
32
|
|
|
35
|
-
export default wrapConfig(
|
|
36
|
-
defineConfig({
|
|
37
|
-
use: {
|
|
38
|
-
trace: 'retain-on-failure',
|
|
39
|
-
},
|
|
40
|
-
}),
|
|
41
|
-
{
|
|
42
|
-
serverUrl: 'http://localhost:3000',
|
|
43
|
-
projectName: 'my-project',
|
|
44
|
-
},
|
|
45
|
-
)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Run your tests — results are uploaded automatically:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npx playwright test
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
**Recommended: enable the [capture fixtures](#capture-fixtures)** — one small file unlocks the dashboard's richest features (locator healing, slow-endpoint analysis, Web Vitals, console capture, failure-time ARIA snapshots):
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// tests/fixtures.ts
|
|
58
|
-
import { test as base, expect } from '@playwright/test'
|
|
59
|
-
import { piwiFixtures } from '@piwitests/reporter'
|
|
60
|
-
|
|
61
|
-
export const test = base.extend(piwiFixtures)
|
|
62
|
-
export { expect }
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Import `test` from this file in your specs instead of `@playwright/test` — see [Capture fixtures](#capture-fixtures) below.
|
|
66
|
-
|
|
67
|
-
Prefer to wire it up by hand? Add the reporter to the `reporter` array instead:
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
import { defineConfig } from '@playwright/test'
|
|
71
|
-
|
|
72
|
-
export default defineConfig({
|
|
73
|
-
reporter: [
|
|
74
|
-
['list'],
|
|
75
|
-
['@piwitests/reporter', {
|
|
76
|
-
serverUrl: 'http://localhost:3000',
|
|
77
|
-
projectName: 'my-project',
|
|
78
|
-
}],
|
|
79
|
-
],
|
|
80
|
-
use: {
|
|
81
|
-
trace: 'retain-on-failure',
|
|
82
|
-
},
|
|
83
|
-
})
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Configuration Options
|
|
87
|
-
|
|
88
|
-
| Option | Type | Default | Description |
|
|
89
|
-
|-----------------------------|----------|---------------------------|------------------------------------------------------------------------|
|
|
90
|
-
| `serverUrl` | string | `'http://localhost:3000'` | URL of the Piwi Dashboard server |
|
|
91
|
-
| `projectName` | string | `'default-project'` | Name of the project to report results under |
|
|
92
|
-
| `uploadTraces` | boolean | `true` | Whether to upload trace files to the dashboard |
|
|
93
|
-
| `uploadReport` | boolean | `true` | Whether to upload the HTML report to the dashboard |
|
|
94
|
-
| `reports` | array | — | Additional report types to upload (html, monocart, blob, or custom) |
|
|
95
|
-
| `streaming` | boolean | `true` | Enable live streaming of results as tests complete |
|
|
96
|
-
| `streamingBatchSize` | number | `5` | Number of test results to batch before sending |
|
|
97
|
-
| `streamingBatchDelay` | number | `2000` | Max delay (ms) before flushing pending events |
|
|
98
|
-
| `projectDescription` | string | — | Description of the project |
|
|
99
|
-
| `environment` | string | — | Deployment environment for the run, e.g. `production`, `staging` |
|
|
100
|
-
| `relatedIssue` | string | — | Related issue reference (e.g., "PROJ-123") |
|
|
101
|
-
| `ciInfo` | string | — | CI job information |
|
|
102
|
-
| `tags` | string[] | — | Tags to categorize the test run |
|
|
103
|
-
| `customData` | object | — | Additional custom metadata as key-value pairs |
|
|
104
|
-
| `collectScmInfo` | boolean | `true` | Auto-collect git commit, branch, author |
|
|
105
|
-
| `collectCiInfo` | boolean | `true` | Auto-collect CI environment info |
|
|
106
|
-
| `collectPerformanceMetrics` | boolean | `true` | Collect step timings, network requests and web vitals from the fixture |
|
|
107
|
-
| `outputFile` | string | — | Write a JSON file with the run URL/id/status so CI can consume it (see below) |
|
|
108
|
-
| `apiKey` | string | — | API key for authentication (preferred for CI) |
|
|
109
|
-
| `username` | string | — | Username for dashboard login (use `apiKey` instead when possible) |
|
|
110
|
-
| `password` | string | — | Password for dashboard login (used with `username`) |
|
|
111
|
-
| `verbose` | boolean | `false` | Enable verbose logging for debugging |
|
|
112
|
-
|
|
113
|
-
## Live streaming
|
|
114
|
-
|
|
115
|
-
By default, the reporter streams test results to the dashboard in real-time. This allows you to monitor progress live in the dashboard UI while CI is still running.
|
|
116
|
-
|
|
117
|
-
To disable streaming and send all results at the end:
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
['@piwitests/reporter', {
|
|
33
|
+
export default wrapConfig(defineConfig({}), {
|
|
121
34
|
serverUrl: 'http://localhost:3000',
|
|
122
35
|
projectName: 'my-project',
|
|
123
|
-
streaming: false,
|
|
124
|
-
}]
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
If the server doesn't support streaming (older versions), the reporter automatically falls back to batch mode.
|
|
128
|
-
|
|
129
|
-
## Multiple reports
|
|
130
|
-
|
|
131
|
-
Attach multiple report types to a single test run:
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
export default defineConfig({
|
|
135
|
-
reporter: [
|
|
136
|
-
['list'],
|
|
137
|
-
['@playwright/test/reporter-html', { outputFolder: 'playwright-report' }],
|
|
138
|
-
['monocart-reporter', { name: 'My Tests', outputFile: 'monocart-report/index.html' }],
|
|
139
|
-
['@piwitests/reporter', {
|
|
140
|
-
serverUrl: 'http://localhost:3000',
|
|
141
|
-
projectName: 'my-project',
|
|
142
|
-
reports: [
|
|
143
|
-
{ type: 'html' },
|
|
144
|
-
{ type: 'monocart' },
|
|
145
|
-
{ type: 'blob', dir: 'blob-report', label: 'Blob archive' },
|
|
146
|
-
],
|
|
147
|
-
}],
|
|
148
|
-
],
|
|
149
36
|
})
|
|
150
37
|
```
|
|
151
38
|
|
|
152
|
-
|
|
39
|
+
Run `npx playwright test` — results are uploaded automatically.
|
|
153
40
|
|
|
154
|
-
|
|
41
|
+
**Recommended: enable the capture fixtures.** One small file unlocks the dashboard's richest features:
|
|
155
42
|
|
|
156
43
|
```typescript
|
|
157
44
|
// tests/fixtures.ts
|
|
@@ -162,181 +49,28 @@ export const test = base.extend(piwiFixtures)
|
|
|
162
49
|
export { expect }
|
|
163
50
|
```
|
|
164
51
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
import { test as base } from '@playwright/test'
|
|
169
|
-
import { extendPiwiFixtures } from '@piwitests/reporter'
|
|
170
|
-
|
|
171
|
-
export const test = extendPiwiFixtures(base)
|
|
172
|
-
export { expect } from '@playwright/test'
|
|
173
|
-
```
|
|
52
|
+
Import `test` from this file in your specs instead of `@playwright/test`. A spec that imports from `@playwright/test` directly still runs and reports fine — it just isn't captured. `extendPiwiFixtures(base)` is an equivalent one-line spelling. See the [capture fixtures guide](https://piwitests.dev/guide/capture-fixtures) for the full feature matrix and composition patterns.
|
|
174
53
|
|
|
175
|
-
|
|
54
|
+
## What you get
|
|
176
55
|
|
|
177
|
-
|
|
56
|
+
- **Run history, statuses, errors, traces, reports and live streaming** — with no test-code changes.
|
|
57
|
+
- **Capture fixtures** add slow-endpoint analysis, Web Vitals, console capture, failure-time ARIA snapshots and locator healing.
|
|
58
|
+
- **AI steps** (`page.piwiLocator(...)` / `page.piwiRun(...)`) drive flows in plain English, compiled once and replayed deterministically with zero LLM calls in CI — see [AI steps](https://piwitests.dev/guide/ai-steps).
|
|
59
|
+
- **CI-aware** — auto-detects the run label, branch and commit, publishes the run URL back to the pipeline, and shards into a single run. See [CI & sharding](https://piwitests.dev/guide/ci).
|
|
178
60
|
|
|
179
|
-
|
|
180
|
-
- **Console entries** — `warning`, `error`, and `assert` messages with their source location.
|
|
181
|
-
- **Browser Web Vitals** — TTFB, DOM Interactive, DOMContentLoaded, Load Complete, First Paint, First Contentful Paint — displayed with color-coded thresholds.
|
|
182
|
-
- **ARIA snapshot** — captured automatically when a test fails, shown as failure evidence and fed to the AI diagnosis.
|
|
183
|
-
- **Locator snapshots** — for each element a test proves resolvable (successful actions and passing `expect(locator)` assertions alike), its attributes plus ranked alternative locators, stamped with the call site. These power locator healing; when a failing locator matches nothing, a fresh suggestion is attached as a Playwright annotation.
|
|
61
|
+
## Configuration
|
|
184
62
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
Without the fixtures you still get full run history, statuses, errors, traces, reports, streaming, and clustering — the fixtures add the slow-endpoint, Web Vitals, console, ARIA, and locator-healing layers. See the [capture fixtures guide](https://piwitests.dev/capture-fixtures) for the full feature matrix and composition patterns.
|
|
188
|
-
|
|
189
|
-
## AI steps
|
|
190
|
-
|
|
191
|
-
Locate elements and drive flows in plain English, without giving up determinism:
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
await page.piwiLocator('the email address field').fill('ada@example.com')
|
|
195
|
-
await page.piwiRun('sign in as {email}', { email: 'ada@example.com' })
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
The LLM is a **compiler, not a runtime**: each prompt is resolved **once** by an agent into a committed, deterministic JSON artifact, and every run after that replays that artifact with plain Playwright — **zero LLM calls and zero network** in the default `replay` mode. Add it by composing `extendPiwiAi` over your test:
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
import { extendPiwiFixtures, extendPiwiAi } from '@piwitests/reporter'
|
|
202
|
-
export const test = extendPiwiAi(extendPiwiFixtures(base))
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Author missing entries once in `resolve` mode (`PIWI_AI=resolve`, pointed at a dashboard with an AI provider configured), commit the artifacts, and CI replays them offline. `{param}` placeholders are type-checked and masked out of everything sent to the model. Manage the committed entries with `piwi ai check | resolve | prune`.
|
|
206
|
-
|
|
207
|
-
See the [AI steps guide](https://piwitests.dev/ai-steps) for the authoring/replay lifecycle, the safety model (allowlisted, drift-guarded, postcondition-verified), and the full option/env-var reference.
|
|
208
|
-
|
|
209
|
-
## Authentication
|
|
210
|
-
|
|
211
|
-
When the dashboard has authentication enabled, use an API key (recommended for CI):
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
['@piwitests/reporter', {
|
|
215
|
-
serverUrl: 'https://your-dashboard.example.com',
|
|
216
|
-
projectName: 'my-project',
|
|
217
|
-
apiKey: process.env.PIWI_API_KEY,
|
|
218
|
-
}]
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Generate a key in the dashboard UI: **Settings → Users → API keys**. Store it as a CI secret.
|
|
222
|
-
|
|
223
|
-
Alternatively, use `username`/`password` — the reporter will call `/api/auth/login` automatically.
|
|
224
|
-
|
|
225
|
-
## Automatic Metadata Collection
|
|
226
|
-
|
|
227
|
-
### SCM Information (Git)
|
|
228
|
-
|
|
229
|
-
When `collectScmInfo` is enabled (default), the reporter collects:
|
|
230
|
-
- Commit hash and message
|
|
231
|
-
- Branch name
|
|
232
|
-
- Author name
|
|
233
|
-
- Remote URL
|
|
234
|
-
|
|
235
|
-
### CI Information
|
|
236
|
-
|
|
237
|
-
When `collectCiInfo` is enabled (default), the reporter auto-detects:
|
|
238
|
-
- **GitHub Actions** — run ID, workflow, actor, repository, ref, SHA
|
|
239
|
-
- **Jenkins** — build number, build URL, job name
|
|
240
|
-
- **GitLab CI** — pipeline ID/URL, job ID/URL, job name
|
|
241
|
-
- **CircleCI** — build number/URL, job name, workflow
|
|
242
|
-
- **Travis CI** — build number/URL, job number
|
|
243
|
-
- **Azure Pipelines** — build number, build ID/URL, job name
|
|
244
|
-
|
|
245
|
-
## Publishing the run URL to CI
|
|
246
|
-
|
|
247
|
-
After a run is submitted, the reporter surfaces the dashboard run URL so a later
|
|
248
|
-
CI step (a custom email, a Slack message, a deploy gate) can pick it up without
|
|
249
|
-
scraping the log. The URL is always printed as `View run: <url>`, and in
|
|
250
|
-
addition:
|
|
251
|
-
|
|
252
|
-
- **Any CI — JSON output file.** Set `outputFile` (or `PIWI_OUTPUT_FILE`) and the
|
|
253
|
-
reporter writes a small JSON file when the run lands:
|
|
254
|
-
|
|
255
|
-
```json
|
|
256
|
-
{ "runUrl": "https://piwi.example.com/test-runs/1234", "runId": 1234, "projectId": 5, "projectName": "checkout", "status": "passed", "ciBuildUrl": "https://ci.example.com/build/9" }
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
Read it from any pipeline, e.g. `node -e "console.log(require('./piwi-run.json').runUrl)"`
|
|
260
|
-
(portable) or `cat piwi-run.json` and parse it in your email step. In Jenkins,
|
|
261
|
-
`def run = readJSON file: 'piwi-run.json'` then use `run.runUrl`.
|
|
262
|
-
|
|
263
|
-
- **GitHub Actions (automatic).** When `GITHUB_ACTIONS` is set, the reporter
|
|
264
|
-
appends step outputs to `$GITHUB_OUTPUT` (`piwi_run_url`, `piwi_run_id`,
|
|
265
|
-
`piwi_project_id`, `piwi_run_status`), writes a markdown link to the job
|
|
266
|
-
summary, and prints a `::notice::` annotation. Give the test step an `id` and a
|
|
267
|
-
downstream step can read it:
|
|
268
|
-
|
|
269
|
-
```yaml
|
|
270
|
-
- id: tests
|
|
271
|
-
run: npx playwright test
|
|
272
|
-
- run: echo "Results at ${{ steps.tests.outputs.piwi_run_url }}"
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
- **GitLab CI (automatic).** When `GITLAB_CI` is set, the reporter writes a
|
|
276
|
-
dotenv report (`piwi.env` by default, override with `PIWI_DOTENV_FILE`).
|
|
277
|
-
Declare it so later jobs inherit `$PIWI_RUN_URL`:
|
|
278
|
-
|
|
279
|
-
```yaml
|
|
280
|
-
test:
|
|
281
|
-
script: npx playwright test
|
|
282
|
-
artifacts:
|
|
283
|
-
reports:
|
|
284
|
-
dotenv: piwi.env
|
|
285
|
-
email:
|
|
286
|
-
needs: [test]
|
|
287
|
-
script: ./send-email.sh "$PIWI_RUN_URL"
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
## How It Works
|
|
291
|
-
|
|
292
|
-
1. When tests start, the reporter creates a run on the server (streaming mode) or collects results locally (batch mode)
|
|
293
|
-
2. As tests complete, results are streamed in batches to the server
|
|
294
|
-
3. After all tests finish, HTML reports are compressed and uploaded
|
|
295
|
-
4. Trace files from test attachments are uploaded
|
|
296
|
-
5. Data from the capture fixtures (network requests, console entries, web vitals, ARIA snapshots, locator snapshots) is included per test case
|
|
297
|
-
6. The server stores everything and makes it available in the dashboard UI
|
|
63
|
+
Every option can also be set via a `PIWI_*` environment variable (config wins over env). The full option and env-var reference lives in the [reporter documentation](https://piwitests.dev/guide/reporter); authentication for CI (API keys) is covered under [Authentication](https://piwitests.dev/operate/authentication).
|
|
298
64
|
|
|
299
65
|
## Requirements
|
|
300
66
|
|
|
301
|
-
- Node.js
|
|
302
|
-
- Playwright Test 1.
|
|
303
|
-
-
|
|
304
|
-
|
|
305
|
-
## Development
|
|
306
|
-
|
|
307
|
-
This package is written in TypeScript. Source files live in `src/` and compile to `dist/`.
|
|
308
|
-
|
|
309
|
-
```bash
|
|
310
|
-
cd reporter
|
|
311
|
-
npm install
|
|
312
|
-
npm run reporter:build # compile TypeScript src/ → dist/
|
|
313
|
-
npm run reporter:dev # watch mode — auto-recompile on changes
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### Source layout
|
|
317
|
-
|
|
318
|
-
The package keeps its **public API** (`src/index.ts`, `src/public/`) separate from internal plumbing (`src/internal/<domain>/`) and the type model (`src/types/`). See [`ARCHITECTURE.md`](./ARCHITECTURE.md) for the full map — the public/internal split, the collect-and-submit data flow, the fallback ladder, and the conventions.
|
|
319
|
-
|
|
320
|
-
Everything public — the reporter, config helpers, and the capture fixtures — is exported from the package's single entry point (`@piwitests/reporter`).
|
|
321
|
-
|
|
322
|
-
## Troubleshooting
|
|
323
|
-
|
|
324
|
-
### Reporter not uploading files
|
|
325
|
-
|
|
326
|
-
- Ensure an HTML reporter is configured: `['html', { outputFolder: 'playwright-report' }]`
|
|
327
|
-
- Ensure traces are enabled: `use: { trace: 'retain-on-failure' }`
|
|
328
|
-
- Check the dashboard server is running and accessible at `serverUrl`
|
|
329
|
-
|
|
330
|
-
### Fixture data not appearing (network, Web Vitals, console, ARIA, locator healing)
|
|
331
|
-
|
|
332
|
-
- Extend your `test` with `piwiFixtures` / `extendPiwiFixtures` from `@piwitests/reporter`, and import `test` from your fixtures file in every spec — not from `@playwright/test` directly
|
|
333
|
-
- Verify `collectPerformanceMetrics` is not set to `false` (and `captureLocators` for locator healing)
|
|
334
|
-
- Ensure tests navigate to at least one page (`await page.goto(...)`)
|
|
67
|
+
- Node.js 20 or higher (the reporter runs inside your test project — the dashboard *server* itself targets Node 22+, or use its Docker image)
|
|
68
|
+
- Playwright Test 1.61 or higher
|
|
69
|
+
- A running Piwi Dashboard server
|
|
335
70
|
|
|
336
|
-
|
|
71
|
+
## Contributing
|
|
337
72
|
|
|
338
|
-
-
|
|
339
|
-
- Verify network connectivity and firewall settings
|
|
73
|
+
Source layout, the collect-and-submit data flow and the public/internal split are documented in [`ARCHITECTURE.md`](./ARCHITECTURE.md). Build with `npm run reporter:build` (or `reporter:dev` for watch mode) from the repository root.
|
|
340
74
|
|
|
341
75
|
## License
|
|
342
76
|
|
package/dist/cli/index.js
CHANGED
|
@@ -477,9 +477,9 @@ function readCount(argv, name) {
|
|
|
477
477
|
if (!Number.isFinite(n) || n < 0) throw new Error(`${name} expects a non-negative number, got "${raw}"`);
|
|
478
478
|
return Math.floor(n);
|
|
479
479
|
}
|
|
480
|
-
function readRunIdFromFile(
|
|
480
|
+
function readRunIdFromFile(path11) {
|
|
481
481
|
try {
|
|
482
|
-
const parsed = JSON.parse(fs3.readFileSync(
|
|
482
|
+
const parsed = JSON.parse(fs3.readFileSync(path11, "utf-8"));
|
|
483
483
|
const runId = Number(parsed.runId);
|
|
484
484
|
return Number.isFinite(runId) && runId > 0 ? runId : null;
|
|
485
485
|
} catch {
|
|
@@ -1196,10 +1196,10 @@ Piwi setup${opts.dryRun ? " (dry run \u2014 nothing written)" : ""} for "${opts.
|
|
|
1196
1196
|
}
|
|
1197
1197
|
|
|
1198
1198
|
// src/cli/select.ts
|
|
1199
|
-
var
|
|
1200
|
-
var
|
|
1199
|
+
var fs8 = __toESM(require("fs"));
|
|
1200
|
+
var path10 = __toESM(require("path"));
|
|
1201
1201
|
var import_node_child_process3 = require("child_process");
|
|
1202
|
-
var
|
|
1202
|
+
var import_node_module2 = require("module");
|
|
1203
1203
|
|
|
1204
1204
|
// src/internal/support/selection-client.ts
|
|
1205
1205
|
function authHeaders(apiKey) {
|
|
@@ -1248,6 +1248,74 @@ async function fetchImpact(options, projectId, changedFiles) {
|
|
|
1248
1248
|
return await res.json();
|
|
1249
1249
|
}
|
|
1250
1250
|
|
|
1251
|
+
// src/cli/add-reporter.ts
|
|
1252
|
+
var fs7 = __toESM(require("fs"));
|
|
1253
|
+
var path9 = __toESM(require("path"));
|
|
1254
|
+
var import_node_module = require("module");
|
|
1255
|
+
var REPORTER_PACKAGE2 = "@piwitests/reporter";
|
|
1256
|
+
var ADD_REPORTER_FLAG = "--add-reporter";
|
|
1257
|
+
var ADD_REPORTER_MIN = { major: 1, minor: 63 };
|
|
1258
|
+
function playwrightSupportsAddReporter(version) {
|
|
1259
|
+
const match = version ? /^(\d+)\.(\d+)/.exec(version) : null;
|
|
1260
|
+
if (!match) return false;
|
|
1261
|
+
const major = Number(match[1]);
|
|
1262
|
+
const minor = Number(match[2]);
|
|
1263
|
+
return major > ADD_REPORTER_MIN.major || major === ADD_REPORTER_MIN.major && minor >= ADD_REPORTER_MIN.minor;
|
|
1264
|
+
}
|
|
1265
|
+
function configHasPiwiReporter(source) {
|
|
1266
|
+
return new RegExp(REPORTER_PACKAGE2.replace(/[/\\]/g, "\\$&")).test(source);
|
|
1267
|
+
}
|
|
1268
|
+
function decideAddReporter(configSource, playwrightVersion) {
|
|
1269
|
+
if (configSource === null || configHasPiwiReporter(configSource)) return { args: [], log: null };
|
|
1270
|
+
if (playwrightSupportsAddReporter(playwrightVersion)) {
|
|
1271
|
+
return {
|
|
1272
|
+
args: [ADD_REPORTER_FLAG, REPORTER_PACKAGE2],
|
|
1273
|
+
log: `piwi run: the Playwright config has no Piwi reporter \u2014 appending ${ADD_REPORTER_FLAG} ${REPORTER_PACKAGE2}`
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
return {
|
|
1277
|
+
args: [],
|
|
1278
|
+
log: `piwi run: the Playwright config has no Piwi reporter and Playwright ${playwrightVersion ?? "unknown"} predates ${ADD_REPORTER_FLAG} (1.63) \u2014 results will not reach the dashboard`
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
function resolveConfigPath(cwd, playwrightArgs) {
|
|
1282
|
+
for (let i = 0; i < playwrightArgs.length; i++) {
|
|
1283
|
+
const arg = playwrightArgs[i];
|
|
1284
|
+
if (arg === "--config" || arg === "-c") {
|
|
1285
|
+
const next = playwrightArgs[i + 1];
|
|
1286
|
+
if (next) return path9.resolve(cwd, next);
|
|
1287
|
+
} else if (arg.startsWith("--config=")) {
|
|
1288
|
+
return path9.resolve(cwd, arg.slice("--config=".length));
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return detectProject(cwd).configPath;
|
|
1292
|
+
}
|
|
1293
|
+
function readConfigSource(configPath) {
|
|
1294
|
+
if (!configPath) return null;
|
|
1295
|
+
try {
|
|
1296
|
+
return fs7.readFileSync(configPath, "utf-8");
|
|
1297
|
+
} catch {
|
|
1298
|
+
return null;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
function readInstalledPlaywrightVersion(cwd) {
|
|
1302
|
+
try {
|
|
1303
|
+
const require2 = (0, import_node_module.createRequire)(path9.join(cwd, "noop.js"));
|
|
1304
|
+
for (const id of ["@playwright/test/package.json", "playwright/package.json"]) {
|
|
1305
|
+
try {
|
|
1306
|
+
return require2(id).version ?? null;
|
|
1307
|
+
} catch {
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
} catch {
|
|
1311
|
+
}
|
|
1312
|
+
return null;
|
|
1313
|
+
}
|
|
1314
|
+
function computeAddReporterArgs(cwd, playwrightArgs) {
|
|
1315
|
+
const source = readConfigSource(resolveConfigPath(cwd, playwrightArgs));
|
|
1316
|
+
return decideAddReporter(source, readInstalledPlaywrightVersion(cwd));
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1251
1319
|
// src/cli/select.ts
|
|
1252
1320
|
var EXIT_OK3 = 0;
|
|
1253
1321
|
var EXIT_ERROR3 = 2;
|
|
@@ -1267,7 +1335,7 @@ Connection:
|
|
|
1267
1335
|
Selection:
|
|
1268
1336
|
--format <fmt> args (file:line, default) | grep | files | json
|
|
1269
1337
|
--budget <duration> Cap total time, e.g. 5m, 90s, 300000 (ms)
|
|
1270
|
-
--shard <i/n> Keep only shard i of n, balanced by
|
|
1338
|
+
--shard <i/n> Keep only shard i of n, balanced by duration, lock-aware
|
|
1271
1339
|
--fail-fast Order the least-reliable tests first (fail-fast)
|
|
1272
1340
|
--base <ref> For "impact": the ref to diff the working tree against
|
|
1273
1341
|
|
|
@@ -1352,13 +1420,13 @@ function parseSelectArgs(argv, env) {
|
|
|
1352
1420
|
extra
|
|
1353
1421
|
};
|
|
1354
1422
|
}
|
|
1355
|
-
var CACHE_FILE =
|
|
1423
|
+
var CACHE_FILE = path10.join(".piwi", "selection-cache.json");
|
|
1356
1424
|
function cacheKey(projectId, args) {
|
|
1357
1425
|
return `${projectId}:${args.key}:${args.format}:${args.budgetMs ?? 0}:${args.shard ?? ""}:${args.order ?? ""}`;
|
|
1358
1426
|
}
|
|
1359
1427
|
function readCache(projectId, args) {
|
|
1360
1428
|
try {
|
|
1361
|
-
const store = JSON.parse(
|
|
1429
|
+
const store = JSON.parse(fs8.readFileSync(CACHE_FILE, "utf-8"));
|
|
1362
1430
|
return store[cacheKey(projectId, args)] ?? null;
|
|
1363
1431
|
} catch {
|
|
1364
1432
|
return null;
|
|
@@ -1368,12 +1436,12 @@ function writeCache(projectId, args, resolution) {
|
|
|
1368
1436
|
try {
|
|
1369
1437
|
let store = {};
|
|
1370
1438
|
try {
|
|
1371
|
-
store = JSON.parse(
|
|
1439
|
+
store = JSON.parse(fs8.readFileSync(CACHE_FILE, "utf-8"));
|
|
1372
1440
|
} catch {
|
|
1373
1441
|
}
|
|
1374
1442
|
store[cacheKey(projectId, args)] = resolution;
|
|
1375
|
-
|
|
1376
|
-
|
|
1443
|
+
fs8.mkdirSync(path10.dirname(CACHE_FILE), { recursive: true });
|
|
1444
|
+
fs8.writeFileSync(CACHE_FILE, JSON.stringify(store, null, 2));
|
|
1377
1445
|
} catch {
|
|
1378
1446
|
}
|
|
1379
1447
|
}
|
|
@@ -1469,7 +1537,7 @@ async function runSelect(argv, env = process.env) {
|
|
|
1469
1537
|
return EXIT_OK3;
|
|
1470
1538
|
}
|
|
1471
1539
|
function resolvePlaywrightCli() {
|
|
1472
|
-
const require2 = (0,
|
|
1540
|
+
const require2 = (0, import_node_module2.createRequire)(path10.join(process.cwd(), "noop.js"));
|
|
1473
1541
|
for (const id of ["playwright/cli", "@playwright/test/cli", "playwright/lib/cli/cli"]) {
|
|
1474
1542
|
try {
|
|
1475
1543
|
return require2.resolve(id);
|
|
@@ -1484,14 +1552,19 @@ function spawnPlaywright(pkgRunner, playwrightArgs, env) {
|
|
|
1484
1552
|
stdio: "inherit",
|
|
1485
1553
|
env
|
|
1486
1554
|
});
|
|
1487
|
-
return new Promise((
|
|
1555
|
+
return new Promise((resolve6) => {
|
|
1488
1556
|
child.on("error", (err) => {
|
|
1489
1557
|
console.error(`piwi run: could not start Playwright \u2014 ${err.message}`);
|
|
1490
|
-
|
|
1558
|
+
resolve6(EXIT_ERROR3);
|
|
1491
1559
|
});
|
|
1492
|
-
child.on("exit", (code) =>
|
|
1560
|
+
child.on("exit", (code) => resolve6(code ?? EXIT_ERROR3));
|
|
1493
1561
|
});
|
|
1494
1562
|
}
|
|
1563
|
+
function spawnPlaywrightForRun(pkgRunner, playwrightArgs, env) {
|
|
1564
|
+
const decision = computeAddReporterArgs(process.cwd(), playwrightArgs);
|
|
1565
|
+
if (decision.log) console.error(decision.log);
|
|
1566
|
+
return spawnPlaywright(pkgRunner, [...decision.args, ...playwrightArgs], env);
|
|
1567
|
+
}
|
|
1495
1568
|
async function runRunImpact(args, env) {
|
|
1496
1569
|
let projectId;
|
|
1497
1570
|
try {
|
|
@@ -1502,7 +1575,7 @@ async function runRunImpact(args, env) {
|
|
|
1502
1575
|
return EXIT_ERROR3;
|
|
1503
1576
|
}
|
|
1504
1577
|
console.error(`piwi run: ${e.message} \u2014 running the full suite`);
|
|
1505
|
-
return
|
|
1578
|
+
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
1506
1579
|
}
|
|
1507
1580
|
let impact;
|
|
1508
1581
|
try {
|
|
@@ -1513,14 +1586,14 @@ async function runRunImpact(args, env) {
|
|
|
1513
1586
|
return EXIT_ERROR3;
|
|
1514
1587
|
}
|
|
1515
1588
|
console.error(`piwi run: ${e.message} \u2014 running the full suite`);
|
|
1516
|
-
return
|
|
1589
|
+
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
1517
1590
|
}
|
|
1518
1591
|
printWarnings(impact);
|
|
1519
1592
|
if (impact.impact.widened) {
|
|
1520
1593
|
console.error(
|
|
1521
1594
|
`piwi run: impact widened to the full suite (${impact.impact.unmappedSourceFiles.length} unmapped source file(s))`
|
|
1522
1595
|
);
|
|
1523
|
-
return
|
|
1596
|
+
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
1524
1597
|
}
|
|
1525
1598
|
if (impact.estimate.count === 0) {
|
|
1526
1599
|
console.error(`piwi run: no tests impacted by ${impact.impact.changedFiles} changed file(s) \u2014 nothing to run`);
|
|
@@ -1534,7 +1607,7 @@ async function runRunImpact(args, env) {
|
|
|
1534
1607
|
PIWI_SELECTION_COUNT: String(impact.estimate.count)
|
|
1535
1608
|
};
|
|
1536
1609
|
console.error(`piwi run: impact \u2192 ${impact.estimate.count} test(s)`);
|
|
1537
|
-
return
|
|
1610
|
+
return spawnPlaywrightForRun(args.pkgRunner, [...impact.materialization.args, ...args.extra], runEnv);
|
|
1538
1611
|
}
|
|
1539
1612
|
async function runRun(argv, env = process.env) {
|
|
1540
1613
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
@@ -1570,7 +1643,7 @@ async function runRun(argv, env = process.env) {
|
|
|
1570
1643
|
return EXIT_ERROR3;
|
|
1571
1644
|
}
|
|
1572
1645
|
console.error(`piwi run: ${e.message} \u2014 running the full suite`);
|
|
1573
|
-
return
|
|
1646
|
+
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
1574
1647
|
}
|
|
1575
1648
|
let outcome;
|
|
1576
1649
|
try {
|
|
@@ -1581,7 +1654,7 @@ async function runRun(argv, env = process.env) {
|
|
|
1581
1654
|
}
|
|
1582
1655
|
if (!outcome) {
|
|
1583
1656
|
console.error("piwi run: dashboard unreachable and no cached resolution \u2014 running the full suite");
|
|
1584
|
-
return
|
|
1657
|
+
return spawnPlaywrightForRun(args.pkgRunner, args.extra, env);
|
|
1585
1658
|
}
|
|
1586
1659
|
const { resolution } = outcome;
|
|
1587
1660
|
printWarnings(resolution);
|
|
@@ -1599,7 +1672,7 @@ async function runRun(argv, env = process.env) {
|
|
|
1599
1672
|
console.error(
|
|
1600
1673
|
`piwi run: ${args.key} \u2192 ${resolution.estimate.count} tests${resolution.materialization.format !== args.format ? ` (materialized as ${resolution.materialization.format})` : ""}`
|
|
1601
1674
|
);
|
|
1602
|
-
return
|
|
1675
|
+
return spawnPlaywrightForRun(args.pkgRunner, [...resolution.materialization.args, ...args.extra], runEnv);
|
|
1603
1676
|
}
|
|
1604
1677
|
|
|
1605
1678
|
// src/cli/index.ts
|