@piwitests/reporter 0.59.0 → 0.60.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 +1456 -1305
- package/dist/global-setup-module.js +137 -54
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1234 -480
- package/dist/internal/capture/attachments.d.ts +1 -0
- package/dist/internal/capture/attachments.js +2 -1
- package/dist/internal/capture/capture-fixtures.js +939 -320
- package/dist/internal/capture/pick-on-failure.d.ts +6 -3
- package/dist/internal/capture/pick-on-failure.js +49 -30
- package/package.json +5 -5
- package/templates/skills/run-the-right-tests/SKILL.md +19 -8
- package/templates/skills/setup-piwi/SKILL.md +4 -1
|
@@ -58,7 +58,7 @@ __export(capture_fixtures_exports, {
|
|
|
58
58
|
stopResourceLedger: () => stopResourceLedger
|
|
59
59
|
});
|
|
60
60
|
module.exports = __toCommonJS(capture_fixtures_exports);
|
|
61
|
-
var
|
|
61
|
+
var path14 = __toESM(require("path"));
|
|
62
62
|
var import_node_zlib2 = require("zlib");
|
|
63
63
|
|
|
64
64
|
// ../picker-dom/src/probe.ts
|
|
@@ -517,8 +517,8 @@ function installPickerOverlay(arg) {
|
|
|
517
517
|
return root && root !== doc && root.host ? root.host : null;
|
|
518
518
|
};
|
|
519
519
|
const targetOf = (e) => {
|
|
520
|
-
const
|
|
521
|
-
const first =
|
|
520
|
+
const path15 = typeof e.composedPath === "function" ? e.composedPath() : null;
|
|
521
|
+
const first = path15 && path15.length ? path15[0] : null;
|
|
522
522
|
return first && first.nodeType === 1 ? first : e.target;
|
|
523
523
|
};
|
|
524
524
|
const buildChain = (raw) => {
|
|
@@ -1222,8 +1222,11 @@ function showPauseBar(arg) {
|
|
|
1222
1222
|
};
|
|
1223
1223
|
const buttons = [
|
|
1224
1224
|
["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
|
|
1225
|
-
["Step", "step", "Pause again
|
|
1225
|
+
["Step", "step", "Pause again before the next call", false],
|
|
1226
1226
|
["Pick a locator", "pick", "Pick an element on the page and confirm a locator for it", false],
|
|
1227
|
+
...arg.record ? [
|
|
1228
|
+
["Record here", "record", "Record steps in this page: the editor writes them before this line", false]
|
|
1229
|
+
] : [],
|
|
1227
1230
|
["Finish", "finish", "Run on and pause no more in this test", false]
|
|
1228
1231
|
];
|
|
1229
1232
|
for (const [label, choice, hint, primary] of buttons) {
|
|
@@ -1985,21 +1988,21 @@ var MAX_INDETERMINATE = 20;
|
|
|
1985
1988
|
var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
|
|
1986
1989
|
var LEVEL = `${STEP}(?:/${STEP})*`;
|
|
1987
1990
|
var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
|
|
1988
|
-
function isElementPath(
|
|
1989
|
-
return typeof
|
|
1991
|
+
function isElementPath(path15) {
|
|
1992
|
+
return typeof path15 === "string" && path15.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path15);
|
|
1990
1993
|
}
|
|
1991
1994
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
1992
1995
|
function elementRef(v) {
|
|
1993
1996
|
if (!v || typeof v !== "object") return null;
|
|
1994
|
-
const { path:
|
|
1995
|
-
if (!isElementPath(
|
|
1996
|
-
return { path:
|
|
1997
|
+
const { path: path15, label } = v;
|
|
1998
|
+
if (!isElementPath(path15)) return null;
|
|
1999
|
+
return { path: path15, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
|
|
1997
2000
|
}
|
|
1998
2001
|
function textPoint(v) {
|
|
1999
2002
|
if (!v || typeof v !== "object") return null;
|
|
2000
|
-
const { path:
|
|
2003
|
+
const { path: path15, offset } = v;
|
|
2001
2004
|
const at = offsetOf(offset);
|
|
2002
|
-
return isElementPath(
|
|
2005
|
+
return isElementPath(path15) && at !== null ? { path: path15, offset: at } : null;
|
|
2003
2006
|
}
|
|
2004
2007
|
function sanitizePageInteraction(raw) {
|
|
2005
2008
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -2405,7 +2408,8 @@ var ATTACHMENT_NAMES = {
|
|
|
2405
2408
|
userPick: "piwi-user-pick",
|
|
2406
2409
|
aiUsage: "piwi-ai-usage",
|
|
2407
2410
|
aiMeta: "piwi-ai-meta",
|
|
2408
|
-
resources: "piwi-resources"
|
|
2411
|
+
resources: "piwi-resources",
|
|
2412
|
+
pauses: "piwi-pauses"
|
|
2409
2413
|
};
|
|
2410
2414
|
var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
|
|
2411
2415
|
var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
|
|
@@ -2639,13 +2643,19 @@ var LocatorPageLog = class {
|
|
|
2639
2643
|
};
|
|
2640
2644
|
|
|
2641
2645
|
// src/internal/probe/mode.ts
|
|
2642
|
-
var
|
|
2646
|
+
var fs3 = __toESM(require("fs"));
|
|
2647
|
+
|
|
2648
|
+
// src/internal/config/env.ts
|
|
2649
|
+
var path3 = __toESM(require("path"));
|
|
2643
2650
|
|
|
2644
2651
|
// src/internal/config/desktop.ts
|
|
2645
2652
|
var fs = __toESM(require("fs"));
|
|
2646
2653
|
var os = __toESM(require("os"));
|
|
2647
2654
|
var path2 = __toESM(require("path"));
|
|
2648
2655
|
|
|
2656
|
+
// src/internal/config/dotenv.ts
|
|
2657
|
+
var fs2 = __toESM(require("fs"));
|
|
2658
|
+
|
|
2649
2659
|
// src/internal/config/env.ts
|
|
2650
2660
|
var DEFAULTS = {
|
|
2651
2661
|
projectName: "default-project",
|
|
@@ -2682,6 +2692,7 @@ var DEFAULTS = {
|
|
|
2682
2692
|
};
|
|
2683
2693
|
var PIWI_ENV_KEYS = {
|
|
2684
2694
|
serverUrl: "PIWI_DASHBOARD_URL",
|
|
2695
|
+
desktopApp: "PIWI_DESKTOP_APP",
|
|
2685
2696
|
projectName: "PIWI_PROJECT_NAME",
|
|
2686
2697
|
verbose: "PIWI_VERBOSE",
|
|
2687
2698
|
apiKey: "PIWI_API_KEY",
|
|
@@ -2779,14 +2790,14 @@ var ENV_FALLBACK_SPECS = [
|
|
|
2779
2790
|
];
|
|
2780
2791
|
|
|
2781
2792
|
// src/internal/support/test-paths.ts
|
|
2782
|
-
var
|
|
2793
|
+
var path4 = __toESM(require("path"));
|
|
2783
2794
|
function configDirFromConfig(config) {
|
|
2784
|
-
if (config?.configFile) return
|
|
2795
|
+
if (config?.configFile) return path4.dirname(config.configFile);
|
|
2785
2796
|
if (config?.rootDir) return config.rootDir;
|
|
2786
2797
|
return process.cwd();
|
|
2787
2798
|
}
|
|
2788
2799
|
function relativeTestPath(file, base) {
|
|
2789
|
-
return
|
|
2800
|
+
return path4.relative(base, file).split(path4.sep).join("/");
|
|
2790
2801
|
}
|
|
2791
2802
|
|
|
2792
2803
|
// src/internal/probe/plan.ts
|
|
@@ -2796,8 +2807,8 @@ function parseRouteKey(routeKey2) {
|
|
|
2796
2807
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
2797
2808
|
}
|
|
2798
2809
|
function patternToRegex(pattern) {
|
|
2799
|
-
const
|
|
2800
|
-
const escaped =
|
|
2810
|
+
const path15 = pattern.split("?")[0] ?? pattern;
|
|
2811
|
+
const escaped = path15.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
2801
2812
|
return new RegExp(`^${escaped}$`);
|
|
2802
2813
|
}
|
|
2803
2814
|
function suitePathEqual(a, b) {
|
|
@@ -2820,13 +2831,13 @@ function matchProbeItem(plan, test) {
|
|
|
2820
2831
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
2821
2832
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
2822
2833
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
2823
|
-
let
|
|
2834
|
+
let path15;
|
|
2824
2835
|
try {
|
|
2825
|
-
|
|
2836
|
+
path15 = new URL(url).pathname;
|
|
2826
2837
|
} catch {
|
|
2827
|
-
|
|
2838
|
+
path15 = url.split("?")[0] ?? url;
|
|
2828
2839
|
}
|
|
2829
|
-
return patternToRegex(pattern).test(
|
|
2840
|
+
return patternToRegex(pattern).test(path15);
|
|
2830
2841
|
}
|
|
2831
2842
|
function createProbeState() {
|
|
2832
2843
|
return { navigated: false, matches: 0 };
|
|
@@ -2848,10 +2859,10 @@ function isProbeMode() {
|
|
|
2848
2859
|
function loadPlan() {
|
|
2849
2860
|
if (planCache !== void 0) return planCache;
|
|
2850
2861
|
planCache = null;
|
|
2851
|
-
const
|
|
2852
|
-
if (
|
|
2862
|
+
const path15 = process.env[PIWI_PROBE_ENV.plan];
|
|
2863
|
+
if (path15) {
|
|
2853
2864
|
try {
|
|
2854
|
-
planCache = JSON.parse(
|
|
2865
|
+
planCache = JSON.parse(fs3.readFileSync(path15, "utf8"));
|
|
2855
2866
|
} catch {
|
|
2856
2867
|
planCache = null;
|
|
2857
2868
|
}
|
|
@@ -2875,10 +2886,10 @@ function outcomeFromStatus(status, applied) {
|
|
|
2875
2886
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
2876
2887
|
}
|
|
2877
2888
|
function recordProbeOutcome(line) {
|
|
2878
|
-
const
|
|
2879
|
-
if (!
|
|
2889
|
+
const path15 = process.env[PIWI_PROBE_ENV.results];
|
|
2890
|
+
if (!path15) return;
|
|
2880
2891
|
try {
|
|
2881
|
-
|
|
2892
|
+
fs3.appendFileSync(path15, `${JSON.stringify(line)}
|
|
2882
2893
|
`);
|
|
2883
2894
|
} catch {
|
|
2884
2895
|
}
|
|
@@ -3008,7 +3019,7 @@ function originOf(url) {
|
|
|
3008
3019
|
return null;
|
|
3009
3020
|
}
|
|
3010
3021
|
}
|
|
3011
|
-
var sleep = (ms) => new Promise((
|
|
3022
|
+
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, Math.max(0, ms)));
|
|
3012
3023
|
async function performRouteAction(route, action, startedAt) {
|
|
3013
3024
|
switch (action.kind) {
|
|
3014
3025
|
case "abort":
|
|
@@ -3119,7 +3130,7 @@ async function installProbeInterception(page, item) {
|
|
|
3119
3130
|
}
|
|
3120
3131
|
|
|
3121
3132
|
// src/internal/flake/mode.ts
|
|
3122
|
-
var
|
|
3133
|
+
var fs4 = __toESM(require("fs"));
|
|
3123
3134
|
|
|
3124
3135
|
// ../core/src/error-parse.ts
|
|
3125
3136
|
var ANSI_RE = new RegExp("\\u001B\\[[0-9;]*m", "g");
|
|
@@ -3430,7 +3441,7 @@ function loadFlakePlan() {
|
|
|
3430
3441
|
if (planCache2 && planCache2.file === file) return planCache2.plan;
|
|
3431
3442
|
let text;
|
|
3432
3443
|
try {
|
|
3433
|
-
text =
|
|
3444
|
+
text = fs4.readFileSync(file, "utf8");
|
|
3434
3445
|
} catch (error) {
|
|
3435
3446
|
throw new Error(`[Piwi Dashboard] Cannot read the flake plan ${file}: ${errorMessage(error)}`);
|
|
3436
3447
|
}
|
|
@@ -3583,7 +3594,7 @@ function recordFlakeResult(line) {
|
|
|
3583
3594
|
const file = process.env[PIWI_FLAKE_ENV.results]?.trim();
|
|
3584
3595
|
if (!file) return;
|
|
3585
3596
|
try {
|
|
3586
|
-
|
|
3597
|
+
fs4.appendFileSync(file, `${JSON.stringify(line)}
|
|
3587
3598
|
`);
|
|
3588
3599
|
} catch {
|
|
3589
3600
|
}
|
|
@@ -3649,13 +3660,13 @@ function normalizeFunctionRanges(ranges) {
|
|
|
3649
3660
|
}
|
|
3650
3661
|
function normalizeCoveragePath(raw) {
|
|
3651
3662
|
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
3652
|
-
const
|
|
3653
|
-
if (!
|
|
3654
|
-
if (/(^|\/)node_modules\//.test(
|
|
3655
|
-
return
|
|
3663
|
+
const path15 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3664
|
+
if (!path15 || path15.startsWith("../") || path15.startsWith("/") || /^[A-Za-z]:\//.test(path15)) return null;
|
|
3665
|
+
if (/(^|\/)node_modules\//.test(path15)) return null;
|
|
3666
|
+
return path15;
|
|
3656
3667
|
}
|
|
3657
|
-
function fileEntry(
|
|
3658
|
-
const file = { path:
|
|
3668
|
+
function fileEntry(path15, runtime, blob, functions = [], lines = []) {
|
|
3669
|
+
const file = { path: path15, runtime };
|
|
3659
3670
|
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
3660
3671
|
if (functions.length > 0) file.functions = functions;
|
|
3661
3672
|
if (lines.length > 0) file.lines = lines;
|
|
@@ -3674,11 +3685,11 @@ function sanitizeCoverage(raw) {
|
|
|
3674
3685
|
const files = [];
|
|
3675
3686
|
for (const item of raw.files) {
|
|
3676
3687
|
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
3677
|
-
const
|
|
3678
|
-
if (
|
|
3688
|
+
const path15 = normalizeCoveragePath(item.path);
|
|
3689
|
+
if (path15 === null) continue;
|
|
3679
3690
|
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
3680
3691
|
files.push(
|
|
3681
|
-
fileEntry(
|
|
3692
|
+
fileEntry(path15, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
3682
3693
|
);
|
|
3683
3694
|
}
|
|
3684
3695
|
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
@@ -4089,7 +4100,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
|
|
|
4089
4100
|
}
|
|
4090
4101
|
|
|
4091
4102
|
// src/internal/capture/pick-on-failure.ts
|
|
4092
|
-
var
|
|
4103
|
+
var path5 = __toESM(require("path"));
|
|
4093
4104
|
function parseLeafLocatorExpression(rawExpr) {
|
|
4094
4105
|
const leaf = parseLeafLocatorCall(rawExpr);
|
|
4095
4106
|
return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
|
|
@@ -4104,37 +4115,60 @@ ${err.stack ?? ""}`));
|
|
|
4104
4115
|
const parsed2 = parseLeafLocatorExpression(header);
|
|
4105
4116
|
if (!parsed2) continue;
|
|
4106
4117
|
const loc = err.location;
|
|
4107
|
-
const location = loc ? `${
|
|
4118
|
+
const location = loc ? `${path5.relative(process.cwd(), loc.file).split(path5.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
|
4108
4119
|
return { method: parsed2.method, args: parsed2.args, location };
|
|
4109
4120
|
}
|
|
4110
4121
|
return null;
|
|
4111
4122
|
}
|
|
4112
|
-
|
|
4123
|
+
var PICK_ENDED = "__piwiPickEnded";
|
|
4124
|
+
async function cleanupPicker(page, ended = false) {
|
|
4113
4125
|
try {
|
|
4114
|
-
await page.evaluate(
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4126
|
+
await page.evaluate(
|
|
4127
|
+
([endedKey, unanswered]) => {
|
|
4128
|
+
const g = globalThis;
|
|
4129
|
+
if (typeof g.__piwiPickCleanup === "function") g.__piwiPickCleanup();
|
|
4130
|
+
if (typeof g.__piwiAnchorCleanup === "function") g.__piwiAnchorCleanup();
|
|
4131
|
+
delete g.__piwiPickCleanup;
|
|
4132
|
+
delete g.__piwiAnchorCleanup;
|
|
4133
|
+
delete g.__piwiPickState;
|
|
4134
|
+
delete g.__piwiPickChoice;
|
|
4135
|
+
delete g.__piwiPickedElement;
|
|
4136
|
+
delete g.__piwiAnchorState;
|
|
4137
|
+
delete g.__piwiPickAnchors;
|
|
4138
|
+
delete g.__piwiPickChainCount;
|
|
4139
|
+
if (unanswered) g[endedKey] = true;
|
|
4140
|
+
else delete g[endedKey];
|
|
4141
|
+
},
|
|
4142
|
+
[PICK_ENDED, ended]
|
|
4143
|
+
);
|
|
4127
4144
|
} catch {
|
|
4128
4145
|
}
|
|
4129
4146
|
}
|
|
4130
|
-
|
|
4147
|
+
function unlessEnded(run, ended) {
|
|
4148
|
+
if (!ended) return run;
|
|
4149
|
+
return Promise.race([
|
|
4150
|
+
run,
|
|
4151
|
+
new Promise((resolve5) => {
|
|
4152
|
+
if (ended.aborted) resolve5(void 0);
|
|
4153
|
+
else ended.addEventListener("abort", () => resolve5(void 0), { once: true });
|
|
4154
|
+
})
|
|
4155
|
+
]);
|
|
4156
|
+
}
|
|
4157
|
+
async function pickLocator(page, probe, failing, ended) {
|
|
4158
|
+
const answered = async (name) => {
|
|
4159
|
+
const wait = page.waitForFunction(
|
|
4160
|
+
([key, endedKey]) => globalThis[key] !== void 0 || globalThis[endedKey] === true,
|
|
4161
|
+
[name, PICK_ENDED],
|
|
4162
|
+
{ timeout: 0, polling: 250 }
|
|
4163
|
+
);
|
|
4164
|
+
await unlessEnded(wait, ended);
|
|
4165
|
+
return !ended?.aborted;
|
|
4166
|
+
};
|
|
4131
4167
|
try {
|
|
4132
4168
|
const overlayArg = { transport: "global", failing };
|
|
4169
|
+
await page.evaluate((endedKey) => delete globalThis[endedKey], PICK_ENDED);
|
|
4133
4170
|
await page.evaluate(installPickerOverlay, overlayArg);
|
|
4134
|
-
await
|
|
4135
|
-
timeout: 0,
|
|
4136
|
-
polling: 250
|
|
4137
|
-
});
|
|
4171
|
+
if (!await answered("__piwiPickState")) return null;
|
|
4138
4172
|
const state = await page.evaluate(() => globalThis.__piwiPickState);
|
|
4139
4173
|
if (state !== "picked") return null;
|
|
4140
4174
|
const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
|
|
@@ -4155,10 +4189,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
4155
4189
|
leafLevel: level,
|
|
4156
4190
|
leafTestId: attrs.attributes["data-testid"] ?? null
|
|
4157
4191
|
});
|
|
4158
|
-
await
|
|
4159
|
-
timeout: 0,
|
|
4160
|
-
polling: 250
|
|
4161
|
-
});
|
|
4192
|
+
if (!await answered("__piwiAnchorState")) return null;
|
|
4162
4193
|
const anchorResult = await page.evaluate(() => {
|
|
4163
4194
|
const g = globalThis;
|
|
4164
4195
|
return { state: g.__piwiAnchorState, anchors: g.__piwiPickAnchors ?? [], chainCount: g.__piwiPickChainCount };
|
|
@@ -4180,10 +4211,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
4180
4211
|
failing,
|
|
4181
4212
|
choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
|
|
4182
4213
|
});
|
|
4183
|
-
await
|
|
4184
|
-
timeout: 0,
|
|
4185
|
-
polling: 250
|
|
4186
|
-
});
|
|
4214
|
+
if (!await answered("__piwiPickChoice")) return null;
|
|
4187
4215
|
const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
|
|
4188
4216
|
if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
|
|
4189
4217
|
const picked = { ...ranked[choice], pickedByUser: true };
|
|
@@ -4202,7 +4230,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
4202
4230
|
...anchors.length > 0 ? { anchors } : {}
|
|
4203
4231
|
};
|
|
4204
4232
|
} finally {
|
|
4205
|
-
await cleanupPicker(page);
|
|
4233
|
+
await cleanupPicker(page, ended?.aborted);
|
|
4206
4234
|
}
|
|
4207
4235
|
}
|
|
4208
4236
|
async function runLocatorPicker(page, testInfo, failed, probe) {
|
|
@@ -4257,7 +4285,7 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
4257
4285
|
}
|
|
4258
4286
|
|
|
4259
4287
|
// src/internal/capture/pause-at.ts
|
|
4260
|
-
var
|
|
4288
|
+
var path8 = __toESM(require("path"));
|
|
4261
4289
|
|
|
4262
4290
|
// ../core/src/editor-send.ts
|
|
4263
4291
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
@@ -4283,6 +4311,93 @@ function isInsidePath(file) {
|
|
|
4283
4311
|
return file.split(/[\\/]/).every((part) => part !== "..");
|
|
4284
4312
|
}
|
|
4285
4313
|
|
|
4314
|
+
// src/internal/capture/quiet-capture.ts
|
|
4315
|
+
var import_node_module = require("module");
|
|
4316
|
+
var path7 = __toESM(require("path"));
|
|
4317
|
+
|
|
4318
|
+
// src/internal/support/reporter-version.ts
|
|
4319
|
+
var fs5 = __toESM(require("fs"));
|
|
4320
|
+
var path6 = __toESM(require("path"));
|
|
4321
|
+
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
4322
|
+
function findOwnPackageJson(fromDir) {
|
|
4323
|
+
for (const offset of CANDIDATE_OFFSETS) {
|
|
4324
|
+
try {
|
|
4325
|
+
const pkgPath = path6.resolve(fromDir, offset, "package.json");
|
|
4326
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
4327
|
+
const { name, version } = pkg;
|
|
4328
|
+
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
4329
|
+
return { version, root: path6.dirname(pkgPath) };
|
|
4330
|
+
} catch {
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
return void 0;
|
|
4334
|
+
}
|
|
4335
|
+
|
|
4336
|
+
// src/internal/capture/quiet-capture.ts
|
|
4337
|
+
var BOXED_PREFIX_EXPORTS = [
|
|
4338
|
+
[
|
|
4339
|
+
"playwright-core/lib/coreBundle",
|
|
4340
|
+
(mod) => mod?.utils?.setBoxedStackPrefixes
|
|
4341
|
+
],
|
|
4342
|
+
["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
|
|
4343
|
+
];
|
|
4344
|
+
var boxing;
|
|
4345
|
+
var resolvedPlaywrightPackageJson;
|
|
4346
|
+
function resolvePlaywrightPackageJson() {
|
|
4347
|
+
if (resolvedPlaywrightPackageJson !== void 0) return resolvedPlaywrightPackageJson;
|
|
4348
|
+
try {
|
|
4349
|
+
const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
|
|
4350
|
+
resolvedPlaywrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
|
|
4351
|
+
} catch {
|
|
4352
|
+
resolvedPlaywrightPackageJson = null;
|
|
4353
|
+
}
|
|
4354
|
+
return resolvedPlaywrightPackageJson;
|
|
4355
|
+
}
|
|
4356
|
+
function resolveBoxing() {
|
|
4357
|
+
try {
|
|
4358
|
+
const root = findOwnPackageJson(__dirname)?.root;
|
|
4359
|
+
if (!root) return null;
|
|
4360
|
+
const playwrightPackageJson = resolvePlaywrightPackageJson();
|
|
4361
|
+
if (!playwrightPackageJson) return null;
|
|
4362
|
+
const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
|
|
4363
|
+
for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
|
|
4364
|
+
let set;
|
|
4365
|
+
try {
|
|
4366
|
+
set = pick(requireFromPlaywright(moduleId));
|
|
4367
|
+
} catch {
|
|
4368
|
+
continue;
|
|
4369
|
+
}
|
|
4370
|
+
if (typeof set !== "function") continue;
|
|
4371
|
+
return {
|
|
4372
|
+
set,
|
|
4373
|
+
prefixes: [path7.dirname(playwrightPackageJson), path7.join(root, "dist") + path7.sep]
|
|
4374
|
+
};
|
|
4375
|
+
}
|
|
4376
|
+
} catch {
|
|
4377
|
+
}
|
|
4378
|
+
return null;
|
|
4379
|
+
}
|
|
4380
|
+
function boxCaptureFrames(extraPrefixes = []) {
|
|
4381
|
+
const first = boxing === void 0;
|
|
4382
|
+
if (first) boxing = resolveBoxing();
|
|
4383
|
+
if (!boxing) return;
|
|
4384
|
+
const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
|
|
4385
|
+
if (!first && added.length === 0) return;
|
|
4386
|
+
boxing.prefixes.push(...added);
|
|
4387
|
+
try {
|
|
4388
|
+
boxing.set([...boxing.prefixes]);
|
|
4389
|
+
} catch {
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
function callMethod(target, prop, args) {
|
|
4393
|
+
return target[prop](...args);
|
|
4394
|
+
}
|
|
4395
|
+
function internalCall(owner, run) {
|
|
4396
|
+
const wrap = owner?._wrapApiCall;
|
|
4397
|
+
if (typeof wrap !== "function") return run();
|
|
4398
|
+
return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4286
4401
|
// src/internal/capture/pause-at.ts
|
|
4287
4402
|
var PauseSet = class {
|
|
4288
4403
|
constructor(lines) {
|
|
@@ -4298,18 +4413,32 @@ var PauseSet = class {
|
|
|
4298
4413
|
return col > 0 && this.lines.has(callerLocation.slice(0, col));
|
|
4299
4414
|
}
|
|
4300
4415
|
};
|
|
4301
|
-
var
|
|
4416
|
+
var NO_BREAKPOINTS = new PauseSet(/* @__PURE__ */ new Set());
|
|
4302
4417
|
function callSiteFile(file, cwd) {
|
|
4303
4418
|
const slashed = file.replace(/\\/g, "/");
|
|
4304
|
-
const absolute =
|
|
4305
|
-
let rel = absolute ?
|
|
4306
|
-
rel = rel.split(
|
|
4419
|
+
const absolute = path8.isAbsolute(file) || path8.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
|
|
4420
|
+
let rel = absolute ? path8.relative(cwd, file) : path8.normalize(slashed);
|
|
4421
|
+
rel = rel.split(path8.sep).join("/").replace(/\\/g, "/");
|
|
4307
4422
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
4308
4423
|
return rel;
|
|
4309
4424
|
}
|
|
4425
|
+
function stopCallSite(location, cwd = process.cwd()) {
|
|
4426
|
+
if (!location.line) return null;
|
|
4427
|
+
return `${callSiteFile(location.file, cwd)}:${location.line}:${location.column ?? 0}`;
|
|
4428
|
+
}
|
|
4429
|
+
function callSiteLine(callSite) {
|
|
4430
|
+
const col = callSite.lastIndexOf(":");
|
|
4431
|
+
return col > 0 ? callSite.slice(0, col) : callSite;
|
|
4432
|
+
}
|
|
4433
|
+
function inFlightCallSite(inFlight) {
|
|
4434
|
+
const [first] = inFlight;
|
|
4435
|
+
if (first === void 0) return null;
|
|
4436
|
+
const line = callSiteLine(first);
|
|
4437
|
+
return inFlight.every((site) => callSiteLine(site) === line) ? first : null;
|
|
4438
|
+
}
|
|
4310
4439
|
var ignoredLogged = /* @__PURE__ */ new Set();
|
|
4311
4440
|
function parsePauseAt(value, cwd = process.cwd()) {
|
|
4312
|
-
if (!value?.trim()) return
|
|
4441
|
+
if (!value?.trim()) return NO_BREAKPOINTS;
|
|
4313
4442
|
const lines = /* @__PURE__ */ new Set();
|
|
4314
4443
|
for (const raw of value.split(";")) {
|
|
4315
4444
|
const entry = raw.trim();
|
|
@@ -4325,7 +4454,7 @@ function parsePauseAt(value, cwd = process.cwd()) {
|
|
|
4325
4454
|
}
|
|
4326
4455
|
lines.add(`${callSiteFile(m[1], cwd)}:${line}`);
|
|
4327
4456
|
}
|
|
4328
|
-
return lines.size ? new PauseSet(lines) :
|
|
4457
|
+
return lines.size ? new PauseSet(lines) : NO_BREAKPOINTS;
|
|
4329
4458
|
}
|
|
4330
4459
|
var parsed = null;
|
|
4331
4460
|
function currentPauseSet() {
|
|
@@ -4335,27 +4464,121 @@ function currentPauseSet() {
|
|
|
4335
4464
|
}
|
|
4336
4465
|
function pauseGateFromTestInfo(testInfo) {
|
|
4337
4466
|
const use = testInfo.project?.use ?? {};
|
|
4338
|
-
|
|
4467
|
+
const debug = testInfo._configInternal?.configCLIOverrides?.debug;
|
|
4468
|
+
return { ci: process.env.CI, headless: use.headless, pwdebug: process.env.PWDEBUG, debug };
|
|
4339
4469
|
}
|
|
4340
4470
|
function pauseSkipReason(gate) {
|
|
4341
4471
|
if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
|
|
4342
4472
|
if (gate.headless !== false)
|
|
4343
4473
|
return "the browser is headless \u2014 re-run with --headed (or set use: { headless: false })";
|
|
4474
|
+
if (gate.pwdebug && !["0", "false", "console"].includes(gate.pwdebug))
|
|
4475
|
+
return "Playwright's Inspector runs the test (PWDEBUG) \u2014 run without --debug";
|
|
4476
|
+
if (typeof gate.debug === "string" && gate.debug)
|
|
4477
|
+
return `Playwright's debugger runs the test (--debug=${gate.debug}) \u2014 run without --debug`;
|
|
4344
4478
|
return null;
|
|
4345
4479
|
}
|
|
4346
|
-
function
|
|
4347
|
-
|
|
4480
|
+
function atStop(state, callSite) {
|
|
4481
|
+
if (callSite && !state.finished) {
|
|
4482
|
+
if (state.step) {
|
|
4483
|
+
state.step = false;
|
|
4484
|
+
return { show: "step" };
|
|
4485
|
+
}
|
|
4486
|
+
if (state.set.matches(callSite)) return { show: "breakpoint" };
|
|
4487
|
+
}
|
|
4488
|
+
return { go: goOn(state) };
|
|
4489
|
+
}
|
|
4490
|
+
function goOn(state, answer) {
|
|
4491
|
+
if (answer === "step") state.step = true;
|
|
4492
|
+
else if (answer === "finish") state.finished = true;
|
|
4493
|
+
return !state.finished && (state.step || state.set.size > 0) ? "next" : "resume";
|
|
4494
|
+
}
|
|
4495
|
+
function contextDebugger(context) {
|
|
4496
|
+
const dbg = context.debugger;
|
|
4497
|
+
return typeof dbg?.requestPause === "function" && typeof dbg.on === "function" ? dbg : null;
|
|
4498
|
+
}
|
|
4499
|
+
var WATCHED = /* @__PURE__ */ new WeakSet();
|
|
4500
|
+
function watchStops(context, onStop) {
|
|
4501
|
+
const dbg = contextDebugger(context);
|
|
4502
|
+
if (!dbg) return false;
|
|
4503
|
+
if (WATCHED.has(context)) return true;
|
|
4504
|
+
WATCHED.add(context);
|
|
4505
|
+
inspectorSwitch.watching = true;
|
|
4506
|
+
syncInspectorSwitch();
|
|
4507
|
+
dbg.on("pausedstatechanged", () => onStop(context));
|
|
4508
|
+
return true;
|
|
4509
|
+
}
|
|
4510
|
+
var NO_INSPECTOR_ENV = "PW_CODEGEN_NO_INSPECTOR";
|
|
4511
|
+
var inspectorSwitch = {
|
|
4512
|
+
/** Set by Piwi, so Piwi's to clear. */
|
|
4513
|
+
set: false,
|
|
4514
|
+
/** Piwi answers the stops of a context in this worker. */
|
|
4515
|
+
watching: false,
|
|
4516
|
+
/** Every `page.pause()` of the worker's Playwright reaches it through Piwi (`markPausesWrapped`). */
|
|
4517
|
+
wrapped: false,
|
|
4518
|
+
/** The `page.pause()` calls left to Playwright that are running (`withInspector`). */
|
|
4519
|
+
open: 0
|
|
4520
|
+
};
|
|
4521
|
+
function syncInspectorSwitch() {
|
|
4522
|
+
const closed = inspectorSwitch.watching && inspectorSwitch.wrapped && inspectorSwitch.open === 0;
|
|
4523
|
+
if (closed && process.env[NO_INSPECTOR_ENV] === void 0) {
|
|
4524
|
+
process.env[NO_INSPECTOR_ENV] = "1";
|
|
4525
|
+
inspectorSwitch.set = true;
|
|
4526
|
+
} else if (!closed && inspectorSwitch.set) {
|
|
4527
|
+
if (process.env[NO_INSPECTOR_ENV] === "1") delete process.env[NO_INSPECTOR_ENV];
|
|
4528
|
+
inspectorSwitch.set = false;
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
function markPausesWrapped() {
|
|
4532
|
+
inspectorSwitch.wrapped = true;
|
|
4533
|
+
syncInspectorSwitch();
|
|
4534
|
+
}
|
|
4535
|
+
async function withInspector(run) {
|
|
4536
|
+
inspectorSwitch.open++;
|
|
4537
|
+
syncInspectorSwitch();
|
|
4538
|
+
try {
|
|
4539
|
+
return await run();
|
|
4540
|
+
} finally {
|
|
4541
|
+
inspectorSwitch.open--;
|
|
4542
|
+
syncInspectorSwitch();
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
async function steerDebugger(context, how) {
|
|
4546
|
+
const dbg = contextDebugger(context);
|
|
4547
|
+
if (!dbg) return;
|
|
4548
|
+
try {
|
|
4549
|
+
await internalCall(
|
|
4550
|
+
context,
|
|
4551
|
+
() => how === "pause" ? dbg.requestPause() : how === "next" ? dbg.next() : dbg.resume()
|
|
4552
|
+
);
|
|
4553
|
+
} catch {
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
async function withTestTimeoutHeld(testInfo, run) {
|
|
4557
|
+
const set = testInfo._setIgnoreTimeouts;
|
|
4558
|
+
if (typeof set !== "function") return run();
|
|
4559
|
+
set.call(testInfo, true);
|
|
4560
|
+
try {
|
|
4561
|
+
return await run();
|
|
4562
|
+
} finally {
|
|
4563
|
+
set.call(testInfo, false);
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
function testTimeSlot(testInfo) {
|
|
4567
|
+
try {
|
|
4568
|
+
const slot = testInfo._timeoutManager?.defaultSlot?.();
|
|
4569
|
+
return slot && typeof slot.elapsed === "number" ? slot : null;
|
|
4570
|
+
} catch {
|
|
4571
|
+
return null;
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
function restoreTestTime(testInfo, heldAt) {
|
|
4575
|
+
const slot = testTimeSlot(testInfo);
|
|
4576
|
+
if (slot && Number.isFinite(heldAt) && heldAt >= 0 && heldAt < slot.elapsed) slot.elapsed = heldAt;
|
|
4348
4577
|
}
|
|
4349
4578
|
function pausePlace(callerLocation) {
|
|
4350
|
-
const
|
|
4351
|
-
const fileLine = col > 0 ? callerLocation.slice(0, col) : callerLocation;
|
|
4579
|
+
const fileLine = callSiteLine(callerLocation);
|
|
4352
4580
|
return fileLine.slice(fileLine.lastIndexOf("/") + 1);
|
|
4353
4581
|
}
|
|
4354
|
-
function assertionLabel(expression, isNot) {
|
|
4355
|
-
const [first = "", ...rest] = expression.split(".").filter(Boolean);
|
|
4356
|
-
const name = first + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
4357
|
-
return `${isNot ? "not." : ""}${name || "expect"}`;
|
|
4358
|
-
}
|
|
4359
4582
|
function pickPlace(callerLocation) {
|
|
4360
4583
|
const m = /^(.+):(\d+):\d+$/.exec(callerLocation);
|
|
4361
4584
|
if (!m) return null;
|
|
@@ -4400,59 +4623,312 @@ function logPauseSkipOnce(reason) {
|
|
|
4400
4623
|
skipLogged = true;
|
|
4401
4624
|
console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt} is set but breakpoints are skipped: ${reason}`);
|
|
4402
4625
|
}
|
|
4626
|
+
var NAVIGATED = /Execution context was destroyed|Cannot find context with specified id/;
|
|
4627
|
+
var MAX_NAVIGATED = 10;
|
|
4403
4628
|
var HIGHLIGHT_MS = 2e3;
|
|
4404
|
-
async function pauseHere(page,
|
|
4405
|
-
const before = testInfo.timeout;
|
|
4406
|
-
const started = Date.now();
|
|
4629
|
+
async function pauseHere(page, point, probe, onPick, onRecord, ended) {
|
|
4407
4630
|
let shown = null;
|
|
4408
4631
|
try {
|
|
4409
|
-
testInfo.setTimeout(0);
|
|
4410
4632
|
const highlight = point.target?.highlight;
|
|
4411
4633
|
if (typeof highlight === "function") {
|
|
4412
4634
|
let timer;
|
|
4413
4635
|
shown = await Promise.race([
|
|
4414
4636
|
Promise.resolve().then(() => highlight.call(point.target)).catch(() => null),
|
|
4415
|
-
new Promise((
|
|
4416
|
-
timer = setTimeout(() =>
|
|
4637
|
+
new Promise((resolve5) => {
|
|
4638
|
+
timer = setTimeout(() => resolve5(null), HIGHLIGHT_MS);
|
|
4417
4639
|
})
|
|
4418
4640
|
]);
|
|
4419
4641
|
clearTimeout(timer);
|
|
4420
4642
|
}
|
|
4421
4643
|
const arg = {
|
|
4422
4644
|
place: pausePlace(point.location),
|
|
4423
|
-
attempt:
|
|
4424
|
-
action: point.
|
|
4425
|
-
locator:
|
|
4645
|
+
attempt: point.attempt,
|
|
4646
|
+
action: point.call,
|
|
4647
|
+
locator: null,
|
|
4648
|
+
record: !!onRecord
|
|
4426
4649
|
};
|
|
4427
4650
|
console.log(
|
|
4428
|
-
`[piwi] Paused at ${point.location} before ${point.
|
|
4651
|
+
`[piwi] Paused at ${callSiteLine(point.location)} before ${point.call}: Resume, Step, Pick a locator${onRecord ? ", Record here" : ""} or Finish in the browser (Esc resumes).`
|
|
4429
4652
|
);
|
|
4653
|
+
let navigated = 0;
|
|
4430
4654
|
for (; ; ) {
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4655
|
+
try {
|
|
4656
|
+
if (ended?.aborted) return "resume";
|
|
4657
|
+
await page.evaluate(showPauseBar, arg);
|
|
4658
|
+
await unlessEnded(page.waitForFunction(pauseAnswered, void 0, { timeout: 0, polling: 250 }), ended);
|
|
4659
|
+
if (ended?.aborted) return "resume";
|
|
4660
|
+
const choice = await page.evaluate(takePauseState);
|
|
4661
|
+
navigated = 0;
|
|
4662
|
+
if (choice === "resume" || choice === "step" || choice === "finish") return choice;
|
|
4663
|
+
if (choice === "record" && onRecord) {
|
|
4664
|
+
await unlessEnded(
|
|
4665
|
+
onRecord(ended).catch(() => void 0),
|
|
4666
|
+
ended
|
|
4667
|
+
);
|
|
4668
|
+
continue;
|
|
4669
|
+
}
|
|
4670
|
+
if (choice !== "pick") continue;
|
|
4671
|
+
const picked = await pickLocator(page, probe, null, ended).catch(() => null);
|
|
4672
|
+
if (picked && !ended?.aborted) await onPick(picked);
|
|
4673
|
+
} catch (error) {
|
|
4674
|
+
if (page.isClosed() || !NAVIGATED.test(String(error)) || ++navigated > MAX_NAVIGATED) throw error;
|
|
4675
|
+
await page.waitForLoadState("domcontentloaded").catch(() => void 0);
|
|
4676
|
+
}
|
|
4438
4677
|
}
|
|
4439
4678
|
} catch {
|
|
4440
4679
|
return "resume";
|
|
4441
4680
|
} finally {
|
|
4442
4681
|
try {
|
|
4682
|
+
if (ended?.aborted) await page.evaluate(takePauseState);
|
|
4443
4683
|
const dispose = shown?.dispose;
|
|
4444
4684
|
const hide = page.hideHighlight;
|
|
4445
4685
|
if (typeof dispose === "function") await dispose.call(shown);
|
|
4446
4686
|
else if (typeof hide === "function") await hide.call(page);
|
|
4447
4687
|
} catch {
|
|
4448
4688
|
}
|
|
4449
|
-
try {
|
|
4450
|
-
testInfo.setTimeout(rearmedTimeout(before, Date.now() - started));
|
|
4451
|
-
} catch {
|
|
4452
|
-
}
|
|
4453
4689
|
}
|
|
4454
4690
|
}
|
|
4455
4691
|
|
|
4692
|
+
// src/internal/capture/record-handoff.ts
|
|
4693
|
+
var fs6 = __toESM(require("fs"));
|
|
4694
|
+
var net = __toESM(require("net"));
|
|
4695
|
+
var path9 = __toESM(require("path"));
|
|
4696
|
+
|
|
4697
|
+
// ../core/src/ide-recorder.ts
|
|
4698
|
+
var IDE_RECORDER_BINDING = "__piwiRecorder";
|
|
4699
|
+
var IDE_DISPATCH_GLOBAL = "__piwiIdeDispatch";
|
|
4700
|
+
var RECORD_CHANNEL_ENV = "PIWI_RECORD_CHANNEL";
|
|
4701
|
+
var MAX_HANDOFF_LINE = 64e6;
|
|
4702
|
+
function parseRecordChannel(value) {
|
|
4703
|
+
const text = value?.trim() ?? "";
|
|
4704
|
+
const hash = text.lastIndexOf("#");
|
|
4705
|
+
if (hash <= 0) return null;
|
|
4706
|
+
const token = text.slice(hash + 1);
|
|
4707
|
+
const path15 = text.slice(0, hash);
|
|
4708
|
+
return /^[A-Za-z0-9_-]{16,}$/.test(token) && !path15.includes("\0") ? { path: path15, token } : null;
|
|
4709
|
+
}
|
|
4710
|
+
function isObject2(value) {
|
|
4711
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4712
|
+
}
|
|
4713
|
+
function parseHandoffToTest(line) {
|
|
4714
|
+
let value;
|
|
4715
|
+
try {
|
|
4716
|
+
value = JSON.parse(line);
|
|
4717
|
+
} catch {
|
|
4718
|
+
return null;
|
|
4719
|
+
}
|
|
4720
|
+
if (!isObject2(value)) return null;
|
|
4721
|
+
switch (value.type) {
|
|
4722
|
+
case "refused":
|
|
4723
|
+
return typeof value.message === "string" ? { type: "refused", message: value.message } : null;
|
|
4724
|
+
case "load":
|
|
4725
|
+
return typeof value.bundle === "string" && value.bundle ? { type: "load", bundle: value.bundle } : null;
|
|
4726
|
+
case "answer":
|
|
4727
|
+
return typeof value.call === "number" ? { type: "answer", call: value.call, value: value.value } : null;
|
|
4728
|
+
case "dispatch":
|
|
4729
|
+
return { type: "dispatch", message: value.message };
|
|
4730
|
+
case "stop":
|
|
4731
|
+
return { type: "stop" };
|
|
4732
|
+
default:
|
|
4733
|
+
return null;
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
function lineReader(onLine, onOverflow) {
|
|
4737
|
+
let buffered = "";
|
|
4738
|
+
return (chunk) => {
|
|
4739
|
+
buffered += chunk;
|
|
4740
|
+
for (let end = buffered.indexOf("\n"); end >= 0; end = buffered.indexOf("\n")) {
|
|
4741
|
+
const line = buffered.slice(0, end);
|
|
4742
|
+
buffered = buffered.slice(end + 1);
|
|
4743
|
+
if (line.trim()) onLine(line);
|
|
4744
|
+
}
|
|
4745
|
+
if (buffered.length > MAX_HANDOFF_LINE) {
|
|
4746
|
+
buffered = "";
|
|
4747
|
+
onOverflow();
|
|
4748
|
+
}
|
|
4749
|
+
};
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4752
|
+
// src/internal/capture/record-handoff.ts
|
|
4753
|
+
var START_WAIT_MS = 6e4;
|
|
4754
|
+
var NEW_TAB_NOTICE = "A new tab opened: what you do there is not recorded yet. Record in the first tab.";
|
|
4755
|
+
var relays = /* @__PURE__ */ new WeakMap();
|
|
4756
|
+
function idleAnswer(payload) {
|
|
4757
|
+
const request = payload ?? {};
|
|
4758
|
+
if (request.kind === "local-get") return {};
|
|
4759
|
+
if (request.kind !== "message") return null;
|
|
4760
|
+
if (request.message?.type === "piwi-ping") return { ok: true };
|
|
4761
|
+
if (request.message?.type === "piwi-session-storage") {
|
|
4762
|
+
return request.message.op === "get" ? { ok: true, items: {} } : { ok: true };
|
|
4763
|
+
}
|
|
4764
|
+
return { ok: false, error: "No recording runs in this page." };
|
|
4765
|
+
}
|
|
4766
|
+
function canRecordFromHere(channel = process.env[RECORD_CHANNEL_ENV]) {
|
|
4767
|
+
return parseRecordChannel(channel) !== null;
|
|
4768
|
+
}
|
|
4769
|
+
function handoffPlace(location, cwd = process.cwd()) {
|
|
4770
|
+
const m = /^(.+):(\d+):\d+$/.exec(location);
|
|
4771
|
+
if (!m) return null;
|
|
4772
|
+
return { file: path9.resolve(cwd, m[1]), line: Number(m[2]) };
|
|
4773
|
+
}
|
|
4774
|
+
async function recordFromHere(options) {
|
|
4775
|
+
const channel = parseRecordChannel(options.channel ?? process.env[RECORD_CHANNEL_ENV]);
|
|
4776
|
+
const place = handoffPlace(options.location);
|
|
4777
|
+
if (!channel || !place) return "this run names no editor to record into.";
|
|
4778
|
+
const { page, testInfo, ended } = options;
|
|
4779
|
+
if (ended?.aborted) return "the test ended.";
|
|
4780
|
+
const use = testInfo.project?.use ?? {};
|
|
4781
|
+
const context = page.context();
|
|
4782
|
+
return new Promise((resolve5) => {
|
|
4783
|
+
const socket = net.connect(channel.path);
|
|
4784
|
+
socket.setEncoding("utf8");
|
|
4785
|
+
const answers = /* @__PURE__ */ new Map();
|
|
4786
|
+
let calls = 0;
|
|
4787
|
+
let attached = false;
|
|
4788
|
+
let recording = false;
|
|
4789
|
+
let settled = false;
|
|
4790
|
+
let initScript;
|
|
4791
|
+
const send = (message) => {
|
|
4792
|
+
if (!socket.destroyed) socket.write(`${JSON.stringify(message)}
|
|
4793
|
+
`);
|
|
4794
|
+
};
|
|
4795
|
+
const onPage = () => send({ type: "notice", message: NEW_TAB_NOTICE });
|
|
4796
|
+
const onGone = () => {
|
|
4797
|
+
if (attached) send({ type: "closed" });
|
|
4798
|
+
finish("the page closed.");
|
|
4799
|
+
};
|
|
4800
|
+
const onEnded = () => {
|
|
4801
|
+
try {
|
|
4802
|
+
if (recording) {
|
|
4803
|
+
void internalCall(
|
|
4804
|
+
page,
|
|
4805
|
+
() => page.evaluate(
|
|
4806
|
+
([name, value]) => {
|
|
4807
|
+
const dispatch = globalThis[name];
|
|
4808
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
4809
|
+
},
|
|
4810
|
+
[IDE_DISPATCH_GLOBAL, { type: "piwi-recording-stopped" }]
|
|
4811
|
+
)
|
|
4812
|
+
).catch(() => void 0);
|
|
4813
|
+
}
|
|
4814
|
+
if (attached) send({ type: "closed" });
|
|
4815
|
+
finish("the test ended.");
|
|
4816
|
+
} catch {
|
|
4817
|
+
}
|
|
4818
|
+
};
|
|
4819
|
+
page.once("close", onGone);
|
|
4820
|
+
page.once("crash", onGone);
|
|
4821
|
+
ended?.addEventListener("abort", onEnded, { once: true });
|
|
4822
|
+
const startWait = setTimeout(() => finish("the editor did not start the recording."), START_WAIT_MS);
|
|
4823
|
+
const release = () => {
|
|
4824
|
+
const relay = relays.get(context);
|
|
4825
|
+
if (relay && relay.recording === relayed) relay.recording = null;
|
|
4826
|
+
void Promise.resolve(initScript?.dispose?.()).catch(() => void 0);
|
|
4827
|
+
initScript = void 0;
|
|
4828
|
+
};
|
|
4829
|
+
const finish = (said) => {
|
|
4830
|
+
if (settled) return;
|
|
4831
|
+
settled = true;
|
|
4832
|
+
clearTimeout(startWait);
|
|
4833
|
+
release();
|
|
4834
|
+
for (const [call, answer] of answers) {
|
|
4835
|
+
answers.delete(call);
|
|
4836
|
+
answer(null);
|
|
4837
|
+
}
|
|
4838
|
+
context.off("page", onPage);
|
|
4839
|
+
page.off("close", onGone);
|
|
4840
|
+
page.off("crash", onGone);
|
|
4841
|
+
ended?.removeEventListener("abort", onEnded);
|
|
4842
|
+
socket.end();
|
|
4843
|
+
resolve5(recording ? null : said);
|
|
4844
|
+
};
|
|
4845
|
+
const relayed = (source, payload) => new Promise((answer) => {
|
|
4846
|
+
const call = ++calls;
|
|
4847
|
+
answers.set(call, answer);
|
|
4848
|
+
const outside = source.page !== page || source.frame !== page.mainFrame();
|
|
4849
|
+
send({ type: "binding", call, outside, payload });
|
|
4850
|
+
});
|
|
4851
|
+
const load = async (bundle) => {
|
|
4852
|
+
if (!/^https?:/i.test(page.url())) {
|
|
4853
|
+
const why = `the recorder records http(s) pages only, and this page is ${page.url() || "blank"}.`;
|
|
4854
|
+
send({ type: "notice", message: `Nothing can be recorded here: ${why}` });
|
|
4855
|
+
finish(why);
|
|
4856
|
+
return;
|
|
4857
|
+
}
|
|
4858
|
+
let relay = relays.get(context);
|
|
4859
|
+
if (!relay) {
|
|
4860
|
+
const created = { recording: null, lingering: false };
|
|
4861
|
+
await context.exposeBinding(
|
|
4862
|
+
IDE_RECORDER_BINDING,
|
|
4863
|
+
(source, payload) => created.recording ? created.recording(source, payload) : idleAnswer(payload)
|
|
4864
|
+
);
|
|
4865
|
+
relays.set(context, created);
|
|
4866
|
+
relay = created;
|
|
4867
|
+
}
|
|
4868
|
+
if (settled) return;
|
|
4869
|
+
relay.recording = relayed;
|
|
4870
|
+
if (!relay.lingering) {
|
|
4871
|
+
initScript = await context.addInitScript({ path: bundle });
|
|
4872
|
+
if (typeof initScript?.dispose !== "function") relay.lingering = true;
|
|
4873
|
+
}
|
|
4874
|
+
if (settled) return release();
|
|
4875
|
+
await page.evaluate(fs6.readFileSync(bundle, "utf-8"));
|
|
4876
|
+
if (settled) return release();
|
|
4877
|
+
recording = true;
|
|
4878
|
+
clearTimeout(startWait);
|
|
4879
|
+
context.on("page", onPage);
|
|
4880
|
+
send({ type: "loaded" });
|
|
4881
|
+
};
|
|
4882
|
+
socket.on("connect", () => {
|
|
4883
|
+
attached = true;
|
|
4884
|
+
send({
|
|
4885
|
+
type: "attach",
|
|
4886
|
+
token: channel.token,
|
|
4887
|
+
file: place.file,
|
|
4888
|
+
line: place.line,
|
|
4889
|
+
reason: options.reason,
|
|
4890
|
+
testIdAttribute: typeof use.testIdAttribute === "string" ? use.testIdAttribute : null,
|
|
4891
|
+
baseURL: typeof use.baseURL === "string" ? use.baseURL : null
|
|
4892
|
+
});
|
|
4893
|
+
});
|
|
4894
|
+
socket.on(
|
|
4895
|
+
"data",
|
|
4896
|
+
lineReader(
|
|
4897
|
+
(line) => {
|
|
4898
|
+
const message = parseHandoffToTest(line);
|
|
4899
|
+
if (!message || settled) return;
|
|
4900
|
+
switch (message.type) {
|
|
4901
|
+
case "refused":
|
|
4902
|
+
return finish(message.message);
|
|
4903
|
+
case "load":
|
|
4904
|
+
return void load(message.bundle).catch(
|
|
4905
|
+
(error) => finish(`the recorder could not be loaded: ${error instanceof Error ? error.message : String(error)}`)
|
|
4906
|
+
);
|
|
4907
|
+
case "answer": {
|
|
4908
|
+
const answer = answers.get(message.call);
|
|
4909
|
+
answers.delete(message.call);
|
|
4910
|
+
return answer?.(message.value);
|
|
4911
|
+
}
|
|
4912
|
+
case "dispatch":
|
|
4913
|
+
return void page.evaluate(
|
|
4914
|
+
([name, value]) => {
|
|
4915
|
+
const dispatch = globalThis[name];
|
|
4916
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
4917
|
+
},
|
|
4918
|
+
[IDE_DISPATCH_GLOBAL, message.message]
|
|
4919
|
+
).catch(() => void 0);
|
|
4920
|
+
case "stop":
|
|
4921
|
+
return finish(null);
|
|
4922
|
+
}
|
|
4923
|
+
},
|
|
4924
|
+
() => finish("the editor sent a message too long to read.")
|
|
4925
|
+
)
|
|
4926
|
+
);
|
|
4927
|
+
socket.on("error", (error) => finish(`the editor could not be reached: ${error.message}`));
|
|
4928
|
+
socket.on("close", () => finish("the editor closed the recording."));
|
|
4929
|
+
});
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4456
4932
|
// src/internal/collect/coverage-settings.ts
|
|
4457
4933
|
var SERVER_VALUES = /* @__PURE__ */ new Set(["files", "functions", "lines"]);
|
|
4458
4934
|
function isServerGranularity(value) {
|
|
@@ -4468,7 +4944,7 @@ function browserFunctionsInWorker() {
|
|
|
4468
4944
|
function browserFunctionCoverage(files) {
|
|
4469
4945
|
return sanitizeCoverage({
|
|
4470
4946
|
v: 1,
|
|
4471
|
-
files: [...files].map(([
|
|
4947
|
+
files: [...files].map(([path15, functions]) => ({ path: path15, runtime: "browser", functions }))
|
|
4472
4948
|
});
|
|
4473
4949
|
}
|
|
4474
4950
|
function newKnownFunctions(known, sent) {
|
|
@@ -4481,19 +4957,19 @@ function newKnownFunctions(known, sent) {
|
|
|
4481
4957
|
budget = MAX_COVERAGE_RANGES;
|
|
4482
4958
|
};
|
|
4483
4959
|
for (const [raw, ranges] of known) {
|
|
4484
|
-
const
|
|
4485
|
-
if (
|
|
4486
|
-
let named = sent.get(
|
|
4960
|
+
const path15 = normalizeCoveragePath(raw);
|
|
4961
|
+
if (path15 === null) continue;
|
|
4962
|
+
let named = sent.get(path15);
|
|
4487
4963
|
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
4488
4964
|
if (fresh.length === 0) continue;
|
|
4489
|
-
if (!named) sent.set(
|
|
4965
|
+
if (!named) sent.set(path15, named = /* @__PURE__ */ new Set());
|
|
4490
4966
|
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
4491
4967
|
while (fresh.length > 0) {
|
|
4492
4968
|
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
4493
4969
|
const part = fresh.slice(0, budget);
|
|
4494
4970
|
fresh = fresh.slice(part.length);
|
|
4495
4971
|
budget -= part.length;
|
|
4496
|
-
files.push({ path:
|
|
4972
|
+
files.push({ path: path15, allFunctions: part });
|
|
4497
4973
|
}
|
|
4498
4974
|
}
|
|
4499
4975
|
close();
|
|
@@ -4504,9 +4980,9 @@ function serverCoverageInWorker() {
|
|
|
4504
4980
|
}
|
|
4505
4981
|
|
|
4506
4982
|
// src/internal/support/aria-sampling.ts
|
|
4507
|
-
var
|
|
4983
|
+
var path10 = __toESM(require("path"));
|
|
4508
4984
|
var os3 = __toESM(require("os"));
|
|
4509
|
-
var
|
|
4985
|
+
var fs7 = __toESM(require("fs"));
|
|
4510
4986
|
|
|
4511
4987
|
// src/internal/support/instance-id.ts
|
|
4512
4988
|
var crypto = __toESM(require("crypto"));
|
|
@@ -4520,14 +4996,14 @@ function ariaSampleIdentity(filePath, title) {
|
|
|
4520
4996
|
return `${filePath}\0${title}`;
|
|
4521
4997
|
}
|
|
4522
4998
|
function getAriaSampleFilePath(projectName) {
|
|
4523
|
-
return
|
|
4999
|
+
return path10.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
4524
5000
|
}
|
|
4525
5001
|
var cachedSets = /* @__PURE__ */ new Map();
|
|
4526
5002
|
function loadAriaSampleSet(projectName) {
|
|
4527
5003
|
if (cachedSets.has(projectName)) return cachedSets.get(projectName);
|
|
4528
5004
|
let set = null;
|
|
4529
5005
|
try {
|
|
4530
|
-
const raw =
|
|
5006
|
+
const raw = fs7.readFileSync(getAriaSampleFilePath(projectName), "utf8");
|
|
4531
5007
|
const parsed2 = JSON.parse(raw);
|
|
4532
5008
|
if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
|
|
4533
5009
|
set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
|
|
@@ -4548,97 +5024,10 @@ function isDueForAriaSample(testInfo) {
|
|
|
4548
5024
|
);
|
|
4549
5025
|
}
|
|
4550
5026
|
|
|
4551
|
-
// src/internal/capture/quiet-capture.ts
|
|
4552
|
-
var import_node_module = require("module");
|
|
4553
|
-
var path8 = __toESM(require("path"));
|
|
4554
|
-
|
|
4555
|
-
// src/internal/support/reporter-version.ts
|
|
4556
|
-
var fs5 = __toESM(require("fs"));
|
|
4557
|
-
var path7 = __toESM(require("path"));
|
|
4558
|
-
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
4559
|
-
function findOwnPackageJson(fromDir) {
|
|
4560
|
-
for (const offset of CANDIDATE_OFFSETS) {
|
|
4561
|
-
try {
|
|
4562
|
-
const pkgPath = path7.resolve(fromDir, offset, "package.json");
|
|
4563
|
-
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
4564
|
-
const { name, version } = pkg;
|
|
4565
|
-
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
4566
|
-
return { version, root: path7.dirname(pkgPath) };
|
|
4567
|
-
} catch {
|
|
4568
|
-
}
|
|
4569
|
-
}
|
|
4570
|
-
return void 0;
|
|
4571
|
-
}
|
|
4572
|
-
|
|
4573
|
-
// src/internal/capture/quiet-capture.ts
|
|
4574
|
-
var BOXED_PREFIX_EXPORTS = [
|
|
4575
|
-
[
|
|
4576
|
-
"playwright-core/lib/coreBundle",
|
|
4577
|
-
(mod) => mod?.utils?.setBoxedStackPrefixes
|
|
4578
|
-
],
|
|
4579
|
-
["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
|
|
4580
|
-
];
|
|
4581
|
-
var boxing;
|
|
4582
|
-
var resolvedPlaywrightPackageJson;
|
|
4583
|
-
function resolvePlaywrightPackageJson() {
|
|
4584
|
-
if (resolvedPlaywrightPackageJson !== void 0) return resolvedPlaywrightPackageJson;
|
|
4585
|
-
try {
|
|
4586
|
-
const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
|
|
4587
|
-
resolvedPlaywrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
|
|
4588
|
-
} catch {
|
|
4589
|
-
resolvedPlaywrightPackageJson = null;
|
|
4590
|
-
}
|
|
4591
|
-
return resolvedPlaywrightPackageJson;
|
|
4592
|
-
}
|
|
4593
|
-
function resolveBoxing() {
|
|
4594
|
-
try {
|
|
4595
|
-
const root = findOwnPackageJson(__dirname)?.root;
|
|
4596
|
-
if (!root) return null;
|
|
4597
|
-
const playwrightPackageJson = resolvePlaywrightPackageJson();
|
|
4598
|
-
if (!playwrightPackageJson) return null;
|
|
4599
|
-
const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
|
|
4600
|
-
for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
|
|
4601
|
-
let set;
|
|
4602
|
-
try {
|
|
4603
|
-
set = pick(requireFromPlaywright(moduleId));
|
|
4604
|
-
} catch {
|
|
4605
|
-
continue;
|
|
4606
|
-
}
|
|
4607
|
-
if (typeof set !== "function") continue;
|
|
4608
|
-
return {
|
|
4609
|
-
set,
|
|
4610
|
-
prefixes: [path8.dirname(playwrightPackageJson), path8.join(root, "dist") + path8.sep]
|
|
4611
|
-
};
|
|
4612
|
-
}
|
|
4613
|
-
} catch {
|
|
4614
|
-
}
|
|
4615
|
-
return null;
|
|
4616
|
-
}
|
|
4617
|
-
function boxCaptureFrames(extraPrefixes = []) {
|
|
4618
|
-
const first = boxing === void 0;
|
|
4619
|
-
if (first) boxing = resolveBoxing();
|
|
4620
|
-
if (!boxing) return;
|
|
4621
|
-
const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
|
|
4622
|
-
if (!first && added.length === 0) return;
|
|
4623
|
-
boxing.prefixes.push(...added);
|
|
4624
|
-
try {
|
|
4625
|
-
boxing.set([...boxing.prefixes]);
|
|
4626
|
-
} catch {
|
|
4627
|
-
}
|
|
4628
|
-
}
|
|
4629
|
-
function callMethod(target, prop, args) {
|
|
4630
|
-
return target[prop](...args);
|
|
4631
|
-
}
|
|
4632
|
-
function internalCall(owner, run) {
|
|
4633
|
-
const wrap = owner?._wrapApiCall;
|
|
4634
|
-
if (typeof wrap !== "function") return run();
|
|
4635
|
-
return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
|
|
4636
|
-
}
|
|
4637
|
-
|
|
4638
5027
|
// src/internal/capture/code-reach.ts
|
|
4639
5028
|
var crypto2 = __toESM(require("crypto"));
|
|
4640
|
-
var
|
|
4641
|
-
var
|
|
5029
|
+
var fs8 = __toESM(require("fs"));
|
|
5030
|
+
var path11 = __toESM(require("path"));
|
|
4642
5031
|
var import_node_child_process = require("child_process");
|
|
4643
5032
|
var MAX_MAP_BYTES = 20 * 1024 * 1024;
|
|
4644
5033
|
var MAP_TIMEOUT_MS = 5e3;
|
|
@@ -4681,18 +5070,18 @@ function repositoryRoot(from) {
|
|
|
4681
5070
|
}).trim();
|
|
4682
5071
|
} catch {
|
|
4683
5072
|
}
|
|
4684
|
-
cachedRepoRoot = { from, root:
|
|
5073
|
+
cachedRepoRoot = { from, root: path11.resolve(root) };
|
|
4685
5074
|
return cachedRepoRoot.root;
|
|
4686
5075
|
}
|
|
4687
5076
|
function codeReachRoots(configDir, configured) {
|
|
4688
5077
|
const repoRoot = repositoryRoot(configDir);
|
|
4689
|
-
const
|
|
4690
|
-
const global = Array.isArray(configured) ?
|
|
5078
|
+
const resolve5 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path11.resolve(configDir, r));
|
|
5079
|
+
const global = Array.isArray(configured) ? resolve5(configured) : resolve5(configured?.["*"]);
|
|
4691
5080
|
const byOrigin = /* @__PURE__ */ new Map();
|
|
4692
5081
|
if (configured && !Array.isArray(configured)) {
|
|
4693
5082
|
for (const [key, dirs] of Object.entries(configured)) {
|
|
4694
5083
|
const origin = key === "*" ? null : originOf2(key);
|
|
4695
|
-
const resolved =
|
|
5084
|
+
const resolved = resolve5(dirs);
|
|
4696
5085
|
if (origin && resolved.length) byOrigin.set(origin, resolved);
|
|
4697
5086
|
}
|
|
4698
5087
|
}
|
|
@@ -4715,7 +5104,7 @@ function scriptRoots(roots, scriptUrl, pageUrl) {
|
|
|
4715
5104
|
}
|
|
4716
5105
|
function isFile(file) {
|
|
4717
5106
|
try {
|
|
4718
|
-
return
|
|
5107
|
+
return fs8.statSync(file).isFile();
|
|
4719
5108
|
} catch {
|
|
4720
5109
|
return false;
|
|
4721
5110
|
}
|
|
@@ -4723,10 +5112,10 @@ function isFile(file) {
|
|
|
4723
5112
|
function resolveSourcePath(source, roots) {
|
|
4724
5113
|
const normalized = normalizeSourcePath(source);
|
|
4725
5114
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
4726
|
-
if (
|
|
5115
|
+
if (path11.isAbsolute(normalized) && isFile(normalized)) return path11.resolve(normalized);
|
|
4727
5116
|
const relative7 = normalized.replace(/^\/+/, "");
|
|
4728
5117
|
for (const root of roots) {
|
|
4729
|
-
const candidate =
|
|
5118
|
+
const candidate = path11.resolve(root, relative7);
|
|
4730
5119
|
if (isFile(candidate)) return candidate;
|
|
4731
5120
|
}
|
|
4732
5121
|
return null;
|
|
@@ -4758,7 +5147,7 @@ function fileForUrl(url, roots, bases) {
|
|
|
4758
5147
|
return file;
|
|
4759
5148
|
}
|
|
4760
5149
|
function relativeToRepo(file, repoRoot) {
|
|
4761
|
-
return
|
|
5150
|
+
return path11.relative(repoRoot, file).split(path11.sep).join("/");
|
|
4762
5151
|
}
|
|
4763
5152
|
function scriptVersion(entry) {
|
|
4764
5153
|
return entry.hash ?? crypto2.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
@@ -4821,10 +5210,10 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
4821
5210
|
const files = /* @__PURE__ */ new Map();
|
|
4822
5211
|
const known = /* @__PURE__ */ new Map();
|
|
4823
5212
|
const addTo = (into, file, functions) => {
|
|
4824
|
-
const
|
|
4825
|
-
const listed = into.get(
|
|
5213
|
+
const path15 = relativeToRepo(file, roots.repoRoot);
|
|
5214
|
+
const listed = into.get(path15);
|
|
4826
5215
|
if (listed) listed.push(...functions);
|
|
4827
|
-
else into.set(
|
|
5216
|
+
else into.set(path15, [...functions]);
|
|
4828
5217
|
};
|
|
4829
5218
|
const add = (file, functions) => addTo(files, file, functions);
|
|
4830
5219
|
const bases = viteBases(entries);
|
|
@@ -4976,8 +5365,8 @@ function onScriptParsed(page, capture, event) {
|
|
|
4976
5365
|
var READ_WAIT_MS = 5e3;
|
|
4977
5366
|
function readCoverage(page, cdp) {
|
|
4978
5367
|
let timer;
|
|
4979
|
-
const late = new Promise((
|
|
4980
|
-
timer = setTimeout(() =>
|
|
5368
|
+
const late = new Promise((resolve5) => {
|
|
5369
|
+
timer = setTimeout(() => resolve5([]), READ_WAIT_MS);
|
|
4981
5370
|
});
|
|
4982
5371
|
const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
|
|
4983
5372
|
(answer) => answer.result,
|
|
@@ -5087,20 +5476,20 @@ async function stopCodeReach(page) {
|
|
|
5087
5476
|
}
|
|
5088
5477
|
|
|
5089
5478
|
// src/internal/capture/resource-ledger.ts
|
|
5090
|
-
var
|
|
5091
|
-
var
|
|
5479
|
+
var fs10 = __toESM(require("fs"));
|
|
5480
|
+
var path13 = __toESM(require("path"));
|
|
5092
5481
|
var import_node_module2 = require("module");
|
|
5093
5482
|
|
|
5094
5483
|
// src/internal/capture/worker-metrics.ts
|
|
5095
5484
|
var import_node_perf_hooks = require("perf_hooks");
|
|
5096
5485
|
|
|
5097
5486
|
// src/internal/support/system-readers.ts
|
|
5098
|
-
var
|
|
5099
|
-
var
|
|
5487
|
+
var fs9 = __toESM(require("fs"));
|
|
5488
|
+
var path12 = __toESM(require("path"));
|
|
5100
5489
|
var CLK_TCK = 100;
|
|
5101
5490
|
function readText(file) {
|
|
5102
5491
|
try {
|
|
5103
|
-
return
|
|
5492
|
+
return fs9.readFileSync(file, "utf8");
|
|
5104
5493
|
} catch {
|
|
5105
5494
|
return null;
|
|
5106
5495
|
}
|
|
@@ -5182,45 +5571,45 @@ var ProcFs = class {
|
|
|
5182
5571
|
}
|
|
5183
5572
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
5184
5573
|
available() {
|
|
5185
|
-
return
|
|
5574
|
+
return fs9.existsSync(path12.join(this.root, "stat"));
|
|
5186
5575
|
}
|
|
5187
5576
|
/** Every process, keyed by pid. */
|
|
5188
5577
|
table() {
|
|
5189
5578
|
const out = /* @__PURE__ */ new Map();
|
|
5190
5579
|
let names;
|
|
5191
5580
|
try {
|
|
5192
|
-
names =
|
|
5581
|
+
names = fs9.readdirSync(this.root);
|
|
5193
5582
|
} catch {
|
|
5194
5583
|
return out;
|
|
5195
5584
|
}
|
|
5196
5585
|
for (const name of names) {
|
|
5197
5586
|
if (!/^\d+$/.test(name)) continue;
|
|
5198
|
-
const text = readText(
|
|
5587
|
+
const text = readText(path12.join(this.root, name, "stat"));
|
|
5199
5588
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
5200
5589
|
if (stat) out.set(stat.pid, stat);
|
|
5201
5590
|
}
|
|
5202
5591
|
return out;
|
|
5203
5592
|
}
|
|
5204
5593
|
stat(pid) {
|
|
5205
|
-
const text = readText(
|
|
5594
|
+
const text = readText(path12.join(this.root, String(pid), "stat"));
|
|
5206
5595
|
return text ? parseProcStat(pid, text) : null;
|
|
5207
5596
|
}
|
|
5208
5597
|
runWaitMs(pid) {
|
|
5209
|
-
const text = readText(
|
|
5598
|
+
const text = readText(path12.join(this.root, String(pid), "schedstat"));
|
|
5210
5599
|
return text ? parseSchedstat(text) : null;
|
|
5211
5600
|
}
|
|
5212
5601
|
cmdline(pid) {
|
|
5213
|
-
return (readText(
|
|
5602
|
+
return (readText(path12.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
5214
5603
|
}
|
|
5215
5604
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
5216
5605
|
statusKb(pid, key) {
|
|
5217
|
-
const text = readText(
|
|
5606
|
+
const text = readText(path12.join(this.root, String(pid), "status"));
|
|
5218
5607
|
return text ? parseKbField(text, key) : null;
|
|
5219
5608
|
}
|
|
5220
5609
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
5221
5610
|
async pssKb(pid) {
|
|
5222
5611
|
try {
|
|
5223
|
-
return parseKbField(await
|
|
5612
|
+
return parseKbField(await fs9.promises.readFile(path12.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
5224
5613
|
} catch {
|
|
5225
5614
|
return null;
|
|
5226
5615
|
}
|
|
@@ -5228,29 +5617,29 @@ var ProcFs = class {
|
|
|
5228
5617
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
5229
5618
|
resetPeakRss(pid) {
|
|
5230
5619
|
try {
|
|
5231
|
-
|
|
5620
|
+
fs9.writeFileSync(path12.join(this.root, String(pid), "clear_refs"), "5");
|
|
5232
5621
|
return true;
|
|
5233
5622
|
} catch {
|
|
5234
5623
|
return false;
|
|
5235
5624
|
}
|
|
5236
5625
|
}
|
|
5237
5626
|
cpuTicks() {
|
|
5238
|
-
const text = readText(
|
|
5627
|
+
const text = readText(path12.join(this.root, "stat"));
|
|
5239
5628
|
return text ? parseCpuTicks(text) : null;
|
|
5240
5629
|
}
|
|
5241
5630
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
5242
5631
|
pressureUs(resource) {
|
|
5243
|
-
const text = readText(
|
|
5632
|
+
const text = readText(path12.join(this.root, "pressure", resource));
|
|
5244
5633
|
return text ? parsePsiTotal(text) : null;
|
|
5245
5634
|
}
|
|
5246
5635
|
memAvailableKb() {
|
|
5247
|
-
const text = readText(
|
|
5636
|
+
const text = readText(path12.join(this.root, "meminfo"));
|
|
5248
5637
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
5249
5638
|
}
|
|
5250
5639
|
/** Open file descriptors of a process. */
|
|
5251
5640
|
fdCount(pid) {
|
|
5252
5641
|
try {
|
|
5253
|
-
return
|
|
5642
|
+
return fs9.readdirSync(path12.join(this.root, String(pid), "fd")).length;
|
|
5254
5643
|
} catch {
|
|
5255
5644
|
return null;
|
|
5256
5645
|
}
|
|
@@ -5412,10 +5801,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
|
5412
5801
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
5413
5802
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
5414
5803
|
function relativePath(file) {
|
|
5415
|
-
return
|
|
5804
|
+
return path13.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path13.sep).join("/");
|
|
5416
5805
|
}
|
|
5417
5806
|
function isLibraryFile(file) {
|
|
5418
|
-
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${
|
|
5807
|
+
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path13.sep}node_modules${path13.sep}`) || file.includes("/node_modules/") || file === __filename;
|
|
5419
5808
|
}
|
|
5420
5809
|
function userSite(stack) {
|
|
5421
5810
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
@@ -5463,7 +5852,7 @@ function playwrightTestInfoSource() {
|
|
|
5463
5852
|
try {
|
|
5464
5853
|
const packageJson = resolvePlaywrightPackageJson();
|
|
5465
5854
|
if (!packageJson) return () => null;
|
|
5466
|
-
const globals = (0, import_node_module2.createRequire)(packageJson)(
|
|
5855
|
+
const globals = (0, import_node_module2.createRequire)(packageJson)(path13.join(path13.dirname(packageJson), "lib", "globals.js"));
|
|
5467
5856
|
const read = globals.currentTestInfo;
|
|
5468
5857
|
if (typeof read !== "function") return () => null;
|
|
5469
5858
|
return () => {
|
|
@@ -5492,15 +5881,15 @@ function countHandles() {
|
|
|
5492
5881
|
async function settledHandles(start) {
|
|
5493
5882
|
const end = countHandles();
|
|
5494
5883
|
if (!Object.entries(end).some(([type, n]) => n > (start[type] ?? 0))) return end;
|
|
5495
|
-
await new Promise((
|
|
5884
|
+
await new Promise((resolve5) => setTimeout(resolve5, 0));
|
|
5496
5885
|
return countHandles();
|
|
5497
5886
|
}
|
|
5498
5887
|
function settleWithin(promise, ms) {
|
|
5499
5888
|
let timer;
|
|
5500
5889
|
return Promise.race([
|
|
5501
5890
|
promise.catch(() => void 0),
|
|
5502
|
-
new Promise((
|
|
5503
|
-
timer = setTimeout(
|
|
5891
|
+
new Promise((resolve5) => {
|
|
5892
|
+
timer = setTimeout(resolve5, ms);
|
|
5504
5893
|
})
|
|
5505
5894
|
]).finally(() => clearTimeout(timer));
|
|
5506
5895
|
}
|
|
@@ -5629,7 +6018,7 @@ var ResourceLedger = class {
|
|
|
5629
6018
|
const census = this.census(null);
|
|
5630
6019
|
if (this.resultsFile) {
|
|
5631
6020
|
try {
|
|
5632
|
-
|
|
6021
|
+
fs10.appendFileSync(this.resultsFile, `${JSON.stringify(census)}
|
|
5633
6022
|
`);
|
|
5634
6023
|
} catch {
|
|
5635
6024
|
}
|
|
@@ -6246,6 +6635,13 @@ function createSink() {
|
|
|
6246
6635
|
pickOffered: false,
|
|
6247
6636
|
userPick: null,
|
|
6248
6637
|
pause: null,
|
|
6638
|
+
locatorCalls: /* @__PURE__ */ new Map(),
|
|
6639
|
+
stops: [],
|
|
6640
|
+
shownStops: /* @__PURE__ */ new Map(),
|
|
6641
|
+
released: [],
|
|
6642
|
+
releasedRequests: /* @__PURE__ */ new Set(),
|
|
6643
|
+
onReleasedRequestsEnded: null,
|
|
6644
|
+
releasedQuietSince: 0,
|
|
6249
6645
|
locatorPages: new LocatorPageLog(),
|
|
6250
6646
|
codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map() },
|
|
6251
6647
|
serverCoverage: /* @__PURE__ */ new Map(),
|
|
@@ -6271,7 +6667,7 @@ function maybeStartCodeReach(page, alreadyOpen = false) {
|
|
|
6271
6667
|
async function startCodeReachOnOpenPages() {
|
|
6272
6668
|
if (!browserCoverageInWorker() || OPEN_PAGES.size === 0) return;
|
|
6273
6669
|
const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
|
|
6274
|
-
await Promise.race([started, new Promise((
|
|
6670
|
+
await Promise.race([started, new Promise((resolve5) => setTimeout(resolve5, CODE_REACH_START_WAIT_MS).unref())]);
|
|
6275
6671
|
}
|
|
6276
6672
|
function configuredCodeReachRoots() {
|
|
6277
6673
|
const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
|
|
@@ -6285,7 +6681,7 @@ function configuredCodeReachRoots() {
|
|
|
6285
6681
|
}
|
|
6286
6682
|
} catch {
|
|
6287
6683
|
}
|
|
6288
|
-
return raw.split(
|
|
6684
|
+
return raw.split(path14.delimiter).filter(Boolean);
|
|
6289
6685
|
}
|
|
6290
6686
|
async function stopSinkCodeReach(sink, closing) {
|
|
6291
6687
|
const stopping = [];
|
|
@@ -6334,8 +6730,8 @@ async function drainPendingProbes(capMs) {
|
|
|
6334
6730
|
let cap;
|
|
6335
6731
|
await Promise.race([
|
|
6336
6732
|
Promise.allSettled(PENDING_PROBES),
|
|
6337
|
-
new Promise((
|
|
6338
|
-
cap = setTimeout(
|
|
6733
|
+
new Promise((resolve5) => {
|
|
6734
|
+
cap = setTimeout(resolve5, capMs);
|
|
6339
6735
|
})
|
|
6340
6736
|
]);
|
|
6341
6737
|
clearTimeout(cap);
|
|
@@ -6375,10 +6771,10 @@ async function readWebVitals(page) {
|
|
|
6375
6771
|
const key = entry.name.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
6376
6772
|
paint[key] = Math.round(entry.startTime);
|
|
6377
6773
|
}
|
|
6378
|
-
const readBuffered = (type, extra) => new Promise((
|
|
6774
|
+
const readBuffered = (type, extra) => new Promise((resolve5) => {
|
|
6379
6775
|
try {
|
|
6380
6776
|
const PO = globalThis.PerformanceObserver;
|
|
6381
|
-
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return
|
|
6777
|
+
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve5(null);
|
|
6382
6778
|
const out = [];
|
|
6383
6779
|
const po = new PO((list) => out.push(...list.getEntries()));
|
|
6384
6780
|
po.observe({ type, buffered: true, ...extra });
|
|
@@ -6388,10 +6784,10 @@ async function readWebVitals(page) {
|
|
|
6388
6784
|
po.disconnect();
|
|
6389
6785
|
} catch {
|
|
6390
6786
|
}
|
|
6391
|
-
|
|
6787
|
+
resolve5(out);
|
|
6392
6788
|
}, 0);
|
|
6393
6789
|
} catch {
|
|
6394
|
-
|
|
6790
|
+
resolve5(null);
|
|
6395
6791
|
}
|
|
6396
6792
|
});
|
|
6397
6793
|
const [lcpRaw, shiftRaw, eventRaw, firstInputRaw] = await Promise.all([
|
|
@@ -6506,12 +6902,12 @@ var PAGE_INVENTORY_IN_PAGE_CAP = 2e3;
|
|
|
6506
6902
|
var PAGE_INVENTORY_EVAL_TIMEOUT_MS = 2e3;
|
|
6507
6903
|
var MAX_PAGE_INVENTORY_WINDOWS = 64;
|
|
6508
6904
|
function withTimeout(promise, ms) {
|
|
6509
|
-
return new Promise((
|
|
6905
|
+
return new Promise((resolve5, reject) => {
|
|
6510
6906
|
const timer = setTimeout(() => reject(new Error("page inventory read timed out")), ms);
|
|
6511
6907
|
promise.then(
|
|
6512
6908
|
(value) => {
|
|
6513
6909
|
clearTimeout(timer);
|
|
6514
|
-
|
|
6910
|
+
resolve5(value);
|
|
6515
6911
|
},
|
|
6516
6912
|
(error) => {
|
|
6517
6913
|
clearTimeout(timer);
|
|
@@ -6626,6 +7022,7 @@ async function maybeOpenPicker(sink, closing) {
|
|
|
6626
7022
|
var INSTRUMENTED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
6627
7023
|
var OPEN_PAGES = /* @__PURE__ */ new Set();
|
|
6628
7024
|
var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
|
|
7025
|
+
var OPEN_CONTEXTS = /* @__PURE__ */ new Set();
|
|
6629
7026
|
var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
|
|
6630
7027
|
var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
|
|
6631
7028
|
var ACTION_METHOD_SET = new Set(ACTION_METHODS);
|
|
@@ -6746,8 +7143,20 @@ function recordLocatorPage(sink, page, locator, location, arrival) {
|
|
|
6746
7143
|
} catch {
|
|
6747
7144
|
}
|
|
6748
7145
|
}
|
|
6749
|
-
function
|
|
6750
|
-
|
|
7146
|
+
function knowLocatorCall(sink, page, target, location, origin) {
|
|
7147
|
+
if (!sink?.pause || !location) return null;
|
|
7148
|
+
let settle = () => {
|
|
7149
|
+
};
|
|
7150
|
+
const settled = new Promise((resolve5) => settle = resolve5);
|
|
7151
|
+
const call = { page, target, location, origin, picked: false, settled, settle };
|
|
7152
|
+
sink.locatorCalls.set(callSiteLine(location), call);
|
|
7153
|
+
return call;
|
|
7154
|
+
}
|
|
7155
|
+
function forgetLocatorCall(sink, call) {
|
|
7156
|
+
if (!sink || !call) return;
|
|
7157
|
+
call.settle();
|
|
7158
|
+
const key = callSiteLine(call.location);
|
|
7159
|
+
if (sink.locatorCalls.get(key) === call) sink.locatorCalls.delete(key);
|
|
6751
7160
|
}
|
|
6752
7161
|
function locatorSource(target, method, args) {
|
|
6753
7162
|
try {
|
|
@@ -6757,41 +7166,219 @@ function locatorSource(target, method, args) {
|
|
|
6757
7166
|
}
|
|
6758
7167
|
return renderFailing({ method, args });
|
|
6759
7168
|
}
|
|
6760
|
-
|
|
6761
|
-
|
|
7169
|
+
var PAGE_PAUSES = /* @__PURE__ */ new WeakMap();
|
|
7170
|
+
var PLAYWRIGHT_PAUSES = /* @__PURE__ */ new WeakMap();
|
|
7171
|
+
async function armBreakpoints(contexts) {
|
|
7172
|
+
const state = currentSink?.pause;
|
|
7173
|
+
if (!state || state.finished || state.set.size === 0) return;
|
|
7174
|
+
await Promise.all(
|
|
7175
|
+
[...contexts].map(
|
|
7176
|
+
(context) => context && watchStops(context, onDebuggerStop) ? steerDebugger(context, "pause") : void 0
|
|
7177
|
+
)
|
|
7178
|
+
);
|
|
7179
|
+
}
|
|
7180
|
+
function onDebuggerStop(context) {
|
|
7181
|
+
const details = contextDebugger(context)?.pausedDetails();
|
|
7182
|
+
if (!details || PLAYWRIGHT_PAUSES.has(context)) return;
|
|
7183
|
+
const sink = currentSink;
|
|
7184
|
+
const state = sink?.pause ?? null;
|
|
7185
|
+
void (async () => {
|
|
7186
|
+
let how = "resume";
|
|
7187
|
+
try {
|
|
7188
|
+
if (!sink || !state) return;
|
|
7189
|
+
const callSite = stopSite(sink, details.location);
|
|
7190
|
+
const pagePause = PAGE_PAUSES.get(context);
|
|
7191
|
+
const decided = pagePause ? { show: "page-pause" } : atStop(state, callSite);
|
|
7192
|
+
if ("go" in decided) {
|
|
7193
|
+
how = decided.go;
|
|
7194
|
+
return;
|
|
7195
|
+
}
|
|
7196
|
+
how = goOn(state, await showStop(sink, context, details.title, callSite ?? pagePause, decided.show));
|
|
7197
|
+
} catch {
|
|
7198
|
+
if (state) how = goOn(state);
|
|
7199
|
+
} finally {
|
|
7200
|
+
await steerDebugger(context, how);
|
|
7201
|
+
}
|
|
7202
|
+
})();
|
|
7203
|
+
}
|
|
7204
|
+
function stopSite(sink, location) {
|
|
7205
|
+
if (path14.dirname(path14.resolve(location.file)) !== __dirname) return stopCallSite(location);
|
|
7206
|
+
return inFlightCallSite([...sink.locatorCalls.values()].map((call) => call.location));
|
|
7207
|
+
}
|
|
7208
|
+
function stopPage(sink, context, call) {
|
|
7209
|
+
for (const page of [call?.page, sink.lastActivePage]) {
|
|
7210
|
+
if (page && !isPageClosed(page) && pageContext(page) === context) return page;
|
|
7211
|
+
}
|
|
7212
|
+
const open = context.pages().filter((page) => !isPageClosed(page));
|
|
7213
|
+
return open[open.length - 1] ?? null;
|
|
7214
|
+
}
|
|
7215
|
+
async function showStop(sink, context, title, location, reason) {
|
|
6762
7216
|
const testInfo = sink.testInfo;
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
7217
|
+
const call = sink.locatorCalls.get(callSiteLine(location));
|
|
7218
|
+
const page = stopPage(sink, context, call);
|
|
7219
|
+
if (!testInfo || !page) return void 0;
|
|
7220
|
+
const onPick = async (picked) => {
|
|
7221
|
+
if (call) {
|
|
7222
|
+
call.picked = true;
|
|
7223
|
+
const { method, args } = call.origin;
|
|
7224
|
+
const rendered = locatorSource(call.target, method, args);
|
|
7225
|
+
applyPickToSnapshots(sink.capturedLocators, {
|
|
7226
|
+
failing: { method, args, rendered, location: call.location },
|
|
6770
7227
|
...picked
|
|
6771
|
-
};
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
7228
|
+
});
|
|
7229
|
+
sink.probedLocations.add(call.location);
|
|
7230
|
+
}
|
|
7231
|
+
console.log(`[piwi] Locator picked at ${callSiteLine(location)}: ${picked.picked.locator}`);
|
|
7232
|
+
await postPickToEditor(process.env[PIWI_LOCAL_DEBUG_ENV.editorSend], pickPayload(picked.picked.locator, location));
|
|
7233
|
+
};
|
|
7234
|
+
const record = { title, location, reason, startedAt: Date.now(), duration: 0 };
|
|
7235
|
+
sink.stops.push(record);
|
|
7236
|
+
const end = new AbortController();
|
|
7237
|
+
const shown = withTestTimeoutHeld(
|
|
7238
|
+
testInfo,
|
|
7239
|
+
() => internalCall(
|
|
6782
7240
|
page,
|
|
6783
7241
|
() => pauseHere(
|
|
6784
7242
|
page,
|
|
6785
|
-
|
|
6786
|
-
|
|
7243
|
+
{
|
|
7244
|
+
location,
|
|
7245
|
+
call: title,
|
|
7246
|
+
attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
|
|
7247
|
+
target: call?.page === page ? call.target : null
|
|
7248
|
+
},
|
|
6787
7249
|
{ fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG },
|
|
6788
|
-
onPick
|
|
7250
|
+
onPick,
|
|
7251
|
+
recorderAt(page, testInfo, location, reason === "page-pause" ? "pause" : "breakpoint"),
|
|
7252
|
+
end.signal
|
|
6789
7253
|
)
|
|
6790
|
-
)
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
7254
|
+
)
|
|
7255
|
+
).finally(() => {
|
|
7256
|
+
if (!end.signal.aborted) record.duration = Date.now() - record.startedAt;
|
|
7257
|
+
else if (end.signal.reason === "ended") releaseCall(sink, call?.settled ?? Promise.resolve());
|
|
7258
|
+
sink.shownStops.delete(end);
|
|
7259
|
+
});
|
|
7260
|
+
const heldAt = testTimeSlot(testInfo)?.elapsed ?? null;
|
|
7261
|
+
sink.shownStops.set(end, { page, record, done: shown.catch(() => void 0), heldAt });
|
|
7262
|
+
const answer = await shown;
|
|
7263
|
+
if (!end.signal.aborted) return answer;
|
|
7264
|
+
return end.signal.reason === "closed" ? "resume" : "finish";
|
|
7265
|
+
}
|
|
7266
|
+
var END_STOP_MS = 2e3;
|
|
7267
|
+
var RELEASED_CALL_MS = 2e3;
|
|
7268
|
+
var RELEASED_QUIET_MS = 200;
|
|
7269
|
+
async function endShownStops(sink, why, closing) {
|
|
7270
|
+
const ending = [];
|
|
7271
|
+
let heldAt = Infinity;
|
|
7272
|
+
for (const [end, { page, record, done, heldAt: stopHeldAt }] of sink.shownStops) {
|
|
7273
|
+
if (closing && closing.page !== page && (!closing.context || pageContext(page) !== closing.context)) continue;
|
|
7274
|
+
if (!end.signal.aborted) {
|
|
7275
|
+
record.duration = Date.now() - record.startedAt;
|
|
7276
|
+
end.abort(why);
|
|
7277
|
+
if (why === "ended" && stopHeldAt !== null) heldAt = Math.min(heldAt, stopHeldAt);
|
|
7278
|
+
}
|
|
7279
|
+
ending.push(done);
|
|
7280
|
+
}
|
|
7281
|
+
if (sink.testInfo && heldAt !== Infinity) restoreTestTime(sink.testInfo, heldAt);
|
|
7282
|
+
await settledWithin(ending, END_STOP_MS);
|
|
7283
|
+
}
|
|
7284
|
+
function releaseCall(sink, settled) {
|
|
7285
|
+
sink.releasedQuietSince = Date.now();
|
|
7286
|
+
sink.released.push(settled.then(() => void (sink.releasedQuietSince = Date.now())));
|
|
7287
|
+
}
|
|
7288
|
+
function releasedRequestsEnded(sink) {
|
|
7289
|
+
if (sink.releasedRequests.size === 0) return Promise.resolve();
|
|
7290
|
+
return new Promise((resolve5) => sink.onReleasedRequestsEnded = resolve5);
|
|
7291
|
+
}
|
|
7292
|
+
async function awaitReleasedCalls(sink) {
|
|
7293
|
+
if (sink.released.length === 0) return;
|
|
7294
|
+
const deadline = Date.now() + RELEASED_CALL_MS;
|
|
7295
|
+
await settledWithin(sink.released, RELEASED_CALL_MS);
|
|
7296
|
+
for (let now = Date.now(); now < deadline; now = Date.now()) {
|
|
7297
|
+
if (sink.releasedRequests.size > 0) {
|
|
7298
|
+
await settledWithin([releasedRequestsEnded(sink)], deadline - now);
|
|
7299
|
+
continue;
|
|
7300
|
+
}
|
|
7301
|
+
const quiet = now - sink.releasedQuietSince;
|
|
7302
|
+
if (quiet >= RELEASED_QUIET_MS) return;
|
|
7303
|
+
await new Promise((resolve5) => setTimeout(resolve5, Math.min(RELEASED_QUIET_MS - quiet, deadline - now)));
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
async function settledWithin(pending, ms) {
|
|
7307
|
+
if (pending.length === 0) return;
|
|
7308
|
+
let timer;
|
|
7309
|
+
await Promise.race([Promise.allSettled(pending), new Promise((resolve5) => timer = setTimeout(resolve5, ms))]);
|
|
7310
|
+
clearTimeout(timer);
|
|
7311
|
+
}
|
|
7312
|
+
function endStopsWithTestFunction(testInfo, sink) {
|
|
7313
|
+
const callbacks = testInfo._onDidFinishTestFunctionCallbacks;
|
|
7314
|
+
if (!(callbacks instanceof Set)) return;
|
|
7315
|
+
const theirs = [...callbacks];
|
|
7316
|
+
callbacks.clear();
|
|
7317
|
+
callbacks.add(() => endShownStops(sink, "ended"));
|
|
7318
|
+
for (const callback of theirs) callbacks.add(callback);
|
|
7319
|
+
}
|
|
7320
|
+
function recorderAt(page, testInfo, location, reason) {
|
|
7321
|
+
if (!canRecordFromHere()) return void 0;
|
|
7322
|
+
return async (ended) => {
|
|
7323
|
+
console.log(`[piwi] Recording from ${callSiteLine(location)}: the editor writes the steps.`);
|
|
7324
|
+
const why = await recordFromHere({ page, testInfo, location, reason, ended });
|
|
7325
|
+
if (ended?.aborted) console.log("[piwi] The recording ended with the test.");
|
|
7326
|
+
else
|
|
7327
|
+
console.log(
|
|
7328
|
+
why ? `[piwi] Nothing was recorded: ${why}` : "[piwi] The recording ended: the test is paused again."
|
|
7329
|
+
);
|
|
7330
|
+
};
|
|
7331
|
+
}
|
|
7332
|
+
async function pauseInPlace(page, playwrightPause, location) {
|
|
7333
|
+
const sink = currentSink;
|
|
7334
|
+
const testInfo = sink?.testInfo;
|
|
7335
|
+
const context = pageContext(page);
|
|
7336
|
+
if (!sink || !testInfo || !location || !context || !contextDebugger(context)) {
|
|
7337
|
+
return leaveToPlaywright(context, playwrightPause);
|
|
7338
|
+
}
|
|
7339
|
+
if (!sink.pause && (!canRecordFromHere() || pauseSkipReason(pauseGateFromTestInfo(testInfo)))) {
|
|
7340
|
+
return leaveToPlaywright(context, playwrightPause);
|
|
7341
|
+
}
|
|
7342
|
+
sink.pause ??= { set: NO_BREAKPOINTS, step: false, finished: false };
|
|
7343
|
+
watchStops(context, onDebuggerStop);
|
|
7344
|
+
await steerDebugger(context, "pause");
|
|
7345
|
+
PAGE_PAUSES.set(context, location);
|
|
7346
|
+
try {
|
|
7347
|
+
await playwrightPause({ __testHookKeepTestTimeout: true });
|
|
7348
|
+
} finally {
|
|
7349
|
+
PAGE_PAUSES.delete(context);
|
|
7350
|
+
}
|
|
7351
|
+
}
|
|
7352
|
+
async function leaveToPlaywright(context, playwrightPause) {
|
|
7353
|
+
if (context) PLAYWRIGHT_PAUSES.set(context, (PLAYWRIGHT_PAUSES.get(context) ?? 0) + 1);
|
|
7354
|
+
try {
|
|
7355
|
+
await withInspector(() => playwrightPause());
|
|
7356
|
+
} finally {
|
|
7357
|
+
if (context) {
|
|
7358
|
+
const left = (PLAYWRIGHT_PAUSES.get(context) ?? 1) - 1;
|
|
7359
|
+
if (left > 0) PLAYWRIGHT_PAUSES.set(context, left);
|
|
7360
|
+
else PLAYWRIGHT_PAUSES.delete(context);
|
|
7361
|
+
}
|
|
7362
|
+
}
|
|
7363
|
+
}
|
|
7364
|
+
var PLAYWRIGHT_PAUSE = /* @__PURE__ */ Symbol.for("@piwitests/reporter:playwright-pause");
|
|
7365
|
+
function playwrightPauseOf(page) {
|
|
7366
|
+
const current = page.pause;
|
|
7367
|
+
if (typeof current !== "function") return null;
|
|
7368
|
+
const own = current[PLAYWRIGHT_PAUSE] ?? current;
|
|
7369
|
+
const proto = Object.getPrototypeOf(page);
|
|
7370
|
+
const theirs = proto?.pause;
|
|
7371
|
+
if (proto && typeof theirs === "function") {
|
|
7372
|
+
if (!theirs[PLAYWRIGHT_PAUSE]) {
|
|
7373
|
+
const wrapped = function(options) {
|
|
7374
|
+
return leaveToPlaywright(pageContext(this), () => theirs.call(this, options));
|
|
7375
|
+
};
|
|
7376
|
+
wrapped[PLAYWRIGHT_PAUSE] = theirs;
|
|
7377
|
+
proto.pause = wrapped;
|
|
7378
|
+
}
|
|
7379
|
+
markPausesWrapped();
|
|
6794
7380
|
}
|
|
7381
|
+
return (options) => own.call(page, options);
|
|
6795
7382
|
}
|
|
6796
7383
|
function wrapLocator(page, locator, originMethod, originArgs) {
|
|
6797
7384
|
return new Proxy(locator, {
|
|
@@ -6814,36 +7401,35 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
6814
7401
|
const isNot = Boolean(callArgs[1]?.isNot);
|
|
6815
7402
|
const callerLocation = sink ? captureCallerLocation() : null;
|
|
6816
7403
|
if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
|
|
6817
|
-
|
|
6818
|
-
|
|
7404
|
+
const call = knowLocatorCall(sink, page, target, callerLocation, { method: originMethod, args: originArgs });
|
|
7405
|
+
try {
|
|
7406
|
+
if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
|
|
7407
|
+
return await callMethod(target, prop, callArgs);
|
|
7408
|
+
}
|
|
7409
|
+
sink.lastActivePage = page;
|
|
7410
|
+
const used = {
|
|
6819
7411
|
method: originMethod,
|
|
6820
|
-
args: originArgs
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
if (matches === true && !alreadyProbed) {
|
|
6841
|
-
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
6842
|
-
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
6843
|
-
} else if (matches === false) {
|
|
6844
|
-
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
7412
|
+
args: originArgs,
|
|
7413
|
+
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
7414
|
+
};
|
|
7415
|
+
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
7416
|
+
let seq = -1;
|
|
7417
|
+
if (!alreadyProbed) {
|
|
7418
|
+
seq = sink.capturedLocators.length;
|
|
7419
|
+
sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
|
|
7420
|
+
}
|
|
7421
|
+
const result = await callMethod(target, prop, callArgs);
|
|
7422
|
+
const matches = result?.matches;
|
|
7423
|
+
if (matches === true && !alreadyProbed && !call?.picked) {
|
|
7424
|
+
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
7425
|
+
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
7426
|
+
} else if (matches === false) {
|
|
7427
|
+
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
7428
|
+
}
|
|
7429
|
+
return result;
|
|
7430
|
+
} finally {
|
|
7431
|
+
forgetLocatorCall(sink, call);
|
|
6845
7432
|
}
|
|
6846
|
-
return result;
|
|
6847
7433
|
};
|
|
6848
7434
|
}
|
|
6849
7435
|
if (!ACTION_METHOD_SET.has(prop)) return original;
|
|
@@ -6858,9 +7444,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
6858
7444
|
args: originArgs,
|
|
6859
7445
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
6860
7446
|
};
|
|
6861
|
-
|
|
6862
|
-
await pauseBefore(sink, page, target, callerLocation, String(prop), used);
|
|
6863
|
-
}
|
|
7447
|
+
const call = knowLocatorCall(sink, page, target, callerLocation, used);
|
|
6864
7448
|
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
6865
7449
|
let seq = -1;
|
|
6866
7450
|
if (!alreadyProbed) {
|
|
@@ -6878,8 +7462,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
6878
7462
|
} catch (error) {
|
|
6879
7463
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
6880
7464
|
throw error;
|
|
7465
|
+
} finally {
|
|
7466
|
+
forgetLocatorCall(sink, call);
|
|
6881
7467
|
}
|
|
6882
|
-
if (!alreadyProbed) {
|
|
7468
|
+
if (!alreadyProbed && !call?.picked) {
|
|
6883
7469
|
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
6884
7470
|
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
6885
7471
|
}
|
|
@@ -6932,12 +7518,17 @@ function instrumentPage(page) {
|
|
|
6932
7518
|
}
|
|
6933
7519
|
const ctx = pageContext(page);
|
|
6934
7520
|
if (ctx) instrumentContext(ctx);
|
|
7521
|
+
const playwrightPause = playwrightPauseOf(page);
|
|
7522
|
+
if (playwrightPause) {
|
|
7523
|
+
page.pause = () => pauseInPlace(page, playwrightPause, captureCallerLocation());
|
|
7524
|
+
}
|
|
6935
7525
|
if (typeof page.close === "function") {
|
|
6936
7526
|
const originalClose = page.close.bind(page);
|
|
6937
7527
|
page.close = async (...args) => {
|
|
6938
7528
|
await drainPendingProbes(1e3);
|
|
6939
7529
|
const sink = currentSink;
|
|
6940
7530
|
if (sink) {
|
|
7531
|
+
await endShownStops(sink, "closed", { page });
|
|
6941
7532
|
await stashPageState(sink, { page });
|
|
6942
7533
|
await maybeOpenPicker(sink, { page });
|
|
6943
7534
|
await stopSinkCodeReach(sink, { page });
|
|
@@ -7026,8 +7617,18 @@ function instrumentPage(page) {
|
|
|
7026
7617
|
} catch {
|
|
7027
7618
|
}
|
|
7028
7619
|
});
|
|
7620
|
+
page.on("request", (request) => {
|
|
7621
|
+
const sink = currentSink;
|
|
7622
|
+
if (!sink || sink.released.length === 0) return;
|
|
7623
|
+
sink.releasedRequests.add(request);
|
|
7624
|
+
sink.releasedQuietSince = Date.now();
|
|
7625
|
+
});
|
|
7029
7626
|
const onRequestEnd = (request, failed) => {
|
|
7030
7627
|
const sink = currentSink;
|
|
7628
|
+
if (sink?.releasedRequests.delete(request)) {
|
|
7629
|
+
sink.releasedQuietSince = Date.now();
|
|
7630
|
+
if (sink.releasedRequests.size === 0) sink.onReleasedRequestsEnded?.();
|
|
7631
|
+
}
|
|
7031
7632
|
if (!sink || RECORDED_OPEN.has(request)) return;
|
|
7032
7633
|
sink.lastActivePage = page;
|
|
7033
7634
|
const response = sink.openResponses.get(request) ?? null;
|
|
@@ -7133,6 +7734,10 @@ async function awaitFlakeConditions() {
|
|
|
7133
7734
|
function instrumentContext(context) {
|
|
7134
7735
|
if (!context || INSTRUMENTED_CONTEXTS.has(context)) return;
|
|
7135
7736
|
INSTRUMENTED_CONTEXTS.add(context);
|
|
7737
|
+
if (typeof context.once === "function") {
|
|
7738
|
+
OPEN_CONTEXTS.add(context);
|
|
7739
|
+
context.once("close", () => OPEN_CONTEXTS.delete(context));
|
|
7740
|
+
}
|
|
7136
7741
|
observeOwnApiRequests(context);
|
|
7137
7742
|
if (process.env.PIWI_CAPTURE_LOCATORS !== "false" && typeof context.addInitScript === "function") {
|
|
7138
7743
|
void Promise.resolve(context.addInitScript({ content: PROBE_INIT_SCRIPT })).catch(() => {
|
|
@@ -7152,6 +7757,7 @@ function instrumentContext(context) {
|
|
|
7152
7757
|
await drainPendingProbes(1e3);
|
|
7153
7758
|
const sink = currentSink;
|
|
7154
7759
|
if (sink) {
|
|
7760
|
+
await endShownStops(sink, "closed", { context });
|
|
7155
7761
|
await stashPageState(sink, { context });
|
|
7156
7762
|
await maybeOpenPicker(sink, { context });
|
|
7157
7763
|
await stopSinkCodeReach(sink, { context });
|
|
@@ -7168,6 +7774,7 @@ function patchBrowser(browser) {
|
|
|
7168
7774
|
browser.newPage = async (...args) => {
|
|
7169
7775
|
const page = await originalNewPage(...args);
|
|
7170
7776
|
instrumentPage(page);
|
|
7777
|
+
await armBreakpoints([pageContext(page)]);
|
|
7171
7778
|
await awaitFlakeConditions();
|
|
7172
7779
|
await maybeStartCodeReach(page);
|
|
7173
7780
|
return page;
|
|
@@ -7176,6 +7783,7 @@ function patchBrowser(browser) {
|
|
|
7176
7783
|
browser.newContext = async (...args) => {
|
|
7177
7784
|
const context = await originalNewContext(...args);
|
|
7178
7785
|
instrumentContext(context);
|
|
7786
|
+
await armBreakpoints([context]);
|
|
7179
7787
|
return context;
|
|
7180
7788
|
};
|
|
7181
7789
|
if (typeof browser.close === "function") {
|
|
@@ -7227,14 +7835,20 @@ async function flushSink(sink, testInfo) {
|
|
|
7227
7835
|
let drainDeadline;
|
|
7228
7836
|
await Promise.race([
|
|
7229
7837
|
Promise.allSettled(sink.capturePromises),
|
|
7230
|
-
new Promise((
|
|
7231
|
-
drainDeadline = setTimeout(
|
|
7838
|
+
new Promise((resolve5) => {
|
|
7839
|
+
drainDeadline = setTimeout(resolve5, 2e3);
|
|
7232
7840
|
})
|
|
7233
7841
|
]);
|
|
7234
7842
|
clearTimeout(drainDeadline);
|
|
7235
7843
|
const page = sink.lastActivePage;
|
|
7236
7844
|
const pageReadable = page !== null && !isPageClosed(page);
|
|
7237
7845
|
if (pageReadable) await maybeOpenPicker(sink);
|
|
7846
|
+
if (sink.stops.length > 0) {
|
|
7847
|
+
await testInfo.attach(ATTACHMENT_NAMES.pauses, {
|
|
7848
|
+
contentType: "application/json",
|
|
7849
|
+
body: Buffer.from(JSON.stringify(sink.stops))
|
|
7850
|
+
});
|
|
7851
|
+
}
|
|
7238
7852
|
if (sink.capturedLocators.length > 0) {
|
|
7239
7853
|
await testInfo.attach(ATTACHMENT_NAMES.locators, {
|
|
7240
7854
|
contentType: "application/json",
|
|
@@ -7435,6 +8049,7 @@ var piwiFixtures = {
|
|
|
7435
8049
|
// never double-wraps a page the browser patch already instrumented.
|
|
7436
8050
|
page: async ({ page }, use) => {
|
|
7437
8051
|
instrumentPage(page);
|
|
8052
|
+
await armBreakpoints([pageContext(page)]);
|
|
7438
8053
|
await awaitFlakeConditions();
|
|
7439
8054
|
await maybeStartCodeReach(page);
|
|
7440
8055
|
await use(page);
|
|
@@ -7455,8 +8070,9 @@ var piwiFixtures = {
|
|
|
7455
8070
|
if (pauseSet.size) {
|
|
7456
8071
|
const reason = pauseSkipReason(pauseGateFromTestInfo(testInfo));
|
|
7457
8072
|
if (reason) logPauseSkipOnce(reason);
|
|
7458
|
-
else sink.pause = { set: pauseSet,
|
|
8073
|
+
else sink.pause = { set: pauseSet, step: false, finished: false };
|
|
7459
8074
|
}
|
|
8075
|
+
if (sink.pause || canRecordFromHere()) endStopsWithTestFunction(testInfo, sink);
|
|
7460
8076
|
if (isFlakeMode()) {
|
|
7461
8077
|
const plan = loadFlakePlan();
|
|
7462
8078
|
const role = flakeRoleForTest(plan, {
|
|
@@ -7481,9 +8097,12 @@ var piwiFixtures = {
|
|
|
7481
8097
|
activeResourceLedger()?.testStarted(testInfo);
|
|
7482
8098
|
currentSink = sink;
|
|
7483
8099
|
try {
|
|
8100
|
+
await armBreakpoints(OPEN_CONTEXTS);
|
|
7484
8101
|
await startCodeReachOnOpenPages();
|
|
7485
8102
|
await use();
|
|
7486
8103
|
} finally {
|
|
8104
|
+
await endShownStops(sink, "ended");
|
|
8105
|
+
await awaitReleasedCalls(sink);
|
|
7487
8106
|
currentSink = null;
|
|
7488
8107
|
if (sink.probeItem) {
|
|
7489
8108
|
const applied = sink.probeInterception?.applied() ?? false;
|