@piwitests/reporter 0.39.0 → 0.40.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/README.md +3 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +469 -81
- package/dist/internal/capture/attachments.d.ts +1 -0
- package/dist/internal/capture/attachments.js +1 -0
- package/dist/internal/capture/capture-fixtures.d.ts +47 -1
- package/dist/internal/capture/capture-fixtures.js +309 -73
- package/dist/internal/capture/locator-healing.js +7 -1
- package/dist/internal/capture/pick-on-failure.js +7 -1
- package/package.json +1 -1
|
@@ -31,11 +31,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var capture_fixtures_exports = {};
|
|
32
32
|
__export(capture_fixtures_exports, {
|
|
33
33
|
CAPTURED_ATTRS_ARG: () => CAPTURED_ATTRS_ARG,
|
|
34
|
+
LocatorPageLog: () => LocatorPageLog,
|
|
34
35
|
ariaSnapshotBestEffort: () => ariaSnapshotBestEffort,
|
|
35
36
|
ariaSnapshotJSONBestEffort: () => ariaSnapshotJSONBestEffort,
|
|
37
|
+
boxCaptureFrames: () => boxCaptureFrames,
|
|
36
38
|
buildPageState: () => buildPageState,
|
|
39
|
+
callMethod: () => callMethod,
|
|
37
40
|
computeCoreVitals: () => computeCoreVitals,
|
|
38
41
|
extendPiwiFixtures: () => extendPiwiFixtures,
|
|
42
|
+
internalCall: () => internalCall,
|
|
43
|
+
noteLocatorCall: () => noteLocatorCall,
|
|
44
|
+
noteNavigation: () => noteNavigation,
|
|
39
45
|
piwiFixtures: () => piwiFixtures,
|
|
40
46
|
probeElementAttrs: () => probeElementAttrs
|
|
41
47
|
});
|
|
@@ -44,7 +50,30 @@ var import_node_zlib2 = require("zlib");
|
|
|
44
50
|
|
|
45
51
|
// ../picker-dom/src/probe.ts
|
|
46
52
|
function probeElementAttrs(el, arg) {
|
|
47
|
-
const { keep, tagRoles, inputRoles, roleSources, includeStructural
|
|
53
|
+
const { keep, tagRoles, inputRoles, roleSources, includeStructural } = arg;
|
|
54
|
+
const FIELD_TAGS = ["input", "select", "textarea"];
|
|
55
|
+
const textWithout = (root, skip) => {
|
|
56
|
+
if (root === skip) return "";
|
|
57
|
+
if (root.nodeType === 3) return root.nodeValue || "";
|
|
58
|
+
let text = "";
|
|
59
|
+
const kids = root.childNodes || [];
|
|
60
|
+
for (let i = 0; i < kids.length; i++) text += textWithout(kids[i], skip);
|
|
61
|
+
return text;
|
|
62
|
+
};
|
|
63
|
+
const labelTextOf = (n) => {
|
|
64
|
+
try {
|
|
65
|
+
const ids = (n.getAttribute("aria-labelledby") || "").split(/\s+/).filter(Boolean);
|
|
66
|
+
let text = "";
|
|
67
|
+
for (const id of ids) {
|
|
68
|
+
const ref = n.ownerDocument.getElementById(id);
|
|
69
|
+
if (ref) text += ` ${textWithout(ref, n)}`;
|
|
70
|
+
}
|
|
71
|
+
if (!text.trim() && n.labels && n.labels.length > 0) text = textWithout(n.labels[0], n);
|
|
72
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 120) || null;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
48
77
|
const attrMap = {};
|
|
49
78
|
for (const key of keep) {
|
|
50
79
|
const v = el.getAttribute(key) ?? el[key];
|
|
@@ -124,10 +153,18 @@ function probeElementAttrs(el, arg) {
|
|
|
124
153
|
const targetRole = roleOf(el);
|
|
125
154
|
const targetLevel = targetRole === "heading" ? levelOf(el) : null;
|
|
126
155
|
const nameOf = (n) => {
|
|
156
|
+
const labelledBy = !!n.getAttribute("aria-labelledby");
|
|
157
|
+
const isField = FIELD_TAGS.indexOf((n.tagName || "").toLowerCase()) !== -1;
|
|
158
|
+
const lt = labelledBy || isField ? labelTextOf(n) : null;
|
|
159
|
+
if (lt && labelledBy) return lt;
|
|
127
160
|
const al = n.getAttribute("aria-label");
|
|
128
161
|
if (al) return al;
|
|
129
|
-
|
|
130
|
-
|
|
162
|
+
if (isField) {
|
|
163
|
+
if (lt) return lt;
|
|
164
|
+
} else {
|
|
165
|
+
const txt = (n.textContent || "").replace(/\s+/g, " ").trim();
|
|
166
|
+
if (txt) return txt;
|
|
167
|
+
}
|
|
131
168
|
return n.getAttribute("title") || n.getAttribute("placeholder") || null;
|
|
132
169
|
};
|
|
133
170
|
const targetName = nameOf(el);
|
|
@@ -319,16 +356,7 @@ function probeElementAttrs(el, arg) {
|
|
|
319
356
|
}
|
|
320
357
|
}
|
|
321
358
|
const hasLabel = !!(el.labels && el.labels.length > 0);
|
|
322
|
-
|
|
323
|
-
if (includeLabelText) {
|
|
324
|
-
labelText = null;
|
|
325
|
-
try {
|
|
326
|
-
const label = hasLabel ? el.labels[0] : null;
|
|
327
|
-
labelText = (label && label.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120) || null;
|
|
328
|
-
} catch {
|
|
329
|
-
labelText = null;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
359
|
+
const labelText = labelTextOf(el);
|
|
332
360
|
return {
|
|
333
361
|
tagName: el.tagName?.toLowerCase?.() ?? "unknown",
|
|
334
362
|
attributes: attrMap,
|
|
@@ -340,7 +368,7 @@ function probeElementAttrs(el, arg) {
|
|
|
340
368
|
y: Math.round(r.y + r.height / 2)
|
|
341
369
|
},
|
|
342
370
|
hasLabel,
|
|
343
|
-
|
|
371
|
+
labelText,
|
|
344
372
|
selectorCounts,
|
|
345
373
|
...includeStructural ? { rolePosition, ancestors } : {}
|
|
346
374
|
};
|
|
@@ -1311,9 +1339,15 @@ function generateAlternatives(attrs) {
|
|
|
1311
1339
|
}
|
|
1312
1340
|
function approximateAccessibleName(attrs) {
|
|
1313
1341
|
const a = attrs.attributes;
|
|
1342
|
+
const labelText = attrs.labelText || null;
|
|
1343
|
+
if (a["aria-labelledby"] && labelText) return labelText;
|
|
1314
1344
|
const ariaLabel = a["aria-label"];
|
|
1315
1345
|
if (ariaLabel) return ariaLabel;
|
|
1316
|
-
if (attrs.
|
|
1346
|
+
if (["input", "select", "textarea"].includes(attrs.tagName)) {
|
|
1347
|
+
if (labelText) return labelText;
|
|
1348
|
+
} else if (attrs.textContent) {
|
|
1349
|
+
return attrs.textContent;
|
|
1350
|
+
}
|
|
1317
1351
|
const title = a["title"];
|
|
1318
1352
|
if (title) return title;
|
|
1319
1353
|
const placeholder = a["placeholder"];
|
|
@@ -1743,6 +1777,7 @@ var ATTACHMENT_NAMES = {
|
|
|
1743
1777
|
dialogs: "piwi-dialogs",
|
|
1744
1778
|
network: "piwi-network",
|
|
1745
1779
|
pageInventory: "piwi-page-inventory",
|
|
1780
|
+
locatorPages: "piwi-locator-pages",
|
|
1746
1781
|
webVitals: "piwi-web-vitals",
|
|
1747
1782
|
locatorSuggestion: "piwi-locator-suggestion",
|
|
1748
1783
|
pageState: "piwi-page-state",
|
|
@@ -1869,6 +1904,94 @@ function inventoryPageKey(url) {
|
|
|
1869
1904
|
}
|
|
1870
1905
|
}
|
|
1871
1906
|
|
|
1907
|
+
// ../core/src/page-key.ts
|
|
1908
|
+
function normalizeRoute(url) {
|
|
1909
|
+
try {
|
|
1910
|
+
const parsed = new URL(url);
|
|
1911
|
+
let pathname = parsed.pathname;
|
|
1912
|
+
pathname = pathname.replace(/\/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+(?=\/|$)/g, "/:jwt");
|
|
1913
|
+
pathname = pathname.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?=\/|$)/gi, "/:uuid");
|
|
1914
|
+
pathname = pathname.replace(/\/[0-9ABCDEFGHJKMNPQRSTVWXYZ]{26}(?=\/|$)/gi, "/:ulid");
|
|
1915
|
+
pathname = pathname.replace(/\/[A-Za-z0-9_-]{40,}(?=\/|$)/g, "/:token");
|
|
1916
|
+
pathname = pathname.replace(/\/[0-9a-f]{32,}(?=\/|$)/gi, "/:token");
|
|
1917
|
+
pathname = pathname.replace(/\/\d+(?=\/|$)/g, "/:id");
|
|
1918
|
+
let queryString = "";
|
|
1919
|
+
if (parsed.search) {
|
|
1920
|
+
const params = new URLSearchParams(parsed.search);
|
|
1921
|
+
const redacted = new URLSearchParams();
|
|
1922
|
+
for (const [key] of params) {
|
|
1923
|
+
redacted.set(key, "<redacted>");
|
|
1924
|
+
}
|
|
1925
|
+
queryString = "?" + redacted.toString();
|
|
1926
|
+
}
|
|
1927
|
+
return `${pathname}${queryString}`;
|
|
1928
|
+
} catch {
|
|
1929
|
+
return url;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
var PATH_ANCHOR_HOST = "piwi.invalid";
|
|
1933
|
+
function pageKey(url) {
|
|
1934
|
+
const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(url);
|
|
1935
|
+
if (hasScheme && !/^https?:\/\//i.test(url)) return null;
|
|
1936
|
+
const absolute = /^https?:\/\//i.test(url) ? url : `http://${PATH_ANCHOR_HOST}${url.startsWith("/") ? "" : "/"}${url}`;
|
|
1937
|
+
const normalized = normalizeRoute(absolute);
|
|
1938
|
+
const q = normalized.indexOf("?");
|
|
1939
|
+
return q < 0 ? normalized : normalized.slice(0, q);
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/internal/capture/locator-pages.ts
|
|
1943
|
+
var MAX_LOCATOR_PAGE_ENTRIES = 1e3;
|
|
1944
|
+
var MAX_LOCATOR_CHARS = 1e3;
|
|
1945
|
+
var NON_INTERACTIONS = /* @__PURE__ */ new Set(["waitFor"]);
|
|
1946
|
+
var interactions = /* @__PURE__ */ new WeakMap();
|
|
1947
|
+
function noteNavigation(page) {
|
|
1948
|
+
interactions.set(page, 0);
|
|
1949
|
+
}
|
|
1950
|
+
function isOnArrival(page) {
|
|
1951
|
+
return (interactions.get(page) ?? 0) === 0;
|
|
1952
|
+
}
|
|
1953
|
+
function noteLocatorCall(page, method, assertion = false) {
|
|
1954
|
+
const arrival = isOnArrival(page);
|
|
1955
|
+
if (!assertion && !NON_INTERACTIONS.has(method)) interactions.set(page, (interactions.get(page) ?? 0) + 1);
|
|
1956
|
+
return arrival;
|
|
1957
|
+
}
|
|
1958
|
+
function pageOf(url) {
|
|
1959
|
+
const key = pageKey(url);
|
|
1960
|
+
if (key === null) return null;
|
|
1961
|
+
try {
|
|
1962
|
+
const origin = new URL(url).origin;
|
|
1963
|
+
return origin && origin !== "null" ? { origin, page: key } : null;
|
|
1964
|
+
} catch {
|
|
1965
|
+
return null;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
var LocatorPageLog = class {
|
|
1969
|
+
constructor() {
|
|
1970
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
1971
|
+
}
|
|
1972
|
+
/** Record one call. Silently skipped without a call site, off an http(s) page, or past the limits. */
|
|
1973
|
+
record(call) {
|
|
1974
|
+
if (!call.location || !call.locator || call.locator.length > MAX_LOCATOR_CHARS) return;
|
|
1975
|
+
const where = pageOf(call.url);
|
|
1976
|
+
if (!where) return;
|
|
1977
|
+
const key = `${call.location}\0${call.locator}\0${where.origin}\0${where.page}`;
|
|
1978
|
+
const existing = this.entries.get(key);
|
|
1979
|
+
if (existing) {
|
|
1980
|
+
existing.arrival ||= call.arrival;
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
if (this.entries.size >= MAX_LOCATOR_PAGE_ENTRIES) return;
|
|
1984
|
+
this.entries.set(key, { location: call.location, locator: call.locator, ...where, arrival: call.arrival });
|
|
1985
|
+
}
|
|
1986
|
+
get size() {
|
|
1987
|
+
return this.entries.size;
|
|
1988
|
+
}
|
|
1989
|
+
/** The entries in the order the calls first happened. */
|
|
1990
|
+
list() {
|
|
1991
|
+
return [...this.entries.values()];
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1994
|
+
|
|
1872
1995
|
// src/internal/probe/mode.ts
|
|
1873
1996
|
var fs2 = __toESM(require("fs"));
|
|
1874
1997
|
var path3 = __toESM(require("path"));
|
|
@@ -1987,8 +2110,8 @@ function parseRouteKey(routeKey) {
|
|
|
1987
2110
|
return { method: routeKey.slice(0, space).toUpperCase(), pattern: routeKey.slice(space + 1) };
|
|
1988
2111
|
}
|
|
1989
2112
|
function patternToRegex(pattern) {
|
|
1990
|
-
const
|
|
1991
|
-
const escaped =
|
|
2113
|
+
const path8 = pattern.split("?")[0] ?? pattern;
|
|
2114
|
+
const escaped = path8.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
1992
2115
|
return new RegExp(`^${escaped}$`);
|
|
1993
2116
|
}
|
|
1994
2117
|
function suitePathEqual(a, b) {
|
|
@@ -2011,13 +2134,13 @@ function matchProbeItem(plan, test) {
|
|
|
2011
2134
|
function requestMatchesRoute(method, url, routeKey) {
|
|
2012
2135
|
const { method: rm, pattern } = parseRouteKey(routeKey);
|
|
2013
2136
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
2014
|
-
let
|
|
2137
|
+
let path8;
|
|
2015
2138
|
try {
|
|
2016
|
-
|
|
2139
|
+
path8 = new URL(url).pathname;
|
|
2017
2140
|
} catch {
|
|
2018
|
-
|
|
2141
|
+
path8 = url.split("?")[0] ?? url;
|
|
2019
2142
|
}
|
|
2020
|
-
return patternToRegex(pattern).test(
|
|
2143
|
+
return patternToRegex(pattern).test(path8);
|
|
2021
2144
|
}
|
|
2022
2145
|
function createProbeState() {
|
|
2023
2146
|
return { navigated: false, matches: 0 };
|
|
@@ -2039,10 +2162,10 @@ function isProbeMode() {
|
|
|
2039
2162
|
function loadPlan() {
|
|
2040
2163
|
if (planCache !== void 0) return planCache;
|
|
2041
2164
|
planCache = null;
|
|
2042
|
-
const
|
|
2043
|
-
if (
|
|
2165
|
+
const path8 = process.env[PIWI_PROBE_ENV.plan];
|
|
2166
|
+
if (path8) {
|
|
2044
2167
|
try {
|
|
2045
|
-
planCache = JSON.parse(fs2.readFileSync(
|
|
2168
|
+
planCache = JSON.parse(fs2.readFileSync(path8, "utf8"));
|
|
2046
2169
|
} catch {
|
|
2047
2170
|
planCache = null;
|
|
2048
2171
|
}
|
|
@@ -2064,10 +2187,10 @@ function outcomeFromStatus(status, applied) {
|
|
|
2064
2187
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
2065
2188
|
}
|
|
2066
2189
|
function recordProbeOutcome(line) {
|
|
2067
|
-
const
|
|
2068
|
-
if (!
|
|
2190
|
+
const path8 = process.env[PIWI_PROBE_ENV.results];
|
|
2191
|
+
if (!path8) return;
|
|
2069
2192
|
try {
|
|
2070
|
-
fs2.appendFileSync(
|
|
2193
|
+
fs2.appendFileSync(path8, `${JSON.stringify(line)}
|
|
2071
2194
|
`);
|
|
2072
2195
|
} catch {
|
|
2073
2196
|
}
|
|
@@ -2249,7 +2372,7 @@ async function installProbeInterception(page, item) {
|
|
|
2249
2372
|
});
|
|
2250
2373
|
applied = out.changed;
|
|
2251
2374
|
if (applied) appliedFault = item.fault;
|
|
2252
|
-
if (out.delayMs > 0) await new Promise((
|
|
2375
|
+
if (out.delayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, out.delayMs));
|
|
2253
2376
|
const headers = fulfillHeaders(response.headers(), out.contentType);
|
|
2254
2377
|
await route.fulfill({ status: out.status, body: out.body, headers });
|
|
2255
2378
|
} catch {
|
|
@@ -2791,6 +2914,79 @@ function isDueForAriaSample(testInfo) {
|
|
|
2791
2914
|
return set.has(ariaSampleIdentity(relativeTestFile(testInfo.file), testInfo.title));
|
|
2792
2915
|
}
|
|
2793
2916
|
|
|
2917
|
+
// src/internal/capture/quiet-capture.ts
|
|
2918
|
+
var import_node_module = require("module");
|
|
2919
|
+
var path7 = __toESM(require("path"));
|
|
2920
|
+
|
|
2921
|
+
// src/internal/support/reporter-version.ts
|
|
2922
|
+
var fs4 = __toESM(require("fs"));
|
|
2923
|
+
var path6 = __toESM(require("path"));
|
|
2924
|
+
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
2925
|
+
function findOwnPackageJson(fromDir) {
|
|
2926
|
+
for (const offset of CANDIDATE_OFFSETS) {
|
|
2927
|
+
try {
|
|
2928
|
+
const pkgPath = path6.resolve(fromDir, offset, "package.json");
|
|
2929
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
|
|
2930
|
+
const { name, version } = pkg;
|
|
2931
|
+
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
2932
|
+
return { version, root: path6.dirname(pkgPath) };
|
|
2933
|
+
} catch {
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
return void 0;
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
// src/internal/capture/quiet-capture.ts
|
|
2940
|
+
var BOXED_PREFIX_EXPORTS = [
|
|
2941
|
+
[
|
|
2942
|
+
"playwright-core/lib/coreBundle",
|
|
2943
|
+
(mod) => mod?.utils?.setBoxedStackPrefixes
|
|
2944
|
+
],
|
|
2945
|
+
["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
|
|
2946
|
+
];
|
|
2947
|
+
var boxing;
|
|
2948
|
+
function resolveBoxing() {
|
|
2949
|
+
try {
|
|
2950
|
+
const root = findOwnPackageJson(__dirname)?.root;
|
|
2951
|
+
if (!root) return null;
|
|
2952
|
+
const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
|
|
2953
|
+
const playwrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
|
|
2954
|
+
const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
|
|
2955
|
+
for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
|
|
2956
|
+
let set;
|
|
2957
|
+
try {
|
|
2958
|
+
set = pick(requireFromPlaywright(moduleId));
|
|
2959
|
+
} catch {
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2962
|
+
if (typeof set !== "function") continue;
|
|
2963
|
+
return {
|
|
2964
|
+
set,
|
|
2965
|
+
prefixes: [path7.dirname(playwrightPackageJson), path7.join(root, "dist") + path7.sep]
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
} catch {
|
|
2969
|
+
}
|
|
2970
|
+
return null;
|
|
2971
|
+
}
|
|
2972
|
+
function boxCaptureFrames(extraPrefixes = []) {
|
|
2973
|
+
const first = boxing === void 0;
|
|
2974
|
+
if (first) boxing = resolveBoxing();
|
|
2975
|
+
if (!boxing) return;
|
|
2976
|
+
const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
|
|
2977
|
+
if (!first && added.length === 0) return;
|
|
2978
|
+
boxing.prefixes.push(...added);
|
|
2979
|
+
try {
|
|
2980
|
+
boxing.set([...boxing.prefixes]);
|
|
2981
|
+
} catch {
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
function internalCall(owner, run) {
|
|
2985
|
+
const wrap = owner?._wrapApiCall;
|
|
2986
|
+
if (typeof wrap !== "function") return run();
|
|
2987
|
+
return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2794
2990
|
// src/internal/capture/capture-fixtures.ts
|
|
2795
2991
|
function computeCoreVitals(lcpEntries, shiftEntries, eventEntries) {
|
|
2796
2992
|
const lastLcp = lcpEntries && lcpEntries.length > 0 ? lcpEntries[lcpEntries.length - 1] : null;
|
|
@@ -2841,7 +3037,8 @@ function createSink() {
|
|
|
2841
3037
|
probeItem: null,
|
|
2842
3038
|
probeInterception: null,
|
|
2843
3039
|
pickOffered: false,
|
|
2844
|
-
userPick: null
|
|
3040
|
+
userPick: null,
|
|
3041
|
+
locatorPages: new LocatorPageLog()
|
|
2845
3042
|
};
|
|
2846
3043
|
}
|
|
2847
3044
|
var currentSink = null;
|
|
@@ -2851,8 +3048,8 @@ async function drainPendingProbes(capMs) {
|
|
|
2851
3048
|
let cap;
|
|
2852
3049
|
await Promise.race([
|
|
2853
3050
|
Promise.allSettled(PENDING_PROBES),
|
|
2854
|
-
new Promise((
|
|
2855
|
-
cap = setTimeout(
|
|
3051
|
+
new Promise((resolve2) => {
|
|
3052
|
+
cap = setTimeout(resolve2, capMs);
|
|
2856
3053
|
})
|
|
2857
3054
|
]);
|
|
2858
3055
|
clearTimeout(cap);
|
|
@@ -2892,10 +3089,10 @@ async function readWebVitals(page) {
|
|
|
2892
3089
|
const key = entry.name.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
2893
3090
|
paint[key] = Math.round(entry.startTime);
|
|
2894
3091
|
}
|
|
2895
|
-
const readBuffered = (type, extra) => new Promise((
|
|
3092
|
+
const readBuffered = (type, extra) => new Promise((resolve2) => {
|
|
2896
3093
|
try {
|
|
2897
3094
|
const PO = globalThis.PerformanceObserver;
|
|
2898
|
-
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return
|
|
3095
|
+
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve2(null);
|
|
2899
3096
|
const out = [];
|
|
2900
3097
|
const po = new PO((list) => out.push(...list.getEntries()));
|
|
2901
3098
|
po.observe({ type, buffered: true, ...extra });
|
|
@@ -2905,10 +3102,10 @@ async function readWebVitals(page) {
|
|
|
2905
3102
|
po.disconnect();
|
|
2906
3103
|
} catch {
|
|
2907
3104
|
}
|
|
2908
|
-
|
|
3105
|
+
resolve2(out);
|
|
2909
3106
|
}, 0);
|
|
2910
3107
|
} catch {
|
|
2911
|
-
|
|
3108
|
+
resolve2(null);
|
|
2912
3109
|
}
|
|
2913
3110
|
});
|
|
2914
3111
|
const [lcpRaw, shiftRaw, eventRaw, firstInputRaw] = await Promise.all([
|
|
@@ -3021,12 +3218,12 @@ var PAGE_INVENTORY_IN_PAGE_CAP = 2e3;
|
|
|
3021
3218
|
var PAGE_INVENTORY_EVAL_TIMEOUT_MS = 2e3;
|
|
3022
3219
|
var MAX_PAGE_INVENTORY_WINDOWS = 64;
|
|
3023
3220
|
function withTimeout(promise, ms) {
|
|
3024
|
-
return new Promise((
|
|
3221
|
+
return new Promise((resolve2, reject) => {
|
|
3025
3222
|
const timer = setTimeout(() => reject(new Error("page inventory read timed out")), ms);
|
|
3026
3223
|
promise.then(
|
|
3027
3224
|
(value) => {
|
|
3028
3225
|
clearTimeout(timer);
|
|
3029
|
-
|
|
3226
|
+
resolve2(value);
|
|
3030
3227
|
},
|
|
3031
3228
|
(error) => {
|
|
3032
3229
|
clearTimeout(timer);
|
|
@@ -3060,7 +3257,7 @@ async function recordPageWindow(sink, page) {
|
|
|
3060
3257
|
if (sink.pageInventories.length >= MAX_PAGE_INVENTORY_WINDOWS) return;
|
|
3061
3258
|
if (key && !inventoriedPageKeys.has(key)) {
|
|
3062
3259
|
inventoriedPageKeys.add(key);
|
|
3063
|
-
const content = await readPageInventory(page);
|
|
3260
|
+
const content = await internalCall(page, () => readPageInventory(page));
|
|
3064
3261
|
if (sink.pageInventories.length >= MAX_PAGE_INVENTORY_WINDOWS) return;
|
|
3065
3262
|
sink.pageInventories.push({
|
|
3066
3263
|
url: content?.url ?? url,
|
|
@@ -3077,6 +3274,9 @@ async function stashPageState(sink, closing) {
|
|
|
3077
3274
|
if (!page || isPageClosed(page)) return;
|
|
3078
3275
|
const belongsToClosing = closing.page === page || closing.context !== void 0 && pageContext(page) === closing.context;
|
|
3079
3276
|
if (!belongsToClosing) return;
|
|
3277
|
+
await internalCall(page, () => readPageBeforeClose(sink, page));
|
|
3278
|
+
}
|
|
3279
|
+
async function readPageBeforeClose(sink, page) {
|
|
3080
3280
|
const vitals = await readWebVitals(page);
|
|
3081
3281
|
if (vitals) sink.stashedWebVitals = vitals;
|
|
3082
3282
|
if (process.env.PIWI_CAPTURE_PAGE_STATE !== "false") {
|
|
@@ -3118,7 +3318,10 @@ async function maybeOpenPicker(sink, closing) {
|
|
|
3118
3318
|
console.log("[piwi] locator picker: no failing locator could be identified in this failure \u2014 nothing to replace.");
|
|
3119
3319
|
return;
|
|
3120
3320
|
}
|
|
3121
|
-
const pick = await
|
|
3321
|
+
const pick = await internalCall(
|
|
3322
|
+
page,
|
|
3323
|
+
() => runLocatorPicker(page, testInfo, failed, { fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG })
|
|
3324
|
+
);
|
|
3122
3325
|
if (!pick) return;
|
|
3123
3326
|
sink.userPick = pick;
|
|
3124
3327
|
applyPickToSnapshots(sink.capturedLocators, pick);
|
|
@@ -3137,12 +3340,8 @@ var CAPTURED_ATTRS_ARG = {
|
|
|
3137
3340
|
// special-cased logic in the probe, so add them explicitly).
|
|
3138
3341
|
roleSources: [.../* @__PURE__ */ new Set(["[role]", "input", "select", ...Object.keys(TAG_TO_ROLE)])].join(","),
|
|
3139
3342
|
// The reporter always wants ancestor-anchored alternatives (the picker's
|
|
3140
|
-
// anchors step and generateAnchoredAlternatives both need them).
|
|
3141
|
-
|
|
3142
|
-
// same pass — so asking for it here is free and settles the accessible name
|
|
3143
|
-
// of a labeled field without a second round trip (`exactAccessibleName`).
|
|
3144
|
-
includeStructural: true,
|
|
3145
|
-
includeLabelText: true
|
|
3343
|
+
// anchors step and generateAnchoredAlternatives both need them).
|
|
3344
|
+
includeStructural: true
|
|
3146
3345
|
};
|
|
3147
3346
|
var PROBE_GLOBAL = "__piwiProbeElement";
|
|
3148
3347
|
var PROBE_INIT_SCRIPT = `(() => {
|
|
@@ -3153,15 +3352,18 @@ var PROBE_INIT_SCRIPT = `(() => {
|
|
|
3153
3352
|
var PROBE_UNSEEDED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
3154
3353
|
function probeElement(page, target) {
|
|
3155
3354
|
const shipSource = () => target.evaluate(probeElementAttrs, CAPTURED_ATTRS_ARG);
|
|
3156
|
-
if (!page || PROBE_UNSEEDED_PAGES.has(page)) return shipSource
|
|
3157
|
-
return
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3355
|
+
if (!page || PROBE_UNSEEDED_PAGES.has(page)) return internalCall(page, shipSource);
|
|
3356
|
+
return internalCall(
|
|
3357
|
+
page,
|
|
3358
|
+
() => target.evaluate((el, name) => {
|
|
3359
|
+
const seeded = globalThis[name];
|
|
3360
|
+
return typeof seeded === "function" ? seeded(el) : null;
|
|
3361
|
+
}, PROBE_GLOBAL).then((attrs) => {
|
|
3362
|
+
if (attrs) return attrs;
|
|
3363
|
+
PROBE_UNSEEDED_PAGES.add(page);
|
|
3364
|
+
return shipSource();
|
|
3365
|
+
})
|
|
3366
|
+
);
|
|
3165
3367
|
}
|
|
3166
3368
|
async function ariaSnapshotBestEffort(target, timeout) {
|
|
3167
3369
|
if (typeof target.ariaSnapshot !== "function") return null;
|
|
@@ -3212,7 +3414,7 @@ function startElementCapture(sink, page, target, seq, callerLocation, used) {
|
|
|
3212
3414
|
const role = resolveAriaRole({ ...attrs, accessibleName: null });
|
|
3213
3415
|
const isFormField = FORM_FIELD_TAGS.has(attrs.tagName);
|
|
3214
3416
|
const exactName = exactAccessibleName(attrs, role);
|
|
3215
|
-
const aria = exactName === null && (role || isFormField) ? await ariaSnapshotBestEffort(target, 500) : null;
|
|
3417
|
+
const aria = exactName === null && (role || isFormField) ? await internalCall(page, () => ariaSnapshotBestEffort(target, 500)) : null;
|
|
3216
3418
|
const accessibleName = exactName ?? (extractAccessibleName(aria) || approximateAccessibleName({ ...attrs, accessibleName: null }));
|
|
3217
3419
|
sink.capturedLocators[seq] = {
|
|
3218
3420
|
location: callerLocation,
|
|
@@ -3240,15 +3442,23 @@ function startElementCapture(sink, page, target, seq, callerLocation, used) {
|
|
|
3240
3442
|
})();
|
|
3241
3443
|
sink.capturePromises.push(resolveAttrs);
|
|
3242
3444
|
}
|
|
3445
|
+
function callMethod(target, prop, args) {
|
|
3446
|
+
return target[prop](...args);
|
|
3447
|
+
}
|
|
3448
|
+
function recordLocatorPage(sink, page, locator, location, arrival) {
|
|
3449
|
+
try {
|
|
3450
|
+
sink.locatorPages.record({ location, locator: String(locator), url: page.url(), arrival });
|
|
3451
|
+
} catch {
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3243
3454
|
function wrapLocator(page, locator, originMethod, originArgs) {
|
|
3244
3455
|
return new Proxy(locator, {
|
|
3245
3456
|
get(target, prop) {
|
|
3246
3457
|
const original = Reflect.get(target, prop);
|
|
3247
3458
|
if (typeof original !== "function") return original;
|
|
3248
|
-
const fn = original;
|
|
3249
3459
|
if (CHAIN_METHOD_SET.has(prop)) {
|
|
3250
3460
|
return (...args) => {
|
|
3251
|
-
const next =
|
|
3461
|
+
const next = callMethod(target, prop, args);
|
|
3252
3462
|
if (LOCATOR_CREATING_CHAINS.has(prop)) {
|
|
3253
3463
|
return wrapLocator(page, next, String(prop), args);
|
|
3254
3464
|
}
|
|
@@ -3260,11 +3470,12 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
3260
3470
|
const sink = currentSink;
|
|
3261
3471
|
const expression = typeof callArgs[0] === "string" ? callArgs[0] : "";
|
|
3262
3472
|
const isNot = Boolean(callArgs[1]?.isNot);
|
|
3473
|
+
const callerLocation = sink ? captureCallerLocation() : null;
|
|
3474
|
+
if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
|
|
3263
3475
|
if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
|
|
3264
|
-
return
|
|
3476
|
+
return callMethod(target, prop, callArgs);
|
|
3265
3477
|
}
|
|
3266
3478
|
sink.lastActivePage = page;
|
|
3267
|
-
const callerLocation = captureCallerLocation();
|
|
3268
3479
|
const used = {
|
|
3269
3480
|
method: originMethod,
|
|
3270
3481
|
args: originArgs,
|
|
@@ -3276,7 +3487,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
3276
3487
|
seq = sink.capturedLocators.length;
|
|
3277
3488
|
sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
|
|
3278
3489
|
}
|
|
3279
|
-
const result = await
|
|
3490
|
+
const result = await callMethod(target, prop, callArgs);
|
|
3280
3491
|
const matches = result?.matches;
|
|
3281
3492
|
if (matches === true && !alreadyProbed) {
|
|
3282
3493
|
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
@@ -3290,9 +3501,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
3290
3501
|
if (!ACTION_METHOD_SET.has(prop)) return original;
|
|
3291
3502
|
return async (...callArgs) => {
|
|
3292
3503
|
const sink = currentSink;
|
|
3293
|
-
if (!sink) return
|
|
3504
|
+
if (!sink) return callMethod(target, prop, callArgs);
|
|
3294
3505
|
sink.lastActivePage = page;
|
|
3295
3506
|
const callerLocation = captureCallerLocation();
|
|
3507
|
+
recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, String(prop)));
|
|
3296
3508
|
const used = {
|
|
3297
3509
|
method: originMethod,
|
|
3298
3510
|
args: originArgs,
|
|
@@ -3311,7 +3523,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
3311
3523
|
}
|
|
3312
3524
|
let result;
|
|
3313
3525
|
try {
|
|
3314
|
-
result = await
|
|
3526
|
+
result = await callMethod(target, prop, callArgs);
|
|
3315
3527
|
} catch (error) {
|
|
3316
3528
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
3317
3529
|
throw error;
|
|
@@ -3330,11 +3542,10 @@ function wrapFrameLocator(page, frameLocator) {
|
|
|
3330
3542
|
get(target, prop) {
|
|
3331
3543
|
const original = Reflect.get(target, prop);
|
|
3332
3544
|
if (typeof original !== "function") return original;
|
|
3333
|
-
const fn = original;
|
|
3334
3545
|
if (!LOCATOR_METHOD_SET.has(prop)) return original;
|
|
3335
3546
|
return (...args) => {
|
|
3336
3547
|
if (currentSink) currentSink.lastActivePage = page;
|
|
3337
|
-
return wrapLocator(page,
|
|
3548
|
+
return wrapLocator(page, callMethod(target, prop, args), String(prop), args);
|
|
3338
3549
|
};
|
|
3339
3550
|
}
|
|
3340
3551
|
});
|
|
@@ -3381,7 +3592,13 @@ function instrumentPage(page) {
|
|
|
3381
3592
|
};
|
|
3382
3593
|
}
|
|
3383
3594
|
if (typeof page.on === "function") {
|
|
3384
|
-
page.on("framenavigated", () =>
|
|
3595
|
+
page.on("framenavigated", (frame) => {
|
|
3596
|
+
PROBE_UNSEEDED_PAGES.delete(page);
|
|
3597
|
+
try {
|
|
3598
|
+
if (frame === page.mainFrame()) noteNavigation(page);
|
|
3599
|
+
} catch {
|
|
3600
|
+
}
|
|
3601
|
+
});
|
|
3385
3602
|
}
|
|
3386
3603
|
}
|
|
3387
3604
|
if (typeof page.on === "function" && process.env.PIWI_CAPTURE_PAGE_INVENTORY === "true") {
|
|
@@ -3520,13 +3737,19 @@ function patchBrowser(browser) {
|
|
|
3520
3737
|
};
|
|
3521
3738
|
}
|
|
3522
3739
|
}
|
|
3740
|
+
function readRootAria(page) {
|
|
3741
|
+
return internalCall(page, () => ariaSnapshotBestEffort(page.locator(":root")));
|
|
3742
|
+
}
|
|
3743
|
+
function readRootAriaJson(page) {
|
|
3744
|
+
return internalCall(page, () => ariaSnapshotJSONBestEffort(page.locator(":root")));
|
|
3745
|
+
}
|
|
3523
3746
|
async function flushSink(sink, testInfo) {
|
|
3524
3747
|
await Promise.allSettled(sink.pendingHandlers);
|
|
3525
3748
|
let drainDeadline;
|
|
3526
3749
|
await Promise.race([
|
|
3527
3750
|
Promise.allSettled(sink.capturePromises),
|
|
3528
|
-
new Promise((
|
|
3529
|
-
drainDeadline = setTimeout(
|
|
3751
|
+
new Promise((resolve2) => {
|
|
3752
|
+
drainDeadline = setTimeout(resolve2, 2e3);
|
|
3530
3753
|
})
|
|
3531
3754
|
]);
|
|
3532
3755
|
clearTimeout(drainDeadline);
|
|
@@ -3542,15 +3765,21 @@ async function flushSink(sink, testInfo) {
|
|
|
3542
3765
|
body: Buffer.from(JSON.stringify(dedupeSnapshotsByLocation(sink.capturedLocators)))
|
|
3543
3766
|
});
|
|
3544
3767
|
}
|
|
3768
|
+
if (sink.locatorPages.size > 0) {
|
|
3769
|
+
await testInfo.attach(ATTACHMENT_NAMES.locatorPages, {
|
|
3770
|
+
contentType: "application/json",
|
|
3771
|
+
body: Buffer.from(JSON.stringify(sink.locatorPages.list()))
|
|
3772
|
+
});
|
|
3773
|
+
}
|
|
3545
3774
|
if (testInfo.status !== "passed" && testInfo.status !== "skipped") {
|
|
3546
3775
|
try {
|
|
3547
|
-
const snapshot = (pageReadable ? await
|
|
3776
|
+
const snapshot = (pageReadable ? await readRootAria(page) : null) ?? sink.stashedAria;
|
|
3548
3777
|
if (snapshot) {
|
|
3549
3778
|
await testInfo.attach(ATTACHMENT_NAMES.ariaSnapshot, {
|
|
3550
3779
|
contentType: "text/plain",
|
|
3551
3780
|
body: snapshot
|
|
3552
3781
|
});
|
|
3553
|
-
const snapshotJson = (pageReadable ? await
|
|
3782
|
+
const snapshotJson = (pageReadable ? await readRootAriaJson(page) : null) ?? sink.stashedAriaJson;
|
|
3554
3783
|
if (snapshotJson) {
|
|
3555
3784
|
await testInfo.attach(ATTACHMENT_NAMES.ariaSnapshotJson, {
|
|
3556
3785
|
contentType: "application/json",
|
|
@@ -3575,13 +3804,13 @@ async function flushSink(sink, testInfo) {
|
|
|
3575
3804
|
}
|
|
3576
3805
|
if (testInfo.status === "passed" && process.env.PIWI_SAMPLE_ARIA_ON_PASS !== "false" && isDueForAriaSample(testInfo)) {
|
|
3577
3806
|
try {
|
|
3578
|
-
const snapshot = (pageReadable ? await
|
|
3807
|
+
const snapshot = (pageReadable ? await readRootAria(page) : null) ?? sink.stashedAria;
|
|
3579
3808
|
if (snapshot) {
|
|
3580
3809
|
await testInfo.attach(ATTACHMENT_NAMES.ariaSnapshot, {
|
|
3581
3810
|
contentType: "text/plain",
|
|
3582
3811
|
body: snapshot
|
|
3583
3812
|
});
|
|
3584
|
-
const snapshotJson = (pageReadable ? await
|
|
3813
|
+
const snapshotJson = (pageReadable ? await readRootAriaJson(page) : null) ?? sink.stashedAriaJson;
|
|
3585
3814
|
if (snapshotJson) {
|
|
3586
3815
|
await testInfo.attach(ATTACHMENT_NAMES.ariaSnapshotJson, {
|
|
3587
3816
|
contentType: "application/json",
|
|
@@ -3627,7 +3856,7 @@ async function flushSink(sink, testInfo) {
|
|
|
3627
3856
|
body: Buffer.from(JSON.stringify(sink.networkRequests))
|
|
3628
3857
|
});
|
|
3629
3858
|
}
|
|
3630
|
-
const webVitals = (pageReadable ? await readWebVitals(page) : null) ?? sink.stashedWebVitals;
|
|
3859
|
+
const webVitals = (pageReadable ? await internalCall(page, () => readWebVitals(page)) : null) ?? sink.stashedWebVitals;
|
|
3631
3860
|
if (webVitals) {
|
|
3632
3861
|
await testInfo.attach(ATTACHMENT_NAMES.webVitals, {
|
|
3633
3862
|
contentType: "application/json",
|
|
@@ -3635,7 +3864,7 @@ async function flushSink(sink, testInfo) {
|
|
|
3635
3864
|
});
|
|
3636
3865
|
}
|
|
3637
3866
|
if (process.env.PIWI_CAPTURE_PAGE_STATE !== "false") {
|
|
3638
|
-
const pageState = (pageReadable ? await readPageState(page) : null) ?? sink.stashedPageState;
|
|
3867
|
+
const pageState = (pageReadable ? await internalCall(page, () => readPageState(page)) : null) ?? sink.stashedPageState;
|
|
3639
3868
|
if (pageState) {
|
|
3640
3869
|
await testInfo.attach(ATTACHMENT_NAMES.pageState, {
|
|
3641
3870
|
contentType: "application/json",
|
|
@@ -3676,6 +3905,7 @@ var piwiFixtures = {
|
|
|
3676
3905
|
// requested, so suites that never destructure `page` are still captured.
|
|
3677
3906
|
piwiCapture: [
|
|
3678
3907
|
async ({}, use, testInfo) => {
|
|
3908
|
+
boxCaptureFrames();
|
|
3679
3909
|
const sink = createSink();
|
|
3680
3910
|
sink.testInfo = testInfo;
|
|
3681
3911
|
if (isProbeMode())
|
|
@@ -3720,11 +3950,17 @@ function extendPiwiFixtures(test) {
|
|
|
3720
3950
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3721
3951
|
0 && (module.exports = {
|
|
3722
3952
|
CAPTURED_ATTRS_ARG,
|
|
3953
|
+
LocatorPageLog,
|
|
3723
3954
|
ariaSnapshotBestEffort,
|
|
3724
3955
|
ariaSnapshotJSONBestEffort,
|
|
3956
|
+
boxCaptureFrames,
|
|
3725
3957
|
buildPageState,
|
|
3958
|
+
callMethod,
|
|
3726
3959
|
computeCoreVitals,
|
|
3727
3960
|
extendPiwiFixtures,
|
|
3961
|
+
internalCall,
|
|
3962
|
+
noteLocatorCall,
|
|
3963
|
+
noteNavigation,
|
|
3728
3964
|
piwiFixtures,
|
|
3729
3965
|
probeElementAttrs
|
|
3730
3966
|
});
|