@piwitests/reporter 0.63.0 → 0.64.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 +1 -1
- package/dist/cli/index.js +62 -27
- package/dist/global-setup-module.js +61 -2
- package/dist/index.d.ts +21 -10
- package/dist/index.js +1833 -546
- package/dist/internal/capture/attachments.d.ts +1 -1
- package/dist/internal/capture/capture-fixtures.d.ts +130 -7
- package/dist/internal/capture/capture-fixtures.js +1900 -776
- package/dist/internal/capture/locator-healing.d.ts +212 -4
- package/dist/internal/capture/pick-on-failure.d.ts +184 -6
- package/dist/types-CGr9jJZ_.d.ts +87 -0
- package/dist/wire-7zsqlBoY.d.ts +48 -0
- package/package.json +2 -2
- package/templates/skills/write-the-missing-test/SKILL.md +6 -4
package/dist/index.js
CHANGED
|
@@ -255,7 +255,19 @@ var PIWI_FLAKE_ENV = {
|
|
|
255
255
|
var PIWI_RESOURCES_RESULTS_ENV = "PIWI_RESOURCES_RESULTS";
|
|
256
256
|
function readBool(val) {
|
|
257
257
|
if (val === void 0) return void 0;
|
|
258
|
-
return
|
|
258
|
+
return ["true", "1", "yes", "on"].includes(val.trim().toLowerCase());
|
|
259
|
+
}
|
|
260
|
+
function normalizeBoolEnv(env = process.env) {
|
|
261
|
+
const keys = [
|
|
262
|
+
...ENV_FALLBACK_SPECS.filter((spec) => spec.kind === "bool").map((spec) => spec.env),
|
|
263
|
+
PIWI_ENV_KEYS.verbose,
|
|
264
|
+
PIWI_ENV_KEYS.aiScreenshotFallback,
|
|
265
|
+
"PIWI_AI_UPDATE"
|
|
266
|
+
];
|
|
267
|
+
for (const key of keys) {
|
|
268
|
+
const value = env[key];
|
|
269
|
+
if (value !== void 0) env[key] = readBool(value) ? "true" : "false";
|
|
270
|
+
}
|
|
259
271
|
}
|
|
260
272
|
var ENV_FALLBACK_SPECS = [
|
|
261
273
|
{ option: "serverUrl", env: PIWI_ENV_KEYS.serverUrl, kind: "string" },
|
|
@@ -374,7 +386,7 @@ function resolveOptions(raw, configDir) {
|
|
|
374
386
|
externalId.onDuplicate = externalIdOnDuplicate;
|
|
375
387
|
if (Object.keys(externalId).length > 0) mergedRaw.externalId = externalId;
|
|
376
388
|
const opts = { ...DEFAULTS, ...mergedRaw };
|
|
377
|
-
if (env[PIWI_ENV_KEYS.verbose] !== void 0) opts.verbose = env[PIWI_ENV_KEYS.verbose]
|
|
389
|
+
if (env[PIWI_ENV_KEYS.verbose] !== void 0) opts.verbose = readBool(env[PIWI_ENV_KEYS.verbose]);
|
|
378
390
|
return opts;
|
|
379
391
|
}
|
|
380
392
|
function applyOptionsToEnv(options) {
|
|
@@ -469,8 +481,8 @@ function readHub(value) {
|
|
|
469
481
|
}
|
|
470
482
|
function readChannel(value) {
|
|
471
483
|
if (!isObject(value)) return null;
|
|
472
|
-
const
|
|
473
|
-
return
|
|
484
|
+
const path37 = text(value.path, 4096);
|
|
485
|
+
return path37 && typeof value.token === "string" && TOKEN.test(value.token) ? { path: path37, token: value.token } : null;
|
|
474
486
|
}
|
|
475
487
|
function parseEditorDiscovery(content) {
|
|
476
488
|
let value;
|
|
@@ -549,8 +561,8 @@ function parseRecordChannel(value) {
|
|
|
549
561
|
const hash = text2.lastIndexOf("#");
|
|
550
562
|
if (hash <= 0) return null;
|
|
551
563
|
const token = text2.slice(hash + 1);
|
|
552
|
-
const
|
|
553
|
-
return /^[A-Za-z0-9_-]{16,}$/.test(token) && !
|
|
564
|
+
const path37 = text2.slice(0, hash);
|
|
565
|
+
return /^[A-Za-z0-9_-]{16,}$/.test(token) && !path37.includes("\0") ? { path: path37, token } : null;
|
|
554
566
|
}
|
|
555
567
|
function isObject2(value) {
|
|
556
568
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -912,10 +924,10 @@ var FrameAcks = class {
|
|
|
912
924
|
const now = this.now();
|
|
913
925
|
this.due = Math.min(Math.max(now, this.due + this.intervalMs), now + this.intervalMs);
|
|
914
926
|
if (this.due <= now) return void 0;
|
|
915
|
-
return new Promise((
|
|
927
|
+
return new Promise((resolve12) => {
|
|
916
928
|
const timer = setTimeout(() => this.release(), this.due - now);
|
|
917
929
|
timer.unref?.();
|
|
918
|
-
this.waiting = { timer, resolve:
|
|
930
|
+
this.waiting = { timer, resolve: resolve12 };
|
|
919
931
|
});
|
|
920
932
|
}
|
|
921
933
|
/** Whether a frame waits for its acknowledgment. */
|
|
@@ -1116,10 +1128,15 @@ function readAct(value) {
|
|
|
1116
1128
|
|
|
1117
1129
|
// src/internal/config/editors.ts
|
|
1118
1130
|
var PIWI_EDITOR_ENV = "PIWI_EDITOR";
|
|
1131
|
+
var TEST_RUNNER_REPORTER_ENV = "PW_TEST_REPORTER";
|
|
1132
|
+
function startedByTestRunner(env) {
|
|
1133
|
+
const reporter = env[TEST_RUNNER_REPORTER_ENV]?.trim();
|
|
1134
|
+
return !!reporter && /[\\/]/.test(reporter);
|
|
1135
|
+
}
|
|
1119
1136
|
function terminalRunSkip(gate) {
|
|
1120
1137
|
const { env, argv } = gate;
|
|
1121
1138
|
const editor = env[PIWI_EDITOR_ENV]?.trim().toLowerCase();
|
|
1122
|
-
if (editor
|
|
1139
|
+
if (editor && ["off", "false", "0", "no"].includes(editor)) return `${PIWI_EDITOR_ENV}=${editor}`;
|
|
1123
1140
|
if (env[RUN_CHANNEL_ENV] !== void 0) return `the run names its ${RUN_CHANNEL_ENV}`;
|
|
1124
1141
|
if (env[PIWI_ORIGIN_ENV.kind]) return "a launcher started the run";
|
|
1125
1142
|
if (detectCi(env)) return "running under CI";
|
|
@@ -1129,7 +1146,8 @@ function terminalRunSkip(gate) {
|
|
|
1129
1146
|
return "Playwright's debugger or UI mode runs it";
|
|
1130
1147
|
}
|
|
1131
1148
|
if (argv.slice(2).includes("merge-reports")) return "it merges reports, running no test";
|
|
1132
|
-
|
|
1149
|
+
const forced = !!editor && ["on", "true", "1", "yes"].includes(editor);
|
|
1150
|
+
if (!gate.terminal && !forced && !startedByTestRunner(env)) return "its output goes to no terminal";
|
|
1133
1151
|
return null;
|
|
1134
1152
|
}
|
|
1135
1153
|
function editorsFolder(env = process.env) {
|
|
@@ -1445,7 +1463,7 @@ var HttpClient = class {
|
|
|
1445
1463
|
}
|
|
1446
1464
|
/** Unified request core: transport, headers, auth, response accumulation, timeout. */
|
|
1447
1465
|
request(method, pathname, opts) {
|
|
1448
|
-
return new Promise((
|
|
1466
|
+
return new Promise((resolve12, reject) => {
|
|
1449
1467
|
if (this.closedReason !== null) {
|
|
1450
1468
|
reject(new Error(this.closedReason));
|
|
1451
1469
|
return;
|
|
@@ -1468,7 +1486,7 @@ var HttpClient = class {
|
|
|
1468
1486
|
data += chunk;
|
|
1469
1487
|
});
|
|
1470
1488
|
res.on("end", () => {
|
|
1471
|
-
|
|
1489
|
+
resolve12({ status: res.statusCode ?? 0, text: data, headers: res.headers });
|
|
1472
1490
|
});
|
|
1473
1491
|
res.on("error", reject);
|
|
1474
1492
|
res.on("close", () => {
|
|
@@ -1511,6 +1529,87 @@ var fs4 = __toESM(require("fs"));
|
|
|
1511
1529
|
var path4 = __toESM(require("path"));
|
|
1512
1530
|
var import_form_data2 = __toESM(require("form-data"));
|
|
1513
1531
|
|
|
1532
|
+
// ../core/src/status-classify.ts
|
|
1533
|
+
function mergeAnnotations(test, result) {
|
|
1534
|
+
const out = [];
|
|
1535
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1536
|
+
for (const list of [test.annotations, result.annotations]) {
|
|
1537
|
+
for (const a of list ?? []) {
|
|
1538
|
+
const key = `${a.type}\0${a.description ?? ""}`;
|
|
1539
|
+
if (seen.has(key)) continue;
|
|
1540
|
+
seen.add(key);
|
|
1541
|
+
out.push(a.description === void 0 ? { type: a.type } : { type: a.type, description: a.description });
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return out;
|
|
1545
|
+
}
|
|
1546
|
+
function expectsFailure(annotations) {
|
|
1547
|
+
if (annotations.some((a) => a.type === "skip" || a.type === "fixme")) return false;
|
|
1548
|
+
return annotations.some((a) => a.type === "fail");
|
|
1549
|
+
}
|
|
1550
|
+
var EXPECTED_FAILURE_PASSED_MESSAGE = "Expected to fail, but passed.";
|
|
1551
|
+
function expectedFailureError(rawStatus, annotations) {
|
|
1552
|
+
return rawStatus === "passed" && expectsFailure(annotations) ? EXPECTED_FAILURE_PASSED_MESSAGE : null;
|
|
1553
|
+
}
|
|
1554
|
+
function isUnexpectedStatus(status) {
|
|
1555
|
+
return status === "failed" || status === "timedout" || status === "timedOut";
|
|
1556
|
+
}
|
|
1557
|
+
function isFlakyResult(lastStatus, earlierStatuses) {
|
|
1558
|
+
if (lastStatus !== "passed") return false;
|
|
1559
|
+
for (const status of earlierStatuses) if (isUnexpectedStatus(status)) return true;
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
function classifyStatus(rawStatus, annotations) {
|
|
1563
|
+
if (rawStatus === "skipped") {
|
|
1564
|
+
const intentional = annotations.some((a) => a.type === "skip" || a.type === "fixme");
|
|
1565
|
+
return intentional ? "skipped" : "didnotrun";
|
|
1566
|
+
}
|
|
1567
|
+
if (expectsFailure(annotations)) {
|
|
1568
|
+
if (rawStatus === "failed") return "passed";
|
|
1569
|
+
if (rawStatus === "passed") return "failed";
|
|
1570
|
+
}
|
|
1571
|
+
return rawStatus;
|
|
1572
|
+
}
|
|
1573
|
+
function resolveUnrunReason(runStatus, opts) {
|
|
1574
|
+
if (runStatus === "timedout") return "global-timeout";
|
|
1575
|
+
if (opts.maxFailures > 0 && opts.failures >= opts.maxFailures) return "max-failures";
|
|
1576
|
+
return "interrupted";
|
|
1577
|
+
}
|
|
1578
|
+
function fileOf(location) {
|
|
1579
|
+
return location.replace(/:\d+:\d+$/, "");
|
|
1580
|
+
}
|
|
1581
|
+
function suiteKey(suitePath) {
|
|
1582
|
+
return (suitePath ?? []).join("");
|
|
1583
|
+
}
|
|
1584
|
+
function sharedPrefixDepth(a, b) {
|
|
1585
|
+
let depth = 0;
|
|
1586
|
+
while (depth < a.length && depth < b.length && a[depth] === b[depth]) depth++;
|
|
1587
|
+
return depth;
|
|
1588
|
+
}
|
|
1589
|
+
function linkBlockedTests(cases) {
|
|
1590
|
+
const blockers = cases.filter((c) => c.status === "failed" || c.status === "timedOut" || c.status === "timedout");
|
|
1591
|
+
if (blockers.length === 0) return;
|
|
1592
|
+
for (const c of cases) {
|
|
1593
|
+
if (c.didNotRunReason !== "previous-failure") continue;
|
|
1594
|
+
const file = fileOf(c.location);
|
|
1595
|
+
const inFile = blockers.filter((b) => fileOf(b.location) === file);
|
|
1596
|
+
if (inFile.length === 0) continue;
|
|
1597
|
+
const key = suiteKey(c.suitePath);
|
|
1598
|
+
let blocker = inFile.find((b) => suiteKey(b.suitePath) === key);
|
|
1599
|
+
if (!blocker) {
|
|
1600
|
+
let bestDepth = 0;
|
|
1601
|
+
for (const b of inFile) {
|
|
1602
|
+
const depth = sharedPrefixDepth(c.suitePath ?? [], b.suitePath ?? []);
|
|
1603
|
+
if (depth > bestDepth) {
|
|
1604
|
+
bestDepth = depth;
|
|
1605
|
+
blocker = b;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
if (blocker) c.blockedBy = blocker.location;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1514
1613
|
// src/internal/submit/serializer.ts
|
|
1515
1614
|
function resolveOverallStatus(result, counters) {
|
|
1516
1615
|
const STATUS_MAP = {
|
|
@@ -1586,6 +1685,7 @@ function isFailingStatus(status) {
|
|
|
1586
1685
|
}
|
|
1587
1686
|
function computeDistinctRunCounts(testCases) {
|
|
1588
1687
|
const final = /* @__PURE__ */ new Map();
|
|
1688
|
+
const attemptsOf = /* @__PURE__ */ new Map();
|
|
1589
1689
|
for (const tc of testCases) {
|
|
1590
1690
|
const key = tc.testId ?? [
|
|
1591
1691
|
tc.title,
|
|
@@ -1596,6 +1696,9 @@ function computeDistinctRunCounts(testCases) {
|
|
|
1596
1696
|
].join("\0");
|
|
1597
1697
|
const status = tc.status ?? "";
|
|
1598
1698
|
const retries = tc.retries ?? 0;
|
|
1699
|
+
const attempts = attemptsOf.get(key);
|
|
1700
|
+
if (attempts) attempts.push({ status, retries });
|
|
1701
|
+
else attemptsOf.set(key, [{ status, retries }]);
|
|
1599
1702
|
const prev = final.get(key);
|
|
1600
1703
|
const decides = !prev || retries > prev.retries || retries === prev.retries && (isFailingStatus(status) || !isFailingStatus(prev.status));
|
|
1601
1704
|
if (decides) final.set(key, { status, retries });
|
|
@@ -1606,11 +1709,15 @@ function computeDistinctRunCounts(testCases) {
|
|
|
1606
1709
|
failedTests: 0,
|
|
1607
1710
|
timedOutTests: 0,
|
|
1608
1711
|
skippedTests: 0,
|
|
1609
|
-
didNotRunTests: 0
|
|
1712
|
+
didNotRunTests: 0,
|
|
1713
|
+
flakyTests: 0
|
|
1610
1714
|
};
|
|
1611
|
-
for (const { status } of final
|
|
1612
|
-
if (status === "passed")
|
|
1613
|
-
|
|
1715
|
+
for (const [key, { status, retries }] of final) {
|
|
1716
|
+
if (status === "passed") {
|
|
1717
|
+
counts.passedTests++;
|
|
1718
|
+
const earlier = (attemptsOf.get(key) ?? []).filter((a) => a.retries < retries).map((a) => a.status);
|
|
1719
|
+
if (isFlakyResult(status, earlier)) counts.flakyTests++;
|
|
1720
|
+
} else if (status === "failed") counts.failedTests++;
|
|
1614
1721
|
else if (status === "timedOut" || status === "timedout") counts.timedOutTests++;
|
|
1615
1722
|
else if (status === "skipped") counts.skippedTests++;
|
|
1616
1723
|
else if (status === "didnotrun") counts.didNotRunTests++;
|
|
@@ -1686,12 +1793,12 @@ var Uploader = class {
|
|
|
1686
1793
|
await this.appendReportsToForm(form, reportOptions.reports, reportOptions.uploadReport);
|
|
1687
1794
|
await this.appendFilesToForm(form, payload.testCases, reportOptions.uploadTraces);
|
|
1688
1795
|
const response = await this.httpClient.postFormData("/api/test-runs/upload", form, auth);
|
|
1689
|
-
this.logger.
|
|
1796
|
+
this.logger.debug(`Successfully uploaded test results with files`);
|
|
1690
1797
|
if (response.runId) {
|
|
1691
|
-
this.logger.
|
|
1798
|
+
this.logger.debug(`Test Run ID: ${response.runId}, Project ID: ${response.projectId}`);
|
|
1692
1799
|
}
|
|
1693
1800
|
if (response.reports) {
|
|
1694
|
-
for (const r of response.reports) this.logger.
|
|
1801
|
+
for (const r of response.reports) this.logger.debug(`${r.label}: ${r.path}`);
|
|
1695
1802
|
}
|
|
1696
1803
|
return response;
|
|
1697
1804
|
}
|
|
@@ -1716,9 +1823,9 @@ var Uploader = class {
|
|
|
1716
1823
|
form.append("testCases", JSON.stringify([]));
|
|
1717
1824
|
await this.appendReportsToForm(form, reportOptions.reports, reportOptions.uploadReport);
|
|
1718
1825
|
const response = await this.httpClient.postFormData("/api/test-runs/upload", form, auth);
|
|
1719
|
-
this.logger.
|
|
1826
|
+
this.logger.debug(`Successfully uploaded reports for streaming run #${runId}`);
|
|
1720
1827
|
if (response.reports) {
|
|
1721
|
-
for (const r of response.reports) this.logger.
|
|
1828
|
+
for (const r of response.reports) this.logger.debug(`${r.label}: ${r.path}`);
|
|
1722
1829
|
}
|
|
1723
1830
|
}
|
|
1724
1831
|
/**
|
|
@@ -1839,7 +1946,7 @@ var Uploader = class {
|
|
|
1839
1946
|
}
|
|
1840
1947
|
}
|
|
1841
1948
|
}
|
|
1842
|
-
this.logger.
|
|
1949
|
+
this.logger.debug(`Found ${traceCount} trace files`);
|
|
1843
1950
|
}
|
|
1844
1951
|
};
|
|
1845
1952
|
|
|
@@ -1959,7 +2066,7 @@ var CrashRecovery = class {
|
|
|
1959
2066
|
try {
|
|
1960
2067
|
fs6.writeFileSync(temp, JSON.stringify(file), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
1961
2068
|
fs6.renameSync(temp, this.filePath);
|
|
1962
|
-
this.logger.
|
|
2069
|
+
this.logger.debug("Saved recovery data for later upload");
|
|
1963
2070
|
} catch (error) {
|
|
1964
2071
|
fs6.rmSync(temp, { force: true });
|
|
1965
2072
|
this.logger.error(`Failed to save recovery data: ${errorMessage(error)}`);
|
|
@@ -2342,8 +2449,8 @@ var FileHandler = class {
|
|
|
2342
2449
|
}
|
|
2343
2450
|
if (!lastPath) return null;
|
|
2344
2451
|
const hash = crypto4.createHash("sha256");
|
|
2345
|
-
await new Promise((
|
|
2346
|
-
fs8.createReadStream(lastPath).on("data", (chunk) => hash.update(chunk)).on("end",
|
|
2452
|
+
await new Promise((resolve12, reject) => {
|
|
2453
|
+
fs8.createReadStream(lastPath).on("data", (chunk) => hash.update(chunk)).on("end", resolve12).on("error", reject);
|
|
2347
2454
|
});
|
|
2348
2455
|
return {
|
|
2349
2456
|
tracePath: lastPath,
|
|
@@ -2819,10 +2926,10 @@ function createLimiter(maxConcurrent) {
|
|
|
2819
2926
|
if (run) run();
|
|
2820
2927
|
};
|
|
2821
2928
|
return function limit(fn) {
|
|
2822
|
-
return new Promise((
|
|
2929
|
+
return new Promise((resolve12, reject) => {
|
|
2823
2930
|
const run = () => {
|
|
2824
2931
|
active2++;
|
|
2825
|
-
Promise.resolve().then(fn).then(
|
|
2932
|
+
Promise.resolve().then(fn).then(resolve12, reject).finally(next);
|
|
2826
2933
|
};
|
|
2827
2934
|
if (active2 < limitValue) run();
|
|
2828
2935
|
else queue.push(run);
|
|
@@ -2871,6 +2978,7 @@ var SERVER_MAX_EVENT_BATCH_BYTES = 10 * 1024 * 1024;
|
|
|
2871
2978
|
var EVENT_ENVELOPE_BYTES = 1024;
|
|
2872
2979
|
var MAX_EVENT_REQUEST_BYTES = 4 * 1024 * 1024;
|
|
2873
2980
|
var MAX_UPLOAD_CONNECTION_FAILURES = 3;
|
|
2981
|
+
var MIN_HEARTBEAT_WAIT_MS = 250;
|
|
2874
2982
|
var StreamManager = class {
|
|
2875
2983
|
/**
|
|
2876
2984
|
* @param httpClient HTTP client for server communication.
|
|
@@ -3066,7 +3174,7 @@ var StreamManager = class {
|
|
|
3066
3174
|
this.streamBuffer.bindRun(response.runId);
|
|
3067
3175
|
this.logger.info(`Streaming enabled. Run ID: ${response.runId}`);
|
|
3068
3176
|
if (this.options.serverUrl) {
|
|
3069
|
-
this.logger.info(`
|
|
3177
|
+
this.logger.info(`View live progress: ${runUrl(this.options.serverUrl, response.runId)}`);
|
|
3070
3178
|
}
|
|
3071
3179
|
this.lastActivityAt = Date.now();
|
|
3072
3180
|
this.scheduleHeartbeat();
|
|
@@ -3233,12 +3341,12 @@ var StreamManager = class {
|
|
|
3233
3341
|
return this.watched === false ? this.unwatchedHeartbeatInterval : this.heartbeatInterval;
|
|
3234
3342
|
}
|
|
3235
3343
|
// Schedule the next idle heartbeat. Self-rescheduling; cleared by stopHeartbeat.
|
|
3236
|
-
scheduleHeartbeat() {
|
|
3344
|
+
scheduleHeartbeat(delay = this.heartbeatDelay()) {
|
|
3237
3345
|
if (this.heartbeatStopped || this.heartbeatTimer) return;
|
|
3238
3346
|
this.heartbeatTimer = setTimeout(() => {
|
|
3239
3347
|
this.heartbeatTimer = null;
|
|
3240
3348
|
void this.sendHeartbeat();
|
|
3241
|
-
},
|
|
3349
|
+
}, delay);
|
|
3242
3350
|
}
|
|
3243
3351
|
// Ping the server only when the run has actually been idle. Real event traffic
|
|
3244
3352
|
// already bumps the server's activity timestamp, so a recent flush (or pending
|
|
@@ -3247,7 +3355,7 @@ var StreamManager = class {
|
|
|
3247
3355
|
if (this.heartbeatStopped || !this._enabled || !this._runId || !this._token) return;
|
|
3248
3356
|
const idleFor = Date.now() - this.lastActivityAt;
|
|
3249
3357
|
if (idleFor < this.heartbeatDelay() || this.pendingEvents.length > 0) {
|
|
3250
|
-
this.scheduleHeartbeat();
|
|
3358
|
+
this.scheduleHeartbeat(Math.max(this.heartbeatDelay() - idleFor, MIN_HEARTBEAT_WAIT_MS));
|
|
3251
3359
|
return;
|
|
3252
3360
|
}
|
|
3253
3361
|
try {
|
|
@@ -3320,7 +3428,7 @@ var StreamManager = class {
|
|
|
3320
3428
|
this.logger.debugError(
|
|
3321
3429
|
`${this.pendingEvents.length} events pending, retrying (attempt ${attempt + 1}/${MAX_ATTEMPTS})...`
|
|
3322
3430
|
);
|
|
3323
|
-
await new Promise((
|
|
3431
|
+
await new Promise((resolve12) => setTimeout(resolve12, Math.min(1e3 * Math.pow(2, attempt), 1e4)));
|
|
3324
3432
|
}
|
|
3325
3433
|
if (!this.pendingEvents.isEmpty) {
|
|
3326
3434
|
const remaining = this.pendingEvents.takeAll();
|
|
@@ -3377,7 +3485,7 @@ var StreamManager = class {
|
|
|
3377
3485
|
if (flush) await flush;
|
|
3378
3486
|
const delays = [0, 1e3, 3e3];
|
|
3379
3487
|
for (let attempt = 0; attempt < delays.length; attempt++) {
|
|
3380
|
-
if (delays[attempt]) await new Promise((
|
|
3488
|
+
if (delays[attempt]) await new Promise((resolve12) => setTimeout(resolve12, delays[attempt]));
|
|
3381
3489
|
try {
|
|
3382
3490
|
await this.limitLiveUpload(
|
|
3383
3491
|
() => this.uploader.uploadCaseFiles(
|
|
@@ -4198,6 +4306,13 @@ var SECRET_AUTOCOMPLETE = [
|
|
|
4198
4306
|
"cc-number",
|
|
4199
4307
|
"cc-csc"
|
|
4200
4308
|
];
|
|
4309
|
+
var SECRET_FIELD_SELECTOR = [
|
|
4310
|
+
'input[type="password" i]',
|
|
4311
|
+
...SECRET_AUTOCOMPLETE.flatMap((token) => [
|
|
4312
|
+
`input[autocomplete~="${token}" i]`,
|
|
4313
|
+
`textarea[autocomplete~="${token}" i]`
|
|
4314
|
+
])
|
|
4315
|
+
].join(", ");
|
|
4201
4316
|
var SECRET_WORDS = /* @__PURE__ */ new Set([
|
|
4202
4317
|
"pass",
|
|
4203
4318
|
"password",
|
|
@@ -4234,7 +4349,7 @@ var SECRET_WORDS = /* @__PURE__ */ new Set([
|
|
|
4234
4349
|
]);
|
|
4235
4350
|
var SECRET_NAMES = /* @__PURE__ */ new Set(["code", "pin"]);
|
|
4236
4351
|
function nameWords(name) {
|
|
4237
|
-
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]
|
|
4352
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])(?=[A-Z][a-z])/g, "$1 ").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
4238
4353
|
}
|
|
4239
4354
|
function isSecretName(name) {
|
|
4240
4355
|
let secret = namesRead.get(name);
|
|
@@ -4358,9 +4473,9 @@ function hideParams(params, count) {
|
|
|
4358
4473
|
return isSecretName(decodeSafe(name)) || isSecretValue(decoded) ? `${name}=${hide()}` : part;
|
|
4359
4474
|
}).join("");
|
|
4360
4475
|
}
|
|
4361
|
-
function hidePath(
|
|
4476
|
+
function hidePath(path37, count) {
|
|
4362
4477
|
const hide = hider(count);
|
|
4363
|
-
return
|
|
4478
|
+
return path37.split("/").map((segment) => {
|
|
4364
4479
|
const [own = "", ...params] = segment.split(";");
|
|
4365
4480
|
const decoded = decodeSafe(own);
|
|
4366
4481
|
const kept = decoded !== HIDDEN_VALUE && isSecretValue(decoded) ? hide() : own;
|
|
@@ -4370,14 +4485,14 @@ function hidePath(path36, count) {
|
|
|
4370
4485
|
function hideSecretsInUrl(url, count = { n: 0 }) {
|
|
4371
4486
|
const parts = URL_PARTS.exec(url);
|
|
4372
4487
|
if (!parts) return url;
|
|
4373
|
-
const [, scheme = "", authority,
|
|
4488
|
+
const [, scheme = "", authority, path37 = "", query = "", fragment = ""] = parts;
|
|
4374
4489
|
let out = "";
|
|
4375
4490
|
if (authority !== void 0) {
|
|
4376
4491
|
const at = authority.lastIndexOf("@");
|
|
4377
4492
|
const colon = at >= 0 ? authority.slice(0, at).indexOf(":") : -1;
|
|
4378
4493
|
out += colon >= 0 && colon < at - 1 ? `${scheme}//${authority.slice(0, colon + 1)}${hider(count)()}${authority.slice(at)}` : `${scheme}//${authority}`;
|
|
4379
4494
|
}
|
|
4380
|
-
out += hidePath(
|
|
4495
|
+
out += hidePath(path37, count);
|
|
4381
4496
|
if (query) out += `?${hideParams(query.slice(1), count)}`;
|
|
4382
4497
|
if (fragment) {
|
|
4383
4498
|
const body = fragment.slice(1);
|
|
@@ -4408,10 +4523,10 @@ function hideInText(text2, count) {
|
|
|
4408
4523
|
if (text2.length < 4 || !/[A-Za-z0-9]/.test(text2)) return text2;
|
|
4409
4524
|
const hide = hider(count);
|
|
4410
4525
|
let out = hidePem(text2, hide);
|
|
4411
|
-
out = out.replace(QUERY_PAIR, (all,
|
|
4526
|
+
out = out.replace(QUERY_PAIR, (all, sep12, name, value) => {
|
|
4412
4527
|
const decoded = decodeSafe(value);
|
|
4413
4528
|
if (!value || decoded === HIDDEN_VALUE) return all;
|
|
4414
|
-
return isSecretName(name) || isSecretValue(decoded) ? `${
|
|
4529
|
+
return isSecretName(name) || isSecretValue(decoded) ? `${sep12}${name}=${hide()}` : all;
|
|
4415
4530
|
});
|
|
4416
4531
|
out = out.replace(URL_PASSWORD, (_all, before, _password, at) => `${before}${hide()}${at}`);
|
|
4417
4532
|
out = out.replace(SET_COOKIE, (_all, before) => `${before}${hide()}`);
|
|
@@ -4442,9 +4557,149 @@ function hideSecretsInText(text2, count = { n: 0 }) {
|
|
|
4442
4557
|
}
|
|
4443
4558
|
return out + hideInText(text2.slice(last), count);
|
|
4444
4559
|
}
|
|
4560
|
+
var TYPED_TITLE = /^(?:Fill|Type|Insert) "/;
|
|
4561
|
+
function hideTypedText(title) {
|
|
4562
|
+
const typing = TYPED_TITLE.exec(title);
|
|
4563
|
+
if (!typing) return title;
|
|
4564
|
+
const start = typing[0].length;
|
|
4565
|
+
const end = title.endsWith('"') ? title.length - 1 : title.lastIndexOf('" ');
|
|
4566
|
+
return end >= start ? `${title.slice(0, start)}${HIDDEN_VALUE}${title.slice(end)}` : `${typing[0]}${HIDDEN_VALUE}"`;
|
|
4567
|
+
}
|
|
4568
|
+
var SECRET_FIELD_WORDS = /* @__PURE__ */ new Set([
|
|
4569
|
+
"passe",
|
|
4570
|
+
"mdp",
|
|
4571
|
+
"passwort",
|
|
4572
|
+
"kennwort",
|
|
4573
|
+
"contrasena",
|
|
4574
|
+
"clave",
|
|
4575
|
+
"senha",
|
|
4576
|
+
"cvv",
|
|
4577
|
+
"cvc",
|
|
4578
|
+
"csc",
|
|
4579
|
+
"pin",
|
|
4580
|
+
"pw",
|
|
4581
|
+
"totp",
|
|
4582
|
+
"mfa",
|
|
4583
|
+
"2fa"
|
|
4584
|
+
]);
|
|
4585
|
+
var SECRET_STEMS = ["password", "passwd", "passwort", "kennwort", "contrasena", "senha", "motdepasse"];
|
|
4586
|
+
var SECRET_CODE_KINDS = /* @__PURE__ */ new Set([
|
|
4587
|
+
"verification",
|
|
4588
|
+
"security",
|
|
4589
|
+
"confirmation",
|
|
4590
|
+
"auth",
|
|
4591
|
+
"authentication",
|
|
4592
|
+
"access",
|
|
4593
|
+
"time",
|
|
4594
|
+
"otp",
|
|
4595
|
+
"sms",
|
|
4596
|
+
"login",
|
|
4597
|
+
"mfa",
|
|
4598
|
+
"2fa"
|
|
4599
|
+
]);
|
|
4600
|
+
function isSecretLocator(locator) {
|
|
4601
|
+
const plain = locator.normalize("NFD").replace(/[̀-ͯ]/g, "");
|
|
4602
|
+
if (/type\s*=\s*["']?password\b/i.test(plain)) return true;
|
|
4603
|
+
for (const quoted of plain.matchAll(/(['"])((?:(?!\1)[^\\]|\\.)*)\1/g)) if (isSecretName(quoted[2])) return true;
|
|
4604
|
+
const words = nameWords(plain);
|
|
4605
|
+
return words.some(
|
|
4606
|
+
(word) => SECRET_WORDS.has(word) || SECRET_FIELD_WORDS.has(word) || SECRET_STEMS.some((stem) => word.includes(stem))
|
|
4607
|
+
) || words.includes("code") && words.some((word) => SECRET_CODE_KINDS.has(word));
|
|
4608
|
+
}
|
|
4609
|
+
var TYPED_CALL = (
|
|
4610
|
+
// eslint-disable-next-line no-control-regex -- matches the ESC byte of a color code
|
|
4611
|
+
/^((?:\u001b\[[\d;]*m)*[ \t]*(?:(?:-|\d+ ×)[ \t]+)?(?:\w+\.)?)(fill|type|insertText)\("/gm
|
|
4612
|
+
);
|
|
4613
|
+
var TYPED_END = /"\)(?=(?:\u001b\[[\d;]*m|[ \t])*(?:\r?\n|$))/g;
|
|
4614
|
+
function hideTypedTextInLog(text2) {
|
|
4615
|
+
let out = "";
|
|
4616
|
+
let from = 0;
|
|
4617
|
+
TYPED_CALL.lastIndex = 0;
|
|
4618
|
+
for (let call = TYPED_CALL.exec(text2); call; call = TYPED_CALL.exec(text2)) {
|
|
4619
|
+
out += `${text2.slice(from, call.index)}${call[1]}${call[2]}("${HIDDEN_VALUE}")`;
|
|
4620
|
+
TYPED_END.lastIndex = call.index + call[0].length;
|
|
4621
|
+
const end = TYPED_END.exec(text2);
|
|
4622
|
+
if (!end) return out;
|
|
4623
|
+
from = end.index + 2;
|
|
4624
|
+
TYPED_CALL.lastIndex = from;
|
|
4625
|
+
}
|
|
4626
|
+
return out + text2.slice(from);
|
|
4627
|
+
}
|
|
4628
|
+
var READ_LOCATOR = /(?:waiting for|Locator:)[ \t]+(.*)$/gm;
|
|
4629
|
+
var RESOLVED_PASSWORD = /resolved to <input\b[^>\n]*\btype="password"/i;
|
|
4630
|
+
var READ_VALUE = (
|
|
4631
|
+
// eslint-disable-next-line no-control-regex -- matches the ESC byte of a color code
|
|
4632
|
+
/(unexpected value |(?:Expected|Received)(?: string)?:[ \t]*(?:\u001b\[[\d;]*m)*(?:not[ \t]+)?)"(?:.*"|.*)/g
|
|
4633
|
+
);
|
|
4634
|
+
var RESOLVED_ELEMENT = /resolved to <[^>\n]*/g;
|
|
4635
|
+
var VALUE_ATTRIBUTE = /\bvalue="[^"\n]*"/g;
|
|
4636
|
+
var COLOR = /\u001b\[[\d;]*m/g;
|
|
4637
|
+
function namesSecretField(text2) {
|
|
4638
|
+
if (RESOLVED_PASSWORD.test(text2)) return true;
|
|
4639
|
+
for (const named of text2.matchAll(READ_LOCATOR))
|
|
4640
|
+
if (isSecretLocator(named[1].replace(COLOR, "").trim())) return true;
|
|
4641
|
+
return false;
|
|
4642
|
+
}
|
|
4643
|
+
function hideReadValues(text2) {
|
|
4644
|
+
return text2.replace(READ_VALUE, (_, before) => `${before}"${HIDDEN_VALUE}"`);
|
|
4645
|
+
}
|
|
4646
|
+
function hideResolvedValues(text2, secretField) {
|
|
4647
|
+
if (!text2.includes("resolved to <")) return text2;
|
|
4648
|
+
return text2.replace(
|
|
4649
|
+
RESOLVED_ELEMENT,
|
|
4650
|
+
(element) => secretField || /\btype="password"/i.test(element) ? element.replace(VALUE_ATTRIBUTE, `value="${HIDDEN_VALUE}"`) : element
|
|
4651
|
+
);
|
|
4652
|
+
}
|
|
4653
|
+
var LOGGED_HEADER = (
|
|
4654
|
+
// eslint-disable-next-line no-control-regex -- matches the ESC byte of a color code
|
|
4655
|
+
/^(?:\u001b\[[\d;]*m)*(?:[ \t]+(?:(?:-|\d+ ×)[ \t]+)?|(?:-|\d+ ×)[ \t]+)([A-Za-z][\w-]*): /
|
|
4656
|
+
);
|
|
4657
|
+
function colorsAtEnd(line) {
|
|
4658
|
+
let end = line.length;
|
|
4659
|
+
while (end > 0 && line[end - 1] === "m") {
|
|
4660
|
+
const start = line.lastIndexOf("\x1B[", end - 1);
|
|
4661
|
+
if (start < 0 || !/^[\d;]*$/.test(line.slice(start + 2, end - 1))) break;
|
|
4662
|
+
end = start;
|
|
4663
|
+
}
|
|
4664
|
+
return end;
|
|
4665
|
+
}
|
|
4666
|
+
function hideSecretHeaders(text2) {
|
|
4667
|
+
if (!text2.includes(": ")) return text2;
|
|
4668
|
+
return text2.split("\n").map((line) => {
|
|
4669
|
+
const header = LOGGED_HEADER.exec(line);
|
|
4670
|
+
if (!header || !isSecretName(header[1])) return line;
|
|
4671
|
+
const rest = line.slice(header[0].length);
|
|
4672
|
+
const end = colorsAtEnd(rest);
|
|
4673
|
+
return end === 0 ? line : `${header[0]}${HIDDEN_VALUE}${rest.slice(end)}`;
|
|
4674
|
+
}).join("\n");
|
|
4675
|
+
}
|
|
4676
|
+
function hideSecretsInLog(text2, secretField = false) {
|
|
4677
|
+
const secret = secretField || namesSecretField(text2);
|
|
4678
|
+
let hidden2 = hideResolvedValues(hideTypedTextInLog(text2), secret);
|
|
4679
|
+
if (secret) hidden2 = hideReadValues(hidden2);
|
|
4680
|
+
return hideSecretHeaders(hidden2);
|
|
4681
|
+
}
|
|
4682
|
+
var CALL_LOG = "\nCall log:";
|
|
4683
|
+
var CALL_LOG_END = /\n(?:---\n|[ \t]+at )/g;
|
|
4684
|
+
function hideSecretsInError(text2) {
|
|
4685
|
+
const secretField = namesSecretField(text2);
|
|
4686
|
+
let out = "";
|
|
4687
|
+
let from = 0;
|
|
4688
|
+
for (; ; ) {
|
|
4689
|
+
const log = text2.indexOf(CALL_LOG, from);
|
|
4690
|
+
const message = text2.slice(from, log === -1 ? text2.length : log);
|
|
4691
|
+
out += secretField ? hideReadValues(message) : message;
|
|
4692
|
+
if (log === -1) return out;
|
|
4693
|
+
const start = log + CALL_LOG.length;
|
|
4694
|
+
CALL_LOG_END.lastIndex = start;
|
|
4695
|
+
const end = CALL_LOG_END.exec(text2)?.index ?? text2.length;
|
|
4696
|
+
out += CALL_LOG + hideSecretsInLog(text2.slice(start, end), secretField);
|
|
4697
|
+
from = end;
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4445
4700
|
|
|
4446
4701
|
// ../core/src/locator-break.ts
|
|
4447
|
-
var filePath2 = (
|
|
4702
|
+
var filePath2 = (path37) => path37.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4448
4703
|
function sameFilePath(a, b) {
|
|
4449
4704
|
const x = filePath2(a);
|
|
4450
4705
|
const y = filePath2(b);
|
|
@@ -4713,20 +4968,47 @@ function markRecoveredSteps(steps, errors) {
|
|
|
4713
4968
|
function firstErrorLine(message) {
|
|
4714
4969
|
return stripAnsi(typeof message === "string" ? message : "").split("\n")[0].trim();
|
|
4715
4970
|
}
|
|
4971
|
+
var TYPED_PARAMS = ["value", "text"];
|
|
4972
|
+
var BARE_TYPED_TITLE = /^(?:Fill|Type|Insert)$/;
|
|
4973
|
+
var OWN_CATEGORIES = /* @__PURE__ */ new Set(["test.step", "hook", "fixture"]);
|
|
4974
|
+
function withoutTypedText(title, params, subtitle, category) {
|
|
4975
|
+
if (category && OWN_CATEGORIES.has(category)) return { title, params };
|
|
4976
|
+
const hidden2 = hideTypedText(title);
|
|
4977
|
+
const bare = BARE_TYPED_TITLE.test(title.trim());
|
|
4978
|
+
if (hidden2 === title && !bare) return { title, params };
|
|
4979
|
+
const named = bare ? "" : hidden2.slice(hidden2.indexOf(`${HIDDEN_VALUE}"`) + HIDDEN_VALUE.length + 1).trim();
|
|
4980
|
+
const locators = [params?.locator, subtitle, named].filter(
|
|
4981
|
+
(locator) => typeof locator === "string" && locator.length > 0
|
|
4982
|
+
);
|
|
4983
|
+
if (locators.length && !locators.some(isSecretLocator)) return { title, params };
|
|
4984
|
+
if (!params) return { title: hidden2, params };
|
|
4985
|
+
const out = { ...params };
|
|
4986
|
+
for (const key of TYPED_PARAMS) if (typeof out[key] === "string") out[key] = HIDDEN_VALUE;
|
|
4987
|
+
return { title: hidden2, params: out };
|
|
4988
|
+
}
|
|
4989
|
+
function hideTypedStepTitle(title, category) {
|
|
4990
|
+
return category && OWN_CATEGORIES.has(category) ? title : hideTypedText(title);
|
|
4991
|
+
}
|
|
4716
4992
|
function flattenSteps(steps, depth = 0) {
|
|
4717
4993
|
const result = [];
|
|
4718
4994
|
for (const step of steps) {
|
|
4995
|
+
const named = [step.subtitle, step.params?.locator].filter((v) => typeof v === "string" && v).join(" ");
|
|
4996
|
+
const typed = withoutTypedText(
|
|
4997
|
+
typeof step.title === "string" ? step.title : "",
|
|
4998
|
+
normalizeStepParams(step.params),
|
|
4999
|
+
named || null,
|
|
5000
|
+
step.category
|
|
5001
|
+
);
|
|
4719
5002
|
const flat = {
|
|
4720
|
-
title:
|
|
5003
|
+
title: typed.title,
|
|
4721
5004
|
duration: step.duration,
|
|
4722
5005
|
category: categorizeStep(step.title, step.category, step.params),
|
|
4723
5006
|
depth
|
|
4724
5007
|
};
|
|
4725
5008
|
if (typeof step.subtitle === "string" && step.subtitle.length > 0) flat.subtitle = maskTokenLike(step.subtitle);
|
|
4726
|
-
|
|
4727
|
-
if (params) flat.params = params;
|
|
5009
|
+
if (typed.params) flat.params = typed.params;
|
|
4728
5010
|
if (step.error?.message) {
|
|
4729
|
-
flat.error = { message: step.error.message };
|
|
5011
|
+
flat.error = { message: hideSecretsInError(step.error.message) };
|
|
4730
5012
|
const thrownAt = locationText(step.error.location);
|
|
4731
5013
|
if (thrownAt) flat.error.location = thrownAt;
|
|
4732
5014
|
flat.failed = true;
|
|
@@ -4989,7 +5271,7 @@ function collectSourceFrames(errorText2, testFile2, declLine, opts = {}) {
|
|
|
4989
5271
|
if (picked.length >= maxFrames) break;
|
|
4990
5272
|
let abs;
|
|
4991
5273
|
try {
|
|
4992
|
-
abs = path12.resolve(frame.file);
|
|
5274
|
+
abs = path12.resolve(projectRoot, frame.file);
|
|
4993
5275
|
} catch {
|
|
4994
5276
|
continue;
|
|
4995
5277
|
}
|
|
@@ -5000,7 +5282,7 @@ function collectSourceFrames(errorText2, testFile2, declLine, opts = {}) {
|
|
|
5000
5282
|
}
|
|
5001
5283
|
const absTest = path12.resolve(testFile2);
|
|
5002
5284
|
if (!picked.some((p) => p.absFile === absTest)) {
|
|
5003
|
-
add2(absTest, extractFailingLine(errorText2, testFile2, declLine));
|
|
5285
|
+
add2(absTest, extractFailingLine(errorText2, testFile2, declLine, projectRoot));
|
|
5004
5286
|
}
|
|
5005
5287
|
const frames = [];
|
|
5006
5288
|
for (const p of picked.slice(0, maxFrames)) {
|
|
@@ -5011,12 +5293,12 @@ function collectSourceFrames(errorText2, testFile2, declLine, opts = {}) {
|
|
|
5011
5293
|
}
|
|
5012
5294
|
return frames;
|
|
5013
5295
|
}
|
|
5014
|
-
function extractFailingLine(errorText2, testFile2, declarationLine) {
|
|
5296
|
+
function extractFailingLine(errorText2, testFile2, declarationLine, base = process.cwd()) {
|
|
5015
5297
|
if (!errorText2) return declarationLine;
|
|
5016
5298
|
const expectedFile = path12.resolve(testFile2);
|
|
5017
5299
|
for (const frame of stackFrames(errorText2)) {
|
|
5018
5300
|
try {
|
|
5019
|
-
if (path12.resolve(frame.file) === expectedFile && !isNaN(frame.line)) return frame.line;
|
|
5301
|
+
if (path12.resolve(base, frame.file) === expectedFile && !isNaN(frame.line)) return frame.line;
|
|
5020
5302
|
} catch {
|
|
5021
5303
|
}
|
|
5022
5304
|
}
|
|
@@ -5030,40 +5312,87 @@ function workerIndexOf(result) {
|
|
|
5030
5312
|
}
|
|
5031
5313
|
|
|
5032
5314
|
// src/internal/support/cli-filters.ts
|
|
5033
|
-
var
|
|
5315
|
+
var REQUIRED_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
5316
|
+
"--add-reporter",
|
|
5317
|
+
"--browser",
|
|
5034
5318
|
"-c",
|
|
5035
5319
|
"--config",
|
|
5036
|
-
"-
|
|
5037
|
-
"--workers",
|
|
5320
|
+
"--global-timeout",
|
|
5038
5321
|
"-g",
|
|
5039
5322
|
"--grep",
|
|
5323
|
+
"-G",
|
|
5040
5324
|
"--grep-invert",
|
|
5041
|
-
"--
|
|
5325
|
+
"--last-failed-file",
|
|
5042
5326
|
"--max-failures",
|
|
5043
5327
|
"--output",
|
|
5044
5328
|
"-p",
|
|
5045
|
-
"--project",
|
|
5046
5329
|
"--repeat-each",
|
|
5047
5330
|
"--reporter",
|
|
5048
5331
|
"--retries",
|
|
5332
|
+
"--run-agents",
|
|
5049
5333
|
"--shard",
|
|
5334
|
+
"--test-list",
|
|
5335
|
+
"--test-list-invert",
|
|
5050
5336
|
"--timeout",
|
|
5051
|
-
"--trace"
|
|
5337
|
+
"--trace",
|
|
5338
|
+
"--tsconfig",
|
|
5339
|
+
"--ui-host",
|
|
5340
|
+
"--ui-port",
|
|
5341
|
+
"--update-source-method",
|
|
5342
|
+
"-j",
|
|
5343
|
+
"--workers"
|
|
5052
5344
|
]);
|
|
5053
|
-
|
|
5345
|
+
var OPTIONAL_VALUE_FLAGS = /* @__PURE__ */ new Set(["--debug", "--only-changed", "--shuffle", "-u", "--update-snapshots"]);
|
|
5346
|
+
var VARIADIC_FLAGS = /* @__PURE__ */ new Set(["--project"]);
|
|
5347
|
+
var NARROWING_FLAGS = ["--last-failed", "--only-changed", "--test-list", "--test-list-invert"];
|
|
5348
|
+
function parseTestArguments(argv) {
|
|
5054
5349
|
const args = argv.slice(2);
|
|
5350
|
+
const files = [];
|
|
5351
|
+
const options = /* @__PURE__ */ new Map();
|
|
5352
|
+
if (args.find((arg) => !arg.startsWith("-")) === "merge-reports") return { files, options };
|
|
5055
5353
|
const testIdx = args.indexOf("test");
|
|
5056
5354
|
const rest = testIdx >= 0 ? args.slice(testIdx + 1) : args;
|
|
5057
|
-
const files = [];
|
|
5058
5355
|
for (let i = 0; i < rest.length; i++) {
|
|
5059
5356
|
const tok = rest[i];
|
|
5060
|
-
if (tok.startsWith("-")) {
|
|
5061
|
-
|
|
5357
|
+
if (!tok.startsWith("-")) {
|
|
5358
|
+
files.push(tok);
|
|
5062
5359
|
continue;
|
|
5063
5360
|
}
|
|
5064
|
-
|
|
5361
|
+
const eq = tok.indexOf("=");
|
|
5362
|
+
if (eq > 0) {
|
|
5363
|
+
options.set(tok.slice(0, eq), tok.slice(eq + 1));
|
|
5364
|
+
continue;
|
|
5365
|
+
}
|
|
5366
|
+
const next = rest[i + 1];
|
|
5367
|
+
if (REQUIRED_VALUE_FLAGS.has(tok) && next !== void 0) {
|
|
5368
|
+
options.set(tok, next);
|
|
5369
|
+
i++;
|
|
5370
|
+
} else if (OPTIONAL_VALUE_FLAGS.has(tok) && next !== void 0 && !next.startsWith("-")) {
|
|
5371
|
+
options.set(tok, next);
|
|
5372
|
+
i++;
|
|
5373
|
+
} else if (VARIADIC_FLAGS.has(tok)) {
|
|
5374
|
+
const values = [];
|
|
5375
|
+
while (rest[i + 1] !== void 0 && !rest[i + 1].startsWith("-")) values.push(rest[++i]);
|
|
5376
|
+
options.set(tok, values.join(","));
|
|
5377
|
+
} else {
|
|
5378
|
+
options.set(tok, true);
|
|
5379
|
+
}
|
|
5065
5380
|
}
|
|
5066
|
-
return files;
|
|
5381
|
+
return { files, options };
|
|
5382
|
+
}
|
|
5383
|
+
function detectCliFileFilters(argv = process.argv) {
|
|
5384
|
+
return parseTestArguments(argv).files;
|
|
5385
|
+
}
|
|
5386
|
+
function detectCliNarrowingFlags(argv = process.argv) {
|
|
5387
|
+
const { options } = parseTestArguments(argv);
|
|
5388
|
+
return [...options.keys()].filter((flag) => NARROWING_FLAGS.includes(flag)).map((flag) => flag.slice(2));
|
|
5389
|
+
}
|
|
5390
|
+
function detectCliGrep(argv = process.argv) {
|
|
5391
|
+
const { options } = parseTestArguments(argv);
|
|
5392
|
+
const valueOf = (...names) => names.map((name) => options.get(name)).find((v) => typeof v === "string" && v.length > 0);
|
|
5393
|
+
const grep = valueOf("--grep", "-g");
|
|
5394
|
+
const grepInvert = valueOf("--grep-invert", "-G");
|
|
5395
|
+
return { ...grep ? { grep } : {}, ...grepInvert ? { grepInvert } : {} };
|
|
5067
5396
|
}
|
|
5068
5397
|
|
|
5069
5398
|
// src/internal/support/selection-env.ts
|
|
@@ -5117,6 +5446,8 @@ var LIVE_FRAME_BYTE_RATE = 256 * 1024;
|
|
|
5117
5446
|
var MAX_LIVE_FRAME = 192 * 1024;
|
|
5118
5447
|
var MAX_LIVE_FRAME_BASE64 = 4 * Math.ceil(MAX_LIVE_FRAME / 3);
|
|
5119
5448
|
var MAX_LIVE_MESSAGE = MAX_LIVE_FRAME_BASE64 + 8 * 1024;
|
|
5449
|
+
var MAX_LIVE_CALL_LOG_LINES = 20;
|
|
5450
|
+
var LIVE_CALL_LOGS_PER_SECOND = 4;
|
|
5120
5451
|
var KEY = /^pdl_[A-Za-z0-9_-]{43}$/;
|
|
5121
5452
|
function isLiveViewKey(value) {
|
|
5122
5453
|
return typeof value === "string" && KEY.test(value);
|
|
@@ -5180,12 +5511,68 @@ function liveShareRefusal(status) {
|
|
|
5180
5511
|
return "Sharing stopped: the instance did not answer. Share the run again to try once more.";
|
|
5181
5512
|
}
|
|
5182
5513
|
}
|
|
5514
|
+
var MAX_LIVE_MASKS = 20;
|
|
5515
|
+
var MAX_LIVE_MASK_LENGTH = 200;
|
|
5516
|
+
var FRAME_CALLS = /* @__PURE__ */ new Set(["frameLocator", "contentFrame", "owner"]);
|
|
5517
|
+
var NARROWING_CALLS = /* @__PURE__ */ new Set(["filter", "and", "or", "visible", "first", "last", "nth"]);
|
|
5518
|
+
function liveMaskProblem(locator) {
|
|
5519
|
+
if (!locator.trim()) return "A masked element needs a locator.";
|
|
5520
|
+
if (locator.length > MAX_LIVE_MASK_LENGTH) return `A locator is ${MAX_LIVE_MASK_LENGTH} characters at most.`;
|
|
5521
|
+
const source = locator.trim();
|
|
5522
|
+
const chain = tryParseLocatorChain(source);
|
|
5523
|
+
if (!chain?.calls.length || NARROWING_CALLS.has(chain.calls[0].method)) {
|
|
5524
|
+
return `\`${source}\` is not a Playwright locator, such as getByLabel('IBAN').`;
|
|
5525
|
+
}
|
|
5526
|
+
if (chain.calls.some((call) => FRAME_CALLS.has(call.method))) {
|
|
5527
|
+
return `\`${source}\` enters a frame: masks are looked for in every frame of the page, so name the element alone, such as getByLabel('Card number'), or the iframe, such as locator('iframe#card').`;
|
|
5528
|
+
}
|
|
5529
|
+
return null;
|
|
5530
|
+
}
|
|
5531
|
+
function parseLiveMasks(value) {
|
|
5532
|
+
if (!Array.isArray(value) || value.length > MAX_LIVE_MASKS) return null;
|
|
5533
|
+
const locators = [];
|
|
5534
|
+
for (const locator of value) {
|
|
5535
|
+
if (typeof locator !== "string" || liveMaskProblem(locator)) return null;
|
|
5536
|
+
if (!locators.includes(locator.trim())) locators.push(locator.trim());
|
|
5537
|
+
}
|
|
5538
|
+
return locators;
|
|
5539
|
+
}
|
|
5183
5540
|
function isObject5(value) {
|
|
5184
5541
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5185
5542
|
}
|
|
5186
5543
|
function isInt2(value, min, max) {
|
|
5187
5544
|
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
5188
5545
|
}
|
|
5546
|
+
var LONGEST_REPEAT = 12;
|
|
5547
|
+
function sameLines(lines, a, b, size) {
|
|
5548
|
+
for (let k = 0; k < size; k++) if (lines[a + k] !== lines[b + k]) return false;
|
|
5549
|
+
return true;
|
|
5550
|
+
}
|
|
5551
|
+
function compressCallLog(lines) {
|
|
5552
|
+
const out = [];
|
|
5553
|
+
let i = 0;
|
|
5554
|
+
while (i < lines.length) {
|
|
5555
|
+
let size = 1;
|
|
5556
|
+
let count = 1;
|
|
5557
|
+
for (let length = 1; length <= LONGEST_REPEAT && i + length * 2 <= lines.length; length++) {
|
|
5558
|
+
let times = 1;
|
|
5559
|
+
while (i + length * (times + 1) <= lines.length && sameLines(lines, i, i + length * times, length)) times++;
|
|
5560
|
+
if (times > 1 && times * length > count * size) {
|
|
5561
|
+
size = length;
|
|
5562
|
+
count = times;
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
for (let k = 0; k < size; k++) {
|
|
5566
|
+
const line = lines[i + k];
|
|
5567
|
+
const indent = /^\s*/.exec(line)[0];
|
|
5568
|
+
if (count === 1) out.push(`${indent}- ${line.trim()}`);
|
|
5569
|
+
else if (k === 0) out.push(`${indent}${count} \xD7 ${line.trim()}`);
|
|
5570
|
+
else out.push(`${indent}${" ".repeat(`${count} \xD7 `.length - 2)}- ${line.trim()}`);
|
|
5571
|
+
}
|
|
5572
|
+
i += size * count;
|
|
5573
|
+
}
|
|
5574
|
+
return out;
|
|
5575
|
+
}
|
|
5189
5576
|
function parseInstanceToWorker(data) {
|
|
5190
5577
|
let value;
|
|
5191
5578
|
try {
|
|
@@ -5193,10 +5580,24 @@ function parseInstanceToWorker(data) {
|
|
|
5193
5580
|
} catch {
|
|
5194
5581
|
return null;
|
|
5195
5582
|
}
|
|
5196
|
-
if (!isObject5(value)
|
|
5583
|
+
if (!isObject5(value)) return null;
|
|
5584
|
+
if (value.type === "masks") return { type: "masks", locators: parseLiveMasks(value.locators) };
|
|
5585
|
+
if (value.type !== "viewers" || !isInt2(value.count, 0, 1e6)) return null;
|
|
5197
5586
|
return { type: "viewers", count: value.count };
|
|
5198
5587
|
}
|
|
5199
5588
|
|
|
5589
|
+
// src/internal/streaming/live-view-note.ts
|
|
5590
|
+
function ciLiveViewNote(why) {
|
|
5591
|
+
switch (why) {
|
|
5592
|
+
case "off":
|
|
5593
|
+
return "Live view off: nobody can watch this run's tests on the dashboard while it runs. A Project admin turns it on in Project \u2192 Settings \u2192 Live view, for the runs that start after.";
|
|
5594
|
+
case "unregistered":
|
|
5595
|
+
return "Live view unavailable: the reporter's global setup did not register this run as it started (wrapConfig adds it), so nobody can watch its tests on the dashboard while it runs.";
|
|
5596
|
+
case "streaming-off":
|
|
5597
|
+
return "Live view unavailable: this run does not stream its results (streaming: false), so nobody can watch its tests on the dashboard while it runs.";
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
|
|
5200
5601
|
// src/public/global-setup.ts
|
|
5201
5602
|
var path16 = __toESM(require("path"));
|
|
5202
5603
|
|
|
@@ -5393,7 +5794,7 @@ async function checkBaseUrl(target, options = {}) {
|
|
|
5393
5794
|
} else {
|
|
5394
5795
|
reason = outcome.error;
|
|
5395
5796
|
}
|
|
5396
|
-
if (attempt < attempts) await new Promise((
|
|
5797
|
+
if (attempt < attempts) await new Promise((resolve12) => setTimeout(resolve12, delayMs));
|
|
5397
5798
|
}
|
|
5398
5799
|
return `${reason} (${attempts} ${attempts === 1 ? "attempt" : "attempts"})`;
|
|
5399
5800
|
}
|
|
@@ -5431,7 +5832,7 @@ var path14 = __toESM(require("path"));
|
|
|
5431
5832
|
var http2 = __toESM(require("http"));
|
|
5432
5833
|
var https2 = __toESM(require("https"));
|
|
5433
5834
|
function backendRequest(url, init) {
|
|
5434
|
-
return new Promise((
|
|
5835
|
+
return new Promise((resolve12, reject) => {
|
|
5435
5836
|
const target = new URL(url);
|
|
5436
5837
|
const headers = { ...init.headers };
|
|
5437
5838
|
if (init.body !== void 0) headers["Content-Length"] = Buffer.byteLength(init.body);
|
|
@@ -5446,7 +5847,7 @@ function backendRequest(url, init) {
|
|
|
5446
5847
|
});
|
|
5447
5848
|
res.on(
|
|
5448
5849
|
"end",
|
|
5449
|
-
() =>
|
|
5850
|
+
() => resolve12({ status: res.statusCode ?? 0, headers: res.headers, text: Buffer.concat(chunks).toString("utf8") })
|
|
5450
5851
|
);
|
|
5451
5852
|
res.on("error", reject);
|
|
5452
5853
|
res.on("close", () => {
|
|
@@ -5520,8 +5921,8 @@ function backendManifestFetch(insecure) {
|
|
|
5520
5921
|
function instrumentationManifestUrls(baseUrl) {
|
|
5521
5922
|
const base = new URL(baseUrl);
|
|
5522
5923
|
const root = new URL(INSTRUMENTATION_MANIFEST_PATH, base).toString();
|
|
5523
|
-
const
|
|
5524
|
-
return
|
|
5924
|
+
const path37 = base.pathname.replace(/\/+$/, "");
|
|
5925
|
+
return path37 ? [new URL(`${path37}${INSTRUMENTATION_MANIFEST_PATH}`, base).toString(), root] : [root];
|
|
5525
5926
|
}
|
|
5526
5927
|
async function fetchInstrumentationManifest(baseUrl, fetchImpl = fetch) {
|
|
5527
5928
|
try {
|
|
@@ -5659,13 +6060,13 @@ function normalizeFunctionRanges(ranges) {
|
|
|
5659
6060
|
}
|
|
5660
6061
|
function normalizeCoveragePath(raw) {
|
|
5661
6062
|
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
5662
|
-
const
|
|
5663
|
-
if (!
|
|
5664
|
-
if (/(^|\/)node_modules\//.test(
|
|
5665
|
-
return
|
|
6063
|
+
const path37 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
6064
|
+
if (!path37 || path37.startsWith("../") || path37.startsWith("/") || /^[A-Za-z]:\//.test(path37)) return null;
|
|
6065
|
+
if (/(^|\/)node_modules\//.test(path37)) return null;
|
|
6066
|
+
return path37;
|
|
5666
6067
|
}
|
|
5667
|
-
function fileEntry(
|
|
5668
|
-
const file = { path:
|
|
6068
|
+
function fileEntry(path37, runtime, blob, functions = [], lines = []) {
|
|
6069
|
+
const file = { path: path37, runtime };
|
|
5669
6070
|
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
5670
6071
|
if (functions.length > 0) file.functions = functions;
|
|
5671
6072
|
if (lines.length > 0) file.lines = lines;
|
|
@@ -5684,11 +6085,11 @@ function sanitizeCoverage(raw) {
|
|
|
5684
6085
|
const files = [];
|
|
5685
6086
|
for (const item of raw.files) {
|
|
5686
6087
|
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
5687
|
-
const
|
|
5688
|
-
if (
|
|
6088
|
+
const path37 = normalizeCoveragePath(item.path);
|
|
6089
|
+
if (path37 === null) continue;
|
|
5689
6090
|
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
5690
6091
|
files.push(
|
|
5691
|
-
fileEntry(
|
|
6092
|
+
fileEntry(path37, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
5692
6093
|
);
|
|
5693
6094
|
}
|
|
5694
6095
|
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
@@ -5967,13 +6368,13 @@ var ServerCoverageCollector = class {
|
|
|
5967
6368
|
if (parts.length === 0 || this.closer.signal.aborted) {
|
|
5968
6369
|
return Promise.resolve({ files: [], incomplete: truncated || parts.length > 0 });
|
|
5969
6370
|
}
|
|
5970
|
-
return new Promise((
|
|
6371
|
+
return new Promise((resolve12) => {
|
|
5971
6372
|
const entry = {
|
|
5972
6373
|
pending: parts.length,
|
|
5973
6374
|
captures: [],
|
|
5974
6375
|
checksums: /* @__PURE__ */ new Map(),
|
|
5975
6376
|
incomplete: truncated,
|
|
5976
|
-
resolve:
|
|
6377
|
+
resolve: resolve12
|
|
5977
6378
|
};
|
|
5978
6379
|
for (const [origin, ids] of parts) this.enqueue(origin, { entry, ids });
|
|
5979
6380
|
});
|
|
@@ -6458,8 +6859,8 @@ function parseRouteKey(routeKey2) {
|
|
|
6458
6859
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
6459
6860
|
}
|
|
6460
6861
|
function patternToRegex(pattern) {
|
|
6461
|
-
const
|
|
6462
|
-
const escaped =
|
|
6862
|
+
const path37 = pattern.split("?")[0] ?? pattern;
|
|
6863
|
+
const escaped = path37.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
6463
6864
|
return new RegExp(`^${escaped}$`);
|
|
6464
6865
|
}
|
|
6465
6866
|
function suitePathEqual(a, b) {
|
|
@@ -6482,13 +6883,13 @@ function matchProbeItem(plan, test) {
|
|
|
6482
6883
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
6483
6884
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
6484
6885
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
6485
|
-
let
|
|
6886
|
+
let path37;
|
|
6486
6887
|
try {
|
|
6487
|
-
|
|
6888
|
+
path37 = new URL(url).pathname;
|
|
6488
6889
|
} catch {
|
|
6489
|
-
|
|
6890
|
+
path37 = url.split("?")[0] ?? url;
|
|
6490
6891
|
}
|
|
6491
|
-
return patternToRegex(pattern).test(
|
|
6892
|
+
return patternToRegex(pattern).test(path37);
|
|
6492
6893
|
}
|
|
6493
6894
|
function createProbeState() {
|
|
6494
6895
|
return { navigated: false, matches: 0 };
|
|
@@ -6510,10 +6911,10 @@ function isProbeMode() {
|
|
|
6510
6911
|
function loadPlan() {
|
|
6511
6912
|
if (planCache !== void 0) return planCache;
|
|
6512
6913
|
planCache = null;
|
|
6513
|
-
const
|
|
6514
|
-
if (
|
|
6914
|
+
const path37 = process.env[PIWI_PROBE_ENV.plan];
|
|
6915
|
+
if (path37) {
|
|
6515
6916
|
try {
|
|
6516
|
-
planCache = JSON.parse(fs16.readFileSync(
|
|
6917
|
+
planCache = JSON.parse(fs16.readFileSync(path37, "utf8"));
|
|
6517
6918
|
} catch {
|
|
6518
6919
|
planCache = null;
|
|
6519
6920
|
}
|
|
@@ -6530,17 +6931,27 @@ function probeItemForTest(test) {
|
|
|
6530
6931
|
function classifyProbeHandled(signals) {
|
|
6531
6932
|
const blank = signals.blankPage === true;
|
|
6532
6933
|
if ((signals.pageErrors ?? 0) > 0 || signals.backendError && blank) return "unhandled";
|
|
6533
|
-
|
|
6934
|
+
const backendDegraded = signals.backendError && signals.faultIsTheError !== true;
|
|
6935
|
+
return signals.consoleErrors > 0 || signals.dialogs > 0 || backendDegraded || blank ? "degraded" : "graceful";
|
|
6936
|
+
}
|
|
6937
|
+
var SELF_ERROR_FAULTS = /* @__PURE__ */ new Set(["throw", "status"]);
|
|
6938
|
+
function faultIsTheError(fault) {
|
|
6939
|
+
return SELF_ERROR_FAULTS.has(fault);
|
|
6940
|
+
}
|
|
6941
|
+
var RESOURCE_ERROR_TEXT = /^Failed to load resource\b/;
|
|
6942
|
+
function probeConsoleErrors(entries) {
|
|
6943
|
+
return entries.filter((e) => e.type === "error" && !(typeof e.text === "string" && RESOURCE_ERROR_TEXT.test(e.text))).length;
|
|
6534
6944
|
}
|
|
6535
6945
|
function outcomeFromStatus(status, applied) {
|
|
6536
6946
|
if (!applied) return "inconclusive";
|
|
6537
|
-
|
|
6947
|
+
if (status === "failed" || status === "timedOut") return "noticed";
|
|
6948
|
+
return status === "passed" ? "not-noticed" : "inconclusive";
|
|
6538
6949
|
}
|
|
6539
6950
|
function recordProbeOutcome(line) {
|
|
6540
|
-
const
|
|
6541
|
-
if (!
|
|
6951
|
+
const path37 = process.env[PIWI_PROBE_ENV.results];
|
|
6952
|
+
if (!path37) return;
|
|
6542
6953
|
try {
|
|
6543
|
-
fs16.appendFileSync(
|
|
6954
|
+
fs16.appendFileSync(path37, `${JSON.stringify(line)}
|
|
6544
6955
|
`);
|
|
6545
6956
|
} catch {
|
|
6546
6957
|
}
|
|
@@ -6830,23 +7241,26 @@ function dropOneField(body) {
|
|
|
6830
7241
|
} catch {
|
|
6831
7242
|
return body;
|
|
6832
7243
|
}
|
|
7244
|
+
let dropped = false;
|
|
6833
7245
|
if (Array.isArray(parsed2)) {
|
|
6834
7246
|
let key;
|
|
6835
7247
|
for (const el of parsed2) {
|
|
6836
7248
|
if (el && typeof el === "object" && !Array.isArray(el)) {
|
|
6837
7249
|
key ??= Object.keys(el)[0];
|
|
6838
|
-
if (key
|
|
7250
|
+
if (key && Object.prototype.hasOwnProperty.call(el, key)) {
|
|
7251
|
+
delete el[key];
|
|
7252
|
+
dropped = true;
|
|
7253
|
+
}
|
|
6839
7254
|
}
|
|
6840
7255
|
}
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
return JSON.stringify(obj);
|
|
7256
|
+
} else if (parsed2 && typeof parsed2 === "object") {
|
|
7257
|
+
const key = Object.keys(parsed2)[0];
|
|
7258
|
+
if (key) {
|
|
7259
|
+
delete parsed2[key];
|
|
7260
|
+
dropped = true;
|
|
7261
|
+
}
|
|
6848
7262
|
}
|
|
6849
|
-
return body;
|
|
7263
|
+
return dropped ? JSON.stringify(parsed2) : body;
|
|
6850
7264
|
}
|
|
6851
7265
|
function computeFault(fault, input2) {
|
|
6852
7266
|
const base = {
|
|
@@ -6923,7 +7337,7 @@ function originOf2(url) {
|
|
|
6923
7337
|
return null;
|
|
6924
7338
|
}
|
|
6925
7339
|
}
|
|
6926
|
-
var sleep = (ms) => new Promise((
|
|
7340
|
+
var sleep = (ms) => new Promise((resolve12) => setTimeout(resolve12, Math.max(0, ms)));
|
|
6927
7341
|
async function performRouteAction(route, action, startedAt) {
|
|
6928
7342
|
switch (action.kind) {
|
|
6929
7343
|
case "abort":
|
|
@@ -7214,13 +7628,23 @@ function recordFlakeResult(line) {
|
|
|
7214
7628
|
}
|
|
7215
7629
|
}
|
|
7216
7630
|
|
|
7631
|
+
// src/internal/support/playwright-version.ts
|
|
7632
|
+
function playwrightVersionAtLeast(version, minimum) {
|
|
7633
|
+
const have = version ? /^(\d+)\.(\d+)/.exec(version) : null;
|
|
7634
|
+
const want = /^(\d+)\.(\d+)/.exec(minimum);
|
|
7635
|
+
if (!have || !want) return false;
|
|
7636
|
+
const major = Number(have[1]);
|
|
7637
|
+
const wantMajor = Number(want[1]);
|
|
7638
|
+
return major > wantMajor || major === wantMajor && Number(have[2]) >= Number(want[2]);
|
|
7639
|
+
}
|
|
7640
|
+
|
|
7217
7641
|
// src/public/config-wrapper.ts
|
|
7218
7642
|
var PIWI_MODULE = "@piwitests/reporter";
|
|
7219
7643
|
var CAPTURE_DEFAULTS = {
|
|
7220
7644
|
screenshot: "only-on-failure",
|
|
7221
7645
|
trace: "retain-on-failure"
|
|
7222
7646
|
};
|
|
7223
|
-
var TRACE_SNAPSHOTS_MIN =
|
|
7647
|
+
var TRACE_SNAPSHOTS_MIN = "1.63";
|
|
7224
7648
|
function installedPlaywrightVersion() {
|
|
7225
7649
|
try {
|
|
7226
7650
|
const nodeRequire = require;
|
|
@@ -7230,18 +7654,11 @@ function installedPlaywrightVersion() {
|
|
|
7230
7654
|
}
|
|
7231
7655
|
}
|
|
7232
7656
|
var readPlaywrightVersion = installedPlaywrightVersion;
|
|
7233
|
-
function supportsTraceSnapshots(version) {
|
|
7234
|
-
const match = version ? /^(\d+)\.(\d+)/.exec(version) : null;
|
|
7235
|
-
if (!match) return false;
|
|
7236
|
-
const major = Number(match[1]);
|
|
7237
|
-
const minor = Number(match[2]);
|
|
7238
|
-
return major > TRACE_SNAPSHOTS_MIN.major || major === TRACE_SNAPSHOTS_MIN.major && minor >= TRACE_SNAPSHOTS_MIN.minor;
|
|
7239
|
-
}
|
|
7240
7657
|
function applyCaptureDefaults(use, piwiOptions) {
|
|
7241
7658
|
delete process.env[PIWI_DEFAULTED_CAPTURE_ENV];
|
|
7242
7659
|
const enabled = piwiOptions?.defaultCapture ?? readBool(process.env[PIWI_ENV_KEYS.defaultCapture]) ?? true;
|
|
7243
7660
|
if (!enabled) return use;
|
|
7244
|
-
const ariaSnapshots =
|
|
7661
|
+
const ariaSnapshots = playwrightVersionAtLeast(readPlaywrightVersion(), TRACE_SNAPSHOTS_MIN);
|
|
7245
7662
|
const traceValue = ariaSnapshots ? { mode: CAPTURE_DEFAULTS.trace, snapshots: { dom: true, aria: true } } : CAPTURE_DEFAULTS.trace;
|
|
7246
7663
|
const defaults = [
|
|
7247
7664
|
{ key: "screenshot", value: CAPTURE_DEFAULTS.screenshot, display: `screenshot: '${CAPTURE_DEFAULTS.screenshot}'` },
|
|
@@ -7308,79 +7725,6 @@ function wrapConfig(config, piwiOptions) {
|
|
|
7308
7725
|
};
|
|
7309
7726
|
}
|
|
7310
7727
|
|
|
7311
|
-
// ../core/src/status-classify.ts
|
|
7312
|
-
function mergeAnnotations(test, result) {
|
|
7313
|
-
const out = [];
|
|
7314
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7315
|
-
for (const list of [test.annotations, result.annotations]) {
|
|
7316
|
-
for (const a of list ?? []) {
|
|
7317
|
-
const key = `${a.type}\0${a.description ?? ""}`;
|
|
7318
|
-
if (seen.has(key)) continue;
|
|
7319
|
-
seen.add(key);
|
|
7320
|
-
out.push(a.description === void 0 ? { type: a.type } : { type: a.type, description: a.description });
|
|
7321
|
-
}
|
|
7322
|
-
}
|
|
7323
|
-
return out;
|
|
7324
|
-
}
|
|
7325
|
-
function expectsFailure(annotations) {
|
|
7326
|
-
if (annotations.some((a) => a.type === "skip" || a.type === "fixme")) return false;
|
|
7327
|
-
return annotations.some((a) => a.type === "fail");
|
|
7328
|
-
}
|
|
7329
|
-
var EXPECTED_FAILURE_PASSED_MESSAGE = "Expected to fail, but passed.";
|
|
7330
|
-
function expectedFailureError(rawStatus, annotations) {
|
|
7331
|
-
return rawStatus === "passed" && expectsFailure(annotations) ? EXPECTED_FAILURE_PASSED_MESSAGE : null;
|
|
7332
|
-
}
|
|
7333
|
-
function classifyStatus(rawStatus, annotations) {
|
|
7334
|
-
if (rawStatus === "skipped") {
|
|
7335
|
-
const intentional = annotations.some((a) => a.type === "skip" || a.type === "fixme");
|
|
7336
|
-
return intentional ? "skipped" : "didnotrun";
|
|
7337
|
-
}
|
|
7338
|
-
if (expectsFailure(annotations)) {
|
|
7339
|
-
if (rawStatus === "failed") return "passed";
|
|
7340
|
-
if (rawStatus === "passed") return "failed";
|
|
7341
|
-
}
|
|
7342
|
-
return rawStatus;
|
|
7343
|
-
}
|
|
7344
|
-
function resolveUnrunReason(runStatus, opts) {
|
|
7345
|
-
if (runStatus === "timedout") return "global-timeout";
|
|
7346
|
-
if (opts.maxFailures > 0 && opts.failures >= opts.maxFailures) return "max-failures";
|
|
7347
|
-
return "interrupted";
|
|
7348
|
-
}
|
|
7349
|
-
function fileOf(location) {
|
|
7350
|
-
return location.replace(/:\d+:\d+$/, "");
|
|
7351
|
-
}
|
|
7352
|
-
function suiteKey(suitePath) {
|
|
7353
|
-
return (suitePath ?? []).join("");
|
|
7354
|
-
}
|
|
7355
|
-
function sharedPrefixDepth(a, b) {
|
|
7356
|
-
let depth = 0;
|
|
7357
|
-
while (depth < a.length && depth < b.length && a[depth] === b[depth]) depth++;
|
|
7358
|
-
return depth;
|
|
7359
|
-
}
|
|
7360
|
-
function linkBlockedTests(cases) {
|
|
7361
|
-
const blockers = cases.filter((c) => c.status === "failed" || c.status === "timedOut" || c.status === "timedout");
|
|
7362
|
-
if (blockers.length === 0) return;
|
|
7363
|
-
for (const c of cases) {
|
|
7364
|
-
if (c.didNotRunReason !== "previous-failure") continue;
|
|
7365
|
-
const file = fileOf(c.location);
|
|
7366
|
-
const inFile = blockers.filter((b) => fileOf(b.location) === file);
|
|
7367
|
-
if (inFile.length === 0) continue;
|
|
7368
|
-
const key = suiteKey(c.suitePath);
|
|
7369
|
-
let blocker = inFile.find((b) => suiteKey(b.suitePath) === key);
|
|
7370
|
-
if (!blocker) {
|
|
7371
|
-
let bestDepth = 0;
|
|
7372
|
-
for (const b of inFile) {
|
|
7373
|
-
const depth = sharedPrefixDepth(c.suitePath ?? [], b.suitePath ?? []);
|
|
7374
|
-
if (depth > bestDepth) {
|
|
7375
|
-
bestDepth = depth;
|
|
7376
|
-
blocker = b;
|
|
7377
|
-
}
|
|
7378
|
-
}
|
|
7379
|
-
}
|
|
7380
|
-
if (blocker) c.blockedBy = blocker.location;
|
|
7381
|
-
}
|
|
7382
|
-
}
|
|
7383
|
-
|
|
7384
7728
|
// ../core/src/test-meta.ts
|
|
7385
7729
|
var PIWI_ANNOTATION_PREFIX = "piwi:";
|
|
7386
7730
|
var TEST_PRIORITIES = ["critical", "high", "medium", "low"];
|
|
@@ -7656,10 +8000,17 @@ function joinErrorMessages(errors) {
|
|
|
7656
8000
|
const dedupeKey = msg.trim().slice(0, 200);
|
|
7657
8001
|
if (seen.has(dedupeKey)) continue;
|
|
7658
8002
|
seen.add(dedupeKey);
|
|
7659
|
-
parts.push(msg);
|
|
8003
|
+
parts.push(hideSecretsInError(msg));
|
|
7660
8004
|
}
|
|
7661
8005
|
return parts.join("\n---\n") || null;
|
|
7662
8006
|
}
|
|
8007
|
+
function locatedError(errors) {
|
|
8008
|
+
for (const error of errors ?? []) {
|
|
8009
|
+
const file = error?.location?.file;
|
|
8010
|
+
if (error && typeof file === "string" && file.length > 0) return error;
|
|
8011
|
+
}
|
|
8012
|
+
return void 0;
|
|
8013
|
+
}
|
|
7663
8014
|
function appendErrorLocation(text2, location) {
|
|
7664
8015
|
if (!location?.file || /\n[ \t]+at\s/.test(text2)) return text2;
|
|
7665
8016
|
return `${text2}
|
|
@@ -7670,7 +8021,7 @@ function appendErrorLocation(text2, location) {
|
|
|
7670
8021
|
function buildErrorText(result, base = process.cwd()) {
|
|
7671
8022
|
const text2 = joinErrorMessages(result.errors);
|
|
7672
8023
|
if (!text2) return null;
|
|
7673
|
-
const loc = result.error?.location;
|
|
8024
|
+
const loc = locatedError([result.error, ...result.errors ?? []])?.location;
|
|
7674
8025
|
if (!loc?.file) return text2;
|
|
7675
8026
|
return appendErrorLocation(text2, { file: relativeTestPath(loc.file, base), line: loc.line, column: loc.column });
|
|
7676
8027
|
}
|
|
@@ -7802,8 +8153,8 @@ function ciBuildUrlFromMetadata(metadata) {
|
|
|
7802
8153
|
|
|
7803
8154
|
// src/internal/submit/run-submitter.ts
|
|
7804
8155
|
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
7805
|
-
function markSubmitFallback(run,
|
|
7806
|
-
run.metadata.ingestHealth = { submitFallback: { path:
|
|
8156
|
+
function markSubmitFallback(run, path37, reason) {
|
|
8157
|
+
run.metadata.ingestHealth = { submitFallback: { path: path37, reason } };
|
|
7807
8158
|
}
|
|
7808
8159
|
var RunSubmitter = class {
|
|
7809
8160
|
/**
|
|
@@ -7888,7 +8239,7 @@ var RunSubmitter = class {
|
|
|
7888
8239
|
if (sm?.enabled && sm?.runId != null) {
|
|
7889
8240
|
if (sm.lostResults) fallbackReason = "results-lost";
|
|
7890
8241
|
else {
|
|
7891
|
-
outcome = await this.tryFinishStreaming(run, overallStatus, duration, auth);
|
|
8242
|
+
outcome = await this.tryFinishStreaming(run, overallStatus, duration, auth, counts.flakyTests);
|
|
7892
8243
|
if (!outcome.done) fallbackReason = "finish-failed";
|
|
7893
8244
|
}
|
|
7894
8245
|
}
|
|
@@ -7958,15 +8309,16 @@ var RunSubmitter = class {
|
|
|
7958
8309
|
deliveryKey: this.deliveryKey
|
|
7959
8310
|
};
|
|
7960
8311
|
}
|
|
7961
|
-
async tryFinishStreaming(run, overallStatus, duration, auth) {
|
|
8312
|
+
async tryFinishStreaming(run, overallStatus, duration, auth, flakyTests) {
|
|
7962
8313
|
const sm = this.streamManager;
|
|
7963
8314
|
try {
|
|
7964
|
-
const flakyTests = run.testCases.filter((tc) => tc.status === "passed" && (tc.retries || 0) > 0).length;
|
|
7965
8315
|
const durations = run.testCases.filter((tc) => tc.duration != null).map((tc) => tc.duration);
|
|
7966
8316
|
await sm.uploadRemaining(run.testCases);
|
|
7967
8317
|
const finishBody = {
|
|
7968
8318
|
streamToken: sm.token,
|
|
7969
8319
|
status: overallStatus,
|
|
8320
|
+
// With `duration`, the span the run of a shard covers in its sharded run.
|
|
8321
|
+
startTime: run.startTime ?? void 0,
|
|
7970
8322
|
duration,
|
|
7971
8323
|
totalTests: run.totalTests,
|
|
7972
8324
|
passedTests: run.passedTests,
|
|
@@ -7992,7 +8344,7 @@ var RunSubmitter = class {
|
|
|
7992
8344
|
finishBody.shardTotal = run.shardInfo.total;
|
|
7993
8345
|
}
|
|
7994
8346
|
await this.httpClient.postJSON(`/api/test-runs/${sm.runId}/finish`, finishBody, auth);
|
|
7995
|
-
this.logger.
|
|
8347
|
+
this.logger.debug(`Successfully finalized streaming run #${sm.runId}`);
|
|
7996
8348
|
this.dropLocalCopies();
|
|
7997
8349
|
if (this.hasReports(run)) {
|
|
7998
8350
|
try {
|
|
@@ -8022,8 +8374,7 @@ var RunSubmitter = class {
|
|
|
8022
8374
|
return { done: true, output: this.buildOutput(response?.runId, response?.projectId, run, overallStatus) };
|
|
8023
8375
|
} catch (error) {
|
|
8024
8376
|
if (error instanceof HttpError && error.status === 401 && !auth) return this.stopForMissingAuth(run, payload);
|
|
8025
|
-
this.logger.warn(`
|
|
8026
|
-
this.logger.info("Falling back to JSON upload...");
|
|
8377
|
+
this.logger.warn(`Could not upload the run's files (${errorMessage(error)}): sending its results alone.`);
|
|
8027
8378
|
return { done: false, output: null };
|
|
8028
8379
|
}
|
|
8029
8380
|
}
|
|
@@ -9016,7 +9367,7 @@ function parseWindowsProcesses(text2) {
|
|
|
9016
9367
|
}
|
|
9017
9368
|
function roleOf(comm, cmdline) {
|
|
9018
9369
|
const command = `${comm} ${cmdline}`;
|
|
9019
|
-
if (/workerProcessEntry/.test(cmdline)) return "worker";
|
|
9370
|
+
if (/workerProcessEntry|[\\/]playwright[\\/]lib[\\/]common[\\/]process\.js/.test(cmdline)) return "worker";
|
|
9020
9371
|
if (/loaderProcessEntry/.test(cmdline)) return "runner";
|
|
9021
9372
|
if (/(^|[\s/])ffmpeg/.test(command)) return "ffmpeg";
|
|
9022
9373
|
const type = /--type=([\w-]+)/.exec(cmdline)?.[1];
|
|
@@ -9469,11 +9820,21 @@ function runnableOf(info) {
|
|
|
9469
9820
|
function fallbackRunnable(window) {
|
|
9470
9821
|
return { phase: window ? "test" : "worker", fixture: null };
|
|
9471
9822
|
}
|
|
9823
|
+
var GLOBALS_FILES = [
|
|
9824
|
+
["lib", "globals.js"],
|
|
9825
|
+
["lib", "common", "globals.js"]
|
|
9826
|
+
];
|
|
9827
|
+
function playwrightGlobalsFile() {
|
|
9828
|
+
const packageJson = resolvePlaywrightPackageJson();
|
|
9829
|
+
if (!packageJson) return null;
|
|
9830
|
+
const root = path22.dirname(packageJson);
|
|
9831
|
+
return GLOBALS_FILES.map((parts) => path22.join(root, ...parts)).find((file) => fs21.existsSync(file)) ?? null;
|
|
9832
|
+
}
|
|
9472
9833
|
function playwrightTestInfoSource() {
|
|
9473
9834
|
try {
|
|
9474
|
-
const
|
|
9475
|
-
if (!
|
|
9476
|
-
const globals = (0, import_node_module2.createRequire)(
|
|
9835
|
+
const file = playwrightGlobalsFile();
|
|
9836
|
+
if (!file) return () => null;
|
|
9837
|
+
const globals = (0, import_node_module2.createRequire)(file)(file);
|
|
9477
9838
|
const read = globals.currentTestInfo;
|
|
9478
9839
|
if (typeof read !== "function") return () => null;
|
|
9479
9840
|
return () => {
|
|
@@ -9502,7 +9863,7 @@ function countHandles(types = TRACKED_HANDLE_TYPES) {
|
|
|
9502
9863
|
async function settledHandles(start, types = TRACKED_HANDLE_TYPES) {
|
|
9503
9864
|
const end = countHandles(types);
|
|
9504
9865
|
if (!Object.entries(end).some(([type, n]) => n > (start[type] ?? 0))) return end;
|
|
9505
|
-
await new Promise((
|
|
9866
|
+
await new Promise((resolve12) => setTimeout(resolve12, 0));
|
|
9506
9867
|
return countHandles(types);
|
|
9507
9868
|
}
|
|
9508
9869
|
function deepUserSite() {
|
|
@@ -9530,8 +9891,8 @@ function settleWithin(promise, ms) {
|
|
|
9530
9891
|
let timer;
|
|
9531
9892
|
return Promise.race([
|
|
9532
9893
|
promise.catch(() => void 0),
|
|
9533
|
-
new Promise((
|
|
9534
|
-
timer = setTimeout(
|
|
9894
|
+
new Promise((resolve12) => {
|
|
9895
|
+
timer = setTimeout(resolve12, ms);
|
|
9535
9896
|
})
|
|
9536
9897
|
]).finally(() => clearTimeout(timer));
|
|
9537
9898
|
}
|
|
@@ -9651,10 +10012,10 @@ var ResourceLedger = class {
|
|
|
9651
10012
|
trackHandle(object, entry) {
|
|
9652
10013
|
const info = this.testInfoSource();
|
|
9653
10014
|
const { phase, fixture } = info ? runnableOf(info) : fallbackRunnable(this.window);
|
|
9654
|
-
const
|
|
9655
|
-
this.handles.add(
|
|
9656
|
-
this.byHandle.set(object,
|
|
9657
|
-
return
|
|
10015
|
+
const tracked2 = { ...entry, phase, fixture, test: testRef2(info) ?? this.window };
|
|
10016
|
+
this.handles.add(tracked2);
|
|
10017
|
+
this.byHandle.set(object, tracked2);
|
|
10018
|
+
return tracked2;
|
|
9658
10019
|
}
|
|
9659
10020
|
onServer(server, site) {
|
|
9660
10021
|
const known = this.byHandle.get(server);
|
|
@@ -9697,7 +10058,7 @@ var ResourceLedger = class {
|
|
|
9697
10058
|
const own = () => [...this.handles].filter(
|
|
9698
10059
|
(entry) => entry.test?.id === testId && !entry.fixture?.worker && (entry.phase === "test" || entry.phase === "beforeEach" || entry.phase === "afterEach")
|
|
9699
10060
|
);
|
|
9700
|
-
if (own().some((entry) => entry.pending?.())) await new Promise((
|
|
10061
|
+
if (own().some((entry) => entry.pending?.())) await new Promise((resolve12) => setTimeout(resolve12, 0));
|
|
9701
10062
|
return own().filter((entry) => entry.running()).map((entry) => ({ type: entry.type, site: entry.site, ...entry.command ? { command: entry.command } : {} }));
|
|
9702
10063
|
}
|
|
9703
10064
|
/** Mark a page used: a locator was built on it. */
|
|
@@ -10174,10 +10535,10 @@ function buildResourceReport(input2) {
|
|
|
10174
10535
|
const all = [];
|
|
10175
10536
|
for (const censuses of byWorker.values()) {
|
|
10176
10537
|
censuses.sort((a, b) => a.at - b.at);
|
|
10177
|
-
const
|
|
10538
|
+
const tracked2 = /* @__PURE__ */ new Map();
|
|
10178
10539
|
for (const census of censuses) {
|
|
10179
10540
|
for (const birth of census.born) {
|
|
10180
|
-
|
|
10541
|
+
tracked2.set(birth.id, {
|
|
10181
10542
|
worker: census.worker,
|
|
10182
10543
|
birth,
|
|
10183
10544
|
bornCensus: census,
|
|
@@ -10189,13 +10550,13 @@ function buildResourceReport(input2) {
|
|
|
10189
10550
|
});
|
|
10190
10551
|
}
|
|
10191
10552
|
for (const close of census.closed) {
|
|
10192
|
-
const entry =
|
|
10553
|
+
const entry = tracked2.get(close.id);
|
|
10193
10554
|
if (!entry) continue;
|
|
10194
10555
|
entry.close = close;
|
|
10195
10556
|
if (close.used !== void 0) entry.used = close.used;
|
|
10196
10557
|
}
|
|
10197
10558
|
for (const open of census.open) {
|
|
10198
|
-
const entry =
|
|
10559
|
+
const entry = tracked2.get(open.id);
|
|
10199
10560
|
if (!entry) continue;
|
|
10200
10561
|
entry.lastOpenAt = census.at;
|
|
10201
10562
|
if (open.used !== void 0) entry.used = open.used;
|
|
@@ -10213,7 +10574,7 @@ function buildResourceReport(input2) {
|
|
|
10213
10574
|
}
|
|
10214
10575
|
const testCensuses = censuses.filter((census) => census.test !== null);
|
|
10215
10576
|
const workerEnded = censuses.some((census) => census.test === null);
|
|
10216
|
-
for (const entry of
|
|
10577
|
+
for (const entry of tracked2.values()) {
|
|
10217
10578
|
all.push(entry);
|
|
10218
10579
|
if (entry.birth.kind === "page" && entry.used === false && !entry.bornCensus.test?.failed) idle.push(entry);
|
|
10219
10580
|
const verdict = leakVerdict(entry, testCensuses, workerEnded);
|
|
@@ -10282,13 +10643,13 @@ function siteOf(birth) {
|
|
|
10282
10643
|
var keyOf = (worker, id) => `${worker}:${id}`;
|
|
10283
10644
|
function groupLeaks(leaked, all) {
|
|
10284
10645
|
const leaks = new Map(leaked.map((leak) => [keyOf(leak.tracked.worker, leak.tracked.birth.id), leak]));
|
|
10285
|
-
const
|
|
10646
|
+
const tracked2 = new Map(all.map((entry) => [keyOf(entry.worker, entry.birth.id), entry]));
|
|
10286
10647
|
const groups = /* @__PURE__ */ new Map();
|
|
10287
10648
|
const groupKey = (leak) => `${leak.scope}|${leak.tracked.birth.kind}|${whereOf(leak.tracked.birth)}`;
|
|
10288
10649
|
for (const leak of leaked) {
|
|
10289
10650
|
const { birth } = leak.tracked;
|
|
10290
10651
|
if (birth.kind === "context" && birth.implicit) continue;
|
|
10291
|
-
if (foldTarget(leak.tracked, leaks,
|
|
10652
|
+
if (foldTarget(leak.tracked, leaks, tracked2)) continue;
|
|
10292
10653
|
const key = groupKey(leak);
|
|
10293
10654
|
const group = groups.get(key) ?? {
|
|
10294
10655
|
verdict: "leaked",
|
|
@@ -10314,7 +10675,7 @@ function groupLeaks(leaked, all) {
|
|
|
10314
10675
|
}
|
|
10315
10676
|
for (const leak of leaked) {
|
|
10316
10677
|
if (leak.tracked.birth.kind !== "page") continue;
|
|
10317
|
-
const root = foldTarget(leak.tracked, leaks,
|
|
10678
|
+
const root = foldTarget(leak.tracked, leaks, tracked2);
|
|
10318
10679
|
const group = root ? groups.get(groupKey(root)) : void 0;
|
|
10319
10680
|
if (!group) continue;
|
|
10320
10681
|
group.pages = (group.pages ?? 0) + 1;
|
|
@@ -10325,11 +10686,11 @@ function groupLeaks(leaked, all) {
|
|
|
10325
10686
|
function addCpu(group, cpuMs) {
|
|
10326
10687
|
if (cpuMs > 0) group.afterTestCpuMs = (group.afterTestCpuMs ?? 0) + cpuMs;
|
|
10327
10688
|
}
|
|
10328
|
-
function foldTarget(entry, leaks,
|
|
10689
|
+
function foldTarget(entry, leaks, tracked2) {
|
|
10329
10690
|
let parentId = entry.birth.parent;
|
|
10330
10691
|
while (parentId !== null) {
|
|
10331
10692
|
const key = keyOf(entry.worker, parentId);
|
|
10332
|
-
const parent =
|
|
10693
|
+
const parent = tracked2.get(key);
|
|
10333
10694
|
if (!parent) return null;
|
|
10334
10695
|
if (!(parent.birth.kind === "context" && parent.birth.implicit)) return leaks.get(key) ?? null;
|
|
10335
10696
|
parentId = parent.birth.parent;
|
|
@@ -10667,17 +11028,17 @@ function nodeCpuTicks() {
|
|
|
10667
11028
|
return { busy, idle, iowait: 0, steal: 0, total: busy + idle };
|
|
10668
11029
|
}
|
|
10669
11030
|
function defaultPs() {
|
|
10670
|
-
return new Promise((
|
|
11031
|
+
return new Promise((resolve12, reject) => {
|
|
10671
11032
|
(0, import_node_child_process2.execFile)(
|
|
10672
11033
|
"ps",
|
|
10673
11034
|
["-A", "-o", "pid=,ppid=,rss=,time=,command="],
|
|
10674
11035
|
{ timeout: 2e3, maxBuffer: 8 * 1024 * 1024 },
|
|
10675
|
-
(error, stdout) => error ? reject(error) :
|
|
11036
|
+
(error, stdout) => error ? reject(error) : resolve12(stdout)
|
|
10676
11037
|
);
|
|
10677
11038
|
});
|
|
10678
11039
|
}
|
|
10679
11040
|
function defaultWindowsQuery() {
|
|
10680
|
-
return new Promise((
|
|
11041
|
+
return new Promise((resolve12, reject) => {
|
|
10681
11042
|
(0, import_node_child_process2.execFile)(
|
|
10682
11043
|
"powershell.exe",
|
|
10683
11044
|
[
|
|
@@ -10687,7 +11048,7 @@ function defaultWindowsQuery() {
|
|
|
10687
11048
|
"[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -ne $PID -and $_.ParentProcessId -ne $PID } | Select-Object ProcessId,ParentProcessId,WorkingSetSize,UserModeTime,KernelModeTime,Name,CommandLine | ConvertTo-Json -Compress"
|
|
10688
11049
|
],
|
|
10689
11050
|
{ timeout: 1e4, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
|
|
10690
|
-
(error, stdout) => error ? reject(error) :
|
|
11051
|
+
(error, stdout) => error ? reject(error) : resolve12(stdout)
|
|
10691
11052
|
);
|
|
10692
11053
|
});
|
|
10693
11054
|
}
|
|
@@ -11452,7 +11813,7 @@ function resourceTimeline(samples, censuses) {
|
|
|
11452
11813
|
let startedAt = hasSamples ? samples.startedAt : Infinity;
|
|
11453
11814
|
for (const { events } of workers) startedAt = Math.min(startedAt, events[0][0]);
|
|
11454
11815
|
const shift = hasSamples ? samples.startedAt - startedAt : 0;
|
|
11455
|
-
const
|
|
11816
|
+
const relative11 = (points, by) => points.map(([at, value]) => [Math.round(at + by), value]);
|
|
11456
11817
|
const trees = hasSamples ? workerTreesByIndex(samples, censuses) : [];
|
|
11457
11818
|
const perWorker = Math.max(
|
|
11458
11819
|
60,
|
|
@@ -11460,17 +11821,17 @@ function resourceTimeline(samples, censuses) {
|
|
|
11460
11821
|
);
|
|
11461
11822
|
return {
|
|
11462
11823
|
startedAt,
|
|
11463
|
-
cpuPct: hasSamples ? downsamplePoints(
|
|
11464
|
-
memoryBytes: hasSamples ? downsamplePoints(
|
|
11824
|
+
cpuPct: hasSamples ? downsamplePoints(relative11(samples.cpuPct, shift), TIMELINE_SERIES_CAP, "mean") : [],
|
|
11825
|
+
memoryBytes: hasSamples ? downsamplePoints(relative11(samples.memoryBytes, shift), TIMELINE_SERIES_CAP, "max") : [],
|
|
11465
11826
|
pages: workers.map(({ worker, events }) => {
|
|
11466
|
-
const points =
|
|
11827
|
+
const points = relative11(events, -startedAt);
|
|
11467
11828
|
const last = points.pop();
|
|
11468
11829
|
return { worker, points: [...downsamplePoints(points, TIMELINE_WORKER_POINTS_CAP - 1, "max"), last] };
|
|
11469
11830
|
}),
|
|
11470
11831
|
workers: trees.map((tree) => ({
|
|
11471
11832
|
worker: tree.worker,
|
|
11472
|
-
cpuCores: downsamplePoints(
|
|
11473
|
-
memoryBytes: downsamplePoints(
|
|
11833
|
+
cpuCores: downsamplePoints(relative11(tree.cpuCores, shift), perWorker, "mean"),
|
|
11834
|
+
memoryBytes: downsamplePoints(relative11(tree.memoryBytes, shift), perWorker, "max")
|
|
11474
11835
|
}))
|
|
11475
11836
|
};
|
|
11476
11837
|
}
|
|
@@ -11550,7 +11911,7 @@ var LiveResources = class {
|
|
|
11550
11911
|
const cursors = this.reset ? /* @__PURE__ */ new Map() : this.sent;
|
|
11551
11912
|
const sent2 = new Map(cursors);
|
|
11552
11913
|
let fresh = false;
|
|
11553
|
-
const
|
|
11914
|
+
const take2 = (key, points, cap, merge) => {
|
|
11554
11915
|
const out = since(points, cursors.get(key), cap, merge);
|
|
11555
11916
|
const last = points[points.length - 1];
|
|
11556
11917
|
if (out.length > 0 && last) {
|
|
@@ -11561,11 +11922,11 @@ var LiveResources = class {
|
|
|
11561
11922
|
};
|
|
11562
11923
|
const timeline = { startedAt, cpuPct: [], memoryBytes: [], pages: [], workers: [] };
|
|
11563
11924
|
if (samples) {
|
|
11564
|
-
timeline.cpuPct =
|
|
11565
|
-
timeline.memoryBytes =
|
|
11925
|
+
timeline.cpuPct = take2("cpu", samples.cpuPct, TIMELINE_SERIES_CAP, "mean");
|
|
11926
|
+
timeline.memoryBytes = take2("memory", samples.memoryBytes, TIMELINE_SERIES_CAP, "max");
|
|
11566
11927
|
for (const tree of workerTreesByIndex(samples, censuses)) {
|
|
11567
|
-
const cpuCores =
|
|
11568
|
-
const memoryBytes =
|
|
11928
|
+
const cpuCores = take2(`cores:${tree.worker}`, tree.cpuCores, TIMELINE_WORKER_POINTS_CAP, "mean");
|
|
11929
|
+
const memoryBytes = take2(`bytes:${tree.worker}`, tree.memoryBytes, TIMELINE_WORKER_POINTS_CAP, "max");
|
|
11569
11930
|
if (cpuCores.length > 0 || memoryBytes.length > 0) {
|
|
11570
11931
|
timeline.workers.push({ worker: tree.worker, cpuCores, memoryBytes });
|
|
11571
11932
|
}
|
|
@@ -12175,7 +12536,7 @@ function browserFunctionsInWorker() {
|
|
|
12175
12536
|
function browserFunctionCoverage(files) {
|
|
12176
12537
|
return sanitizeCoverage({
|
|
12177
12538
|
v: 1,
|
|
12178
|
-
files: [...files].map(([
|
|
12539
|
+
files: [...files].map(([path37, functions]) => ({ path: path37, runtime: "browser", functions }))
|
|
12179
12540
|
});
|
|
12180
12541
|
}
|
|
12181
12542
|
function newKnownFunctions(known, sent2) {
|
|
@@ -12188,19 +12549,19 @@ function newKnownFunctions(known, sent2) {
|
|
|
12188
12549
|
budget = MAX_COVERAGE_RANGES;
|
|
12189
12550
|
};
|
|
12190
12551
|
for (const [raw, ranges] of known) {
|
|
12191
|
-
const
|
|
12192
|
-
if (
|
|
12193
|
-
let named = sent2.get(
|
|
12552
|
+
const path37 = normalizeCoveragePath(raw);
|
|
12553
|
+
if (path37 === null) continue;
|
|
12554
|
+
let named = sent2.get(path37);
|
|
12194
12555
|
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
12195
12556
|
if (fresh.length === 0) continue;
|
|
12196
|
-
if (!named) sent2.set(
|
|
12557
|
+
if (!named) sent2.set(path37, named = /* @__PURE__ */ new Set());
|
|
12197
12558
|
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
12198
12559
|
while (fresh.length > 0) {
|
|
12199
12560
|
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
12200
12561
|
const part = fresh.slice(0, budget);
|
|
12201
12562
|
fresh = fresh.slice(part.length);
|
|
12202
12563
|
budget -= part.length;
|
|
12203
|
-
files.push({ path:
|
|
12564
|
+
files.push({ path: path37, allFunctions: part });
|
|
12204
12565
|
}
|
|
12205
12566
|
}
|
|
12206
12567
|
close();
|
|
@@ -12563,13 +12924,13 @@ function repositoryRoot(from) {
|
|
|
12563
12924
|
}
|
|
12564
12925
|
function codeReachRoots(configDir, configured) {
|
|
12565
12926
|
const repoRoot = repositoryRoot(configDir);
|
|
12566
|
-
const
|
|
12567
|
-
const global = Array.isArray(configured) ?
|
|
12927
|
+
const resolve12 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path25.resolve(configDir, r));
|
|
12928
|
+
const global = Array.isArray(configured) ? resolve12(configured) : resolve12(configured?.["*"]);
|
|
12568
12929
|
const byOrigin = /* @__PURE__ */ new Map();
|
|
12569
12930
|
if (configured && !Array.isArray(configured)) {
|
|
12570
12931
|
for (const [key, dirs] of Object.entries(configured)) {
|
|
12571
12932
|
const origin = key === "*" ? null : originOf3(key);
|
|
12572
|
-
const resolved =
|
|
12933
|
+
const resolved = resolve12(dirs);
|
|
12573
12934
|
if (origin && resolved.length) byOrigin.set(origin, resolved);
|
|
12574
12935
|
}
|
|
12575
12936
|
}
|
|
@@ -12601,9 +12962,9 @@ function resolveSourcePath(source, roots) {
|
|
|
12601
12962
|
const normalized = normalizeSourcePath(source);
|
|
12602
12963
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
12603
12964
|
if (path25.isAbsolute(normalized) && isFile(normalized)) return path25.resolve(normalized);
|
|
12604
|
-
const
|
|
12965
|
+
const relative11 = normalized.replace(/^\/+/, "");
|
|
12605
12966
|
for (const root of roots) {
|
|
12606
|
-
const candidate = path25.resolve(root,
|
|
12967
|
+
const candidate = path25.resolve(root, relative11);
|
|
12607
12968
|
if (isFile(candidate)) return candidate;
|
|
12608
12969
|
}
|
|
12609
12970
|
return null;
|
|
@@ -12692,10 +13053,10 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
12692
13053
|
const files = /* @__PURE__ */ new Map();
|
|
12693
13054
|
const known = /* @__PURE__ */ new Map();
|
|
12694
13055
|
const addTo = (into, file, functions) => {
|
|
12695
|
-
const
|
|
12696
|
-
const listed = into.get(
|
|
13056
|
+
const path37 = relativeToRepo(file, roots.repoRoot);
|
|
13057
|
+
const listed = into.get(path37);
|
|
12697
13058
|
if (listed) listed.push(...functions);
|
|
12698
|
-
else into.set(
|
|
13059
|
+
else into.set(path37, [...functions]);
|
|
12699
13060
|
};
|
|
12700
13061
|
const add2 = (file, functions) => addTo(files, file, functions);
|
|
12701
13062
|
const bases = viteBases(entries);
|
|
@@ -12850,8 +13211,8 @@ function onScriptParsed(page, capture, event) {
|
|
|
12850
13211
|
var READ_WAIT_MS = 5e3;
|
|
12851
13212
|
function readCoverage(page, cdp) {
|
|
12852
13213
|
let timer;
|
|
12853
|
-
const late = new Promise((
|
|
12854
|
-
timer = setTimeout(() =>
|
|
13214
|
+
const late = new Promise((resolve12) => {
|
|
13215
|
+
timer = setTimeout(() => resolve12([]), READ_WAIT_MS);
|
|
12855
13216
|
});
|
|
12856
13217
|
const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
|
|
12857
13218
|
(answer) => answer.result,
|
|
@@ -13047,14 +13408,14 @@ function namesLines(file) {
|
|
|
13047
13408
|
}
|
|
13048
13409
|
var GIT_BATCH = 500;
|
|
13049
13410
|
function gitHashObjects(root, paths) {
|
|
13050
|
-
return new Promise((
|
|
13411
|
+
return new Promise((resolve12) => {
|
|
13051
13412
|
const child = (0, import_node_child_process4.execFile)(
|
|
13052
13413
|
"git",
|
|
13053
13414
|
["hash-object", "--stdin-paths"],
|
|
13054
13415
|
{ cwd: root, encoding: "utf8", timeout: 3e4, windowsHide: true },
|
|
13055
13416
|
(error, stdout) => {
|
|
13056
13417
|
const ids = error ? [] : stdout.split("\n").filter(Boolean);
|
|
13057
|
-
|
|
13418
|
+
resolve12(ids.length === paths.length ? ids : null);
|
|
13058
13419
|
}
|
|
13059
13420
|
);
|
|
13060
13421
|
child.stdin?.on("error", () => {
|
|
@@ -13352,7 +13713,7 @@ var CoverageCollection = class {
|
|
|
13352
13713
|
}
|
|
13353
13714
|
let timer;
|
|
13354
13715
|
let finish;
|
|
13355
|
-
const done = new Promise((
|
|
13716
|
+
const done = new Promise((resolve12) => {
|
|
13356
13717
|
let settled = false;
|
|
13357
13718
|
finish = (server) => {
|
|
13358
13719
|
if (settled) return;
|
|
@@ -13361,7 +13722,7 @@ var CoverageCollection = class {
|
|
|
13361
13722
|
this.pending.delete(entry);
|
|
13362
13723
|
void Promise.resolve(browser).then((side) => {
|
|
13363
13724
|
this.assign(testCase, composeTestCoverage(server, side.files, this.options.settings, side.css));
|
|
13364
|
-
|
|
13725
|
+
resolve12();
|
|
13365
13726
|
});
|
|
13366
13727
|
};
|
|
13367
13728
|
});
|
|
@@ -13393,8 +13754,8 @@ var CoverageCollection = class {
|
|
|
13393
13754
|
let timer;
|
|
13394
13755
|
await Promise.race([
|
|
13395
13756
|
Promise.all(waiting),
|
|
13396
|
-
new Promise((
|
|
13397
|
-
timer = setTimeout(
|
|
13757
|
+
new Promise((resolve12) => {
|
|
13758
|
+
timer = setTimeout(resolve12, boundMs);
|
|
13398
13759
|
})
|
|
13399
13760
|
]);
|
|
13400
13761
|
clearTimeout(timer);
|
|
@@ -13457,6 +13818,15 @@ function testLocation(test, base) {
|
|
|
13457
13818
|
function testFile(test, base) {
|
|
13458
13819
|
return relativeTestPath(test.location.file, base);
|
|
13459
13820
|
}
|
|
13821
|
+
function uncapturedCalls(steps) {
|
|
13822
|
+
let calls = false;
|
|
13823
|
+
const captured = (list, inHook) => list.some((step) => {
|
|
13824
|
+
if (step.category === "pw:api" && !inHook) calls = true;
|
|
13825
|
+
return step.category === "fixture" && /\bpiwiCapture\b/.test(step.title) || captured(step.steps, inHook || step.category === "hook");
|
|
13826
|
+
});
|
|
13827
|
+
return !captured(steps, false) && calls;
|
|
13828
|
+
}
|
|
13829
|
+
var MAX_UNCAPTURED_NAMED = 5;
|
|
13460
13830
|
var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
13461
13831
|
constructor(rawOptions = {}) {
|
|
13462
13832
|
this.testCases = [];
|
|
@@ -13473,6 +13843,10 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13473
13843
|
this.plannedTests = [];
|
|
13474
13844
|
/** Ids of tests that actually reported via `onTestEnd`, to find the ones that never ran. */
|
|
13475
13845
|
this.reportedTestIds = /* @__PURE__ */ new Set();
|
|
13846
|
+
/** The spec files said to have run without the capture fixtures in a run that can stop (`noteUncaptured`). */
|
|
13847
|
+
this.uncapturedSpecs = /* @__PURE__ */ new Set();
|
|
13848
|
+
/** The live steps begun and not ended, by step, sent again when someone opens the run (`onWatched`). */
|
|
13849
|
+
this.openSteps = /* @__PURE__ */ new Map();
|
|
13476
13850
|
/** Per-test attempt history, keyed by `test.id`; snapshotted onto every attempt's payload. */
|
|
13477
13851
|
this.attemptsByTest = /* @__PURE__ */ new Map();
|
|
13478
13852
|
/** The folder test files are named relative to: the Playwright config's, set in `onBegin`. */
|
|
@@ -13573,13 +13947,14 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13573
13947
|
this.logger.debug("List mode (--list) detected \u2014 no run registered or report uploaded.");
|
|
13574
13948
|
return;
|
|
13575
13949
|
}
|
|
13576
|
-
|
|
13950
|
+
normalizeBoolEnv(process.env);
|
|
13951
|
+
const followed2 = followEditor([configDirFromConfig(config), process.cwd()], {
|
|
13577
13952
|
env: process.env,
|
|
13578
13953
|
argv: process.argv,
|
|
13579
13954
|
terminal: !!process.stdout.isTTY || !!process.stderr.isTTY
|
|
13580
13955
|
});
|
|
13581
|
-
if (
|
|
13582
|
-
const { editor } =
|
|
13956
|
+
if (followed2) {
|
|
13957
|
+
const { editor } = followed2;
|
|
13583
13958
|
this.logger.info(
|
|
13584
13959
|
`Following this run in ${editor.client ?? "the editor"} (pid ${editor.pid}), whose breakpoints stop it: ${PIWI_EDITOR_ENV}=off runs it alone.`
|
|
13585
13960
|
);
|
|
@@ -13622,11 +13997,13 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13622
13997
|
const rawConfig = config;
|
|
13623
13998
|
const grepRe = rawConfig.grep instanceof RegExp ? rawConfig.grep : void 0;
|
|
13624
13999
|
const grepInvertRe = rawConfig.grepInvert instanceof RegExp ? rawConfig.grepInvert : void 0;
|
|
13625
|
-
const
|
|
13626
|
-
const
|
|
14000
|
+
const cliGrep = detectCliGrep();
|
|
14001
|
+
const grep = (grepRe && grepRe.source !== ".*" ? grepRe.source : void 0) ?? cliGrep.grep;
|
|
14002
|
+
const grepInvert = grepInvertRe?.source ?? cliGrep.grepInvert;
|
|
13627
14003
|
const fileFilters = detectCliFileFilters();
|
|
14004
|
+
const narrowed = detectCliNarrowingFlags().length > 0;
|
|
13628
14005
|
const selection = readSelectionStamp();
|
|
13629
|
-
if (grep || grepInvert || fileFilters.length > 0 || selection) {
|
|
14006
|
+
if (grep || grepInvert || fileFilters.length > 0 || selection || narrowed) {
|
|
13630
14007
|
this.isFullRun = false;
|
|
13631
14008
|
this.filterDetails = {
|
|
13632
14009
|
...grep ? { grep } : {},
|
|
@@ -13634,7 +14011,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13634
14011
|
...fileFilters.length > 0 ? { files: fileFilters } : {},
|
|
13635
14012
|
...selection ? { selection } : {}
|
|
13636
14013
|
};
|
|
13637
|
-
this.logger.
|
|
14014
|
+
this.logger.debug(
|
|
13638
14015
|
selection ? `Selection run detected (${selection.key})` : "Partial run detected (filter active)"
|
|
13639
14016
|
);
|
|
13640
14017
|
}
|
|
@@ -13697,6 +14074,12 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13697
14074
|
// the dashboard shows the real total from the first render.
|
|
13698
14075
|
this.plannedTests.length
|
|
13699
14076
|
);
|
|
14077
|
+
if (this.streamManager) {
|
|
14078
|
+
this.streamManager.onWatched = () => {
|
|
14079
|
+
void this.liveResources?.tick();
|
|
14080
|
+
for (const event of this.openSteps.values()) this.streamManager?.queueEvent(event);
|
|
14081
|
+
};
|
|
14082
|
+
}
|
|
13700
14083
|
if (this.streamManager && this.options.captureResources !== false) {
|
|
13701
14084
|
const live = new LiveResources(
|
|
13702
14085
|
this.streamManager,
|
|
@@ -13709,7 +14092,6 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13709
14092
|
},
|
|
13710
14093
|
this.logger
|
|
13711
14094
|
);
|
|
13712
|
-
this.streamManager.onWatched = () => void live.tick();
|
|
13713
14095
|
live.start();
|
|
13714
14096
|
this.liveResources = live;
|
|
13715
14097
|
}
|
|
@@ -13718,18 +14100,16 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13718
14100
|
* Name the run's live view on the instance for the workers, which Playwright starts after this hook with this
|
|
13719
14101
|
* environment: the key the instance gave the global setup, for a project that turned its live view on, for this run
|
|
13720
14102
|
* alone. A CI run's workers stream with it at once; a run outside CI's hold it until a person shares the run (Share
|
|
13721
|
-
* live), and such a run without a key names why instead, for the surfaces that would share it
|
|
13722
|
-
* stream starts, which takes the setup file.
|
|
14103
|
+
* live), and such a run without a key names why instead, for the surfaces that would share it; a CI run without one
|
|
14104
|
+
* says why in its log (`ciLiveViewNote`). Read before the stream starts, which takes the setup file.
|
|
13723
14105
|
*/
|
|
13724
14106
|
shareLiveView() {
|
|
13725
14107
|
const ci = detectCi(process.env);
|
|
13726
14108
|
const setup = this.streamManager ? peekSetupInfo(this.options.projectName) : null;
|
|
13727
14109
|
if (!setup?.liveViewKey) {
|
|
13728
|
-
|
|
13729
|
-
|
|
13730
|
-
|
|
13731
|
-
);
|
|
13732
|
-
}
|
|
14110
|
+
const why = setup?.liveViewLocal ? "local" : setup ? "off" : "unregistered";
|
|
14111
|
+
if (!ci) process.env[LIVE_VIEW_ENV] = formatLiveViewMissing(why);
|
|
14112
|
+
else if (why !== "local") this.logger.info(ciLiveViewNote(this.streamManager ? why : "streaming-off"));
|
|
13733
14113
|
return;
|
|
13734
14114
|
}
|
|
13735
14115
|
process.env[LIVE_VIEW_ENV] = formatLiveViewEnv(this.httpClient.baseUrl, setup.runId, setup.liveViewKey);
|
|
@@ -13817,6 +14197,33 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13817
14197
|
}
|
|
13818
14198
|
return true;
|
|
13819
14199
|
}
|
|
14200
|
+
/**
|
|
14201
|
+
* In a run that can stop (breakpoints, or a run channel to an editor or the desktop app), say of the first spec file
|
|
14202
|
+
* whose test made Playwright calls without the capture fixtures, where the stops happen, that it never stops at a
|
|
14203
|
+
* breakpoint nor at Pause, which nothing else says; the others are named in one line as the run ends
|
|
14204
|
+
* (`noteOtherUncaptured`).
|
|
14205
|
+
*/
|
|
14206
|
+
noteUncaptured(test, result) {
|
|
14207
|
+
if (!process.env.PIWI_PAUSE_AT && !process.env[RUN_CHANNEL_ENV]) return;
|
|
14208
|
+
if (!uncapturedCalls(result.steps)) return;
|
|
14209
|
+
const file = testFile(test, this.testPathBase);
|
|
14210
|
+
if (this.uncapturedSpecs.has(file)) return;
|
|
14211
|
+
this.uncapturedSpecs.add(file);
|
|
14212
|
+
if (this.uncapturedSpecs.size > 1) return;
|
|
14213
|
+
this.logger.warn(
|
|
14214
|
+
`${file} ran without Piwi's capture fixtures, so its tests never stop at a breakpoint or at Pause: import \`test\` from your fixtures file (the one that extends piwiFixtures) instead of '@playwright/test'.`
|
|
14215
|
+
);
|
|
14216
|
+
}
|
|
14217
|
+
/** The spec files past the first that ran without the capture fixtures, in one line (`noteUncaptured`). */
|
|
14218
|
+
noteOtherUncaptured() {
|
|
14219
|
+
const others = [...this.uncapturedSpecs].slice(1);
|
|
14220
|
+
if (!others.length) return;
|
|
14221
|
+
const named = others.slice(0, MAX_UNCAPTURED_NAMED).join(", ");
|
|
14222
|
+
const more = others.length > MAX_UNCAPTURED_NAMED ? `, and ${others.length - MAX_UNCAPTURED_NAMED} more` : "";
|
|
14223
|
+
this.logger.warn(
|
|
14224
|
+
`${others.length} more spec file${others.length === 1 ? "" : "s"} ran without Piwi's capture fixtures, so their tests never stop at a breakpoint or at Pause either: ${named}${more}.`
|
|
14225
|
+
);
|
|
14226
|
+
}
|
|
13820
14227
|
/** Playwright reporter hook: called when a step (including hook/fixture) begins */
|
|
13821
14228
|
onStepBegin(test, _result, step) {
|
|
13822
14229
|
if (!this.enabled || !this.streamManager) return;
|
|
@@ -13824,7 +14231,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13824
14231
|
const cat = step.category;
|
|
13825
14232
|
const event = {
|
|
13826
14233
|
type: "step-begin",
|
|
13827
|
-
title: step.title,
|
|
14234
|
+
title: hideTypedStepTitle(step.title, cat),
|
|
13828
14235
|
subtitle: typeof step.subtitle === "string" && step.subtitle.length > 0 ? step.subtitle : null,
|
|
13829
14236
|
location: step.location ? `${step.location.file}:${step.location.line}:${step.location.column}` : "unknown",
|
|
13830
14237
|
stepCategory: cat,
|
|
@@ -13832,18 +14239,20 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13832
14239
|
workerIndex: workerIndexOf(_result),
|
|
13833
14240
|
startedAt: step.startTime instanceof Date ? step.startTime.getTime() : null
|
|
13834
14241
|
};
|
|
14242
|
+
this.openSteps.set(step, event);
|
|
13835
14243
|
this.streamManager?.queueBeginEvent(event);
|
|
13836
14244
|
}
|
|
13837
14245
|
/** Playwright reporter hook: called when a step (including hook/fixture) ends */
|
|
13838
14246
|
onStepEnd(test, _result, step) {
|
|
13839
14247
|
if (!this.enabled || !this.streamManager) return;
|
|
13840
14248
|
if (!_PiwiDashboardReporter.isLiveStep(step)) return;
|
|
14249
|
+
this.openSteps.delete(step);
|
|
13841
14250
|
const cat = step.category;
|
|
13842
14251
|
const workerIndex = workerIndexOf(_result);
|
|
13843
14252
|
const startedAt = step.startTime instanceof Date ? step.startTime.getTime() : null;
|
|
13844
14253
|
const event = {
|
|
13845
14254
|
type: "step-end",
|
|
13846
|
-
title: step.title,
|
|
14255
|
+
title: hideTypedStepTitle(step.title, cat),
|
|
13847
14256
|
subtitle: typeof step.subtitle === "string" && step.subtitle.length > 0 ? step.subtitle : null,
|
|
13848
14257
|
location: step.location ? `${step.location.file}:${step.location.line}:${step.location.column}` : "unknown",
|
|
13849
14258
|
status: step.error ? "failed" : "passed",
|
|
@@ -13869,7 +14278,10 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13869
14278
|
/** Playwright reporter hook: called when an individual test finishes */
|
|
13870
14279
|
onTestEnd(test, result) {
|
|
13871
14280
|
this.totalTests++;
|
|
14281
|
+
const worker = workerIndexOf(result);
|
|
14282
|
+
for (const [step, event] of this.openSteps) if (event.workerIndex === worker) this.openSteps.delete(step);
|
|
13872
14283
|
this.reportedTestIds.add(test.id);
|
|
14284
|
+
this.noteUncaptured(test, result);
|
|
13873
14285
|
const { suitePath, suiteConfig } = this.metadataCollector.getSuiteInfo(test);
|
|
13874
14286
|
const annotations = mergeAnnotations(test, result);
|
|
13875
14287
|
const status = classifyStatus(result.status, annotations);
|
|
@@ -13918,10 +14330,12 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
13918
14330
|
expectedStatus: test.expectedStatus ?? null
|
|
13919
14331
|
};
|
|
13920
14332
|
if (result.status === "failed" || result.status === "timedOut") {
|
|
13921
|
-
const failingLine = extractFailingLine(testCase.error, test.location.file, test.location.line);
|
|
14333
|
+
const failingLine = extractFailingLine(testCase.error, test.location.file, test.location.line, this.testPathBase);
|
|
13922
14334
|
const snippet = readSourceSnippet(test.location.file, test.location.line, 30, failingLine);
|
|
13923
14335
|
if (snippet) testCase.testSource = snippet;
|
|
13924
|
-
const frames = collectSourceFrames(testCase.error, test.location.file, test.location.line
|
|
14336
|
+
const frames = collectSourceFrames(testCase.error, test.location.file, test.location.line, {
|
|
14337
|
+
projectRoot: this.testPathBase
|
|
14338
|
+
});
|
|
13925
14339
|
if (frames.length) testCase.testSourceFrames = frames;
|
|
13926
14340
|
}
|
|
13927
14341
|
if (this.options.collectPerformanceMetrics && result.steps?.length > 0) {
|
|
@@ -14136,6 +14550,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
14136
14550
|
* ID and `externalId.onDuplicate` is `'fail'`.
|
|
14137
14551
|
*/
|
|
14138
14552
|
async onEnd(result) {
|
|
14553
|
+
this.noteOtherUncaptured();
|
|
14139
14554
|
const duplicates = this.externalIds?.duplicates.length ?? 0;
|
|
14140
14555
|
const failsOnDuplicates = duplicates > 0 && resolveOnDuplicate(this.options.externalId?.onDuplicate).onDuplicate === "fail";
|
|
14141
14556
|
const overridden = failsOnDuplicates && result?.status === "passed";
|
|
@@ -14278,7 +14693,7 @@ async function resolveSelection(options = {}) {
|
|
|
14278
14693
|
}
|
|
14279
14694
|
|
|
14280
14695
|
// src/internal/capture/capture-fixtures.ts
|
|
14281
|
-
var
|
|
14696
|
+
var path33 = __toESM(require("path"));
|
|
14282
14697
|
var import_node_zlib2 = require("zlib");
|
|
14283
14698
|
|
|
14284
14699
|
// ../picker-dom/src/probe.ts
|
|
@@ -14737,8 +15152,8 @@ function installPickerOverlay(arg) {
|
|
|
14737
15152
|
return root && root !== doc && root.host ? root.host : null;
|
|
14738
15153
|
};
|
|
14739
15154
|
const targetOf = (e) => {
|
|
14740
|
-
const
|
|
14741
|
-
const first =
|
|
15155
|
+
const path37 = typeof e.composedPath === "function" ? e.composedPath() : null;
|
|
15156
|
+
const first = path37 && path37.length ? path37[0] : null;
|
|
14742
15157
|
return first && first.nodeType === 1 ? first : e.target;
|
|
14743
15158
|
};
|
|
14744
15159
|
const buildChain = (raw) => {
|
|
@@ -15043,14 +15458,14 @@ function showAnchorPicker(arg) {
|
|
|
15043
15458
|
} catch {
|
|
15044
15459
|
shadowRoots.length = 0;
|
|
15045
15460
|
}
|
|
15046
|
-
const
|
|
15461
|
+
const within4 = (node2, scope) => {
|
|
15047
15462
|
if (scope === doc) return true;
|
|
15048
15463
|
for (let n = node2; n; n = parentOf(n)) if (n === scope) return true;
|
|
15049
15464
|
return false;
|
|
15050
15465
|
};
|
|
15051
15466
|
const queryAll = (scope, sel) => {
|
|
15052
15467
|
const found = Array.from(scope.querySelectorAll(sel));
|
|
15053
|
-
for (const sr of shadowRoots) if (
|
|
15468
|
+
for (const sr of shadowRoots) if (within4(sr.host, scope)) found.push(...Array.from(sr.querySelectorAll(sel)));
|
|
15054
15469
|
return found;
|
|
15055
15470
|
};
|
|
15056
15471
|
const leafCountable = !!leafTestId || roleOf2(el) === leafRole;
|
|
@@ -15439,8 +15854,22 @@ function showPauseBar(arg) {
|
|
|
15439
15854
|
cleanup();
|
|
15440
15855
|
g.__piwiPauseState = choice;
|
|
15441
15856
|
};
|
|
15857
|
+
const pageTakesEscape = (e) => {
|
|
15858
|
+
const path37 = typeof e.composedPath === "function" ? e.composedPath() : [e.target];
|
|
15859
|
+
const el = path37.find((node) => node && node.nodeType === 1);
|
|
15860
|
+
if (el && !bar.contains(el) && typeof el.closest === "function") {
|
|
15861
|
+
if (el.closest('dialog[open],[role="dialog"],[role="alertdialog"],[role="listbox"],[role="menu"]')) return true;
|
|
15862
|
+
const popup = '[aria-expanded="true"]:is([role="combobox"],[aria-haspopup]:not([aria-haspopup="false"]))';
|
|
15863
|
+
if (el.matches(popup)) return true;
|
|
15864
|
+
}
|
|
15865
|
+
try {
|
|
15866
|
+
return !!doc.querySelector("dialog:modal");
|
|
15867
|
+
} catch {
|
|
15868
|
+
return false;
|
|
15869
|
+
}
|
|
15870
|
+
};
|
|
15442
15871
|
const onKey = (e) => {
|
|
15443
|
-
if (e.key !== "Escape") return;
|
|
15872
|
+
if (e.key !== "Escape" || pageTakesEscape(e)) return;
|
|
15444
15873
|
e.preventDefault();
|
|
15445
15874
|
e.stopImmediatePropagation();
|
|
15446
15875
|
done("resume");
|
|
@@ -15457,7 +15886,9 @@ function showPauseBar(arg) {
|
|
|
15457
15886
|
...arg.record ? [
|
|
15458
15887
|
["Record here", "record", "Record steps in this page: the editor writes them before this line", false]
|
|
15459
15888
|
] : [],
|
|
15460
|
-
...arg.failure ? [] : [
|
|
15889
|
+
...arg.failure ? [] : [
|
|
15890
|
+
["Finish", "finish", "Run the test on without stopping at a breakpoint again in this attempt", false]
|
|
15891
|
+
],
|
|
15461
15892
|
...arg.end && !arg.failure ? [
|
|
15462
15893
|
["End the test", "end", "Skip the rest of this test: it is reported as skipped", false]
|
|
15463
15894
|
] : []
|
|
@@ -15493,6 +15924,102 @@ function takePauseState() {
|
|
|
15493
15924
|
return typeof state === "string" ? state : null;
|
|
15494
15925
|
}
|
|
15495
15926
|
|
|
15927
|
+
// ../picker-dom/src/overlay-stop.ts
|
|
15928
|
+
function showStopHighlight(elements, arg) {
|
|
15929
|
+
const g = globalThis;
|
|
15930
|
+
const doc = g.document;
|
|
15931
|
+
const gone = g.__piwiStopHighlightsGone instanceof Set ? g.__piwiStopHighlightsGone : /* @__PURE__ */ new Set();
|
|
15932
|
+
g.__piwiStopHighlightsGone = gone;
|
|
15933
|
+
if (!doc || !doc.documentElement) return 0;
|
|
15934
|
+
const drawn = doc.getElementById(`__piwi_stop_highlight_${arg.id}`);
|
|
15935
|
+
const targets = gone.has(arg.id) ? [] : elements.slice(0, 500);
|
|
15936
|
+
const shownIn = (layer2) => Array.from(layer2.children).filter((box) => box.dataset.box && box.style.display !== "none").length;
|
|
15937
|
+
const raise = (layer2) => {
|
|
15938
|
+
if (!layer2.hasAttribute("popover")) return;
|
|
15939
|
+
try {
|
|
15940
|
+
if (layer2.matches(":popover-open")) layer2.hidePopover();
|
|
15941
|
+
layer2.showPopover();
|
|
15942
|
+
} catch {
|
|
15943
|
+
layer2.removeAttribute("popover");
|
|
15944
|
+
}
|
|
15945
|
+
};
|
|
15946
|
+
const before = drawn?.__piwiTargets;
|
|
15947
|
+
if (drawn && before && before.length === targets.length && before.every((el, i) => el === targets[i])) {
|
|
15948
|
+
if (targets.length) raise(drawn);
|
|
15949
|
+
drawn.__piwiPlace?.();
|
|
15950
|
+
return shownIn(drawn);
|
|
15951
|
+
}
|
|
15952
|
+
drawn?.remove();
|
|
15953
|
+
if (!targets.length) return 0;
|
|
15954
|
+
const Z = 2147483600;
|
|
15955
|
+
const MONO = "ui-monospace,SFMono-Regular,Menlo,Consolas,monospace";
|
|
15956
|
+
const layer = doc.createElement("div");
|
|
15957
|
+
layer.id = `__piwi_stop_highlight_${arg.id}`;
|
|
15958
|
+
layer.setAttribute("data-piwi-stop-highlight", arg.kind);
|
|
15959
|
+
layer.setAttribute("aria-hidden", "true");
|
|
15960
|
+
layer.setAttribute("popover", "manual");
|
|
15961
|
+
layer.style.cssText = `position:fixed;inset:auto;top:0;left:0;width:0;height:0;margin:0;padding:0;border:0;background:transparent;overflow:visible;pointer-events:none;z-index:${Z - 1};`;
|
|
15962
|
+
layer.__piwiTargets = targets;
|
|
15963
|
+
const look = arg.kind === "call" ? "border:2px solid #a855f7;background:rgba(168,85,247,.14);border-radius:4px;box-shadow:0 0 0 1px rgba(255,255,255,.9),0 0 0 3px rgba(59,7,100,.55);" : "border:2px dashed #a78bfa;background:rgba(167,139,250,.15);border-radius:3px;box-shadow:0 0 0 1px rgba(255,255,255,.8);";
|
|
15964
|
+
const boxes = targets.map(() => {
|
|
15965
|
+
const box = doc.createElement("div");
|
|
15966
|
+
box.dataset.box = "1";
|
|
15967
|
+
box.style.cssText = `position:fixed;display:none;box-sizing:border-box;pointer-events:none;${look}`;
|
|
15968
|
+
layer.appendChild(box);
|
|
15969
|
+
return box;
|
|
15970
|
+
});
|
|
15971
|
+
const label = doc.createElement("div");
|
|
15972
|
+
label.style.cssText = `position:fixed;display:none;pointer-events:none;box-sizing:border-box;max-width:min(620px,92vw);background:#0b1120;color:#f9fafb;border:1px solid #7c3aed;border-radius:6px;padding:2px 7px;font:12px/1.45 ${MONO};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-shadow:0 2px 10px rgba(0,0,0,.4);`;
|
|
15973
|
+
label.textContent = arg.label;
|
|
15974
|
+
layer.appendChild(label);
|
|
15975
|
+
doc.documentElement.appendChild(layer);
|
|
15976
|
+
raise(layer);
|
|
15977
|
+
const place = () => {
|
|
15978
|
+
let first = null;
|
|
15979
|
+
targets.forEach((el, i) => {
|
|
15980
|
+
const box = boxes[i];
|
|
15981
|
+
const rect = el && el.isConnected && typeof el.getBoundingClientRect === "function" ? el.getBoundingClientRect() : null;
|
|
15982
|
+
if (!rect || rect.width === 0 && rect.height === 0) {
|
|
15983
|
+
box.style.display = "none";
|
|
15984
|
+
return;
|
|
15985
|
+
}
|
|
15986
|
+
box.style.display = "block";
|
|
15987
|
+
box.style.left = `${rect.left - 2}px`;
|
|
15988
|
+
box.style.top = `${rect.top - 2}px`;
|
|
15989
|
+
box.style.width = `${rect.width + 4}px`;
|
|
15990
|
+
box.style.height = `${rect.height + 4}px`;
|
|
15991
|
+
first ??= rect;
|
|
15992
|
+
});
|
|
15993
|
+
if (first && arg.label) {
|
|
15994
|
+
label.style.display = "block";
|
|
15995
|
+
const height = label.offsetHeight || 20;
|
|
15996
|
+
const above = first.top - 2 - height - 4;
|
|
15997
|
+
label.style.left = `${Math.max(4, first.left - 2)}px`;
|
|
15998
|
+
label.style.top = `${above >= 4 ? above : first.bottom + 6}px`;
|
|
15999
|
+
} else {
|
|
16000
|
+
label.style.display = "none";
|
|
16001
|
+
}
|
|
16002
|
+
};
|
|
16003
|
+
layer.__piwiPlace = place;
|
|
16004
|
+
const follow = () => {
|
|
16005
|
+
if (!layer.isConnected) return;
|
|
16006
|
+
place();
|
|
16007
|
+
g.requestAnimationFrame(follow);
|
|
16008
|
+
};
|
|
16009
|
+
follow();
|
|
16010
|
+
return shownIn(layer);
|
|
16011
|
+
}
|
|
16012
|
+
function removeStopHighlight(id) {
|
|
16013
|
+
const g = globalThis;
|
|
16014
|
+
const gone = g.__piwiStopHighlightsGone instanceof Set ? g.__piwiStopHighlightsGone : /* @__PURE__ */ new Set();
|
|
16015
|
+
g.__piwiStopHighlightsGone = gone;
|
|
16016
|
+
gone.add(id);
|
|
16017
|
+
const layer = g.document?.getElementById(`__piwi_stop_highlight_${id}`);
|
|
16018
|
+
if (!layer) return false;
|
|
16019
|
+
layer.remove();
|
|
16020
|
+
return true;
|
|
16021
|
+
}
|
|
16022
|
+
|
|
15496
16023
|
// ../picker-dom/src/anchor-alternatives.ts
|
|
15497
16024
|
var cssIdSelector = (id) => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(id) ? `#${id}` : `[id="${id.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"]`;
|
|
15498
16025
|
var ANCHOR_KIND_SCORES = { testid: 80, id: 74, labeledRole: 68, role: 62 };
|
|
@@ -15575,21 +16102,21 @@ var MAX_INDETERMINATE = 20;
|
|
|
15575
16102
|
var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
|
|
15576
16103
|
var LEVEL = `${STEP}(?:/${STEP})*`;
|
|
15577
16104
|
var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
|
|
15578
|
-
function isElementPath(
|
|
15579
|
-
return typeof
|
|
16105
|
+
function isElementPath(path37) {
|
|
16106
|
+
return typeof path37 === "string" && path37.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path37);
|
|
15580
16107
|
}
|
|
15581
16108
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
15582
16109
|
function elementRef(v) {
|
|
15583
16110
|
if (!v || typeof v !== "object") return null;
|
|
15584
|
-
const { path:
|
|
15585
|
-
if (!isElementPath(
|
|
15586
|
-
return { path:
|
|
16111
|
+
const { path: path37, label } = v;
|
|
16112
|
+
if (!isElementPath(path37)) return null;
|
|
16113
|
+
return { path: path37, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
|
|
15587
16114
|
}
|
|
15588
16115
|
function textPoint(v) {
|
|
15589
16116
|
if (!v || typeof v !== "object") return null;
|
|
15590
|
-
const { path:
|
|
16117
|
+
const { path: path37, offset } = v;
|
|
15591
16118
|
const at = offsetOf(offset);
|
|
15592
|
-
return isElementPath(
|
|
16119
|
+
return isElementPath(path37) && at !== null ? { path: path37, offset: at } : null;
|
|
15593
16120
|
}
|
|
15594
16121
|
function sanitizePageInteraction(raw) {
|
|
15595
16122
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -15892,9 +16419,9 @@ function unlessEnded(run, ended) {
|
|
|
15892
16419
|
if (!ended) return run;
|
|
15893
16420
|
return Promise.race([
|
|
15894
16421
|
run,
|
|
15895
|
-
new Promise((
|
|
15896
|
-
if (ended.aborted)
|
|
15897
|
-
else ended.addEventListener("abort", () =>
|
|
16422
|
+
new Promise((resolve12) => {
|
|
16423
|
+
if (ended.aborted) resolve12(void 0);
|
|
16424
|
+
else ended.addEventListener("abort", () => resolve12(void 0), { once: true });
|
|
15898
16425
|
})
|
|
15899
16426
|
]);
|
|
15900
16427
|
}
|
|
@@ -16040,6 +16567,7 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
16040
16567
|
}
|
|
16041
16568
|
|
|
16042
16569
|
// src/internal/capture/pause-at.ts
|
|
16570
|
+
var fs26 = __toESM(require("fs"));
|
|
16043
16571
|
var path29 = __toESM(require("path"));
|
|
16044
16572
|
|
|
16045
16573
|
// ../core/src/editor-send.ts
|
|
@@ -16067,9 +16595,12 @@ function isInsidePath(file) {
|
|
|
16067
16595
|
}
|
|
16068
16596
|
|
|
16069
16597
|
// src/internal/capture/pause-at.ts
|
|
16598
|
+
function lineKey(line, windows = process.platform === "win32") {
|
|
16599
|
+
return windows ? line.toLowerCase() : line;
|
|
16600
|
+
}
|
|
16070
16601
|
var PauseSet = class {
|
|
16071
16602
|
constructor(lines) {
|
|
16072
|
-
this.lines = lines;
|
|
16603
|
+
this.lines = new Set([...lines].map((line) => lineKey(line)));
|
|
16073
16604
|
}
|
|
16074
16605
|
get size() {
|
|
16075
16606
|
return this.lines.size;
|
|
@@ -16078,18 +16609,27 @@ var PauseSet = class {
|
|
|
16078
16609
|
matches(callerLocation) {
|
|
16079
16610
|
if (!callerLocation) return false;
|
|
16080
16611
|
const col = callerLocation.lastIndexOf(":");
|
|
16081
|
-
return col > 0 && this.lines.has(callerLocation.slice(0, col));
|
|
16612
|
+
return col > 0 && this.lines.has(lineKey(callerLocation.slice(0, col)));
|
|
16082
16613
|
}
|
|
16083
16614
|
};
|
|
16084
16615
|
var NO_BREAKPOINTS = new PauseSet(/* @__PURE__ */ new Set());
|
|
16085
16616
|
function callSiteFile(file, cwd) {
|
|
16086
16617
|
const slashed = file.replace(/\\/g, "/");
|
|
16087
16618
|
const absolute = path29.isAbsolute(file) || path29.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
|
|
16088
|
-
|
|
16619
|
+
const resolved = path29.resolve(cwd, absolute ? file : slashed);
|
|
16620
|
+
const real = realPath(resolved);
|
|
16621
|
+
let rel = real !== null && real !== resolved ? path29.relative(realPath(cwd) ?? cwd, real) : absolute ? path29.relative(cwd, file) : path29.normalize(slashed);
|
|
16089
16622
|
rel = rel.split(path29.sep).join("/").replace(/\\/g, "/");
|
|
16090
16623
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
16091
16624
|
return rel;
|
|
16092
16625
|
}
|
|
16626
|
+
function realPath(file) {
|
|
16627
|
+
try {
|
|
16628
|
+
return fs26.realpathSync(file);
|
|
16629
|
+
} catch {
|
|
16630
|
+
return null;
|
|
16631
|
+
}
|
|
16632
|
+
}
|
|
16093
16633
|
function stopCallSite(location, cwd = process.cwd()) {
|
|
16094
16634
|
if (!location.line) return null;
|
|
16095
16635
|
return `${callSiteFile(location.file, cwd)}:${location.line}:${location.column ?? 0}`;
|
|
@@ -16150,10 +16690,20 @@ function callSitePlace(callSite) {
|
|
|
16150
16690
|
function placeLine(place, cwd = process.cwd()) {
|
|
16151
16691
|
return `${callSiteFile(place.file, cwd)}:${place.line}`;
|
|
16152
16692
|
}
|
|
16153
|
-
function pauseGateFromTestInfo(testInfo) {
|
|
16693
|
+
function pauseGateFromTestInfo(testInfo, headless) {
|
|
16154
16694
|
const use = testInfo.project?.use ?? {};
|
|
16155
16695
|
const debug = testInfo._configInternal?.configCLIOverrides?.debug;
|
|
16156
|
-
return {
|
|
16696
|
+
return {
|
|
16697
|
+
ci: process.env.CI,
|
|
16698
|
+
headless: headless !== void 0 ? headless : use.headless,
|
|
16699
|
+
pwdebug: process.env.PWDEBUG,
|
|
16700
|
+
debug
|
|
16701
|
+
};
|
|
16702
|
+
}
|
|
16703
|
+
function mayPause(gate) {
|
|
16704
|
+
if (process.env.PIWI_PAUSE_AT) return true;
|
|
16705
|
+
if (gate.pwdebug && !["0", "false", "console"].includes(gate.pwdebug)) return true;
|
|
16706
|
+
return typeof gate.debug === "string" && gate.debug.length > 0;
|
|
16157
16707
|
}
|
|
16158
16708
|
function pauseSkipReason(gate) {
|
|
16159
16709
|
if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
|
|
@@ -16176,7 +16726,7 @@ function atBreakpoint(state, callSite) {
|
|
|
16176
16726
|
}
|
|
16177
16727
|
function atStop(state, callSite) {
|
|
16178
16728
|
if (callSite) {
|
|
16179
|
-
const reason = state.step ? "step" : state.pause ? "pause" : state.runTo !== null && callSiteLine(callSite) === state.runTo ? "run-to" : atBreakpoint(state, callSite) ? "breakpoint" : null;
|
|
16729
|
+
const reason = state.step ? "step" : state.pause ? "pause" : state.runTo !== null && lineKey(callSiteLine(callSite)) === lineKey(state.runTo) ? "run-to" : atBreakpoint(state, callSite) ? "breakpoint" : null;
|
|
16180
16730
|
if (reason) {
|
|
16181
16731
|
stopShown(state);
|
|
16182
16732
|
return { show: reason };
|
|
@@ -16207,6 +16757,9 @@ function goOn(state, answer) {
|
|
|
16207
16757
|
return wantsStops(state) ? "next" : "resume";
|
|
16208
16758
|
}
|
|
16209
16759
|
var ENDED_REASON = "Ended from Piwi";
|
|
16760
|
+
function takesEndSignal(version) {
|
|
16761
|
+
return playwrightVersionAtLeast(version, "1.62");
|
|
16762
|
+
}
|
|
16210
16763
|
var OPTIONS_AT = {
|
|
16211
16764
|
fill: 1,
|
|
16212
16765
|
selectOption: 1,
|
|
@@ -16264,11 +16817,23 @@ function syncInspectorSwitch() {
|
|
|
16264
16817
|
if (closed && process.env[NO_INSPECTOR_ENV] === void 0) {
|
|
16265
16818
|
process.env[NO_INSPECTOR_ENV] = "1";
|
|
16266
16819
|
inspectorSwitch.set = true;
|
|
16820
|
+
clearSwitchAtStop();
|
|
16267
16821
|
} else if (!closed && inspectorSwitch.set) {
|
|
16268
|
-
|
|
16269
|
-
inspectorSwitch.set = false;
|
|
16822
|
+
clearSwitch();
|
|
16270
16823
|
}
|
|
16271
16824
|
}
|
|
16825
|
+
function clearSwitch() {
|
|
16826
|
+
if (inspectorSwitch.set && process.env[NO_INSPECTOR_ENV] === "1") delete process.env[NO_INSPECTOR_ENV];
|
|
16827
|
+
inspectorSwitch.set = false;
|
|
16828
|
+
}
|
|
16829
|
+
var clearsAtStop = false;
|
|
16830
|
+
function clearSwitchAtStop() {
|
|
16831
|
+
if (clearsAtStop) return;
|
|
16832
|
+
clearsAtStop = true;
|
|
16833
|
+
process.prependListener("message", (message) => {
|
|
16834
|
+
if (message?.method === "__stop__") clearSwitch();
|
|
16835
|
+
});
|
|
16836
|
+
}
|
|
16272
16837
|
function markPausesWrapped() {
|
|
16273
16838
|
inspectorSwitch.wrapped = true;
|
|
16274
16839
|
syncInspectorSwitch();
|
|
@@ -16376,9 +16941,9 @@ var BarIntents = class {
|
|
|
16376
16941
|
/** Hand the bar an intent; false while it runs one, or holds one already. */
|
|
16377
16942
|
offer(intent) {
|
|
16378
16943
|
if (this.busy || this.queued) return false;
|
|
16379
|
-
const
|
|
16944
|
+
const take2 = this.take;
|
|
16380
16945
|
this.take = null;
|
|
16381
|
-
if (
|
|
16946
|
+
if (take2) take2(intent);
|
|
16382
16947
|
else this.queued = intent;
|
|
16383
16948
|
return true;
|
|
16384
16949
|
}
|
|
@@ -16388,8 +16953,8 @@ var BarIntents = class {
|
|
|
16388
16953
|
const queued = this.queued;
|
|
16389
16954
|
this.queued = null;
|
|
16390
16955
|
if (queued) return Promise.resolve(queued);
|
|
16391
|
-
return new Promise((
|
|
16392
|
-
this.take =
|
|
16956
|
+
return new Promise((resolve12) => {
|
|
16957
|
+
this.take = resolve12;
|
|
16393
16958
|
});
|
|
16394
16959
|
}
|
|
16395
16960
|
/** The bar got its answer: it takes no intent until it waits again. */
|
|
@@ -16404,7 +16969,7 @@ async function within(run, ms) {
|
|
|
16404
16969
|
try {
|
|
16405
16970
|
return await Promise.race([
|
|
16406
16971
|
run,
|
|
16407
|
-
new Promise((
|
|
16972
|
+
new Promise((resolve12) => timer = setTimeout(() => resolve12(void 0), ms))
|
|
16408
16973
|
]);
|
|
16409
16974
|
} finally {
|
|
16410
16975
|
clearTimeout(timer);
|
|
@@ -16437,13 +17002,68 @@ async function viewHere(page, probe, onPick, onRecord, ended, intents, onIntent,
|
|
|
16437
17002
|
}
|
|
16438
17003
|
}
|
|
16439
17004
|
}
|
|
17005
|
+
var REDRAW_MS = 500;
|
|
17006
|
+
var stopHighlights = 0;
|
|
17007
|
+
var FRAME_HOP = " >> internal:control=enter-frame >> ";
|
|
17008
|
+
async function frameOf(locator) {
|
|
17009
|
+
const own = locator;
|
|
17010
|
+
if (!own._frame || typeof own._selector !== "string") return null;
|
|
17011
|
+
const at = own._selector.lastIndexOf(FRAME_HOP);
|
|
17012
|
+
if (at < 0) return { frame: own._frame, selector: own._selector };
|
|
17013
|
+
const [iframe] = await own._frame.locator(own._selector.slice(0, at)).first().elementHandles();
|
|
17014
|
+
if (!iframe) return null;
|
|
17015
|
+
try {
|
|
17016
|
+
const frame = await iframe.contentFrame();
|
|
17017
|
+
return frame ? { frame, selector: own._selector.slice(at + FRAME_HOP.length) } : null;
|
|
17018
|
+
} finally {
|
|
17019
|
+
await iframe.dispose().catch(() => void 0);
|
|
17020
|
+
}
|
|
17021
|
+
}
|
|
17022
|
+
function highlightAtStop(page, locator, what, label) {
|
|
17023
|
+
const id = `${what}-${++stopHighlights}`;
|
|
17024
|
+
const arg = { id, kind: what, label };
|
|
17025
|
+
const drawnIn = /* @__PURE__ */ new Set();
|
|
17026
|
+
let hidden2 = false;
|
|
17027
|
+
let timer;
|
|
17028
|
+
const draw = async () => {
|
|
17029
|
+
const target = await frameOf(locator);
|
|
17030
|
+
if (hidden2) return 0;
|
|
17031
|
+
if (!target) return locator.evaluateAll(showStopHighlight, arg);
|
|
17032
|
+
drawnIn.add(target.frame);
|
|
17033
|
+
return target.frame.locator(target.selector).evaluateAll(showStopHighlight, arg);
|
|
17034
|
+
};
|
|
17035
|
+
const again = () => {
|
|
17036
|
+
if (hidden2 || page.isClosed?.()) return;
|
|
17037
|
+
timer = setTimeout(
|
|
17038
|
+
() => void internalCall(page, draw).catch(() => 0).then(again),
|
|
17039
|
+
REDRAW_MS
|
|
17040
|
+
);
|
|
17041
|
+
timer.unref?.();
|
|
17042
|
+
};
|
|
17043
|
+
const first = internalCall(page, draw).catch(() => 0);
|
|
17044
|
+
void first.then(again);
|
|
17045
|
+
const removeIn = (frame) => internalCall(page, async () => frame.evaluate(removeStopHighlight, id)).catch(() => false);
|
|
17046
|
+
return {
|
|
17047
|
+
shown: first.then((boxes) => boxes > 0),
|
|
17048
|
+
hide: async () => {
|
|
17049
|
+
hidden2 = true;
|
|
17050
|
+
clearTimeout(timer);
|
|
17051
|
+
try {
|
|
17052
|
+
const frames = drawnIn.size ? [...drawnIn] : [page.mainFrame()];
|
|
17053
|
+
const removed = await within(Promise.all(frames.map(removeIn)), TAKE_DOWN_MS);
|
|
17054
|
+
return removed?.includes(true) ?? false;
|
|
17055
|
+
} catch {
|
|
17056
|
+
return false;
|
|
17057
|
+
}
|
|
17058
|
+
}
|
|
17059
|
+
};
|
|
17060
|
+
}
|
|
16440
17061
|
async function pauseHere(page, point, probe, onPick, onRecord, ended, intents, onIntent) {
|
|
16441
|
-
let
|
|
17062
|
+
let highlight = null;
|
|
16442
17063
|
try {
|
|
16443
|
-
|
|
16444
|
-
|
|
16445
|
-
|
|
16446
|
-
shown2 = await within(highlighted, HIGHLIGHT_MS) ?? null;
|
|
17064
|
+
if (point.target) {
|
|
17065
|
+
highlight = point.target.highlight();
|
|
17066
|
+
await within(highlight.shown, HIGHLIGHT_MS);
|
|
16447
17067
|
}
|
|
16448
17068
|
const arg = {
|
|
16449
17069
|
place: pausePlace(point.location),
|
|
@@ -16502,17 +17122,10 @@ async function pauseHere(page, point, probe, onPick, onRecord, ended, intents, o
|
|
|
16502
17122
|
} catch {
|
|
16503
17123
|
return "resume";
|
|
16504
17124
|
} finally {
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
const hide = page.hideHighlight;
|
|
16510
|
-
if (typeof dispose === "function") await dispose.call(shown2);
|
|
16511
|
-
else if (typeof hide === "function") await hide.call(page);
|
|
16512
|
-
} catch {
|
|
16513
|
-
}
|
|
16514
|
-
};
|
|
16515
|
-
await within(takeDown(), TAKE_DOWN_MS);
|
|
17125
|
+
await Promise.all([
|
|
17126
|
+
ended?.aborted ? within(page.evaluate(takePauseState), TAKE_DOWN_MS).catch(() => void 0) : void 0,
|
|
17127
|
+
highlight?.hide()
|
|
17128
|
+
]);
|
|
16516
17129
|
}
|
|
16517
17130
|
}
|
|
16518
17131
|
|
|
@@ -16558,6 +17171,12 @@ function locatorOn(page, source) {
|
|
|
16558
17171
|
}
|
|
16559
17172
|
}
|
|
16560
17173
|
|
|
17174
|
+
// src/internal/capture/aria-ref.ts
|
|
17175
|
+
function locatorOfRef(page, ref) {
|
|
17176
|
+
const getByRef = page.getByRef;
|
|
17177
|
+
return typeof getByRef === "function" ? getByRef.call(page, ref) : page.locator(`aria-ref=${ref}`);
|
|
17178
|
+
}
|
|
17179
|
+
|
|
16561
17180
|
// src/internal/capture/pick-at.ts
|
|
16562
17181
|
var LINE = /^(\s*)- ([a-z][\w-]*)(?: "(?:[^"\\]|\\.)*")?((?: \[[^\]\n]*\])*)(?::.*)?$/;
|
|
16563
17182
|
var REF = /\[ref=(e\d+)\]/;
|
|
@@ -16597,18 +17216,16 @@ var PIWI_OVERLAY = '[id^="__piwi"], [id^="piwi-"][id$="-host"]';
|
|
|
16597
17216
|
var MAX_UNDER = 24;
|
|
16598
17217
|
var MAX_CHECKED = 8;
|
|
16599
17218
|
var NORMALIZED_SCORE = 60;
|
|
16600
|
-
var
|
|
17219
|
+
var PICK_AT_SINCE = "1.60";
|
|
17220
|
+
var PICK_AT_NEEDS = "Pick on the image needs Playwright 1.60 or later (the boxes of page.ariaSnapshot): update @playwright/test.";
|
|
16601
17221
|
var NO_ELEMENT = "No element there: pick an element of the page, not its background.";
|
|
16602
17222
|
var IN_IFRAME = "That point is in an iframe, whose elements Pick on the image does not reach: use Pick in the test's window.";
|
|
16603
|
-
function canPickAt(page) {
|
|
16604
|
-
return typeof page.getByRef === "function";
|
|
16605
|
-
}
|
|
16606
17223
|
var hitIn = ({ x, y, overlay, elements }) => {
|
|
16607
17224
|
let scope = globalThis.document;
|
|
16608
17225
|
let hit = null;
|
|
16609
17226
|
for (let depth = 0; scope && depth < 32; depth++) {
|
|
16610
|
-
const
|
|
16611
|
-
const top =
|
|
17227
|
+
const within4 = scope;
|
|
17228
|
+
const top = within4.elementsFromPoint(x, y).find((el) => el.getRootNode?.() === within4 && !el.closest?.(overlay));
|
|
16612
17229
|
if (!top) break;
|
|
16613
17230
|
hit = top;
|
|
16614
17231
|
scope = top.shadowRoot ?? null;
|
|
@@ -16622,14 +17239,12 @@ var hitIn = ({ x, y, overlay, elements }) => {
|
|
|
16622
17239
|
return -1;
|
|
16623
17240
|
};
|
|
16624
17241
|
async function pickAtPoint(page, probe, x, y) {
|
|
16625
|
-
if (!canPickAt(page)) return { refusal: PICK_AT_NEEDS };
|
|
16626
17242
|
const snapshot = await page.ariaSnapshot({ mode: "ai", boxes: true, timeout: 5e3 });
|
|
16627
17243
|
if (!/\[box=/.test(snapshot)) return { refusal: PICK_AT_NEEDS };
|
|
16628
17244
|
const under = boxesAt(snapshotBoxes(snapshot), x, y).slice(0, MAX_UNDER);
|
|
16629
|
-
const byRef2 = (ref) => page.getByRef(ref);
|
|
16630
17245
|
const handles = await Promise.all(
|
|
16631
17246
|
under.map(
|
|
16632
|
-
(box) =>
|
|
17247
|
+
(box) => locatorOfRef(page, box.ref).elementHandle({ timeout: 2e3 }).catch(() => null)
|
|
16633
17248
|
)
|
|
16634
17249
|
);
|
|
16635
17250
|
try {
|
|
@@ -16646,7 +17261,7 @@ async function pickAtPoint(page, probe, x, y) {
|
|
|
16646
17261
|
(el, overlay) => !!el.closest(overlay),
|
|
16647
17262
|
PIWI_OVERLAY
|
|
16648
17263
|
);
|
|
16649
|
-
if (!piwi) return await rank(page, probe,
|
|
17264
|
+
if (!piwi) return await rank(page, probe, locatorOfRef(page, box.ref), handle);
|
|
16650
17265
|
}
|
|
16651
17266
|
return { refusal: NO_ELEMENT };
|
|
16652
17267
|
} finally {
|
|
@@ -16694,31 +17309,360 @@ async function rank(page, probe, locator, handle) {
|
|
|
16694
17309
|
return { picked: { picked, alternatives: [picked, ...candidates.filter((c) => c !== found)], element } };
|
|
16695
17310
|
}
|
|
16696
17311
|
|
|
17312
|
+
// src/internal/capture/live-mask.ts
|
|
17313
|
+
var import_node_module3 = require("module");
|
|
17314
|
+
var MASKED_SPACING_MS = 500;
|
|
17315
|
+
var FRESH_MS = 1e3;
|
|
17316
|
+
var READS_KEPT = 4;
|
|
17317
|
+
var MAX_FRAMES = 32;
|
|
17318
|
+
var PAD = 2;
|
|
17319
|
+
var SAME_WITHIN = 1;
|
|
17320
|
+
var FRAME_READ_MS = 1e3;
|
|
17321
|
+
var MAX_FAILED_READS = 3;
|
|
17322
|
+
var MASK_RGB = [96, 96, 96];
|
|
17323
|
+
var TOO_MANY_FRAMES = `This page has more than ${MAX_FRAMES} frames: the live view cannot check them all for secret fields, so no image of it goes.`;
|
|
17324
|
+
var NO_CODEC = "A secret field shows on this page, and this Playwright gives the reporter nothing to mask it with: no image goes while it shows.";
|
|
17325
|
+
var UNREAD = "The live view cannot read where the secret fields of this page are, so no image of it goes until it can.";
|
|
17326
|
+
var ZOOMED = "This page is zoomed in: no image of it goes until it is not, its secret fields shown larger than read.";
|
|
17327
|
+
var UNREADABLE_MASKS = "The project masks elements this reporter cannot read: no image goes. Update @piwitests/reporter.";
|
|
17328
|
+
var codec;
|
|
17329
|
+
function jpegCodec() {
|
|
17330
|
+
if (codec !== void 0) return codec;
|
|
17331
|
+
codec = null;
|
|
17332
|
+
try {
|
|
17333
|
+
const playwright = resolvePlaywrightPackageJson();
|
|
17334
|
+
const bundle = playwright ? (0, import_node_module3.createRequire)(playwright)("playwright-core/lib/utilsBundle") : null;
|
|
17335
|
+
const found = bundle?.jpegjs;
|
|
17336
|
+
if (typeof found?.decode === "function" && typeof found.encode === "function") codec = found;
|
|
17337
|
+
} catch {
|
|
17338
|
+
}
|
|
17339
|
+
return codec;
|
|
17340
|
+
}
|
|
17341
|
+
function maskedIn(frame, masks) {
|
|
17342
|
+
let locator = frame.locator(SECRET_FIELD_SELECTOR);
|
|
17343
|
+
for (const source of masks) {
|
|
17344
|
+
const own = locatorOn(frame, source);
|
|
17345
|
+
if (!own) throw new Error(`The locator ${source} cannot be built.`);
|
|
17346
|
+
locator = locator.or(own);
|
|
17347
|
+
}
|
|
17348
|
+
return locator;
|
|
17349
|
+
}
|
|
17350
|
+
var unbuilt = (source) => `The project masks ${source}, which this Playwright cannot find elements by: no image of the page goes.`;
|
|
17351
|
+
var near = (a, b) => Math.abs(a - b) <= SAME_WITHIN;
|
|
17352
|
+
function readFrameRects(page, frame, masks) {
|
|
17353
|
+
return internalCall(
|
|
17354
|
+
page,
|
|
17355
|
+
() => maskedIn(frame, masks).evaluateAll((elements) => {
|
|
17356
|
+
const view3 = globalThis;
|
|
17357
|
+
const visual = view3.visualViewport;
|
|
17358
|
+
const layout = view3.document.scrollingElement;
|
|
17359
|
+
const zoomed = !!visual && (Math.abs(visual.offsetLeft) > 0.5 || Math.abs(visual.offsetTop) > 0.5 || (layout ? visual.width < layout.clientWidth - 1 || visual.height < layout.clientHeight - 1 : visual.scale > 1.001));
|
|
17360
|
+
return {
|
|
17361
|
+
rects: elements.map((element) => {
|
|
17362
|
+
const rect = element.getBoundingClientRect();
|
|
17363
|
+
return [rect.x, rect.y, rect.width, rect.height];
|
|
17364
|
+
}).filter(([, , width, height]) => width > 0 && height > 0),
|
|
17365
|
+
width: view3.innerWidth,
|
|
17366
|
+
height: view3.innerHeight,
|
|
17367
|
+
zoomed
|
|
17368
|
+
};
|
|
17369
|
+
})
|
|
17370
|
+
);
|
|
17371
|
+
}
|
|
17372
|
+
async function childOrigin(page, frame, inner) {
|
|
17373
|
+
return internalCall(page, async () => {
|
|
17374
|
+
const element = await frame.frameElement();
|
|
17375
|
+
try {
|
|
17376
|
+
const outer = await element.boundingBox();
|
|
17377
|
+
if (!outer) return null;
|
|
17378
|
+
const own = await element.evaluate((iframe) => {
|
|
17379
|
+
const el = iframe;
|
|
17380
|
+
const style = globalThis.getComputedStyle(iframe);
|
|
17381
|
+
const pad = (side) => parseFloat(side) || 0;
|
|
17382
|
+
return {
|
|
17383
|
+
left: el.clientLeft + pad(style.paddingLeft),
|
|
17384
|
+
top: el.clientTop + pad(style.paddingTop),
|
|
17385
|
+
width: el.clientWidth - pad(style.paddingLeft) - pad(style.paddingRight),
|
|
17386
|
+
height: el.clientHeight - pad(style.paddingTop) - pad(style.paddingBottom),
|
|
17387
|
+
offsetWidth: el.offsetWidth,
|
|
17388
|
+
offsetHeight: el.offsetHeight
|
|
17389
|
+
};
|
|
17390
|
+
});
|
|
17391
|
+
const drawnAsLaidOut = near(outer.width, own.offsetWidth) && near(outer.height, own.offsetHeight);
|
|
17392
|
+
const contentIsViewport = near(own.width, inner.width) && near(own.height, inner.height);
|
|
17393
|
+
if (!drawnAsLaidOut || !contentIsViewport) return { whole: outer };
|
|
17394
|
+
return { x: outer.x + own.left, y: outer.y + own.top };
|
|
17395
|
+
} finally {
|
|
17396
|
+
await element.dispose();
|
|
17397
|
+
}
|
|
17398
|
+
});
|
|
17399
|
+
}
|
|
17400
|
+
async function within2(run, ms) {
|
|
17401
|
+
let timer;
|
|
17402
|
+
try {
|
|
17403
|
+
return await Promise.race([
|
|
17404
|
+
run,
|
|
17405
|
+
new Promise((resolve12) => timer = setTimeout(() => resolve12(void 0), ms))
|
|
17406
|
+
]);
|
|
17407
|
+
} finally {
|
|
17408
|
+
clearTimeout(timer);
|
|
17409
|
+
}
|
|
17410
|
+
}
|
|
17411
|
+
async function wholeFrame(page, frame) {
|
|
17412
|
+
const box = await within2(
|
|
17413
|
+
internalCall(page, async () => {
|
|
17414
|
+
const element = await frame.frameElement();
|
|
17415
|
+
try {
|
|
17416
|
+
return await element.boundingBox();
|
|
17417
|
+
} finally {
|
|
17418
|
+
await element.dispose();
|
|
17419
|
+
}
|
|
17420
|
+
}),
|
|
17421
|
+
FRAME_READ_MS
|
|
17422
|
+
).catch(() => void 0);
|
|
17423
|
+
if (box === void 0) return null;
|
|
17424
|
+
return { rects: box ? [[box.x, box.y, box.width, box.height]] : [], width: 0, height: 0 };
|
|
17425
|
+
}
|
|
17426
|
+
async function readFrame(page, frame, masks) {
|
|
17427
|
+
try {
|
|
17428
|
+
const read = await within2(readFrameRects(page, frame, masks), FRAME_READ_MS);
|
|
17429
|
+
if (!read) return frame === page.mainFrame() ? null : await wholeFrame(page, frame);
|
|
17430
|
+
if (!read.rects.length || frame === page.mainFrame()) return read;
|
|
17431
|
+
const origin = await childOrigin(page, frame, read);
|
|
17432
|
+
if (!origin) return { ...read, rects: [] };
|
|
17433
|
+
if ("whole" in origin) {
|
|
17434
|
+
const { x, y, width, height } = origin.whole;
|
|
17435
|
+
return { ...read, rects: [[x, y, width, height]] };
|
|
17436
|
+
}
|
|
17437
|
+
return {
|
|
17438
|
+
...read,
|
|
17439
|
+
rects: read.rects.map(([left, top, width, height]) => [origin.x + left, origin.y + top, width, height])
|
|
17440
|
+
};
|
|
17441
|
+
} catch {
|
|
17442
|
+
return frame.isDetached() ? { rects: [], width: 0, height: 0 } : null;
|
|
17443
|
+
}
|
|
17444
|
+
}
|
|
17445
|
+
async function readBoxes(page, masks) {
|
|
17446
|
+
if (!masks) return UNREADABLE_MASKS;
|
|
17447
|
+
const frames = page.frames();
|
|
17448
|
+
if (frames.length > MAX_FRAMES) return TOO_MANY_FRAMES;
|
|
17449
|
+
for (const source of masks) if (!locatorOn(page.mainFrame(), source)) return unbuilt(source);
|
|
17450
|
+
const main = page.mainFrame();
|
|
17451
|
+
const read = await Promise.all(frames.map((frame) => readFrame(page, frame, masks)));
|
|
17452
|
+
if (read.some((frame) => frame === null)) return null;
|
|
17453
|
+
const top = read[frames.indexOf(main)];
|
|
17454
|
+
if (!top) return null;
|
|
17455
|
+
if (top.zoomed) return ZOOMED;
|
|
17456
|
+
return {
|
|
17457
|
+
boxes: read.flatMap(
|
|
17458
|
+
(frame) => frame.rects.map(([x, y, width, height]) => ({ x, y, width, height }))
|
|
17459
|
+
),
|
|
17460
|
+
viewport: { width: top.width, height: top.height }
|
|
17461
|
+
};
|
|
17462
|
+
}
|
|
17463
|
+
function sameBoxes(a, b) {
|
|
17464
|
+
return a.length === b.length && a.every((box, i) => {
|
|
17465
|
+
const other = b[i];
|
|
17466
|
+
return Math.abs(box.x - other.x) <= SAME_WITHIN && Math.abs(box.y - other.y) <= SAME_WITHIN && Math.abs(box.width - other.width) <= SAME_WITHIN && Math.abs(box.height - other.height) <= SAME_WITHIN;
|
|
17467
|
+
});
|
|
17468
|
+
}
|
|
17469
|
+
function jpegSize(data) {
|
|
17470
|
+
for (let i = 2; i + 9 < data.length; ) {
|
|
17471
|
+
if (data[i] !== 255) return null;
|
|
17472
|
+
const marker2 = data[i + 1];
|
|
17473
|
+
if (marker2 >= 192 && marker2 <= 195) return { height: data.readUInt16BE(i + 5), width: data.readUInt16BE(i + 7) };
|
|
17474
|
+
i += 2 + data.readUInt16BE(i + 2);
|
|
17475
|
+
}
|
|
17476
|
+
return null;
|
|
17477
|
+
}
|
|
17478
|
+
function shrink(image, width, height) {
|
|
17479
|
+
const data = new Uint8Array(width * height * 4);
|
|
17480
|
+
const sx = image.width / width;
|
|
17481
|
+
const sy = image.height / height;
|
|
17482
|
+
for (let y = 0; y < height; y++) {
|
|
17483
|
+
const y0 = Math.floor(y * sy);
|
|
17484
|
+
const y1 = Math.max(y0 + 1, Math.min(image.height, Math.floor((y + 1) * sy)));
|
|
17485
|
+
for (let x = 0; x < width; x++) {
|
|
17486
|
+
const x0 = Math.floor(x * sx);
|
|
17487
|
+
const x1 = Math.max(x0 + 1, Math.min(image.width, Math.floor((x + 1) * sx)));
|
|
17488
|
+
let r = 0;
|
|
17489
|
+
let g = 0;
|
|
17490
|
+
let b = 0;
|
|
17491
|
+
for (let yy = y0; yy < y1; yy++) {
|
|
17492
|
+
for (let xx = x0; xx < x1; xx++) {
|
|
17493
|
+
const i = (yy * image.width + xx) * 4;
|
|
17494
|
+
r += image.data[i];
|
|
17495
|
+
g += image.data[i + 1];
|
|
17496
|
+
b += image.data[i + 2];
|
|
17497
|
+
}
|
|
17498
|
+
}
|
|
17499
|
+
const n = (y1 - y0) * (x1 - x0);
|
|
17500
|
+
const o = (y * width + x) * 4;
|
|
17501
|
+
data[o] = r / n;
|
|
17502
|
+
data[o + 1] = g / n;
|
|
17503
|
+
data[o + 2] = b / n;
|
|
17504
|
+
data[o + 3] = 255;
|
|
17505
|
+
}
|
|
17506
|
+
}
|
|
17507
|
+
return { width, height, data };
|
|
17508
|
+
}
|
|
17509
|
+
function paintBoxes(jpeg, frame, boxes, size, quality) {
|
|
17510
|
+
const image = jpeg.decode(frame.data, { useTArray: true, formatAsRGBA: true });
|
|
17511
|
+
const sx = image.width / frame.width;
|
|
17512
|
+
const sy = image.height / frame.height;
|
|
17513
|
+
for (const box of boxes) {
|
|
17514
|
+
const x0 = Math.max(0, Math.floor((box.x - PAD) * sx));
|
|
17515
|
+
const y0 = Math.max(0, Math.floor((box.y - PAD) * sy));
|
|
17516
|
+
const x1 = Math.min(image.width, Math.ceil((box.x + box.width + PAD) * sx));
|
|
17517
|
+
const y1 = Math.min(image.height, Math.ceil((box.y + box.height + PAD) * sy));
|
|
17518
|
+
for (let y = y0; y < y1; y++) {
|
|
17519
|
+
for (let x = x0; x < x1; x++) {
|
|
17520
|
+
const i = (y * image.width + x) * 4;
|
|
17521
|
+
image.data[i] = MASK_RGB[0];
|
|
17522
|
+
image.data[i + 1] = MASK_RGB[1];
|
|
17523
|
+
image.data[i + 2] = MASK_RGB[2];
|
|
17524
|
+
image.data[i + 3] = 255;
|
|
17525
|
+
}
|
|
17526
|
+
}
|
|
17527
|
+
}
|
|
17528
|
+
const fits = image.width <= size.width && image.height <= size.height;
|
|
17529
|
+
return jpeg.encode(fits ? image : shrink(image, size.width, size.height), quality).data;
|
|
17530
|
+
}
|
|
17531
|
+
async function measureFrame(page, frame) {
|
|
17532
|
+
try {
|
|
17533
|
+
const measured = await within2(
|
|
17534
|
+
internalCall(page, async () => {
|
|
17535
|
+
const fields = await frame.locator(SECRET_FIELD_SELECTOR).elementHandles();
|
|
17536
|
+
try {
|
|
17537
|
+
return await Promise.all(fields.map((field) => field.boundingBox()));
|
|
17538
|
+
} finally {
|
|
17539
|
+
await Promise.all(fields.map((field) => field.dispose().catch(() => void 0)));
|
|
17540
|
+
}
|
|
17541
|
+
}),
|
|
17542
|
+
FRAME_READ_MS
|
|
17543
|
+
);
|
|
17544
|
+
if (measured) return measured.filter((box) => !!box && box.width > 0 && box.height > 0);
|
|
17545
|
+
const whole = frame === page.mainFrame() ? null : await wholeFrame(page, frame);
|
|
17546
|
+
return whole && whole.rects.map(([x, y, width, height]) => ({ x, y, width, height }));
|
|
17547
|
+
} catch {
|
|
17548
|
+
return frame.isDetached() ? [] : null;
|
|
17549
|
+
}
|
|
17550
|
+
}
|
|
17551
|
+
async function secretBoxes(page) {
|
|
17552
|
+
const [read, measured] = await Promise.all([
|
|
17553
|
+
readBoxes(page, []),
|
|
17554
|
+
Promise.all(
|
|
17555
|
+
page.frames().slice(0, MAX_FRAMES).map((frame) => measureFrame(page, frame))
|
|
17556
|
+
)
|
|
17557
|
+
]);
|
|
17558
|
+
if (!read || typeof read === "string" || measured.some((boxes) => !boxes)) return null;
|
|
17559
|
+
return { ...read, boxes: [...read.boxes, ...measured.flat()] };
|
|
17560
|
+
}
|
|
17561
|
+
async function maskedImage(page, options) {
|
|
17562
|
+
const before = await secretBoxes(page);
|
|
17563
|
+
if (!before) return null;
|
|
17564
|
+
const shot = await internalCall(
|
|
17565
|
+
page,
|
|
17566
|
+
() => page.screenshot({
|
|
17567
|
+
type: "jpeg",
|
|
17568
|
+
quality: options.quality,
|
|
17569
|
+
timeout: options.timeout,
|
|
17570
|
+
...options.style ? { style: options.style } : {}
|
|
17571
|
+
})
|
|
17572
|
+
).catch(() => null);
|
|
17573
|
+
if (!shot) return null;
|
|
17574
|
+
const after = await secretBoxes(page);
|
|
17575
|
+
if (!after || !sameBoxes(before.boxes, after.boxes)) return null;
|
|
17576
|
+
const { width, height } = after.viewport;
|
|
17577
|
+
if (!near(width, before.viewport.width) || !near(height, before.viewport.height)) return null;
|
|
17578
|
+
if (!after.boxes.length) return shot;
|
|
17579
|
+
const jpeg = jpegCodec();
|
|
17580
|
+
const size = jpegSize(shot);
|
|
17581
|
+
if (!jpeg || !size) return null;
|
|
17582
|
+
try {
|
|
17583
|
+
return paintBoxes(jpeg, { data: shot, width, height, at: Date.now() }, after.boxes, size, options.quality);
|
|
17584
|
+
} catch {
|
|
17585
|
+
return null;
|
|
17586
|
+
}
|
|
17587
|
+
}
|
|
17588
|
+
function maskSecretFields(page, options) {
|
|
17589
|
+
const reads = [];
|
|
17590
|
+
let maskedAt = -Infinity;
|
|
17591
|
+
let failed2 = 0;
|
|
17592
|
+
const read = async () => {
|
|
17593
|
+
const began = Date.now();
|
|
17594
|
+
const found = await readBoxes(page, options.masks());
|
|
17595
|
+
if (typeof found === "string") return found;
|
|
17596
|
+
if (!found) return ++failed2 >= MAX_FAILED_READS ? UNREAD : null;
|
|
17597
|
+
failed2 = 0;
|
|
17598
|
+
const done = { began, ended: Date.now(), ...found };
|
|
17599
|
+
reads.push(done);
|
|
17600
|
+
if (reads.length > READS_KEPT) reads.shift();
|
|
17601
|
+
return done;
|
|
17602
|
+
};
|
|
17603
|
+
const showsViewportOf = (frame, read2) => near(frame.width, read2.viewport.width) && near(frame.height, read2.viewport.height);
|
|
17604
|
+
const shown2 = (frame, boxes, size) => {
|
|
17605
|
+
const own = jpegSize(frame.data);
|
|
17606
|
+
const fits = own === null || own.width <= size.width && own.height <= size.height;
|
|
17607
|
+
const jpeg = jpegCodec();
|
|
17608
|
+
if (!boxes.length && (fits || !jpeg)) return frame.data.length <= options.maxBytes ? frame : null;
|
|
17609
|
+
if (!jpeg) return NO_CODEC;
|
|
17610
|
+
let data;
|
|
17611
|
+
try {
|
|
17612
|
+
data = paintBoxes(jpeg, frame, boxes, size, options.quality);
|
|
17613
|
+
} catch {
|
|
17614
|
+
return null;
|
|
17615
|
+
}
|
|
17616
|
+
if (!boxes.length) return data.length <= options.maxBytes ? { ...frame, data } : null;
|
|
17617
|
+
if (data.length > options.maxBytes) return null;
|
|
17618
|
+
maskedAt = Date.now();
|
|
17619
|
+
return { ...frame, data };
|
|
17620
|
+
};
|
|
17621
|
+
return {
|
|
17622
|
+
get resting() {
|
|
17623
|
+
return Date.now() - maskedAt < MASKED_SPACING_MS;
|
|
17624
|
+
},
|
|
17625
|
+
async screen(frame) {
|
|
17626
|
+
const before = [...reads].reverse().find((r) => r.ended <= frame.at && r.ended >= frame.at - FRESH_MS) ?? null;
|
|
17627
|
+
const after = await read();
|
|
17628
|
+
if (typeof after === "string" || !after) return after;
|
|
17629
|
+
const size = jpegSize(frame.data) ?? frame;
|
|
17630
|
+
if (before && sameBoxes(before.boxes, after.boxes) && showsViewportOf(frame, before) && showsViewportOf(frame, after)) {
|
|
17631
|
+
return shown2(frame, after.boxes, size);
|
|
17632
|
+
}
|
|
17633
|
+
if (page.isClosed()) return null;
|
|
17634
|
+
const at = Date.now();
|
|
17635
|
+
const shot = await internalCall(
|
|
17636
|
+
page,
|
|
17637
|
+
() => page.screenshot({ type: "jpeg", quality: options.quality, scale: "css", caret: "initial", timeout: 5e3 })
|
|
17638
|
+
).catch(() => null);
|
|
17639
|
+
if (!shot) return null;
|
|
17640
|
+
const again = await read();
|
|
17641
|
+
if (typeof again === "string" || !again) return again;
|
|
17642
|
+
if (!sameBoxes(after.boxes, again.boxes)) return null;
|
|
17643
|
+
const { width, height } = again.viewport;
|
|
17644
|
+
if (!near(width, after.viewport.width) || !near(height, after.viewport.height)) return null;
|
|
17645
|
+
return shown2(
|
|
17646
|
+
{ data: shot, width, height, at },
|
|
17647
|
+
again.boxes,
|
|
17648
|
+
fitViewSize({ width, height }, size.width, size.height)
|
|
17649
|
+
);
|
|
17650
|
+
}
|
|
17651
|
+
};
|
|
17652
|
+
}
|
|
17653
|
+
|
|
16697
17654
|
// src/internal/capture/agent-page.ts
|
|
16698
17655
|
var ACT_MS = 5e3;
|
|
16699
|
-
var SECRET_FIELDS =
|
|
16700
|
-
'input[type="password" i]',
|
|
16701
|
-
...SECRET_AUTOCOMPLETE.flatMap((token) => [
|
|
16702
|
-
`input[autocomplete~="${token}" i]`,
|
|
16703
|
-
`textarea[autocomplete~="${token}" i]`
|
|
16704
|
-
])
|
|
16705
|
-
].join(", ");
|
|
17656
|
+
var SECRET_FIELDS = SECRET_FIELD_SELECTOR;
|
|
16706
17657
|
var BAR_HIDDEN = "#__piwi-pause-bar { display: none !important; }";
|
|
16707
|
-
var AGENT_NEEDS = "An agent reads and acts on a stopped page with Playwright 1.64 or later (page.getByRef and the AI snapshot): update @playwright/test.";
|
|
16708
|
-
function canActByRef(page) {
|
|
16709
|
-
return typeof page.getByRef === "function";
|
|
16710
|
-
}
|
|
16711
|
-
function byRef(page, ref) {
|
|
16712
|
-
return page.getByRef(ref);
|
|
16713
|
-
}
|
|
16714
17658
|
function failed(prefix, error) {
|
|
16715
17659
|
const first = errorMessage(error).split("\n")[0].trim();
|
|
16716
17660
|
return hideSecretsInText(`${prefix}: ${first}`).slice(0, 300);
|
|
16717
17661
|
}
|
|
16718
|
-
var
|
|
17662
|
+
var MAX_FRAMES2 = 32;
|
|
16719
17663
|
async function fieldSecrets(page) {
|
|
16720
17664
|
const values = await Promise.all(
|
|
16721
|
-
page.frames().slice(0,
|
|
17665
|
+
page.frames().slice(0, MAX_FRAMES2).map(
|
|
16722
17666
|
(frame) => frame.locator(SECRET_FIELDS).evaluateAll((fields) => fields.map((field) => field.value)).catch(() => [])
|
|
16723
17667
|
)
|
|
16724
17668
|
);
|
|
@@ -16772,13 +17716,7 @@ async function readSnapshot(page, depth, image) {
|
|
|
16772
17716
|
const cut = hidden2.length > MAX_AGENT_SNAPSHOT;
|
|
16773
17717
|
let shot = null;
|
|
16774
17718
|
if (image) {
|
|
16775
|
-
const data = await page
|
|
16776
|
-
type: "jpeg",
|
|
16777
|
-
quality: 60,
|
|
16778
|
-
style: BAR_HIDDEN,
|
|
16779
|
-
mask: page.frames().slice(0, MAX_FRAMES).map((frame) => frame.locator(SECRET_FIELDS)),
|
|
16780
|
-
timeout: ACT_MS
|
|
16781
|
-
}).catch(() => null);
|
|
17719
|
+
const data = await maskedImage(page, { quality: 60, style: BAR_HIDDEN, timeout: ACT_MS });
|
|
16782
17720
|
const viewport = page.viewportSize();
|
|
16783
17721
|
if (data) shot = { mime: "image/jpeg", data: data.toString("base64"), ...viewport };
|
|
16784
17722
|
}
|
|
@@ -16847,14 +17785,13 @@ async function ranked(page, probe, locator, handle, testIdAttribute) {
|
|
|
16847
17785
|
}
|
|
16848
17786
|
var NO_REF = (ref) => `No element of the stopped page has the ref ${ref}: take the page's snapshot again, its refs may have changed.`;
|
|
16849
17787
|
async function handleOf(page, ref) {
|
|
16850
|
-
return
|
|
17788
|
+
return locatorOfRef(page, ref).elementHandle({ timeout: 2e3 }).catch(() => null);
|
|
16851
17789
|
}
|
|
16852
17790
|
async function refLocators(page, probe, ref, testIdAttribute) {
|
|
16853
|
-
if (!canActByRef(page)) return { ok: false, message: AGENT_NEEDS, element: null, locators: [] };
|
|
16854
17791
|
const handle = await handleOf(page, ref);
|
|
16855
17792
|
if (!handle) return { ok: false, message: NO_REF(ref), element: null, locators: [] };
|
|
16856
17793
|
try {
|
|
16857
|
-
const { element, locators } = await ranked(page, probe,
|
|
17794
|
+
const { element, locators } = await ranked(page, probe, locatorOfRef(page, ref), handle, testIdAttribute);
|
|
16858
17795
|
const alone = locators.filter((l) => l.unique).length;
|
|
16859
17796
|
return {
|
|
16860
17797
|
ok: true,
|
|
@@ -16898,12 +17835,11 @@ var PLAYED = {
|
|
|
16898
17835
|
select: "Selected an option of"
|
|
16899
17836
|
};
|
|
16900
17837
|
async function actOnRef(page, probe, act, testIdAttribute) {
|
|
16901
|
-
if (!canActByRef(page)) return { ok: false, message: AGENT_NEEDS, element: null, locators: [] };
|
|
16902
17838
|
const handle = await handleOf(page, act.ref);
|
|
16903
17839
|
if (!handle) return { ok: false, message: NO_REF(act.ref), element: null, locators: [] };
|
|
16904
17840
|
let before;
|
|
16905
17841
|
try {
|
|
16906
|
-
before = await ranked(page, probe,
|
|
17842
|
+
before = await ranked(page, probe, locatorOfRef(page, act.ref), handle, testIdAttribute);
|
|
16907
17843
|
} catch (error) {
|
|
16908
17844
|
return { ok: false, message: failed("The element could not be read", error), element: null, locators: [] };
|
|
16909
17845
|
} finally {
|
|
@@ -16911,7 +17847,7 @@ async function actOnRef(page, probe, act, testIdAttribute) {
|
|
|
16911
17847
|
}
|
|
16912
17848
|
try {
|
|
16913
17849
|
await page.evaluate(barLetsThrough, true).catch(() => void 0);
|
|
16914
|
-
await play(
|
|
17850
|
+
await play(locatorOfRef(page, act.ref), act.action, act.value);
|
|
16915
17851
|
} catch (error) {
|
|
16916
17852
|
return { ok: false, message: failed(`${PLAYED[act.action]} ${act.ref} failed`, error), ...before };
|
|
16917
17853
|
} finally {
|
|
@@ -16955,7 +17891,7 @@ async function bindBrowser(browser, name, workspaceDir, on) {
|
|
|
16955
17891
|
}
|
|
16956
17892
|
|
|
16957
17893
|
// src/internal/capture/run-events.ts
|
|
16958
|
-
var
|
|
17894
|
+
var path31 = __toESM(require("path"));
|
|
16959
17895
|
|
|
16960
17896
|
// src/internal/capture/run-channel.ts
|
|
16961
17897
|
var net3 = __toESM(require("net"));
|
|
@@ -17034,7 +17970,198 @@ async function keptResponse(id) {
|
|
|
17034
17970
|
var http3 = __toESM(require("http"));
|
|
17035
17971
|
var https3 = __toESM(require("https"));
|
|
17036
17972
|
|
|
17973
|
+
// src/internal/capture/live-call-log.ts
|
|
17974
|
+
var path30 = __toESM(require("path"));
|
|
17975
|
+
var TARGET_EVERY_MS = 1e3;
|
|
17976
|
+
var KEPT_LINES = 1e3;
|
|
17977
|
+
var MAX_LINE2 = 600;
|
|
17978
|
+
var MAX_TITLE = 500;
|
|
17979
|
+
var MAX_HEARD = 200;
|
|
17980
|
+
function callStreamOf(context) {
|
|
17981
|
+
const dbg = context.debugger;
|
|
17982
|
+
return typeof dbg?._enable === "function" && typeof dbg.on === "function" && typeof dbg.off === "function" ? dbg : null;
|
|
17983
|
+
}
|
|
17984
|
+
var heard = /* @__PURE__ */ new Map();
|
|
17985
|
+
var tracked = /* @__PURE__ */ new WeakSet();
|
|
17986
|
+
function trackCalls(playwright) {
|
|
17987
|
+
const instrumentation = playwright?._instrumentation;
|
|
17988
|
+
if (!playwright || tracked.has(playwright) || typeof instrumentation?.addListener !== "function") return;
|
|
17989
|
+
tracked.add(playwright);
|
|
17990
|
+
instrumentation.addListener({
|
|
17991
|
+
onApiCallBegin: (zone, channel) => {
|
|
17992
|
+
if (channel?.method === "__waitInfo__") return;
|
|
17993
|
+
const selector = channel?.params?.selector;
|
|
17994
|
+
heard.set(zone, {
|
|
17995
|
+
zone,
|
|
17996
|
+
selector: typeof selector === "string" && selector ? selector : null,
|
|
17997
|
+
began: Date.now()
|
|
17998
|
+
});
|
|
17999
|
+
if (heard.size > MAX_HEARD) heard.delete(heard.keys().next().value);
|
|
18000
|
+
},
|
|
18001
|
+
onApiCallEnd: (zone) => {
|
|
18002
|
+
heard.delete(zone);
|
|
18003
|
+
}
|
|
18004
|
+
});
|
|
18005
|
+
}
|
|
18006
|
+
function heardAs(id) {
|
|
18007
|
+
for (const call of heard.values()) if (call.zone.callId === id) return call;
|
|
18008
|
+
return null;
|
|
18009
|
+
}
|
|
18010
|
+
function userFile(file) {
|
|
18011
|
+
if (typeof file !== "string" || !file || /[\\/]node_modules[\\/]/.test(file)) return null;
|
|
18012
|
+
const relative11 = path30.relative(process.cwd(), file).split(path30.sep).join("/");
|
|
18013
|
+
return relative11 && !relative11.startsWith("..") && !path30.isAbsolute(relative11) ? relative11 : null;
|
|
18014
|
+
}
|
|
18015
|
+
var followed = /* @__PURE__ */ new WeakMap();
|
|
18016
|
+
function take(state, update) {
|
|
18017
|
+
if (typeof update?.id !== "string" || typeof update.title !== "string") return;
|
|
18018
|
+
const status = update.status === "running" ? "running" : update.status === "error" ? "failed" : "passed";
|
|
18019
|
+
let call = state.calls.get(update.id);
|
|
18020
|
+
if (!call) {
|
|
18021
|
+
const heardOne = heardAs(update.id);
|
|
18022
|
+
call = {
|
|
18023
|
+
id: update.id,
|
|
18024
|
+
title: hideSecretsInText(hideTypedText(update.title)).slice(0, MAX_TITLE),
|
|
18025
|
+
file: userFile(update.location?.file),
|
|
18026
|
+
line: typeof update.location?.line === "number" ? update.location.line : null,
|
|
18027
|
+
status,
|
|
18028
|
+
lines: [],
|
|
18029
|
+
point: null,
|
|
18030
|
+
error: null,
|
|
18031
|
+
began: heardOne?.began ?? Date.now(),
|
|
18032
|
+
ended: null,
|
|
18033
|
+
order: ++state.told,
|
|
18034
|
+
selector: heardOne?.selector ?? null,
|
|
18035
|
+
secretField: heardOne?.selector ? isSecretLocator(heardOne.selector) : false
|
|
18036
|
+
};
|
|
18037
|
+
state.calls.set(update.id, call);
|
|
18038
|
+
}
|
|
18039
|
+
if (Array.isArray(update.newLogEntries)) {
|
|
18040
|
+
for (const entry of update.newLogEntries) {
|
|
18041
|
+
if (typeof entry !== "string") continue;
|
|
18042
|
+
call.secretField ||= namesSecretField(entry);
|
|
18043
|
+
call.lines.push(hideSecretsInText(hideSecretsInLog(entry, call.secretField)).slice(0, MAX_LINE2));
|
|
18044
|
+
}
|
|
18045
|
+
if (call.lines.length > KEPT_LINES) call.lines.splice(0, call.lines.length - KEPT_LINES);
|
|
18046
|
+
}
|
|
18047
|
+
const point = update.actionPoint;
|
|
18048
|
+
if (point && Number.isFinite(point.x) && Number.isFinite(point.y)) {
|
|
18049
|
+
call.point = { x: point.x, y: point.y };
|
|
18050
|
+
}
|
|
18051
|
+
if (typeof update.error === "string" && update.error) {
|
|
18052
|
+
const first = update.error.split("\n")[0];
|
|
18053
|
+
call.error = hideSecretsInText(hideSecretsInLog(first, call.secretField)).slice(0, MAX_LINE2);
|
|
18054
|
+
}
|
|
18055
|
+
call.status = status;
|
|
18056
|
+
if (status !== "running" && call.ended === null) call.ended = Date.now();
|
|
18057
|
+
}
|
|
18058
|
+
function shownCall(state, since2) {
|
|
18059
|
+
let shown2 = null;
|
|
18060
|
+
for (const call of state.calls.values()) {
|
|
18061
|
+
if (call.ended !== null && call.ended < since2) continue;
|
|
18062
|
+
if (!shown2) {
|
|
18063
|
+
shown2 = call;
|
|
18064
|
+
continue;
|
|
18065
|
+
}
|
|
18066
|
+
const running2 = call.status === "running";
|
|
18067
|
+
if (running2 !== (shown2.status === "running") ? running2 : call.order > shown2.order) shown2 = call;
|
|
18068
|
+
}
|
|
18069
|
+
return shown2;
|
|
18070
|
+
}
|
|
18071
|
+
function prune(state) {
|
|
18072
|
+
let last = null;
|
|
18073
|
+
for (const call of state.calls.values()) if (!last || call.order > last.order) last = call;
|
|
18074
|
+
for (const [id, call] of state.calls) if (call.status !== "running" && call !== last) state.calls.delete(id);
|
|
18075
|
+
}
|
|
18076
|
+
async function followContext(context) {
|
|
18077
|
+
const stream = callStreamOf(context);
|
|
18078
|
+
if (!stream) return null;
|
|
18079
|
+
const state = { calls: /* @__PURE__ */ new Map(), told: 0, views: /* @__PURE__ */ new Set() };
|
|
18080
|
+
const listener = (updates) => {
|
|
18081
|
+
if (!Array.isArray(updates)) return;
|
|
18082
|
+
for (const update of updates) take(state, update);
|
|
18083
|
+
prune(state);
|
|
18084
|
+
for (const view3 of state.views) view3();
|
|
18085
|
+
};
|
|
18086
|
+
stream.on("apicallsupdated", listener);
|
|
18087
|
+
try {
|
|
18088
|
+
await internalCall(context, () => stream._enable());
|
|
18089
|
+
} catch {
|
|
18090
|
+
stream.off("apicallsupdated", listener);
|
|
18091
|
+
return null;
|
|
18092
|
+
}
|
|
18093
|
+
return state;
|
|
18094
|
+
}
|
|
18095
|
+
async function followCallLog(page, since2, onLog) {
|
|
18096
|
+
const context = page.context();
|
|
18097
|
+
let state = followed.get(context);
|
|
18098
|
+
if (!state) {
|
|
18099
|
+
state = followContext(context);
|
|
18100
|
+
followed.set(context, state);
|
|
18101
|
+
}
|
|
18102
|
+
const calls = await state;
|
|
18103
|
+
if (!calls) return null;
|
|
18104
|
+
let stopped = false;
|
|
18105
|
+
let reading = false;
|
|
18106
|
+
let target = null;
|
|
18107
|
+
const onThisPage = () => !page.isClosed() && context.pages().length === 1;
|
|
18108
|
+
const tell = () => {
|
|
18109
|
+
const shown2 = shownCall(calls, since2);
|
|
18110
|
+
if (stopped || !shown2) return;
|
|
18111
|
+
const here = onThisPage();
|
|
18112
|
+
onLog({
|
|
18113
|
+
id: shown2.id,
|
|
18114
|
+
call: { title: shown2.title, file: shown2.file, line: shown2.line },
|
|
18115
|
+
status: shown2.status,
|
|
18116
|
+
lines: compressCallLog(shown2.lines).slice(-MAX_LIVE_CALL_LOG_LINES),
|
|
18117
|
+
point: here ? shown2.point : null,
|
|
18118
|
+
target: here && shown2.status === "running" && target?.id === shown2.id ? target.matches : null,
|
|
18119
|
+
viewport: page.viewportSize(),
|
|
18120
|
+
ms: (shown2.ended ?? Date.now()) - shown2.began,
|
|
18121
|
+
error: shown2.error
|
|
18122
|
+
});
|
|
18123
|
+
};
|
|
18124
|
+
calls.views.add(tell);
|
|
18125
|
+
tell();
|
|
18126
|
+
const timer = setInterval(() => {
|
|
18127
|
+
const shown2 = shownCall(calls, since2);
|
|
18128
|
+
const selector = shown2?.selector;
|
|
18129
|
+
if (stopped || reading || !shown2 || shown2.status !== "running" || !selector || !onThisPage()) return;
|
|
18130
|
+
if (Date.now() - shown2.began < TARGET_EVERY_MS) return;
|
|
18131
|
+
reading = true;
|
|
18132
|
+
const framed = selector.includes("internal:control=enter-frame");
|
|
18133
|
+
void internalCall(
|
|
18134
|
+
page,
|
|
18135
|
+
() => page.locator(selector).evaluateAll((elements) => {
|
|
18136
|
+
const first = elements[0];
|
|
18137
|
+
if (!first) return { count: 0, box: null, enabled: null };
|
|
18138
|
+
const rect = first.getBoundingClientRect();
|
|
18139
|
+
const disabled = first.disabled === true || first.getAttribute("aria-disabled") === "true";
|
|
18140
|
+
return {
|
|
18141
|
+
count: elements.length,
|
|
18142
|
+
box: rect.width || rect.height ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null,
|
|
18143
|
+
enabled: !disabled
|
|
18144
|
+
};
|
|
18145
|
+
})
|
|
18146
|
+
).then((matches) => {
|
|
18147
|
+
if (stopped || shownCall(calls, since2) !== shown2 || shown2.status !== "running") return;
|
|
18148
|
+
if (!matches.count && shown2.lines.some((line) => line.includes("locator resolved to"))) return;
|
|
18149
|
+
target = { id: shown2.id, matches: framed ? { ...matches, box: null } : matches };
|
|
18150
|
+
tell();
|
|
18151
|
+
}).catch(() => void 0).finally(() => {
|
|
18152
|
+
reading = false;
|
|
18153
|
+
});
|
|
18154
|
+
}, TARGET_EVERY_MS);
|
|
18155
|
+
timer.unref?.();
|
|
18156
|
+
return () => {
|
|
18157
|
+
stopped = true;
|
|
18158
|
+
clearInterval(timer);
|
|
18159
|
+
calls.views.delete(tell);
|
|
18160
|
+
};
|
|
18161
|
+
}
|
|
18162
|
+
|
|
17037
18163
|
// src/internal/capture/page-view.ts
|
|
18164
|
+
var SCREENCAST_LAG_MS = 100;
|
|
17038
18165
|
var HOLDERS = /* @__PURE__ */ new WeakMap();
|
|
17039
18166
|
var TEST_SCREENCASTS = /* @__PURE__ */ new WeakSet();
|
|
17040
18167
|
var OWN_SCREENCASTS = /* @__PURE__ */ new WeakMap();
|
|
@@ -17087,16 +18214,39 @@ async function openPageView(page, options) {
|
|
|
17087
18214
|
let last = null;
|
|
17088
18215
|
let sent2 = null;
|
|
17089
18216
|
let refused = false;
|
|
18217
|
+
let screening = false;
|
|
18218
|
+
let screened = null;
|
|
17090
18219
|
const interval = 1e3 / options.fps;
|
|
18220
|
+
const deliver = (frame) => {
|
|
18221
|
+
if (closed || !options.live()) return;
|
|
18222
|
+
refused = false;
|
|
18223
|
+
sent2 = frame;
|
|
18224
|
+
options.send(frame);
|
|
18225
|
+
};
|
|
18226
|
+
const refuse2 = (why) => {
|
|
18227
|
+
if (closed || refused || !options.live()) return;
|
|
18228
|
+
refused = true;
|
|
18229
|
+
sent2 = { refused: why };
|
|
18230
|
+
options.refuse(why);
|
|
18231
|
+
};
|
|
17091
18232
|
const pacer = new LatestPacer({
|
|
17092
18233
|
intervalMs: interval,
|
|
17093
18234
|
...options.spacing ? { spacing: options.spacing } : {},
|
|
17094
|
-
busy: options.busy,
|
|
18235
|
+
busy: () => screening || options.busy(),
|
|
17095
18236
|
send: (frame) => {
|
|
17096
18237
|
if (!options.live()) return;
|
|
17097
|
-
|
|
17098
|
-
|
|
17099
|
-
options.
|
|
18238
|
+
if (!options.screen) return deliver(frame);
|
|
18239
|
+
screening = true;
|
|
18240
|
+
screened = options.screen(frame).then(
|
|
18241
|
+
(shown2) => {
|
|
18242
|
+
if (typeof shown2 === "string") refuse2(shown2);
|
|
18243
|
+
else if (shown2) deliver(shown2);
|
|
18244
|
+
},
|
|
18245
|
+
() => void 0
|
|
18246
|
+
).finally(() => {
|
|
18247
|
+
screening = false;
|
|
18248
|
+
screened = null;
|
|
18249
|
+
});
|
|
17100
18250
|
}
|
|
17101
18251
|
});
|
|
17102
18252
|
let lighter = null;
|
|
@@ -17111,23 +18261,20 @@ async function openPageView(page, options) {
|
|
|
17111
18261
|
).then(
|
|
17112
18262
|
(shot) => {
|
|
17113
18263
|
if (closed) return;
|
|
17114
|
-
if (shot.length <= options.maxFrameBytes) return pacer.offer({ data: shot, ...viewport });
|
|
17115
|
-
|
|
17116
|
-
refused = true;
|
|
17117
|
-
sent2 = "refused";
|
|
17118
|
-
options.refuse();
|
|
18264
|
+
if (shot.length <= options.maxFrameBytes) return pacer.offer({ data: shot, ...viewport, at: now });
|
|
18265
|
+
refuse2();
|
|
17119
18266
|
},
|
|
17120
18267
|
() => void 0
|
|
17121
18268
|
).finally(() => {
|
|
17122
18269
|
lighter = null;
|
|
17123
18270
|
});
|
|
17124
18271
|
};
|
|
17125
|
-
const onFrame = (data, width, height) => {
|
|
18272
|
+
const onFrame = (data, width, height, at) => {
|
|
17126
18273
|
if (closed || last?.equals(data)) return;
|
|
17127
18274
|
last = data;
|
|
17128
18275
|
if (data.length > options.maxFrameBytes) return lighten();
|
|
17129
18276
|
const named = isViewSize(width, height);
|
|
17130
|
-
pacer.offer({ data, width: named ? width : viewport.width, height: named ? height : viewport.height });
|
|
18277
|
+
pacer.offer({ data, width: named ? width : viewport.width, height: named ? height : viewport.height, at });
|
|
17131
18278
|
};
|
|
17132
18279
|
const screencast = screencastOf(page);
|
|
17133
18280
|
let end = null;
|
|
@@ -17136,11 +18283,12 @@ async function openPageView(page, options) {
|
|
|
17136
18283
|
const shoot = async () => {
|
|
17137
18284
|
shots = null;
|
|
17138
18285
|
if (closed || page.isClosed()) return;
|
|
18286
|
+
const at = Date.now();
|
|
17139
18287
|
const shot = await internalCall(
|
|
17140
18288
|
page,
|
|
17141
18289
|
() => page.screenshot({ type: "jpeg", quality: options.quality, scale: "css", caret: "initial", timeout: 5e3 })
|
|
17142
18290
|
).catch(() => null);
|
|
17143
|
-
if (shot) onFrame(shot, viewport.width, viewport.height);
|
|
18291
|
+
if (shot) onFrame(shot, viewport.width, viewport.height, at);
|
|
17144
18292
|
if (!closed) {
|
|
17145
18293
|
shots = setTimeout(() => void shoot(), options.shotMs);
|
|
17146
18294
|
shots.unref?.();
|
|
@@ -17173,7 +18321,9 @@ async function openPageView(page, options) {
|
|
|
17173
18321
|
size: options.size(viewport),
|
|
17174
18322
|
quality: options.quality,
|
|
17175
18323
|
onFrame: (frame) => {
|
|
17176
|
-
|
|
18324
|
+
const now = Date.now();
|
|
18325
|
+
const at = typeof frame.timestamp === "number" && Number.isFinite(frame.timestamp) ? Math.min(frame.timestamp, now) - SCREENCAST_LAG_MS : Number.NEGATIVE_INFINITY;
|
|
18326
|
+
onFrame(frame.data, frame.viewportWidth, frame.viewportHeight, at);
|
|
17177
18327
|
return acks.next();
|
|
17178
18328
|
}
|
|
17179
18329
|
})
|
|
@@ -17197,9 +18347,22 @@ async function openPageView(page, options) {
|
|
|
17197
18347
|
},
|
|
17198
18348
|
resend: () => {
|
|
17199
18349
|
if (closed || !sent2 || !options.live()) return;
|
|
17200
|
-
if (
|
|
18350
|
+
if ("refused" in sent2) options.refuse(sent2.refused);
|
|
17201
18351
|
else options.send(sent2);
|
|
17202
18352
|
},
|
|
18353
|
+
refresh: () => {
|
|
18354
|
+
if (closed) return;
|
|
18355
|
+
sent2 = null;
|
|
18356
|
+
last = null;
|
|
18357
|
+
const at = Date.now();
|
|
18358
|
+
void internalCall(
|
|
18359
|
+
page,
|
|
18360
|
+
() => page.screenshot({ type: "jpeg", quality: options.quality, scale: "css", caret: "initial", timeout: 5e3 })
|
|
18361
|
+
).then(
|
|
18362
|
+
(shot) => onFrame(shot, viewport.width, viewport.height, at),
|
|
18363
|
+
() => void 0
|
|
18364
|
+
);
|
|
18365
|
+
},
|
|
17203
18366
|
close: async () => {
|
|
17204
18367
|
closed = true;
|
|
17205
18368
|
sent2 = null;
|
|
@@ -17207,7 +18370,7 @@ async function openPageView(page, options) {
|
|
|
17207
18370
|
pacer.close();
|
|
17208
18371
|
if (shots) clearTimeout(shots);
|
|
17209
18372
|
holders.delete(holder);
|
|
17210
|
-
await lighter;
|
|
18373
|
+
await Promise.all([lighter, screened]);
|
|
17211
18374
|
const stop = end;
|
|
17212
18375
|
end = null;
|
|
17213
18376
|
if (stop && !page.isClosed()) await stop().catch(() => void 0);
|
|
@@ -17225,6 +18388,7 @@ var MAX_EVENT_TEXT = 64 * 1024;
|
|
|
17225
18388
|
var MAX_STATES_WAITING = 8;
|
|
17226
18389
|
var LIGHT_QUALITY = 30;
|
|
17227
18390
|
var SHOT_MS = 1e3;
|
|
18391
|
+
var LOG_SPACING_MS = Math.ceil(1e3 / LIVE_CALL_LOGS_PER_SECOND);
|
|
17228
18392
|
var VIEW_END_MS = 1e3;
|
|
17229
18393
|
var GIVE_UP = /* @__PURE__ */ new Set([401, 403, 404, 410]);
|
|
17230
18394
|
var TOO_HEAVY = `This page is too heavy for the live view: each image of it is over ${MAX_LIVE_FRAME / 1024} KB, a lighter one included.`;
|
|
@@ -17297,7 +18461,8 @@ function openLiveView(worker, env = process.env, ci = detectCi(env)) {
|
|
|
17297
18461
|
address,
|
|
17298
18462
|
id: { shard: resolveShardInfo(worker.config, env)?.current ?? null, worker: worker.workerIndex },
|
|
17299
18463
|
client,
|
|
17300
|
-
|
|
18464
|
+
// The stream, and a frame, a call log and a stop or an end in flight at once: none waits behind another.
|
|
18465
|
+
agent: new client.Agent({ keepAlive: true, maxSockets: 4 }),
|
|
17301
18466
|
stream: null,
|
|
17302
18467
|
timer: null,
|
|
17303
18468
|
retries: 0,
|
|
@@ -17305,13 +18470,20 @@ function openLiveView(worker, env = process.env, ci = detectCi(env)) {
|
|
|
17305
18470
|
ended: false,
|
|
17306
18471
|
refused: null,
|
|
17307
18472
|
sharing: ci,
|
|
18473
|
+
ci,
|
|
17308
18474
|
lost: null,
|
|
17309
18475
|
answered: null,
|
|
17310
18476
|
viewers: 0,
|
|
17311
18477
|
states: Promise.resolve(),
|
|
17312
18478
|
statesWaiting: 0,
|
|
17313
18479
|
stop: null,
|
|
17314
|
-
posting: false
|
|
18480
|
+
posting: false,
|
|
18481
|
+
log: null,
|
|
18482
|
+
logPosting: false,
|
|
18483
|
+
logSentAt: 0,
|
|
18484
|
+
logTimer: null,
|
|
18485
|
+
logRefused: false,
|
|
18486
|
+
masks: []
|
|
17315
18487
|
};
|
|
17316
18488
|
if (ci) connect(link);
|
|
17317
18489
|
changed();
|
|
@@ -17361,9 +18533,9 @@ async function stopSharing() {
|
|
|
17361
18533
|
own.viewers = 0;
|
|
17362
18534
|
changed();
|
|
17363
18535
|
}
|
|
17364
|
-
function liveViewTestBegan(title, call) {
|
|
18536
|
+
function liveViewTestBegan(title, call, mayPause2 = false) {
|
|
17365
18537
|
if (!link) return;
|
|
17366
|
-
running = { title, page: null, call };
|
|
18538
|
+
running = { title, page: null, call, mayPause: mayPause2, began: Date.now() };
|
|
17367
18539
|
}
|
|
17368
18540
|
function liveViewPage(page) {
|
|
17369
18541
|
if (!link || !running || running.page) return;
|
|
@@ -17389,7 +18561,7 @@ function liveViewStopped(stop) {
|
|
|
17389
18561
|
};
|
|
17390
18562
|
}
|
|
17391
18563
|
function hideCallSecrets(call) {
|
|
17392
|
-
return { ...call, title: hideSecretsInText(call.title) };
|
|
18564
|
+
return { ...call, title: hideSecretsInText(hideTypedText(call.title)) };
|
|
17393
18565
|
}
|
|
17394
18566
|
function postState(own, message) {
|
|
17395
18567
|
if (!active(own) || own.statesWaiting >= MAX_STATES_WAITING) return;
|
|
@@ -17457,7 +18629,8 @@ function connect(own) {
|
|
|
17457
18629
|
text2 = text2.slice(end + 2);
|
|
17458
18630
|
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
17459
18631
|
const message = data ? parseInstanceToWorker(data) : null;
|
|
17460
|
-
if (message)
|
|
18632
|
+
if (message?.type === "masks") setMasks(own, message.locators);
|
|
18633
|
+
else if (message) setViewers(own, message.count);
|
|
17461
18634
|
}
|
|
17462
18635
|
});
|
|
17463
18636
|
response.on("error", () => drop(null));
|
|
@@ -17497,11 +18670,19 @@ function setViewers(own, count) {
|
|
|
17497
18670
|
return;
|
|
17498
18671
|
}
|
|
17499
18672
|
if (view) {
|
|
17500
|
-
if (count > before)
|
|
18673
|
+
if (count > before) {
|
|
18674
|
+
void view.opened.then((opened) => opened?.resend());
|
|
18675
|
+
if (own.log) postLog(own, own.log);
|
|
18676
|
+
}
|
|
17501
18677
|
return;
|
|
17502
18678
|
}
|
|
17503
18679
|
startView();
|
|
17504
18680
|
}
|
|
18681
|
+
function setMasks(own, locators) {
|
|
18682
|
+
if (JSON.stringify(locators) === JSON.stringify(own.masks)) return;
|
|
18683
|
+
own.masks = locators;
|
|
18684
|
+
if (own === link) void view?.opened.then((opened) => opened?.refresh());
|
|
18685
|
+
}
|
|
17505
18686
|
function startView() {
|
|
17506
18687
|
const own = link;
|
|
17507
18688
|
const page = running?.page ?? null;
|
|
@@ -17511,6 +18692,11 @@ function startView() {
|
|
|
17511
18692
|
return;
|
|
17512
18693
|
}
|
|
17513
18694
|
const live = () => view?.page === page && link === own && active(own) && own.viewers > 0;
|
|
18695
|
+
const mask = maskSecretFields(page, {
|
|
18696
|
+
masks: () => own.masks,
|
|
18697
|
+
quality: LIVE_FRAME_QUALITY,
|
|
18698
|
+
maxBytes: MAX_LIVE_FRAME
|
|
18699
|
+
});
|
|
17514
18700
|
const opened = openPageView(page, {
|
|
17515
18701
|
size: (viewport) => fitViewSize(viewport, LIVE_FRAME_WIDTH, LIVE_FRAME_HEIGHT),
|
|
17516
18702
|
quality: LIVE_FRAME_QUALITY,
|
|
@@ -17519,28 +18705,39 @@ function startView() {
|
|
|
17519
18705
|
maxFrameBytes: MAX_LIVE_FRAME,
|
|
17520
18706
|
lightQuality: LIGHT_QUALITY,
|
|
17521
18707
|
shotMs: SHOT_MS,
|
|
17522
|
-
busy: () => own.posting,
|
|
18708
|
+
busy: () => own.posting || mask.resting,
|
|
17523
18709
|
live,
|
|
18710
|
+
screen: (frame) => mask.screen(frame),
|
|
17524
18711
|
send: (frame) => postFrame(own, frame),
|
|
17525
|
-
refuse: () => void post(own, { type: "frame", ...own.id, refused: TOO_HEAVY })
|
|
18712
|
+
refuse: (why) => void post(own, { type: "frame", ...own.id, refused: why ?? TOO_HEAVY })
|
|
17526
18713
|
}).catch(() => null);
|
|
17527
18714
|
const onClose = () => {
|
|
17528
18715
|
if (view?.page === page) void stopView();
|
|
17529
18716
|
};
|
|
17530
18717
|
page.once("close", onClose);
|
|
17531
|
-
|
|
18718
|
+
const following = own.ci && running && !running.mayPause && !own.logRefused ? followCallLog(page, running.began, (log) => {
|
|
18719
|
+
if (live()) postLog(own, log);
|
|
18720
|
+
}).catch(() => null) : null;
|
|
18721
|
+
view = { page, opened, onClose, following };
|
|
17532
18722
|
}
|
|
17533
18723
|
async function stopView() {
|
|
17534
18724
|
const shown2 = view;
|
|
17535
18725
|
view = null;
|
|
17536
18726
|
if (!shown2) return;
|
|
17537
18727
|
shown2.page.off("close", shown2.onClose);
|
|
18728
|
+
void shown2.following?.then((stop) => stop?.());
|
|
18729
|
+
const own = link;
|
|
18730
|
+
if (own) {
|
|
18731
|
+
own.log = null;
|
|
18732
|
+
if (own.logTimer) clearTimeout(own.logTimer);
|
|
18733
|
+
own.logTimer = null;
|
|
18734
|
+
}
|
|
17538
18735
|
const closed = shown2.opened.then((opened) => opened?.close());
|
|
17539
18736
|
let timer;
|
|
17540
18737
|
await Promise.race([
|
|
17541
18738
|
closed.catch(() => void 0),
|
|
17542
|
-
new Promise((
|
|
17543
|
-
timer = setTimeout(
|
|
18739
|
+
new Promise((resolve12) => {
|
|
18740
|
+
timer = setTimeout(resolve12, VIEW_END_MS);
|
|
17544
18741
|
timer.unref?.();
|
|
17545
18742
|
})
|
|
17546
18743
|
]);
|
|
@@ -17561,19 +18758,47 @@ function postFrame(own, frame) {
|
|
|
17561
18758
|
own.posting = false;
|
|
17562
18759
|
});
|
|
17563
18760
|
}
|
|
18761
|
+
function postLog(own, log) {
|
|
18762
|
+
own.log = log;
|
|
18763
|
+
nextLog(own);
|
|
18764
|
+
}
|
|
18765
|
+
function nextLog(own) {
|
|
18766
|
+
if (own.logPosting || own.logTimer) return;
|
|
18767
|
+
const wait = LOG_SPACING_MS - (Date.now() - own.logSentAt);
|
|
18768
|
+
if (wait > 0) {
|
|
18769
|
+
own.logTimer = setTimeout(() => {
|
|
18770
|
+
own.logTimer = null;
|
|
18771
|
+
nextLog(own);
|
|
18772
|
+
}, wait);
|
|
18773
|
+
own.logTimer.unref?.();
|
|
18774
|
+
return;
|
|
18775
|
+
}
|
|
18776
|
+
const log = own.log;
|
|
18777
|
+
if (!active(own) || !view || !log || own.logRefused) return;
|
|
18778
|
+
own.logPosting = true;
|
|
18779
|
+
own.logSentAt = Date.now();
|
|
18780
|
+
let again = false;
|
|
18781
|
+
void post(own, { type: "call-log", ...own.id, log, test: running?.title ?? null }).then((status) => {
|
|
18782
|
+
if (status === 400) own.logRefused = true;
|
|
18783
|
+
again = status === 429;
|
|
18784
|
+
}).catch(() => void 0).finally(() => {
|
|
18785
|
+
own.logPosting = false;
|
|
18786
|
+
if (own.log && (own.log !== log || again)) nextLog(own);
|
|
18787
|
+
});
|
|
18788
|
+
}
|
|
17564
18789
|
function post(own, message) {
|
|
17565
18790
|
return active(own) ? send(own, message) : Promise.resolve(null);
|
|
17566
18791
|
}
|
|
17567
18792
|
function send(own, message) {
|
|
17568
18793
|
if (own.ended || own.refused !== null) return Promise.resolve(null);
|
|
17569
18794
|
const body = JSON.stringify(message);
|
|
17570
|
-
return new Promise((
|
|
18795
|
+
return new Promise((resolve12) => {
|
|
17571
18796
|
let settled = false;
|
|
17572
18797
|
const settle = (status) => {
|
|
17573
18798
|
if (settled) return;
|
|
17574
18799
|
settled = true;
|
|
17575
18800
|
if (status !== null && GIVE_UP.has(status)) refuse(own, status);
|
|
17576
|
-
|
|
18801
|
+
resolve12(status);
|
|
17577
18802
|
};
|
|
17578
18803
|
const request3 = own.client.request(own.address.url, {
|
|
17579
18804
|
method: "POST",
|
|
@@ -17600,7 +18825,6 @@ function send(own, message) {
|
|
|
17600
18825
|
// src/internal/capture/run-channel.ts
|
|
17601
18826
|
var CONNECT_MS = 2e3;
|
|
17602
18827
|
var BAR_HIDDEN2 = "#__piwi-pause-bar { display: none !important; }";
|
|
17603
|
-
var OUTLINE_STYLE = "outline: 2px dashed #a78bfa; outline-offset: 2px; background: rgba(167, 139, 250, 0.15);";
|
|
17604
18828
|
var WENT_ON = "The test went on.";
|
|
17605
18829
|
var MAX_FRAME_BACKLOG = 1024 * 1024;
|
|
17606
18830
|
var SHOT_MS2 = 500;
|
|
@@ -17608,6 +18832,7 @@ var LIGHT_QUALITY2 = 30;
|
|
|
17608
18832
|
var TOO_HEAVY2 = `This page is too heavy for the live view: each image of it is over ${MAX_VIEW_FRAME / 1024} KB, a lighter one included. The image of the stop still shows it.`;
|
|
17609
18833
|
var VIEW_END_MS2 = 1e3;
|
|
17610
18834
|
var PAGE_ANSWER_MS = 1e4;
|
|
18835
|
+
var OUTLINE_MS = 2e3;
|
|
17611
18836
|
var NO_ANSWER = "The page did not answer in time.";
|
|
17612
18837
|
var LATE_ACT = "The page did not answer in time: the action may still take effect, and is told when it does.";
|
|
17613
18838
|
var MAX_INPUT_WAITING = 4;
|
|
@@ -17647,11 +18872,12 @@ var sharedHere = false;
|
|
|
17647
18872
|
var liveSaid = "";
|
|
17648
18873
|
var unhearLive = null;
|
|
17649
18874
|
var NO_AGENT_PAGE = "This stop has no page an agent can read: its call does not name one among its context's pages.";
|
|
17650
|
-
function workerTakes(env = process.env) {
|
|
18875
|
+
function workerTakes(env = process.env, playwright) {
|
|
17651
18876
|
const pairing = !!env[PIWI_LOCAL_DEBUG_ENV.editorSend];
|
|
17652
18877
|
const recorder = parseRecordChannel(env[RECORD_CHANNEL_ENV]) !== null;
|
|
18878
|
+
const boxes = playwright === void 0 || playwrightVersionAtLeast(playwright, PICK_AT_SINCE);
|
|
17653
18879
|
return RUN_TAKES.filter(
|
|
17654
|
-
(
|
|
18880
|
+
(take2) => take2 === "pick" ? pairing : take2 === "record" || take2 === "assert" ? recorder : take2 === "pick-at" ? boxes : true
|
|
17655
18881
|
);
|
|
17656
18882
|
}
|
|
17657
18883
|
async function openRunChannel(worker, handlers, env = process.env) {
|
|
@@ -17660,15 +18886,15 @@ async function openRunChannel(worker, handlers, env = process.env) {
|
|
|
17660
18886
|
if (socket || !channel || !ref || !/^[\w-]{1,64}$/.test(ref)) return false;
|
|
17661
18887
|
const opening = net3.connect(channel.path);
|
|
17662
18888
|
opening.setEncoding("utf8");
|
|
17663
|
-
const opened = await new Promise((
|
|
17664
|
-
const timer = setTimeout(() =>
|
|
18889
|
+
const opened = await new Promise((resolve12) => {
|
|
18890
|
+
const timer = setTimeout(() => resolve12(false), CONNECT_MS);
|
|
17665
18891
|
opening.once("connect", () => {
|
|
17666
18892
|
clearTimeout(timer);
|
|
17667
|
-
|
|
18893
|
+
resolve12(true);
|
|
17668
18894
|
});
|
|
17669
18895
|
opening.once("error", () => {
|
|
17670
18896
|
clearTimeout(timer);
|
|
17671
|
-
|
|
18897
|
+
resolve12(false);
|
|
17672
18898
|
});
|
|
17673
18899
|
});
|
|
17674
18900
|
if (!opened || opening.destroyed) {
|
|
@@ -17711,8 +18937,10 @@ async function openRunChannel(worker, handlers, env = process.env) {
|
|
|
17711
18937
|
pid: process.pid,
|
|
17712
18938
|
playwright: worker.config.version,
|
|
17713
18939
|
reporter: getReporterVersion(),
|
|
17714
|
-
takes: workerTakes(env),
|
|
17715
|
-
holds: [...RUN_HOLDS]
|
|
18940
|
+
takes: workerTakes(env, worker.config.version),
|
|
18941
|
+
holds: [...RUN_HOLDS],
|
|
18942
|
+
// A run the reporter follows from outside the editor, which its own test runner started.
|
|
18943
|
+
...ref.startsWith(TERMINAL_REF_PREFIX) && startedByTestRunner(env) ? { runner: true } : {}
|
|
17716
18944
|
});
|
|
17717
18945
|
liveSaid = "";
|
|
17718
18946
|
tellLive();
|
|
@@ -17785,7 +19013,7 @@ async function unbind() {
|
|
|
17785
19013
|
const shared = bound?.browser;
|
|
17786
19014
|
bound = null;
|
|
17787
19015
|
if (shared?.isConnected()) {
|
|
17788
|
-
await
|
|
19016
|
+
await within3(
|
|
17789
19017
|
internalCall(shared, () => bindBrowser(shared, runName, process.cwd(), false)),
|
|
17790
19018
|
VIEW_END_MS2
|
|
17791
19019
|
);
|
|
@@ -17845,7 +19073,7 @@ function viewEnded(stop) {
|
|
|
17845
19073
|
else viewEnds.add(() => ended.abort());
|
|
17846
19074
|
return ended.signal;
|
|
17847
19075
|
}
|
|
17848
|
-
async function
|
|
19076
|
+
async function within3(run, ms) {
|
|
17849
19077
|
await answerWithin(run, ms, void 0);
|
|
17850
19078
|
}
|
|
17851
19079
|
async function answerWithin(run, ms, late) {
|
|
@@ -17853,20 +19081,20 @@ async function answerWithin(run, ms, late) {
|
|
|
17853
19081
|
try {
|
|
17854
19082
|
return await Promise.race([
|
|
17855
19083
|
run.catch(() => late),
|
|
17856
|
-
new Promise((
|
|
19084
|
+
new Promise((resolve12) => timer = setTimeout(() => resolve12(late), ms))
|
|
17857
19085
|
]);
|
|
17858
19086
|
} finally {
|
|
17859
19087
|
clearTimeout(timer);
|
|
17860
19088
|
}
|
|
17861
19089
|
}
|
|
17862
19090
|
async function endView(stop, page) {
|
|
17863
|
-
if (viewStarting?.stop === stop) await
|
|
19091
|
+
if (viewStarting?.stop === stop) await within3(viewStarting.started, VIEW_END_MS2);
|
|
17864
19092
|
if (view2?.stop === stop) await stopView2();
|
|
17865
19093
|
const own = input;
|
|
17866
19094
|
input = freshInput();
|
|
17867
|
-
await
|
|
19095
|
+
await within3(own.playing, VIEW_END_MS2);
|
|
17868
19096
|
if (page && !page.isClosed() && (own.buttons.size || own.keys.size)) {
|
|
17869
|
-
await
|
|
19097
|
+
await within3(
|
|
17870
19098
|
internalCall(page, async () => {
|
|
17871
19099
|
for (const button of own.buttons) await page.mouse.up({ button });
|
|
17872
19100
|
for (const key of own.keys) await page.keyboard.up(key);
|
|
@@ -17880,7 +19108,7 @@ async function stopView2() {
|
|
|
17880
19108
|
view2 = null;
|
|
17881
19109
|
for (const end of viewEnds) end();
|
|
17882
19110
|
viewEnds.clear();
|
|
17883
|
-
if (shown2) await
|
|
19111
|
+
if (shown2) await within3(shown2.close(), VIEW_END_MS2);
|
|
17884
19112
|
}
|
|
17885
19113
|
function startView2(holding) {
|
|
17886
19114
|
if (view2?.stop === holding.stop) {
|
|
@@ -17900,7 +19128,7 @@ function startView2(holding) {
|
|
|
17900
19128
|
}
|
|
17901
19129
|
if (!opened) return { ok: false, message: "The page could not be shown: it closed, or does not answer." };
|
|
17902
19130
|
if (held !== holding || page.isClosed()) {
|
|
17903
|
-
await
|
|
19131
|
+
await within3(opened.close(), VIEW_END_MS2);
|
|
17904
19132
|
return { ok: false, message: WENT_ON };
|
|
17905
19133
|
}
|
|
17906
19134
|
view2 = opened;
|
|
@@ -17944,7 +19172,7 @@ async function openView(page, stop) {
|
|
|
17944
19172
|
};
|
|
17945
19173
|
}
|
|
17946
19174
|
async function stopViewOf(holding) {
|
|
17947
|
-
if (viewStarting?.stop === holding.stop) await
|
|
19175
|
+
if (viewStarting?.stop === holding.stop) await within3(viewStarting.started, VIEW_END_MS2);
|
|
17948
19176
|
if (view2?.stop === holding.stop) await stopView2();
|
|
17949
19177
|
return { ok: true, message: "The live view is off." };
|
|
17950
19178
|
}
|
|
@@ -18035,18 +19263,17 @@ async function countLocator(holding, source) {
|
|
|
18035
19263
|
if (!locator) return { ok: false, message: `${source} is not a locator Piwi can build.`, count: null };
|
|
18036
19264
|
try {
|
|
18037
19265
|
const count = await internalCall(page, () => locator.count());
|
|
19266
|
+
let outlined = false;
|
|
18038
19267
|
if (holding.bar && count > 0 && held === holding) {
|
|
18039
|
-
const shown2 =
|
|
18040
|
-
|
|
18041
|
-
|
|
18042
|
-
stop: holding.stop,
|
|
18043
|
-
remove: () => internalCall(page, () => typeof dispose === "function" ? dispose.call(shown2) : page.hideHighlight())
|
|
18044
|
-
};
|
|
19268
|
+
const shown2 = highlightAtStop(page, locator, "count", source);
|
|
19269
|
+
outline = { stop: holding.stop, remove: async () => void await shown2.hide() };
|
|
19270
|
+
outlined = await answerWithin(shown2.shown, OUTLINE_MS, false);
|
|
18045
19271
|
}
|
|
18046
19272
|
return {
|
|
18047
19273
|
ok: true,
|
|
18048
19274
|
message: count === 0 ? "No element matches." : count === 1 ? "1 element matches." : `${count} elements match.`,
|
|
18049
|
-
count
|
|
19275
|
+
count,
|
|
19276
|
+
outlined
|
|
18050
19277
|
};
|
|
18051
19278
|
} catch (error) {
|
|
18052
19279
|
return { ok: false, message: `The locator could not be counted: ${errorMessage(error)}`, count: null };
|
|
@@ -18233,17 +19460,17 @@ async function imageOf(page) {
|
|
|
18233
19460
|
|
|
18234
19461
|
// src/internal/capture/run-events.ts
|
|
18235
19462
|
var begun = /* @__PURE__ */ new WeakMap();
|
|
18236
|
-
var
|
|
19463
|
+
var heard2 = /* @__PURE__ */ new WeakSet();
|
|
18237
19464
|
var watched = /* @__PURE__ */ new WeakSet();
|
|
18238
|
-
function
|
|
19465
|
+
function userFile2(file, cwd, windows) {
|
|
18239
19466
|
if (!file || /[\\/]node_modules[\\/]/.test(file)) return null;
|
|
18240
|
-
const
|
|
18241
|
-
if (!
|
|
18242
|
-
return
|
|
19467
|
+
const relative11 = callSitePath(file, cwd, windows);
|
|
19468
|
+
if (!relative11 || (windows ? path31.win32 : path31.posix).isAbsolute(relative11) || relative11.startsWith("..")) return null;
|
|
19469
|
+
return relative11;
|
|
18243
19470
|
}
|
|
18244
19471
|
function callPlace(frames, cwd = process.cwd(), windows = process.platform === "win32") {
|
|
18245
19472
|
for (const frame of frames ?? []) {
|
|
18246
|
-
const file =
|
|
19473
|
+
const file = userFile2(frame.file, cwd, windows);
|
|
18247
19474
|
if (!file || !frame.line || frame.line < 1) continue;
|
|
18248
19475
|
return { file, line: frame.line, ...frame.column && frame.column > 0 ? { column: frame.column } : {} };
|
|
18249
19476
|
}
|
|
@@ -18260,9 +19487,9 @@ function errorText(error) {
|
|
|
18260
19487
|
}
|
|
18261
19488
|
function watchCalls(playwright) {
|
|
18262
19489
|
const instrumentation = playwright?._instrumentation;
|
|
18263
|
-
if (!playwright ||
|
|
19490
|
+
if (!playwright || heard2.has(playwright)) return true;
|
|
18264
19491
|
if (!instrumentation || typeof instrumentation.addListener !== "function") return false;
|
|
18265
|
-
|
|
19492
|
+
heard2.add(playwright);
|
|
18266
19493
|
const titles = /* @__PURE__ */ new WeakMap();
|
|
18267
19494
|
instrumentation.addListener({
|
|
18268
19495
|
onApiCallBegin: (zone, channel) => {
|
|
@@ -18379,9 +19606,9 @@ function yamlScalar(text2) {
|
|
|
18379
19606
|
}
|
|
18380
19607
|
|
|
18381
19608
|
// src/internal/capture/record-handoff.ts
|
|
18382
|
-
var
|
|
19609
|
+
var fs27 = __toESM(require("fs"));
|
|
18383
19610
|
var net4 = __toESM(require("net"));
|
|
18384
|
-
var
|
|
19611
|
+
var path32 = __toESM(require("path"));
|
|
18385
19612
|
var START_WAIT_MS = 6e4;
|
|
18386
19613
|
var NEW_TAB_NOTICE = "A new tab opened: what you do there is not recorded yet. Record in the first tab.";
|
|
18387
19614
|
var relays = /* @__PURE__ */ new WeakMap();
|
|
@@ -18401,7 +19628,7 @@ function canRecordFromHere(channel = process.env[RECORD_CHANNEL_ENV]) {
|
|
|
18401
19628
|
function handoffPlace(location, cwd = process.cwd()) {
|
|
18402
19629
|
const m = /^(.+):(\d+):\d+$/.exec(location);
|
|
18403
19630
|
if (!m) return null;
|
|
18404
|
-
return { file:
|
|
19631
|
+
return { file: path32.resolve(cwd, m[1]), line: Number(m[2]) };
|
|
18405
19632
|
}
|
|
18406
19633
|
async function recordFromHere(options) {
|
|
18407
19634
|
const channel = parseRecordChannel(options.channel ?? process.env[RECORD_CHANNEL_ENV]);
|
|
@@ -18411,7 +19638,7 @@ async function recordFromHere(options) {
|
|
|
18411
19638
|
if (ended?.aborted) return "the test ended.";
|
|
18412
19639
|
const use = testInfo.project?.use ?? {};
|
|
18413
19640
|
const context = page.context();
|
|
18414
|
-
return new Promise((
|
|
19641
|
+
return new Promise((resolve12) => {
|
|
18415
19642
|
const socket2 = net4.connect(channel.path);
|
|
18416
19643
|
socket2.setEncoding("utf8");
|
|
18417
19644
|
const answers = /* @__PURE__ */ new Map();
|
|
@@ -18472,7 +19699,7 @@ async function recordFromHere(options) {
|
|
|
18472
19699
|
page.off("crash", onGone);
|
|
18473
19700
|
ended?.removeEventListener("abort", onEnded);
|
|
18474
19701
|
socket2.end();
|
|
18475
|
-
|
|
19702
|
+
resolve12(recording ? null : said);
|
|
18476
19703
|
};
|
|
18477
19704
|
const relayed = (source, payload) => new Promise((answer) => {
|
|
18478
19705
|
const call = ++calls;
|
|
@@ -18504,7 +19731,7 @@ async function recordFromHere(options) {
|
|
|
18504
19731
|
if (typeof initScript?.dispose !== "function") relay.lingering = true;
|
|
18505
19732
|
}
|
|
18506
19733
|
if (settled) return release();
|
|
18507
|
-
await page.evaluate(
|
|
19734
|
+
await page.evaluate(fs27.readFileSync(bundle, "utf-8"));
|
|
18508
19735
|
if (settled) return release();
|
|
18509
19736
|
recording = true;
|
|
18510
19737
|
clearTimeout(startWait);
|
|
@@ -18564,7 +19791,7 @@ async function recordFromHere(options) {
|
|
|
18564
19791
|
|
|
18565
19792
|
// src/internal/capture/css-coverage.ts
|
|
18566
19793
|
var crypto8 = __toESM(require("crypto"));
|
|
18567
|
-
var
|
|
19794
|
+
var fs28 = __toESM(require("fs"));
|
|
18568
19795
|
|
|
18569
19796
|
// ../core/src/css-rules.ts
|
|
18570
19797
|
var GROUPING = /* @__PURE__ */ new Set(["media", "supports", "layer", "container", "scope", "starting-style", "document"]);
|
|
@@ -18704,7 +19931,7 @@ function sourceFile(source, roots) {
|
|
|
18704
19931
|
function styleBlockLine(file, index) {
|
|
18705
19932
|
let text2;
|
|
18706
19933
|
try {
|
|
18707
|
-
text2 =
|
|
19934
|
+
text2 = fs28.readFileSync(file, "utf-8");
|
|
18708
19935
|
} catch {
|
|
18709
19936
|
return 0;
|
|
18710
19937
|
}
|
|
@@ -18732,10 +19959,10 @@ async function sheetMap(sheet, text2, fetchMap) {
|
|
|
18732
19959
|
return null;
|
|
18733
19960
|
}
|
|
18734
19961
|
}
|
|
18735
|
-
function
|
|
19962
|
+
function sameLines2(text2, rules, file, lineOffset) {
|
|
18736
19963
|
let source;
|
|
18737
19964
|
try {
|
|
18738
|
-
source =
|
|
19965
|
+
source = fs28.readFileSync(file, "utf-8");
|
|
18739
19966
|
} catch {
|
|
18740
19967
|
return false;
|
|
18741
19968
|
}
|
|
@@ -18770,12 +19997,12 @@ async function resolveSheet(sheet, text2, dirs, fetchMap) {
|
|
|
18770
19997
|
if (!file2) return null;
|
|
18771
19998
|
const index = /[?&]index=(\d+)/.exec(sheet.devId);
|
|
18772
19999
|
const lineOffset2 = /\.(?:vue|svelte|astro)$/i.test(file2) && index ? styleBlockLine(file2, Number(index[1])) : 0;
|
|
18773
|
-
return { rules, lookup: null, file: file2, lineOffset: lineOffset2, sources: [], exact:
|
|
20000
|
+
return { rules, lookup: null, file: file2, lineOffset: lineOffset2, sources: [], exact: sameLines2(text2, rules, file2, lineOffset2) };
|
|
18774
20001
|
}
|
|
18775
20002
|
const file = fileForStyleUrl(sheet.sourceURL, dirs);
|
|
18776
20003
|
if (!file) return null;
|
|
18777
20004
|
const lineOffset = sheet.isInline ? sheet.startLine : 0;
|
|
18778
|
-
return { rules, lookup: null, file, lineOffset, sources: [], exact:
|
|
20005
|
+
return { rules, lookup: null, file, lineOffset, sources: [], exact: sameLines2(text2, rules, file, lineOffset) };
|
|
18779
20006
|
}
|
|
18780
20007
|
function cachedResolution(sheet, text2, dirs, fetchMap) {
|
|
18781
20008
|
const version = crypto8.createHash("sha1").update(text2).digest("hex");
|
|
@@ -19137,7 +20364,8 @@ function createSink() {
|
|
|
19137
20364
|
locatorPages: new LocatorPageLog(),
|
|
19138
20365
|
codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map(), cssFiles: /* @__PURE__ */ new Map(), cssKnown: /* @__PURE__ */ new Map() },
|
|
19139
20366
|
serverCoverage: /* @__PURE__ */ new Map(),
|
|
19140
|
-
baseURL: null
|
|
20367
|
+
baseURL: null,
|
|
20368
|
+
headless: void 0
|
|
19141
20369
|
};
|
|
19142
20370
|
}
|
|
19143
20371
|
var MAX_CODE_REACH_PAGES = 16;
|
|
@@ -19162,7 +20390,7 @@ function maybeStartCodeReach(page, alreadyOpen = false) {
|
|
|
19162
20390
|
async function startCodeReachOnOpenPages() {
|
|
19163
20391
|
if (!pageCoverageInWorker() || OPEN_PAGES.size === 0) return;
|
|
19164
20392
|
const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
|
|
19165
|
-
await Promise.race([started, new Promise((
|
|
20393
|
+
await Promise.race([started, new Promise((resolve12) => setTimeout(resolve12, CODE_REACH_START_WAIT_MS).unref())]);
|
|
19166
20394
|
}
|
|
19167
20395
|
function configuredCodeReachRoots() {
|
|
19168
20396
|
const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
|
|
@@ -19176,7 +20404,7 @@ function configuredCodeReachRoots() {
|
|
|
19176
20404
|
}
|
|
19177
20405
|
} catch {
|
|
19178
20406
|
}
|
|
19179
|
-
return raw.split(
|
|
20407
|
+
return raw.split(path33.delimiter).filter(Boolean);
|
|
19180
20408
|
}
|
|
19181
20409
|
async function stopSinkCodeReach(sink, closing) {
|
|
19182
20410
|
const stopping = [];
|
|
@@ -19231,8 +20459,8 @@ async function drainPendingProbes(capMs) {
|
|
|
19231
20459
|
let cap;
|
|
19232
20460
|
await Promise.race([
|
|
19233
20461
|
Promise.allSettled(PENDING_PROBES),
|
|
19234
|
-
new Promise((
|
|
19235
|
-
cap = setTimeout(
|
|
20462
|
+
new Promise((resolve12) => {
|
|
20463
|
+
cap = setTimeout(resolve12, capMs);
|
|
19236
20464
|
})
|
|
19237
20465
|
]);
|
|
19238
20466
|
clearTimeout(cap);
|
|
@@ -19291,10 +20519,10 @@ async function readWebVitals(page) {
|
|
|
19291
20519
|
const key = entry.name.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
19292
20520
|
paint[key] = Math.round(entry.startTime);
|
|
19293
20521
|
}
|
|
19294
|
-
const readBuffered = (type, extra) => new Promise((
|
|
20522
|
+
const readBuffered = (type, extra) => new Promise((resolve12) => {
|
|
19295
20523
|
try {
|
|
19296
20524
|
const PO = globalThis.PerformanceObserver;
|
|
19297
|
-
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return
|
|
20525
|
+
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve12(null);
|
|
19298
20526
|
const out = [];
|
|
19299
20527
|
const po = new PO((list) => out.push(...list.getEntries()));
|
|
19300
20528
|
po.observe({ type, buffered: true, ...extra });
|
|
@@ -19304,10 +20532,10 @@ async function readWebVitals(page) {
|
|
|
19304
20532
|
po.disconnect();
|
|
19305
20533
|
} catch {
|
|
19306
20534
|
}
|
|
19307
|
-
|
|
20535
|
+
resolve12(out);
|
|
19308
20536
|
}, 0);
|
|
19309
20537
|
} catch {
|
|
19310
|
-
|
|
20538
|
+
resolve12(null);
|
|
19311
20539
|
}
|
|
19312
20540
|
});
|
|
19313
20541
|
const [lcpRaw, shiftRaw, eventRaw, firstInputRaw] = await Promise.all([
|
|
@@ -19424,12 +20652,12 @@ var PAGE_INVENTORY_IN_PAGE_CAP = 2e3;
|
|
|
19424
20652
|
var PAGE_INVENTORY_EVAL_TIMEOUT_MS = 2e3;
|
|
19425
20653
|
var MAX_PAGE_INVENTORY_WINDOWS = 64;
|
|
19426
20654
|
function withTimeout(promise, ms) {
|
|
19427
|
-
return new Promise((
|
|
20655
|
+
return new Promise((resolve12, reject) => {
|
|
19428
20656
|
const timer = setTimeout(() => reject(new Error("page inventory read timed out")), ms);
|
|
19429
20657
|
promise.then(
|
|
19430
20658
|
(value) => {
|
|
19431
20659
|
clearTimeout(timer);
|
|
19432
|
-
|
|
20660
|
+
resolve12(value);
|
|
19433
20661
|
},
|
|
19434
20662
|
(error) => {
|
|
19435
20663
|
clearTimeout(timer);
|
|
@@ -19548,6 +20776,20 @@ var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
|
|
|
19548
20776
|
var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
|
|
19549
20777
|
var ACTION_METHOD_SET = new Set(ACTION_METHODS);
|
|
19550
20778
|
var LOCATOR_METHOD_SET = new Set(LOCATOR_METHODS);
|
|
20779
|
+
var EARLY_READ_ACTIONS = /* @__PURE__ */ new Set([
|
|
20780
|
+
"click",
|
|
20781
|
+
"dblclick",
|
|
20782
|
+
"tap",
|
|
20783
|
+
"press",
|
|
20784
|
+
"pressSequentially",
|
|
20785
|
+
"type",
|
|
20786
|
+
"check",
|
|
20787
|
+
"uncheck",
|
|
20788
|
+
"selectOption",
|
|
20789
|
+
"dispatchEvent"
|
|
20790
|
+
]);
|
|
20791
|
+
var CAPTURE_DEADLINE_MS = 500;
|
|
20792
|
+
var EARLY_READ_TRUSTED_MS = 250;
|
|
19551
20793
|
var CAPTURED_ATTRS_ARG = {
|
|
19552
20794
|
keep: [...CAPTURED_ATTRIBUTES],
|
|
19553
20795
|
tagRoles: TAG_TO_ROLE,
|
|
@@ -19566,15 +20808,36 @@ var PROBE_INIT_SCRIPT = `(() => {
|
|
|
19566
20808
|
globalThis[${JSON.stringify(PROBE_GLOBAL)}] = (el) => probe(el, arg);
|
|
19567
20809
|
})();`;
|
|
19568
20810
|
var PROBE_UNSEEDED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
19569
|
-
|
|
19570
|
-
|
|
20811
|
+
var PAGE_NAVIGATIONS = /* @__PURE__ */ new WeakMap();
|
|
20812
|
+
function readyProbeWorld(page) {
|
|
20813
|
+
if (!currentSink) return;
|
|
20814
|
+
try {
|
|
20815
|
+
const ready = internalCall(
|
|
20816
|
+
page,
|
|
20817
|
+
() => page.mainFrame().locator(":root").evaluate(() => 0, void 0, { timeout: CAPTURE_DEADLINE_MS })
|
|
20818
|
+
).then(
|
|
20819
|
+
() => void 0,
|
|
20820
|
+
() => void 0
|
|
20821
|
+
);
|
|
20822
|
+
PENDING_PROBES.add(ready);
|
|
20823
|
+
void ready.then(() => PENDING_PROBES.delete(ready));
|
|
20824
|
+
} catch {
|
|
20825
|
+
}
|
|
20826
|
+
}
|
|
20827
|
+
function probeElement(page, target, timeout) {
|
|
20828
|
+
const options = timeout != null ? { timeout } : void 0;
|
|
20829
|
+
const shipSource = () => target.evaluate(probeElementAttrs, CAPTURED_ATTRS_ARG, options);
|
|
19571
20830
|
if (!page || PROBE_UNSEEDED_PAGES.has(page)) return internalCall(page, shipSource);
|
|
19572
20831
|
return internalCall(
|
|
19573
20832
|
page,
|
|
19574
|
-
() => target.evaluate(
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
20833
|
+
() => target.evaluate(
|
|
20834
|
+
(el, name) => {
|
|
20835
|
+
const seeded = globalThis[name];
|
|
20836
|
+
return typeof seeded === "function" ? seeded(el) : null;
|
|
20837
|
+
},
|
|
20838
|
+
PROBE_GLOBAL,
|
|
20839
|
+
options
|
|
20840
|
+
).then((attrs) => {
|
|
19578
20841
|
if (attrs) return attrs;
|
|
19579
20842
|
PROBE_UNSEEDED_PAGES.add(page);
|
|
19580
20843
|
return shipSource();
|
|
@@ -19609,54 +20872,60 @@ async function ariaSnapshotJSONBestEffort(target, timeout) {
|
|
|
19609
20872
|
return null;
|
|
19610
20873
|
}
|
|
19611
20874
|
}
|
|
19612
|
-
function
|
|
19613
|
-
|
|
19614
|
-
|
|
19615
|
-
|
|
19616
|
-
|
|
19617
|
-
|
|
19618
|
-
|
|
19619
|
-
|
|
19620
|
-
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
19624
|
-
|
|
19625
|
-
|
|
19626
|
-
|
|
19627
|
-
|
|
19628
|
-
|
|
19629
|
-
|
|
19630
|
-
|
|
19631
|
-
|
|
19632
|
-
|
|
19633
|
-
|
|
19634
|
-
|
|
19635
|
-
|
|
19636
|
-
|
|
19637
|
-
|
|
19638
|
-
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
|
|
19643
|
-
|
|
19644
|
-
|
|
19645
|
-
|
|
19646
|
-
|
|
19647
|
-
|
|
19648
|
-
|
|
19649
|
-
|
|
19650
|
-
|
|
19651
|
-
|
|
19652
|
-
|
|
19653
|
-
|
|
19654
|
-
|
|
19655
|
-
|
|
19656
|
-
|
|
19657
|
-
|
|
20875
|
+
async function readElementSnapshot(sink, page, target, callerLocation, used) {
|
|
20876
|
+
let deadline;
|
|
20877
|
+
try {
|
|
20878
|
+
const probe = probeElement(page, target, CAPTURE_DEADLINE_MS);
|
|
20879
|
+
const settledProbe = probe.then(
|
|
20880
|
+
() => void 0,
|
|
20881
|
+
() => void 0
|
|
20882
|
+
);
|
|
20883
|
+
PENDING_PROBES.add(settledProbe);
|
|
20884
|
+
settledProbe.then(() => PENDING_PROBES.delete(settledProbe));
|
|
20885
|
+
sink.capturePromises.push(settledProbe);
|
|
20886
|
+
const attrs = await Promise.race([
|
|
20887
|
+
probe,
|
|
20888
|
+
new Promise((_, reject) => {
|
|
20889
|
+
deadline = setTimeout(() => reject(new Error("locator capture timeout")), CAPTURE_DEADLINE_MS);
|
|
20890
|
+
})
|
|
20891
|
+
]);
|
|
20892
|
+
const role = resolveAriaRole({ ...attrs, accessibleName: null });
|
|
20893
|
+
const isFormField = FORM_FIELD_TAGS.has(attrs.tagName);
|
|
20894
|
+
const exactName = exactAccessibleName(attrs, role);
|
|
20895
|
+
const aria = exactName === null && (role || isFormField) ? await internalCall(page, () => ariaSnapshotBestEffort(target, CAPTURE_DEADLINE_MS)) : null;
|
|
20896
|
+
const accessibleName = exactName ?? (extractAccessibleName(aria) || approximateAccessibleName({ ...attrs, accessibleName: null }));
|
|
20897
|
+
return {
|
|
20898
|
+
location: callerLocation,
|
|
20899
|
+
used,
|
|
20900
|
+
// hasLabel/selectorCounts inform alternative generation only —
|
|
20901
|
+
// keep the stored element to the wire shape. rolePosition and
|
|
20902
|
+
// ancestors ARE wire fields: the server's renamed-element match
|
|
20903
|
+
// uses them at heal time.
|
|
20904
|
+
element: {
|
|
20905
|
+
tagName: attrs.tagName,
|
|
20906
|
+
attributes: attrs.attributes,
|
|
20907
|
+
textContent: attrs.textContent,
|
|
20908
|
+
accessibleName,
|
|
20909
|
+
center: attrs.center,
|
|
20910
|
+
...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
|
|
20911
|
+
...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
|
|
20912
|
+
},
|
|
20913
|
+
alternatives: generateAlternatives({ ...attrs, accessibleName })
|
|
20914
|
+
};
|
|
20915
|
+
} catch {
|
|
20916
|
+
return null;
|
|
20917
|
+
} finally {
|
|
20918
|
+
clearTimeout(deadline);
|
|
20919
|
+
}
|
|
20920
|
+
}
|
|
20921
|
+
function startElementCapture(sink, page, target, seq, callerLocation, used, early = null, earlyFirst = false) {
|
|
20922
|
+
const capture = (async () => {
|
|
20923
|
+
const readNow = () => readElementSnapshot(sink, page, target, callerLocation, used);
|
|
20924
|
+
const snapshot = earlyFirst ? await early ?? await readNow() : await readNow() ?? await early;
|
|
20925
|
+
if (snapshot) sink.capturedLocators[seq] = snapshot;
|
|
20926
|
+
else if (callerLocation) sink.probedLocations.delete(callerLocation);
|
|
19658
20927
|
})();
|
|
19659
|
-
sink.capturePromises.push(
|
|
20928
|
+
sink.capturePromises.push(capture);
|
|
19660
20929
|
}
|
|
19661
20930
|
function recordLocatorPage(sink, page, locator, location, arrival) {
|
|
19662
20931
|
try {
|
|
@@ -19668,7 +20937,7 @@ function knowLocatorCall(sink, page, target, location, origin) {
|
|
|
19668
20937
|
if (!sink?.pause || !location) return null;
|
|
19669
20938
|
let settle = () => {
|
|
19670
20939
|
};
|
|
19671
|
-
const settled = new Promise((
|
|
20940
|
+
const settled = new Promise((resolve12) => settle = resolve12);
|
|
19672
20941
|
const call = { page, target, location, origin, picked: false, endable: false, settled, settle };
|
|
19673
20942
|
sink.locatorCalls.set(callSiteLine(location), call);
|
|
19674
20943
|
return call;
|
|
@@ -19776,7 +21045,7 @@ function endedWaiting(sink, call) {
|
|
|
19776
21045
|
return "finish";
|
|
19777
21046
|
}
|
|
19778
21047
|
function stopSite(sink, location) {
|
|
19779
|
-
if (
|
|
21048
|
+
if (path33.dirname(path33.resolve(location.file)) !== __dirname) return stopCallSite(location);
|
|
19780
21049
|
return inFlightCallSite([...sink.locatorCalls.values()].map((call) => call.location));
|
|
19781
21050
|
}
|
|
19782
21051
|
function stopBarPage(sink, context, of) {
|
|
@@ -19791,7 +21060,7 @@ async function showStop(sink, context, title, location, reason, call, of, failur
|
|
|
19791
21060
|
const page = stopBarPage(sink, context, of);
|
|
19792
21061
|
const guessed = !("page" in of);
|
|
19793
21062
|
if (!testInfo) return void 0;
|
|
19794
|
-
const bar = !!page && testInfo.
|
|
21063
|
+
const bar = !!page && pauseGateFromTestInfo(testInfo, sink.headless).headless === false;
|
|
19795
21064
|
const place = runChannelOpen() ? callSitePlace(location) : null;
|
|
19796
21065
|
if (!bar && !place) return void 0;
|
|
19797
21066
|
const failing = failure?.failed ?? null;
|
|
@@ -19899,6 +21168,7 @@ async function showStop(sink, context, title, location, reason, call, of, failur
|
|
|
19899
21168
|
}
|
|
19900
21169
|
return "resume";
|
|
19901
21170
|
}
|
|
21171
|
+
const stoppedOn = call?.page === page ? call : null;
|
|
19902
21172
|
return internalCall(
|
|
19903
21173
|
page,
|
|
19904
21174
|
() => pauseHere(
|
|
@@ -19907,7 +21177,14 @@ async function showStop(sink, context, title, location, reason, call, of, failur
|
|
|
19907
21177
|
location,
|
|
19908
21178
|
call: title,
|
|
19909
21179
|
attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
|
|
19910
|
-
target:
|
|
21180
|
+
target: stoppedOn ? {
|
|
21181
|
+
highlight: () => highlightAtStop(
|
|
21182
|
+
page,
|
|
21183
|
+
stoppedOn.target,
|
|
21184
|
+
"call",
|
|
21185
|
+
locatorSource(stoppedOn.target, stoppedOn.origin.method, stoppedOn.origin.args)
|
|
21186
|
+
)
|
|
21187
|
+
} : null,
|
|
19911
21188
|
...failure ? { failure: true } : sink.functionEnded ? {} : { end: true }
|
|
19912
21189
|
},
|
|
19913
21190
|
probe,
|
|
@@ -19954,7 +21231,7 @@ function endTest(sink, call, title, location, reason) {
|
|
|
19954
21231
|
return "end";
|
|
19955
21232
|
}
|
|
19956
21233
|
const said = hideSecretsInText(
|
|
19957
|
-
`${title} at ${line} runs before the test ends: Piwi holds no locator call there. The test is skipped at its next locator call, or as it ends.`
|
|
21234
|
+
call && !takesEndSignal(sink.testInfo.config?.version) ? `${title} at ${line} runs before the test ends: a locator call is given up from Playwright 1.62 on. The test is skipped as the call returns.` : `${title} at ${line} runs before the test ends: Piwi holds no locator call there. The test is skipped at its next locator call, or as it ends.`
|
|
19958
21235
|
);
|
|
19959
21236
|
console.log(`[piwi] ${said}`);
|
|
19960
21237
|
tellHub({ type: "notice", message: said });
|
|
@@ -20113,8 +21390,8 @@ function errorPlace(error, windows = process.platform === "win32") {
|
|
|
20113
21390
|
for (const line of typeof stack === "string" ? stack.split("\n") : []) {
|
|
20114
21391
|
const frame = /^\s*at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?$/.exec(line);
|
|
20115
21392
|
const file = frame ? framePath(frame[1], windows) : null;
|
|
20116
|
-
if (!frame || !file || !(windows ?
|
|
20117
|
-
if (/[\\/]node_modules[\\/]/.test(file) ||
|
|
21393
|
+
if (!frame || !file || !(windows ? path33.win32 : path33.posix).isAbsolute(file)) continue;
|
|
21394
|
+
if (/[\\/]node_modules[\\/]/.test(file) || path33.dirname(path33.resolve(file)) === __dirname) continue;
|
|
20118
21395
|
return { file, line: Number(frame[2]), column: Number(frame[3]) };
|
|
20119
21396
|
}
|
|
20120
21397
|
return null;
|
|
@@ -20125,7 +21402,7 @@ function callInProgress(testInfo) {
|
|
|
20125
21402
|
if (!Array.isArray(steps)) return;
|
|
20126
21403
|
for (const step of steps) {
|
|
20127
21404
|
if (step.endWallTime) continue;
|
|
20128
|
-
const at = step.stack?.[0];
|
|
21405
|
+
const at = step.stack?.[0] ?? step.location;
|
|
20129
21406
|
const called = step.category === "pw:api" || step.category === "expect" || step.category === "test.step";
|
|
20130
21407
|
if (called && typeof at?.file === "string" && typeof at.line === "number") {
|
|
20131
21408
|
found = {
|
|
@@ -20145,13 +21422,13 @@ function stepInProgress(testInfo) {
|
|
|
20145
21422
|
function runningCall(testInfo) {
|
|
20146
21423
|
const call = callInProgress(testInfo);
|
|
20147
21424
|
if (!call?.title) return null;
|
|
20148
|
-
const file =
|
|
21425
|
+
const file = path33.relative(process.cwd(), call.place.file).split(path33.sep).join("/");
|
|
20149
21426
|
return { title: call.title, file, line: call.place.line };
|
|
20150
21427
|
}
|
|
20151
21428
|
function abortion(signal) {
|
|
20152
|
-
return new Promise((
|
|
20153
|
-
if (signal.aborted)
|
|
20154
|
-
else signal.addEventListener("abort", () =>
|
|
21429
|
+
return new Promise((resolve12) => {
|
|
21430
|
+
if (signal.aborted) resolve12();
|
|
21431
|
+
else signal.addEventListener("abort", () => resolve12(), { once: true });
|
|
20155
21432
|
});
|
|
20156
21433
|
}
|
|
20157
21434
|
var END_STOP_MS = 2e3;
|
|
@@ -20186,7 +21463,7 @@ function releaseCall(sink, settled) {
|
|
|
20186
21463
|
}
|
|
20187
21464
|
function releasedRequestsEnded(sink) {
|
|
20188
21465
|
if (sink.releasedRequests.size === 0) return Promise.resolve();
|
|
20189
|
-
return new Promise((
|
|
21466
|
+
return new Promise((resolve12) => sink.onReleasedRequestsEnded = resolve12);
|
|
20190
21467
|
}
|
|
20191
21468
|
async function awaitReleasedCalls(sink) {
|
|
20192
21469
|
if (sink.released.length === 0) return;
|
|
@@ -20199,13 +21476,13 @@ async function awaitReleasedCalls(sink) {
|
|
|
20199
21476
|
}
|
|
20200
21477
|
const quiet = now - sink.releasedQuietSince;
|
|
20201
21478
|
if (quiet >= RELEASED_QUIET_MS) return;
|
|
20202
|
-
await new Promise((
|
|
21479
|
+
await new Promise((resolve12) => setTimeout(resolve12, Math.min(RELEASED_QUIET_MS - quiet, deadline - now)));
|
|
20203
21480
|
}
|
|
20204
21481
|
}
|
|
20205
21482
|
async function settledWithin(pending, ms) {
|
|
20206
21483
|
if (pending.length === 0) return;
|
|
20207
21484
|
let timer;
|
|
20208
|
-
await Promise.race([Promise.allSettled(pending), new Promise((
|
|
21485
|
+
await Promise.race([Promise.allSettled(pending), new Promise((resolve12) => timer = setTimeout(resolve12, ms))]);
|
|
20209
21486
|
clearTimeout(timer);
|
|
20210
21487
|
}
|
|
20211
21488
|
function endStopsWithTestFunction(testInfo, sink) {
|
|
@@ -20238,7 +21515,7 @@ async function pauseInPlace(page, playwrightPause, location) {
|
|
|
20238
21515
|
if (!sink || !testInfo || !location || !context || !contextDebugger(context)) {
|
|
20239
21516
|
return leaveToPlaywright(context, playwrightPause);
|
|
20240
21517
|
}
|
|
20241
|
-
if (!sink.pause && (!canRecordFromHere() || pauseSkipReason(pauseGateFromTestInfo(testInfo)))) {
|
|
21518
|
+
if (!sink.pause && (!canRecordFromHere() || pauseSkipReason(pauseGateFromTestInfo(testInfo, sink.headless)))) {
|
|
20242
21519
|
return leaveToPlaywright(context, playwrightPause);
|
|
20243
21520
|
}
|
|
20244
21521
|
sink.pause ??= pauseState(NO_BREAKPOINTS);
|
|
@@ -20306,7 +21583,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
20306
21583
|
const callerLocation = sink ? captureCallerLocation() : null;
|
|
20307
21584
|
if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
|
|
20308
21585
|
const call = knowLocatorCall(sink, page, target, callerLocation, { method: originMethod, args: originArgs });
|
|
20309
|
-
const ending = sink?.pause && !sink.ended ? withEndSignal(EXPECT_METHOD, callArgs, sink.end.signal) : null;
|
|
21586
|
+
const ending = sink?.pause && !sink.ended && takesEndSignal(sink.testInfo?.config?.version) ? withEndSignal(EXPECT_METHOD, callArgs, sink.end.signal) : null;
|
|
20310
21587
|
if (call && ending) call.endable = true;
|
|
20311
21588
|
const expectArgs = ending ?? callArgs;
|
|
20312
21589
|
try {
|
|
@@ -20359,7 +21636,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
20359
21636
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
20360
21637
|
};
|
|
20361
21638
|
const call = knowLocatorCall(sink, page, target, callerLocation, used);
|
|
20362
|
-
const ending = sink.pause && !sink.ended ? withEndSignal(String(prop), callArgs, sink.end.signal) : null;
|
|
21639
|
+
const ending = sink.pause && !sink.ended && takesEndSignal(sink.testInfo?.config?.version) ? withEndSignal(String(prop), callArgs, sink.end.signal) : null;
|
|
20363
21640
|
if (call && ending) call.endable = true;
|
|
20364
21641
|
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
20365
21642
|
let seq = -1;
|
|
@@ -20372,7 +21649,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
20372
21649
|
alternatives: []
|
|
20373
21650
|
});
|
|
20374
21651
|
}
|
|
21652
|
+
const early = !alreadyProbed && EARLY_READ_ACTIONS.has(prop) ? readElementSnapshot(sink, page, target, callerLocation, used) : null;
|
|
20375
21653
|
let result;
|
|
21654
|
+
const startedAt = Date.now();
|
|
21655
|
+
const navigations = PAGE_NAVIGATIONS.get(page) ?? 0;
|
|
20376
21656
|
try {
|
|
20377
21657
|
result = await callMethod(target, prop, ending ?? callArgs);
|
|
20378
21658
|
} catch (error) {
|
|
@@ -20385,7 +21665,8 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
20385
21665
|
if (endsNow(sink)) throw skipOf(sink);
|
|
20386
21666
|
if (!alreadyProbed && !call?.picked) {
|
|
20387
21667
|
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
20388
|
-
|
|
21668
|
+
const earlyFirst = (PAGE_NAVIGATIONS.get(page) ?? 0) !== navigations || Date.now() - startedAt < EARLY_READ_TRUSTED_MS;
|
|
21669
|
+
startElementCapture(sink, page, target, seq, callerLocation, used, early, earlyFirst);
|
|
20389
21670
|
}
|
|
20390
21671
|
return result;
|
|
20391
21672
|
};
|
|
@@ -20447,7 +21728,7 @@ function instrumentPage(page) {
|
|
|
20447
21728
|
page.close = async (...args) => {
|
|
20448
21729
|
let closed = () => {
|
|
20449
21730
|
};
|
|
20450
|
-
CLOSING_PAGES.set(page, new Promise((
|
|
21731
|
+
CLOSING_PAGES.set(page, new Promise((resolve12) => closed = resolve12));
|
|
20451
21732
|
try {
|
|
20452
21733
|
await settleBeforeClose({ page });
|
|
20453
21734
|
return await originalClose(...args);
|
|
@@ -20475,10 +21756,14 @@ function instrumentPage(page) {
|
|
|
20475
21756
|
};
|
|
20476
21757
|
}
|
|
20477
21758
|
if (typeof page.on === "function") {
|
|
21759
|
+
page.on("domcontentloaded", () => readyProbeWorld(page));
|
|
20478
21760
|
page.on("framenavigated", (frame) => {
|
|
20479
21761
|
PROBE_UNSEEDED_PAGES.delete(page);
|
|
20480
21762
|
try {
|
|
20481
|
-
if (frame === page.mainFrame())
|
|
21763
|
+
if (frame === page.mainFrame()) {
|
|
21764
|
+
PAGE_NAVIGATIONS.set(page, (PAGE_NAVIGATIONS.get(page) ?? 0) + 1);
|
|
21765
|
+
noteNavigation(page);
|
|
21766
|
+
}
|
|
20482
21767
|
} catch {
|
|
20483
21768
|
}
|
|
20484
21769
|
});
|
|
@@ -20759,8 +22044,8 @@ async function flushSink(sink, testInfo) {
|
|
|
20759
22044
|
let drainDeadline;
|
|
20760
22045
|
await Promise.race([
|
|
20761
22046
|
Promise.allSettled(sink.capturePromises),
|
|
20762
|
-
new Promise((
|
|
20763
|
-
drainDeadline = setTimeout(
|
|
22047
|
+
new Promise((resolve12) => {
|
|
22048
|
+
drainDeadline = setTimeout(resolve12, 2e3);
|
|
20764
22049
|
})
|
|
20765
22050
|
]);
|
|
20766
22051
|
clearTimeout(drainDeadline);
|
|
@@ -20967,7 +22252,7 @@ var runControl = {
|
|
|
20967
22252
|
return;
|
|
20968
22253
|
}
|
|
20969
22254
|
if (!sink.pause) {
|
|
20970
|
-
const why = sink.testInfo ? pauseSkipReason({ ...pauseGateFromTestInfo(sink.testInfo), channel: true }) : null;
|
|
22255
|
+
const why = sink.testInfo ? pauseSkipReason({ ...pauseGateFromTestInfo(sink.testInfo, sink.headless), channel: true }) : null;
|
|
20971
22256
|
tellHub({ type: "notice", message: `This test does not stop${why ? `: ${why}` : ""}.` });
|
|
20972
22257
|
return;
|
|
20973
22258
|
}
|
|
@@ -20989,7 +22274,7 @@ var runControl = {
|
|
|
20989
22274
|
}
|
|
20990
22275
|
};
|
|
20991
22276
|
function runFile(file) {
|
|
20992
|
-
return
|
|
22277
|
+
return path33.relative(process.cwd(), file).split(path33.sep).join("/") || file;
|
|
20993
22278
|
}
|
|
20994
22279
|
function testBegin(testInfo, cannotPause) {
|
|
20995
22280
|
const title = testInfo.titlePath.slice(1);
|
|
@@ -21029,6 +22314,7 @@ var piwiFixtures = {
|
|
|
21029
22314
|
startResourceLedger(playwright, workerInfo.workerIndex);
|
|
21030
22315
|
observeApiRequests(playwright, apiCallRecorder);
|
|
21031
22316
|
openLiveView(workerInfo);
|
|
22317
|
+
if (liveViewOpen()) trackCalls(playwright);
|
|
21032
22318
|
if (await openRunChannel(workerInfo, runControl)) watchCalls(playwright);
|
|
21033
22319
|
try {
|
|
21034
22320
|
await use();
|
|
@@ -21066,17 +22352,18 @@ var piwiFixtures = {
|
|
|
21066
22352
|
// (attach the collected data) at teardown. Runs for every test without being
|
|
21067
22353
|
// requested, so suites that never destructure `page` are still captured.
|
|
21068
22354
|
piwiCapture: [
|
|
21069
|
-
async ({ screenshot, baseURL }, use, testInfo) => {
|
|
22355
|
+
async ({ screenshot, baseURL, headless }, use, testInfo) => {
|
|
21070
22356
|
boxCaptureFrames();
|
|
21071
22357
|
const conflict = labModeConflict();
|
|
21072
22358
|
if (conflict) throw new Error(conflict);
|
|
21073
22359
|
const sink = createSink();
|
|
21074
22360
|
sink.testInfo = testInfo;
|
|
21075
22361
|
sink.baseURL = baseURL ?? null;
|
|
22362
|
+
sink.headless = headless;
|
|
21076
22363
|
if (keepsFailureScreenshots(screenshot, testInfo.retry)) sink.failureScreenshot = screenshot;
|
|
21077
22364
|
const pauseSet = currentPauseSet();
|
|
21078
22365
|
const channel = runChannelOpen();
|
|
21079
|
-
const cannotPause = pauseSkipReason({ ...pauseGateFromTestInfo(testInfo), channel });
|
|
22366
|
+
const cannotPause = pauseSkipReason({ ...pauseGateFromTestInfo(testInfo, headless), channel });
|
|
21080
22367
|
if (pauseSet.size || channel) {
|
|
21081
22368
|
if (!cannotPause) sink.pause = pauseState(pauseSet, pauseNextTest);
|
|
21082
22369
|
else if (pauseSet.size) logPauseSkipOnce(cannotPause);
|
|
@@ -21108,7 +22395,7 @@ var piwiFixtures = {
|
|
|
21108
22395
|
});
|
|
21109
22396
|
activeResourceLedger()?.testStarted(testInfo);
|
|
21110
22397
|
currentSink = sink;
|
|
21111
|
-
liveViewTestBegan(testInfo.title, () => runningCall(testInfo));
|
|
22398
|
+
liveViewTestBegan(testInfo.title, () => runningCall(testInfo), mayPause(pauseGateFromTestInfo(testInfo)));
|
|
21112
22399
|
const showRunChannel = hideRunChannel();
|
|
21113
22400
|
try {
|
|
21114
22401
|
await armStops(OPEN_CONTEXTS);
|
|
@@ -21126,9 +22413,10 @@ var piwiFixtures = {
|
|
|
21126
22413
|
const applied = sink.probeInterception?.applied() ?? false;
|
|
21127
22414
|
const level = sink.probeItem.level ?? "client";
|
|
21128
22415
|
const handled = level === "server" && applied ? classifyProbeHandled({
|
|
21129
|
-
consoleErrors: sink.consoleEntries
|
|
22416
|
+
consoleErrors: probeConsoleErrors(sink.consoleEntries),
|
|
21130
22417
|
dialogs: sink.dialogs.length,
|
|
21131
22418
|
backendError: sink.probeInterception?.serverError() ?? false,
|
|
22419
|
+
faultIsTheError: faultIsTheError(sink.probeItem.fault),
|
|
21132
22420
|
pageErrors: sink.probePageErrors,
|
|
21133
22421
|
blankPage: sink.probeBlankPage
|
|
21134
22422
|
}) : "n/a";
|
|
@@ -21183,13 +22471,13 @@ function extendPiwiFixtures(test) {
|
|
|
21183
22471
|
}
|
|
21184
22472
|
|
|
21185
22473
|
// src/internal/ai/ai-fixtures.ts
|
|
21186
|
-
var
|
|
21187
|
-
var
|
|
22474
|
+
var fs30 = __toESM(require("fs"));
|
|
22475
|
+
var path36 = __toESM(require("path"));
|
|
21188
22476
|
var import_test = require("@playwright/test");
|
|
21189
22477
|
|
|
21190
22478
|
// src/internal/ai/artifact.ts
|
|
21191
|
-
var
|
|
21192
|
-
var
|
|
22479
|
+
var fs29 = __toESM(require("fs"));
|
|
22480
|
+
var path34 = __toESM(require("path"));
|
|
21193
22481
|
var ARTIFACT_VERSION = 1;
|
|
21194
22482
|
var LOCATOR_METHOD_SET2 = new Set(LOCATOR_METHODS);
|
|
21195
22483
|
var ACTION_METHOD_SET2 = new Set(ACTION_METHODS);
|
|
@@ -21282,7 +22570,7 @@ function parseEntry(text2) {
|
|
|
21282
22570
|
function readEntry(file) {
|
|
21283
22571
|
let text2;
|
|
21284
22572
|
try {
|
|
21285
|
-
text2 =
|
|
22573
|
+
text2 = fs29.readFileSync(file, "utf8");
|
|
21286
22574
|
} catch (error) {
|
|
21287
22575
|
if (error.code === "ENOENT") return null;
|
|
21288
22576
|
throw error;
|
|
@@ -21292,28 +22580,28 @@ function readEntry(file) {
|
|
|
21292
22580
|
function writeEntry(file, entry) {
|
|
21293
22581
|
const canonical = serializeEntry(entry);
|
|
21294
22582
|
return withEntryLock(file, () => {
|
|
21295
|
-
if (
|
|
21296
|
-
|
|
22583
|
+
if (fs29.existsSync(file) && fs29.readFileSync(file, "utf8") === canonical) return { written: false };
|
|
22584
|
+
fs29.mkdirSync(path34.dirname(file), { recursive: true });
|
|
21297
22585
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
21298
|
-
|
|
21299
|
-
|
|
22586
|
+
fs29.writeFileSync(tmp, canonical);
|
|
22587
|
+
fs29.renameSync(tmp, file);
|
|
21300
22588
|
return { written: true };
|
|
21301
22589
|
});
|
|
21302
22590
|
}
|
|
21303
22591
|
function withEntryLock(file, fn) {
|
|
21304
22592
|
const lock = `${file}.lock`;
|
|
21305
|
-
|
|
22593
|
+
fs29.mkdirSync(path34.dirname(file), { recursive: true });
|
|
21306
22594
|
const deadline = Date.now() + 5e3;
|
|
21307
22595
|
for (; ; ) {
|
|
21308
22596
|
try {
|
|
21309
|
-
const fd =
|
|
21310
|
-
|
|
22597
|
+
const fd = fs29.openSync(lock, "wx");
|
|
22598
|
+
fs29.closeSync(fd);
|
|
21311
22599
|
break;
|
|
21312
22600
|
} catch (error) {
|
|
21313
22601
|
if (error.code !== "EEXIST") throw error;
|
|
21314
22602
|
if (Date.now() > deadline) {
|
|
21315
22603
|
try {
|
|
21316
|
-
|
|
22604
|
+
fs29.unlinkSync(lock);
|
|
21317
22605
|
} catch {
|
|
21318
22606
|
}
|
|
21319
22607
|
}
|
|
@@ -21323,7 +22611,7 @@ function withEntryLock(file, fn) {
|
|
|
21323
22611
|
return fn();
|
|
21324
22612
|
} finally {
|
|
21325
22613
|
try {
|
|
21326
|
-
|
|
22614
|
+
fs29.unlinkSync(lock);
|
|
21327
22615
|
} catch {
|
|
21328
22616
|
}
|
|
21329
22617
|
}
|
|
@@ -21331,7 +22619,7 @@ function withEntryLock(file, fn) {
|
|
|
21331
22619
|
|
|
21332
22620
|
// src/internal/ai/keys.ts
|
|
21333
22621
|
var crypto9 = __toESM(require("crypto"));
|
|
21334
|
-
var
|
|
22622
|
+
var path35 = __toESM(require("path"));
|
|
21335
22623
|
var DEFAULT_AI_DIR = "__piwi__";
|
|
21336
22624
|
function normalizeTemplate(template) {
|
|
21337
22625
|
return template.trim().replace(/\s+/g, " ").toLowerCase();
|
|
@@ -21345,13 +22633,13 @@ function slug(text2, maxLength = 40) {
|
|
|
21345
22633
|
return (base || "x").slice(0, maxLength).replace(/-+$/g, "") || "x";
|
|
21346
22634
|
}
|
|
21347
22635
|
function entryDir(specFile, dir = DEFAULT_AI_DIR) {
|
|
21348
|
-
return
|
|
22636
|
+
return path35.join(path35.dirname(specFile), dir, path35.basename(specFile));
|
|
21349
22637
|
}
|
|
21350
22638
|
function entryPath(params) {
|
|
21351
22639
|
const basis = `${normalizeTemplate(params.testTitle)}::${normalizeTemplate(params.template)}`;
|
|
21352
22640
|
const hash = hashTemplate(basis, params.ordinal ?? 0);
|
|
21353
22641
|
const name = `${slug(params.testTitle)}.${slug(params.template)}.${hash}.json`;
|
|
21354
|
-
return
|
|
22642
|
+
return path35.join(entryDir(params.specFile, params.dir), name);
|
|
21355
22643
|
}
|
|
21356
22644
|
function parseLocation2(location) {
|
|
21357
22645
|
const match = /:(\d+):(\d+)$/.exec(location);
|
|
@@ -21668,9 +22956,8 @@ function compileFromCandidate(candidate) {
|
|
|
21668
22956
|
var GROUNDED_METHODS = new Set(LOCATOR_METHODS.filter((method) => method !== "locator"));
|
|
21669
22957
|
async function refLocator(page, ref) {
|
|
21670
22958
|
if (!/^[a-z0-9]+$/i.test(ref)) return null;
|
|
21671
|
-
const getByRef = page.getByRef;
|
|
21672
22959
|
try {
|
|
21673
|
-
const target =
|
|
22960
|
+
const target = locatorOfRef(page, ref);
|
|
21674
22961
|
return await target.count() === 1 ? target : null;
|
|
21675
22962
|
} catch {
|
|
21676
22963
|
return null;
|
|
@@ -21954,7 +23241,7 @@ function testIdentity(testInfo) {
|
|
|
21954
23241
|
return titles.length > 0 ? titles.join(" \u203A ") : testInfo.title;
|
|
21955
23242
|
}
|
|
21956
23243
|
function relativeToCwd(file) {
|
|
21957
|
-
return
|
|
23244
|
+
return path36.relative(process.cwd(), file).split(path36.sep).join("/");
|
|
21958
23245
|
}
|
|
21959
23246
|
function recordIntents(intents, entry) {
|
|
21960
23247
|
const add2 = (locator, kind) => {
|
|
@@ -21981,7 +23268,7 @@ function createAiApi(page, testInfo, config, used, intents) {
|
|
|
21981
23268
|
const readSource = () => {
|
|
21982
23269
|
if (source === null) {
|
|
21983
23270
|
try {
|
|
21984
|
-
source =
|
|
23271
|
+
source = fs30.readFileSync(testInfo.file, "utf8");
|
|
21985
23272
|
} catch {
|
|
21986
23273
|
source = "";
|
|
21987
23274
|
}
|