@piwitests/reporter 0.58.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 +1321 -494
- package/dist/internal/capture/attachments.d.ts +1 -0
- package/dist/internal/capture/attachments.js +2 -1
- package/dist/internal/capture/capture-fixtures.js +1011 -333
- package/dist/internal/capture/pick-on-failure.d.ts +6 -3
- package/dist/internal/capture/pick-on-failure.js +123 -45
- 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
|
|
@@ -412,10 +412,11 @@ function installPickerOverlay(arg) {
|
|
|
412
412
|
}
|
|
413
413
|
const Z = 2147483600;
|
|
414
414
|
const testIdAttr = arg.testIdAttribute || "data-testid";
|
|
415
|
+
const enterReaches = arg.transport === "global";
|
|
415
416
|
const words = {
|
|
416
417
|
banner: "Piwi inspector \u2014 click any element to generate locators for it",
|
|
417
|
-
keys: "\u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
418
|
-
keysHovering: "click to pick \xB7 \u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
418
|
+
keys: enterReaches ? "Enter picks the focused element \xB7 \u2191 parent \xB7 \u2193 child \xB7 Esc skip" : "\u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
419
|
+
keysHovering: enterReaches ? "click or Enter to pick \xB7 \u2191 parent \xB7 \u2193 child \xB7 Esc skip" : "click to pick \xB7 \u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
419
420
|
analyzing: "Analyzing element\u2026"
|
|
420
421
|
};
|
|
421
422
|
for (const key of Object.keys(words)) {
|
|
@@ -510,6 +511,16 @@ function installPickerOverlay(arg) {
|
|
|
510
511
|
const cls = (el.getAttribute && el.getAttribute("class") || "").split(/\s+/).find((c) => c.length > 1);
|
|
511
512
|
return cls ? `locator(${quoteJs(`.${cls}`)})` : tag;
|
|
512
513
|
};
|
|
514
|
+
const parentOf = (node) => {
|
|
515
|
+
if (node.parentElement) return node.parentElement;
|
|
516
|
+
const root = node.getRootNode ? node.getRootNode() : null;
|
|
517
|
+
return root && root !== doc && root.host ? root.host : null;
|
|
518
|
+
};
|
|
519
|
+
const targetOf = (e) => {
|
|
520
|
+
const path15 = typeof e.composedPath === "function" ? e.composedPath() : null;
|
|
521
|
+
const first = path15 && path15.length ? path15[0] : null;
|
|
522
|
+
return first && first.nodeType === 1 ? first : e.target;
|
|
523
|
+
};
|
|
513
524
|
const buildChain = (raw) => {
|
|
514
525
|
const chain2 = [];
|
|
515
526
|
let node = raw;
|
|
@@ -517,7 +528,7 @@ function installPickerOverlay(arg) {
|
|
|
517
528
|
const tag = (node.tagName || "").toLowerCase();
|
|
518
529
|
if (tag === "body" || tag === "html") break;
|
|
519
530
|
chain2.push(node);
|
|
520
|
-
node = node
|
|
531
|
+
node = parentOf(node);
|
|
521
532
|
}
|
|
522
533
|
return chain2.length ? chain2 : [raw];
|
|
523
534
|
};
|
|
@@ -608,10 +619,14 @@ function installPickerOverlay(arg) {
|
|
|
608
619
|
}
|
|
609
620
|
};
|
|
610
621
|
const onMove = (e) => {
|
|
611
|
-
const raw = e
|
|
622
|
+
const raw = targetOf(e);
|
|
612
623
|
if (!raw || isOwn(raw)) {
|
|
624
|
+
chain = [];
|
|
625
|
+
idx = 0;
|
|
626
|
+
lastRaw = null;
|
|
613
627
|
highlight.style.display = "none";
|
|
614
628
|
label.style.display = "none";
|
|
629
|
+
foot.textContent = words.keys;
|
|
615
630
|
return;
|
|
616
631
|
}
|
|
617
632
|
if (raw !== lastRaw) {
|
|
@@ -690,17 +705,30 @@ function installPickerOverlay(arg) {
|
|
|
690
705
|
const onClick = (e) => {
|
|
691
706
|
stop(e);
|
|
692
707
|
const el = current();
|
|
693
|
-
if (!el || isOwn(e
|
|
708
|
+
if (!el || isOwn(targetOf(e))) return;
|
|
694
709
|
reportPicked(el);
|
|
695
710
|
};
|
|
711
|
+
const focused = () => {
|
|
712
|
+
let el = doc.activeElement;
|
|
713
|
+
while (el && el.shadowRoot && el.shadowRoot.activeElement) el = el.shadowRoot.activeElement;
|
|
714
|
+
const tag = (el && el.tagName || "").toLowerCase();
|
|
715
|
+
return !el || tag === "body" || tag === "html" || isOwn(el) ? null : el;
|
|
716
|
+
};
|
|
696
717
|
const onKey = (e) => {
|
|
697
718
|
if (e.key === "Escape") {
|
|
698
719
|
stop(e);
|
|
699
720
|
reportSkipped();
|
|
700
721
|
return;
|
|
701
722
|
}
|
|
723
|
+
if (e.key === "Enter" && !e.isComposing) {
|
|
724
|
+
stop(e);
|
|
725
|
+
const el = current() || focused();
|
|
726
|
+
if (el) reportPicked(el);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
702
729
|
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
703
730
|
stop(e);
|
|
731
|
+
if (!chain.length) return;
|
|
704
732
|
if (e.key === "ArrowUp") idx = Math.min(idx + 1, chain.length - 1);
|
|
705
733
|
else idx = Math.max(idx - 1, 0);
|
|
706
734
|
refresh();
|
|
@@ -774,12 +802,43 @@ function showAnchorPicker(arg) {
|
|
|
774
802
|
const al = n.getAttribute && n.getAttribute("aria-level");
|
|
775
803
|
return al && /^\d+$/.test(al) ? Number(al) : null;
|
|
776
804
|
};
|
|
805
|
+
const parentOf = (n) => {
|
|
806
|
+
if (n.parentElement) return n.parentElement;
|
|
807
|
+
const root = n.getRootNode ? n.getRootNode() : null;
|
|
808
|
+
return root && root !== doc && root.host ? root.host : null;
|
|
809
|
+
};
|
|
810
|
+
const shadowRoots = [];
|
|
811
|
+
const collectRoots = (root) => {
|
|
812
|
+
const all = root.querySelectorAll("*");
|
|
813
|
+
for (let i = 0; i < all.length; i++) {
|
|
814
|
+
const sr = all[i].shadowRoot;
|
|
815
|
+
if (sr) {
|
|
816
|
+
shadowRoots.push(sr);
|
|
817
|
+
collectRoots(sr);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
try {
|
|
822
|
+
collectRoots(doc);
|
|
823
|
+
} catch {
|
|
824
|
+
shadowRoots.length = 0;
|
|
825
|
+
}
|
|
826
|
+
const within = (node2, scope) => {
|
|
827
|
+
if (scope === doc) return true;
|
|
828
|
+
for (let n = node2; n; n = parentOf(n)) if (n === scope) return true;
|
|
829
|
+
return false;
|
|
830
|
+
};
|
|
831
|
+
const queryAll = (scope, sel) => {
|
|
832
|
+
const found = Array.from(scope.querySelectorAll(sel));
|
|
833
|
+
for (const sr of shadowRoots) if (within(sr.host, scope)) found.push(...Array.from(sr.querySelectorAll(sel)));
|
|
834
|
+
return found;
|
|
835
|
+
};
|
|
777
836
|
const leafCountable = !!leafTestId || roleOf2(el) === leafRole;
|
|
778
837
|
const leafMatches = (scope) => {
|
|
779
838
|
try {
|
|
780
|
-
if (leafTestId) return scope
|
|
839
|
+
if (leafTestId) return queryAll(scope, `[${testIdAttr}=${JSON.stringify(leafTestId)}]`).length;
|
|
781
840
|
if (!leafCountable) return -1;
|
|
782
|
-
const nodes = scope
|
|
841
|
+
const nodes = queryAll(scope, roleSources);
|
|
783
842
|
if (nodes.length > 2e3) return -1;
|
|
784
843
|
let matched = 0;
|
|
785
844
|
for (let i = 0; i < nodes.length; i++) {
|
|
@@ -795,20 +854,20 @@ function showAnchorPicker(arg) {
|
|
|
795
854
|
};
|
|
796
855
|
let roleNodes = [];
|
|
797
856
|
try {
|
|
798
|
-
const all = doc
|
|
799
|
-
if (all.length <= 4e3) roleNodes =
|
|
857
|
+
const all = queryAll(doc, roleSources);
|
|
858
|
+
if (all.length <= 4e3) roleNodes = all;
|
|
800
859
|
} catch {
|
|
801
860
|
roleNodes = [];
|
|
802
861
|
}
|
|
803
862
|
const count = (sel) => {
|
|
804
863
|
try {
|
|
805
|
-
return doc
|
|
864
|
+
return queryAll(doc, sel).length;
|
|
806
865
|
} catch {
|
|
807
866
|
return void 0;
|
|
808
867
|
}
|
|
809
868
|
};
|
|
810
869
|
const rows = [];
|
|
811
|
-
let node = el
|
|
870
|
+
let node = parentOf(el);
|
|
812
871
|
let depth = 0;
|
|
813
872
|
while (node && depth < 12) {
|
|
814
873
|
depth++;
|
|
@@ -845,7 +904,7 @@ function showAnchorPicker(arg) {
|
|
|
845
904
|
hookLabel,
|
|
846
905
|
selectable: !!(testId || id || role && (ariaLabel || info.roleCount === 1))
|
|
847
906
|
});
|
|
848
|
-
node = node
|
|
907
|
+
node = parentOf(node);
|
|
849
908
|
}
|
|
850
909
|
if (rows.length === 0) {
|
|
851
910
|
g.__piwiAnchorState = "skipped";
|
|
@@ -874,9 +933,9 @@ function showAnchorPicker(arg) {
|
|
|
874
933
|
footer.style.cssText = "margin:10px 0;font-weight:600;";
|
|
875
934
|
const segMatches = (scope, info) => {
|
|
876
935
|
try {
|
|
877
|
-
if (info.testId) return
|
|
878
|
-
if (info.id) return
|
|
879
|
-
const nodes =
|
|
936
|
+
if (info.testId) return queryAll(scope, `[${testIdAttr}=${JSON.stringify(info.testId)}]`);
|
|
937
|
+
if (info.id) return queryAll(scope, `#${doc.defaultView.CSS.escape(info.id)}`);
|
|
938
|
+
const nodes = queryAll(scope, roleSources);
|
|
880
939
|
if (nodes.length > 2e3) return [];
|
|
881
940
|
return nodes.filter(
|
|
882
941
|
(n) => roleOf2(n) === info.role && (!info.ariaLabel || n.getAttribute && n.getAttribute("aria-label") === info.ariaLabel)
|
|
@@ -1163,8 +1222,11 @@ function showPauseBar(arg) {
|
|
|
1163
1222
|
};
|
|
1164
1223
|
const buttons = [
|
|
1165
1224
|
["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
|
|
1166
|
-
["Step", "step", "Pause again
|
|
1225
|
+
["Step", "step", "Pause again before the next call", false],
|
|
1167
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
|
+
] : [],
|
|
1168
1230
|
["Finish", "finish", "Run on and pause no more in this test", false]
|
|
1169
1231
|
];
|
|
1170
1232
|
for (const [label, choice, hint, primary] of buttons) {
|
|
@@ -1926,21 +1988,21 @@ var MAX_INDETERMINATE = 20;
|
|
|
1926
1988
|
var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
|
|
1927
1989
|
var LEVEL = `${STEP}(?:/${STEP})*`;
|
|
1928
1990
|
var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
|
|
1929
|
-
function isElementPath(
|
|
1930
|
-
return typeof
|
|
1991
|
+
function isElementPath(path15) {
|
|
1992
|
+
return typeof path15 === "string" && path15.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path15);
|
|
1931
1993
|
}
|
|
1932
1994
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
1933
1995
|
function elementRef(v) {
|
|
1934
1996
|
if (!v || typeof v !== "object") return null;
|
|
1935
|
-
const { path:
|
|
1936
|
-
if (!isElementPath(
|
|
1937
|
-
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) : "" };
|
|
1938
2000
|
}
|
|
1939
2001
|
function textPoint(v) {
|
|
1940
2002
|
if (!v || typeof v !== "object") return null;
|
|
1941
|
-
const { path:
|
|
2003
|
+
const { path: path15, offset } = v;
|
|
1942
2004
|
const at = offsetOf(offset);
|
|
1943
|
-
return isElementPath(
|
|
2005
|
+
return isElementPath(path15) && at !== null ? { path: path15, offset: at } : null;
|
|
1944
2006
|
}
|
|
1945
2007
|
function sanitizePageInteraction(raw) {
|
|
1946
2008
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -2346,7 +2408,8 @@ var ATTACHMENT_NAMES = {
|
|
|
2346
2408
|
userPick: "piwi-user-pick",
|
|
2347
2409
|
aiUsage: "piwi-ai-usage",
|
|
2348
2410
|
aiMeta: "piwi-ai-meta",
|
|
2349
|
-
resources: "piwi-resources"
|
|
2411
|
+
resources: "piwi-resources",
|
|
2412
|
+
pauses: "piwi-pauses"
|
|
2350
2413
|
};
|
|
2351
2414
|
var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
|
|
2352
2415
|
var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
|
|
@@ -2580,13 +2643,19 @@ var LocatorPageLog = class {
|
|
|
2580
2643
|
};
|
|
2581
2644
|
|
|
2582
2645
|
// src/internal/probe/mode.ts
|
|
2583
|
-
var
|
|
2646
|
+
var fs3 = __toESM(require("fs"));
|
|
2647
|
+
|
|
2648
|
+
// src/internal/config/env.ts
|
|
2649
|
+
var path3 = __toESM(require("path"));
|
|
2584
2650
|
|
|
2585
2651
|
// src/internal/config/desktop.ts
|
|
2586
2652
|
var fs = __toESM(require("fs"));
|
|
2587
2653
|
var os = __toESM(require("os"));
|
|
2588
2654
|
var path2 = __toESM(require("path"));
|
|
2589
2655
|
|
|
2656
|
+
// src/internal/config/dotenv.ts
|
|
2657
|
+
var fs2 = __toESM(require("fs"));
|
|
2658
|
+
|
|
2590
2659
|
// src/internal/config/env.ts
|
|
2591
2660
|
var DEFAULTS = {
|
|
2592
2661
|
projectName: "default-project",
|
|
@@ -2623,6 +2692,7 @@ var DEFAULTS = {
|
|
|
2623
2692
|
};
|
|
2624
2693
|
var PIWI_ENV_KEYS = {
|
|
2625
2694
|
serverUrl: "PIWI_DASHBOARD_URL",
|
|
2695
|
+
desktopApp: "PIWI_DESKTOP_APP",
|
|
2626
2696
|
projectName: "PIWI_PROJECT_NAME",
|
|
2627
2697
|
verbose: "PIWI_VERBOSE",
|
|
2628
2698
|
apiKey: "PIWI_API_KEY",
|
|
@@ -2720,14 +2790,14 @@ var ENV_FALLBACK_SPECS = [
|
|
|
2720
2790
|
];
|
|
2721
2791
|
|
|
2722
2792
|
// src/internal/support/test-paths.ts
|
|
2723
|
-
var
|
|
2793
|
+
var path4 = __toESM(require("path"));
|
|
2724
2794
|
function configDirFromConfig(config) {
|
|
2725
|
-
if (config?.configFile) return
|
|
2795
|
+
if (config?.configFile) return path4.dirname(config.configFile);
|
|
2726
2796
|
if (config?.rootDir) return config.rootDir;
|
|
2727
2797
|
return process.cwd();
|
|
2728
2798
|
}
|
|
2729
2799
|
function relativeTestPath(file, base) {
|
|
2730
|
-
return
|
|
2800
|
+
return path4.relative(base, file).split(path4.sep).join("/");
|
|
2731
2801
|
}
|
|
2732
2802
|
|
|
2733
2803
|
// src/internal/probe/plan.ts
|
|
@@ -2737,8 +2807,8 @@ function parseRouteKey(routeKey2) {
|
|
|
2737
2807
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
2738
2808
|
}
|
|
2739
2809
|
function patternToRegex(pattern) {
|
|
2740
|
-
const
|
|
2741
|
-
const escaped =
|
|
2810
|
+
const path15 = pattern.split("?")[0] ?? pattern;
|
|
2811
|
+
const escaped = path15.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
2742
2812
|
return new RegExp(`^${escaped}$`);
|
|
2743
2813
|
}
|
|
2744
2814
|
function suitePathEqual(a, b) {
|
|
@@ -2761,13 +2831,13 @@ function matchProbeItem(plan, test) {
|
|
|
2761
2831
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
2762
2832
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
2763
2833
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
2764
|
-
let
|
|
2834
|
+
let path15;
|
|
2765
2835
|
try {
|
|
2766
|
-
|
|
2836
|
+
path15 = new URL(url).pathname;
|
|
2767
2837
|
} catch {
|
|
2768
|
-
|
|
2838
|
+
path15 = url.split("?")[0] ?? url;
|
|
2769
2839
|
}
|
|
2770
|
-
return patternToRegex(pattern).test(
|
|
2840
|
+
return patternToRegex(pattern).test(path15);
|
|
2771
2841
|
}
|
|
2772
2842
|
function createProbeState() {
|
|
2773
2843
|
return { navigated: false, matches: 0 };
|
|
@@ -2789,10 +2859,10 @@ function isProbeMode() {
|
|
|
2789
2859
|
function loadPlan() {
|
|
2790
2860
|
if (planCache !== void 0) return planCache;
|
|
2791
2861
|
planCache = null;
|
|
2792
|
-
const
|
|
2793
|
-
if (
|
|
2862
|
+
const path15 = process.env[PIWI_PROBE_ENV.plan];
|
|
2863
|
+
if (path15) {
|
|
2794
2864
|
try {
|
|
2795
|
-
planCache = JSON.parse(
|
|
2865
|
+
planCache = JSON.parse(fs3.readFileSync(path15, "utf8"));
|
|
2796
2866
|
} catch {
|
|
2797
2867
|
planCache = null;
|
|
2798
2868
|
}
|
|
@@ -2816,10 +2886,10 @@ function outcomeFromStatus(status, applied) {
|
|
|
2816
2886
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
2817
2887
|
}
|
|
2818
2888
|
function recordProbeOutcome(line) {
|
|
2819
|
-
const
|
|
2820
|
-
if (!
|
|
2889
|
+
const path15 = process.env[PIWI_PROBE_ENV.results];
|
|
2890
|
+
if (!path15) return;
|
|
2821
2891
|
try {
|
|
2822
|
-
|
|
2892
|
+
fs3.appendFileSync(path15, `${JSON.stringify(line)}
|
|
2823
2893
|
`);
|
|
2824
2894
|
} catch {
|
|
2825
2895
|
}
|
|
@@ -2949,7 +3019,7 @@ function originOf(url) {
|
|
|
2949
3019
|
return null;
|
|
2950
3020
|
}
|
|
2951
3021
|
}
|
|
2952
|
-
var sleep = (ms) => new Promise((
|
|
3022
|
+
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, Math.max(0, ms)));
|
|
2953
3023
|
async function performRouteAction(route, action, startedAt) {
|
|
2954
3024
|
switch (action.kind) {
|
|
2955
3025
|
case "abort":
|
|
@@ -3060,7 +3130,7 @@ async function installProbeInterception(page, item) {
|
|
|
3060
3130
|
}
|
|
3061
3131
|
|
|
3062
3132
|
// src/internal/flake/mode.ts
|
|
3063
|
-
var
|
|
3133
|
+
var fs4 = __toESM(require("fs"));
|
|
3064
3134
|
|
|
3065
3135
|
// ../core/src/error-parse.ts
|
|
3066
3136
|
var ANSI_RE = new RegExp("\\u001B\\[[0-9;]*m", "g");
|
|
@@ -3371,7 +3441,7 @@ function loadFlakePlan() {
|
|
|
3371
3441
|
if (planCache2 && planCache2.file === file) return planCache2.plan;
|
|
3372
3442
|
let text;
|
|
3373
3443
|
try {
|
|
3374
|
-
text =
|
|
3444
|
+
text = fs4.readFileSync(file, "utf8");
|
|
3375
3445
|
} catch (error) {
|
|
3376
3446
|
throw new Error(`[Piwi Dashboard] Cannot read the flake plan ${file}: ${errorMessage(error)}`);
|
|
3377
3447
|
}
|
|
@@ -3524,7 +3594,7 @@ function recordFlakeResult(line) {
|
|
|
3524
3594
|
const file = process.env[PIWI_FLAKE_ENV.results]?.trim();
|
|
3525
3595
|
if (!file) return;
|
|
3526
3596
|
try {
|
|
3527
|
-
|
|
3597
|
+
fs4.appendFileSync(file, `${JSON.stringify(line)}
|
|
3528
3598
|
`);
|
|
3529
3599
|
} catch {
|
|
3530
3600
|
}
|
|
@@ -3590,13 +3660,13 @@ function normalizeFunctionRanges(ranges) {
|
|
|
3590
3660
|
}
|
|
3591
3661
|
function normalizeCoveragePath(raw) {
|
|
3592
3662
|
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
3593
|
-
const
|
|
3594
|
-
if (!
|
|
3595
|
-
if (/(^|\/)node_modules\//.test(
|
|
3596
|
-
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;
|
|
3597
3667
|
}
|
|
3598
|
-
function fileEntry(
|
|
3599
|
-
const file = { path:
|
|
3668
|
+
function fileEntry(path15, runtime, blob, functions = [], lines = []) {
|
|
3669
|
+
const file = { path: path15, runtime };
|
|
3600
3670
|
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
3601
3671
|
if (functions.length > 0) file.functions = functions;
|
|
3602
3672
|
if (lines.length > 0) file.lines = lines;
|
|
@@ -3615,11 +3685,11 @@ function sanitizeCoverage(raw) {
|
|
|
3615
3685
|
const files = [];
|
|
3616
3686
|
for (const item of raw.files) {
|
|
3617
3687
|
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
3618
|
-
const
|
|
3619
|
-
if (
|
|
3688
|
+
const path15 = normalizeCoveragePath(item.path);
|
|
3689
|
+
if (path15 === null) continue;
|
|
3620
3690
|
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
3621
3691
|
files.push(
|
|
3622
|
-
fileEntry(
|
|
3692
|
+
fileEntry(path15, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
3623
3693
|
);
|
|
3624
3694
|
}
|
|
3625
3695
|
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
@@ -4030,7 +4100,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
|
|
|
4030
4100
|
}
|
|
4031
4101
|
|
|
4032
4102
|
// src/internal/capture/pick-on-failure.ts
|
|
4033
|
-
var
|
|
4103
|
+
var path5 = __toESM(require("path"));
|
|
4034
4104
|
function parseLeafLocatorExpression(rawExpr) {
|
|
4035
4105
|
const leaf = parseLeafLocatorCall(rawExpr);
|
|
4036
4106
|
return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
|
|
@@ -4045,37 +4115,60 @@ ${err.stack ?? ""}`));
|
|
|
4045
4115
|
const parsed2 = parseLeafLocatorExpression(header);
|
|
4046
4116
|
if (!parsed2) continue;
|
|
4047
4117
|
const loc = err.location;
|
|
4048
|
-
const location = loc ? `${
|
|
4118
|
+
const location = loc ? `${path5.relative(process.cwd(), loc.file).split(path5.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
|
4049
4119
|
return { method: parsed2.method, args: parsed2.args, location };
|
|
4050
4120
|
}
|
|
4051
4121
|
return null;
|
|
4052
4122
|
}
|
|
4053
|
-
|
|
4123
|
+
var PICK_ENDED = "__piwiPickEnded";
|
|
4124
|
+
async function cleanupPicker(page, ended = false) {
|
|
4054
4125
|
try {
|
|
4055
|
-
await page.evaluate(
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
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
|
+
);
|
|
4068
4144
|
} catch {
|
|
4069
4145
|
}
|
|
4070
4146
|
}
|
|
4071
|
-
|
|
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
|
+
};
|
|
4072
4167
|
try {
|
|
4073
4168
|
const overlayArg = { transport: "global", failing };
|
|
4169
|
+
await page.evaluate((endedKey) => delete globalThis[endedKey], PICK_ENDED);
|
|
4074
4170
|
await page.evaluate(installPickerOverlay, overlayArg);
|
|
4075
|
-
await
|
|
4076
|
-
timeout: 0,
|
|
4077
|
-
polling: 250
|
|
4078
|
-
});
|
|
4171
|
+
if (!await answered("__piwiPickState")) return null;
|
|
4079
4172
|
const state = await page.evaluate(() => globalThis.__piwiPickState);
|
|
4080
4173
|
if (state !== "picked") return null;
|
|
4081
4174
|
const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
|
|
@@ -4096,10 +4189,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
4096
4189
|
leafLevel: level,
|
|
4097
4190
|
leafTestId: attrs.attributes["data-testid"] ?? null
|
|
4098
4191
|
});
|
|
4099
|
-
await
|
|
4100
|
-
timeout: 0,
|
|
4101
|
-
polling: 250
|
|
4102
|
-
});
|
|
4192
|
+
if (!await answered("__piwiAnchorState")) return null;
|
|
4103
4193
|
const anchorResult = await page.evaluate(() => {
|
|
4104
4194
|
const g = globalThis;
|
|
4105
4195
|
return { state: g.__piwiAnchorState, anchors: g.__piwiPickAnchors ?? [], chainCount: g.__piwiPickChainCount };
|
|
@@ -4121,10 +4211,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
4121
4211
|
failing,
|
|
4122
4212
|
choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
|
|
4123
4213
|
});
|
|
4124
|
-
await
|
|
4125
|
-
timeout: 0,
|
|
4126
|
-
polling: 250
|
|
4127
|
-
});
|
|
4214
|
+
if (!await answered("__piwiPickChoice")) return null;
|
|
4128
4215
|
const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
|
|
4129
4216
|
if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
|
|
4130
4217
|
const picked = { ...ranked[choice], pickedByUser: true };
|
|
@@ -4143,7 +4230,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
4143
4230
|
...anchors.length > 0 ? { anchors } : {}
|
|
4144
4231
|
};
|
|
4145
4232
|
} finally {
|
|
4146
|
-
await cleanupPicker(page);
|
|
4233
|
+
await cleanupPicker(page, ended?.aborted);
|
|
4147
4234
|
}
|
|
4148
4235
|
}
|
|
4149
4236
|
async function runLocatorPicker(page, testInfo, failed, probe) {
|
|
@@ -4198,7 +4285,7 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
4198
4285
|
}
|
|
4199
4286
|
|
|
4200
4287
|
// src/internal/capture/pause-at.ts
|
|
4201
|
-
var
|
|
4288
|
+
var path8 = __toESM(require("path"));
|
|
4202
4289
|
|
|
4203
4290
|
// ../core/src/editor-send.ts
|
|
4204
4291
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
@@ -4224,6 +4311,93 @@ function isInsidePath(file) {
|
|
|
4224
4311
|
return file.split(/[\\/]/).every((part) => part !== "..");
|
|
4225
4312
|
}
|
|
4226
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
|
+
|
|
4227
4401
|
// src/internal/capture/pause-at.ts
|
|
4228
4402
|
var PauseSet = class {
|
|
4229
4403
|
constructor(lines) {
|
|
@@ -4239,18 +4413,32 @@ var PauseSet = class {
|
|
|
4239
4413
|
return col > 0 && this.lines.has(callerLocation.slice(0, col));
|
|
4240
4414
|
}
|
|
4241
4415
|
};
|
|
4242
|
-
var
|
|
4416
|
+
var NO_BREAKPOINTS = new PauseSet(/* @__PURE__ */ new Set());
|
|
4243
4417
|
function callSiteFile(file, cwd) {
|
|
4244
4418
|
const slashed = file.replace(/\\/g, "/");
|
|
4245
|
-
const absolute =
|
|
4246
|
-
let rel = absolute ?
|
|
4247
|
-
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, "/");
|
|
4248
4422
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
4249
4423
|
return rel;
|
|
4250
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
|
+
}
|
|
4251
4439
|
var ignoredLogged = /* @__PURE__ */ new Set();
|
|
4252
4440
|
function parsePauseAt(value, cwd = process.cwd()) {
|
|
4253
|
-
if (!value?.trim()) return
|
|
4441
|
+
if (!value?.trim()) return NO_BREAKPOINTS;
|
|
4254
4442
|
const lines = /* @__PURE__ */ new Set();
|
|
4255
4443
|
for (const raw of value.split(";")) {
|
|
4256
4444
|
const entry = raw.trim();
|
|
@@ -4266,7 +4454,7 @@ function parsePauseAt(value, cwd = process.cwd()) {
|
|
|
4266
4454
|
}
|
|
4267
4455
|
lines.add(`${callSiteFile(m[1], cwd)}:${line}`);
|
|
4268
4456
|
}
|
|
4269
|
-
return lines.size ? new PauseSet(lines) :
|
|
4457
|
+
return lines.size ? new PauseSet(lines) : NO_BREAKPOINTS;
|
|
4270
4458
|
}
|
|
4271
4459
|
var parsed = null;
|
|
4272
4460
|
function currentPauseSet() {
|
|
@@ -4276,27 +4464,121 @@ function currentPauseSet() {
|
|
|
4276
4464
|
}
|
|
4277
4465
|
function pauseGateFromTestInfo(testInfo) {
|
|
4278
4466
|
const use = testInfo.project?.use ?? {};
|
|
4279
|
-
|
|
4467
|
+
const debug = testInfo._configInternal?.configCLIOverrides?.debug;
|
|
4468
|
+
return { ci: process.env.CI, headless: use.headless, pwdebug: process.env.PWDEBUG, debug };
|
|
4280
4469
|
}
|
|
4281
4470
|
function pauseSkipReason(gate) {
|
|
4282
4471
|
if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
|
|
4283
4472
|
if (gate.headless !== false)
|
|
4284
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`;
|
|
4285
4478
|
return null;
|
|
4286
4479
|
}
|
|
4287
|
-
function
|
|
4288
|
-
|
|
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;
|
|
4289
4577
|
}
|
|
4290
4578
|
function pausePlace(callerLocation) {
|
|
4291
|
-
const
|
|
4292
|
-
const fileLine = col > 0 ? callerLocation.slice(0, col) : callerLocation;
|
|
4579
|
+
const fileLine = callSiteLine(callerLocation);
|
|
4293
4580
|
return fileLine.slice(fileLine.lastIndexOf("/") + 1);
|
|
4294
4581
|
}
|
|
4295
|
-
function assertionLabel(expression, isNot) {
|
|
4296
|
-
const [first = "", ...rest] = expression.split(".").filter(Boolean);
|
|
4297
|
-
const name = first + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
4298
|
-
return `${isNot ? "not." : ""}${name || "expect"}`;
|
|
4299
|
-
}
|
|
4300
4582
|
function pickPlace(callerLocation) {
|
|
4301
4583
|
const m = /^(.+):(\d+):\d+$/.exec(callerLocation);
|
|
4302
4584
|
if (!m) return null;
|
|
@@ -4341,59 +4623,312 @@ function logPauseSkipOnce(reason) {
|
|
|
4341
4623
|
skipLogged = true;
|
|
4342
4624
|
console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt} is set but breakpoints are skipped: ${reason}`);
|
|
4343
4625
|
}
|
|
4626
|
+
var NAVIGATED = /Execution context was destroyed|Cannot find context with specified id/;
|
|
4627
|
+
var MAX_NAVIGATED = 10;
|
|
4344
4628
|
var HIGHLIGHT_MS = 2e3;
|
|
4345
|
-
async function pauseHere(page,
|
|
4346
|
-
const before = testInfo.timeout;
|
|
4347
|
-
const started = Date.now();
|
|
4629
|
+
async function pauseHere(page, point, probe, onPick, onRecord, ended) {
|
|
4348
4630
|
let shown = null;
|
|
4349
4631
|
try {
|
|
4350
|
-
testInfo.setTimeout(0);
|
|
4351
4632
|
const highlight = point.target?.highlight;
|
|
4352
4633
|
if (typeof highlight === "function") {
|
|
4353
4634
|
let timer;
|
|
4354
4635
|
shown = await Promise.race([
|
|
4355
4636
|
Promise.resolve().then(() => highlight.call(point.target)).catch(() => null),
|
|
4356
|
-
new Promise((
|
|
4357
|
-
timer = setTimeout(() =>
|
|
4637
|
+
new Promise((resolve5) => {
|
|
4638
|
+
timer = setTimeout(() => resolve5(null), HIGHLIGHT_MS);
|
|
4358
4639
|
})
|
|
4359
4640
|
]);
|
|
4360
4641
|
clearTimeout(timer);
|
|
4361
4642
|
}
|
|
4362
4643
|
const arg = {
|
|
4363
4644
|
place: pausePlace(point.location),
|
|
4364
|
-
attempt:
|
|
4365
|
-
action: point.
|
|
4366
|
-
locator:
|
|
4645
|
+
attempt: point.attempt,
|
|
4646
|
+
action: point.call,
|
|
4647
|
+
locator: null,
|
|
4648
|
+
record: !!onRecord
|
|
4367
4649
|
};
|
|
4368
4650
|
console.log(
|
|
4369
|
-
`[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).`
|
|
4370
4652
|
);
|
|
4653
|
+
let navigated = 0;
|
|
4371
4654
|
for (; ; ) {
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
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
|
+
}
|
|
4379
4677
|
}
|
|
4380
4678
|
} catch {
|
|
4381
4679
|
return "resume";
|
|
4382
4680
|
} finally {
|
|
4383
4681
|
try {
|
|
4682
|
+
if (ended?.aborted) await page.evaluate(takePauseState);
|
|
4384
4683
|
const dispose = shown?.dispose;
|
|
4385
4684
|
const hide = page.hideHighlight;
|
|
4386
4685
|
if (typeof dispose === "function") await dispose.call(shown);
|
|
4387
4686
|
else if (typeof hide === "function") await hide.call(page);
|
|
4388
4687
|
} catch {
|
|
4389
4688
|
}
|
|
4390
|
-
try {
|
|
4391
|
-
testInfo.setTimeout(rearmedTimeout(before, Date.now() - started));
|
|
4392
|
-
} catch {
|
|
4393
|
-
}
|
|
4394
4689
|
}
|
|
4395
4690
|
}
|
|
4396
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
|
+
|
|
4397
4932
|
// src/internal/collect/coverage-settings.ts
|
|
4398
4933
|
var SERVER_VALUES = /* @__PURE__ */ new Set(["files", "functions", "lines"]);
|
|
4399
4934
|
function isServerGranularity(value) {
|
|
@@ -4409,7 +4944,7 @@ function browserFunctionsInWorker() {
|
|
|
4409
4944
|
function browserFunctionCoverage(files) {
|
|
4410
4945
|
return sanitizeCoverage({
|
|
4411
4946
|
v: 1,
|
|
4412
|
-
files: [...files].map(([
|
|
4947
|
+
files: [...files].map(([path15, functions]) => ({ path: path15, runtime: "browser", functions }))
|
|
4413
4948
|
});
|
|
4414
4949
|
}
|
|
4415
4950
|
function newKnownFunctions(known, sent) {
|
|
@@ -4422,19 +4957,19 @@ function newKnownFunctions(known, sent) {
|
|
|
4422
4957
|
budget = MAX_COVERAGE_RANGES;
|
|
4423
4958
|
};
|
|
4424
4959
|
for (const [raw, ranges] of known) {
|
|
4425
|
-
const
|
|
4426
|
-
if (
|
|
4427
|
-
let named = sent.get(
|
|
4960
|
+
const path15 = normalizeCoveragePath(raw);
|
|
4961
|
+
if (path15 === null) continue;
|
|
4962
|
+
let named = sent.get(path15);
|
|
4428
4963
|
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
4429
4964
|
if (fresh.length === 0) continue;
|
|
4430
|
-
if (!named) sent.set(
|
|
4965
|
+
if (!named) sent.set(path15, named = /* @__PURE__ */ new Set());
|
|
4431
4966
|
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
4432
4967
|
while (fresh.length > 0) {
|
|
4433
4968
|
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
4434
4969
|
const part = fresh.slice(0, budget);
|
|
4435
4970
|
fresh = fresh.slice(part.length);
|
|
4436
4971
|
budget -= part.length;
|
|
4437
|
-
files.push({ path:
|
|
4972
|
+
files.push({ path: path15, allFunctions: part });
|
|
4438
4973
|
}
|
|
4439
4974
|
}
|
|
4440
4975
|
close();
|
|
@@ -4445,9 +4980,9 @@ function serverCoverageInWorker() {
|
|
|
4445
4980
|
}
|
|
4446
4981
|
|
|
4447
4982
|
// src/internal/support/aria-sampling.ts
|
|
4448
|
-
var
|
|
4983
|
+
var path10 = __toESM(require("path"));
|
|
4449
4984
|
var os3 = __toESM(require("os"));
|
|
4450
|
-
var
|
|
4985
|
+
var fs7 = __toESM(require("fs"));
|
|
4451
4986
|
|
|
4452
4987
|
// src/internal/support/instance-id.ts
|
|
4453
4988
|
var crypto = __toESM(require("crypto"));
|
|
@@ -4461,14 +4996,14 @@ function ariaSampleIdentity(filePath, title) {
|
|
|
4461
4996
|
return `${filePath}\0${title}`;
|
|
4462
4997
|
}
|
|
4463
4998
|
function getAriaSampleFilePath(projectName) {
|
|
4464
|
-
return
|
|
4999
|
+
return path10.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
4465
5000
|
}
|
|
4466
5001
|
var cachedSets = /* @__PURE__ */ new Map();
|
|
4467
5002
|
function loadAriaSampleSet(projectName) {
|
|
4468
5003
|
if (cachedSets.has(projectName)) return cachedSets.get(projectName);
|
|
4469
5004
|
let set = null;
|
|
4470
5005
|
try {
|
|
4471
|
-
const raw =
|
|
5006
|
+
const raw = fs7.readFileSync(getAriaSampleFilePath(projectName), "utf8");
|
|
4472
5007
|
const parsed2 = JSON.parse(raw);
|
|
4473
5008
|
if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
|
|
4474
5009
|
set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
|
|
@@ -4489,97 +5024,10 @@ function isDueForAriaSample(testInfo) {
|
|
|
4489
5024
|
);
|
|
4490
5025
|
}
|
|
4491
5026
|
|
|
4492
|
-
// src/internal/capture/quiet-capture.ts
|
|
4493
|
-
var import_node_module = require("module");
|
|
4494
|
-
var path8 = __toESM(require("path"));
|
|
4495
|
-
|
|
4496
|
-
// src/internal/support/reporter-version.ts
|
|
4497
|
-
var fs5 = __toESM(require("fs"));
|
|
4498
|
-
var path7 = __toESM(require("path"));
|
|
4499
|
-
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
4500
|
-
function findOwnPackageJson(fromDir) {
|
|
4501
|
-
for (const offset of CANDIDATE_OFFSETS) {
|
|
4502
|
-
try {
|
|
4503
|
-
const pkgPath = path7.resolve(fromDir, offset, "package.json");
|
|
4504
|
-
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
4505
|
-
const { name, version } = pkg;
|
|
4506
|
-
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
4507
|
-
return { version, root: path7.dirname(pkgPath) };
|
|
4508
|
-
} catch {
|
|
4509
|
-
}
|
|
4510
|
-
}
|
|
4511
|
-
return void 0;
|
|
4512
|
-
}
|
|
4513
|
-
|
|
4514
|
-
// src/internal/capture/quiet-capture.ts
|
|
4515
|
-
var BOXED_PREFIX_EXPORTS = [
|
|
4516
|
-
[
|
|
4517
|
-
"playwright-core/lib/coreBundle",
|
|
4518
|
-
(mod) => mod?.utils?.setBoxedStackPrefixes
|
|
4519
|
-
],
|
|
4520
|
-
["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
|
|
4521
|
-
];
|
|
4522
|
-
var boxing;
|
|
4523
|
-
var resolvedPlaywrightPackageJson;
|
|
4524
|
-
function resolvePlaywrightPackageJson() {
|
|
4525
|
-
if (resolvedPlaywrightPackageJson !== void 0) return resolvedPlaywrightPackageJson;
|
|
4526
|
-
try {
|
|
4527
|
-
const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
|
|
4528
|
-
resolvedPlaywrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
|
|
4529
|
-
} catch {
|
|
4530
|
-
resolvedPlaywrightPackageJson = null;
|
|
4531
|
-
}
|
|
4532
|
-
return resolvedPlaywrightPackageJson;
|
|
4533
|
-
}
|
|
4534
|
-
function resolveBoxing() {
|
|
4535
|
-
try {
|
|
4536
|
-
const root = findOwnPackageJson(__dirname)?.root;
|
|
4537
|
-
if (!root) return null;
|
|
4538
|
-
const playwrightPackageJson = resolvePlaywrightPackageJson();
|
|
4539
|
-
if (!playwrightPackageJson) return null;
|
|
4540
|
-
const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
|
|
4541
|
-
for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
|
|
4542
|
-
let set;
|
|
4543
|
-
try {
|
|
4544
|
-
set = pick(requireFromPlaywright(moduleId));
|
|
4545
|
-
} catch {
|
|
4546
|
-
continue;
|
|
4547
|
-
}
|
|
4548
|
-
if (typeof set !== "function") continue;
|
|
4549
|
-
return {
|
|
4550
|
-
set,
|
|
4551
|
-
prefixes: [path8.dirname(playwrightPackageJson), path8.join(root, "dist") + path8.sep]
|
|
4552
|
-
};
|
|
4553
|
-
}
|
|
4554
|
-
} catch {
|
|
4555
|
-
}
|
|
4556
|
-
return null;
|
|
4557
|
-
}
|
|
4558
|
-
function boxCaptureFrames(extraPrefixes = []) {
|
|
4559
|
-
const first = boxing === void 0;
|
|
4560
|
-
if (first) boxing = resolveBoxing();
|
|
4561
|
-
if (!boxing) return;
|
|
4562
|
-
const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
|
|
4563
|
-
if (!first && added.length === 0) return;
|
|
4564
|
-
boxing.prefixes.push(...added);
|
|
4565
|
-
try {
|
|
4566
|
-
boxing.set([...boxing.prefixes]);
|
|
4567
|
-
} catch {
|
|
4568
|
-
}
|
|
4569
|
-
}
|
|
4570
|
-
function callMethod(target, prop, args) {
|
|
4571
|
-
return target[prop](...args);
|
|
4572
|
-
}
|
|
4573
|
-
function internalCall(owner, run) {
|
|
4574
|
-
const wrap = owner?._wrapApiCall;
|
|
4575
|
-
if (typeof wrap !== "function") return run();
|
|
4576
|
-
return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
|
|
4577
|
-
}
|
|
4578
|
-
|
|
4579
5027
|
// src/internal/capture/code-reach.ts
|
|
4580
5028
|
var crypto2 = __toESM(require("crypto"));
|
|
4581
|
-
var
|
|
4582
|
-
var
|
|
5029
|
+
var fs8 = __toESM(require("fs"));
|
|
5030
|
+
var path11 = __toESM(require("path"));
|
|
4583
5031
|
var import_node_child_process = require("child_process");
|
|
4584
5032
|
var MAX_MAP_BYTES = 20 * 1024 * 1024;
|
|
4585
5033
|
var MAP_TIMEOUT_MS = 5e3;
|
|
@@ -4622,18 +5070,18 @@ function repositoryRoot(from) {
|
|
|
4622
5070
|
}).trim();
|
|
4623
5071
|
} catch {
|
|
4624
5072
|
}
|
|
4625
|
-
cachedRepoRoot = { from, root:
|
|
5073
|
+
cachedRepoRoot = { from, root: path11.resolve(root) };
|
|
4626
5074
|
return cachedRepoRoot.root;
|
|
4627
5075
|
}
|
|
4628
5076
|
function codeReachRoots(configDir, configured) {
|
|
4629
5077
|
const repoRoot = repositoryRoot(configDir);
|
|
4630
|
-
const
|
|
4631
|
-
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?.["*"]);
|
|
4632
5080
|
const byOrigin = /* @__PURE__ */ new Map();
|
|
4633
5081
|
if (configured && !Array.isArray(configured)) {
|
|
4634
5082
|
for (const [key, dirs] of Object.entries(configured)) {
|
|
4635
5083
|
const origin = key === "*" ? null : originOf2(key);
|
|
4636
|
-
const resolved =
|
|
5084
|
+
const resolved = resolve5(dirs);
|
|
4637
5085
|
if (origin && resolved.length) byOrigin.set(origin, resolved);
|
|
4638
5086
|
}
|
|
4639
5087
|
}
|
|
@@ -4656,7 +5104,7 @@ function scriptRoots(roots, scriptUrl, pageUrl) {
|
|
|
4656
5104
|
}
|
|
4657
5105
|
function isFile(file) {
|
|
4658
5106
|
try {
|
|
4659
|
-
return
|
|
5107
|
+
return fs8.statSync(file).isFile();
|
|
4660
5108
|
} catch {
|
|
4661
5109
|
return false;
|
|
4662
5110
|
}
|
|
@@ -4664,10 +5112,10 @@ function isFile(file) {
|
|
|
4664
5112
|
function resolveSourcePath(source, roots) {
|
|
4665
5113
|
const normalized = normalizeSourcePath(source);
|
|
4666
5114
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
4667
|
-
if (
|
|
5115
|
+
if (path11.isAbsolute(normalized) && isFile(normalized)) return path11.resolve(normalized);
|
|
4668
5116
|
const relative7 = normalized.replace(/^\/+/, "");
|
|
4669
5117
|
for (const root of roots) {
|
|
4670
|
-
const candidate =
|
|
5118
|
+
const candidate = path11.resolve(root, relative7);
|
|
4671
5119
|
if (isFile(candidate)) return candidate;
|
|
4672
5120
|
}
|
|
4673
5121
|
return null;
|
|
@@ -4699,7 +5147,7 @@ function fileForUrl(url, roots, bases) {
|
|
|
4699
5147
|
return file;
|
|
4700
5148
|
}
|
|
4701
5149
|
function relativeToRepo(file, repoRoot) {
|
|
4702
|
-
return
|
|
5150
|
+
return path11.relative(repoRoot, file).split(path11.sep).join("/");
|
|
4703
5151
|
}
|
|
4704
5152
|
function scriptVersion(entry) {
|
|
4705
5153
|
return entry.hash ?? crypto2.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
@@ -4762,10 +5210,10 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
4762
5210
|
const files = /* @__PURE__ */ new Map();
|
|
4763
5211
|
const known = /* @__PURE__ */ new Map();
|
|
4764
5212
|
const addTo = (into, file, functions) => {
|
|
4765
|
-
const
|
|
4766
|
-
const listed = into.get(
|
|
5213
|
+
const path15 = relativeToRepo(file, roots.repoRoot);
|
|
5214
|
+
const listed = into.get(path15);
|
|
4767
5215
|
if (listed) listed.push(...functions);
|
|
4768
|
-
else into.set(
|
|
5216
|
+
else into.set(path15, [...functions]);
|
|
4769
5217
|
};
|
|
4770
5218
|
const add = (file, functions) => addTo(files, file, functions);
|
|
4771
5219
|
const bases = viteBases(entries);
|
|
@@ -4917,8 +5365,8 @@ function onScriptParsed(page, capture, event) {
|
|
|
4917
5365
|
var READ_WAIT_MS = 5e3;
|
|
4918
5366
|
function readCoverage(page, cdp) {
|
|
4919
5367
|
let timer;
|
|
4920
|
-
const late = new Promise((
|
|
4921
|
-
timer = setTimeout(() =>
|
|
5368
|
+
const late = new Promise((resolve5) => {
|
|
5369
|
+
timer = setTimeout(() => resolve5([]), READ_WAIT_MS);
|
|
4922
5370
|
});
|
|
4923
5371
|
const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
|
|
4924
5372
|
(answer) => answer.result,
|
|
@@ -5028,20 +5476,20 @@ async function stopCodeReach(page) {
|
|
|
5028
5476
|
}
|
|
5029
5477
|
|
|
5030
5478
|
// src/internal/capture/resource-ledger.ts
|
|
5031
|
-
var
|
|
5032
|
-
var
|
|
5479
|
+
var fs10 = __toESM(require("fs"));
|
|
5480
|
+
var path13 = __toESM(require("path"));
|
|
5033
5481
|
var import_node_module2 = require("module");
|
|
5034
5482
|
|
|
5035
5483
|
// src/internal/capture/worker-metrics.ts
|
|
5036
5484
|
var import_node_perf_hooks = require("perf_hooks");
|
|
5037
5485
|
|
|
5038
5486
|
// src/internal/support/system-readers.ts
|
|
5039
|
-
var
|
|
5040
|
-
var
|
|
5487
|
+
var fs9 = __toESM(require("fs"));
|
|
5488
|
+
var path12 = __toESM(require("path"));
|
|
5041
5489
|
var CLK_TCK = 100;
|
|
5042
5490
|
function readText(file) {
|
|
5043
5491
|
try {
|
|
5044
|
-
return
|
|
5492
|
+
return fs9.readFileSync(file, "utf8");
|
|
5045
5493
|
} catch {
|
|
5046
5494
|
return null;
|
|
5047
5495
|
}
|
|
@@ -5123,45 +5571,45 @@ var ProcFs = class {
|
|
|
5123
5571
|
}
|
|
5124
5572
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
5125
5573
|
available() {
|
|
5126
|
-
return
|
|
5574
|
+
return fs9.existsSync(path12.join(this.root, "stat"));
|
|
5127
5575
|
}
|
|
5128
5576
|
/** Every process, keyed by pid. */
|
|
5129
5577
|
table() {
|
|
5130
5578
|
const out = /* @__PURE__ */ new Map();
|
|
5131
5579
|
let names;
|
|
5132
5580
|
try {
|
|
5133
|
-
names =
|
|
5581
|
+
names = fs9.readdirSync(this.root);
|
|
5134
5582
|
} catch {
|
|
5135
5583
|
return out;
|
|
5136
5584
|
}
|
|
5137
5585
|
for (const name of names) {
|
|
5138
5586
|
if (!/^\d+$/.test(name)) continue;
|
|
5139
|
-
const text = readText(
|
|
5587
|
+
const text = readText(path12.join(this.root, name, "stat"));
|
|
5140
5588
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
5141
5589
|
if (stat) out.set(stat.pid, stat);
|
|
5142
5590
|
}
|
|
5143
5591
|
return out;
|
|
5144
5592
|
}
|
|
5145
5593
|
stat(pid) {
|
|
5146
|
-
const text = readText(
|
|
5594
|
+
const text = readText(path12.join(this.root, String(pid), "stat"));
|
|
5147
5595
|
return text ? parseProcStat(pid, text) : null;
|
|
5148
5596
|
}
|
|
5149
5597
|
runWaitMs(pid) {
|
|
5150
|
-
const text = readText(
|
|
5598
|
+
const text = readText(path12.join(this.root, String(pid), "schedstat"));
|
|
5151
5599
|
return text ? parseSchedstat(text) : null;
|
|
5152
5600
|
}
|
|
5153
5601
|
cmdline(pid) {
|
|
5154
|
-
return (readText(
|
|
5602
|
+
return (readText(path12.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
5155
5603
|
}
|
|
5156
5604
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
5157
5605
|
statusKb(pid, key) {
|
|
5158
|
-
const text = readText(
|
|
5606
|
+
const text = readText(path12.join(this.root, String(pid), "status"));
|
|
5159
5607
|
return text ? parseKbField(text, key) : null;
|
|
5160
5608
|
}
|
|
5161
5609
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
5162
5610
|
async pssKb(pid) {
|
|
5163
5611
|
try {
|
|
5164
|
-
return parseKbField(await
|
|
5612
|
+
return parseKbField(await fs9.promises.readFile(path12.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
5165
5613
|
} catch {
|
|
5166
5614
|
return null;
|
|
5167
5615
|
}
|
|
@@ -5169,29 +5617,29 @@ var ProcFs = class {
|
|
|
5169
5617
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
5170
5618
|
resetPeakRss(pid) {
|
|
5171
5619
|
try {
|
|
5172
|
-
|
|
5620
|
+
fs9.writeFileSync(path12.join(this.root, String(pid), "clear_refs"), "5");
|
|
5173
5621
|
return true;
|
|
5174
5622
|
} catch {
|
|
5175
5623
|
return false;
|
|
5176
5624
|
}
|
|
5177
5625
|
}
|
|
5178
5626
|
cpuTicks() {
|
|
5179
|
-
const text = readText(
|
|
5627
|
+
const text = readText(path12.join(this.root, "stat"));
|
|
5180
5628
|
return text ? parseCpuTicks(text) : null;
|
|
5181
5629
|
}
|
|
5182
5630
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
5183
5631
|
pressureUs(resource) {
|
|
5184
|
-
const text = readText(
|
|
5632
|
+
const text = readText(path12.join(this.root, "pressure", resource));
|
|
5185
5633
|
return text ? parsePsiTotal(text) : null;
|
|
5186
5634
|
}
|
|
5187
5635
|
memAvailableKb() {
|
|
5188
|
-
const text = readText(
|
|
5636
|
+
const text = readText(path12.join(this.root, "meminfo"));
|
|
5189
5637
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
5190
5638
|
}
|
|
5191
5639
|
/** Open file descriptors of a process. */
|
|
5192
5640
|
fdCount(pid) {
|
|
5193
5641
|
try {
|
|
5194
|
-
return
|
|
5642
|
+
return fs9.readdirSync(path12.join(this.root, String(pid), "fd")).length;
|
|
5195
5643
|
} catch {
|
|
5196
5644
|
return null;
|
|
5197
5645
|
}
|
|
@@ -5353,10 +5801,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
|
5353
5801
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
5354
5802
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
5355
5803
|
function relativePath(file) {
|
|
5356
|
-
return
|
|
5804
|
+
return path13.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path13.sep).join("/");
|
|
5357
5805
|
}
|
|
5358
5806
|
function isLibraryFile(file) {
|
|
5359
|
-
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;
|
|
5360
5808
|
}
|
|
5361
5809
|
function userSite(stack) {
|
|
5362
5810
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
@@ -5404,7 +5852,7 @@ function playwrightTestInfoSource() {
|
|
|
5404
5852
|
try {
|
|
5405
5853
|
const packageJson = resolvePlaywrightPackageJson();
|
|
5406
5854
|
if (!packageJson) return () => null;
|
|
5407
|
-
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"));
|
|
5408
5856
|
const read = globals.currentTestInfo;
|
|
5409
5857
|
if (typeof read !== "function") return () => null;
|
|
5410
5858
|
return () => {
|
|
@@ -5433,15 +5881,15 @@ function countHandles() {
|
|
|
5433
5881
|
async function settledHandles(start) {
|
|
5434
5882
|
const end = countHandles();
|
|
5435
5883
|
if (!Object.entries(end).some(([type, n]) => n > (start[type] ?? 0))) return end;
|
|
5436
|
-
await new Promise((
|
|
5884
|
+
await new Promise((resolve5) => setTimeout(resolve5, 0));
|
|
5437
5885
|
return countHandles();
|
|
5438
5886
|
}
|
|
5439
5887
|
function settleWithin(promise, ms) {
|
|
5440
5888
|
let timer;
|
|
5441
5889
|
return Promise.race([
|
|
5442
5890
|
promise.catch(() => void 0),
|
|
5443
|
-
new Promise((
|
|
5444
|
-
timer = setTimeout(
|
|
5891
|
+
new Promise((resolve5) => {
|
|
5892
|
+
timer = setTimeout(resolve5, ms);
|
|
5445
5893
|
})
|
|
5446
5894
|
]).finally(() => clearTimeout(timer));
|
|
5447
5895
|
}
|
|
@@ -5570,7 +6018,7 @@ var ResourceLedger = class {
|
|
|
5570
6018
|
const census = this.census(null);
|
|
5571
6019
|
if (this.resultsFile) {
|
|
5572
6020
|
try {
|
|
5573
|
-
|
|
6021
|
+
fs10.appendFileSync(this.resultsFile, `${JSON.stringify(census)}
|
|
5574
6022
|
`);
|
|
5575
6023
|
} catch {
|
|
5576
6024
|
}
|
|
@@ -6187,6 +6635,13 @@ function createSink() {
|
|
|
6187
6635
|
pickOffered: false,
|
|
6188
6636
|
userPick: null,
|
|
6189
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,
|
|
6190
6645
|
locatorPages: new LocatorPageLog(),
|
|
6191
6646
|
codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map() },
|
|
6192
6647
|
serverCoverage: /* @__PURE__ */ new Map(),
|
|
@@ -6212,7 +6667,7 @@ function maybeStartCodeReach(page, alreadyOpen = false) {
|
|
|
6212
6667
|
async function startCodeReachOnOpenPages() {
|
|
6213
6668
|
if (!browserCoverageInWorker() || OPEN_PAGES.size === 0) return;
|
|
6214
6669
|
const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
|
|
6215
|
-
await Promise.race([started, new Promise((
|
|
6670
|
+
await Promise.race([started, new Promise((resolve5) => setTimeout(resolve5, CODE_REACH_START_WAIT_MS).unref())]);
|
|
6216
6671
|
}
|
|
6217
6672
|
function configuredCodeReachRoots() {
|
|
6218
6673
|
const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
|
|
@@ -6226,7 +6681,7 @@ function configuredCodeReachRoots() {
|
|
|
6226
6681
|
}
|
|
6227
6682
|
} catch {
|
|
6228
6683
|
}
|
|
6229
|
-
return raw.split(
|
|
6684
|
+
return raw.split(path14.delimiter).filter(Boolean);
|
|
6230
6685
|
}
|
|
6231
6686
|
async function stopSinkCodeReach(sink, closing) {
|
|
6232
6687
|
const stopping = [];
|
|
@@ -6275,8 +6730,8 @@ async function drainPendingProbes(capMs) {
|
|
|
6275
6730
|
let cap;
|
|
6276
6731
|
await Promise.race([
|
|
6277
6732
|
Promise.allSettled(PENDING_PROBES),
|
|
6278
|
-
new Promise((
|
|
6279
|
-
cap = setTimeout(
|
|
6733
|
+
new Promise((resolve5) => {
|
|
6734
|
+
cap = setTimeout(resolve5, capMs);
|
|
6280
6735
|
})
|
|
6281
6736
|
]);
|
|
6282
6737
|
clearTimeout(cap);
|
|
@@ -6316,10 +6771,10 @@ async function readWebVitals(page) {
|
|
|
6316
6771
|
const key = entry.name.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
6317
6772
|
paint[key] = Math.round(entry.startTime);
|
|
6318
6773
|
}
|
|
6319
|
-
const readBuffered = (type, extra) => new Promise((
|
|
6774
|
+
const readBuffered = (type, extra) => new Promise((resolve5) => {
|
|
6320
6775
|
try {
|
|
6321
6776
|
const PO = globalThis.PerformanceObserver;
|
|
6322
|
-
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return
|
|
6777
|
+
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve5(null);
|
|
6323
6778
|
const out = [];
|
|
6324
6779
|
const po = new PO((list) => out.push(...list.getEntries()));
|
|
6325
6780
|
po.observe({ type, buffered: true, ...extra });
|
|
@@ -6329,10 +6784,10 @@ async function readWebVitals(page) {
|
|
|
6329
6784
|
po.disconnect();
|
|
6330
6785
|
} catch {
|
|
6331
6786
|
}
|
|
6332
|
-
|
|
6787
|
+
resolve5(out);
|
|
6333
6788
|
}, 0);
|
|
6334
6789
|
} catch {
|
|
6335
|
-
|
|
6790
|
+
resolve5(null);
|
|
6336
6791
|
}
|
|
6337
6792
|
});
|
|
6338
6793
|
const [lcpRaw, shiftRaw, eventRaw, firstInputRaw] = await Promise.all([
|
|
@@ -6447,12 +6902,12 @@ var PAGE_INVENTORY_IN_PAGE_CAP = 2e3;
|
|
|
6447
6902
|
var PAGE_INVENTORY_EVAL_TIMEOUT_MS = 2e3;
|
|
6448
6903
|
var MAX_PAGE_INVENTORY_WINDOWS = 64;
|
|
6449
6904
|
function withTimeout(promise, ms) {
|
|
6450
|
-
return new Promise((
|
|
6905
|
+
return new Promise((resolve5, reject) => {
|
|
6451
6906
|
const timer = setTimeout(() => reject(new Error("page inventory read timed out")), ms);
|
|
6452
6907
|
promise.then(
|
|
6453
6908
|
(value) => {
|
|
6454
6909
|
clearTimeout(timer);
|
|
6455
|
-
|
|
6910
|
+
resolve5(value);
|
|
6456
6911
|
},
|
|
6457
6912
|
(error) => {
|
|
6458
6913
|
clearTimeout(timer);
|
|
@@ -6567,6 +7022,7 @@ async function maybeOpenPicker(sink, closing) {
|
|
|
6567
7022
|
var INSTRUMENTED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
6568
7023
|
var OPEN_PAGES = /* @__PURE__ */ new Set();
|
|
6569
7024
|
var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
|
|
7025
|
+
var OPEN_CONTEXTS = /* @__PURE__ */ new Set();
|
|
6570
7026
|
var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
|
|
6571
7027
|
var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
|
|
6572
7028
|
var ACTION_METHOD_SET = new Set(ACTION_METHODS);
|
|
@@ -6687,8 +7143,20 @@ function recordLocatorPage(sink, page, locator, location, arrival) {
|
|
|
6687
7143
|
} catch {
|
|
6688
7144
|
}
|
|
6689
7145
|
}
|
|
6690
|
-
function
|
|
6691
|
-
|
|
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);
|
|
6692
7160
|
}
|
|
6693
7161
|
function locatorSource(target, method, args) {
|
|
6694
7162
|
try {
|
|
@@ -6698,41 +7166,219 @@ function locatorSource(target, method, args) {
|
|
|
6698
7166
|
}
|
|
6699
7167
|
return renderFailing({ method, args });
|
|
6700
7168
|
}
|
|
6701
|
-
|
|
6702
|
-
|
|
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) {
|
|
6703
7216
|
const testInfo = sink.testInfo;
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
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 },
|
|
6711
7227
|
...picked
|
|
6712
|
-
};
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
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(
|
|
6723
7240
|
page,
|
|
6724
7241
|
() => pauseHere(
|
|
6725
7242
|
page,
|
|
6726
|
-
|
|
6727
|
-
|
|
7243
|
+
{
|
|
7244
|
+
location,
|
|
7245
|
+
call: title,
|
|
7246
|
+
attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
|
|
7247
|
+
target: call?.page === page ? call.target : null
|
|
7248
|
+
},
|
|
6728
7249
|
{ fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG },
|
|
6729
|
-
onPick
|
|
7250
|
+
onPick,
|
|
7251
|
+
recorderAt(page, testInfo, location, reason === "page-pause" ? "pause" : "breakpoint"),
|
|
7252
|
+
end.signal
|
|
6730
7253
|
)
|
|
6731
|
-
)
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
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();
|
|
6735
7380
|
}
|
|
7381
|
+
return (options) => own.call(page, options);
|
|
6736
7382
|
}
|
|
6737
7383
|
function wrapLocator(page, locator, originMethod, originArgs) {
|
|
6738
7384
|
return new Proxy(locator, {
|
|
@@ -6755,36 +7401,35 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
6755
7401
|
const isNot = Boolean(callArgs[1]?.isNot);
|
|
6756
7402
|
const callerLocation = sink ? captureCallerLocation() : null;
|
|
6757
7403
|
if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
|
|
6758
|
-
|
|
6759
|
-
|
|
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 = {
|
|
6760
7411
|
method: originMethod,
|
|
6761
|
-
args: originArgs
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
if (matches === true && !alreadyProbed) {
|
|
6782
|
-
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
6783
|
-
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
6784
|
-
} else if (matches === false) {
|
|
6785
|
-
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);
|
|
6786
7432
|
}
|
|
6787
|
-
return result;
|
|
6788
7433
|
};
|
|
6789
7434
|
}
|
|
6790
7435
|
if (!ACTION_METHOD_SET.has(prop)) return original;
|
|
@@ -6799,9 +7444,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
6799
7444
|
args: originArgs,
|
|
6800
7445
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
6801
7446
|
};
|
|
6802
|
-
|
|
6803
|
-
await pauseBefore(sink, page, target, callerLocation, String(prop), used);
|
|
6804
|
-
}
|
|
7447
|
+
const call = knowLocatorCall(sink, page, target, callerLocation, used);
|
|
6805
7448
|
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
6806
7449
|
let seq = -1;
|
|
6807
7450
|
if (!alreadyProbed) {
|
|
@@ -6819,8 +7462,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
6819
7462
|
} catch (error) {
|
|
6820
7463
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
6821
7464
|
throw error;
|
|
7465
|
+
} finally {
|
|
7466
|
+
forgetLocatorCall(sink, call);
|
|
6822
7467
|
}
|
|
6823
|
-
if (!alreadyProbed) {
|
|
7468
|
+
if (!alreadyProbed && !call?.picked) {
|
|
6824
7469
|
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
6825
7470
|
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
6826
7471
|
}
|
|
@@ -6873,12 +7518,17 @@ function instrumentPage(page) {
|
|
|
6873
7518
|
}
|
|
6874
7519
|
const ctx = pageContext(page);
|
|
6875
7520
|
if (ctx) instrumentContext(ctx);
|
|
7521
|
+
const playwrightPause = playwrightPauseOf(page);
|
|
7522
|
+
if (playwrightPause) {
|
|
7523
|
+
page.pause = () => pauseInPlace(page, playwrightPause, captureCallerLocation());
|
|
7524
|
+
}
|
|
6876
7525
|
if (typeof page.close === "function") {
|
|
6877
7526
|
const originalClose = page.close.bind(page);
|
|
6878
7527
|
page.close = async (...args) => {
|
|
6879
7528
|
await drainPendingProbes(1e3);
|
|
6880
7529
|
const sink = currentSink;
|
|
6881
7530
|
if (sink) {
|
|
7531
|
+
await endShownStops(sink, "closed", { page });
|
|
6882
7532
|
await stashPageState(sink, { page });
|
|
6883
7533
|
await maybeOpenPicker(sink, { page });
|
|
6884
7534
|
await stopSinkCodeReach(sink, { page });
|
|
@@ -6967,8 +7617,18 @@ function instrumentPage(page) {
|
|
|
6967
7617
|
} catch {
|
|
6968
7618
|
}
|
|
6969
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
|
+
});
|
|
6970
7626
|
const onRequestEnd = (request, failed) => {
|
|
6971
7627
|
const sink = currentSink;
|
|
7628
|
+
if (sink?.releasedRequests.delete(request)) {
|
|
7629
|
+
sink.releasedQuietSince = Date.now();
|
|
7630
|
+
if (sink.releasedRequests.size === 0) sink.onReleasedRequestsEnded?.();
|
|
7631
|
+
}
|
|
6972
7632
|
if (!sink || RECORDED_OPEN.has(request)) return;
|
|
6973
7633
|
sink.lastActivePage = page;
|
|
6974
7634
|
const response = sink.openResponses.get(request) ?? null;
|
|
@@ -7074,6 +7734,10 @@ async function awaitFlakeConditions() {
|
|
|
7074
7734
|
function instrumentContext(context) {
|
|
7075
7735
|
if (!context || INSTRUMENTED_CONTEXTS.has(context)) return;
|
|
7076
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
|
+
}
|
|
7077
7741
|
observeOwnApiRequests(context);
|
|
7078
7742
|
if (process.env.PIWI_CAPTURE_LOCATORS !== "false" && typeof context.addInitScript === "function") {
|
|
7079
7743
|
void Promise.resolve(context.addInitScript({ content: PROBE_INIT_SCRIPT })).catch(() => {
|
|
@@ -7093,6 +7757,7 @@ function instrumentContext(context) {
|
|
|
7093
7757
|
await drainPendingProbes(1e3);
|
|
7094
7758
|
const sink = currentSink;
|
|
7095
7759
|
if (sink) {
|
|
7760
|
+
await endShownStops(sink, "closed", { context });
|
|
7096
7761
|
await stashPageState(sink, { context });
|
|
7097
7762
|
await maybeOpenPicker(sink, { context });
|
|
7098
7763
|
await stopSinkCodeReach(sink, { context });
|
|
@@ -7109,6 +7774,7 @@ function patchBrowser(browser) {
|
|
|
7109
7774
|
browser.newPage = async (...args) => {
|
|
7110
7775
|
const page = await originalNewPage(...args);
|
|
7111
7776
|
instrumentPage(page);
|
|
7777
|
+
await armBreakpoints([pageContext(page)]);
|
|
7112
7778
|
await awaitFlakeConditions();
|
|
7113
7779
|
await maybeStartCodeReach(page);
|
|
7114
7780
|
return page;
|
|
@@ -7117,6 +7783,7 @@ function patchBrowser(browser) {
|
|
|
7117
7783
|
browser.newContext = async (...args) => {
|
|
7118
7784
|
const context = await originalNewContext(...args);
|
|
7119
7785
|
instrumentContext(context);
|
|
7786
|
+
await armBreakpoints([context]);
|
|
7120
7787
|
return context;
|
|
7121
7788
|
};
|
|
7122
7789
|
if (typeof browser.close === "function") {
|
|
@@ -7168,14 +7835,20 @@ async function flushSink(sink, testInfo) {
|
|
|
7168
7835
|
let drainDeadline;
|
|
7169
7836
|
await Promise.race([
|
|
7170
7837
|
Promise.allSettled(sink.capturePromises),
|
|
7171
|
-
new Promise((
|
|
7172
|
-
drainDeadline = setTimeout(
|
|
7838
|
+
new Promise((resolve5) => {
|
|
7839
|
+
drainDeadline = setTimeout(resolve5, 2e3);
|
|
7173
7840
|
})
|
|
7174
7841
|
]);
|
|
7175
7842
|
clearTimeout(drainDeadline);
|
|
7176
7843
|
const page = sink.lastActivePage;
|
|
7177
7844
|
const pageReadable = page !== null && !isPageClosed(page);
|
|
7178
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
|
+
}
|
|
7179
7852
|
if (sink.capturedLocators.length > 0) {
|
|
7180
7853
|
await testInfo.attach(ATTACHMENT_NAMES.locators, {
|
|
7181
7854
|
contentType: "application/json",
|
|
@@ -7376,6 +8049,7 @@ var piwiFixtures = {
|
|
|
7376
8049
|
// never double-wraps a page the browser patch already instrumented.
|
|
7377
8050
|
page: async ({ page }, use) => {
|
|
7378
8051
|
instrumentPage(page);
|
|
8052
|
+
await armBreakpoints([pageContext(page)]);
|
|
7379
8053
|
await awaitFlakeConditions();
|
|
7380
8054
|
await maybeStartCodeReach(page);
|
|
7381
8055
|
await use(page);
|
|
@@ -7396,8 +8070,9 @@ var piwiFixtures = {
|
|
|
7396
8070
|
if (pauseSet.size) {
|
|
7397
8071
|
const reason = pauseSkipReason(pauseGateFromTestInfo(testInfo));
|
|
7398
8072
|
if (reason) logPauseSkipOnce(reason);
|
|
7399
|
-
else sink.pause = { set: pauseSet,
|
|
8073
|
+
else sink.pause = { set: pauseSet, step: false, finished: false };
|
|
7400
8074
|
}
|
|
8075
|
+
if (sink.pause || canRecordFromHere()) endStopsWithTestFunction(testInfo, sink);
|
|
7401
8076
|
if (isFlakeMode()) {
|
|
7402
8077
|
const plan = loadFlakePlan();
|
|
7403
8078
|
const role = flakeRoleForTest(plan, {
|
|
@@ -7422,9 +8097,12 @@ var piwiFixtures = {
|
|
|
7422
8097
|
activeResourceLedger()?.testStarted(testInfo);
|
|
7423
8098
|
currentSink = sink;
|
|
7424
8099
|
try {
|
|
8100
|
+
await armBreakpoints(OPEN_CONTEXTS);
|
|
7425
8101
|
await startCodeReachOnOpenPages();
|
|
7426
8102
|
await use();
|
|
7427
8103
|
} finally {
|
|
8104
|
+
await endShownStops(sink, "ended");
|
|
8105
|
+
await awaitReleasedCalls(sink);
|
|
7428
8106
|
currentSink = null;
|
|
7429
8107
|
if (sink.probeItem) {
|
|
7430
8108
|
const applied = sink.probeInterception?.applied() ?? false;
|