@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.
- package/dist/global-setup-module.js +13 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +665 -227
- package/dist/internal/capture/capture-fixtures.js +445 -100
- package/dist/internal/capture/inspect-on-failure.d.ts +3 -1
- package/dist/internal/capture/inspect-on-failure.js +2 -0
- package/dist/internal/capture/pick-on-failure.d.ts +15 -7
- package/dist/internal/capture/pick-on-failure.js +36 -30
- package/package.json +1 -1
- package/templates/skills/investigate-failure/SKILL.md +1 -1
|
@@ -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
|
|
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
|
-
*
|
|
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.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
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
|
|
1548
|
+
async function pickLocator(page, probe, failing) {
|
|
1548
1549
|
try {
|
|
1549
|
-
|
|
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
|
|
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
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|