@piwitests/reporter 0.47.0 → 0.49.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/cli/index.js +1 -1
- package/dist/global-setup-module.js +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +597 -215
- package/dist/internal/capture/capture-fixtures.js +446 -101
- 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
|
@@ -57,7 +57,7 @@ __export(capture_fixtures_exports, {
|
|
|
57
57
|
stopResourceLedger: () => stopResourceLedger
|
|
58
58
|
});
|
|
59
59
|
module.exports = __toCommonJS(capture_fixtures_exports);
|
|
60
|
-
var
|
|
60
|
+
var path13 = __toESM(require("path"));
|
|
61
61
|
var import_node_zlib2 = require("zlib");
|
|
62
62
|
|
|
63
63
|
// ../picker-dom/src/probe.ts
|
|
@@ -1120,6 +1120,83 @@ function showPickerChoices(arg) {
|
|
|
1120
1120
|
doc.body.appendChild(wrap);
|
|
1121
1121
|
}
|
|
1122
1122
|
|
|
1123
|
+
// ../picker-dom/src/overlay-pause.ts
|
|
1124
|
+
function showPauseBar(arg) {
|
|
1125
|
+
const g = globalThis;
|
|
1126
|
+
const doc = g.document;
|
|
1127
|
+
delete g.__piwiPauseState;
|
|
1128
|
+
if (!doc || !doc.body) {
|
|
1129
|
+
g.__piwiPauseState = "resume";
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
if (typeof g.__piwiPauseCleanup === "function") g.__piwiPauseCleanup();
|
|
1133
|
+
const Z = 2147483600;
|
|
1134
|
+
const bar = doc.createElement("div");
|
|
1135
|
+
bar.id = "__piwi-pause-bar";
|
|
1136
|
+
bar.setAttribute("role", "toolbar");
|
|
1137
|
+
bar.setAttribute("aria-label", "Piwi: test paused");
|
|
1138
|
+
bar.style.cssText = `position:fixed;top:0;left:0;right:0;z-index:${Z + 4};display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:8px 12px;background:#111827;color:#f9fafb;border-bottom:2px solid #a78bfa;box-shadow:0 4px 20px rgba(0,0,0,.35);font:13px/1.5 system-ui,sans-serif;`;
|
|
1139
|
+
const where = doc.createElement("div");
|
|
1140
|
+
where.style.cssText = "font-weight:600;white-space:nowrap;";
|
|
1141
|
+
where.textContent = `Paused at ${arg.place}${arg.attempt ? ` \xB7 attempt ${arg.attempt}` : ""}`;
|
|
1142
|
+
const what = doc.createElement("code");
|
|
1143
|
+
what.style.cssText = "flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#d8b4fe;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;";
|
|
1144
|
+
what.textContent = arg.locator ? `${arg.action} \xB7 ${arg.locator}` : arg.action;
|
|
1145
|
+
what.title = what.textContent;
|
|
1146
|
+
bar.appendChild(where);
|
|
1147
|
+
bar.appendChild(what);
|
|
1148
|
+
const done = (choice) => {
|
|
1149
|
+
cleanup();
|
|
1150
|
+
g.__piwiPauseState = choice;
|
|
1151
|
+
};
|
|
1152
|
+
const onKey = (e) => {
|
|
1153
|
+
if (e.key !== "Escape") return;
|
|
1154
|
+
e.preventDefault();
|
|
1155
|
+
e.stopImmediatePropagation();
|
|
1156
|
+
done("resume");
|
|
1157
|
+
};
|
|
1158
|
+
const cleanup = () => {
|
|
1159
|
+
doc.removeEventListener("keydown", onKey, true);
|
|
1160
|
+
bar.remove();
|
|
1161
|
+
delete g.__piwiPauseCleanup;
|
|
1162
|
+
};
|
|
1163
|
+
const buttons = [
|
|
1164
|
+
["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
|
|
1165
|
+
["Step", "step", "Pause again at the next action", false],
|
|
1166
|
+
["Pick a locator", "pick", "Pick an element on the page and confirm a locator for it", false],
|
|
1167
|
+
["Finish", "finish", "Run on and pause no more in this test", false]
|
|
1168
|
+
];
|
|
1169
|
+
for (const [label, choice, hint, primary] of buttons) {
|
|
1170
|
+
const btn = doc.createElement("button");
|
|
1171
|
+
btn.type = "button";
|
|
1172
|
+
btn.textContent = label;
|
|
1173
|
+
btn.title = hint;
|
|
1174
|
+
btn.style.cssText = "border-radius:6px;padding:4px 12px;cursor:pointer;font:600 12px/1.5 system-ui,sans-serif;" + (primary ? "background:#7c3aed;color:#fff;border:1px solid #7c3aed;" : "background:#1f2937;color:#f3f4f6;border:1px solid #374151;");
|
|
1175
|
+
btn.addEventListener("click", (e) => {
|
|
1176
|
+
e.preventDefault();
|
|
1177
|
+
e.stopImmediatePropagation();
|
|
1178
|
+
done(choice);
|
|
1179
|
+
});
|
|
1180
|
+
bar.appendChild(btn);
|
|
1181
|
+
}
|
|
1182
|
+
g.__piwiPauseCleanup = cleanup;
|
|
1183
|
+
doc.addEventListener("keydown", onKey, true);
|
|
1184
|
+
doc.body.appendChild(bar);
|
|
1185
|
+
}
|
|
1186
|
+
function pauseAnswered() {
|
|
1187
|
+
const g = globalThis;
|
|
1188
|
+
if (g.__piwiPauseState !== void 0) return true;
|
|
1189
|
+
return !g.document?.getElementById("__piwi-pause-bar");
|
|
1190
|
+
}
|
|
1191
|
+
function takePauseState() {
|
|
1192
|
+
const g = globalThis;
|
|
1193
|
+
const state = g.__piwiPauseState;
|
|
1194
|
+
if (typeof g.__piwiPauseCleanup === "function") g.__piwiPauseCleanup();
|
|
1195
|
+
delete g.__piwiPauseCleanup;
|
|
1196
|
+
delete g.__piwiPauseState;
|
|
1197
|
+
return typeof state === "string" ? state : null;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1123
1200
|
// ../core/src/locator-generation.ts
|
|
1124
1201
|
var TAG_TO_ROLE = {
|
|
1125
1202
|
a: "link",
|
|
@@ -2035,8 +2112,8 @@ function inventoryPageKey(url) {
|
|
|
2035
2112
|
// ../core/src/page-key.ts
|
|
2036
2113
|
function normalizeRoute(url) {
|
|
2037
2114
|
try {
|
|
2038
|
-
const
|
|
2039
|
-
let pathname =
|
|
2115
|
+
const parsed2 = new URL(url);
|
|
2116
|
+
let pathname = parsed2.pathname;
|
|
2040
2117
|
pathname = pathname.replace(/\/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+(?=\/|$)/g, "/:jwt");
|
|
2041
2118
|
pathname = pathname.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?=\/|$)/gi, "/:uuid");
|
|
2042
2119
|
pathname = pathname.replace(/\/[0-9ABCDEFGHJKMNPQRSTVWXYZ]{26}(?=\/|$)/gi, "/:ulid");
|
|
@@ -2044,8 +2121,8 @@ function normalizeRoute(url) {
|
|
|
2044
2121
|
pathname = pathname.replace(/\/[0-9a-f]{32,}(?=\/|$)/gi, "/:token");
|
|
2045
2122
|
pathname = pathname.replace(/\/\d+(?=\/|$)/g, "/:id");
|
|
2046
2123
|
let queryString = "";
|
|
2047
|
-
if (
|
|
2048
|
-
const params = new URLSearchParams(
|
|
2124
|
+
if (parsed2.search) {
|
|
2125
|
+
const params = new URLSearchParams(parsed2.search);
|
|
2049
2126
|
const redacted = new URLSearchParams();
|
|
2050
2127
|
for (const [key] of params) {
|
|
2051
2128
|
redacted.set(key, "<redacted>");
|
|
@@ -2158,7 +2235,7 @@ var DEFAULTS = {
|
|
|
2158
2235
|
defaultCapture: true,
|
|
2159
2236
|
checkBaseUrl: false,
|
|
2160
2237
|
streaming: true,
|
|
2161
|
-
streamingBatchSize:
|
|
2238
|
+
streamingBatchSize: 20,
|
|
2162
2239
|
streamingBatchDelay: 2e3,
|
|
2163
2240
|
maxStreamBufferBytes: 100 * 1024 * 1024,
|
|
2164
2241
|
submitTimeout: 15 * 60 * 1e3,
|
|
@@ -2212,6 +2289,10 @@ var PIWI_ENV_KEYS = {
|
|
|
2212
2289
|
aiResponseWaitTimeout: "PIWI_AI_RESPONSE_WAIT_TIMEOUT",
|
|
2213
2290
|
aiScreenshotFallback: "PIWI_AI_SCREENSHOT_FALLBACK"
|
|
2214
2291
|
};
|
|
2292
|
+
var PIWI_LOCAL_DEBUG_ENV = {
|
|
2293
|
+
pauseAt: "PIWI_PAUSE_AT",
|
|
2294
|
+
editorSend: "PIWI_EDITOR_SEND"
|
|
2295
|
+
};
|
|
2215
2296
|
var PIWI_PROBE_ENV = {
|
|
2216
2297
|
flag: "PIWI_PROBE",
|
|
2217
2298
|
plan: "PIWI_PROBE_PLAN",
|
|
@@ -2265,8 +2346,8 @@ function parseRouteKey(routeKey2) {
|
|
|
2265
2346
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
2266
2347
|
}
|
|
2267
2348
|
function patternToRegex(pattern) {
|
|
2268
|
-
const
|
|
2269
|
-
const escaped =
|
|
2349
|
+
const path14 = pattern.split("?")[0] ?? pattern;
|
|
2350
|
+
const escaped = path14.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
2270
2351
|
return new RegExp(`^${escaped}$`);
|
|
2271
2352
|
}
|
|
2272
2353
|
function suitePathEqual(a, b) {
|
|
@@ -2289,13 +2370,13 @@ function matchProbeItem(plan, test) {
|
|
|
2289
2370
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
2290
2371
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
2291
2372
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
2292
|
-
let
|
|
2373
|
+
let path14;
|
|
2293
2374
|
try {
|
|
2294
|
-
|
|
2375
|
+
path14 = new URL(url).pathname;
|
|
2295
2376
|
} catch {
|
|
2296
|
-
|
|
2377
|
+
path14 = url.split("?")[0] ?? url;
|
|
2297
2378
|
}
|
|
2298
|
-
return patternToRegex(pattern).test(
|
|
2379
|
+
return patternToRegex(pattern).test(path14);
|
|
2299
2380
|
}
|
|
2300
2381
|
function createProbeState() {
|
|
2301
2382
|
return { navigated: false, matches: 0 };
|
|
@@ -2317,10 +2398,10 @@ function isProbeMode() {
|
|
|
2317
2398
|
function loadPlan() {
|
|
2318
2399
|
if (planCache !== void 0) return planCache;
|
|
2319
2400
|
planCache = null;
|
|
2320
|
-
const
|
|
2321
|
-
if (
|
|
2401
|
+
const path14 = process.env[PIWI_PROBE_ENV.plan];
|
|
2402
|
+
if (path14) {
|
|
2322
2403
|
try {
|
|
2323
|
-
planCache = JSON.parse(fs2.readFileSync(
|
|
2404
|
+
planCache = JSON.parse(fs2.readFileSync(path14, "utf8"));
|
|
2324
2405
|
} catch {
|
|
2325
2406
|
planCache = null;
|
|
2326
2407
|
}
|
|
@@ -2342,10 +2423,10 @@ function outcomeFromStatus(status, applied) {
|
|
|
2342
2423
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
2343
2424
|
}
|
|
2344
2425
|
function recordProbeOutcome(line) {
|
|
2345
|
-
const
|
|
2346
|
-
if (!
|
|
2426
|
+
const path14 = process.env[PIWI_PROBE_ENV.results];
|
|
2427
|
+
if (!path14) return;
|
|
2347
2428
|
try {
|
|
2348
|
-
fs2.appendFileSync(
|
|
2429
|
+
fs2.appendFileSync(path14, `${JSON.stringify(line)}
|
|
2349
2430
|
`);
|
|
2350
2431
|
} catch {
|
|
2351
2432
|
}
|
|
@@ -2376,24 +2457,24 @@ function faultRouteAction(fault) {
|
|
|
2376
2457
|
return fault === "slow" ? { kind: "delay", ms: SLOW_FAULT_DELAY_MS } : null;
|
|
2377
2458
|
}
|
|
2378
2459
|
function dropOneField(body) {
|
|
2379
|
-
let
|
|
2460
|
+
let parsed2;
|
|
2380
2461
|
try {
|
|
2381
|
-
|
|
2462
|
+
parsed2 = JSON.parse(body);
|
|
2382
2463
|
} catch {
|
|
2383
2464
|
return body;
|
|
2384
2465
|
}
|
|
2385
|
-
if (Array.isArray(
|
|
2466
|
+
if (Array.isArray(parsed2)) {
|
|
2386
2467
|
let key;
|
|
2387
|
-
for (const el of
|
|
2468
|
+
for (const el of parsed2) {
|
|
2388
2469
|
if (el && typeof el === "object" && !Array.isArray(el)) {
|
|
2389
2470
|
key ??= Object.keys(el)[0];
|
|
2390
2471
|
if (key) delete el[key];
|
|
2391
2472
|
}
|
|
2392
2473
|
}
|
|
2393
|
-
return JSON.stringify(
|
|
2474
|
+
return JSON.stringify(parsed2);
|
|
2394
2475
|
}
|
|
2395
|
-
if (
|
|
2396
|
-
const obj =
|
|
2476
|
+
if (parsed2 && typeof parsed2 === "object") {
|
|
2477
|
+
const obj = parsed2;
|
|
2397
2478
|
const key = Object.keys(obj)[0];
|
|
2398
2479
|
if (key) delete obj[key];
|
|
2399
2480
|
return JSON.stringify(obj);
|
|
@@ -3391,11 +3472,11 @@ function deriveFailedLocator(testInfo) {
|
|
|
3391
3472
|
${err.stack ?? ""}`.replace(ANSI_RE2, "");
|
|
3392
3473
|
const line = /^\s*Locator:\s*(.+)$/m.exec(text);
|
|
3393
3474
|
if (!line) continue;
|
|
3394
|
-
const
|
|
3395
|
-
if (!
|
|
3475
|
+
const parsed2 = parseLeafLocatorExpression(line[1].trim());
|
|
3476
|
+
if (!parsed2) continue;
|
|
3396
3477
|
const loc = err.location;
|
|
3397
3478
|
const location = loc ? `${path5.relative(process.cwd(), loc.file).split(path5.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
|
3398
|
-
return { method:
|
|
3479
|
+
return { method: parsed2.method, args: parsed2.args, location };
|
|
3399
3480
|
}
|
|
3400
3481
|
return null;
|
|
3401
3482
|
}
|
|
@@ -3417,25 +3498,16 @@ async function cleanupPicker(page) {
|
|
|
3417
3498
|
} catch {
|
|
3418
3499
|
}
|
|
3419
3500
|
}
|
|
3420
|
-
async function
|
|
3501
|
+
async function pickLocator(page, probe, failing) {
|
|
3421
3502
|
try {
|
|
3422
|
-
|
|
3423
|
-
const rendered = failed ? renderFailing(failed) : null;
|
|
3424
|
-
console.log(
|
|
3425
|
-
`
|
|
3426
|
-
[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).`
|
|
3427
|
-
);
|
|
3428
|
-
const overlayArg = { transport: "global", failing: rendered };
|
|
3503
|
+
const overlayArg = { transport: "global", failing };
|
|
3429
3504
|
await page.evaluate(installPickerOverlay, overlayArg);
|
|
3430
3505
|
await page.waitForFunction(() => globalThis.__piwiPickState !== void 0, void 0, {
|
|
3431
3506
|
timeout: 0,
|
|
3432
3507
|
polling: 250
|
|
3433
3508
|
});
|
|
3434
3509
|
const state = await page.evaluate(() => globalThis.__piwiPickState);
|
|
3435
|
-
if (state !== "picked")
|
|
3436
|
-
await cleanupPicker(page);
|
|
3437
|
-
return null;
|
|
3438
|
-
}
|
|
3510
|
+
if (state !== "picked") return null;
|
|
3439
3511
|
const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
|
|
3440
3512
|
const attrs = await handle.evaluate(probe.fn, probe.arg);
|
|
3441
3513
|
await handle.dispose();
|
|
@@ -3473,11 +3545,10 @@ async function runLocatorPicker(page, testInfo, failed, probe) {
|
|
|
3473
3545
|
);
|
|
3474
3546
|
if (ranked.length === 0) {
|
|
3475
3547
|
console.log("[piwi] No stable locator could be generated for the picked element \u2014 nothing recorded.");
|
|
3476
|
-
await cleanupPicker(page);
|
|
3477
3548
|
return null;
|
|
3478
3549
|
}
|
|
3479
3550
|
await page.evaluate(showPickerChoices, {
|
|
3480
|
-
failing
|
|
3551
|
+
failing,
|
|
3481
3552
|
choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
|
|
3482
3553
|
});
|
|
3483
3554
|
await page.waitForFunction(() => globalThis.__piwiPickChoice !== void 0, void 0, {
|
|
@@ -3485,31 +3556,45 @@ async function runLocatorPicker(page, testInfo, failed, probe) {
|
|
|
3485
3556
|
polling: 250
|
|
3486
3557
|
});
|
|
3487
3558
|
const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
|
|
3488
|
-
await cleanupPicker(page);
|
|
3489
3559
|
if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
|
|
3490
3560
|
const picked = { ...ranked[choice], pickedByUser: true };
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3561
|
+
return {
|
|
3562
|
+
picked,
|
|
3563
|
+
alternatives: [picked, ...ranked.filter((_, i) => i !== choice)],
|
|
3564
|
+
element: {
|
|
3565
|
+
tagName: attrs.tagName,
|
|
3566
|
+
attributes: attrs.attributes,
|
|
3567
|
+
textContent: attrs.textContent,
|
|
3568
|
+
accessibleName,
|
|
3569
|
+
center: attrs.center,
|
|
3570
|
+
...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
|
|
3571
|
+
...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
|
|
3572
|
+
},
|
|
3573
|
+
...anchors.length > 0 ? { anchors } : {}
|
|
3500
3574
|
};
|
|
3575
|
+
} finally {
|
|
3576
|
+
await cleanupPicker(page);
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
async function runLocatorPicker(page, testInfo, failed, probe) {
|
|
3580
|
+
try {
|
|
3581
|
+
testInfo.setTimeout(0);
|
|
3582
|
+
const rendered = failed ? renderFailing(failed) : null;
|
|
3583
|
+
console.log(
|
|
3584
|
+
`
|
|
3585
|
+
[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).`
|
|
3586
|
+
);
|
|
3587
|
+
const pick = await pickLocator(page, probe, rendered);
|
|
3588
|
+
if (!pick) return null;
|
|
3501
3589
|
const location = failed?.location ?? null;
|
|
3502
3590
|
console.log(
|
|
3503
3591
|
failed ? `[piwi] Replacement picked for ${rendered}${location ? ` at ${location}` : ""}:
|
|
3504
|
-
[piwi] ${picked.locator}` : `[piwi] Locator picked while inspecting:
|
|
3505
|
-
[piwi] ${picked.locator}`
|
|
3592
|
+
[piwi] ${pick.picked.locator}` : `[piwi] Locator picked while inspecting:
|
|
3593
|
+
[piwi] ${pick.picked.locator}`
|
|
3506
3594
|
);
|
|
3507
3595
|
return {
|
|
3508
3596
|
failing: failed ? { method: failed.method, args: failed.args, rendered, location } : null,
|
|
3509
|
-
|
|
3510
|
-
alternatives,
|
|
3511
|
-
element,
|
|
3512
|
-
...anchors.length > 0 ? { anchors } : {}
|
|
3597
|
+
...pick
|
|
3513
3598
|
};
|
|
3514
3599
|
} catch {
|
|
3515
3600
|
return null;
|
|
@@ -3542,8 +3627,205 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
3542
3627
|
return true;
|
|
3543
3628
|
}
|
|
3544
3629
|
|
|
3545
|
-
// src/internal/
|
|
3630
|
+
// src/internal/capture/pause-at.ts
|
|
3546
3631
|
var path6 = __toESM(require("path"));
|
|
3632
|
+
|
|
3633
|
+
// ../core/src/editor-send.ts
|
|
3634
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
3635
|
+
function parsePairing(text) {
|
|
3636
|
+
const trimmed = text.trim();
|
|
3637
|
+
const hash = trimmed.indexOf("#");
|
|
3638
|
+
if (hash < 0) return null;
|
|
3639
|
+
const url = trimmed.slice(0, hash);
|
|
3640
|
+
const token = trimmed.slice(hash + 1);
|
|
3641
|
+
if (!/^[A-Za-z0-9_-]{16,}$/.test(token)) return null;
|
|
3642
|
+
let parsed2;
|
|
3643
|
+
try {
|
|
3644
|
+
parsed2 = new URL(url);
|
|
3645
|
+
} catch {
|
|
3646
|
+
return null;
|
|
3647
|
+
}
|
|
3648
|
+
if (parsed2.protocol !== "http:" || !LOOPBACK_HOSTS.has(parsed2.hostname) || !parsed2.port) return null;
|
|
3649
|
+
return { url: `${parsed2.origin}${parsed2.pathname}`, token };
|
|
3650
|
+
}
|
|
3651
|
+
function isInsidePath(file) {
|
|
3652
|
+
if (!file || file.length > 1e3 || file.includes("\0")) return false;
|
|
3653
|
+
if (file.startsWith("/") || file.startsWith("\\") || /^[A-Za-z]:/.test(file)) return false;
|
|
3654
|
+
return file.split(/[\\/]/).every((part) => part !== "..");
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
// src/internal/capture/pause-at.ts
|
|
3658
|
+
var PauseSet = class {
|
|
3659
|
+
constructor(lines) {
|
|
3660
|
+
this.lines = lines;
|
|
3661
|
+
}
|
|
3662
|
+
get size() {
|
|
3663
|
+
return this.lines.size;
|
|
3664
|
+
}
|
|
3665
|
+
/** Whether a call site (`file:line:col`, as `captureCallerLocation` writes it) is on one of the lines. */
|
|
3666
|
+
matches(callerLocation) {
|
|
3667
|
+
if (!callerLocation) return false;
|
|
3668
|
+
const col = callerLocation.lastIndexOf(":");
|
|
3669
|
+
return col > 0 && this.lines.has(callerLocation.slice(0, col));
|
|
3670
|
+
}
|
|
3671
|
+
};
|
|
3672
|
+
var EMPTY = new PauseSet(/* @__PURE__ */ new Set());
|
|
3673
|
+
function callSiteFile(file, cwd) {
|
|
3674
|
+
const slashed = file.replace(/\\/g, "/");
|
|
3675
|
+
const absolute = path6.isAbsolute(file) || path6.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
|
|
3676
|
+
let rel = absolute ? path6.relative(cwd, file) : path6.normalize(slashed);
|
|
3677
|
+
rel = rel.split(path6.sep).join("/").replace(/\\/g, "/");
|
|
3678
|
+
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
3679
|
+
return rel;
|
|
3680
|
+
}
|
|
3681
|
+
var ignoredLogged = /* @__PURE__ */ new Set();
|
|
3682
|
+
function parsePauseAt(value, cwd = process.cwd()) {
|
|
3683
|
+
if (!value?.trim()) return EMPTY;
|
|
3684
|
+
const lines = /* @__PURE__ */ new Set();
|
|
3685
|
+
for (const raw of value.split(";")) {
|
|
3686
|
+
const entry = raw.trim();
|
|
3687
|
+
if (!entry) continue;
|
|
3688
|
+
const m = /^(.+):(\d+)$/.exec(entry);
|
|
3689
|
+
const line = m ? Number(m[2]) : 0;
|
|
3690
|
+
if (!m?.[1] || line < 1) {
|
|
3691
|
+
if (!ignoredLogged.has(entry)) {
|
|
3692
|
+
ignoredLogged.add(entry);
|
|
3693
|
+
console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt}: ignoring "${entry}": expected file:line`);
|
|
3694
|
+
}
|
|
3695
|
+
continue;
|
|
3696
|
+
}
|
|
3697
|
+
lines.add(`${callSiteFile(m[1], cwd)}:${line}`);
|
|
3698
|
+
}
|
|
3699
|
+
return lines.size ? new PauseSet(lines) : EMPTY;
|
|
3700
|
+
}
|
|
3701
|
+
var parsed = null;
|
|
3702
|
+
function currentPauseSet() {
|
|
3703
|
+
const value = process.env[PIWI_LOCAL_DEBUG_ENV.pauseAt];
|
|
3704
|
+
if (!parsed || parsed.value !== value) parsed = { value, set: parsePauseAt(value) };
|
|
3705
|
+
return parsed.set;
|
|
3706
|
+
}
|
|
3707
|
+
function pauseGateFromTestInfo(testInfo) {
|
|
3708
|
+
const use = testInfo.project?.use ?? {};
|
|
3709
|
+
return { ci: process.env.CI, headless: use.headless };
|
|
3710
|
+
}
|
|
3711
|
+
function pauseSkipReason(gate) {
|
|
3712
|
+
if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
|
|
3713
|
+
if (gate.headless !== false)
|
|
3714
|
+
return "the browser is headless \u2014 re-run with --headed (or set use: { headless: false })";
|
|
3715
|
+
return null;
|
|
3716
|
+
}
|
|
3717
|
+
function rearmedTimeout(before, pausedMs) {
|
|
3718
|
+
return before > 0 ? before + Math.max(0, Math.round(pausedMs)) : 0;
|
|
3719
|
+
}
|
|
3720
|
+
function pausePlace(callerLocation) {
|
|
3721
|
+
const col = callerLocation.lastIndexOf(":");
|
|
3722
|
+
const fileLine = col > 0 ? callerLocation.slice(0, col) : callerLocation;
|
|
3723
|
+
return fileLine.slice(fileLine.lastIndexOf("/") + 1);
|
|
3724
|
+
}
|
|
3725
|
+
function assertionLabel(expression, isNot) {
|
|
3726
|
+
const [first = "", ...rest] = expression.split(".").filter(Boolean);
|
|
3727
|
+
const name = first + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
3728
|
+
return `${isNot ? "not." : ""}${name || "expect"}`;
|
|
3729
|
+
}
|
|
3730
|
+
function pickPlace(callerLocation) {
|
|
3731
|
+
const m = /^(.+):(\d+):\d+$/.exec(callerLocation);
|
|
3732
|
+
if (!m) return null;
|
|
3733
|
+
const file = m[1];
|
|
3734
|
+
if (!isInsidePath(file)) return null;
|
|
3735
|
+
return { file, line: Number(m[2]) };
|
|
3736
|
+
}
|
|
3737
|
+
function pickPayload(locator, callerLocation) {
|
|
3738
|
+
const at = pickPlace(callerLocation);
|
|
3739
|
+
return at ? { kind: "locator", text: locator, at } : { kind: "locator", text: locator };
|
|
3740
|
+
}
|
|
3741
|
+
var postFailureLogged = false;
|
|
3742
|
+
async function postPickToEditor(pairingAddress, payload, fetchImpl = fetch) {
|
|
3743
|
+
if (!pairingAddress) return false;
|
|
3744
|
+
const logOnce = (why) => {
|
|
3745
|
+
if (postFailureLogged) return;
|
|
3746
|
+
postFailureLogged = true;
|
|
3747
|
+
console.log(`[piwi] Could not send the picked locator to the editor: ${why}.`);
|
|
3748
|
+
};
|
|
3749
|
+
const pairing = parsePairing(pairingAddress);
|
|
3750
|
+
if (!pairing) {
|
|
3751
|
+
logOnce(`${PIWI_LOCAL_DEBUG_ENV.editorSend} is not a pairing address`);
|
|
3752
|
+
return false;
|
|
3753
|
+
}
|
|
3754
|
+
try {
|
|
3755
|
+
const response = await fetchImpl(pairing.url, {
|
|
3756
|
+
method: "POST",
|
|
3757
|
+
headers: { Authorization: `Bearer ${pairing.token}`, "Content-Type": "application/json" },
|
|
3758
|
+
body: JSON.stringify(payload),
|
|
3759
|
+
signal: AbortSignal.timeout(5e3)
|
|
3760
|
+
});
|
|
3761
|
+
if (response.ok) return true;
|
|
3762
|
+
logOnce(`the editor answered ${response.status}`);
|
|
3763
|
+
} catch (error) {
|
|
3764
|
+
logOnce(error instanceof Error ? error.message : String(error));
|
|
3765
|
+
}
|
|
3766
|
+
return false;
|
|
3767
|
+
}
|
|
3768
|
+
var skipLogged = false;
|
|
3769
|
+
function logPauseSkipOnce(reason) {
|
|
3770
|
+
if (skipLogged) return;
|
|
3771
|
+
skipLogged = true;
|
|
3772
|
+
console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt} is set but breakpoints are skipped: ${reason}`);
|
|
3773
|
+
}
|
|
3774
|
+
var HIGHLIGHT_MS = 2e3;
|
|
3775
|
+
async function pauseHere(page, testInfo, point, probe, onPick) {
|
|
3776
|
+
const before = testInfo.timeout;
|
|
3777
|
+
const started = Date.now();
|
|
3778
|
+
let shown = null;
|
|
3779
|
+
try {
|
|
3780
|
+
testInfo.setTimeout(0);
|
|
3781
|
+
const highlight = point.target?.highlight;
|
|
3782
|
+
if (typeof highlight === "function") {
|
|
3783
|
+
let timer;
|
|
3784
|
+
shown = await Promise.race([
|
|
3785
|
+
Promise.resolve().then(() => highlight.call(point.target)).catch(() => null),
|
|
3786
|
+
new Promise((resolve3) => {
|
|
3787
|
+
timer = setTimeout(() => resolve3(null), HIGHLIGHT_MS);
|
|
3788
|
+
})
|
|
3789
|
+
]);
|
|
3790
|
+
clearTimeout(timer);
|
|
3791
|
+
}
|
|
3792
|
+
const arg = {
|
|
3793
|
+
place: pausePlace(point.location),
|
|
3794
|
+
attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
|
|
3795
|
+
action: point.action,
|
|
3796
|
+
locator: point.locator
|
|
3797
|
+
};
|
|
3798
|
+
console.log(
|
|
3799
|
+
`[piwi] Paused at ${point.location} before ${point.locator ? `${point.action} \xB7 ${point.locator}` : point.action}: Resume, Step, Pick a locator or Finish in the browser (Esc resumes).`
|
|
3800
|
+
);
|
|
3801
|
+
for (; ; ) {
|
|
3802
|
+
await page.evaluate(showPauseBar, arg);
|
|
3803
|
+
await page.waitForFunction(pauseAnswered, void 0, { timeout: 0, polling: 250 });
|
|
3804
|
+
const choice = await page.evaluate(takePauseState);
|
|
3805
|
+
if (choice === "resume" || choice === "step" || choice === "finish") return choice;
|
|
3806
|
+
if (choice !== "pick") continue;
|
|
3807
|
+
const picked = await pickLocator(page, probe, null).catch(() => null);
|
|
3808
|
+
if (picked) await onPick(picked);
|
|
3809
|
+
}
|
|
3810
|
+
} catch {
|
|
3811
|
+
return "resume";
|
|
3812
|
+
} finally {
|
|
3813
|
+
try {
|
|
3814
|
+
const dispose = shown?.dispose;
|
|
3815
|
+
const hide = page.hideHighlight;
|
|
3816
|
+
if (typeof dispose === "function") await dispose.call(shown);
|
|
3817
|
+
else if (typeof hide === "function") await hide.call(page);
|
|
3818
|
+
} catch {
|
|
3819
|
+
}
|
|
3820
|
+
try {
|
|
3821
|
+
testInfo.setTimeout(rearmedTimeout(before, Date.now() - started));
|
|
3822
|
+
} catch {
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
// src/internal/support/aria-sampling.ts
|
|
3828
|
+
var path7 = __toESM(require("path"));
|
|
3547
3829
|
var os3 = __toESM(require("os"));
|
|
3548
3830
|
var fs4 = __toESM(require("fs"));
|
|
3549
3831
|
|
|
@@ -3559,7 +3841,7 @@ function ariaSampleIdentity(filePath, title) {
|
|
|
3559
3841
|
return `${filePath}\0${title}`;
|
|
3560
3842
|
}
|
|
3561
3843
|
function getAriaSampleFilePath(projectName) {
|
|
3562
|
-
return
|
|
3844
|
+
return path7.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
3563
3845
|
}
|
|
3564
3846
|
var cachedSets = /* @__PURE__ */ new Map();
|
|
3565
3847
|
function loadAriaSampleSet(projectName) {
|
|
@@ -3567,9 +3849,9 @@ function loadAriaSampleSet(projectName) {
|
|
|
3567
3849
|
let set = null;
|
|
3568
3850
|
try {
|
|
3569
3851
|
const raw = fs4.readFileSync(getAriaSampleFilePath(projectName), "utf8");
|
|
3570
|
-
const
|
|
3571
|
-
if (
|
|
3572
|
-
set = new Set(
|
|
3852
|
+
const parsed2 = JSON.parse(raw);
|
|
3853
|
+
if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
|
|
3854
|
+
set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
|
|
3573
3855
|
}
|
|
3574
3856
|
} catch {
|
|
3575
3857
|
set = null;
|
|
@@ -3578,7 +3860,7 @@ function loadAriaSampleSet(projectName) {
|
|
|
3578
3860
|
return set;
|
|
3579
3861
|
}
|
|
3580
3862
|
function relativeTestFile2(file) {
|
|
3581
|
-
return
|
|
3863
|
+
return path7.relative(process.cwd(), file).split(path7.sep).join("/");
|
|
3582
3864
|
}
|
|
3583
3865
|
function isDueForAriaSample(testInfo) {
|
|
3584
3866
|
const projectName = process.env.PIWI_PROJECT_NAME;
|
|
@@ -3590,20 +3872,20 @@ function isDueForAriaSample(testInfo) {
|
|
|
3590
3872
|
|
|
3591
3873
|
// src/internal/capture/quiet-capture.ts
|
|
3592
3874
|
var import_node_module = require("module");
|
|
3593
|
-
var
|
|
3875
|
+
var path9 = __toESM(require("path"));
|
|
3594
3876
|
|
|
3595
3877
|
// src/internal/support/reporter-version.ts
|
|
3596
3878
|
var fs5 = __toESM(require("fs"));
|
|
3597
|
-
var
|
|
3879
|
+
var path8 = __toESM(require("path"));
|
|
3598
3880
|
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
3599
3881
|
function findOwnPackageJson(fromDir) {
|
|
3600
3882
|
for (const offset of CANDIDATE_OFFSETS) {
|
|
3601
3883
|
try {
|
|
3602
|
-
const pkgPath =
|
|
3884
|
+
const pkgPath = path8.resolve(fromDir, offset, "package.json");
|
|
3603
3885
|
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
3604
3886
|
const { name, version } = pkg;
|
|
3605
3887
|
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
3606
|
-
return { version, root:
|
|
3888
|
+
return { version, root: path8.dirname(pkgPath) };
|
|
3607
3889
|
} catch {
|
|
3608
3890
|
}
|
|
3609
3891
|
}
|
|
@@ -3647,7 +3929,7 @@ function resolveBoxing() {
|
|
|
3647
3929
|
if (typeof set !== "function") continue;
|
|
3648
3930
|
return {
|
|
3649
3931
|
set,
|
|
3650
|
-
prefixes: [
|
|
3932
|
+
prefixes: [path9.dirname(playwrightPackageJson), path9.join(root, "dist") + path9.sep]
|
|
3651
3933
|
};
|
|
3652
3934
|
}
|
|
3653
3935
|
} catch {
|
|
@@ -3675,7 +3957,7 @@ function internalCall(owner, run) {
|
|
|
3675
3957
|
// src/internal/capture/code-reach.ts
|
|
3676
3958
|
var crypto2 = __toESM(require("crypto"));
|
|
3677
3959
|
var fs6 = __toESM(require("fs"));
|
|
3678
|
-
var
|
|
3960
|
+
var path10 = __toESM(require("path"));
|
|
3679
3961
|
var import_node_child_process = require("child_process");
|
|
3680
3962
|
|
|
3681
3963
|
// ../core/src/source-map.ts
|
|
@@ -3938,12 +4220,12 @@ function repositoryRoot(from) {
|
|
|
3938
4220
|
}).trim();
|
|
3939
4221
|
} catch {
|
|
3940
4222
|
}
|
|
3941
|
-
cachedRepoRoot = { from, root:
|
|
4223
|
+
cachedRepoRoot = { from, root: path10.resolve(root) };
|
|
3942
4224
|
return cachedRepoRoot.root;
|
|
3943
4225
|
}
|
|
3944
4226
|
function codeReachRoots(configDir, configured) {
|
|
3945
4227
|
const repoRoot = repositoryRoot(configDir);
|
|
3946
|
-
const roots = configured?.length ? configured.map((r) =>
|
|
4228
|
+
const roots = configured?.length ? configured.map((r) => path10.resolve(configDir, r)) : [configDir];
|
|
3947
4229
|
return { roots: [.../* @__PURE__ */ new Set([...roots, repoRoot])], repoRoot };
|
|
3948
4230
|
}
|
|
3949
4231
|
function isFile(file) {
|
|
@@ -3956,10 +4238,10 @@ function isFile(file) {
|
|
|
3956
4238
|
function resolveSourcePath(source, roots) {
|
|
3957
4239
|
const normalized = normalizeSourcePath(source);
|
|
3958
4240
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
3959
|
-
if (
|
|
3960
|
-
const
|
|
4241
|
+
if (path10.isAbsolute(normalized) && isFile(normalized)) return path10.resolve(normalized);
|
|
4242
|
+
const relative9 = normalized.replace(/^\/+/, "");
|
|
3961
4243
|
for (const root of roots) {
|
|
3962
|
-
const candidate =
|
|
4244
|
+
const candidate = path10.resolve(root, relative9);
|
|
3963
4245
|
if (isFile(candidate)) return candidate;
|
|
3964
4246
|
}
|
|
3965
4247
|
return null;
|
|
@@ -3975,10 +4257,10 @@ function viteBases(entries) {
|
|
|
3975
4257
|
function fileForUrl(url, roots, bases) {
|
|
3976
4258
|
let pathname;
|
|
3977
4259
|
try {
|
|
3978
|
-
const
|
|
3979
|
-
if (
|
|
3980
|
-
pathname =
|
|
3981
|
-
const base = bases.get(
|
|
4260
|
+
const parsed2 = new URL(url);
|
|
4261
|
+
if (parsed2.protocol !== "http:" && parsed2.protocol !== "https:" && parsed2.protocol !== "file:") return null;
|
|
4262
|
+
pathname = parsed2.pathname;
|
|
4263
|
+
const base = bases.get(parsed2.origin);
|
|
3982
4264
|
if (base && pathname.startsWith(base)) pathname = pathname.slice(base.length - 1);
|
|
3983
4265
|
} catch {
|
|
3984
4266
|
return null;
|
|
@@ -3991,7 +4273,7 @@ function fileForUrl(url, roots, bases) {
|
|
|
3991
4273
|
return file;
|
|
3992
4274
|
}
|
|
3993
4275
|
function relativeToRepo(file, repoRoot) {
|
|
3994
|
-
return
|
|
4276
|
+
return path10.relative(repoRoot, file).split(path10.sep).join("/");
|
|
3995
4277
|
}
|
|
3996
4278
|
function scriptHash(entry) {
|
|
3997
4279
|
return crypto2.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
@@ -4111,7 +4393,7 @@ async function stopCodeReach(page) {
|
|
|
4111
4393
|
|
|
4112
4394
|
// src/internal/capture/resource-ledger.ts
|
|
4113
4395
|
var fs8 = __toESM(require("fs"));
|
|
4114
|
-
var
|
|
4396
|
+
var path12 = __toESM(require("path"));
|
|
4115
4397
|
var import_node_module2 = require("module");
|
|
4116
4398
|
|
|
4117
4399
|
// src/internal/capture/worker-metrics.ts
|
|
@@ -4119,7 +4401,7 @@ var import_node_perf_hooks = require("perf_hooks");
|
|
|
4119
4401
|
|
|
4120
4402
|
// src/internal/support/system-readers.ts
|
|
4121
4403
|
var fs7 = __toESM(require("fs"));
|
|
4122
|
-
var
|
|
4404
|
+
var path11 = __toESM(require("path"));
|
|
4123
4405
|
var CLK_TCK = 100;
|
|
4124
4406
|
function readText(file) {
|
|
4125
4407
|
try {
|
|
@@ -4205,7 +4487,7 @@ var ProcFs = class {
|
|
|
4205
4487
|
}
|
|
4206
4488
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
4207
4489
|
available() {
|
|
4208
|
-
return fs7.existsSync(
|
|
4490
|
+
return fs7.existsSync(path11.join(this.root, "stat"));
|
|
4209
4491
|
}
|
|
4210
4492
|
/** Every process, keyed by pid. */
|
|
4211
4493
|
table() {
|
|
@@ -4218,32 +4500,32 @@ var ProcFs = class {
|
|
|
4218
4500
|
}
|
|
4219
4501
|
for (const name of names) {
|
|
4220
4502
|
if (!/^\d+$/.test(name)) continue;
|
|
4221
|
-
const text = readText(
|
|
4503
|
+
const text = readText(path11.join(this.root, name, "stat"));
|
|
4222
4504
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
4223
4505
|
if (stat) out.set(stat.pid, stat);
|
|
4224
4506
|
}
|
|
4225
4507
|
return out;
|
|
4226
4508
|
}
|
|
4227
4509
|
stat(pid) {
|
|
4228
|
-
const text = readText(
|
|
4510
|
+
const text = readText(path11.join(this.root, String(pid), "stat"));
|
|
4229
4511
|
return text ? parseProcStat(pid, text) : null;
|
|
4230
4512
|
}
|
|
4231
4513
|
runWaitMs(pid) {
|
|
4232
|
-
const text = readText(
|
|
4514
|
+
const text = readText(path11.join(this.root, String(pid), "schedstat"));
|
|
4233
4515
|
return text ? parseSchedstat(text) : null;
|
|
4234
4516
|
}
|
|
4235
4517
|
cmdline(pid) {
|
|
4236
|
-
return (readText(
|
|
4518
|
+
return (readText(path11.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
4237
4519
|
}
|
|
4238
4520
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
4239
4521
|
statusKb(pid, key) {
|
|
4240
|
-
const text = readText(
|
|
4522
|
+
const text = readText(path11.join(this.root, String(pid), "status"));
|
|
4241
4523
|
return text ? parseKbField(text, key) : null;
|
|
4242
4524
|
}
|
|
4243
4525
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
4244
4526
|
async pssKb(pid) {
|
|
4245
4527
|
try {
|
|
4246
|
-
return parseKbField(await fs7.promises.readFile(
|
|
4528
|
+
return parseKbField(await fs7.promises.readFile(path11.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
4247
4529
|
} catch {
|
|
4248
4530
|
return null;
|
|
4249
4531
|
}
|
|
@@ -4251,29 +4533,29 @@ var ProcFs = class {
|
|
|
4251
4533
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
4252
4534
|
resetPeakRss(pid) {
|
|
4253
4535
|
try {
|
|
4254
|
-
fs7.writeFileSync(
|
|
4536
|
+
fs7.writeFileSync(path11.join(this.root, String(pid), "clear_refs"), "5");
|
|
4255
4537
|
return true;
|
|
4256
4538
|
} catch {
|
|
4257
4539
|
return false;
|
|
4258
4540
|
}
|
|
4259
4541
|
}
|
|
4260
4542
|
cpuTicks() {
|
|
4261
|
-
const text = readText(
|
|
4543
|
+
const text = readText(path11.join(this.root, "stat"));
|
|
4262
4544
|
return text ? parseCpuTicks(text) : null;
|
|
4263
4545
|
}
|
|
4264
4546
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
4265
4547
|
pressureUs(resource) {
|
|
4266
|
-
const text = readText(
|
|
4548
|
+
const text = readText(path11.join(this.root, "pressure", resource));
|
|
4267
4549
|
return text ? parsePsiTotal(text) : null;
|
|
4268
4550
|
}
|
|
4269
4551
|
memAvailableKb() {
|
|
4270
|
-
const text = readText(
|
|
4552
|
+
const text = readText(path11.join(this.root, "meminfo"));
|
|
4271
4553
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
4272
4554
|
}
|
|
4273
4555
|
/** Open file descriptors of a process. */
|
|
4274
4556
|
fdCount(pid) {
|
|
4275
4557
|
try {
|
|
4276
|
-
return fs7.readdirSync(
|
|
4558
|
+
return fs7.readdirSync(path11.join(this.root, String(pid), "fd")).length;
|
|
4277
4559
|
} catch {
|
|
4278
4560
|
return null;
|
|
4279
4561
|
}
|
|
@@ -4435,10 +4717,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
|
4435
4717
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
4436
4718
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
4437
4719
|
function relativePath(file) {
|
|
4438
|
-
return
|
|
4720
|
+
return path12.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path12.sep).join("/");
|
|
4439
4721
|
}
|
|
4440
4722
|
function isLibraryFile(file) {
|
|
4441
|
-
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${
|
|
4723
|
+
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path12.sep}node_modules${path12.sep}`) || file.includes("/node_modules/") || file === __filename;
|
|
4442
4724
|
}
|
|
4443
4725
|
function userSite(stack) {
|
|
4444
4726
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
@@ -4486,7 +4768,7 @@ function playwrightTestInfoSource() {
|
|
|
4486
4768
|
try {
|
|
4487
4769
|
const packageJson = resolvePlaywrightPackageJson();
|
|
4488
4770
|
if (!packageJson) return () => null;
|
|
4489
|
-
const globals = (0, import_node_module2.createRequire)(packageJson)(
|
|
4771
|
+
const globals = (0, import_node_module2.createRequire)(packageJson)(path12.join(path12.dirname(packageJson), "lib", "globals.js"));
|
|
4490
4772
|
const read = globals.currentTestInfo;
|
|
4491
4773
|
if (typeof read !== "function") return () => null;
|
|
4492
4774
|
return () => {
|
|
@@ -5026,6 +5308,7 @@ function createSink() {
|
|
|
5026
5308
|
flake: null,
|
|
5027
5309
|
pickOffered: false,
|
|
5028
5310
|
userPick: null,
|
|
5311
|
+
pause: null,
|
|
5029
5312
|
locatorPages: new LocatorPageLog(),
|
|
5030
5313
|
codeReach: null
|
|
5031
5314
|
};
|
|
@@ -5042,11 +5325,11 @@ function configuredCodeReachRoots() {
|
|
|
5042
5325
|
const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
|
|
5043
5326
|
if (!raw) return null;
|
|
5044
5327
|
try {
|
|
5045
|
-
const
|
|
5046
|
-
if (Array.isArray(
|
|
5328
|
+
const parsed2 = JSON.parse(raw);
|
|
5329
|
+
if (Array.isArray(parsed2)) return parsed2.filter((r) => typeof r === "string" && r !== "");
|
|
5047
5330
|
} catch {
|
|
5048
5331
|
}
|
|
5049
|
-
return raw.split(
|
|
5332
|
+
return raw.split(path13.delimiter).filter(Boolean);
|
|
5050
5333
|
}
|
|
5051
5334
|
async function stopSinkCodeReach(sink, closing) {
|
|
5052
5335
|
const session = sink.codeReach;
|
|
@@ -5061,7 +5344,7 @@ async function stopSinkCodeReach(sink, closing) {
|
|
|
5061
5344
|
if (!entries) return;
|
|
5062
5345
|
const info = sink.testInfo;
|
|
5063
5346
|
const configFile = info?.config.configFile;
|
|
5064
|
-
const configDir = configFile ?
|
|
5347
|
+
const configDir = configFile ? path13.dirname(configFile) : info?.config.rootDir ?? process.cwd();
|
|
5065
5348
|
session.files = await resolveCodeReach(
|
|
5066
5349
|
entries,
|
|
5067
5350
|
codeReachRoots(configDir, configuredCodeReachRoots()),
|
|
@@ -5480,6 +5763,53 @@ function recordLocatorPage(sink, page, locator, location, arrival) {
|
|
|
5480
5763
|
} catch {
|
|
5481
5764
|
}
|
|
5482
5765
|
}
|
|
5766
|
+
function pausesAt(pause, callerLocation) {
|
|
5767
|
+
return !pause.finished && (pause.next || pause.set.matches(callerLocation));
|
|
5768
|
+
}
|
|
5769
|
+
function locatorSource(target, method, args) {
|
|
5770
|
+
try {
|
|
5771
|
+
const text = String(target);
|
|
5772
|
+
if (text && !text.startsWith("[object")) return text;
|
|
5773
|
+
} catch {
|
|
5774
|
+
}
|
|
5775
|
+
return renderFailing({ method, args });
|
|
5776
|
+
}
|
|
5777
|
+
async function pauseBefore(sink, page, target, callerLocation, action, origin) {
|
|
5778
|
+
const pause = sink.pause;
|
|
5779
|
+
const testInfo = sink.testInfo;
|
|
5780
|
+
if (!pause || !testInfo || !callerLocation) return;
|
|
5781
|
+
pause.next = false;
|
|
5782
|
+
try {
|
|
5783
|
+
const locator = locatorSource(target, origin.method, origin.args);
|
|
5784
|
+
const onPick = async (picked) => {
|
|
5785
|
+
const pick = {
|
|
5786
|
+
failing: { method: origin.method, args: origin.args, rendered: locator, location: callerLocation },
|
|
5787
|
+
...picked
|
|
5788
|
+
};
|
|
5789
|
+
applyPickToSnapshots(sink.capturedLocators, pick);
|
|
5790
|
+
sink.probedLocations.add(callerLocation);
|
|
5791
|
+
const place = callerLocation.slice(0, callerLocation.lastIndexOf(":"));
|
|
5792
|
+
console.log(`[piwi] Locator picked at ${place}: ${picked.picked.locator}`);
|
|
5793
|
+
await postPickToEditor(
|
|
5794
|
+
process.env[PIWI_LOCAL_DEBUG_ENV.editorSend],
|
|
5795
|
+
pickPayload(picked.picked.locator, callerLocation)
|
|
5796
|
+
);
|
|
5797
|
+
};
|
|
5798
|
+
const choice = await internalCall(
|
|
5799
|
+
page,
|
|
5800
|
+
() => pauseHere(
|
|
5801
|
+
page,
|
|
5802
|
+
testInfo,
|
|
5803
|
+
{ location: callerLocation, action, locator, target },
|
|
5804
|
+
{ fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG },
|
|
5805
|
+
onPick
|
|
5806
|
+
)
|
|
5807
|
+
);
|
|
5808
|
+
if (choice === "step") pause.next = true;
|
|
5809
|
+
else if (choice === "finish") pause.finished = true;
|
|
5810
|
+
} catch {
|
|
5811
|
+
}
|
|
5812
|
+
}
|
|
5483
5813
|
function wrapLocator(page, locator, originMethod, originArgs) {
|
|
5484
5814
|
return new Proxy(locator, {
|
|
5485
5815
|
get(target, prop) {
|
|
@@ -5501,6 +5831,12 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
5501
5831
|
const isNot = Boolean(callArgs[1]?.isNot);
|
|
5502
5832
|
const callerLocation = sink ? captureCallerLocation() : null;
|
|
5503
5833
|
if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
|
|
5834
|
+
if (sink?.pause && pausesAt(sink.pause, callerLocation)) {
|
|
5835
|
+
await pauseBefore(sink, page, target, callerLocation, assertionLabel(expression, isNot), {
|
|
5836
|
+
method: originMethod,
|
|
5837
|
+
args: originArgs
|
|
5838
|
+
});
|
|
5839
|
+
}
|
|
5504
5840
|
if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
|
|
5505
5841
|
return callMethod(target, prop, callArgs);
|
|
5506
5842
|
}
|
|
@@ -5539,6 +5875,9 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
5539
5875
|
args: originArgs,
|
|
5540
5876
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
5541
5877
|
};
|
|
5878
|
+
if (sink.pause && pausesAt(sink.pause, callerLocation)) {
|
|
5879
|
+
await pauseBefore(sink, page, target, callerLocation, String(prop), used);
|
|
5880
|
+
}
|
|
5542
5881
|
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
5543
5882
|
let seq = -1;
|
|
5544
5883
|
if (!alreadyProbed) {
|
|
@@ -5999,6 +6338,12 @@ var piwiFixtures = {
|
|
|
5999
6338
|
if (conflict) throw new Error(conflict);
|
|
6000
6339
|
const sink = createSink();
|
|
6001
6340
|
sink.testInfo = testInfo;
|
|
6341
|
+
const pauseSet = currentPauseSet();
|
|
6342
|
+
if (pauseSet.size) {
|
|
6343
|
+
const reason = pauseSkipReason(pauseGateFromTestInfo(testInfo));
|
|
6344
|
+
if (reason) logPauseSkipOnce(reason);
|
|
6345
|
+
else sink.pause = { set: pauseSet, next: false, finished: false };
|
|
6346
|
+
}
|
|
6002
6347
|
if (isFlakeMode()) {
|
|
6003
6348
|
const plan = loadFlakePlan();
|
|
6004
6349
|
const role = flakeRoleForTest(plan, {
|