@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 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. It handles uploading test results, HTML reports, trace files, and performance metrics with optional live streaming of results as tests execute.
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
- ## Capture fixtures
39
+ Run `npx playwright test` — results are uploaded automatically.
153
40
 
154
- The reporter works without any test-code changes, but the **capture fixtures** observe your tests from the inside and unlock the dashboard's richest features. Extend your `test` with them:
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
- Or extend the base `test` in one line with `extendPiwiFixtures`:
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
- Then import `test` from your fixtures file in every spec — a spec that imports `test` from `@playwright/test` directly still runs and reports fine, it just isn't captured.
54
+ ## What you get
176
55
 
177
- ### What gets captured
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
- - **Network requests** — method, URL, status, duration, resource type (API/document traffic only). Aggregated on the dashboard into a *Slow API Endpoints* table grouped by `METHOD + normalized route`.
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
- Capture works for the `page` fixture, `browser.newPage()`, `browser.newContext().newPage()`, and popups. Everything is only collected when `collectPerformanceMetrics` is `true` (the default); locator snapshots can be disabled separately with `captureLocators: false`.
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 18 or higher (the reporter runs inside your test project — the dashboard *server* itself targets Node 22+, or use its Docker image)
302
- - Playwright Test 1.40 or higher
303
- - Running Piwi Dashboard server
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
- ### Connection errors
71
+ ## Contributing
337
72
 
338
- - Check that `serverUrl` is correct and the server is running
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(path10) {
480
+ function readRunIdFromFile(path11) {
481
481
  try {
482
- const parsed = JSON.parse(fs3.readFileSync(path10, "utf-8"));
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 fs7 = __toESM(require("fs"));
1200
- var path9 = __toESM(require("path"));
1199
+ var fs8 = __toESM(require("fs"));
1200
+ var path10 = __toESM(require("path"));
1201
1201
  var import_node_child_process3 = require("child_process");
1202
- var import_node_module = require("module");
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 test duration
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 = path9.join(".piwi", "selection-cache.json");
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(fs7.readFileSync(CACHE_FILE, "utf-8"));
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(fs7.readFileSync(CACHE_FILE, "utf-8"));
1439
+ store = JSON.parse(fs8.readFileSync(CACHE_FILE, "utf-8"));
1372
1440
  } catch {
1373
1441
  }
1374
1442
  store[cacheKey(projectId, args)] = resolution;
1375
- fs7.mkdirSync(path9.dirname(CACHE_FILE), { recursive: true });
1376
- fs7.writeFileSync(CACHE_FILE, JSON.stringify(store, null, 2));
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, import_node_module.createRequire)(path9.join(process.cwd(), "noop.js"));
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((resolve5) => {
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
- resolve5(EXIT_ERROR3);
1558
+ resolve6(EXIT_ERROR3);
1491
1559
  });
1492
- child.on("exit", (code) => resolve5(code ?? EXIT_ERROR3));
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 spawnPlaywright(args.pkgRunner, args.extra, env);
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 spawnPlaywright(args.pkgRunner, args.extra, env);
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 spawnPlaywright(args.pkgRunner, args.extra, env);
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 spawnPlaywright(args.pkgRunner, [...impact.materialization.args, ...args.extra], runEnv);
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 spawnPlaywright(args.pkgRunner, args.extra, env);
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 spawnPlaywright(args.pkgRunner, args.extra, env);
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 spawnPlaywright(args.pkgRunner, [...resolution.materialization.args, ...args.extra], runEnv);
1675
+ return spawnPlaywrightForRun(args.pkgRunner, [...resolution.materialization.args, ...args.extra], runEnv);
1603
1676
  }
1604
1677
 
1605
1678
  // src/cli/index.ts