@piwitests/reporter 0.48.0 → 0.50.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.
@@ -29,6 +29,8 @@ interface InspectionGate {
29
29
  /** Configured retry count for the project (`testInfo.project.retries`). */
30
30
  retries: number;
31
31
  }
32
+ /** True when this raw `CI` env value counts as "running under CI". */
33
+ declare function isCi(ci: string | undefined): boolean;
32
34
  /** Decide whether the failing page should be handed to the Inspector. */
33
35
  declare function shouldInspectOnFailure(gate: InspectionGate): boolean;
34
36
  /**
@@ -47,4 +49,4 @@ declare function environmentalSkipReason(gate: InspectionGate): string | null;
47
49
  */
48
50
  declare function inspectionGateFromTestInfo(testInfo: TestInfo, enabled?: string | undefined): InspectionGate;
49
51
 
50
- export { type InspectionGate, environmentalSkipReason, inspectionGateFromTestInfo, shouldInspectOnFailure };
52
+ export { type InspectionGate, environmentalSkipReason, inspectionGateFromTestInfo, isCi, shouldInspectOnFailure };
@@ -22,6 +22,7 @@ var inspect_on_failure_exports = {};
22
22
  __export(inspect_on_failure_exports, {
23
23
  environmentalSkipReason: () => environmentalSkipReason,
24
24
  inspectionGateFromTestInfo: () => inspectionGateFromTestInfo,
25
+ isCi: () => isCi,
25
26
  shouldInspectOnFailure: () => shouldInspectOnFailure
26
27
  });
27
28
  module.exports = __toCommonJS(inspect_on_failure_exports);
@@ -62,5 +63,6 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
62
63
  0 && (module.exports = {
63
64
  environmentalSkipReason,
64
65
  inspectionGateFromTestInfo,
66
+ isCi,
65
67
  shouldInspectOnFailure
66
68
  });
@@ -1,5 +1,5 @@
1
1
  import { TestInfo, Page } from '@playwright/test';
2
- import { ProbeArg, ProbedAttrs, PickedAnchorInfo } from '@piwitests/picker-dom';
2
+ import { PickedAnchorInfo, ProbeArg, ProbedAttrs } from '@piwitests/picker-dom';
3
3
  export { PickedAnchorInfo, PickedLeafInfo, generateAnchoredAlternatives, installPickerOverlay, mergeCandidates, showAnchorPicker, showPickerChoices } from '@piwitests/picker-dom';
4
4
  import { FailedLocatorInfo } from './locator-healing.js';
5
5
  import { RankedLocator, LocatorSnapshot } from '@piwitests/core/locator-healing-types';
