@piwitests/reporter 0.61.0 → 0.63.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.
@@ -131,6 +131,16 @@ declare function renderFailing(failed: FailedLocatorInfo): string;
131
131
  * present (so the failure wasn't a rename), or when no candidate is confident.
132
132
  */
133
133
  declare function suggestLocatorsFromAria(failed: FailedLocatorInfo, ariaSnapshot: string | null): LocatorSuggestion | null;
134
+ /**
135
+ * The local path a stack frame's file names: a `file://` URL (an ES-module suite's frames) through `fileURLToPath`,
136
+ * any other path as it is. `windows` reads the URL as Windows does. Null for a URL that names no local file.
137
+ */
138
+ declare function framePath(file: string, windows?: boolean): string | null;
139
+ /**
140
+ * A stack frame's file relative to `cwd`, with forward slashes: the form Playwright writes a location in its error
141
+ * messages. A file on another drive stays absolute. Null for a URL that names no local file.
142
+ */
143
+ declare function callSitePath(file: string, cwd?: string, windows?: boolean): string | null;
134
144
  /**
135
145
  * Capture the calling test's source location (`file:line:col`) from the current
136
146
  * stack, for stamping onto a locator snapshot. The path is made cwd-relative so
@@ -147,4 +157,4 @@ declare function suggestLocatorsFromAria(failed: FailedLocatorInfo, ariaSnapshot
147
157
  */
148
158
  declare function captureCallerLocation(stack?: string): string | null;
149
159
 
150
- export { ACTION_METHODS, CHAIN_METHODS, EXPECT_CAPTURE_EXPRESSIONS, EXPECT_METHOD, FORM_FIELD_TAGS, type FailedLocatorInfo, LOCATOR_CREATING_CHAINS, LOCATOR_METHODS, type LocatorSuggestion, type NameSource, captureCallerLocation, dedupeSnapshotsByLocation, exactAccessibleName, extractAccessibleName, renderFailing, suggestLocatorsFromAria };
160
+ export { ACTION_METHODS, CHAIN_METHODS, EXPECT_CAPTURE_EXPRESSIONS, EXPECT_METHOD, FORM_FIELD_TAGS, type FailedLocatorInfo, LOCATOR_CREATING_CHAINS, LOCATOR_METHODS, type LocatorSuggestion, type NameSource, callSitePath, captureCallerLocation, dedupeSnapshotsByLocation, exactAccessibleName, extractAccessibleName, framePath, renderFailing, suggestLocatorsFromAria };
@@ -41,11 +41,13 @@ __export(locator_healing_exports, {
41
41
  LOCATOR_METHODS: () => LOCATOR_METHODS,
42
42
  TAG_TO_ROLE: () => TAG_TO_ROLE,
43
43
  approximateAccessibleName: () => approximateAccessibleName,
44
+ callSitePath: () => callSitePath,
44
45
  captureCallerLocation: () => captureCallerLocation,
45
46
  classifyCssStability: () => classifyCssStability,
46
47
  dedupeSnapshotsByLocation: () => dedupeSnapshotsByLocation,
47
48
  exactAccessibleName: () => exactAccessibleName,
48
49
  extractAccessibleName: () => extractAccessibleName,
50
+ framePath: () => framePath,
49
51
  generateAlternatives: () => generateAlternatives,
50
52
  headingLevel: () => headingLevel,
51
53
  isAutoGenerated: () => isAutoGenerated,
@@ -55,6 +57,7 @@ __export(locator_healing_exports, {
55
57
  });
56
58
  module.exports = __toCommonJS(locator_healing_exports);
57
59
  var path = __toESM(require("path"));
60
+ var import_node_url = require("url");
58
61
 
59
62
  // ../core/src/locator-chain.ts
60
63
  var LOCATING_METHODS = /* @__PURE__ */ new Set([
@@ -744,6 +747,20 @@ function suggestLocatorsFromAria(failed, ariaSnapshot) {
744
747
  if (suggestions.length === 0) return null;
745
748
  return { failing: renderFailing(failed), suggestions };
746
749
  }
750
+ function framePath(file, windows = process.platform === "win32") {
751
+ if (!file.startsWith("file://")) return file;
752
+ try {
753
+ return (0, import_node_url.fileURLToPath)(file, { windows });
754
+ } catch {
755
+ return null;
756
+ }
757
+ }
758
+ function callSitePath(file, cwd = process.cwd(), windows = process.platform === "win32") {
759
+ const local = framePath(file, windows);
760
+ if (local === null) return null;
761
+ const paths = windows ? path.win32 : path.posix;
762
+ return paths.relative(cwd, local).split(paths.sep).join("/");
763
+ }
747
764
  function captureCallerLocation(stack = new Error().stack ?? "") {
748
765
  const lines = stack.split("\n");
749
766
  let prevWasCaptureModule = false;
@@ -756,13 +773,8 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
756
773
  prevWasCaptureModule = false;
757
774
  continue;
758
775
  }
759
- let file = m[2];
760
- if (!file || file.startsWith("node:")) {
761
- prevWasCaptureModule = false;
762
- continue;
763
- }
764
- file = file.replace(/^file:\/\/\/?/, "");
765
- if (!/\.[a-z]+$/i.test(file)) {
776
+ const file = m[2] ? framePath(m[2]) : null;
777
+ if (!file || file.startsWith("node:") || !/\.[a-z]+$/i.test(file)) {
766
778
  prevWasCaptureModule = false;
767
779
  continue;
768
780
  }
@@ -785,11 +797,9 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
785
797
  }
786
798
  let rel = file;
787
799
  try {
788
- rel = path.relative(process.cwd(), file);
800
+ rel = callSitePath(file) ?? file;
789
801
  } catch {
790
802
  }
791
- rel = rel.split(path.sep).join("/");
792
- if (rel.startsWith("./")) rel = rel.slice(2);
793
803
  return `${rel}:${m[3]}:${m[4]}`;
794
804
  }
795
805
  return null;
@@ -807,11 +817,13 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
807
817
  LOCATOR_METHODS,
808
818
  TAG_TO_ROLE,
809
819
  approximateAccessibleName,
820
+ callSitePath,
810
821
  captureCallerLocation,
811
822
  classifyCssStability,
812
823
  dedupeSnapshotsByLocation,
813
824
  exactAccessibleName,
814
825
  extractAccessibleName,
826
+ framePath,
815
827
  generateAlternatives,
816
828
  headingLevel,
817
829
  isAutoGenerated,
@@ -880,13 +880,17 @@ function showAnchorPicker(arg) {
880
880
  }
881
881
  const MONO = "ui-monospace,SFMono-Regular,Menlo,Consolas,monospace";
882
882
  const outline = doc.createElement("div");
883
+ outline.id = "__piwi_anchor_outline";
883
884
  outline.style.cssText = `position:fixed;pointer-events:none;z-index:${Z};box-sizing:border-box;border:2px solid #22c55e;background:rgba(34,197,94,.10);border-radius:4px;display:none;box-shadow:0 0 0 1px rgba(255,255,255,.9),0 0 0 3px rgba(5,46,22,.5);`;
884
885
  const outlineLabel = doc.createElement("div");
886
+ outlineLabel.id = "__piwi_anchor_label";
885
887
  outlineLabel.style.cssText = `position:fixed;pointer-events:none;z-index:${Z + 1};display:none;box-sizing:border-box;max-width:min(420px,90vw);background:#0b1120;color:#f9fafb;border:1px solid #22c55e;border-radius:6px;padding:3px 7px;font:12px/1.45 ${MONO};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-shadow:0 4px 18px rgba(0,0,0,.5);`;
886
888
  const pickedOutline = doc.createElement("div");
889
+ pickedOutline.id = "__piwi_anchor_picked";
887
890
  const pr = el.getBoundingClientRect();
888
891
  pickedOutline.style.cssText = `position:fixed;pointer-events:none;z-index:${Z};box-sizing:border-box;border:2px solid #a855f7;background:rgba(168,85,247,.14);border-radius:4px;box-shadow:0 0 0 1px rgba(255,255,255,.9),0 0 0 3px rgba(59,7,100,.55);left:${pr.left}px;top:${pr.top}px;width:${pr.width}px;height:${pr.height}px;`;
889
892
  const panel = doc.createElement("div");
893
+ panel.id = "__piwi_anchor_panel";
890
894
  panel.style.cssText = `position:fixed;top:12px;right:12px;z-index:${Z + 3};width:min(340px,calc(100vw - 56px));max-height:82vh;overflow:auto;background:#111827;color:#f9fafb;border-radius:10px;padding:16px;font:12px/1.5 system-ui,sans-serif;box-shadow:0 8px 40px rgba(0,0,0,.5);`;
891
895
  const title = doc.createElement("div");
892
896
  title.style.cssText = "font-weight:600;font-size:13px;margin-bottom:2px;";
@@ -1075,6 +1079,7 @@ function showPickerChoices(arg) {
1075
1079
  }
1076
1080
  const Z = 2147483600;
1077
1081
  const wrap = doc.createElement("div");
1082
+ wrap.id = "__piwi_picker_choices";
1078
1083
  wrap.style.cssText = `position:fixed;inset:0;z-index:${Z + 3};background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;font:13px/1.5 system-ui,sans-serif;`;
1079
1084
  const panel = doc.createElement("div");
1080
1085
  panel.style.cssText = "background:#111827;color:#f9fafb;border-radius:10px;padding:20px;max-width:640px;width:90vw;max-height:70vh;overflow:auto;box-shadow:0 8px 40px rgba(0,0,0,.5);";
@@ -1105,10 +1110,14 @@ function showPickerChoices(arg) {
1105
1110
  }
1106
1111
  panel.appendChild(title);
1107
1112
  panel.appendChild(sub);
1108
- const done = (choice) => {
1109
- g.__piwiPickChoice = choice;
1113
+ const cleanup = () => {
1110
1114
  doc.removeEventListener("keydown", onKey, true);
1111
1115
  wrap.remove();
1116
+ if (g.__piwiChoiceCleanup === cleanup) delete g.__piwiChoiceCleanup;
1117
+ };
1118
+ const done = (choice) => {
1119
+ g.__piwiPickChoice = choice;
1120
+ cleanup();
1112
1121
  };
1113
1122
  const onKey = (e) => {
1114
1123
  if (e.key !== "Escape") return;
@@ -1144,6 +1153,7 @@ function showPickerChoices(arg) {
1144
1153
  });
1145
1154
  panel.appendChild(skip);
1146
1155
  doc.addEventListener("keydown", onKey, true);
1156
+ g.__piwiChoiceCleanup = cleanup;
1147
1157
  wrap.appendChild(panel);
1148
1158
  doc.body.appendChild(wrap);
1149
1159
  }
@@ -1569,6 +1579,7 @@ function mergeCandidates(base, extra) {
1569
1579
 
1570
1580
  // src/internal/capture/locator-healing.ts
1571
1581
  var path = __toESM(require("path"));
1582
+ var import_node_url = require("url");
1572
1583
 
1573
1584
  // ../core/src/locator-fingerprint.ts
1574
1585
  var FUNCTION_WORDS = new Set(
@@ -1625,8 +1636,10 @@ async function cleanupPicker(page, ended = false) {
1625
1636
  const g = globalThis;
1626
1637
  if (typeof g.__piwiPickCleanup === "function") g.__piwiPickCleanup();
1627
1638
  if (typeof g.__piwiAnchorCleanup === "function") g.__piwiAnchorCleanup();
1639
+ if (typeof g.__piwiChoiceCleanup === "function") g.__piwiChoiceCleanup();
1628
1640
  delete g.__piwiPickCleanup;
1629
1641
  delete g.__piwiAnchorCleanup;
1642
+ delete g.__piwiChoiceCleanup;
1630
1643
  delete g.__piwiPickState;
1631
1644
  delete g.__piwiPickChoice;
1632
1645
  delete g.__piwiPickedElement;
@@ -1652,13 +1665,24 @@ function unlessEnded(run, ended) {
1652
1665
  ]);
1653
1666
  }
1654
1667
  async function pickLocator(page, probe, failing, ended) {
1668
+ let polling = false;
1655
1669
  const answered = async (name) => {
1656
1670
  const wait = page.waitForFunction(
1657
- ([key, endedKey]) => globalThis[key] !== void 0 || globalThis[endedKey] === true,
1671
+ ([key, endedKey]) => {
1672
+ const g = globalThis;
1673
+ if (g[endedKey] !== true) return g[key] !== void 0;
1674
+ delete g[endedKey];
1675
+ return true;
1676
+ },
1658
1677
  [name, PICK_ENDED],
1659
1678
  { timeout: 0, polling: 250 }
1660
1679
  );
1661
- await unlessEnded(wait, ended);
1680
+ polling = true;
1681
+ const done = await unlessEnded(
1682
+ wait.then(() => true),
1683
+ ended
1684
+ );
1685
+ if (done) polling = false;
1662
1686
  return !ended?.aborted;
1663
1687
  };
1664
1688
  try {
@@ -1727,7 +1751,7 @@ async function pickLocator(page, probe, failing, ended) {
1727
1751
  ...anchors.length > 0 ? { anchors } : {}
1728
1752
  };
1729
1753
  } finally {
1730
- await cleanupPicker(page, ended?.aborted);
1754
+ await cleanupPicker(page, polling);
1731
1755
  }
1732
1756
  }
1733
1757
  async function runLocatorPicker(page, testInfo, failed, probe) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piwitests/reporter",
3
- "version": "0.61.0",
3
+ "version": "0.63.0",
4
4
  "description": "Playwright reporter that streams results, traces and HTML reports to a Piwi Dashboard instance",
5
5
  "url": "https://github.com/PiwiTests/piwi",
6
6
  "homepage": "https://piwitests.dev",
@@ -15,7 +15,7 @@ Prefer the **Piwi MCP server** if it is connected (`get_locator_healing`, `expla
15
15
 
16
16
  1. **Find the failing case.** From a run the user names, or the latest failed run (`list_recent_activity` → `get_run` with a failed filter). Identify the case whose failure is a locator that matched nothing (a timeout on `click` / `fill` / `expect(locator)`).
17
17
 
18
- 2. **Get the healed locator.** Call `get_locator_healing` for that case. It returns the **recommended durable locator** plus ranked alternatives, each stamped with the **call site** (file and line) where the original locator was used. Read the call site — that is the exact spot to edit.
18
+ 2. **Get the healed locator.** Call `get_locator_healing` for that case. It returns the **recommended durable locator** plus ranked alternatives, each stamped with the **call site** (file and line) where the original locator was used, and a ready-to-apply `edit`. Read the call site — that is the exact spot to edit. An answer with `applicable: false` means the locator resolved and the failure came after it: do not rewrite the selector.
19
19
 
20
20
  3. **Confirm it's really a locator problem.** The suggestion is only right if the element still exists under a new selector. Skim the error and the ARIA snapshot (`get_test_run_case` / `explain_failure`): if the element is genuinely gone or the page errored before rendering, this is not a healing case — hand it to the `investigate-failure` skill instead.
21
21
 
@@ -25,7 +25,7 @@ Prefer the **Piwi MCP server** if it is connected (`get_locator_healing`, `expla
25
25
 
26
26
  6. **Report.** Show the before/after locator, the file and line, and the verifying result. If several tests shared the selector, list every place you changed.
27
27
 
28
- 7. **Report back to Piwi.** Commit with the trailer `Piwi-Cluster: <clusterId>` as the last line of the message (the cluster of the failing case; `get_fix_plan` returns the line in `verify.commitTrailer`), then call `report_fix_attempt` with that cluster, `kind: "locator-edit"`, the commit or the branch, and the `edit` (`filePath`, `line`, `from`: the old locator, `to`: the one you applied). Do it once per call site you changed. Piwi records the attempt verified when the test passes on that commit, and counts the healing suggestion as adopted.
28
+ 7. **Report back to Piwi.** Commit with the trailer `Piwi-Cluster: <clusterId>` as the last line of the message (the cluster of the failing case; `get_fix_plan` returns the line in `verify.commitTrailer`), then call `report_fix_attempt` with that cluster, `kind: "locator-edit"`, the commit or the branch, and the `edit` (`filePath`, `line`, `from`: the old locator, `to`: the one you applied). Do it once per call site you changed. Piwi records the attempt verified when the test passes on that commit, and records the healing suggestion applied once a run uses the recommended locator at that call site. `report_fix_attempt` needs the Maintainer role or above on the project.
29
29
 
30
30
  ## Guardrails
31
31
 
@@ -13,7 +13,8 @@ fixes it, and proves the fix with that same test.
13
13
 
14
14
  Prefer the **Piwi MCP server** if it is connected: `get_bug_report` (the steps in words, what was expected, the
15
15
  evidence, the reproductions, the tests that visit the page) and `render_steps` with `bugReportId` (the failing test).
16
- Without it, the CLI does the same with the dashboard's URL and an API key: `npx @piwitests/reporter bug <id>`.
16
+ Without it, the CLI does the same: `npx @piwitests/reporter bug <id>`, with the dashboard's URL and an API key in the
17
+ `PIWI_DASHBOARD_URL` and `PIWI_API_KEY` environment variables, the project's `.env`, or `--server-url` and `--api-key`.
17
18
 
18
19
  ## Steps
19
20
 
@@ -21,10 +22,12 @@ Without it, the CLI does the same with the dashboard's URL and an API key: `npx
21
22
  the reporter's note, the failed requests and console errors. The evidence often names the cause (a `POST … 500`).
22
23
 
23
24
  2. **Write the failing test.** `npx @piwitests/reporter bug <id> --write` writes the spec to the project's bugs folder
24
- (`tests/bugs` unless the project names another) and runs it once. It is marked `test.fail()` and annotated
25
- `piwi:bug <id>`, so it passes while the bug exists. Exit code 0 means the bug reproduces here. Exit code 1 means it
26
- does not: read the output. A step that finds no element means the page or the data differ here (a user, a cart, a
27
- flag the report assumed): make the test's setup provide them, in the project's style, rather than deleting steps.
25
+ (`tests/bugs` unless the project names another) and runs it once. It is marked `test.fail()`, tagged `@bug` and
26
+ annotated `piwi:bug <id>`, so it passes while the bug exists. Exit code 0 means the bug reproduces here. Exit code 1
27
+ means it does not: read the output. Exit code 2 means the report could not be read or the file could not be written
28
+ (`--force` replaces an existing file). A step that finds no element means the page or the data differ here (a
29
+ user, a cart, a flag the report assumed): make the test's setup provide them, in the project's style, rather than
30
+ deleting steps.
28
31
 
29
32
  3. **Look at the tests that already visit the page** (`missedBy` in `get_bug_report`). If one of them reaches the
30
33
  element, the missing piece is often one assertion in that test; say so in your report, and keep the bug's own spec
@@ -35,9 +38,9 @@ Without it, the CLI does the same with the dashboard's URL and an API key: `npx
35
38
  5. **Prove the fix.** Remove the `test.fail()` line from the bug's spec and run it: it must pass. Run the tests that
36
39
  visit the page too (`npx playwright test <their files>`), so the fix breaks nothing around it.
37
40
 
38
- 6. **Report.** The cause, the fix, the spec file, and the two runs. Once the spec runs in CI without `test.fail()`, Piwi
39
- closes the report on its own; before that, a run where the spec still carries `test.fail()` and passes marks it
40
- "looks fixed".
41
+ 6. **Report.** The cause, the fix, the spec file, and the two runs. Once the spec passes without `test.fail()` in a
42
+ run on the project's default branch (not a `piwi bug` run), Piwi closes the report on its own; before that, such a
43
+ run where the spec still carries `test.fail()` and passes marks it "looks fixed".
41
44
 
42
45
  ## Guardrails
43
46
 
@@ -15,7 +15,7 @@ Prefer the **Piwi MCP server** if it is connected to this agent (tools named `li
15
15
 
16
16
  1. **Find the run.** If the user gave a run URL or id, use it. Otherwise call `list_recent_activity` (or `list_runs` for a specific project) and take the most recent failed run. Confirm with the user if several projects are in play.
17
17
 
18
- 2. **Get the failures.** Call `get_run` with a failed-status filter to list the failing cases. For a fast single-call evidence bundle on one case, use `explain_failure` (error + steps + console + locator fix + diagnosis context).
18
+ 2. **Get the failures.** Call `get_run` (its default `statusFilter` is `failed`) to list the failing cases. For a fast single-call evidence bundle on one case, use `explain_failure` (error + steps + console + locator fix + diagnosis context).
19
19
 
20
20
  3. **Group by cause.** Call `get_failure_groups` (or `list_clusters` / `get_cluster`) — failures that share a root cause are clustered, so you fix one thing, not five. Work cluster by cluster.
21
21
 
@@ -25,9 +25,9 @@ Prefer the **Piwi MCP server** if it is connected to this agent (tools named `li
25
25
 
26
26
  6. **Propose the fix.** Make the smallest change that addresses the root cause, in the actual source or spec files. Prefer Piwi's own suggestions where they exist — for a broken selector, the ranked replacement from `get_locator_healing` (the `apply-locator-healing` skill does exactly this). Show the diff.
27
27
 
28
- 7. **Verify.** Re-run the affected spec(s): `npx playwright test <file>`. Confirm they pass and the new run is green in the dashboard (`get_run_insights` compares against the last green run: regressions cleared, nothing new broken).
28
+ 7. **Verify.** Re-run the affected spec(s): `npx playwright test <file>`. Confirm they pass and the new run is green in the dashboard (`get_run_insights` compares the run with its baseline, usually the last passing run: regressions cleared, nothing new broken).
29
29
 
30
- 8. **Report back.** Piwi learns from what you tell it, so end every investigation with these calls (they need reporter or admin access; each is logged with your API key):
30
+ 8. **Report back.** Piwi learns from what you tell it, so end every investigation with these calls (each is logged with your API key; `record_diagnosis` and `report_fix_attempt` need the Maintainer role or above on the project, rating a diagnosis needs read access only):
31
31
  - **Your diagnosis.** If the cluster had no diagnosis, or yours differs, record it with `record_diagnosis` (`clusterId`, or `executionId` for a failure in no cluster or with a cause of its own): the model you run on and the diagnosis in the schema the tool describes (summary, confidenceScore, severity, ranked hypotheses with their evidence, suggestedFix with the unified-diff `patch`). Piwi validates the patch against the source that failed and shows the diagnosis as written by an agent.
32
32
  - **Your rating of Piwi's diagnosis.** If you read one with `get_cluster_diagnosis`, rate it with `submit_diagnosis_feedback`: `up` when it pointed at the real cause, `down` with a one-line note on what it got wrong when it did not. A `down` keeps the next diagnosis from repeating it.
33
33
  - **Your fix.** Commit the change with the trailer `get_fix_plan` returns in `verify.commitTrailer` (`Piwi-Cluster: <clusterId>`) as the last line of the commit message, then call `report_fix_attempt` with the cluster, the kind (`patch`, `locator-edit` or `fix-plan`), the commit or the branch, the patch you applied and the `diagnosisId` you followed. When the tests pass on that commit, Piwi records the attempt verified and shows it on the cluster's activity.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-piwi
3
- description: Wire a Playwright project up to a Piwi Dashboard — install the reporter, wrap the config, add capture fixtures, and confirm a run lands. Use when the user asks to "set up Piwi", "connect Playwright to the dashboard", "start reporting test results", or after they mention a Piwi/dashboard server URL.
3
+ description: Use when the user asks to "set up Piwi", "connect Playwright to the dashboard", "start reporting test results", or mentions a Piwi or dashboard server URL. Wires a Playwright project up to a Piwi Dashboard — install the reporter, wrap the config, add capture fixtures, and confirm a run lands.
4
4
  ---
5
5
 
6
6
  # Set up Piwi in a Playwright project
@@ -9,7 +9,7 @@ Connect a Playwright test suite to a [Piwi Dashboard](https://piwitests.dev) so
9
9
 
10
10
  ## Before you start
11
11
 
12
- Find out two things (ask the user only if you cannot infer them):
12
+ Find out three things (ask the user only if you cannot infer them):
13
13
 
14
14
  - **Dashboard URL** — e.g. `http://localhost:3000` for a local server, or a deployed URL. Defaults to `http://localhost:3000`.
15
15
  - **Project name** — the label runs are grouped under. Defaults to the package or folder name.
@@ -21,16 +21,19 @@ Confirm this is a Playwright project: there should be a `playwright.config.ts|js
21
21
 
22
22
  ## Do it
23
23
 
24
- Run the initializer from the project root. It installs `@piwitests/reporter`, wraps `defineConfig(...)` with `wrapConfig(...)`, creates `tests/fixtures.ts`, and records `PIWI_*` settings in `.env.example`:
24
+ Run the initializer from the project root. It installs `@piwitests/reporter`, wraps `defineConfig(...)` with `wrapConfig(...)`, creates `tests/fixtures.ts` (`tests/fixtures.js` for a JavaScript config), records the connection in `.env.example`, and installs the Piwi workflow skills into `.claude/skills/`:
25
25
 
26
26
  ```bash
27
27
  npx @piwitests/reporter init --server-url <dashboard-url> --project <name> --json
28
28
  ```
29
29
 
30
30
  - Preview first with `--dry-run` if you want to see the plan before anything is written.
31
- - `--json` prints a `steps[]` array. Read it. Each step has a `status`:
31
+ - `--api-key <key>` also writes the key and the URL into `.env` and adds `.env` to `.gitignore`.
32
+ - `--skills none` installs no skills; `--skills-dir <path>` installs them where your agent reads skills from.
33
+ - `--json` prints a `steps[]` array and the `nextSteps` to tell the user. Read it. Each step has a `status`:
32
34
  - `created` / `updated` / `already` — done, nothing more to do.
33
- - `manual` — the tool would not edit that file safely. The `detail` field is the exact change to make; apply it yourself (see below).
35
+ - `manual` — the tool would not edit that file safely, or was told not to run the install. The `detail` field is the exact change to make; apply it yourself (see below).
36
+ - `skipped` — a skill file exists and was changed by hand; `--force` replaces it.
34
37
  - `error` — something failed (usually the install); the `detail` says what. Fix it and re-run — `init` is idempotent.
35
38
 
36
39
  ### Finishing `manual` steps
@@ -50,7 +53,7 @@ change it to import from the fixtures file instead, e.g. `import { test, expect
50
53
 
51
54
  ## Authentication
52
55
 
53
- If the dashboard has auth enabled, runs need an API key. Have the user create one in the dashboard (**Settings → Account → API keys**; keys start with `pd_`), then put it in `.env` as `PIWI_API_KEY=pd_...` and make sure `.env` is git-ignored. The reporter reads `PIWI_DASHBOARD_URL`, `PIWI_API_KEY` and `PIWI_PROJECT_NAME` from the `.env` next to `playwright.config`, so the config needs no dotenv loading; a variable set in the environment wins over the file. Never hardcode a key into `playwright.config.ts` or commit it. In CI, where `.env` is not checked out, pass it as a secret (`PIWI_API_KEY`).
56
+ If the dashboard has auth enabled, runs need an API key. Have the user create one in the dashboard (**Settings → Account → API keys**; keys start with `pd_`), then put it in `.env` as `PIWI_API_KEY=pd_...` (or re-run `init` with `--api-key`) and make sure `.env` is git-ignored. The reporter reads `PIWI_DASHBOARD_URL`, `PIWI_API_KEY` and `PIWI_PROJECT_NAME` from the `.env` next to `playwright.config`, so the config needs no dotenv loading; a variable set in the environment wins over the file. Never hardcode a key into `playwright.config.ts` or commit it. In CI, where `.env` is not checked out, pass it as a secret (`PIWI_API_KEY`).
54
57
 
55
58
  ## Verify it worked
56
59
 
@@ -58,8 +61,8 @@ Setup is not done until a run reaches the dashboard.
58
61
 
59
62
  1. Run one spec: `npx playwright test` (or a single file to keep it quick).
60
63
  2. Confirm the run landed. Deterministically: set `PIWI_OUTPUT_FILE=piwi-run.json` when running, then read `runUrl`/`status` from that JSON file — its existence with a `runUrl` proves the upload. Otherwise look for the `View run: <url>` line the reporter prints, or open the dashboard and check the project's latest run.
61
- 3. If nothing appears: verify the dashboard is reachable at the URL, that auth (if on) has a valid key, and that traces are enabled (`use: { trace: 'retain-on-failure' }`).
64
+ 3. If nothing appears: read the reporter's `[Piwi Dashboard]` lines (set `PIWI_VERBOSE=true` for more), verify the dashboard is reachable at the URL, and that auth (if on) has a valid key. When the reporter could not deliver the run, it saves it and sends it with the next run.
62
65
 
63
66
  ## Wrap up
64
67
 
65
- Tell the user what changed (config, fixtures, `.env.example`), what they still need to do (API key if auth is on; rewire remaining specs), and the run URL you verified. If they use CI, offer to add the reporter's env there too.
68
+ Tell the user what changed (config, fixtures, `.env.example`, the skills), what they still need to do (API key if auth is on; rewire remaining specs), and the run URL you verified. If they use CI, offer to add the reporter's env there too.
@@ -5,7 +5,7 @@ description: Find the flakiest Playwright tests from Piwi's flaky analysis, make
5
5
 
6
6
  # Stabilize flaky tests with Piwi
7
7
 
8
- Piwi scores every test's flakiness over its whole run history and ranks it by impact, so you spend effort on the tests that actually cost the team — not whichever one failed most recently. For each test it also ranks **suspects** from that history (a route that is slower when it fails, a test running alongside, load), and its **Flake Lab** replays each suspect as a condition next to a control until the failure reproduces. A reproduced condition proves the cause, and the same condition later proves the fix. This skill follows that loop: rank, reproduce, fix, verify.
8
+ Piwi scores every test's flakiness over its recent runs, with the CI time each one wastes, so you spend effort on the tests that actually cost the team — not whichever one failed most recently. For each test it also ranks **suspects** from that history (a route that is slower when it fails, a test running alongside, load), and its **Flake Lab** replays each suspect as a condition next to a control until the failure reproduces. A reproduced condition proves the cause, and the same condition later proves the fix. This skill follows that loop: rank, reproduce, fix, verify.
9
9
 
10
10
  ## How you reach Piwi
11
11
 
@@ -13,7 +13,7 @@ Prefer the **Piwi MCP server** if it is connected (`list_flaky_tests`, `get_flak
13
13
 
14
14
  ## Steps
15
15
 
16
- 1. **Rank the flaky tests.** Call `list_flaky_tests` for the project and take the top few by impact. Do not try to fix the whole list at once.
16
+ 1. **Rank the flaky tests.** Call `list_flaky_tests` for the project (highest flakiness score first, each with its `impact` and `wastedCiMinutes`) and take the few that cost the most. Do not try to fix the whole list at once.
17
17
 
18
18
  2. **Read the suspects.** For a chosen test, call `get_flake_profile`. Each suspect carries raw counts ("slow in 7 of 8 failures and 3 of 44 passes"), the condition that would test it, and, once the lab has run, its latest result (`lab`). `experiments` lists what the lab already found; if an experiment reproduced the test, skip to step 4. Read one failing execution (`get_test_run_case` / `explain_failure`) for the error at the moment it flaked. A test whose root cause is `order` depends on state an earlier test leaves behind: `compare_coverage` with its `testCaseId` names that test and the code whose path changes, so reproduce by running that test before this one in a single worker, and fix the shared state, not the wait.
19
19
 
@@ -23,7 +23,7 @@ Prefer the **Piwi MCP server** if it is connected (`list_flaky_tests`, `get_flak
23
23
  npx @piwitests/reporter flake <testCaseId>
24
24
  ```
25
25
 
26
- It runs a control, then one arm per suspect, retries off, and counts only failures with the same error as in CI. Exit code `0` means an arm reproduced the failure ("reproduced by delaying GET /api/cart to 1.8 s: 3/4 against 0/10, p = 0.011"); `1` means nothing did; `2` is an error (read its message: often the fixtures are not in use). Use `--suspect <n>` to test one suspect, `--all` to also try every condition at once. If the output warns that this checkout differs from the commit of the failures, say so in your report.
26
+ `<testCaseId>` can also be the test's spec file and line (`tests/checkout.spec.ts:42`). It runs a control, then one arm per suspect, retries off, and counts only failures with the same error as in CI. Exit code `0` means an arm reproduced the failure (`Verdict: reproduced by delay GET /api/cart 1.8 s (3/4 against 0/10, p = 0.011)`); `1` means nothing did; `2` is an error (read its message: often the fixtures are not in use). Use `--suspect <n>` to test one suspect, `--all` to also try every condition at once. If the output warns that this checkout differs from the commit of the failures, say so in your report.
27
27
 
28
28
  If nothing reproduces, do not guess a fix from the suspects alone. Report the arms and their counts, and fall back to reading the failure and the attempts diff.
29
29
 
@@ -15,21 +15,24 @@ drafts it from the graph, and opens it in the same change.
15
15
 
16
16
  Prefer the **Piwi MCP server** if it is connected (`list_scenario_gaps`,
17
17
  `draft_scenario`, `get_change_coverage`, `get_coverage`, `triage_gap`). Otherwise use the dashboard's **Gaps**
18
- view, or the reporter CLI with `PIWI_DASHBOARD_URL` / `PIWI_API_KEY` /
19
- `PIWI_PROJECT_NAME` set.
18
+ view, and work from what it shows plus the repo.
20
19
 
21
20
  ## Steps
22
21
 
23
- 1. **Find the gaps.** On a change, call `get_change_coverage` for the current
24
- branch first — the files whose `reach` is `unreached` (the uncovered
25
- changes) and the `lines.notRun` of the files it did reach are what the
26
- change left untested. Otherwise call `list_scenario_gaps` for the project. Filter to what you are
27
- working on with `class`, `feature`, `minScore` or `pr`. Read the classes:
22
+ 1. **Find the gaps.** On a change, call `get_change_coverage` first, with the
23
+ change's `base` and `head` commits (or a `run` of it): the files whose `reach`
24
+ is `unreached` (the uncovered changes) and the `lines.notRun` of the files it
25
+ did reach are what the change left untested. Otherwise call
26
+ `list_scenario_gaps` for the project. Filter to what you are working on with
27
+ `class`, `feature`, `minScore` or `pr`. Read the classes:
28
28
  - **blind-spot** — no trusted test reaches it (a route, a page, a control, a
29
29
  new error path).
30
30
  - **false-comfort** — tests reach it and a probe showed they would not notice
31
31
  it breaking. The most valuable to fix: the catalog says it is covered.
32
32
  - **fragile** — reached by a single, flaky, quarantined or long-skipped test.
33
+ - **unhandled** / **degraded** — a server probe made a route or a dependency
34
+ fail, and the application did not handle it, or degraded visibly: an
35
+ error-state scenario is missing.
33
36
 
34
37
  2. **Pick one.** Take the highest-scored gap in scope. Every gap is *observed
35
38
  reach*, never instrumented coverage, and every evidence line pairs "no test"
@@ -52,12 +55,12 @@ view, or the reporter CLI with `PIWI_DASHBOARD_URL` / `PIWI_API_KEY` /
52
55
  the draft named over hand-rolled locators.
53
56
 
54
57
  5. **Run just this test, then verify.** Run the new spec with a file filter
55
- (`npx @piwitests/reporter run` or `playwright test <file>`). Confirm it passes
58
+ (`npx playwright test <file>`). Confirm it passes
56
59
  for the right reason: temporarily break the behavior it asserts and see it
57
60
  fail. Open it in the same pull request as the change that motivated it.
58
61
 
59
62
  6. **Prove it reaches its target lines.** When the project records server
60
- coverage (an ASP.NET Core backend in a coverage build), run the new test with
63
+ coverage (an ASP.NET Core or Nitro backend built for coverage), run the new test with
61
64
  it on and reporting to Piwi:
62
65
 
63
66
  ```bash
@@ -83,7 +86,7 @@ view, or the reporter CLI with `PIWI_DASHBOARD_URL` / `PIWI_API_KEY` /
83
86
  the gap may need a fixture.
84
87
 
85
88
  8. **Report back to Piwi.** Tell Piwi what you decided about the gap with
86
- `triage_gap` (reporter or admin access): `verb: "accept"` for the gap you
89
+ `triage_gap` (Maintainer role or above on the project): `verb: "accept"` for the gap you
87
90
  wrote the test for, `covered-by` with the covering test's id when a test you
88
91
  found already covers it, or `dismiss` with the reason (`not-worth-testing`,
89
92
  `covered-elsewhere`, `wrong`) when the evidence did not hold. Each verdict counts toward the precision of the