@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.
- package/dist/cli/index.js +1520 -1307
- package/dist/global-setup-module.js +203 -63
- package/dist/index.d.ts +36 -2
- package/dist/index.js +1973 -584
- package/dist/internal/capture/attachments.d.ts +3 -0
- package/dist/internal/capture/attachments.js +4 -1
- package/dist/internal/capture/capture-fixtures.js +1363 -373
- package/dist/internal/capture/pick-on-failure.d.ts +6 -3
- package/dist/internal/capture/pick-on-failure.js +49 -30
- package/package.json +5 -5
- package/templates/skills/run-the-right-tests/SKILL.md +19 -8
- package/templates/skills/setup-piwi/SKILL.md +4 -1
|
@@ -58,7 +58,7 @@ __export(capture_fixtures_exports, {
|
|
|
58
58
|
stopResourceLedger: () => stopResourceLedger
|
|
59
59
|
});
|
|
60
60
|
module.exports = __toCommonJS(capture_fixtures_exports);
|
|
61
|
-
var
|
|
61
|
+
var path14 = __toESM(require("path"));
|
|
62
62
|
var import_node_zlib2 = require("zlib");
|
|
63
63
|
|
|
64
64
|
// ../picker-dom/src/probe.ts
|
|
@@ -517,8 +517,8 @@ function installPickerOverlay(arg) {
|
|
|
517
517
|
return root && root !== doc && root.host ? root.host : null;
|
|
518
518
|
};
|
|
519
519
|
const targetOf = (e) => {
|
|
520
|
-
const
|
|
521
|
-
const first =
|
|
520
|
+
const path15 = typeof e.composedPath === "function" ? e.composedPath() : null;
|
|
521
|
+
const first = path15 && path15.length ? path15[0] : null;
|
|
522
522
|
return first && first.nodeType === 1 ? first : e.target;
|
|
523
523
|
};
|
|
524
524
|
const buildChain = (raw) => {
|
|
@@ -1222,8 +1222,11 @@ function showPauseBar(arg) {
|
|
|
1222
1222
|
};
|
|
1223
1223
|
const buttons = [
|
|
1224
1224
|
["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
|
|
1225
|
-
["Step", "step", "Pause again
|
|
1225
|
+
["Step", "step", "Pause again before the next call", false],
|
|
1226
1226
|
["Pick a locator", "pick", "Pick an element on the page and confirm a locator for it", false],
|
|
1227
|
+
...arg.record ? [
|
|
1228
|
+
["Record here", "record", "Record steps in this page: the editor writes them before this line", false]
|
|
1229
|
+
] : [],
|
|
1227
1230
|
["Finish", "finish", "Run on and pause no more in this test", false]
|
|
1228
1231
|
];
|
|
1229
1232
|
for (const [label, choice, hint, primary] of buttons) {
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1989
|
-
return typeof
|
|
1991
|
+
function isElementPath(path15) {
|
|
1992
|
+
return typeof path15 === "string" && path15.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path15);
|
|
1990
1993
|
}
|
|
1991
1994
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
1992
1995
|
function elementRef(v) {
|
|
1993
1996
|
if (!v || typeof v !== "object") return null;
|
|
1994
|
-
const { path:
|
|
1995
|
-
if (!isElementPath(
|
|
1996
|
-
return { path:
|
|
1997
|
+
const { path: path15, label } = v;
|
|
1998
|
+
if (!isElementPath(path15)) return null;
|
|
1999
|
+
return { path: path15, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
|
|
1997
2000
|
}
|
|
1998
2001
|
function textPoint(v) {
|
|
1999
2002
|
if (!v || typeof v !== "object") return null;
|
|
2000
|
-
const { path:
|
|
2003
|
+
const { path: path15, offset } = v;
|
|
2001
2004
|
const at = offsetOf(offset);
|
|
2002
|
-
return isElementPath(
|
|
2005
|
+
return isElementPath(path15) && at !== null ? { path: path15, offset: at } : null;
|
|
2003
2006
|
}
|
|
2004
2007
|
function sanitizePageInteraction(raw) {
|
|
2005
2008
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -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
|
|
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
|
|
2796
|
+
var path4 = __toESM(require("path"));
|
|
2783
2797
|
function configDirFromConfig(config) {
|
|
2784
|
-
if (config?.configFile) return
|
|
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
|
|
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
|
|
2800
|
-
const escaped =
|
|
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
|
|
2837
|
+
let path15;
|
|
2824
2838
|
try {
|
|
2825
|
-
|
|
2839
|
+
path15 = new URL(url).pathname;
|
|
2826
2840
|
} catch {
|
|
2827
|
-
|
|
2841
|
+
path15 = url.split("?")[0] ?? url;
|
|
2828
2842
|
}
|
|
2829
|
-
return patternToRegex(pattern).test(
|
|
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
|
|
2852
|
-
if (
|
|
2865
|
+
const path15 = process.env[PIWI_PROBE_ENV.plan];
|
|
2866
|
+
if (path15) {
|
|
2853
2867
|
try {
|
|
2854
|
-
planCache = JSON.parse(
|
|
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
|
|
2879
|
-
if (!
|
|
2892
|
+
const path15 = process.env[PIWI_PROBE_ENV.results];
|
|
2893
|
+
if (!path15) return;
|
|
2880
2894
|
try {
|
|
2881
|
-
|
|
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((
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
3653
|
-
if (!
|
|
3654
|
-
if (/(^|\/)node_modules\//.test(
|
|
3655
|
-
return
|
|
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(
|
|
3658
|
-
const file = { path:
|
|
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
|
|
3678
|
-
if (
|
|
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(
|
|
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
|
|
3813
|
+
const add2 = (map, lineOffset, columnOffset) => {
|
|
3800
3814
|
if (map.sections) {
|
|
3801
3815
|
for (const section of map.sections) {
|
|
3802
|
-
if (!
|
|
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
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
|
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 ? `${
|
|
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
|
-
|
|
4126
|
+
var PICK_ENDED = "__piwiPickEnded";
|
|
4127
|
+
async function cleanupPicker(page, ended = false) {
|
|
4113
4128
|
try {
|
|
4114
|
-
await page.evaluate(
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
4305
|
-
let rel = absolute ?
|
|
4306
|
-
rel = rel.split(
|
|
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
|
|
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) :
|
|
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
|
-
|
|
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
|
|
4347
|
-
|
|
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
|
|
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,
|
|
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((
|
|
4416
|
-
timer = setTimeout(() =>
|
|
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:
|
|
4424
|
-
action: point.
|
|
4425
|
-
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.
|
|
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
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
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(([
|
|
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
|
|
4485
|
-
if (
|
|
4486
|
-
let named = sent.get(
|
|
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(
|
|
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:
|
|
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
|
|
4989
|
+
var path10 = __toESM(require("path"));
|
|
4508
4990
|
var os3 = __toESM(require("os"));
|
|
4509
|
-
var
|
|
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
|
|
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 =
|
|
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
|
|
4641
|
-
var
|
|
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:
|
|
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
|
|
4690
|
-
const global = Array.isArray(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 =
|
|
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
|
|
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 (
|
|
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 =
|
|
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
|
|
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
|
|
4825
|
-
const listed = into.get(
|
|
5219
|
+
const path15 = relativeToRepo(file, roots.repoRoot);
|
|
5220
|
+
const listed = into.get(path15);
|
|
4826
5221
|
if (listed) listed.push(...functions);
|
|
4827
|
-
else into.set(
|
|
5222
|
+
else into.set(path15, [...functions]);
|
|
4828
5223
|
};
|
|
4829
|
-
const
|
|
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)
|
|
5251
|
+
if (reached.size > 0) add2(resolution.file, [...reached.values()].flat());
|
|
4857
5252
|
} else if (scriptRan(covered, text)) {
|
|
4858
|
-
|
|
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)
|
|
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 ?
|
|
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((
|
|
4980
|
-
timer = setTimeout(() =>
|
|
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
|
-
|
|
5047
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
5083
|
-
|
|
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
|
|
5091
|
-
var
|
|
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
|
|
5099
|
-
var
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(`${
|
|
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)(
|
|
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((
|
|
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((
|
|
5503
|
-
timer = setTimeout(
|
|
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
|
-
|
|
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 || !
|
|
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 (!
|
|
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((
|
|
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(
|
|
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
|
|
6316
|
-
|
|
6317
|
-
const
|
|
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
|
-
|
|
6321
|
-
|
|
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((
|
|
6338
|
-
cap = setTimeout(
|
|
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((
|
|
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
|
|
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
|
-
|
|
7143
|
+
resolve5(out);
|
|
6392
7144
|
}, 0);
|
|
6393
7145
|
} catch {
|
|
6394
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
6750
|
-
|
|
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
|
-
|
|
6761
|
-
|
|
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
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
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
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
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
|
-
|
|
6786
|
-
|
|
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
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
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
|
-
|
|
6818
|
-
|
|
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
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
if (matches === true && !alreadyProbed) {
|
|
6841
|
-
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
6842
|
-
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
6843
|
-
} else if (matches === false) {
|
|
6844
|
-
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
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
|
-
|
|
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((
|
|
7231
|
-
drainDeadline = setTimeout(
|
|
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,
|
|
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;
|