@@ -55,13 +55,21 @@ declare function parseLeafLocatorExpression(rawExpr: string): {
55
55
  * when no locator can be identified. Exported for tests.
56
56
  */
57
57
  declare function deriveFailedLocator(testInfo: TestInfo): FailedLocatorInfo | null;
58
+ /** What the element → anchors → confirm flow yields: the confirmed locator, its alternatives and the element. */
59
+ type PickedLocator = Omit<UserPickResult, 'failing'>;
58
60
  /**
59
- * Drive the full pick flow on the failing page: element pick (snap +
60
- * tree-walk), optional stable-parent anchoring with live match counts, ranked
61
+ * Run the element → anchors → confirm flow on a live page: element pick (snap
62
+ * + tree-walk), optional stable-parent anchoring with live match counts, ranked
61
63
  * candidates (standard generation merged with anchor-scoped chains), and a
62
- * final confirmation. The test timeout is lifted while waiting. Returns null
63
- * when skipped or when anything breaks — the picker must never mask the
64
- * test's own failure.
64
+ * final confirmation. `failing` is the locator the pick replaces, rendered, for
65
+ * the overlay's headings; null to pick any element. Returns null when the
66
+ * human skips or no locator could be generated; throws when the page breaks.
67
+ */
68
+ declare function pickLocator(page: Page, probe: PickerProbe, failing: string | null): Promise<PickedLocator | null>;
69
+ /**
70
+ * Drive the full pick flow on the failing page (`pickLocator`), the test
71
+ * timeout lifted while waiting. Returns null when skipped or when anything
72
+ * breaks — the picker must never mask the test's own failure.
65
73
  */
66
74
  declare function runLocatorPicker(page: Page, testInfo: TestInfo, failed: FailedLocatorInfo | null, probe: PickerProbe): Promise<UserPickResult | null>;
67
75
  /**
@@ -80,4 +88,4 @@ declare function runLocatorPicker(page: Page, testInfo: TestInfo, failed: Failed
80
88
  */
81
89
  declare function applyPickToSnapshots(snapshots: LocatorSnapshot[], pick: UserPickResult): boolean;
82
90
 
83
- export { type PickerProbe, type UserPickResult, applyPickToSnapshots, deriveFailedLocator, parseLeafLocatorExpression, runLocatorPicker };
91
+ export { type PickedLocator, type PickerProbe, type UserPickResult, applyPickToSnapshots, deriveFailedLocator, parseLeafLocatorExpression, pickLocator, runLocatorPicker };
@@ -36,6 +36,7 @@ __export(pick_on_failure_exports, {
36
36
  installPickerOverlay: () => installPickerOverlay,
37
37
  mergeCandidates: () => mergeCandidates,
38
38
  parseLeafLocatorExpression: () => parseLeafLocatorExpression,
39
+ pickLocator: () => pickLocator,
39
40
  runLocatorPicker: () => runLocatorPicker,
40
41
  showAnchorPicker: () => showAnchorPicker,
41
42
  showPickerChoices: () => showPickerChoices
@@ -1544,25 +1545,16 @@ async function cleanupPicker(page) {
1544
1545
  } catch {
1545
1546
  }
1546
1547
  }
1547
- async function runLocatorPicker(page, testInfo, failed, probe) {
1548
+ async function pickLocator(page, probe, failing) {
1548
1549
  try {
1549
- testInfo.setTimeout(0);
1550
- const rendered = failed ? renderFailing(failed) : null;
1551
- console.log(
1552
- `
1553
- [piwi] "${testInfo.title}" ${testInfo.status} \u2014 ${rendered ? "locator picker" : "inspector"} open in the browser: ${rendered ? `click the element that should replace ${rendered}` : "click any element to generate locators for it"} (\u2191/\u2193 to select a parent/child, Esc to skip).`
1554
- );
1555
- const overlayArg = { transport: "global", failing: rendered };
1550
+ const overlayArg = { transport: "global", failing };
1556
1551
  await page.evaluate(installPickerOverlay, overlayArg);
1557
1552
  await page.waitForFunction(() => globalThis.__piwiPickState !== void 0, void 0, {
1558
1553
  timeout: 0,
1559
1554
  polling: 250
1560
1555
  });
1561
1556
  const state = await page.evaluate(() => globalThis.__piwiPickState);
1562
- if (state !== "picked") {
1563
- await cleanupPicker(page);
1564
- return null;
1565
- }
1557
+ if (state !== "picked") return null;
1566
1558
  const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
1567
1559
  const attrs = await handle.evaluate(probe.fn, probe.arg);
1568
1560
  await handle.dispose();
@@ -1600,11 +1592,10 @@ async function runLocatorPicker(page, testInfo, failed, probe) {
1600
1592
  );
1601
1593
  if (ranked.length === 0) {
1602
1594
  console.log("[piwi] No stable locator could be generated for the picked element \u2014 nothing recorded.");
1603
- await cleanupPicker(page);
1604
1595
  return null;
1605
1596
  }
1606
1597
  await page.evaluate(showPickerChoices, {
1607
- failing: rendered,
1598
+ failing,
1608
1599
  choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
1609
1600
  });
1610
1601
  await page.waitForFunction(() => globalThis.__piwiPickChoice !== void 0, void 0, {
@@ -1612,31 +1603,45 @@ async function runLocatorPicker(page, testInfo, failed, probe) {
1612
1603
  polling: 250
1613
1604
  });
1614
1605
  const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
1615
- await cleanupPicker(page);
1616
1606
  if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
1617
1607
  const picked = { ...ranked[choice], pickedByUser: true };
1618
- const alternatives = [picked, ...ranked.filter((_, i) => i !== choice)];
1619
- const element = {
1620
- tagName: attrs.tagName,
1621
- attributes: attrs.attributes,
1622
- textContent: attrs.textContent,
1623
- accessibleName,
1624
- center: attrs.center,
1625
- ...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
1626
- ...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
1608
+ return {
1609
+ picked,
1610
+ alternatives: [picked, ...ranked.filter((_, i) => i !== choice)],
1611
+ element: {
1612
+ tagName: attrs.tagName,
1613
+ attributes: attrs.attributes,
1614
+ textContent: attrs.textContent,
1615
+ accessibleName,
1616
+ center: attrs.center,
1617
+ ...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
1618
+ ...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
1619
+ },
1620
+ ...anchors.length > 0 ? { anchors } : {}
1627
1621
  };
1622
+ } finally {
1623
+ await cleanupPicker(page);
1624
+ }
1625
+ }
1626
+ async function runLocatorPicker(page, testInfo, failed, probe) {
1627
+ try {
1628
+ testInfo.setTimeout(0);
1629
+ const rendered = failed ? renderFailing(failed) : null;
1630
+ console.log(
1631
+ `
1632
+ [piwi] "${testInfo.title}" ${testInfo.status} \u2014 ${rendered ? "locator picker" : "inspector"} open in the browser: ${rendered ? `click the element that should replace ${rendered}` : "click any element to generate locators for it"} (\u2191/\u2193 to select a parent/child, Esc to skip).`
1633
+ );
1634
+ const pick = await pickLocator(page, probe, rendered);
1635
+ if (!pick) return null;
1628
1636
  const location = failed?.location ?? null;
1629
1637
  console.log(
1630
1638
  failed ? `[piwi] Replacement picked for ${rendered}${location ? ` at ${location}` : ""}:
1631
- [piwi] ${picked.locator}` : `[piwi] Locator picked while inspecting:
1632
- [piwi] ${picked.locator}`
1639
+ [piwi] ${pick.picked.locator}` : `[piwi] Locator picked while inspecting:
1640
+ [piwi] ${pick.picked.locator}`
1633
1641
  );
1634
1642
  return {
1635
1643
  failing: failed ? { method: failed.method, args: failed.args, rendered, location } : null,
1636
- picked,
1637
- alternatives,
1638
- element,
1639
- ...anchors.length > 0 ? { anchors } : {}
1644
+ ...pick
1640
1645
  };
1641
1646
  } catch {
1642
1647
  return null;
@@ -1676,6 +1681,7 @@ function applyPickToSnapshots(snapshots, pick) {
1676
1681
  installPickerOverlay,
1677
1682
  mergeCandidates,
1678
1683
  parseLeafLocatorExpression,
1684
+ pickLocator,
1679
1685
  runLocatorPicker,
1680
1686
  showAnchorPicker,
1681
1687
  showPickerChoices
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piwitests/reporter",
3
- "version": "0.48.0",
3
+ "version": "0.50.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/platform",
6
6
  "homepage": "https://piwitests.dev",
@@ -28,7 +28,7 @@ Prefer the **Piwi MCP server** if it is connected to this agent (tools named `li
28
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).
29
29
 
30
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):
31
- - **Your diagnosis.** If the cluster had no diagnosis, or yours differs, record it with `record_diagnosis`: 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.
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.
34
34
  - Leave the cluster's status alone: the run that passes on your commit records the fix.