@piwitests/reporter 0.49.0 → 0.51.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.
|
@@ -648,11 +648,17 @@ function wildcard(pattern) {
|
|
|
648
648
|
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
649
649
|
return new RegExp(`^${escaped.join(".*")}$`, "i");
|
|
650
650
|
}
|
|
651
|
-
function selectedProjects(projects, argv = process.argv) {
|
|
652
|
-
|
|
653
|
-
if (
|
|
654
|
-
|
|
655
|
-
|
|
651
|
+
function selectedProjects(projects, argv = process.argv, filtered) {
|
|
652
|
+
let chosen;
|
|
653
|
+
if (Array.isArray(filtered)) {
|
|
654
|
+
const names = new Set(filtered.map((p) => p.name).filter(Boolean));
|
|
655
|
+
chosen = new Set(projects.filter((p) => filtered.includes(p) || !!p.name && names.has(p.name)));
|
|
656
|
+
} else {
|
|
657
|
+
const names = cliProjectNames(argv);
|
|
658
|
+
if (!names || names.length === 0) return projects;
|
|
659
|
+
const patterns = names.map(wildcard);
|
|
660
|
+
chosen = new Set(projects.filter((p) => patterns.some((re) => re.test(p.name ?? ""))));
|
|
661
|
+
}
|
|
656
662
|
if (!argv.includes("--no-deps")) {
|
|
657
663
|
const byName = new Map(projects.map((p) => [p.name ?? "", p]));
|
|
658
664
|
const queue = [...chosen];
|
|
@@ -674,8 +680,9 @@ function asProxy(value) {
|
|
|
674
680
|
}
|
|
675
681
|
function baseUrlTargets(config, argv = process.argv) {
|
|
676
682
|
const projects = config.projects?.length ? config.projects : [{ name: "", use: config.use }];
|
|
683
|
+
const filtered = config.projects?.length ? config.filteredProjects : void 0;
|
|
677
684
|
const targets = /* @__PURE__ */ new Map();
|
|
678
|
-
for (const project of selectedProjects(projects, argv)) {
|
|
685
|
+
for (const project of selectedProjects(projects, argv, filtered)) {
|
|
679
686
|
const url = project.use?.baseURL;
|
|
680
687
|
if (typeof url !== "string" || !/^https?:\/\//i.test(url)) continue;
|
|
681
688
|
const target = targets.get(url);
|
package/dist/index.d.ts
CHANGED
|
@@ -175,7 +175,9 @@ interface PiwiDashboardOptions {
|
|
|
175
175
|
* runs no test and reaches no dashboard. The check runs in the reporter's
|
|
176
176
|
* global setup, after `webServer` has started and before the config's own
|
|
177
177
|
* `globalSetup`, so leave it off when that setup is what starts the app. Only
|
|
178
|
-
* the projects
|
|
178
|
+
* the projects the run selects are checked, with their dependencies: the ones
|
|
179
|
+
* `--project` names, or every project but those declared `default: false`
|
|
180
|
+
* (Playwright 1.64+). **Defaults to `false`**. Can
|
|
179
181
|
* also be set with `PIWI_CHECK_BASE_URL=true`.
|
|
180
182
|
*/
|
|
181
183
|
checkBaseUrl?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -3831,11 +3831,17 @@ function wildcard(pattern) {
|
|
|
3831
3831
|
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
3832
3832
|
return new RegExp(`^${escaped.join(".*")}$`, "i");
|
|
3833
3833
|
}
|
|
3834
|
-
function selectedProjects(projects, argv = process.argv) {
|
|
3835
|
-
|
|
3836
|
-
if (
|
|
3837
|
-
|
|
3838
|
-
|
|
3834
|
+
function selectedProjects(projects, argv = process.argv, filtered) {
|
|
3835
|
+
let chosen;
|
|
3836
|
+
if (Array.isArray(filtered)) {
|
|
3837
|
+
const names = new Set(filtered.map((p) => p.name).filter(Boolean));
|
|
3838
|
+
chosen = new Set(projects.filter((p) => filtered.includes(p) || !!p.name && names.has(p.name)));
|
|
3839
|
+
} else {
|
|
3840
|
+
const names = cliProjectNames(argv);
|
|
3841
|
+
if (!names || names.length === 0) return projects;
|
|
3842
|
+
const patterns = names.map(wildcard);
|
|
3843
|
+
chosen = new Set(projects.filter((p) => patterns.some((re) => re.test(p.name ?? ""))));
|
|
3844
|
+
}
|
|
3839
3845
|
if (!argv.includes("--no-deps")) {
|
|
3840
3846
|
const byName = new Map(projects.map((p) => [p.name ?? "", p]));
|
|
3841
3847
|
const queue = [...chosen];
|
|
@@ -3857,8 +3863,9 @@ function asProxy(value) {
|
|
|
3857
3863
|
}
|
|
3858
3864
|
function baseUrlTargets(config, argv = process.argv) {
|
|
3859
3865
|
const projects = config.projects?.length ? config.projects : [{ name: "", use: config.use }];
|
|
3866
|
+
const filtered = config.projects?.length ? config.filteredProjects : void 0;
|
|
3860
3867
|
const targets = /* @__PURE__ */ new Map();
|
|
3861
|
-
for (const project of selectedProjects(projects, argv)) {
|
|
3868
|
+
for (const project of selectedProjects(projects, argv, filtered)) {
|
|
3862
3869
|
const url = project.use?.baseURL;
|
|
3863
3870
|
if (typeof url !== "string" || !/^https?:\/\//i.test(url)) continue;
|
|
3864
3871
|
const target = targets.get(url);
|
|
@@ -4218,7 +4225,9 @@ function probeItemForTest(test) {
|
|
|
4218
4225
|
return matchProbeItem(plan, { title: test.title, filePath, suitePath });
|
|
4219
4226
|
}
|
|
4220
4227
|
function classifyProbeHandled(signals) {
|
|
4221
|
-
|
|
4228
|
+
const blank = signals.blankPage === true;
|
|
4229
|
+
if ((signals.pageErrors ?? 0) > 0 || signals.backendError && blank) return "unhandled";
|
|
4230
|
+
return signals.consoleErrors > 0 || signals.dialogs > 0 || signals.backendError || blank ? "degraded" : "graceful";
|
|
4222
4231
|
}
|
|
4223
4232
|
function outcomeFromStatus(status, applied) {
|
|
4224
4233
|
if (!applied) return "inconclusive";
|
|
@@ -12094,6 +12103,8 @@ function createSink() {
|
|
|
12094
12103
|
stashedAriaJson: null,
|
|
12095
12104
|
pageInventories: [],
|
|
12096
12105
|
probeItem: null,
|
|
12106
|
+
probePageErrors: 0,
|
|
12107
|
+
probeBlankPage: false,
|
|
12097
12108
|
probeInterception: null,
|
|
12098
12109
|
flake: null,
|
|
12099
12110
|
pickOffered: false,
|
|
@@ -12378,9 +12389,16 @@ async function stashPageState(sink, closing) {
|
|
|
12378
12389
|
if (!belongsToClosing) return;
|
|
12379
12390
|
await internalCall(page, () => readPageBeforeClose(sink, page));
|
|
12380
12391
|
}
|
|
12392
|
+
async function readBlankPage(page) {
|
|
12393
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 1e3 }).catch(() => {
|
|
12394
|
+
});
|
|
12395
|
+
const aria = await ariaSnapshotBestEffort(page.locator("body"), 1e3);
|
|
12396
|
+
return aria != null && aria.trim() === "";
|
|
12397
|
+
}
|
|
12381
12398
|
async function readPageBeforeClose(sink, page) {
|
|
12382
12399
|
const vitals = await readWebVitals(page);
|
|
12383
12400
|
if (vitals) sink.stashedWebVitals = vitals;
|
|
12401
|
+
if (sink.probeItem?.level === "server") sink.probeBlankPage = await readBlankPage(page);
|
|
12384
12402
|
if (process.env.PIWI_CAPTURE_PAGE_STATE !== "false") {
|
|
12385
12403
|
const pageState = await readPageState(page);
|
|
12386
12404
|
if (pageState) sink.stashedPageState = pageState;
|
|
@@ -12783,6 +12801,10 @@ function instrumentPage(page) {
|
|
|
12783
12801
|
});
|
|
12784
12802
|
});
|
|
12785
12803
|
}
|
|
12804
|
+
page.on("pageerror", () => {
|
|
12805
|
+
const sink = currentSink;
|
|
12806
|
+
if (sink?.probeItem?.level === "server" && sink.probeInterception?.applied()) sink.probePageErrors++;
|
|
12807
|
+
});
|
|
12786
12808
|
page.on("console", (msg) => {
|
|
12787
12809
|
const sink = currentSink;
|
|
12788
12810
|
if (!sink) return;
|
|
@@ -12839,6 +12861,9 @@ function instrumentPage(page) {
|
|
|
12839
12861
|
};
|
|
12840
12862
|
if (failure) entry.failure = failure;
|
|
12841
12863
|
if (response) {
|
|
12864
|
+
if (timing.requestStart < 0 && typeof response.serverAddr === "function") {
|
|
12865
|
+
if (await response.serverAddr().catch(() => void 0) === null) entry.fulfilled = true;
|
|
12866
|
+
}
|
|
12842
12867
|
const headers = response.headers();
|
|
12843
12868
|
const contentType = headers["content-type"];
|
|
12844
12869
|
if (contentType) entry.contentType = contentType.split(";")[0].trim();
|
|
@@ -13162,10 +13187,12 @@ var piwiFixtures = {
|
|
|
13162
13187
|
if (sink.probeItem) {
|
|
13163
13188
|
const applied = sink.probeInterception?.applied() ?? false;
|
|
13164
13189
|
const level = sink.probeItem.level ?? "client";
|
|
13165
|
-
const handled = level === "server" ? classifyProbeHandled({
|
|
13190
|
+
const handled = level === "server" && applied ? classifyProbeHandled({
|
|
13166
13191
|
consoleErrors: sink.consoleEntries.filter((e) => e.type === "error").length,
|
|
13167
13192
|
dialogs: sink.dialogs.length,
|
|
13168
|
-
backendError: sink.probeInterception?.serverError() ?? false
|
|
13193
|
+
backendError: sink.probeInterception?.serverError() ?? false,
|
|
13194
|
+
pageErrors: sink.probePageErrors,
|
|
13195
|
+
blankPage: sink.probeBlankPage
|
|
13169
13196
|
}) : "n/a";
|
|
13170
13197
|
recordProbeOutcome({
|
|
13171
13198
|
testCaseId: sink.probeItem.testCaseId,
|
|
@@ -13693,6 +13720,97 @@ function compileFromCandidate(candidate) {
|
|
|
13693
13720
|
return null;
|
|
13694
13721
|
}
|
|
13695
13722
|
|
|
13723
|
+
// src/internal/ai/ground.ts
|
|
13724
|
+
var GROUNDED_METHODS = new Set(LOCATOR_METHODS.filter((method) => method !== "locator"));
|
|
13725
|
+
async function refLocator(page, ref) {
|
|
13726
|
+
if (!/^[a-z0-9]+$/i.test(ref)) return null;
|
|
13727
|
+
const getByRef = page.getByRef;
|
|
13728
|
+
try {
|
|
13729
|
+
const target = typeof getByRef === "function" ? getByRef.call(page, ref) : page.locator(`aria-ref=${ref}`);
|
|
13730
|
+
return await target.count() === 1 ? target : null;
|
|
13731
|
+
} catch {
|
|
13732
|
+
return null;
|
|
13733
|
+
}
|
|
13734
|
+
}
|
|
13735
|
+
async function matchesOnly(candidate, target) {
|
|
13736
|
+
try {
|
|
13737
|
+
return await candidate.count() === 1 && await candidate.and(target).count() === 1;
|
|
13738
|
+
} catch {
|
|
13739
|
+
return false;
|
|
13740
|
+
}
|
|
13741
|
+
}
|
|
13742
|
+
async function includes(candidate, target) {
|
|
13743
|
+
try {
|
|
13744
|
+
return await candidate.and(target).count() === 1;
|
|
13745
|
+
} catch {
|
|
13746
|
+
return false;
|
|
13747
|
+
}
|
|
13748
|
+
}
|
|
13749
|
+
function plainArg(arg, params, mask = true) {
|
|
13750
|
+
switch (arg.type) {
|
|
13751
|
+
case "string":
|
|
13752
|
+
return mask ? maskValues(arg.value, params) : arg.value;
|
|
13753
|
+
case "number":
|
|
13754
|
+
case "boolean":
|
|
13755
|
+
return arg.value;
|
|
13756
|
+
case "object": {
|
|
13757
|
+
const out = {};
|
|
13758
|
+
for (const [key, value] of arg.entries) {
|
|
13759
|
+
const plain = plainArg(value, params, mask);
|
|
13760
|
+
if (plain === void 0) return void 0;
|
|
13761
|
+
out[key] = plain;
|
|
13762
|
+
}
|
|
13763
|
+
return out;
|
|
13764
|
+
}
|
|
13765
|
+
default:
|
|
13766
|
+
return void 0;
|
|
13767
|
+
}
|
|
13768
|
+
}
|
|
13769
|
+
function chainToStructured(calls, params = {}) {
|
|
13770
|
+
const links = [];
|
|
13771
|
+
for (const call of calls) {
|
|
13772
|
+
if (!GROUNDED_METHODS.has(call.method)) return null;
|
|
13773
|
+
const args = [];
|
|
13774
|
+
for (const arg of call.args) {
|
|
13775
|
+
const plain = plainArg(arg, params, call.method !== "getByTestId");
|
|
13776
|
+
if (plain === void 0) return null;
|
|
13777
|
+
args.push(plain);
|
|
13778
|
+
}
|
|
13779
|
+
links.push({ method: call.method, args });
|
|
13780
|
+
}
|
|
13781
|
+
const [head, ...chain] = links;
|
|
13782
|
+
if (!head) return null;
|
|
13783
|
+
return chain.length > 0 ? { ...head, chain } : head;
|
|
13784
|
+
}
|
|
13785
|
+
async function normalizedLocator(target, params) {
|
|
13786
|
+
if (typeof target.normalize !== "function") return null;
|
|
13787
|
+
try {
|
|
13788
|
+
const chain = tryParseLocatorChain(String(await target.normalize()));
|
|
13789
|
+
return chain ? chainToStructured(chain.calls, params) : null;
|
|
13790
|
+
} catch {
|
|
13791
|
+
return null;
|
|
13792
|
+
}
|
|
13793
|
+
}
|
|
13794
|
+
async function groundElement(page, element, params) {
|
|
13795
|
+
const compiled = compileFromCandidate({
|
|
13796
|
+
role: element.role,
|
|
13797
|
+
name: element.name ?? null,
|
|
13798
|
+
level: element.level ?? null
|
|
13799
|
+
});
|
|
13800
|
+
const target = element.ref ? await refLocator(page, element.ref) : null;
|
|
13801
|
+
if (!target) return compiled;
|
|
13802
|
+
if (compiled) {
|
|
13803
|
+
const byName = buildLocator(page, compiled.locator, params);
|
|
13804
|
+
if (await matchesOnly(byName, target)) return compiled;
|
|
13805
|
+
if (!await includes(byName, target)) return compiled;
|
|
13806
|
+
}
|
|
13807
|
+
const normalized = await normalizedLocator(target, params);
|
|
13808
|
+
if (!normalized || !await matchesOnly(buildLocator(page, normalized, params), target)) return compiled;
|
|
13809
|
+
const fingerprint = compiled?.fingerprint ?? { role: element.role, name: element.name ?? null };
|
|
13810
|
+
if (!compiled && element.level != null) fingerprint.level = element.level;
|
|
13811
|
+
return { locator: normalized, fingerprint, score: compiled?.score ?? 0 };
|
|
13812
|
+
}
|
|
13813
|
+
|
|
13696
13814
|
// src/internal/ai/resolution.ts
|
|
13697
13815
|
var MAX_SNAPSHOT_CHARS = 24e3;
|
|
13698
13816
|
var DEFAULT_MAX_STEPS = 20;
|
|
@@ -13713,12 +13831,6 @@ var ServerStepResolver = class {
|
|
|
13713
13831
|
return await res.json();
|
|
13714
13832
|
}
|
|
13715
13833
|
};
|
|
13716
|
-
function candidateOf(element) {
|
|
13717
|
-
return { role: element.role, name: element.name ?? null, level: element.level ?? null };
|
|
13718
|
-
}
|
|
13719
|
-
function locatorFromElement(element) {
|
|
13720
|
-
return compileFromCandidate(candidateOf(element));
|
|
13721
|
-
}
|
|
13722
13834
|
function lazyLocator(resolveOnce) {
|
|
13723
13835
|
let pending = null;
|
|
13724
13836
|
const ensure = () => pending ??= resolveOnce();
|
|
@@ -13730,20 +13842,21 @@ function lazyLocator(resolveOnce) {
|
|
|
13730
13842
|
};
|
|
13731
13843
|
return new Proxy({}, handler);
|
|
13732
13844
|
}
|
|
13733
|
-
function buildPostcondition(resolved, template) {
|
|
13845
|
+
async function buildPostcondition(resolved, template, ctx) {
|
|
13734
13846
|
if (!resolved)
|
|
13735
13847
|
throw new Error(`piwi AI: flow "${template}" resolved without a postcondition (the oracle is mandatory)`);
|
|
13736
13848
|
if (resolved.assert === "url") {
|
|
13737
13849
|
if (!resolved.url) throw new Error(`piwi AI: flow "${template}" postcondition is a url assert without a url`);
|
|
13738
13850
|
return { assert: "url", url: resolved.url };
|
|
13739
13851
|
}
|
|
13740
|
-
const compiled = resolved.element ?
|
|
13852
|
+
const compiled = resolved.element ? await groundElement(ctx.page, resolved.element, ctx.params) : null;
|
|
13741
13853
|
if (!compiled) throw new Error(`piwi AI: flow "${template}" postcondition element could not be compiled`);
|
|
13742
13854
|
return { assert: resolved.assert, locator: compiled.locator };
|
|
13743
13855
|
}
|
|
13744
13856
|
var readMaskedSnapshot = async (page, params) => {
|
|
13745
13857
|
const raw = await ariaSnapshotBestEffort(page.locator("body"));
|
|
13746
|
-
|
|
13858
|
+
if (!raw) return "";
|
|
13859
|
+
return raw.split(/(\[ref=\w+\])/).map((part, i) => i % 2 === 1 ? part : maskValues(part, params)).join("");
|
|
13747
13860
|
};
|
|
13748
13861
|
var observeXhrResponses = async (page, action, settleMs) => {
|
|
13749
13862
|
const urls = [];
|
|
@@ -13795,7 +13908,7 @@ async function resolveLocator(template, ctx) {
|
|
|
13795
13908
|
...screenshot ? { screenshot } : {}
|
|
13796
13909
|
});
|
|
13797
13910
|
if (!decision.element) throw new Error(`piwi AI: resolver returned no element for "${template}"`);
|
|
13798
|
-
const compiled =
|
|
13911
|
+
const compiled = await groundElement(ctx.page, decision.element, ctx.params);
|
|
13799
13912
|
if (!compiled) throw new Error(`piwi AI: element for "${template}" could not be compiled to a stable locator`);
|
|
13800
13913
|
const entry = {
|
|
13801
13914
|
version: ARTIFACT_VERSION,
|
|
@@ -13828,14 +13941,14 @@ async function resolveRun(template, ctx) {
|
|
|
13828
13941
|
...screenshot ? { screenshot } : {}
|
|
13829
13942
|
});
|
|
13830
13943
|
if (decision.done) {
|
|
13831
|
-
const postcondition = buildPostcondition(decision.postcondition, template);
|
|
13944
|
+
const postcondition = await buildPostcondition(decision.postcondition, template, ctx);
|
|
13832
13945
|
await assertPostcondition(postcondition, { page: ctx.page, params: ctx.params });
|
|
13833
13946
|
return { version: ARTIFACT_VERSION, kind: "run", template, steps, postcondition };
|
|
13834
13947
|
}
|
|
13835
13948
|
if (!decision.element || !decision.action) {
|
|
13836
13949
|
throw new Error(`piwi AI: resolver returned neither a step nor done for "${template}"`);
|
|
13837
13950
|
}
|
|
13838
|
-
const compiled =
|
|
13951
|
+
const compiled = await groundElement(ctx.page, decision.element, ctx.params);
|
|
13839
13952
|
if (!compiled) throw new Error(`piwi AI: a step element for "${template}" could not be compiled`);
|
|
13840
13953
|
const step = { locator: compiled.locator, action: decision.action, fingerprint: compiled.fingerprint };
|
|
13841
13954
|
if (decision.value !== void 0) step.value = decision.value;
|
|
@@ -2416,7 +2416,9 @@ function probeItemForTest(test) {
|
|
|
2416
2416
|
return matchProbeItem(plan, { title: test.title, filePath, suitePath });
|
|
2417
2417
|
}
|
|
2418
2418
|
function classifyProbeHandled(signals) {
|
|
2419
|
-
|
|
2419
|
+
const blank = signals.blankPage === true;
|
|
2420
|
+
if ((signals.pageErrors ?? 0) > 0 || signals.backendError && blank) return "unhandled";
|
|
2421
|
+
return signals.consoleErrors > 0 || signals.dialogs > 0 || signals.backendError || blank ? "degraded" : "graceful";
|
|
2420
2422
|
}
|
|
2421
2423
|
function outcomeFromStatus(status, applied) {
|
|
2422
2424
|
if (!applied) return "inconclusive";
|
|
@@ -5304,6 +5306,8 @@ function createSink() {
|
|
|
5304
5306
|
stashedAriaJson: null,
|
|
5305
5307
|
pageInventories: [],
|
|
5306
5308
|
probeItem: null,
|
|
5309
|
+
probePageErrors: 0,
|
|
5310
|
+
probeBlankPage: false,
|
|
5307
5311
|
probeInterception: null,
|
|
5308
5312
|
flake: null,
|
|
5309
5313
|
pickOffered: false,
|
|
@@ -5588,9 +5592,16 @@ async function stashPageState(sink, closing) {
|
|
|
5588
5592
|
if (!belongsToClosing) return;
|
|
5589
5593
|
await internalCall(page, () => readPageBeforeClose(sink, page));
|
|
5590
5594
|
}
|
|
5595
|
+
async function readBlankPage(page) {
|
|
5596
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 1e3 }).catch(() => {
|
|
5597
|
+
});
|
|
5598
|
+
const aria = await ariaSnapshotBestEffort(page.locator("body"), 1e3);
|
|
5599
|
+
return aria != null && aria.trim() === "";
|
|
5600
|
+
}
|
|
5591
5601
|
async function readPageBeforeClose(sink, page) {
|
|
5592
5602
|
const vitals = await readWebVitals(page);
|
|
5593
5603
|
if (vitals) sink.stashedWebVitals = vitals;
|
|
5604
|
+
if (sink.probeItem?.level === "server") sink.probeBlankPage = await readBlankPage(page);
|
|
5594
5605
|
if (process.env.PIWI_CAPTURE_PAGE_STATE !== "false") {
|
|
5595
5606
|
const pageState = await readPageState(page);
|
|
5596
5607
|
if (pageState) sink.stashedPageState = pageState;
|
|
@@ -5993,6 +6004,10 @@ function instrumentPage(page) {
|
|
|
5993
6004
|
});
|
|
5994
6005
|
});
|
|
5995
6006
|
}
|
|
6007
|
+
page.on("pageerror", () => {
|
|
6008
|
+
const sink = currentSink;
|
|
6009
|
+
if (sink?.probeItem?.level === "server" && sink.probeInterception?.applied()) sink.probePageErrors++;
|
|
6010
|
+
});
|
|
5996
6011
|
page.on("console", (msg) => {
|
|
5997
6012
|
const sink = currentSink;
|
|
5998
6013
|
if (!sink) return;
|
|
@@ -6049,6 +6064,9 @@ function instrumentPage(page) {
|
|
|
6049
6064
|
};
|
|
6050
6065
|
if (failure) entry.failure = failure;
|
|
6051
6066
|
if (response) {
|
|
6067
|
+
if (timing.requestStart < 0 && typeof response.serverAddr === "function") {
|
|
6068
|
+
if (await response.serverAddr().catch(() => void 0) === null) entry.fulfilled = true;
|
|
6069
|
+
}
|
|
6052
6070
|
const headers = response.headers();
|
|
6053
6071
|
const contentType = headers["content-type"];
|
|
6054
6072
|
if (contentType) entry.contentType = contentType.split(";")[0].trim();
|
|
@@ -6372,10 +6390,12 @@ var piwiFixtures = {
|
|
|
6372
6390
|
if (sink.probeItem) {
|
|
6373
6391
|
const applied = sink.probeInterception?.applied() ?? false;
|
|
6374
6392
|
const level = sink.probeItem.level ?? "client";
|
|
6375
|
-
const handled = level === "server" ? classifyProbeHandled({
|
|
6393
|
+
const handled = level === "server" && applied ? classifyProbeHandled({
|
|
6376
6394
|
consoleErrors: sink.consoleEntries.filter((e) => e.type === "error").length,
|
|
6377
6395
|
dialogs: sink.dialogs.length,
|
|
6378
|
-
backendError: sink.probeInterception?.serverError() ?? false
|
|
6396
|
+
backendError: sink.probeInterception?.serverError() ?? false,
|
|
6397
|
+
pageErrors: sink.probePageErrors,
|
|
6398
|
+
blankPage: sink.probeBlankPage
|
|
6379
6399
|
}) : "n/a";
|
|
6380
6400
|
recordProbeOutcome({
|
|
6381
6401
|
testCaseId: sink.probeItem.testCaseId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piwitests/reporter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.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.
|