@piwitests/reporter 0.59.0 → 0.61.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.
@@ -58,7 +58,7 @@ __export(capture_fixtures_exports, {
58
58
  stopResourceLedger: () => stopResourceLedger
59
59
  });
60
60
  module.exports = __toCommonJS(capture_fixtures_exports);
61
- var path12 = __toESM(require("path"));
61
+ var path14 = __toESM(require("path"));
62
62
  var import_node_zlib2 = require("zlib");
63
63
 
64
64
  // ../picker-dom/src/probe.ts
@@ -517,8 +517,8 @@ function installPickerOverlay(arg) {
517
517
  return root && root !== doc && root.host ? root.host : null;
518
518
  };
519
519
  const targetOf = (e) => {
520
- const path13 = typeof e.composedPath === "function" ? e.composedPath() : null;
521
- const first = path13 && path13.length ? path13[0] : null;
520
+ const path15 = typeof e.composedPath === "function" ? e.composedPath() : null;
521
+ const first = path15 && path15.length ? path15[0] : null;
522
522
  return first && first.nodeType === 1 ? first : e.target;
523
523
  };
524
524
  const buildChain = (raw) => {
@@ -1222,8 +1222,11 @@ function showPauseBar(arg) {
1222
1222
  };
1223
1223
  const buttons = [
1224
1224
  ["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
1225
- ["Step", "step", "Pause again at the next action", false],
1225
+ ["Step", "step", "Pause again before the next call", false],
1226
1226
  ["Pick a locator", "pick", "Pick an element on the page and confirm a locator for it", false],
1227
+ ...arg.record ? [
1228
+ ["Record here", "record", "Record steps in this page: the editor writes them before this line", false]
1229
+ ] : [],
1227
1230
  ["Finish", "finish", "Run on and pause no more in this test", false]
1228
1231
  ];
1229
1232
  for (const [label, choice, hint, primary] of buttons) {
@@ -1675,7 +1678,7 @@ var TEST_DATA_ATTRS = /* @__PURE__ */ new Set(["data-test", "data-test-id", "dat
1675
1678
  function generateAlternatives(attrs, options = {}) {
1676
1679
  const alts = [];
1677
1680
  const seen = /* @__PURE__ */ new Set();
1678
- const add = (loc) => {
1681
+ const add2 = (loc) => {
1679
1682
  if (!seen.has(loc.locator)) {
1680
1683
  seen.add(loc.locator);
1681
1684
  alts.push(loc);
@@ -1692,7 +1695,7 @@ function generateAlternatives(attrs, options = {}) {
1692
1695
  const selectable = (name2) => name2.toLowerCase() !== testIdAttribute.toLowerCase();
1693
1696
  const testId = attr(attrs, testIdAttribute);
1694
1697
  if (testId && isUnique(counts?.testId)) {
1695
- add({
1698
+ add2({
1696
1699
  locator: `getByTestId(${quoteJsString(testId)})`,
1697
1700
  method: "getByTestId",
1698
1701
  args: { testId },
@@ -1703,7 +1706,7 @@ function generateAlternatives(attrs, options = {}) {
1703
1706
  const levelPart = level != null ? `, level: ${level}` : "";
1704
1707
  const withLevel = (base) => level != null ? { ...base, level } : base;
1705
1708
  if (role && accessibleName) {
1706
- add({
1709
+ add2({
1707
1710
  locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(accessibleName)}${levelPart} })`,
1708
1711
  method: "getByRole",
1709
1712
  args: withLevel({ role, name: accessibleName }),
@@ -1712,7 +1715,7 @@ function generateAlternatives(attrs, options = {}) {
1712
1715
  }
1713
1716
  const ariaLabel = attr(attrs, "aria-label");
1714
1717
  if (role && ariaLabel && ariaLabel !== accessibleName) {
1715
- add({
1718
+ add2({
1716
1719
  locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(ariaLabel)}${levelPart} })`,
1717
1720
  method: "getByRole",
1718
1721
  args: withLevel({ role, name: ariaLabel }),
@@ -1723,7 +1726,7 @@ function generateAlternatives(attrs, options = {}) {
1723
1726
  const label = attr(attrs, "aria-label");
1724
1727
  const labelBacked = attrs.hasLabel === void 0 ? true : attrs.hasLabel === true || label === accessibleName;
1725
1728
  if (labelBacked) {
1726
- add({
1729
+ add2({
1727
1730
  locator: `getByLabel(${quoteJsString(accessibleName)})`,
1728
1731
  method: "getByLabel",
1729
1732
  args: { label: accessibleName },
@@ -1733,7 +1736,7 @@ function generateAlternatives(attrs, options = {}) {
1733
1736
  }
1734
1737
  const placeholder = attr(attrs, "placeholder");
1735
1738
  if (placeholder) {
1736
- add({
1739
+ add2({
1737
1740
  locator: `getByPlaceholder(${quoteJsString(placeholder)})`,
1738
1741
  method: "getByPlaceholder",
1739
1742
  args: { placeholder },
@@ -1741,7 +1744,7 @@ function generateAlternatives(attrs, options = {}) {
1741
1744
  });
1742
1745
  }
1743
1746
  if (text && text.length < 80) {
1744
- add({
1747
+ add2({
1745
1748
  locator: `getByText(${quoteJsString(text)})`,
1746
1749
  method: "getByText",
1747
1750
  args: { text },
@@ -1751,7 +1754,7 @@ function generateAlternatives(attrs, options = {}) {
1751
1754
  const id = attr(attrs, "id");
1752
1755
  if (id && selectable("id") && !isAutoGenerated(id) && isUnique(counts?.id)) {
1753
1756
  const selector = isCssSafeId(id) ? `#${id}` : `[id="${escCssAttrValue(id)}"]`;
1754
- add({
1757
+ add2({
1755
1758
  locator: `locator(${quoteJsString(selector)})`,
1756
1759
  method: "locator",
1757
1760
  args: { selector },
@@ -1761,7 +1764,7 @@ function generateAlternatives(attrs, options = {}) {
1761
1764
  const name = attr(attrs, "name");
1762
1765
  if (name && selectable("name") && isUnique(counts?.name)) {
1763
1766
  const selector = `[name="${escCssAttrValue(name)}"]`;
1764
- add({
1767
+ add2({
1765
1768
  locator: `locator(${quoteJsString(selector)})`,
1766
1769
  method: "locator",
1767
1770
  args: { selector },
@@ -1770,7 +1773,7 @@ function generateAlternatives(attrs, options = {}) {
1770
1773
  }
1771
1774
  const alt = attr(attrs, "alt");
1772
1775
  if (alt) {
1773
- add({
1776
+ add2({
1774
1777
  locator: `getByAltText(${quoteJsString(alt)})`,
1775
1778
  method: "getByAltText",
1776
1779
  args: { text: alt },
@@ -1779,7 +1782,7 @@ function generateAlternatives(attrs, options = {}) {
1779
1782
  }
1780
1783
  const title = attr(attrs, "title");
1781
1784
  if (title) {
1782
- add({
1785
+ add2({
1783
1786
  locator: `getByTitle(${quoteJsString(title)})`,
1784
1787
  method: "getByTitle",
1785
1788
  args: { title },
@@ -1797,7 +1800,7 @@ function generateAlternatives(attrs, options = {}) {
1797
1800
  if (scopedCount(anc) !== 1) continue;
1798
1801
  if (!testIdAnchorDone && anc.testId && anc.testIdCount === 1) {
1799
1802
  testIdAnchorDone = true;
1800
- add({
1803
+ add2({
1801
1804
  locator: `getByTestId(${quoteJsString(anc.testId)}).${leaf.expr}`,
1802
1805
  method: leaf.method,
1803
1806
  args: { ...leaf.args, anchorTestId: anc.testId },
@@ -1807,7 +1810,7 @@ function generateAlternatives(attrs, options = {}) {
1807
1810
  if (!idAnchorDone && anc.id && selectable("id") && !isAutoGenerated(anc.id) && anc.idCount === 1) {
1808
1811
  idAnchorDone = true;
1809
1812
  const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
1810
- add({
1813
+ add2({
1811
1814
  locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
1812
1815
  method: leaf.method,
1813
1816
  args: { ...leaf.args, anchorSelector },
@@ -1818,7 +1821,7 @@ function generateAlternatives(attrs, options = {}) {
1818
1821
  dataAnchorDone = true;
1819
1822
  const { name: name2, value } = anc.dataAttr;
1820
1823
  const anchorSelector = `[${name2}="${escCssAttrValue(value)}"]`;
1821
- add({
1824
+ add2({
1822
1825
  locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
1823
1826
  method: leaf.method,
1824
1827
  args: { ...leaf.args, anchorSelector },
@@ -1828,7 +1831,7 @@ function generateAlternatives(attrs, options = {}) {
1828
1831
  const ancestorRole = anc.role || TAG_TO_ROLE[anc.tag] || null;
1829
1832
  if (!roleAnchorDone && ancestorRole && ancestorRole !== leaf.role && anc.roleCount === 1) {
1830
1833
  roleAnchorDone = true;
1831
- add({
1834
+ add2({
1832
1835
  locator: `getByRole(${quoteJsString(ancestorRole)}).${leaf.expr}`,
1833
1836
  method: leaf.method,
1834
1837
  args: { ...leaf.args, anchorRole: ancestorRole },
@@ -1837,7 +1840,7 @@ function generateAlternatives(attrs, options = {}) {
1837
1840
  }
1838
1841
  if (!filterAnchorDone && ancestorRole && anc.filterText && anc.filterRoleCount === 1) {
1839
1842
  filterAnchorDone = true;
1840
- add({
1843
+ add2({
1841
1844
  locator: `getByRole(${quoteJsString(ancestorRole)}).filter({ hasText: ${quoteJsString(anc.filterText)} }).${leaf.expr}`,
1842
1845
  method: leaf.method,
1843
1846
  args: { ...leaf.args, anchorRole: ancestorRole, anchorHasText: anc.filterText },
@@ -1856,7 +1859,7 @@ function generateAlternatives(attrs, options = {}) {
1856
1859
  );
1857
1860
  const pos = attrs.rolePosition;
1858
1861
  if (pos && pos.role === role && (pos.count === 1 || level != null && pos.levelCount === 1)) {
1859
- add({
1862
+ add2({
1860
1863
  locator: `getByRole(${rolePart})`,
1861
1864
  method: "getByRole",
1862
1865
  args: leafArgs,
@@ -1875,7 +1878,7 @@ function generateAlternatives(attrs, options = {}) {
1875
1878
  const countsClasses = counts?.classes;
1876
1879
  const classes = clsStr.split(/\s+/).filter((c) => c.length > 1 && /^[A-Za-z_-][A-Za-z0-9_-]*$/.test(c) && isUnique(countsClasses?.[c])).map((cls) => ({ cls, score: classifyCssStability(cls) })).sort((a, b) => b.score - a.score).slice(0, 3);
1877
1880
  for (const { cls, score } of classes) {
1878
- add({
1881
+ add2({
1879
1882
  locator: `locator(${quoteJsString(`.${cls}`)})`,
1880
1883
  method: "locator",
1881
1884
  args: { selector: `.${cls}` },
@@ -1932,7 +1935,7 @@ function generateAnchoredAlternatives(leaf, anchors, chainLeafCount) {
1932
1935
  const seen = /* @__PURE__ */ new Set();
1933
1936
  const rolePart = leaf.level != null ? `${quoteJsString(leaf.role)}, { level: ${leaf.level} }` : quoteJsString(leaf.role);
1934
1937
  const leafArgs = leaf.level != null ? { role: leaf.role, level: leaf.level } : { role: leaf.role };
1935
- const add = (l) => {
1938
+ const add2 = (l) => {
1936
1939
  if (!seen.has(l.locator)) {
1937
1940
  seen.add(l.locator);
1938
1941
  out.push(l);
@@ -1942,7 +1945,7 @@ function generateAnchoredAlternatives(leaf, anchors, chainLeafCount) {
1942
1945
  if (anchor.scopedLeafCount !== 1) continue;
1943
1946
  const seg = anchorSegment(anchor);
1944
1947
  if (!seg) continue;
1945
- add({
1948
+ add2({
1946
1949
  locator: `${seg.code}.getByRole(${rolePart})`,
1947
1950
  method: "getByRole",
1948
1951
  args: { ...leafArgs, ...seg.flatArgs },
@@ -1955,7 +1958,7 @@ function generateAnchoredAlternatives(leaf, anchors, chainLeafCount) {
1955
1958
  if (segments.every((s) => s !== null)) {
1956
1959
  const innermost = segments[segments.length - 1];
1957
1960
  const score = Math.min(...segments.map((s) => ANCHOR_KIND_SCORES[s.kind]));
1958
- add({
1961
+ add2({
1959
1962
  locator: `${segments.map((s) => s.code).join(".")}.getByRole(${rolePart})`,
1960
1963
  method: "getByRole",
1961
1964
  // Flat args describe the innermost anchor (what the healing fingerprint
@@ -1985,21 +1988,21 @@ var MAX_INDETERMINATE = 20;
1985
1988
  var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
1986
1989
  var LEVEL = `${STEP}(?:/${STEP})*`;
1987
1990
  var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
1988
- function isElementPath(path13) {
1989
- return typeof path13 === "string" && path13.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path13);
1991
+ function isElementPath(path15) {
1992
+ return typeof path15 === "string" && path15.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path15);
1990
1993
  }
1991
1994
  var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
1992
1995
  function elementRef(v) {
1993
1996
  if (!v || typeof v !== "object") return null;
1994
- const { path: path13, label } = v;
1995
- if (!isElementPath(path13)) return null;
1996
- return { path: path13, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
1997
+ const { path: path15, label } = v;
1998
+ if (!isElementPath(path15)) return null;
1999
+ return { path: path15, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
1997
2000
  }
1998
2001
  function textPoint(v) {
1999
2002
  if (!v || typeof v !== "object") return null;
2000
- const { path: path13, offset } = v;
2003
+ const { path: path15, offset } = v;
2001
2004
  const at = offsetOf(offset);
2002
- return isElementPath(path13) && at !== null ? { path: path13, offset: at } : null;
2005
+ return isElementPath(path15) && at !== null ? { path: path15, offset: at } : null;
2003
2006
  }
2004
2007
  function sanitizePageInteraction(raw) {
2005
2008
  if (!raw || typeof raw !== "object") return {};
@@ -2398,6 +2401,8 @@ var ATTACHMENT_NAMES = {
2398
2401
  codeReach: "piwi-code-reach",
2399
2402
  browserCoverage: "piwi-browser-coverage",
2400
2403
  browserFunctions: "piwi-browser-functions",
2404
+ cssCoverage: "piwi-css-coverage",
2405
+ cssRules: "piwi-css-rules",
2401
2406
  serverCoverageRequests: "piwi-server-coverage-requests",
2402
2407
  webVitals: "piwi-web-vitals",
2403
2408
  locatorSuggestion: "piwi-locator-suggestion",
@@ -2405,7 +2410,8 @@ var ATTACHMENT_NAMES = {
2405
2410
  userPick: "piwi-user-pick",
2406
2411
  aiUsage: "piwi-ai-usage",
2407
2412
  aiMeta: "piwi-ai-meta",
2408
- resources: "piwi-resources"
2413
+ resources: "piwi-resources",
2414
+ pauses: "piwi-pauses"
2409
2415
  };
2410
2416
  var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
2411
2417
  var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
@@ -2639,13 +2645,19 @@ var LocatorPageLog = class {
2639
2645
  };
2640
2646
 
2641
2647
  // src/internal/probe/mode.ts
2642
- var fs2 = __toESM(require("fs"));
2648
+ var fs3 = __toESM(require("fs"));
2649
+
2650
+ // src/internal/config/env.ts
2651
+ var path3 = __toESM(require("path"));
2643
2652
 
2644
2653
  // src/internal/config/desktop.ts
2645
2654
  var fs = __toESM(require("fs"));
2646
2655
  var os = __toESM(require("os"));
2647
2656
  var path2 = __toESM(require("path"));
2648
2657
 
2658
+ // src/internal/config/dotenv.ts
2659
+ var fs2 = __toESM(require("fs"));
2660
+
2649
2661
  // src/internal/config/env.ts
2650
2662
  var DEFAULTS = {
2651
2663
  projectName: "default-project",
@@ -2682,6 +2694,7 @@ var DEFAULTS = {
2682
2694
  };
2683
2695
  var PIWI_ENV_KEYS = {
2684
2696
  serverUrl: "PIWI_DASHBOARD_URL",
2697
+ desktopApp: "PIWI_DESKTOP_APP",
2685
2698
  projectName: "PIWI_PROJECT_NAME",
2686
2699
  verbose: "PIWI_VERBOSE",
2687
2700
  apiKey: "PIWI_API_KEY",
@@ -2707,6 +2720,7 @@ var PIWI_ENV_KEYS = {
2707
2720
  codeReachRoots: "PIWI_CODE_REACH_ROOTS",
2708
2721
  coverageBrowser: "PIWI_COVERAGE_BROWSER",
2709
2722
  coverageServer: "PIWI_COVERAGE_SERVER",
2723
+ coverageCss: "PIWI_COVERAGE_CSS",
2710
2724
  externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
2711
2725
  externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
2712
2726
  captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
@@ -2779,14 +2793,14 @@ var ENV_FALLBACK_SPECS = [
2779
2793
  ];
2780
2794
 
2781
2795
  // src/internal/support/test-paths.ts
2782
- var path3 = __toESM(require("path"));
2796
+ var path4 = __toESM(require("path"));
2783
2797
  function configDirFromConfig(config) {
2784
- if (config?.configFile) return path3.dirname(config.configFile);
2798
+ if (config?.configFile) return path4.dirname(config.configFile);
2785
2799
  if (config?.rootDir) return config.rootDir;
2786
2800
  return process.cwd();
2787
2801
  }
2788
2802
  function relativeTestPath(file, base) {
2789
- return path3.relative(base, file).split(path3.sep).join("/");
2803
+ return path4.relative(base, file).split(path4.sep).join("/");
2790
2804
  }
2791
2805
 
2792
2806
  // src/internal/probe/plan.ts
@@ -2796,8 +2810,8 @@ function parseRouteKey(routeKey2) {
2796
2810
  return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
2797
2811
  }
2798
2812
  function patternToRegex(pattern) {
2799
- const path13 = pattern.split("?")[0] ?? pattern;
2800
- const escaped = path13.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
2813
+ const path15 = pattern.split("?")[0] ?? pattern;
2814
+ const escaped = path15.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
2801
2815
  return new RegExp(`^${escaped}$`);
2802
2816
  }
2803
2817
  function suitePathEqual(a, b) {
@@ -2820,13 +2834,13 @@ function matchProbeItem(plan, test) {
2820
2834
  function requestMatchesRoute(method, url, routeKey2) {
2821
2835
  const { method: rm, pattern } = parseRouteKey(routeKey2);
2822
2836
  if (rm && rm !== method.toUpperCase()) return false;
2823
- let path13;
2837
+ let path15;
2824
2838
  try {
2825
- path13 = new URL(url).pathname;
2839
+ path15 = new URL(url).pathname;
2826
2840
  } catch {
2827
- path13 = url.split("?")[0] ?? url;
2841
+ path15 = url.split("?")[0] ?? url;
2828
2842
  }
2829
- return patternToRegex(pattern).test(path13);
2843
+ return patternToRegex(pattern).test(path15);
2830
2844
  }
2831
2845
  function createProbeState() {
2832
2846
  return { navigated: false, matches: 0 };
@@ -2848,10 +2862,10 @@ function isProbeMode() {
2848
2862
  function loadPlan() {
2849
2863
  if (planCache !== void 0) return planCache;
2850
2864
  planCache = null;
2851
- const path13 = process.env[PIWI_PROBE_ENV.plan];
2852
- if (path13) {
2865
+ const path15 = process.env[PIWI_PROBE_ENV.plan];
2866
+ if (path15) {
2853
2867
  try {
2854
- planCache = JSON.parse(fs2.readFileSync(path13, "utf8"));
2868
+ planCache = JSON.parse(fs3.readFileSync(path15, "utf8"));
2855
2869
  } catch {
2856
2870
  planCache = null;
2857
2871
  }
@@ -2875,10 +2889,10 @@ function outcomeFromStatus(status, applied) {
2875
2889
  return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
2876
2890
  }
2877
2891
  function recordProbeOutcome(line) {
2878
- const path13 = process.env[PIWI_PROBE_ENV.results];
2879
- if (!path13) return;
2892
+ const path15 = process.env[PIWI_PROBE_ENV.results];
2893
+ if (!path15) return;
2880
2894
  try {
2881
- fs2.appendFileSync(path13, `${JSON.stringify(line)}
2895
+ fs3.appendFileSync(path15, `${JSON.stringify(line)}
2882
2896
  `);
2883
2897
  } catch {
2884
2898
  }
@@ -3008,7 +3022,7 @@ function originOf(url) {
3008
3022
  return null;
3009
3023
  }
3010
3024
  }
3011
- var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, Math.max(0, ms)));
3025
+ var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, Math.max(0, ms)));
3012
3026
  async function performRouteAction(route, action, startedAt) {
3013
3027
  switch (action.kind) {
3014
3028
  case "abort":
@@ -3119,7 +3133,7 @@ async function installProbeInterception(page, item) {
3119
3133
  }
3120
3134
 
3121
3135
  // src/internal/flake/mode.ts
3122
- var fs3 = __toESM(require("fs"));
3136
+ var fs4 = __toESM(require("fs"));
3123
3137
 
3124
3138
  // ../core/src/error-parse.ts
3125
3139
  var ANSI_RE = new RegExp("\\u001B\\[[0-9;]*m", "g");
@@ -3430,7 +3444,7 @@ function loadFlakePlan() {
3430
3444
  if (planCache2 && planCache2.file === file) return planCache2.plan;
3431
3445
  let text;
3432
3446
  try {
3433
- text = fs3.readFileSync(file, "utf8");
3447
+ text = fs4.readFileSync(file, "utf8");
3434
3448
  } catch (error) {
3435
3449
  throw new Error(`[Piwi Dashboard] Cannot read the flake plan ${file}: ${errorMessage(error)}`);
3436
3450
  }
@@ -3583,7 +3597,7 @@ function recordFlakeResult(line) {
3583
3597
  const file = process.env[PIWI_FLAKE_ENV.results]?.trim();
3584
3598
  if (!file) return;
3585
3599
  try {
3586
- fs3.appendFileSync(file, `${JSON.stringify(line)}
3600
+ fs4.appendFileSync(file, `${JSON.stringify(line)}
3587
3601
  `);
3588
3602
  } catch {
3589
3603
  }
@@ -3649,13 +3663,13 @@ function normalizeFunctionRanges(ranges) {
3649
3663
  }
3650
3664
  function normalizeCoveragePath(raw) {
3651
3665
  if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
3652
- const path13 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
3653
- if (!path13 || path13.startsWith("../") || path13.startsWith("/") || /^[A-Za-z]:\//.test(path13)) return null;
3654
- if (/(^|\/)node_modules\//.test(path13)) return null;
3655
- return path13;
3666
+ const path15 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
3667
+ if (!path15 || path15.startsWith("../") || path15.startsWith("/") || /^[A-Za-z]:\//.test(path15)) return null;
3668
+ if (/(^|\/)node_modules\//.test(path15)) return null;
3669
+ return path15;
3656
3670
  }
3657
- function fileEntry(path13, runtime, blob, functions = [], lines = []) {
3658
- const file = { path: path13, runtime };
3671
+ function fileEntry(path15, runtime, blob, functions = [], lines = []) {
3672
+ const file = { path: path15, runtime };
3659
3673
  if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
3660
3674
  if (functions.length > 0) file.functions = functions;
3661
3675
  if (lines.length > 0) file.lines = lines;
@@ -3674,11 +3688,11 @@ function sanitizeCoverage(raw) {
3674
3688
  const files = [];
3675
3689
  for (const item of raw.files) {
3676
3690
  if (!isRecord(item) || !isRuntime(item.runtime)) continue;
3677
- const path13 = normalizeCoveragePath(item.path);
3678
- if (path13 === null) continue;
3691
+ const path15 = normalizeCoveragePath(item.path);
3692
+ if (path15 === null) continue;
3679
3693
  const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
3680
3694
  files.push(
3681
- fileEntry(path13, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
3695
+ fileEntry(path15, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
3682
3696
  );
3683
3697
  }
3684
3698
  const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
@@ -3796,10 +3810,10 @@ function joinRoot(root, source) {
3796
3810
  function decodeLines(raw) {
3797
3811
  const sources = [];
3798
3812
  const decoded = [];
3799
- const add = (map, lineOffset, columnOffset) => {
3813
+ const add2 = (map, lineOffset, columnOffset) => {
3800
3814
  if (map.sections) {
3801
3815
  for (const section of map.sections) {
3802
- if (!add(section.map, section.offset.line, section.offset.column)) return false;
3816
+ if (!add2(section.map, section.offset.line, section.offset.column)) return false;
3803
3817
  }
3804
3818
  return true;
3805
3819
  }
@@ -3820,7 +3834,7 @@ function decodeLines(raw) {
3820
3834
  });
3821
3835
  return true;
3822
3836
  };
3823
- return add(raw, 0, 0) ? { sources, lines: decoded } : null;
3837
+ return add2(raw, 0, 0) ? { sources, lines: decoded } : null;
3824
3838
  }
3825
3839
  function compactSegments(lines) {
3826
3840
  let total = 0;
@@ -3996,7 +4010,7 @@ function functionLines(entry, map, text, all) {
3996
4010
  const read = textOf(entry, text);
3997
4011
  const ranges = executedFunctionRanges(entry, read?.length, all);
3998
4012
  if (!ranges.length || !read) return out;
3999
- const add = (source, range) => {
4013
+ const add2 = (source, range) => {
4000
4014
  const list = out.get(source);
4001
4015
  if (list) list.push(range);
4002
4016
  else out.set(source, [range]);
@@ -4006,7 +4020,7 @@ function functionLines(entry, map, text, all) {
4006
4020
  const start = offsetToPosition(starts, range.start);
4007
4021
  if (!map) {
4008
4022
  const last = offsetToPosition(starts, Math.max(range.start, range.end - 1));
4009
- add(-1, [start.line + 1, last.line + 1]);
4023
+ add2(-1, [start.line + 1, last.line + 1]);
4010
4024
  continue;
4011
4025
  }
4012
4026
  const at = map.originalAt(start.line, start.column);
@@ -4016,7 +4030,7 @@ function functionLines(entry, map, text, all) {
4016
4030
  if (first === null) continue;
4017
4031
  const header = first === at.line || first === at.line + 1 ? at.line : first;
4018
4032
  const lines = map.originalLinesBetween(at.source, start, end, header);
4019
- if (lines) add(at.source, [header + 1, lines[1] + 1]);
4033
+ if (lines) add2(at.source, [header + 1, lines[1] + 1]);
4020
4034
  }
4021
4035
  return out;
4022
4036
  }
@@ -4089,7 +4103,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
4089
4103
  }
4090
4104
 
4091
4105
  // src/internal/capture/pick-on-failure.ts
4092
- var path4 = __toESM(require("path"));
4106
+ var path5 = __toESM(require("path"));
4093
4107
  function parseLeafLocatorExpression(rawExpr) {
4094
4108
  const leaf = parseLeafLocatorCall(rawExpr);
4095
4109
  return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
@@ -4104,37 +4118,60 @@ ${err.stack ?? ""}`));
4104
4118
  const parsed2 = parseLeafLocatorExpression(header);
4105
4119
  if (!parsed2) continue;
4106
4120
  const loc = err.location;
4107
- const location = loc ? `${path4.relative(process.cwd(), loc.file).split(path4.sep).join("/")}:${loc.line}:${loc.column}` : null;
4121
+ const location = loc ? `${path5.relative(process.cwd(), loc.file).split(path5.sep).join("/")}:${loc.line}:${loc.column}` : null;
4108
4122
  return { method: parsed2.method, args: parsed2.args, location };
4109
4123
  }
4110
4124
  return null;
4111
4125
  }
4112
- async function cleanupPicker(page) {
4126
+ var PICK_ENDED = "__piwiPickEnded";
4127
+ async function cleanupPicker(page, ended = false) {
4113
4128
  try {
4114
- await page.evaluate(() => {
4115
- const g = globalThis;
4116
- if (typeof g.__piwiPickCleanup === "function") g.__piwiPickCleanup();
4117
- if (typeof g.__piwiAnchorCleanup === "function") g.__piwiAnchorCleanup();
4118
- delete g.__piwiPickCleanup;
4119
- delete g.__piwiAnchorCleanup;
4120
- delete g.__piwiPickState;
4121
- delete g.__piwiPickChoice;
4122
- delete g.__piwiPickedElement;
4123
- delete g.__piwiAnchorState;
4124
- delete g.__piwiPickAnchors;
4125
- delete g.__piwiPickChainCount;
4126
- });
4129
+ await page.evaluate(
4130
+ ([endedKey, unanswered]) => {
4131
+ const g = globalThis;
4132
+ if (typeof g.__piwiPickCleanup === "function") g.__piwiPickCleanup();
4133
+ if (typeof g.__piwiAnchorCleanup === "function") g.__piwiAnchorCleanup();
4134
+ delete g.__piwiPickCleanup;
4135
+ delete g.__piwiAnchorCleanup;
4136
+ delete g.__piwiPickState;
4137
+ delete g.__piwiPickChoice;
4138
+ delete g.__piwiPickedElement;
4139
+ delete g.__piwiAnchorState;
4140
+ delete g.__piwiPickAnchors;
4141
+ delete g.__piwiPickChainCount;
4142
+ if (unanswered) g[endedKey] = true;
4143
+ else delete g[endedKey];
4144
+ },
4145
+ [PICK_ENDED, ended]
4146
+ );
4127
4147
  } catch {
4128
4148
  }
4129
4149
  }
4130
- async function pickLocator(page, probe, failing) {
4150
+ function unlessEnded(run, ended) {
4151
+ if (!ended) return run;
4152
+ return Promise.race([
4153
+ run,
4154
+ new Promise((resolve5) => {
4155
+ if (ended.aborted) resolve5(void 0);
4156
+ else ended.addEventListener("abort", () => resolve5(void 0), { once: true });
4157
+ })
4158
+ ]);
4159
+ }
4160
+ async function pickLocator(page, probe, failing, ended) {
4161
+ const answered = async (name) => {
4162
+ const wait = page.waitForFunction(
4163
+ ([key, endedKey]) => globalThis[key] !== void 0 || globalThis[endedKey] === true,
4164
+ [name, PICK_ENDED],
4165
+ { timeout: 0, polling: 250 }
4166
+ );
4167
+ await unlessEnded(wait, ended);
4168
+ return !ended?.aborted;
4169
+ };
4131
4170
  try {
4132
4171
  const overlayArg = { transport: "global", failing };
4172
+ await page.evaluate((endedKey) => delete globalThis[endedKey], PICK_ENDED);
4133
4173
  await page.evaluate(installPickerOverlay, overlayArg);
4134
- await page.waitForFunction(() => globalThis.__piwiPickState !== void 0, void 0, {
4135
- timeout: 0,
4136
- polling: 250
4137
- });
4174
+ if (!await answered("__piwiPickState")) return null;
4138
4175
  const state = await page.evaluate(() => globalThis.__piwiPickState);
4139
4176
  if (state !== "picked") return null;
4140
4177
  const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
@@ -4155,10 +4192,7 @@ async function pickLocator(page, probe, failing) {
4155
4192
  leafLevel: level,
4156
4193
  leafTestId: attrs.attributes["data-testid"] ?? null
4157
4194
  });
4158
- await page.waitForFunction(() => globalThis.__piwiAnchorState !== void 0, void 0, {
4159
- timeout: 0,
4160
- polling: 250
4161
- });
4195
+ if (!await answered("__piwiAnchorState")) return null;
4162
4196
  const anchorResult = await page.evaluate(() => {
4163
4197
  const g = globalThis;
4164
4198
  return { state: g.__piwiAnchorState, anchors: g.__piwiPickAnchors ?? [], chainCount: g.__piwiPickChainCount };
@@ -4180,10 +4214,7 @@ async function pickLocator(page, probe, failing) {
4180
4214
  failing,
4181
4215
  choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
4182
4216
  });
4183
- await page.waitForFunction(() => globalThis.__piwiPickChoice !== void 0, void 0, {
4184
- timeout: 0,
4185
- polling: 250
4186
- });
4217
+ if (!await answered("__piwiPickChoice")) return null;
4187
4218
  const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
4188
4219
  if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
4189
4220
  const picked = { ...ranked[choice], pickedByUser: true };
@@ -4202,7 +4233,7 @@ async function pickLocator(page, probe, failing) {
4202
4233
  ...anchors.length > 0 ? { anchors } : {}
4203
4234
  };
4204
4235
  } finally {
4205
- await cleanupPicker(page);
4236
+ await cleanupPicker(page, ended?.aborted);
4206
4237
  }
4207
4238
  }
4208
4239
  async function runLocatorPicker(page, testInfo, failed, probe) {
@@ -4257,7 +4288,7 @@ function applyPickToSnapshots(snapshots, pick) {
4257
4288
  }
4258
4289
 
4259
4290
  // src/internal/capture/pause-at.ts
4260
- var path5 = __toESM(require("path"));
4291
+ var path8 = __toESM(require("path"));
4261
4292
 
4262
4293
  // ../core/src/editor-send.ts
4263
4294
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
@@ -4283,6 +4314,93 @@ function isInsidePath(file) {
4283
4314
  return file.split(/[\\/]/).every((part) => part !== "..");
4284
4315
  }
4285
4316
 
4317
+ // src/internal/capture/quiet-capture.ts
4318
+ var import_node_module = require("module");
4319
+ var path7 = __toESM(require("path"));
4320
+
4321
+ // src/internal/support/reporter-version.ts
4322
+ var fs5 = __toESM(require("fs"));
4323
+ var path6 = __toESM(require("path"));
4324
+ var CANDIDATE_OFFSETS = ["..", "../../.."];
4325
+ function findOwnPackageJson(fromDir) {
4326
+ for (const offset of CANDIDATE_OFFSETS) {
4327
+ try {
4328
+ const pkgPath = path6.resolve(fromDir, offset, "package.json");
4329
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
4330
+ const { name, version } = pkg;
4331
+ if (name === "@piwitests/reporter" && typeof version === "string")
4332
+ return { version, root: path6.dirname(pkgPath) };
4333
+ } catch {
4334
+ }
4335
+ }
4336
+ return void 0;
4337
+ }
4338
+
4339
+ // src/internal/capture/quiet-capture.ts
4340
+ var BOXED_PREFIX_EXPORTS = [
4341
+ [
4342
+ "playwright-core/lib/coreBundle",
4343
+ (mod) => mod?.utils?.setBoxedStackPrefixes
4344
+ ],
4345
+ ["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
4346
+ ];
4347
+ var boxing;
4348
+ var resolvedPlaywrightPackageJson;
4349
+ function resolvePlaywrightPackageJson() {
4350
+ if (resolvedPlaywrightPackageJson !== void 0) return resolvedPlaywrightPackageJson;
4351
+ try {
4352
+ const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
4353
+ resolvedPlaywrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
4354
+ } catch {
4355
+ resolvedPlaywrightPackageJson = null;
4356
+ }
4357
+ return resolvedPlaywrightPackageJson;
4358
+ }
4359
+ function resolveBoxing() {
4360
+ try {
4361
+ const root = findOwnPackageJson(__dirname)?.root;
4362
+ if (!root) return null;
4363
+ const playwrightPackageJson = resolvePlaywrightPackageJson();
4364
+ if (!playwrightPackageJson) return null;
4365
+ const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
4366
+ for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
4367
+ let set;
4368
+ try {
4369
+ set = pick(requireFromPlaywright(moduleId));
4370
+ } catch {
4371
+ continue;
4372
+ }
4373
+ if (typeof set !== "function") continue;
4374
+ return {
4375
+ set,
4376
+ prefixes: [path7.dirname(playwrightPackageJson), path7.join(root, "dist") + path7.sep]
4377
+ };
4378
+ }
4379
+ } catch {
4380
+ }
4381
+ return null;
4382
+ }
4383
+ function boxCaptureFrames(extraPrefixes = []) {
4384
+ const first = boxing === void 0;
4385
+ if (first) boxing = resolveBoxing();
4386
+ if (!boxing) return;
4387
+ const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
4388
+ if (!first && added.length === 0) return;
4389
+ boxing.prefixes.push(...added);
4390
+ try {
4391
+ boxing.set([...boxing.prefixes]);
4392
+ } catch {
4393
+ }
4394
+ }
4395
+ function callMethod(target, prop, args) {
4396
+ return target[prop](...args);
4397
+ }
4398
+ function internalCall(owner, run) {
4399
+ const wrap = owner?._wrapApiCall;
4400
+ if (typeof wrap !== "function") return run();
4401
+ return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
4402
+ }
4403
+
4286
4404
  // src/internal/capture/pause-at.ts
4287
4405
  var PauseSet = class {
4288
4406
  constructor(lines) {
@@ -4298,18 +4416,32 @@ var PauseSet = class {
4298
4416
  return col > 0 && this.lines.has(callerLocation.slice(0, col));
4299
4417
  }
4300
4418
  };
4301
- var EMPTY = new PauseSet(/* @__PURE__ */ new Set());
4419
+ var NO_BREAKPOINTS = new PauseSet(/* @__PURE__ */ new Set());
4302
4420
  function callSiteFile(file, cwd) {
4303
4421
  const slashed = file.replace(/\\/g, "/");
4304
- const absolute = path5.isAbsolute(file) || path5.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
4305
- let rel = absolute ? path5.relative(cwd, file) : path5.normalize(slashed);
4306
- rel = rel.split(path5.sep).join("/").replace(/\\/g, "/");
4422
+ const absolute = path8.isAbsolute(file) || path8.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
4423
+ let rel = absolute ? path8.relative(cwd, file) : path8.normalize(slashed);
4424
+ rel = rel.split(path8.sep).join("/").replace(/\\/g, "/");
4307
4425
  if (rel.startsWith("./")) rel = rel.slice(2);
4308
4426
  return rel;
4309
4427
  }
4428
+ function stopCallSite(location, cwd = process.cwd()) {
4429
+ if (!location.line) return null;
4430
+ return `${callSiteFile(location.file, cwd)}:${location.line}:${location.column ?? 0}`;
4431
+ }
4432
+ function callSiteLine(callSite) {
4433
+ const col = callSite.lastIndexOf(":");
4434
+ return col > 0 ? callSite.slice(0, col) : callSite;
4435
+ }
4436
+ function inFlightCallSite(inFlight) {
4437
+ const [first] = inFlight;
4438
+ if (first === void 0) return null;
4439
+ const line = callSiteLine(first);
4440
+ return inFlight.every((site) => callSiteLine(site) === line) ? first : null;
4441
+ }
4310
4442
  var ignoredLogged = /* @__PURE__ */ new Set();
4311
4443
  function parsePauseAt(value, cwd = process.cwd()) {
4312
- if (!value?.trim()) return EMPTY;
4444
+ if (!value?.trim()) return NO_BREAKPOINTS;
4313
4445
  const lines = /* @__PURE__ */ new Set();
4314
4446
  for (const raw of value.split(";")) {
4315
4447
  const entry = raw.trim();
@@ -4325,7 +4457,7 @@ function parsePauseAt(value, cwd = process.cwd()) {
4325
4457
  }
4326
4458
  lines.add(`${callSiteFile(m[1], cwd)}:${line}`);
4327
4459
  }
4328
- return lines.size ? new PauseSet(lines) : EMPTY;
4460
+ return lines.size ? new PauseSet(lines) : NO_BREAKPOINTS;
4329
4461
  }
4330
4462
  var parsed = null;
4331
4463
  function currentPauseSet() {
@@ -4335,27 +4467,121 @@ function currentPauseSet() {
4335
4467
  }
4336
4468
  function pauseGateFromTestInfo(testInfo) {
4337
4469
  const use = testInfo.project?.use ?? {};
4338
- return { ci: process.env.CI, headless: use.headless };
4470
+ const debug = testInfo._configInternal?.configCLIOverrides?.debug;
4471
+ return { ci: process.env.CI, headless: use.headless, pwdebug: process.env.PWDEBUG, debug };
4339
4472
  }
4340
4473
  function pauseSkipReason(gate) {
4341
4474
  if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
4342
4475
  if (gate.headless !== false)
4343
4476
  return "the browser is headless \u2014 re-run with --headed (or set use: { headless: false })";
4477
+ if (gate.pwdebug && !["0", "false", "console"].includes(gate.pwdebug))
4478
+ return "Playwright's Inspector runs the test (PWDEBUG) \u2014 run without --debug";
4479
+ if (typeof gate.debug === "string" && gate.debug)
4480
+ return `Playwright's debugger runs the test (--debug=${gate.debug}) \u2014 run without --debug`;
4344
4481
  return null;
4345
4482
  }
4346
- function rearmedTimeout(before, pausedMs) {
4347
- return before > 0 ? before + Math.max(0, Math.round(pausedMs)) : 0;
4483
+ function atStop(state, callSite) {
4484
+ if (callSite && !state.finished) {
4485
+ if (state.step) {
4486
+ state.step = false;
4487
+ return { show: "step" };
4488
+ }
4489
+ if (state.set.matches(callSite)) return { show: "breakpoint" };
4490
+ }
4491
+ return { go: goOn(state) };
4492
+ }
4493
+ function goOn(state, answer) {
4494
+ if (answer === "step") state.step = true;
4495
+ else if (answer === "finish") state.finished = true;
4496
+ return !state.finished && (state.step || state.set.size > 0) ? "next" : "resume";
4497
+ }
4498
+ function contextDebugger(context) {
4499
+ const dbg = context.debugger;
4500
+ return typeof dbg?.requestPause === "function" && typeof dbg.on === "function" ? dbg : null;
4501
+ }
4502
+ var WATCHED = /* @__PURE__ */ new WeakSet();
4503
+ function watchStops(context, onStop) {
4504
+ const dbg = contextDebugger(context);
4505
+ if (!dbg) return false;
4506
+ if (WATCHED.has(context)) return true;
4507
+ WATCHED.add(context);
4508
+ inspectorSwitch.watching = true;
4509
+ syncInspectorSwitch();
4510
+ dbg.on("pausedstatechanged", () => onStop(context));
4511
+ return true;
4512
+ }
4513
+ var NO_INSPECTOR_ENV = "PW_CODEGEN_NO_INSPECTOR";
4514
+ var inspectorSwitch = {
4515
+ /** Set by Piwi, so Piwi's to clear. */
4516
+ set: false,
4517
+ /** Piwi answers the stops of a context in this worker. */
4518
+ watching: false,
4519
+ /** Every `page.pause()` of the worker's Playwright reaches it through Piwi (`markPausesWrapped`). */
4520
+ wrapped: false,
4521
+ /** The `page.pause()` calls left to Playwright that are running (`withInspector`). */
4522
+ open: 0
4523
+ };
4524
+ function syncInspectorSwitch() {
4525
+ const closed = inspectorSwitch.watching && inspectorSwitch.wrapped && inspectorSwitch.open === 0;
4526
+ if (closed && process.env[NO_INSPECTOR_ENV] === void 0) {
4527
+ process.env[NO_INSPECTOR_ENV] = "1";
4528
+ inspectorSwitch.set = true;
4529
+ } else if (!closed && inspectorSwitch.set) {
4530
+ if (process.env[NO_INSPECTOR_ENV] === "1") delete process.env[NO_INSPECTOR_ENV];
4531
+ inspectorSwitch.set = false;
4532
+ }
4533
+ }
4534
+ function markPausesWrapped() {
4535
+ inspectorSwitch.wrapped = true;
4536
+ syncInspectorSwitch();
4537
+ }
4538
+ async function withInspector(run) {
4539
+ inspectorSwitch.open++;
4540
+ syncInspectorSwitch();
4541
+ try {
4542
+ return await run();
4543
+ } finally {
4544
+ inspectorSwitch.open--;
4545
+ syncInspectorSwitch();
4546
+ }
4547
+ }
4548
+ async function steerDebugger(context, how) {
4549
+ const dbg = contextDebugger(context);
4550
+ if (!dbg) return;
4551
+ try {
4552
+ await internalCall(
4553
+ context,
4554
+ () => how === "pause" ? dbg.requestPause() : how === "next" ? dbg.next() : dbg.resume()
4555
+ );
4556
+ } catch {
4557
+ }
4558
+ }
4559
+ async function withTestTimeoutHeld(testInfo, run) {
4560
+ const set = testInfo._setIgnoreTimeouts;
4561
+ if (typeof set !== "function") return run();
4562
+ set.call(testInfo, true);
4563
+ try {
4564
+ return await run();
4565
+ } finally {
4566
+ set.call(testInfo, false);
4567
+ }
4568
+ }
4569
+ function testTimeSlot(testInfo) {
4570
+ try {
4571
+ const slot = testInfo._timeoutManager?.defaultSlot?.();
4572
+ return slot && typeof slot.elapsed === "number" ? slot : null;
4573
+ } catch {
4574
+ return null;
4575
+ }
4576
+ }
4577
+ function restoreTestTime(testInfo, heldAt) {
4578
+ const slot = testTimeSlot(testInfo);
4579
+ if (slot && Number.isFinite(heldAt) && heldAt >= 0 && heldAt < slot.elapsed) slot.elapsed = heldAt;
4348
4580
  }
4349
4581
  function pausePlace(callerLocation) {
4350
- const col = callerLocation.lastIndexOf(":");
4351
- const fileLine = col > 0 ? callerLocation.slice(0, col) : callerLocation;
4582
+ const fileLine = callSiteLine(callerLocation);
4352
4583
  return fileLine.slice(fileLine.lastIndexOf("/") + 1);
4353
4584
  }
4354
- function assertionLabel(expression, isNot) {
4355
- const [first = "", ...rest] = expression.split(".").filter(Boolean);
4356
- const name = first + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
4357
- return `${isNot ? "not." : ""}${name || "expect"}`;
4358
- }
4359
4585
  function pickPlace(callerLocation) {
4360
4586
  const m = /^(.+):(\d+):\d+$/.exec(callerLocation);
4361
4587
  if (!m) return null;
@@ -4400,64 +4626,320 @@ function logPauseSkipOnce(reason) {
4400
4626
  skipLogged = true;
4401
4627
  console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt} is set but breakpoints are skipped: ${reason}`);
4402
4628
  }
4629
+ var NAVIGATED = /Execution context was destroyed|Cannot find context with specified id/;
4630
+ var MAX_NAVIGATED = 10;
4403
4631
  var HIGHLIGHT_MS = 2e3;
4404
- async function pauseHere(page, testInfo, point, probe, onPick) {
4405
- const before = testInfo.timeout;
4406
- const started = Date.now();
4632
+ async function pauseHere(page, point, probe, onPick, onRecord, ended) {
4407
4633
  let shown = null;
4408
4634
  try {
4409
- testInfo.setTimeout(0);
4410
4635
  const highlight = point.target?.highlight;
4411
4636
  if (typeof highlight === "function") {
4412
4637
  let timer;
4413
4638
  shown = await Promise.race([
4414
4639
  Promise.resolve().then(() => highlight.call(point.target)).catch(() => null),
4415
- new Promise((resolve3) => {
4416
- timer = setTimeout(() => resolve3(null), HIGHLIGHT_MS);
4640
+ new Promise((resolve5) => {
4641
+ timer = setTimeout(() => resolve5(null), HIGHLIGHT_MS);
4417
4642
  })
4418
4643
  ]);
4419
4644
  clearTimeout(timer);
4420
4645
  }
4421
4646
  const arg = {
4422
4647
  place: pausePlace(point.location),
4423
- attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
4424
- action: point.action,
4425
- locator: point.locator
4648
+ attempt: point.attempt,
4649
+ action: point.call,
4650
+ locator: null,
4651
+ record: !!onRecord
4426
4652
  };
4427
4653
  console.log(
4428
- `[piwi] Paused at ${point.location} before ${point.locator ? `${point.action} \xB7 ${point.locator}` : point.action}: Resume, Step, Pick a locator or Finish in the browser (Esc resumes).`
4654
+ `[piwi] Paused at ${callSiteLine(point.location)} before ${point.call}: Resume, Step, Pick a locator${onRecord ? ", Record here" : ""} or Finish in the browser (Esc resumes).`
4429
4655
  );
4656
+ let navigated = 0;
4430
4657
  for (; ; ) {
4431
- await page.evaluate(showPauseBar, arg);
4432
- await page.waitForFunction(pauseAnswered, void 0, { timeout: 0, polling: 250 });
4433
- const choice = await page.evaluate(takePauseState);
4434
- if (choice === "resume" || choice === "step" || choice === "finish") return choice;
4435
- if (choice !== "pick") continue;
4436
- const picked = await pickLocator(page, probe, null).catch(() => null);
4437
- if (picked) await onPick(picked);
4658
+ try {
4659
+ if (ended?.aborted) return "resume";
4660
+ await page.evaluate(showPauseBar, arg);
4661
+ await unlessEnded(page.waitForFunction(pauseAnswered, void 0, { timeout: 0, polling: 250 }), ended);
4662
+ if (ended?.aborted) return "resume";
4663
+ const choice = await page.evaluate(takePauseState);
4664
+ navigated = 0;
4665
+ if (choice === "resume" || choice === "step" || choice === "finish") return choice;
4666
+ if (choice === "record" && onRecord) {
4667
+ await unlessEnded(
4668
+ onRecord(ended).catch(() => void 0),
4669
+ ended
4670
+ );
4671
+ continue;
4672
+ }
4673
+ if (choice !== "pick") continue;
4674
+ const picked = await pickLocator(page, probe, null, ended).catch(() => null);
4675
+ if (picked && !ended?.aborted) await onPick(picked);
4676
+ } catch (error) {
4677
+ if (page.isClosed() || !NAVIGATED.test(String(error)) || ++navigated > MAX_NAVIGATED) throw error;
4678
+ await page.waitForLoadState("domcontentloaded").catch(() => void 0);
4679
+ }
4438
4680
  }
4439
4681
  } catch {
4440
4682
  return "resume";
4441
4683
  } finally {
4442
4684
  try {
4685
+ if (ended?.aborted) await page.evaluate(takePauseState);
4443
4686
  const dispose = shown?.dispose;
4444
4687
  const hide = page.hideHighlight;
4445
4688
  if (typeof dispose === "function") await dispose.call(shown);
4446
4689
  else if (typeof hide === "function") await hide.call(page);
4447
4690
  } catch {
4448
4691
  }
4449
- try {
4450
- testInfo.setTimeout(rearmedTimeout(before, Date.now() - started));
4451
- } catch {
4452
- }
4453
4692
  }
4454
4693
  }
4455
4694
 
4695
+ // src/internal/capture/record-handoff.ts
4696
+ var fs6 = __toESM(require("fs"));
4697
+ var net = __toESM(require("net"));
4698
+ var path9 = __toESM(require("path"));
4699
+
4700
+ // ../core/src/ide-recorder.ts
4701
+ var IDE_RECORDER_BINDING = "__piwiRecorder";
4702
+ var IDE_DISPATCH_GLOBAL = "__piwiIdeDispatch";
4703
+ var RECORD_CHANNEL_ENV = "PIWI_RECORD_CHANNEL";
4704
+ var MAX_HANDOFF_LINE = 64e6;
4705
+ function parseRecordChannel(value) {
4706
+ const text = value?.trim() ?? "";
4707
+ const hash = text.lastIndexOf("#");
4708
+ if (hash <= 0) return null;
4709
+ const token = text.slice(hash + 1);
4710
+ const path15 = text.slice(0, hash);
4711
+ return /^[A-Za-z0-9_-]{16,}$/.test(token) && !path15.includes("\0") ? { path: path15, token } : null;
4712
+ }
4713
+ function isObject2(value) {
4714
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4715
+ }
4716
+ function parseHandoffToTest(line) {
4717
+ let value;
4718
+ try {
4719
+ value = JSON.parse(line);
4720
+ } catch {
4721
+ return null;
4722
+ }
4723
+ if (!isObject2(value)) return null;
4724
+ switch (value.type) {
4725
+ case "refused":
4726
+ return typeof value.message === "string" ? { type: "refused", message: value.message } : null;
4727
+ case "load":
4728
+ return typeof value.bundle === "string" && value.bundle ? { type: "load", bundle: value.bundle } : null;
4729
+ case "answer":
4730
+ return typeof value.call === "number" ? { type: "answer", call: value.call, value: value.value } : null;
4731
+ case "dispatch":
4732
+ return { type: "dispatch", message: value.message };
4733
+ case "stop":
4734
+ return { type: "stop" };
4735
+ default:
4736
+ return null;
4737
+ }
4738
+ }
4739
+ function lineReader(onLine, onOverflow) {
4740
+ let buffered = "";
4741
+ return (chunk) => {
4742
+ buffered += chunk;
4743
+ for (let end = buffered.indexOf("\n"); end >= 0; end = buffered.indexOf("\n")) {
4744
+ const line = buffered.slice(0, end);
4745
+ buffered = buffered.slice(end + 1);
4746
+ if (line.trim()) onLine(line);
4747
+ }
4748
+ if (buffered.length > MAX_HANDOFF_LINE) {
4749
+ buffered = "";
4750
+ onOverflow();
4751
+ }
4752
+ };
4753
+ }
4754
+
4755
+ // src/internal/capture/record-handoff.ts
4756
+ var START_WAIT_MS = 6e4;
4757
+ var NEW_TAB_NOTICE = "A new tab opened: what you do there is not recorded yet. Record in the first tab.";
4758
+ var relays = /* @__PURE__ */ new WeakMap();
4759
+ function idleAnswer(payload) {
4760
+ const request = payload ?? {};
4761
+ if (request.kind === "local-get") return {};
4762
+ if (request.kind !== "message") return null;
4763
+ if (request.message?.type === "piwi-ping") return { ok: true };
4764
+ if (request.message?.type === "piwi-session-storage") {
4765
+ return request.message.op === "get" ? { ok: true, items: {} } : { ok: true };
4766
+ }
4767
+ return { ok: false, error: "No recording runs in this page." };
4768
+ }
4769
+ function canRecordFromHere(channel = process.env[RECORD_CHANNEL_ENV]) {
4770
+ return parseRecordChannel(channel) !== null;
4771
+ }
4772
+ function handoffPlace(location, cwd = process.cwd()) {
4773
+ const m = /^(.+):(\d+):\d+$/.exec(location);
4774
+ if (!m) return null;
4775
+ return { file: path9.resolve(cwd, m[1]), line: Number(m[2]) };
4776
+ }
4777
+ async function recordFromHere(options) {
4778
+ const channel = parseRecordChannel(options.channel ?? process.env[RECORD_CHANNEL_ENV]);
4779
+ const place = handoffPlace(options.location);
4780
+ if (!channel || !place) return "this run names no editor to record into.";
4781
+ const { page, testInfo, ended } = options;
4782
+ if (ended?.aborted) return "the test ended.";
4783
+ const use = testInfo.project?.use ?? {};
4784
+ const context = page.context();
4785
+ return new Promise((resolve5) => {
4786
+ const socket = net.connect(channel.path);
4787
+ socket.setEncoding("utf8");
4788
+ const answers = /* @__PURE__ */ new Map();
4789
+ let calls = 0;
4790
+ let attached = false;
4791
+ let recording = false;
4792
+ let settled = false;
4793
+ let initScript;
4794
+ const send = (message) => {
4795
+ if (!socket.destroyed) socket.write(`${JSON.stringify(message)}
4796
+ `);
4797
+ };
4798
+ const onPage = () => send({ type: "notice", message: NEW_TAB_NOTICE });
4799
+ const onGone = () => {
4800
+ if (attached) send({ type: "closed" });
4801
+ finish("the page closed.");
4802
+ };
4803
+ const onEnded = () => {
4804
+ try {
4805
+ if (recording) {
4806
+ void internalCall(
4807
+ page,
4808
+ () => page.evaluate(
4809
+ ([name, value]) => {
4810
+ const dispatch = globalThis[name];
4811
+ if (typeof dispatch === "function") dispatch(value);
4812
+ },
4813
+ [IDE_DISPATCH_GLOBAL, { type: "piwi-recording-stopped" }]
4814
+ )
4815
+ ).catch(() => void 0);
4816
+ }
4817
+ if (attached) send({ type: "closed" });
4818
+ finish("the test ended.");
4819
+ } catch {
4820
+ }
4821
+ };
4822
+ page.once("close", onGone);
4823
+ page.once("crash", onGone);
4824
+ ended?.addEventListener("abort", onEnded, { once: true });
4825
+ const startWait = setTimeout(() => finish("the editor did not start the recording."), START_WAIT_MS);
4826
+ const release = () => {
4827
+ const relay = relays.get(context);
4828
+ if (relay && relay.recording === relayed) relay.recording = null;
4829
+ void Promise.resolve(initScript?.dispose?.()).catch(() => void 0);
4830
+ initScript = void 0;
4831
+ };
4832
+ const finish = (said) => {
4833
+ if (settled) return;
4834
+ settled = true;
4835
+ clearTimeout(startWait);
4836
+ release();
4837
+ for (const [call, answer] of answers) {
4838
+ answers.delete(call);
4839
+ answer(null);
4840
+ }
4841
+ context.off("page", onPage);
4842
+ page.off("close", onGone);
4843
+ page.off("crash", onGone);
4844
+ ended?.removeEventListener("abort", onEnded);
4845
+ socket.end();
4846
+ resolve5(recording ? null : said);
4847
+ };
4848
+ const relayed = (source, payload) => new Promise((answer) => {
4849
+ const call = ++calls;
4850
+ answers.set(call, answer);
4851
+ const outside = source.page !== page || source.frame !== page.mainFrame();
4852
+ send({ type: "binding", call, outside, payload });
4853
+ });
4854
+ const load = async (bundle) => {
4855
+ if (!/^https?:/i.test(page.url())) {
4856
+ const why = `the recorder records http(s) pages only, and this page is ${page.url() || "blank"}.`;
4857
+ send({ type: "notice", message: `Nothing can be recorded here: ${why}` });
4858
+ finish(why);
4859
+ return;
4860
+ }
4861
+ let relay = relays.get(context);
4862
+ if (!relay) {
4863
+ const created = { recording: null, lingering: false };
4864
+ await context.exposeBinding(
4865
+ IDE_RECORDER_BINDING,
4866
+ (source, payload) => created.recording ? created.recording(source, payload) : idleAnswer(payload)
4867
+ );
4868
+ relays.set(context, created);
4869
+ relay = created;
4870
+ }
4871
+ if (settled) return;
4872
+ relay.recording = relayed;
4873
+ if (!relay.lingering) {
4874
+ initScript = await context.addInitScript({ path: bundle });
4875
+ if (typeof initScript?.dispose !== "function") relay.lingering = true;
4876
+ }
4877
+ if (settled) return release();
4878
+ await page.evaluate(fs6.readFileSync(bundle, "utf-8"));
4879
+ if (settled) return release();
4880
+ recording = true;
4881
+ clearTimeout(startWait);
4882
+ context.on("page", onPage);
4883
+ send({ type: "loaded" });
4884
+ };
4885
+ socket.on("connect", () => {
4886
+ attached = true;
4887
+ send({
4888
+ type: "attach",
4889
+ token: channel.token,
4890
+ file: place.file,
4891
+ line: place.line,
4892
+ reason: options.reason,
4893
+ testIdAttribute: typeof use.testIdAttribute === "string" ? use.testIdAttribute : null,
4894
+ baseURL: typeof use.baseURL === "string" ? use.baseURL : null
4895
+ });
4896
+ });
4897
+ socket.on(
4898
+ "data",
4899
+ lineReader(
4900
+ (line) => {
4901
+ const message = parseHandoffToTest(line);
4902
+ if (!message || settled) return;
4903
+ switch (message.type) {
4904
+ case "refused":
4905
+ return finish(message.message);
4906
+ case "load":
4907
+ return void load(message.bundle).catch(
4908
+ (error) => finish(`the recorder could not be loaded: ${error instanceof Error ? error.message : String(error)}`)
4909
+ );
4910
+ case "answer": {
4911
+ const answer = answers.get(message.call);
4912
+ answers.delete(message.call);
4913
+ return answer?.(message.value);
4914
+ }
4915
+ case "dispatch":
4916
+ return void page.evaluate(
4917
+ ([name, value]) => {
4918
+ const dispatch = globalThis[name];
4919
+ if (typeof dispatch === "function") dispatch(value);
4920
+ },
4921
+ [IDE_DISPATCH_GLOBAL, message.message]
4922
+ ).catch(() => void 0);
4923
+ case "stop":
4924
+ return finish(null);
4925
+ }
4926
+ },
4927
+ () => finish("the editor sent a message too long to read.")
4928
+ )
4929
+ );
4930
+ socket.on("error", (error) => finish(`the editor could not be reached: ${error.message}`));
4931
+ socket.on("close", () => finish("the editor closed the recording."));
4932
+ });
4933
+ }
4934
+
4456
4935
  // src/internal/collect/coverage-settings.ts
4457
4936
  var SERVER_VALUES = /* @__PURE__ */ new Set(["files", "functions", "lines"]);
4458
4937
  function isServerGranularity(value) {
4459
4938
  return typeof value === "string" && SERVER_VALUES.has(value);
4460
4939
  }
4940
+ function cssCoverageInWorker() {
4941
+ return process.env[PIWI_ENV_KEYS.coverageCss] === "rules";
4942
+ }
4461
4943
  function browserCoverageInWorker() {
4462
4944
  const browser = process.env[PIWI_ENV_KEYS.coverageBrowser];
4463
4945
  return process.env[PIWI_ENV_KEYS.captureCodeReach] === "true" || browser === "files" || browser === "functions";
@@ -4468,7 +4950,7 @@ function browserFunctionsInWorker() {
4468
4950
  function browserFunctionCoverage(files) {
4469
4951
  return sanitizeCoverage({
4470
4952
  v: 1,
4471
- files: [...files].map(([path13, functions]) => ({ path: path13, runtime: "browser", functions }))
4953
+ files: [...files].map(([path15, functions]) => ({ path: path15, runtime: "browser", functions }))
4472
4954
  });
4473
4955
  }
4474
4956
  function newKnownFunctions(known, sent) {
@@ -4481,19 +4963,19 @@ function newKnownFunctions(known, sent) {
4481
4963
  budget = MAX_COVERAGE_RANGES;
4482
4964
  };
4483
4965
  for (const [raw, ranges] of known) {
4484
- const path13 = normalizeCoveragePath(raw);
4485
- if (path13 === null) continue;
4486
- let named = sent.get(path13);
4966
+ const path15 = normalizeCoveragePath(raw);
4967
+ if (path15 === null) continue;
4968
+ let named = sent.get(path15);
4487
4969
  let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
4488
4970
  if (fresh.length === 0) continue;
4489
- if (!named) sent.set(path13, named = /* @__PURE__ */ new Set());
4971
+ if (!named) sent.set(path15, named = /* @__PURE__ */ new Set());
4490
4972
  for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
4491
4973
  while (fresh.length > 0) {
4492
4974
  if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
4493
4975
  const part = fresh.slice(0, budget);
4494
4976
  fresh = fresh.slice(part.length);
4495
4977
  budget -= part.length;
4496
- files.push({ path: path13, allFunctions: part });
4978
+ files.push({ path: path15, allFunctions: part });
4497
4979
  }
4498
4980
  }
4499
4981
  close();
@@ -4504,9 +4986,9 @@ function serverCoverageInWorker() {
4504
4986
  }
4505
4987
 
4506
4988
  // src/internal/support/aria-sampling.ts
4507
- var path6 = __toESM(require("path"));
4989
+ var path10 = __toESM(require("path"));
4508
4990
  var os3 = __toESM(require("os"));
4509
- var fs4 = __toESM(require("fs"));
4991
+ var fs7 = __toESM(require("fs"));
4510
4992
 
4511
4993
  // src/internal/support/instance-id.ts
4512
4994
  var crypto = __toESM(require("crypto"));
@@ -4520,14 +5002,14 @@ function ariaSampleIdentity(filePath, title) {
4520
5002
  return `${filePath}\0${title}`;
4521
5003
  }
4522
5004
  function getAriaSampleFilePath(projectName) {
4523
- return path6.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
5005
+ return path10.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
4524
5006
  }
4525
5007
  var cachedSets = /* @__PURE__ */ new Map();
4526
5008
  function loadAriaSampleSet(projectName) {
4527
5009
  if (cachedSets.has(projectName)) return cachedSets.get(projectName);
4528
5010
  let set = null;
4529
5011
  try {
4530
- const raw = fs4.readFileSync(getAriaSampleFilePath(projectName), "utf8");
5012
+ const raw = fs7.readFileSync(getAriaSampleFilePath(projectName), "utf8");
4531
5013
  const parsed2 = JSON.parse(raw);
4532
5014
  if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
4533
5015
  set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
@@ -4548,97 +5030,10 @@ function isDueForAriaSample(testInfo) {
4548
5030
  );
4549
5031
  }
4550
5032
 
4551
- // src/internal/capture/quiet-capture.ts
4552
- var import_node_module = require("module");
4553
- var path8 = __toESM(require("path"));
4554
-
4555
- // src/internal/support/reporter-version.ts
4556
- var fs5 = __toESM(require("fs"));
4557
- var path7 = __toESM(require("path"));
4558
- var CANDIDATE_OFFSETS = ["..", "../../.."];
4559
- function findOwnPackageJson(fromDir) {
4560
- for (const offset of CANDIDATE_OFFSETS) {
4561
- try {
4562
- const pkgPath = path7.resolve(fromDir, offset, "package.json");
4563
- const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
4564
- const { name, version } = pkg;
4565
- if (name === "@piwitests/reporter" && typeof version === "string")
4566
- return { version, root: path7.dirname(pkgPath) };
4567
- } catch {
4568
- }
4569
- }
4570
- return void 0;
4571
- }
4572
-
4573
- // src/internal/capture/quiet-capture.ts
4574
- var BOXED_PREFIX_EXPORTS = [
4575
- [
4576
- "playwright-core/lib/coreBundle",
4577
- (mod) => mod?.utils?.setBoxedStackPrefixes
4578
- ],
4579
- ["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
4580
- ];
4581
- var boxing;
4582
- var resolvedPlaywrightPackageJson;
4583
- function resolvePlaywrightPackageJson() {
4584
- if (resolvedPlaywrightPackageJson !== void 0) return resolvedPlaywrightPackageJson;
4585
- try {
4586
- const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
4587
- resolvedPlaywrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
4588
- } catch {
4589
- resolvedPlaywrightPackageJson = null;
4590
- }
4591
- return resolvedPlaywrightPackageJson;
4592
- }
4593
- function resolveBoxing() {
4594
- try {
4595
- const root = findOwnPackageJson(__dirname)?.root;
4596
- if (!root) return null;
4597
- const playwrightPackageJson = resolvePlaywrightPackageJson();
4598
- if (!playwrightPackageJson) return null;
4599
- const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
4600
- for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
4601
- let set;
4602
- try {
4603
- set = pick(requireFromPlaywright(moduleId));
4604
- } catch {
4605
- continue;
4606
- }
4607
- if (typeof set !== "function") continue;
4608
- return {
4609
- set,
4610
- prefixes: [path8.dirname(playwrightPackageJson), path8.join(root, "dist") + path8.sep]
4611
- };
4612
- }
4613
- } catch {
4614
- }
4615
- return null;
4616
- }
4617
- function boxCaptureFrames(extraPrefixes = []) {
4618
- const first = boxing === void 0;
4619
- if (first) boxing = resolveBoxing();
4620
- if (!boxing) return;
4621
- const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
4622
- if (!first && added.length === 0) return;
4623
- boxing.prefixes.push(...added);
4624
- try {
4625
- boxing.set([...boxing.prefixes]);
4626
- } catch {
4627
- }
4628
- }
4629
- function callMethod(target, prop, args) {
4630
- return target[prop](...args);
4631
- }
4632
- function internalCall(owner, run) {
4633
- const wrap = owner?._wrapApiCall;
4634
- if (typeof wrap !== "function") return run();
4635
- return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
4636
- }
4637
-
4638
5033
  // src/internal/capture/code-reach.ts
4639
5034
  var crypto2 = __toESM(require("crypto"));
4640
- var fs6 = __toESM(require("fs"));
4641
- var path9 = __toESM(require("path"));
5035
+ var fs8 = __toESM(require("fs"));
5036
+ var path11 = __toESM(require("path"));
4642
5037
  var import_node_child_process = require("child_process");
4643
5038
  var MAX_MAP_BYTES = 20 * 1024 * 1024;
4644
5039
  var MAP_TIMEOUT_MS = 5e3;
@@ -4681,18 +5076,18 @@ function repositoryRoot(from) {
4681
5076
  }).trim();
4682
5077
  } catch {
4683
5078
  }
4684
- cachedRepoRoot = { from, root: path9.resolve(root) };
5079
+ cachedRepoRoot = { from, root: path11.resolve(root) };
4685
5080
  return cachedRepoRoot.root;
4686
5081
  }
4687
5082
  function codeReachRoots(configDir, configured) {
4688
5083
  const repoRoot = repositoryRoot(configDir);
4689
- const resolve3 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path9.resolve(configDir, r));
4690
- const global = Array.isArray(configured) ? resolve3(configured) : resolve3(configured?.["*"]);
5084
+ const resolve5 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path11.resolve(configDir, r));
5085
+ const global = Array.isArray(configured) ? resolve5(configured) : resolve5(configured?.["*"]);
4691
5086
  const byOrigin = /* @__PURE__ */ new Map();
4692
5087
  if (configured && !Array.isArray(configured)) {
4693
5088
  for (const [key, dirs] of Object.entries(configured)) {
4694
5089
  const origin = key === "*" ? null : originOf2(key);
4695
- const resolved = resolve3(dirs);
5090
+ const resolved = resolve5(dirs);
4696
5091
  if (origin && resolved.length) byOrigin.set(origin, resolved);
4697
5092
  }
4698
5093
  }
@@ -4715,7 +5110,7 @@ function scriptRoots(roots, scriptUrl, pageUrl) {
4715
5110
  }
4716
5111
  function isFile(file) {
4717
5112
  try {
4718
- return fs6.statSync(file).isFile();
5113
+ return fs8.statSync(file).isFile();
4719
5114
  } catch {
4720
5115
  return false;
4721
5116
  }
@@ -4723,10 +5118,10 @@ function isFile(file) {
4723
5118
  function resolveSourcePath(source, roots) {
4724
5119
  const normalized = normalizeSourcePath(source);
4725
5120
  if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
4726
- if (path9.isAbsolute(normalized) && isFile(normalized)) return path9.resolve(normalized);
5121
+ if (path11.isAbsolute(normalized) && isFile(normalized)) return path11.resolve(normalized);
4727
5122
  const relative7 = normalized.replace(/^\/+/, "");
4728
5123
  for (const root of roots) {
4729
- const candidate = path9.resolve(root, relative7);
5124
+ const candidate = path11.resolve(root, relative7);
4730
5125
  if (isFile(candidate)) return candidate;
4731
5126
  }
4732
5127
  return null;
@@ -4758,7 +5153,7 @@ function fileForUrl(url, roots, bases) {
4758
5153
  return file;
4759
5154
  }
4760
5155
  function relativeToRepo(file, repoRoot) {
4761
- return path9.relative(repoRoot, file).split(path9.sep).join("/");
5156
+ return path11.relative(repoRoot, file).split(path11.sep).join("/");
4762
5157
  }
4763
5158
  function scriptVersion(entry) {
4764
5159
  return entry.hash ?? crypto2.createHash("sha1").update(entry.source ?? "").digest("hex");
@@ -4821,12 +5216,12 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
4821
5216
  const files = /* @__PURE__ */ new Map();
4822
5217
  const known = /* @__PURE__ */ new Map();
4823
5218
  const addTo = (into, file, functions) => {
4824
- const path13 = relativeToRepo(file, roots.repoRoot);
4825
- const listed = into.get(path13);
5219
+ const path15 = relativeToRepo(file, roots.repoRoot);
5220
+ const listed = into.get(path15);
4826
5221
  if (listed) listed.push(...functions);
4827
- else into.set(path13, [...functions]);
5222
+ else into.set(path15, [...functions]);
4828
5223
  };
4829
- const add = (file, functions) => addTo(files, file, functions);
5224
+ const add2 = (file, functions) => addTo(files, file, functions);
4830
5225
  const bases = viteBases(entries);
4831
5226
  const scripts = entries.filter((entry) => entry.url && entry.functions?.length && !REPLAYED_SCRIPT.test(entry.url));
4832
5227
  const cached = scripts.map((entry) => scriptCache.get(entry.url, scriptVersion(entry)));
@@ -4853,14 +5248,14 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
4853
5248
  const text = textOf2(covered, resolution);
4854
5249
  if (resolution.lookup) {
4855
5250
  const reached = reachedFunctionLines(covered, resolution.lookup, text);
4856
- if (reached.size > 0) add(resolution.file, [...reached.values()].flat());
5251
+ if (reached.size > 0) add2(resolution.file, [...reached.values()].flat());
4857
5252
  } else if (scriptRan(covered, text)) {
4858
- add(resolution.file, reachedFunctionLines(covered, null, text).get(-1) ?? []);
5253
+ add2(resolution.file, reachedFunctionLines(covered, null, text).get(-1) ?? []);
4859
5254
  }
4860
5255
  } else if (resolution.kind === "map") {
4861
5256
  for (const [index, functions] of reachedFunctionLines(covered, resolution.lookup, textOf2(covered, resolution))) {
4862
5257
  const file = mapFile(resolution, index);
4863
- if (file) add(file, functions);
5258
+ if (file) add2(file, functions);
4864
5259
  }
4865
5260
  }
4866
5261
  if (!withKnown || resolution.kind === "none") continue;
@@ -4939,10 +5334,13 @@ async function markCurrentDocument(page, cdp) {
4939
5334
  }
4940
5335
  function onPaused(page, capture, event) {
4941
5336
  const leaving = event.reason === "EventListener" && event.data?.eventName === "listener:beforeunload" && (event.callFrames?.[0]?.functionName ?? MARK_FUNCTION) === MARK_FUNCTION;
4942
- const read = leaving ? readCoverage(page, capture.cdp) : null;
5337
+ const read = leaving ? Promise.all([
5338
+ capture.js ? readCoverage(page, capture.cdp) : Promise.resolve([]),
5339
+ capture.css ? readCssUsage(page, capture.cdp, capture.css) : Promise.resolve()
5340
+ ]) : null;
4943
5341
  if (read) {
4944
5342
  const merging = read.then(
4945
- (coverage) => mergeCoverageRead(capture.ran, capture.scripts, coverage, capture.keepIdle)
5343
+ ([coverage]) => mergeCoverageRead(capture.ran, capture.scripts, coverage, capture.keepIdle)
4946
5344
  );
4947
5345
  capture.reading.add(merging);
4948
5346
  void merging.finally(() => capture.reading.delete(merging));
@@ -4956,7 +5354,7 @@ function versionKey(url, hash) {
4956
5354
  return `${hash} ${url}`;
4957
5355
  }
4958
5356
  function onScriptParsed(page, capture, event) {
4959
- if (!event.url || REPLAYED_SCRIPT.test(event.url)) return;
5357
+ if (!capture.js || !event.url || REPLAYED_SCRIPT.test(event.url)) return;
4960
5358
  capture.scripts.set(event.scriptId, { url: event.url, hash: event.hash, pageUrl: capture.pageUrl });
4961
5359
  if (NEVER_REACHED.test(event.url)) return;
4962
5360
  const key = versionKey(event.url, event.hash);
@@ -4976,8 +5374,8 @@ function onScriptParsed(page, capture, event) {
4976
5374
  var READ_WAIT_MS = 5e3;
4977
5375
  function readCoverage(page, cdp) {
4978
5376
  let timer;
4979
- const late = new Promise((resolve3) => {
4980
- timer = setTimeout(() => resolve3([]), READ_WAIT_MS);
5377
+ const late = new Promise((resolve5) => {
5378
+ timer = setTimeout(() => resolve5([]), READ_WAIT_MS);
4981
5379
  });
4982
5380
  const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
4983
5381
  (answer) => answer.result,
@@ -4985,6 +5383,53 @@ function readCoverage(page, cdp) {
4985
5383
  );
4986
5384
  return Promise.race([taken, late]).finally(() => clearTimeout(timer));
4987
5385
  }
5386
+ function onStyleSheetAdded(page, cdp, css, pageUrl, header) {
5387
+ if (header.isConstructed) return;
5388
+ const sheet = {
5389
+ sourceURL: header.sourceURL,
5390
+ ...header.sourceMapURL ? { sourceMapURL: header.sourceMapURL } : {},
5391
+ isInline: header.isInline,
5392
+ startLine: header.startLine,
5393
+ pageUrl,
5394
+ used: /* @__PURE__ */ new Map()
5395
+ };
5396
+ css.sheets.set(header.styleSheetId, sheet);
5397
+ const reading = (async () => {
5398
+ try {
5399
+ if (header.ownerNode !== void 0 && !/\.css(?:[?#]|$)/i.test(header.sourceURL)) {
5400
+ const { node } = await internalCall(
5401
+ page,
5402
+ () => cdp.send("DOM.describeNode", { backendNodeId: header.ownerNode })
5403
+ );
5404
+ const attributes = node.attributes ?? [];
5405
+ const at = attributes.indexOf("data-vite-dev-id");
5406
+ if (at >= 0 && typeof attributes[at + 1] === "string") sheet.devId = attributes[at + 1];
5407
+ }
5408
+ if (/\/node_modules\//.test(sheet.devId ?? header.sourceURL)) return;
5409
+ const { text } = await internalCall(
5410
+ page,
5411
+ () => cdp.send("CSS.getStyleSheetText", { styleSheetId: header.styleSheetId })
5412
+ );
5413
+ sheet.text = text;
5414
+ } catch {
5415
+ }
5416
+ })();
5417
+ css.reading.add(reading);
5418
+ void reading.finally(() => css.reading.delete(reading));
5419
+ }
5420
+ async function readCssUsage(page, cdp, css) {
5421
+ try {
5422
+ const { coverage } = await internalCall(page, () => cdp.send("CSS.takeCoverageDelta"));
5423
+ for (const rule of coverage) {
5424
+ if (!rule.used) continue;
5425
+ css.sheets.get(rule.styleSheetId)?.used.set(`${rule.startOffset}:${rule.endOffset}`, {
5426
+ startOffset: rule.startOffset,
5427
+ endOffset: rule.endOffset
5428
+ });
5429
+ }
5430
+ } catch {
5431
+ }
5432
+ }
4988
5433
  function mergeCoverageRead(ran, scripts, read, keepIdle = true) {
4989
5434
  for (const covered of read) {
4990
5435
  const script = scripts.get(covered.scriptId);
@@ -5019,11 +5464,15 @@ async function detach(page, cdp) {
5019
5464
  }
5020
5465
  async function startCodeReach(page) {
5021
5466
  if (!isChromiumPage(page) || captures.has(page)) return false;
5467
+ const withCss = cssCoverageInWorker();
5468
+ const js = browserCoverageInWorker() || !withCss;
5022
5469
  let cdp = null;
5023
5470
  try {
5024
5471
  cdp = await internalCall(page, () => page.context().newCDPSession(page));
5025
5472
  const capture = {
5026
5473
  cdp,
5474
+ js,
5475
+ css: withCss ? { sheets: /* @__PURE__ */ new Map(), reading: /* @__PURE__ */ new Set() } : null,
5027
5476
  scripts: /* @__PURE__ */ new Map(),
5028
5477
  sources: /* @__PURE__ */ new Map(),
5029
5478
  ran: /* @__PURE__ */ new Map(),
@@ -5033,6 +5482,9 @@ async function startCodeReach(page) {
5033
5482
  };
5034
5483
  const marks = marksOf(page);
5035
5484
  cdp.on("Debugger.scriptParsed", (event) => onScriptParsed(page, capture, event));
5485
+ const css = capture.css;
5486
+ if (css)
5487
+ cdp.on("CSS.styleSheetAdded", (event) => onStyleSheetAdded(page, cdp, css, capture.pageUrl, event.header));
5036
5488
  cdp.on("Debugger.paused", (event) => onPaused(page, capture, event));
5037
5489
  cdp.on("Page.frameNavigated", (event) => {
5038
5490
  if (event.frame.parentId) return;
@@ -5043,8 +5495,17 @@ async function startCodeReach(page) {
5043
5495
  page,
5044
5496
  () => Promise.all([
5045
5497
  capture.cdp.send("Debugger.enable"),
5046
- capture.cdp.send("Profiler.enable"),
5047
- capture.cdp.send("Profiler.startPreciseCoverage", { callCount: false, detailed: false }),
5498
+ ...js ? [
5499
+ capture.cdp.send("Profiler.enable"),
5500
+ capture.cdp.send("Profiler.startPreciseCoverage", { callCount: false, detailed: false })
5501
+ ] : [],
5502
+ // The style sheets' rule usage records nothing without the DOM and CSS domains. A failure there leaves the
5503
+ // scripts' coverage running.
5504
+ ...css ? [
5505
+ capture.cdp.send("DOM.enable").then(() => capture.cdp.send("CSS.enable")).then(() => capture.cdp.send("CSS.startRuleUsageTracking")).catch(() => {
5506
+ capture.css = null;
5507
+ })
5508
+ ] : [],
5048
5509
  capture.cdp.send("Page.enable"),
5049
5510
  capture.cdp.send("Page.addScriptToEvaluateOnNewDocument", { source: MARK_SOURCE, worldName: MARK_WORLD }),
5050
5511
  capture.cdp.send("DOMDebugger.setEventListenerBreakpoint", { eventName: "beforeunload" })
@@ -5059,17 +5520,28 @@ async function startCodeReach(page) {
5059
5520
  }
5060
5521
  }
5061
5522
  async function stopCodeReach(page) {
5523
+ return (await stopPageCoverage(page)).scripts;
5524
+ }
5525
+ async function stopPageCoverage(page) {
5062
5526
  const capture = captures.get(page);
5063
- if (!capture) return null;
5527
+ if (!capture) return { scripts: null, sheets: [] };
5064
5528
  captures.delete(page);
5065
5529
  const { cdp } = capture;
5066
- try {
5530
+ const sheets = async () => {
5531
+ if (!capture.css) return [];
5532
+ await readCssUsage(page, cdp, capture.css);
5533
+ await Promise.all(capture.css.reading);
5534
+ await internalCall(page, () => cdp.send("CSS.stopRuleUsageTracking")).catch(() => {
5535
+ });
5536
+ return [...capture.css.sheets.values()].filter((sheet) => sheet.used.size > 0 || sheet.text !== void 0);
5537
+ };
5538
+ const scripts = async () => {
5539
+ if (!capture.js) return null;
5067
5540
  const last = await internalCall(page, async () => {
5068
5541
  const taken = await cdp.send("Profiler.takePreciseCoverage");
5069
5542
  await cdp.send("Profiler.stopPreciseCoverage");
5070
5543
  return taken.result;
5071
5544
  });
5072
- await Promise.all(capture.reading);
5073
5545
  mergeCoverageRead(capture.ran, capture.scripts, last, capture.keepIdle);
5074
5546
  const entries = [];
5075
5547
  for (const [key, { script, functions }] of capture.ran) {
@@ -5079,28 +5551,292 @@ async function stopCodeReach(page) {
5079
5551
  entries.push(entry);
5080
5552
  }
5081
5553
  return entries;
5082
- } catch {
5083
- return null;
5554
+ };
5555
+ try {
5556
+ await Promise.all(capture.reading);
5557
+ const [read, styles] = await Promise.all([scripts().catch(() => null), sheets().catch(() => [])]);
5558
+ return { scripts: read, sheets: styles };
5084
5559
  } finally {
5085
5560
  await detach(page, cdp);
5086
5561
  }
5087
5562
  }
5088
5563
 
5564
+ // src/internal/capture/css-coverage.ts
5565
+ var crypto3 = __toESM(require("crypto"));
5566
+ var fs9 = __toESM(require("fs"));
5567
+
5568
+ // ../core/src/css-rules.ts
5569
+ var GROUPING = /* @__PURE__ */ new Set(["media", "supports", "layer", "container", "scope", "starting-style", "document"]);
5570
+ function parseCssRules(text) {
5571
+ const rules = [];
5572
+ const open = [];
5573
+ let preludeStart = -1;
5574
+ for (let i = 0; i < text.length; i++) {
5575
+ const c = text[i];
5576
+ if (c === "/" && text[i + 1] === "*") {
5577
+ const end = text.indexOf("*/", i + 2);
5578
+ i = end < 0 ? text.length : end + 1;
5579
+ continue;
5580
+ }
5581
+ if (c === '"' || c === "'") {
5582
+ if (preludeStart < 0) preludeStart = i;
5583
+ for (i++; i < text.length && text[i] !== c && text[i] !== "\n"; i++) if (text[i] === "\\") i++;
5584
+ continue;
5585
+ }
5586
+ if (c === "\\") {
5587
+ if (preludeStart < 0) preludeStart = i;
5588
+ i++;
5589
+ continue;
5590
+ }
5591
+ if ((c === "u" || c === "U") && /^url\(/i.test(text.slice(i, i + 4)) && !/[\w-]/.test(text[i - 1] ?? "")) {
5592
+ let j = i + 4;
5593
+ while (j < text.length && /\s/.test(text[j])) j++;
5594
+ if (text[j] !== '"' && text[j] !== "'") {
5595
+ if (preludeStart < 0) preludeStart = i;
5596
+ for (; j < text.length && text[j] !== ")"; j++) if (text[j] === "\\") j++;
5597
+ i = j;
5598
+ continue;
5599
+ }
5600
+ }
5601
+ if (text.startsWith("<!--", i) || text.startsWith("-->", i)) {
5602
+ i += text.startsWith("<!--", i) ? 3 : 2;
5603
+ continue;
5604
+ }
5605
+ if (c === "{") {
5606
+ const start = preludeStart < 0 ? i : preludeStart;
5607
+ const atRule = /^@([\w-]+)/.exec(text.slice(start, i));
5608
+ const parent = open[open.length - 1];
5609
+ const kind = atRule ? GROUPING.has(atRule[1].toLowerCase()) ? "group" : "other" : parent?.kind === "other" ? "other" : "style";
5610
+ open.push({ kind, start, open: i });
5611
+ preludeStart = -1;
5612
+ continue;
5613
+ }
5614
+ if (c === "}") {
5615
+ const block = open.pop();
5616
+ if (block && block.kind !== "other")
5617
+ rules.push({ kind: block.kind, start: block.start, open: block.open, end: i + 1 });
5618
+ preludeStart = -1;
5619
+ continue;
5620
+ }
5621
+ if (c === ";") {
5622
+ preludeStart = -1;
5623
+ continue;
5624
+ }
5625
+ if (preludeStart < 0 && !/\s/.test(c)) preludeStart = i;
5626
+ }
5627
+ for (const block of open) {
5628
+ if (block.kind !== "other")
5629
+ rules.push({ kind: block.kind, start: block.start, open: block.open, end: text.length });
5630
+ }
5631
+ return rules.sort((a, b) => a.start - b.start);
5632
+ }
5633
+ function usedCssRules(rules, used) {
5634
+ const styleAt = /* @__PURE__ */ new Map();
5635
+ const groups = [];
5636
+ for (const rule of rules) {
5637
+ if (rule.kind === "style") styleAt.set(rule.start, rule);
5638
+ else groups.push(rule);
5639
+ }
5640
+ const out = /* @__PURE__ */ new Map();
5641
+ for (const range of used) {
5642
+ const style = styleAt.get(range.startOffset);
5643
+ if (style) {
5644
+ out.set(`${style.start}`, [style.start, style.end]);
5645
+ continue;
5646
+ }
5647
+ const group = groups.find((rule) => rule.start <= range.startOffset && range.startOffset <= rule.open);
5648
+ if (group) out.set(`${group.start}`, [group.start, group.start + 1]);
5649
+ }
5650
+ return [...out.values()].sort((a, b) => a[0] - b[0]);
5651
+ }
5652
+ function allCssRules(rules) {
5653
+ return rules.map((rule) => rule.kind === "style" ? [rule.start, rule.end] : [rule.start, rule.start + 1]);
5654
+ }
5655
+ function cssRuleLines(text, offsets, options = {}) {
5656
+ const starts = lineStarts(text);
5657
+ const lineOffset = options.lineOffset ?? 0;
5658
+ const out = /* @__PURE__ */ new Map();
5659
+ const add2 = (source, range) => {
5660
+ const ranges = out.get(source);
5661
+ if (ranges) ranges.push(range);
5662
+ else out.set(source, [range]);
5663
+ };
5664
+ for (const [startOffset, endOffset] of offsets) {
5665
+ const start = offsetToPosition(starts, startOffset);
5666
+ const end = offsetToPosition(starts, Math.max(startOffset, endOffset - 1));
5667
+ if (!options.lookup) {
5668
+ add2(-1, [start.line + lineOffset + 1, end.line + lineOffset + 1]);
5669
+ continue;
5670
+ }
5671
+ const first = options.lookup.originalAt(start.line, start.column);
5672
+ if (!first) continue;
5673
+ const last = options.lookup.originalAt(end.line, end.column);
5674
+ const lastLine = last && last.source === first.source && last.line >= first.line ? last.line : first.line;
5675
+ add2(first.source, [first.line + 1, lastLine + 1]);
5676
+ }
5677
+ return out;
5678
+ }
5679
+
5680
+ // src/internal/capture/css-coverage.ts
5681
+ var STYLE_SOURCES = /\.(?:css|scss|sass|less|styl|stylus|pcss|postcss|vue|svelte|astro|html?)$/i;
5682
+ var BUILD_DIRS2 = /(?:^|\/)(?:dist|build|out|\.output|\.next|\.nuxt|\.svelte-kit)\//;
5683
+ var resolutions = /* @__PURE__ */ new Map();
5684
+ var MAX_CACHED_SHEETS = 2e3;
5685
+ function fileForStyleUrl(url, roots) {
5686
+ let pathname;
5687
+ try {
5688
+ const parsed2 = new URL(url);
5689
+ if (parsed2.protocol !== "http:" && parsed2.protocol !== "https:" && parsed2.protocol !== "file:") return null;
5690
+ pathname = parsed2.pathname;
5691
+ } catch {
5692
+ return null;
5693
+ }
5694
+ return sourceFile(decodeURIComponent(pathname), roots);
5695
+ }
5696
+ function sourceFile(source, roots) {
5697
+ const bare = source.replace(/[?#].*$/, "");
5698
+ if (!STYLE_SOURCES.test(bare) || /(?:^|\/)node_modules\//.test(bare)) return null;
5699
+ const file = resolveSourcePath(bare, roots);
5700
+ if (!file || BUILD_DIRS2.test(file.replace(/\\/g, "/")) || /(?:^|[\\/])node_modules[\\/]/.test(file)) return null;
5701
+ return file;
5702
+ }
5703
+ function styleBlockLine(file, index) {
5704
+ let text;
5705
+ try {
5706
+ text = fs9.readFileSync(file, "utf-8");
5707
+ } catch {
5708
+ return 0;
5709
+ }
5710
+ const blocks = [...text.matchAll(/^<style\b[^>]*>/gim)];
5711
+ const block = blocks[index];
5712
+ if (!block) return 0;
5713
+ return text.slice(0, block.index + block[0].length).split("\n").length - 1;
5714
+ }
5715
+ async function sheetMap(sheet, text, fetchMap) {
5716
+ const ref = sourceMappingUrl(text) ?? sheet.sourceMapURL ?? null;
5717
+ if (!ref) return null;
5718
+ let raw;
5719
+ if (ref.startsWith("data:")) raw = decodeDataUrl(ref);
5720
+ else {
5721
+ try {
5722
+ raw = await fetchMap(new URL(ref, sheet.sourceURL).href);
5723
+ } catch {
5724
+ raw = null;
5725
+ }
5726
+ }
5727
+ if (!raw) return null;
5728
+ try {
5729
+ return decodeSourceMap(JSON.parse(raw));
5730
+ } catch {
5731
+ return null;
5732
+ }
5733
+ }
5734
+ function sameLines(text, rules, file, lineOffset) {
5735
+ let source;
5736
+ try {
5737
+ source = fs9.readFileSync(file, "utf-8");
5738
+ } catch {
5739
+ return false;
5740
+ }
5741
+ const fileLines = source.split(/\r?\n/);
5742
+ const sheetLineStarts = [0];
5743
+ for (let i = 0; i < text.length; i++) if (text[i] === "\n") sheetLineStarts.push(i + 1);
5744
+ let low = 0;
5745
+ for (const rule of rules) {
5746
+ while (low + 1 < sheetLineStarts.length && sheetLineStarts[low + 1] <= rule.start) low++;
5747
+ const head = text.slice(rule.start, rule.open).split("\n")[0].trim();
5748
+ if (head && !(fileLines[low + lineOffset] ?? "").includes(head)) return false;
5749
+ }
5750
+ return true;
5751
+ }
5752
+ async function resolveSheet(sheet, text, dirs, fetchMap) {
5753
+ const lookup = await sheetMap(sheet, text, fetchMap);
5754
+ const rules = parseCssRules(text);
5755
+ if (lookup) {
5756
+ const sources = lookup.sources.map((source) => {
5757
+ const direct = sourceFile(source, dirs);
5758
+ if (direct) return direct;
5759
+ try {
5760
+ return /^[a-z][\w+.-]*:/i.test(source) ? null : fileForStyleUrl(new URL(source, sheet.sourceURL).href, dirs);
5761
+ } catch {
5762
+ return null;
5763
+ }
5764
+ });
5765
+ return { rules, lookup, file: null, lineOffset: 0, sources, exact: true };
5766
+ }
5767
+ if (sheet.devId) {
5768
+ const file2 = sourceFile(sheet.devId, dirs);
5769
+ if (!file2) return null;
5770
+ const index = /[?&]index=(\d+)/.exec(sheet.devId);
5771
+ const lineOffset2 = /\.(?:vue|svelte|astro)$/i.test(file2) && index ? styleBlockLine(file2, Number(index[1])) : 0;
5772
+ return { rules, lookup: null, file: file2, lineOffset: lineOffset2, sources: [], exact: sameLines(text, rules, file2, lineOffset2) };
5773
+ }
5774
+ const file = fileForStyleUrl(sheet.sourceURL, dirs);
5775
+ if (!file) return null;
5776
+ const lineOffset = sheet.isInline ? sheet.startLine : 0;
5777
+ return { rules, lookup: null, file, lineOffset, sources: [], exact: sameLines(text, rules, file, lineOffset) };
5778
+ }
5779
+ function cachedResolution(sheet, text, dirs, fetchMap) {
5780
+ const version = crypto3.createHash("sha1").update(text).digest("hex");
5781
+ const key = [sheet.devId ?? sheet.sourceURL, sheet.isInline ? sheet.startLine : "", dirs.join("\0"), version].join(
5782
+ " "
5783
+ );
5784
+ let pending = resolutions.get(key);
5785
+ if (!pending) {
5786
+ pending = resolveSheet(sheet, text, dirs, fetchMap).catch(() => null);
5787
+ resolutions.set(key, pending);
5788
+ if (resolutions.size > MAX_CACHED_SHEETS) resolutions.delete(resolutions.keys().next().value);
5789
+ }
5790
+ return pending;
5791
+ }
5792
+ function add(into, file, ranges, repoRoot, fileLevel = false) {
5793
+ if (ranges.length === 0 && !fileLevel) return;
5794
+ const key = relativeToRepo(file, repoRoot);
5795
+ into.set(key, [...into.get(key) ?? [], ...ranges]);
5796
+ }
5797
+ async function resolveCssCoverage(sheets, roots, fetchMap) {
5798
+ const reached = /* @__PURE__ */ new Map();
5799
+ const known = /* @__PURE__ */ new Map();
5800
+ for (const sheet of sheets) {
5801
+ if (sheet.text === void 0) continue;
5802
+ const dirs = scriptRoots(roots, sheet.sourceURL, sheet.pageUrl);
5803
+ const resolution = await cachedResolution(sheet, sheet.text, dirs, fetchMap);
5804
+ if (!resolution) continue;
5805
+ const used = usedCssRules(resolution.rules, [...sheet.used.values()]);
5806
+ if (!resolution.exact) {
5807
+ if (resolution.file && used.length > 0) add(reached, resolution.file, [], roots.repoRoot, true);
5808
+ continue;
5809
+ }
5810
+ const options = { lookup: resolution.lookup, lineOffset: resolution.lineOffset };
5811
+ const fileOf = (source) => source === -1 ? resolution.file : resolution.sources[source] ?? null;
5812
+ const matched = cssRuleLines(sheet.text, used, options);
5813
+ for (const [source, ranges] of matched) {
5814
+ const file = fileOf(source);
5815
+ if (file) add(reached, file, ranges, roots.repoRoot);
5816
+ }
5817
+ for (const [source, ranges] of cssRuleLines(sheet.text, allCssRules(resolution.rules), options)) {
5818
+ const file = fileOf(source);
5819
+ if (file) add(known, file, ranges, roots.repoRoot);
5820
+ }
5821
+ }
5822
+ return { reached, known };
5823
+ }
5824
+
5089
5825
  // src/internal/capture/resource-ledger.ts
5090
- var fs8 = __toESM(require("fs"));
5091
- var path11 = __toESM(require("path"));
5826
+ var fs11 = __toESM(require("fs"));
5827
+ var path13 = __toESM(require("path"));
5092
5828
  var import_node_module2 = require("module");
5093
5829
 
5094
5830
  // src/internal/capture/worker-metrics.ts
5095
5831
  var import_node_perf_hooks = require("perf_hooks");
5096
5832
 
5097
5833
  // src/internal/support/system-readers.ts
5098
- var fs7 = __toESM(require("fs"));
5099
- var path10 = __toESM(require("path"));
5834
+ var fs10 = __toESM(require("fs"));
5835
+ var path12 = __toESM(require("path"));
5100
5836
  var CLK_TCK = 100;
5101
5837
  function readText(file) {
5102
5838
  try {
5103
- return fs7.readFileSync(file, "utf8");
5839
+ return fs10.readFileSync(file, "utf8");
5104
5840
  } catch {
5105
5841
  return null;
5106
5842
  }
@@ -5182,45 +5918,45 @@ var ProcFs = class {
5182
5918
  }
5183
5919
  /** Whether this looks like a Linux `/proc` at all. */
5184
5920
  available() {
5185
- return fs7.existsSync(path10.join(this.root, "stat"));
5921
+ return fs10.existsSync(path12.join(this.root, "stat"));
5186
5922
  }
5187
5923
  /** Every process, keyed by pid. */
5188
5924
  table() {
5189
5925
  const out = /* @__PURE__ */ new Map();
5190
5926
  let names;
5191
5927
  try {
5192
- names = fs7.readdirSync(this.root);
5928
+ names = fs10.readdirSync(this.root);
5193
5929
  } catch {
5194
5930
  return out;
5195
5931
  }
5196
5932
  for (const name of names) {
5197
5933
  if (!/^\d+$/.test(name)) continue;
5198
- const text = readText(path10.join(this.root, name, "stat"));
5934
+ const text = readText(path12.join(this.root, name, "stat"));
5199
5935
  const stat = text ? parseProcStat(Number(name), text) : null;
5200
5936
  if (stat) out.set(stat.pid, stat);
5201
5937
  }
5202
5938
  return out;
5203
5939
  }
5204
5940
  stat(pid) {
5205
- const text = readText(path10.join(this.root, String(pid), "stat"));
5941
+ const text = readText(path12.join(this.root, String(pid), "stat"));
5206
5942
  return text ? parseProcStat(pid, text) : null;
5207
5943
  }
5208
5944
  runWaitMs(pid) {
5209
- const text = readText(path10.join(this.root, String(pid), "schedstat"));
5945
+ const text = readText(path12.join(this.root, String(pid), "schedstat"));
5210
5946
  return text ? parseSchedstat(text) : null;
5211
5947
  }
5212
5948
  cmdline(pid) {
5213
- return (readText(path10.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
5949
+ return (readText(path12.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
5214
5950
  }
5215
5951
  /** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
5216
5952
  statusKb(pid, key) {
5217
- const text = readText(path10.join(this.root, String(pid), "status"));
5953
+ const text = readText(path12.join(this.root, String(pid), "status"));
5218
5954
  return text ? parseKbField(text, key) : null;
5219
5955
  }
5220
5956
  /** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
5221
5957
  async pssKb(pid) {
5222
5958
  try {
5223
- return parseKbField(await fs7.promises.readFile(path10.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
5959
+ return parseKbField(await fs10.promises.readFile(path12.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
5224
5960
  } catch {
5225
5961
  return null;
5226
5962
  }
@@ -5228,29 +5964,29 @@ var ProcFs = class {
5228
5964
  /** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
5229
5965
  resetPeakRss(pid) {
5230
5966
  try {
5231
- fs7.writeFileSync(path10.join(this.root, String(pid), "clear_refs"), "5");
5967
+ fs10.writeFileSync(path12.join(this.root, String(pid), "clear_refs"), "5");
5232
5968
  return true;
5233
5969
  } catch {
5234
5970
  return false;
5235
5971
  }
5236
5972
  }
5237
5973
  cpuTicks() {
5238
- const text = readText(path10.join(this.root, "stat"));
5974
+ const text = readText(path12.join(this.root, "stat"));
5239
5975
  return text ? parseCpuTicks(text) : null;
5240
5976
  }
5241
5977
  /** Total stall time of a resource's pressure file, in µs. */
5242
5978
  pressureUs(resource) {
5243
- const text = readText(path10.join(this.root, "pressure", resource));
5979
+ const text = readText(path12.join(this.root, "pressure", resource));
5244
5980
  return text ? parsePsiTotal(text) : null;
5245
5981
  }
5246
5982
  memAvailableKb() {
5247
- const text = readText(path10.join(this.root, "meminfo"));
5983
+ const text = readText(path12.join(this.root, "meminfo"));
5248
5984
  return text ? parseKbField(text, "MemAvailable") : null;
5249
5985
  }
5250
5986
  /** Open file descriptors of a process. */
5251
5987
  fdCount(pid) {
5252
5988
  try {
5253
- return fs7.readdirSync(path10.join(this.root, String(pid), "fd")).length;
5989
+ return fs10.readdirSync(path12.join(this.root, String(pid), "fd")).length;
5254
5990
  } catch {
5255
5991
  return null;
5256
5992
  }
@@ -5412,10 +6148,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
5412
6148
  var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
5413
6149
  var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
5414
6150
  function relativePath(file) {
5415
- return path11.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path11.sep).join("/");
6151
+ return path13.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path13.sep).join("/");
5416
6152
  }
5417
6153
  function isLibraryFile(file) {
5418
- return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path11.sep}node_modules${path11.sep}`) || file.includes("/node_modules/") || file === __filename;
6154
+ return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path13.sep}node_modules${path13.sep}`) || file.includes("/node_modules/") || file === __filename;
5419
6155
  }
5420
6156
  function userSite(stack) {
5421
6157
  for (const line of (stack ?? "").split("\n").slice(1)) {
@@ -5463,7 +6199,7 @@ function playwrightTestInfoSource() {
5463
6199
  try {
5464
6200
  const packageJson = resolvePlaywrightPackageJson();
5465
6201
  if (!packageJson) return () => null;
5466
- const globals = (0, import_node_module2.createRequire)(packageJson)(path11.join(path11.dirname(packageJson), "lib", "globals.js"));
6202
+ const globals = (0, import_node_module2.createRequire)(packageJson)(path13.join(path13.dirname(packageJson), "lib", "globals.js"));
5467
6203
  const read = globals.currentTestInfo;
5468
6204
  if (typeof read !== "function") return () => null;
5469
6205
  return () => {
@@ -5492,15 +6228,15 @@ function countHandles() {
5492
6228
  async function settledHandles(start) {
5493
6229
  const end = countHandles();
5494
6230
  if (!Object.entries(end).some(([type, n]) => n > (start[type] ?? 0))) return end;
5495
- await new Promise((resolve3) => setTimeout(resolve3, 0));
6231
+ await new Promise((resolve5) => setTimeout(resolve5, 0));
5496
6232
  return countHandles();
5497
6233
  }
5498
6234
  function settleWithin(promise, ms) {
5499
6235
  let timer;
5500
6236
  return Promise.race([
5501
6237
  promise.catch(() => void 0),
5502
- new Promise((resolve3) => {
5503
- timer = setTimeout(resolve3, ms);
6238
+ new Promise((resolve5) => {
6239
+ timer = setTimeout(resolve5, ms);
5504
6240
  })
5505
6241
  ]).finally(() => clearTimeout(timer));
5506
6242
  }
@@ -5629,7 +6365,7 @@ var ResourceLedger = class {
5629
6365
  const census = this.census(null);
5630
6366
  if (this.resultsFile) {
5631
6367
  try {
5632
- fs8.appendFileSync(this.resultsFile, `${JSON.stringify(census)}
6368
+ fs11.appendFileSync(this.resultsFile, `${JSON.stringify(census)}
5633
6369
  `);
5634
6370
  } catch {
5635
6371
  }
@@ -6246,17 +6982,27 @@ function createSink() {
6246
6982
  pickOffered: false,
6247
6983
  userPick: null,
6248
6984
  pause: null,
6985
+ locatorCalls: /* @__PURE__ */ new Map(),
6986
+ stops: [],
6987
+ shownStops: /* @__PURE__ */ new Map(),
6988
+ released: [],
6989
+ releasedRequests: /* @__PURE__ */ new Set(),
6990
+ onReleasedRequestsEnded: null,
6991
+ releasedQuietSince: 0,
6249
6992
  locatorPages: new LocatorPageLog(),
6250
- codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map() },
6993
+ codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map(), cssFiles: /* @__PURE__ */ new Map(), cssKnown: /* @__PURE__ */ new Map() },
6251
6994
  serverCoverage: /* @__PURE__ */ new Map(),
6252
6995
  baseURL: null
6253
6996
  };
6254
6997
  }
6255
6998
  var MAX_CODE_REACH_PAGES = 16;
6256
6999
  var CODE_REACH_START_WAIT_MS = 2e3;
7000
+ function pageCoverageInWorker() {
7001
+ return browserCoverageInWorker() || cssCoverageInWorker();
7002
+ }
6257
7003
  function maybeStartCodeReach(page, alreadyOpen = false) {
6258
7004
  const sink = currentSink;
6259
- if (!sink || !browserCoverageInWorker() || isPageClosed(page)) return Promise.resolve(false);
7005
+ if (!sink || !pageCoverageInWorker() || isPageClosed(page)) return Promise.resolve(false);
6260
7006
  const known = sink.codeReach.sessions.get(page);
6261
7007
  if (known) return known.started;
6262
7008
  if (alreadyOpen) {
@@ -6269,9 +7015,9 @@ function maybeStartCodeReach(page, alreadyOpen = false) {
6269
7015
  return started;
6270
7016
  }
6271
7017
  async function startCodeReachOnOpenPages() {
6272
- if (!browserCoverageInWorker() || OPEN_PAGES.size === 0) return;
7018
+ if (!pageCoverageInWorker() || OPEN_PAGES.size === 0) return;
6273
7019
  const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
6274
- await Promise.race([started, new Promise((resolve3) => setTimeout(resolve3, CODE_REACH_START_WAIT_MS).unref())]);
7020
+ await Promise.race([started, new Promise((resolve5) => setTimeout(resolve5, CODE_REACH_START_WAIT_MS).unref())]);
6275
7021
  }
6276
7022
  function configuredCodeReachRoots() {
6277
7023
  const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
@@ -6285,7 +7031,7 @@ function configuredCodeReachRoots() {
6285
7031
  }
6286
7032
  } catch {
6287
7033
  }
6288
- return raw.split(path12.delimiter).filter(Boolean);
7034
+ return raw.split(path14.delimiter).filter(Boolean);
6289
7035
  }
6290
7036
  async function stopSinkCodeReach(sink, closing) {
6291
7037
  const stopping = [];
@@ -6312,30 +7058,36 @@ async function stopSinkCodeReach(sink, closing) {
6312
7058
  async function stopPageCodeReach(sink, page, session, roots) {
6313
7059
  try {
6314
7060
  if (!await session.started || isPageClosed(page)) return;
6315
- const entries = await stopCodeReach(page);
6316
- if (!entries) return;
6317
- const { reached, known } = await resolveCodeReachFunctions(entries, roots, pageMapFetcher(page), page.url(), {
7061
+ const { scripts, sheets } = await stopPageCoverage(page);
7062
+ const fetchMap = pageMapFetcher(page);
7063
+ const append = (into, from) => {
7064
+ for (const [file, ranges] of from) into.set(file, [...into.get(file) ?? [], ...ranges]);
7065
+ };
7066
+ if (sheets.length > 0) {
7067
+ const rules = await resolveCssCoverage(sheets, roots, fetchMap);
7068
+ append(sink.codeReach.cssFiles, rules.reached);
7069
+ append(sink.codeReach.cssKnown, rules.known);
7070
+ }
7071
+ if (!scripts) return;
7072
+ const { reached, known } = await resolveCodeReachFunctions(scripts, roots, fetchMap, page.url(), {
6318
7073
  known: browserFunctionsInWorker()
6319
7074
  });
6320
- for (const [file, functions] of reached) {
6321
- sink.codeReach.files.set(file, [...sink.codeReach.files.get(file) ?? [], ...functions]);
6322
- }
6323
- for (const [file, functions] of known) {
6324
- sink.codeReach.known.set(file, [...sink.codeReach.known.get(file) ?? [], ...functions]);
6325
- }
7075
+ append(sink.codeReach.files, reached);
7076
+ append(sink.codeReach.known, known);
6326
7077
  } catch {
6327
7078
  }
6328
7079
  }
6329
7080
  var currentSink = null;
6330
7081
  var KNOWN_FUNCTIONS_SENT = /* @__PURE__ */ new Map();
7082
+ var KNOWN_CSS_RULES_SENT = /* @__PURE__ */ new Map();
6331
7083
  var PENDING_PROBES = /* @__PURE__ */ new Set();
6332
7084
  async function drainPendingProbes(capMs) {
6333
7085
  if (PENDING_PROBES.size === 0) return;
6334
7086
  let cap;
6335
7087
  await Promise.race([
6336
7088
  Promise.allSettled(PENDING_PROBES),
6337
- new Promise((resolve3) => {
6338
- cap = setTimeout(resolve3, capMs);
7089
+ new Promise((resolve5) => {
7090
+ cap = setTimeout(resolve5, capMs);
6339
7091
  })
6340
7092
  ]);
6341
7093
  clearTimeout(cap);
@@ -6375,10 +7127,10 @@ async function readWebVitals(page) {
6375
7127
  const key = entry.name.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
6376
7128
  paint[key] = Math.round(entry.startTime);
6377
7129
  }
6378
- const readBuffered = (type, extra) => new Promise((resolve3) => {
7130
+ const readBuffered = (type, extra) => new Promise((resolve5) => {
6379
7131
  try {
6380
7132
  const PO = globalThis.PerformanceObserver;
6381
- if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve3(null);
7133
+ if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve5(null);
6382
7134
  const out = [];
6383
7135
  const po = new PO((list) => out.push(...list.getEntries()));
6384
7136
  po.observe({ type, buffered: true, ...extra });
@@ -6388,10 +7140,10 @@ async function readWebVitals(page) {
6388
7140
  po.disconnect();
6389
7141
  } catch {
6390
7142
  }
6391
- resolve3(out);
7143
+ resolve5(out);
6392
7144
  }, 0);
6393
7145
  } catch {
6394
- resolve3(null);
7146
+ resolve5(null);
6395
7147
  }
6396
7148
  });
6397
7149
  const [lcpRaw, shiftRaw, eventRaw, firstInputRaw] = await Promise.all([
@@ -6506,12 +7258,12 @@ var PAGE_INVENTORY_IN_PAGE_CAP = 2e3;
6506
7258
  var PAGE_INVENTORY_EVAL_TIMEOUT_MS = 2e3;
6507
7259
  var MAX_PAGE_INVENTORY_WINDOWS = 64;
6508
7260
  function withTimeout(promise, ms) {
6509
- return new Promise((resolve3, reject) => {
7261
+ return new Promise((resolve5, reject) => {
6510
7262
  const timer = setTimeout(() => reject(new Error("page inventory read timed out")), ms);
6511
7263
  promise.then(
6512
7264
  (value) => {
6513
7265
  clearTimeout(timer);
6514
- resolve3(value);
7266
+ resolve5(value);
6515
7267
  },
6516
7268
  (error) => {
6517
7269
  clearTimeout(timer);
@@ -6626,6 +7378,7 @@ async function maybeOpenPicker(sink, closing) {
6626
7378
  var INSTRUMENTED_PAGES = /* @__PURE__ */ new WeakSet();
6627
7379
  var OPEN_PAGES = /* @__PURE__ */ new Set();
6628
7380
  var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
7381
+ var OPEN_CONTEXTS = /* @__PURE__ */ new Set();
6629
7382
  var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
6630
7383
  var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
6631
7384
  var ACTION_METHOD_SET = new Set(ACTION_METHODS);
@@ -6746,8 +7499,20 @@ function recordLocatorPage(sink, page, locator, location, arrival) {
6746
7499
  } catch {
6747
7500
  }
6748
7501
  }
6749
- function pausesAt(pause, callerLocation) {
6750
- return !pause.finished && (pause.next || pause.set.matches(callerLocation));
7502
+ function knowLocatorCall(sink, page, target, location, origin) {
7503
+ if (!sink?.pause || !location) return null;
7504
+ let settle = () => {
7505
+ };
7506
+ const settled = new Promise((resolve5) => settle = resolve5);
7507
+ const call = { page, target, location, origin, picked: false, settled, settle };
7508
+ sink.locatorCalls.set(callSiteLine(location), call);
7509
+ return call;
7510
+ }
7511
+ function forgetLocatorCall(sink, call) {
7512
+ if (!sink || !call) return;
7513
+ call.settle();
7514
+ const key = callSiteLine(call.location);
7515
+ if (sink.locatorCalls.get(key) === call) sink.locatorCalls.delete(key);
6751
7516
  }
6752
7517
  function locatorSource(target, method, args) {
6753
7518
  try {
@@ -6757,41 +7522,219 @@ function locatorSource(target, method, args) {
6757
7522
  }
6758
7523
  return renderFailing({ method, args });
6759
7524
  }
6760
- async function pauseBefore(sink, page, target, callerLocation, action, origin) {
6761
- const pause = sink.pause;
7525
+ var PAGE_PAUSES = /* @__PURE__ */ new WeakMap();
7526
+ var PLAYWRIGHT_PAUSES = /* @__PURE__ */ new WeakMap();
7527
+ async function armBreakpoints(contexts) {
7528
+ const state = currentSink?.pause;
7529
+ if (!state || state.finished || state.set.size === 0) return;
7530
+ await Promise.all(
7531
+ [...contexts].map(
7532
+ (context) => context && watchStops(context, onDebuggerStop) ? steerDebugger(context, "pause") : void 0
7533
+ )
7534
+ );
7535
+ }
7536
+ function onDebuggerStop(context) {
7537
+ const details = contextDebugger(context)?.pausedDetails();
7538
+ if (!details || PLAYWRIGHT_PAUSES.has(context)) return;
7539
+ const sink = currentSink;
7540
+ const state = sink?.pause ?? null;
7541
+ void (async () => {
7542
+ let how = "resume";
7543
+ try {
7544
+ if (!sink || !state) return;
7545
+ const callSite = stopSite(sink, details.location);
7546
+ const pagePause = PAGE_PAUSES.get(context);
7547
+ const decided = pagePause ? { show: "page-pause" } : atStop(state, callSite);
7548
+ if ("go" in decided) {
7549
+ how = decided.go;
7550
+ return;
7551
+ }
7552
+ how = goOn(state, await showStop(sink, context, details.title, callSite ?? pagePause, decided.show));
7553
+ } catch {
7554
+ if (state) how = goOn(state);
7555
+ } finally {
7556
+ await steerDebugger(context, how);
7557
+ }
7558
+ })();
7559
+ }
7560
+ function stopSite(sink, location) {
7561
+ if (path14.dirname(path14.resolve(location.file)) !== __dirname) return stopCallSite(location);
7562
+ return inFlightCallSite([...sink.locatorCalls.values()].map((call) => call.location));
7563
+ }
7564
+ function stopPage(sink, context, call) {
7565
+ for (const page of [call?.page, sink.lastActivePage]) {
7566
+ if (page && !isPageClosed(page) && pageContext(page) === context) return page;
7567
+ }
7568
+ const open = context.pages().filter((page) => !isPageClosed(page));
7569
+ return open[open.length - 1] ?? null;
7570
+ }
7571
+ async function showStop(sink, context, title, location, reason) {
6762
7572
  const testInfo = sink.testInfo;
6763
- if (!pause || !testInfo || !callerLocation) return;
6764
- pause.next = false;
6765
- try {
6766
- const locator = locatorSource(target, origin.method, origin.args);
6767
- const onPick = async (picked) => {
6768
- const pick = {
6769
- failing: { method: origin.method, args: origin.args, rendered: locator, location: callerLocation },
7573
+ const call = sink.locatorCalls.get(callSiteLine(location));
7574
+ const page = stopPage(sink, context, call);
7575
+ if (!testInfo || !page) return void 0;
7576
+ const onPick = async (picked) => {
7577
+ if (call) {
7578
+ call.picked = true;
7579
+ const { method, args } = call.origin;
7580
+ const rendered = locatorSource(call.target, method, args);
7581
+ applyPickToSnapshots(sink.capturedLocators, {
7582
+ failing: { method, args, rendered, location: call.location },
6770
7583
  ...picked
6771
- };
6772
- applyPickToSnapshots(sink.capturedLocators, pick);
6773
- sink.probedLocations.add(callerLocation);
6774
- const place = callerLocation.slice(0, callerLocation.lastIndexOf(":"));
6775
- console.log(`[piwi] Locator picked at ${place}: ${picked.picked.locator}`);
6776
- await postPickToEditor(
6777
- process.env[PIWI_LOCAL_DEBUG_ENV.editorSend],
6778
- pickPayload(picked.picked.locator, callerLocation)
6779
- );
6780
- };
6781
- const choice = await internalCall(
7584
+ });
7585
+ sink.probedLocations.add(call.location);
7586
+ }
7587
+ console.log(`[piwi] Locator picked at ${callSiteLine(location)}: ${picked.picked.locator}`);
7588
+ await postPickToEditor(process.env[PIWI_LOCAL_DEBUG_ENV.editorSend], pickPayload(picked.picked.locator, location));
7589
+ };
7590
+ const record = { title, location, reason, startedAt: Date.now(), duration: 0 };
7591
+ sink.stops.push(record);
7592
+ const end = new AbortController();
7593
+ const shown = withTestTimeoutHeld(
7594
+ testInfo,
7595
+ () => internalCall(
6782
7596
  page,
6783
7597
  () => pauseHere(
6784
7598
  page,
6785
- testInfo,
6786
- { location: callerLocation, action, locator, target },
7599
+ {
7600
+ location,
7601
+ call: title,
7602
+ attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
7603
+ target: call?.page === page ? call.target : null
7604
+ },
6787
7605
  { fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG },
6788
- onPick
7606
+ onPick,
7607
+ recorderAt(page, testInfo, location, reason === "page-pause" ? "pause" : "breakpoint"),
7608
+ end.signal
6789
7609
  )
6790
- );
6791
- if (choice === "step") pause.next = true;
6792
- else if (choice === "finish") pause.finished = true;
6793
- } catch {
7610
+ )
7611
+ ).finally(() => {
7612
+ if (!end.signal.aborted) record.duration = Date.now() - record.startedAt;
7613
+ else if (end.signal.reason === "ended") releaseCall(sink, call?.settled ?? Promise.resolve());
7614
+ sink.shownStops.delete(end);
7615
+ });
7616
+ const heldAt = testTimeSlot(testInfo)?.elapsed ?? null;
7617
+ sink.shownStops.set(end, { page, record, done: shown.catch(() => void 0), heldAt });
7618
+ const answer = await shown;
7619
+ if (!end.signal.aborted) return answer;
7620
+ return end.signal.reason === "closed" ? "resume" : "finish";
7621
+ }
7622
+ var END_STOP_MS = 2e3;
7623
+ var RELEASED_CALL_MS = 2e3;
7624
+ var RELEASED_QUIET_MS = 200;
7625
+ async function endShownStops(sink, why, closing) {
7626
+ const ending = [];
7627
+ let heldAt = Infinity;
7628
+ for (const [end, { page, record, done, heldAt: stopHeldAt }] of sink.shownStops) {
7629
+ if (closing && closing.page !== page && (!closing.context || pageContext(page) !== closing.context)) continue;
7630
+ if (!end.signal.aborted) {
7631
+ record.duration = Date.now() - record.startedAt;
7632
+ end.abort(why);
7633
+ if (why === "ended" && stopHeldAt !== null) heldAt = Math.min(heldAt, stopHeldAt);
7634
+ }
7635
+ ending.push(done);
7636
+ }
7637
+ if (sink.testInfo && heldAt !== Infinity) restoreTestTime(sink.testInfo, heldAt);
7638
+ await settledWithin(ending, END_STOP_MS);
7639
+ }
7640
+ function releaseCall(sink, settled) {
7641
+ sink.releasedQuietSince = Date.now();
7642
+ sink.released.push(settled.then(() => void (sink.releasedQuietSince = Date.now())));
7643
+ }
7644
+ function releasedRequestsEnded(sink) {
7645
+ if (sink.releasedRequests.size === 0) return Promise.resolve();
7646
+ return new Promise((resolve5) => sink.onReleasedRequestsEnded = resolve5);
7647
+ }
7648
+ async function awaitReleasedCalls(sink) {
7649
+ if (sink.released.length === 0) return;
7650
+ const deadline = Date.now() + RELEASED_CALL_MS;
7651
+ await settledWithin(sink.released, RELEASED_CALL_MS);
7652
+ for (let now = Date.now(); now < deadline; now = Date.now()) {
7653
+ if (sink.releasedRequests.size > 0) {
7654
+ await settledWithin([releasedRequestsEnded(sink)], deadline - now);
7655
+ continue;
7656
+ }
7657
+ const quiet = now - sink.releasedQuietSince;
7658
+ if (quiet >= RELEASED_QUIET_MS) return;
7659
+ await new Promise((resolve5) => setTimeout(resolve5, Math.min(RELEASED_QUIET_MS - quiet, deadline - now)));
7660
+ }
7661
+ }
7662
+ async function settledWithin(pending, ms) {
7663
+ if (pending.length === 0) return;
7664
+ let timer;
7665
+ await Promise.race([Promise.allSettled(pending), new Promise((resolve5) => timer = setTimeout(resolve5, ms))]);
7666
+ clearTimeout(timer);
7667
+ }
7668
+ function endStopsWithTestFunction(testInfo, sink) {
7669
+ const callbacks = testInfo._onDidFinishTestFunctionCallbacks;
7670
+ if (!(callbacks instanceof Set)) return;
7671
+ const theirs = [...callbacks];
7672
+ callbacks.clear();
7673
+ callbacks.add(() => endShownStops(sink, "ended"));
7674
+ for (const callback of theirs) callbacks.add(callback);
7675
+ }
7676
+ function recorderAt(page, testInfo, location, reason) {
7677
+ if (!canRecordFromHere()) return void 0;
7678
+ return async (ended) => {
7679
+ console.log(`[piwi] Recording from ${callSiteLine(location)}: the editor writes the steps.`);
7680
+ const why = await recordFromHere({ page, testInfo, location, reason, ended });
7681
+ if (ended?.aborted) console.log("[piwi] The recording ended with the test.");
7682
+ else
7683
+ console.log(
7684
+ why ? `[piwi] Nothing was recorded: ${why}` : "[piwi] The recording ended: the test is paused again."
7685
+ );
7686
+ };
7687
+ }
7688
+ async function pauseInPlace(page, playwrightPause, location) {
7689
+ const sink = currentSink;
7690
+ const testInfo = sink?.testInfo;
7691
+ const context = pageContext(page);
7692
+ if (!sink || !testInfo || !location || !context || !contextDebugger(context)) {
7693
+ return leaveToPlaywright(context, playwrightPause);
7694
+ }
7695
+ if (!sink.pause && (!canRecordFromHere() || pauseSkipReason(pauseGateFromTestInfo(testInfo)))) {
7696
+ return leaveToPlaywright(context, playwrightPause);
7697
+ }
7698
+ sink.pause ??= { set: NO_BREAKPOINTS, step: false, finished: false };
7699
+ watchStops(context, onDebuggerStop);
7700
+ await steerDebugger(context, "pause");
7701
+ PAGE_PAUSES.set(context, location);
7702
+ try {
7703
+ await playwrightPause({ __testHookKeepTestTimeout: true });
7704
+ } finally {
7705
+ PAGE_PAUSES.delete(context);
7706
+ }
7707
+ }
7708
+ async function leaveToPlaywright(context, playwrightPause) {
7709
+ if (context) PLAYWRIGHT_PAUSES.set(context, (PLAYWRIGHT_PAUSES.get(context) ?? 0) + 1);
7710
+ try {
7711
+ await withInspector(() => playwrightPause());
7712
+ } finally {
7713
+ if (context) {
7714
+ const left = (PLAYWRIGHT_PAUSES.get(context) ?? 1) - 1;
7715
+ if (left > 0) PLAYWRIGHT_PAUSES.set(context, left);
7716
+ else PLAYWRIGHT_PAUSES.delete(context);
7717
+ }
7718
+ }
7719
+ }
7720
+ var PLAYWRIGHT_PAUSE = /* @__PURE__ */ Symbol.for("@piwitests/reporter:playwright-pause");
7721
+ function playwrightPauseOf(page) {
7722
+ const current = page.pause;
7723
+ if (typeof current !== "function") return null;
7724
+ const own = current[PLAYWRIGHT_PAUSE] ?? current;
7725
+ const proto = Object.getPrototypeOf(page);
7726
+ const theirs = proto?.pause;
7727
+ if (proto && typeof theirs === "function") {
7728
+ if (!theirs[PLAYWRIGHT_PAUSE]) {
7729
+ const wrapped = function(options) {
7730
+ return leaveToPlaywright(pageContext(this), () => theirs.call(this, options));
7731
+ };
7732
+ wrapped[PLAYWRIGHT_PAUSE] = theirs;
7733
+ proto.pause = wrapped;
7734
+ }
7735
+ markPausesWrapped();
6794
7736
  }
7737
+ return (options) => own.call(page, options);
6795
7738
  }
6796
7739
  function wrapLocator(page, locator, originMethod, originArgs) {
6797
7740
  return new Proxy(locator, {
@@ -6814,36 +7757,35 @@ function wrapLocator(page, locator, originMethod, originArgs) {
6814
7757
  const isNot = Boolean(callArgs[1]?.isNot);
6815
7758
  const callerLocation = sink ? captureCallerLocation() : null;
6816
7759
  if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
6817
- if (sink?.pause && pausesAt(sink.pause, callerLocation)) {
6818
- await pauseBefore(sink, page, target, callerLocation, assertionLabel(expression, isNot), {
7760
+ const call = knowLocatorCall(sink, page, target, callerLocation, { method: originMethod, args: originArgs });
7761
+ try {
7762
+ if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
7763
+ return await callMethod(target, prop, callArgs);
7764
+ }
7765
+ sink.lastActivePage = page;
7766
+ const used = {
6819
7767
  method: originMethod,
6820
- args: originArgs
6821
- });
6822
- }
6823
- if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
6824
- return callMethod(target, prop, callArgs);
6825
- }
6826
- sink.lastActivePage = page;
6827
- const used = {
6828
- method: originMethod,
6829
- args: originArgs,
6830
- raw: `${originMethod}(${JSON.stringify(originArgs)})`
6831
- };
6832
- const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
6833
- let seq = -1;
6834
- if (!alreadyProbed) {
6835
- seq = sink.capturedLocators.length;
6836
- sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
6837
- }
6838
- const result = await callMethod(target, prop, callArgs);
6839
- const matches = result?.matches;
6840
- if (matches === true && !alreadyProbed) {
6841
- if (callerLocation) sink.probedLocations.add(callerLocation);
6842
- startElementCapture(sink, page, target, seq, callerLocation, used);
6843
- } else if (matches === false) {
6844
- sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
7768
+ args: originArgs,
7769
+ raw: `${originMethod}(${JSON.stringify(originArgs)})`
7770
+ };
7771
+ const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
7772
+ let seq = -1;
7773
+ if (!alreadyProbed) {
7774
+ seq = sink.capturedLocators.length;
7775
+ sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
7776
+ }
7777
+ const result = await callMethod(target, prop, callArgs);
7778
+ const matches = result?.matches;
7779
+ if (matches === true && !alreadyProbed && !call?.picked) {
7780
+ if (callerLocation) sink.probedLocations.add(callerLocation);
7781
+ startElementCapture(sink, page, target, seq, callerLocation, used);
7782
+ } else if (matches === false) {
7783
+ sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
7784
+ }
7785
+ return result;
7786
+ } finally {
7787
+ forgetLocatorCall(sink, call);
6845
7788
  }
6846
- return result;
6847
7789
  };
6848
7790
  }
6849
7791
  if (!ACTION_METHOD_SET.has(prop)) return original;
@@ -6858,9 +7800,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
6858
7800
  args: originArgs,
6859
7801
  raw: `${originMethod}(${JSON.stringify(originArgs)})`
6860
7802
  };
6861
- if (sink.pause && pausesAt(sink.pause, callerLocation)) {
6862
- await pauseBefore(sink, page, target, callerLocation, String(prop), used);
6863
- }
7803
+ const call = knowLocatorCall(sink, page, target, callerLocation, used);
6864
7804
  const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
6865
7805
  let seq = -1;
6866
7806
  if (!alreadyProbed) {
@@ -6878,8 +7818,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
6878
7818
  } catch (error) {
6879
7819
  sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
6880
7820
  throw error;
7821
+ } finally {
7822
+ forgetLocatorCall(sink, call);
6881
7823
  }
6882
- if (!alreadyProbed) {
7824
+ if (!alreadyProbed && !call?.picked) {
6883
7825
  if (callerLocation) sink.probedLocations.add(callerLocation);
6884
7826
  startElementCapture(sink, page, target, seq, callerLocation, used);
6885
7827
  }
@@ -6932,12 +7874,17 @@ function instrumentPage(page) {
6932
7874
  }
6933
7875
  const ctx = pageContext(page);
6934
7876
  if (ctx) instrumentContext(ctx);
7877
+ const playwrightPause = playwrightPauseOf(page);
7878
+ if (playwrightPause) {
7879
+ page.pause = () => pauseInPlace(page, playwrightPause, captureCallerLocation());
7880
+ }
6935
7881
  if (typeof page.close === "function") {
6936
7882
  const originalClose = page.close.bind(page);
6937
7883
  page.close = async (...args) => {
6938
7884
  await drainPendingProbes(1e3);
6939
7885
  const sink = currentSink;
6940
7886
  if (sink) {
7887
+ await endShownStops(sink, "closed", { page });
6941
7888
  await stashPageState(sink, { page });
6942
7889
  await maybeOpenPicker(sink, { page });
6943
7890
  await stopSinkCodeReach(sink, { page });
@@ -7026,8 +7973,18 @@ function instrumentPage(page) {
7026
7973
  } catch {
7027
7974
  }
7028
7975
  });
7976
+ page.on("request", (request) => {
7977
+ const sink = currentSink;
7978
+ if (!sink || sink.released.length === 0) return;
7979
+ sink.releasedRequests.add(request);
7980
+ sink.releasedQuietSince = Date.now();
7981
+ });
7029
7982
  const onRequestEnd = (request, failed) => {
7030
7983
  const sink = currentSink;
7984
+ if (sink?.releasedRequests.delete(request)) {
7985
+ sink.releasedQuietSince = Date.now();
7986
+ if (sink.releasedRequests.size === 0) sink.onReleasedRequestsEnded?.();
7987
+ }
7031
7988
  if (!sink || RECORDED_OPEN.has(request)) return;
7032
7989
  sink.lastActivePage = page;
7033
7990
  const response = sink.openResponses.get(request) ?? null;
@@ -7133,6 +8090,10 @@ async function awaitFlakeConditions() {
7133
8090
  function instrumentContext(context) {
7134
8091
  if (!context || INSTRUMENTED_CONTEXTS.has(context)) return;
7135
8092
  INSTRUMENTED_CONTEXTS.add(context);
8093
+ if (typeof context.once === "function") {
8094
+ OPEN_CONTEXTS.add(context);
8095
+ context.once("close", () => OPEN_CONTEXTS.delete(context));
8096
+ }
7136
8097
  observeOwnApiRequests(context);
7137
8098
  if (process.env.PIWI_CAPTURE_LOCATORS !== "false" && typeof context.addInitScript === "function") {
7138
8099
  void Promise.resolve(context.addInitScript({ content: PROBE_INIT_SCRIPT })).catch(() => {
@@ -7152,6 +8113,7 @@ function instrumentContext(context) {
7152
8113
  await drainPendingProbes(1e3);
7153
8114
  const sink = currentSink;
7154
8115
  if (sink) {
8116
+ await endShownStops(sink, "closed", { context });
7155
8117
  await stashPageState(sink, { context });
7156
8118
  await maybeOpenPicker(sink, { context });
7157
8119
  await stopSinkCodeReach(sink, { context });
@@ -7168,6 +8130,7 @@ function patchBrowser(browser) {
7168
8130
  browser.newPage = async (...args) => {
7169
8131
  const page = await originalNewPage(...args);
7170
8132
  instrumentPage(page);
8133
+ await armBreakpoints([pageContext(page)]);
7171
8134
  await awaitFlakeConditions();
7172
8135
  await maybeStartCodeReach(page);
7173
8136
  return page;
@@ -7176,6 +8139,7 @@ function patchBrowser(browser) {
7176
8139
  browser.newContext = async (...args) => {
7177
8140
  const context = await originalNewContext(...args);
7178
8141
  instrumentContext(context);
8142
+ await armBreakpoints([context]);
7179
8143
  return context;
7180
8144
  };
7181
8145
  if (typeof browser.close === "function") {
@@ -7227,14 +8191,20 @@ async function flushSink(sink, testInfo) {
7227
8191
  let drainDeadline;
7228
8192
  await Promise.race([
7229
8193
  Promise.allSettled(sink.capturePromises),
7230
- new Promise((resolve3) => {
7231
- drainDeadline = setTimeout(resolve3, 2e3);
8194
+ new Promise((resolve5) => {
8195
+ drainDeadline = setTimeout(resolve5, 2e3);
7232
8196
  })
7233
8197
  ]);
7234
8198
  clearTimeout(drainDeadline);
7235
8199
  const page = sink.lastActivePage;
7236
8200
  const pageReadable = page !== null && !isPageClosed(page);
7237
8201
  if (pageReadable) await maybeOpenPicker(sink);
8202
+ if (sink.stops.length > 0) {
8203
+ await testInfo.attach(ATTACHMENT_NAMES.pauses, {
8204
+ contentType: "application/json",
8205
+ body: Buffer.from(JSON.stringify(sink.stops))
8206
+ });
8207
+ }
7238
8208
  if (sink.capturedLocators.length > 0) {
7239
8209
  await testInfo.attach(ATTACHMENT_NAMES.locators, {
7240
8210
  contentType: "application/json",
@@ -7265,6 +8235,21 @@ async function flushSink(sink, testInfo) {
7265
8235
  body: Buffer.from(JSON.stringify(part))
7266
8236
  });
7267
8237
  }
8238
+ if (cssCoverageInWorker()) {
8239
+ const rules = sink.codeReach.cssFiles.size > 0 ? browserFunctionCoverage(sink.codeReach.cssFiles) : null;
8240
+ if (rules) {
8241
+ await testInfo.attach(ATTACHMENT_NAMES.cssCoverage, {
8242
+ contentType: "application/json",
8243
+ body: Buffer.from(JSON.stringify(rules))
8244
+ });
8245
+ }
8246
+ for (const part of newKnownFunctions(sink.codeReach.cssKnown, KNOWN_CSS_RULES_SENT)) {
8247
+ await testInfo.attach(ATTACHMENT_NAMES.cssRules, {
8248
+ contentType: "application/json",
8249
+ body: Buffer.from(JSON.stringify(part))
8250
+ });
8251
+ }
8252
+ }
7268
8253
  if (sink.serverCoverage.size > 0) {
7269
8254
  const requests = {};
7270
8255
  for (const [origin, ids] of sink.serverCoverage) requests[origin] = [...ids];
@@ -7435,6 +8420,7 @@ var piwiFixtures = {
7435
8420
  // never double-wraps a page the browser patch already instrumented.
7436
8421
  page: async ({ page }, use) => {
7437
8422
  instrumentPage(page);
8423
+ await armBreakpoints([pageContext(page)]);
7438
8424
  await awaitFlakeConditions();
7439
8425
  await maybeStartCodeReach(page);
7440
8426
  await use(page);
@@ -7455,8 +8441,9 @@ var piwiFixtures = {
7455
8441
  if (pauseSet.size) {
7456
8442
  const reason = pauseSkipReason(pauseGateFromTestInfo(testInfo));
7457
8443
  if (reason) logPauseSkipOnce(reason);
7458
- else sink.pause = { set: pauseSet, next: false, finished: false };
8444
+ else sink.pause = { set: pauseSet, step: false, finished: false };
7459
8445
  }
8446
+ if (sink.pause || canRecordFromHere()) endStopsWithTestFunction(testInfo, sink);
7460
8447
  if (isFlakeMode()) {
7461
8448
  const plan = loadFlakePlan();
7462
8449
  const role = flakeRoleForTest(plan, {
@@ -7481,9 +8468,12 @@ var piwiFixtures = {
7481
8468
  activeResourceLedger()?.testStarted(testInfo);
7482
8469
  currentSink = sink;
7483
8470
  try {
8471
+ await armBreakpoints(OPEN_CONTEXTS);
7484
8472
  await startCodeReachOnOpenPages();
7485
8473
  await use();
7486
8474
  } finally {
8475
+ await endShownStops(sink, "ended");
8476
+ await awaitReleasedCalls(sink);
7487
8477
  currentSink = null;
7488
8478
  if (sink.probeItem) {
7489
8479
  const applied = sink.probeInterception?.applied() ?? false;