@piwitests/reporter 0.53.0 → 0.55.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.
@@ -2037,6 +2037,8 @@ var ATTACHMENT_NAMES = {
2037
2037
  pageInventory: "piwi-page-inventory",
2038
2038
  locatorPages: "piwi-locator-pages",
2039
2039
  codeReach: "piwi-code-reach",
2040
+ browserCoverage: "piwi-browser-coverage",
2041
+ browserFunctions: "piwi-browser-functions",
2040
2042
  serverCoverageRequests: "piwi-server-coverage-requests",
2041
2043
  webVitals: "piwi-web-vitals",
2042
2044
  locatorSuggestion: "piwi-locator-suggestion",
@@ -3501,6 +3503,438 @@ function joinErrorMessages(errors) {
3501
3503
  return parts.join("\n---\n") || null;
3502
3504
  }
3503
3505
 
3506
+ // ../core/src/coverage.ts
3507
+ var MAX_COVERAGE_FILES = 2e3;
3508
+ var MAX_COVERAGE_RANGES = 1e4;
3509
+ var MAX_LINE = 1e6;
3510
+ var MAX_PATH_LENGTH = 500;
3511
+ var BLOB_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
3512
+ function isRecord(value) {
3513
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3514
+ }
3515
+ function isRuntime(value) {
3516
+ return value === "browser" || value === "server";
3517
+ }
3518
+ function isLine(value) {
3519
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_LINE;
3520
+ }
3521
+ function validRanges(raw) {
3522
+ if (!Array.isArray(raw)) return [];
3523
+ const out = [];
3524
+ for (const item of raw) {
3525
+ if (!Array.isArray(item) || item.length !== 2) continue;
3526
+ const [start, end] = item;
3527
+ if (isLine(start) && isLine(end) && start <= end) out.push([start, end]);
3528
+ }
3529
+ return out.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
3530
+ }
3531
+ function normalizeLineRanges(ranges) {
3532
+ const out = [];
3533
+ for (const range of validRanges(ranges)) {
3534
+ const last = out[out.length - 1];
3535
+ if (last && range[0] <= last[1] + 1) last[1] = Math.max(last[1], range[1]);
3536
+ else out.push(range);
3537
+ }
3538
+ return out;
3539
+ }
3540
+ function normalizeFunctionRanges(ranges) {
3541
+ const out = [];
3542
+ for (const range of validRanges(ranges)) {
3543
+ const last = out[out.length - 1];
3544
+ if (!last || last[0] !== range[0] || last[1] !== range[1]) out.push(range);
3545
+ }
3546
+ return out;
3547
+ }
3548
+ function normalizeCoveragePath(raw) {
3549
+ if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
3550
+ const path14 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
3551
+ if (!path14 || path14.startsWith("../") || path14.startsWith("/") || /^[A-Za-z]:\//.test(path14)) return null;
3552
+ if (/(^|\/)node_modules\//.test(path14)) return null;
3553
+ return path14;
3554
+ }
3555
+ function fileEntry(path14, runtime, blob, functions = [], lines = []) {
3556
+ const file = { path: path14, runtime };
3557
+ if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
3558
+ if (functions.length > 0) file.functions = functions;
3559
+ if (lines.length > 0) file.lines = lines;
3560
+ return file;
3561
+ }
3562
+ function captureOf(files, incomplete) {
3563
+ return incomplete ? { v: 1, files, incomplete: true } : { v: 1, files };
3564
+ }
3565
+ function compareFiles(a, b) {
3566
+ if (a.path !== b.path) return a.path < b.path ? -1 : 1;
3567
+ if (a.runtime !== b.runtime) return a.runtime < b.runtime ? -1 : 1;
3568
+ return 0;
3569
+ }
3570
+ function sanitizeCoverage(raw) {
3571
+ if (!isRecord(raw) || raw.v !== 1 || !Array.isArray(raw.files)) return null;
3572
+ const files = [];
3573
+ for (const item of raw.files) {
3574
+ if (!isRecord(item) || !isRuntime(item.runtime)) continue;
3575
+ const path14 = normalizeCoveragePath(item.path);
3576
+ if (path14 === null) continue;
3577
+ const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
3578
+ files.push(
3579
+ fileEntry(path14, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
3580
+ );
3581
+ }
3582
+ const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
3583
+ if (!merged) return null;
3584
+ let incomplete = merged.incomplete === true || merged.files.length > MAX_COVERAGE_FILES;
3585
+ let budget = MAX_COVERAGE_RANGES;
3586
+ const capped = merged.files.slice(0, MAX_COVERAGE_FILES).map((file) => {
3587
+ const count = (file.functions?.length ?? 0) + (file.lines?.length ?? 0);
3588
+ if (count <= budget) {
3589
+ budget -= count;
3590
+ return file;
3591
+ }
3592
+ incomplete = true;
3593
+ return fileEntry(file.path, file.runtime);
3594
+ });
3595
+ return captureOf(capped, incomplete);
3596
+ }
3597
+ function mergeCoverage(...captures2) {
3598
+ const entries = /* @__PURE__ */ new Map();
3599
+ let incomplete = false;
3600
+ for (const capture of captures2) {
3601
+ if (!capture) continue;
3602
+ if (capture.incomplete) incomplete = true;
3603
+ for (const file of capture.files) {
3604
+ const key = `${file.runtime}:${file.path}`;
3605
+ let entry = entries.get(key);
3606
+ if (!entry) {
3607
+ entry = { path: file.path, runtime: file.runtime, blobs: /* @__PURE__ */ new Set(), functions: [], lines: [] };
3608
+ entries.set(key, entry);
3609
+ }
3610
+ if (file.blob) entry.blobs.add(file.blob);
3611
+ for (const range of file.functions ?? []) entry.functions.push(range);
3612
+ for (const range of file.lines ?? []) entry.lines.push(range);
3613
+ }
3614
+ }
3615
+ if (entries.size === 0) return null;
3616
+ const files = [...entries.values()].map((entry) => {
3617
+ if (entry.blobs.size > 1) incomplete = true;
3618
+ const blob = entry.blobs.size === 1 ? [...entry.blobs][0] : void 0;
3619
+ return fileEntry(
3620
+ entry.path,
3621
+ entry.runtime,
3622
+ blob,
3623
+ normalizeFunctionRanges(entry.functions),
3624
+ normalizeLineRanges(entry.lines)
3625
+ );
3626
+ });
3627
+ return captureOf(files.sort(compareFiles), incomplete);
3628
+ }
3629
+
3630
+ // ../core/src/source-map.ts
3631
+ var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3632
+ var BASE64_VALUE = new Int8Array(128).fill(-1);
3633
+ for (let i = 0; i < BASE64.length; i++) BASE64_VALUE[BASE64.charCodeAt(i)] = i;
3634
+ function decodeMappings(mappings, sourceBase) {
3635
+ const lines = [];
3636
+ let current = { columns: [], sources: [], originalLines: [] };
3637
+ let column = 0;
3638
+ let source = 0;
3639
+ let originalLine = 0;
3640
+ const fields = [];
3641
+ let value = 0;
3642
+ let shift = 0;
3643
+ const flush = () => {
3644
+ if (fields.length === 0) return;
3645
+ column += fields[0];
3646
+ if (fields.length >= 4) {
3647
+ source += fields[1];
3648
+ originalLine += fields[2];
3649
+ current.columns.push(column);
3650
+ current.sources.push(sourceBase + source);
3651
+ current.originalLines.push(originalLine);
3652
+ } else {
3653
+ current.columns.push(column);
3654
+ current.sources.push(-1);
3655
+ current.originalLines.push(-1);
3656
+ }
3657
+ fields.length = 0;
3658
+ };
3659
+ for (let i = 0; i <= mappings.length; i++) {
3660
+ const ch = i < mappings.length ? mappings.charCodeAt(i) : 59;
3661
+ if (ch === 59 || ch === 44) {
3662
+ flush();
3663
+ if (ch === 59) {
3664
+ lines.push(current);
3665
+ current = { columns: [], sources: [], originalLines: [] };
3666
+ column = 0;
3667
+ }
3668
+ continue;
3669
+ }
3670
+ const digit = ch < 128 ? BASE64_VALUE[ch] : -1;
3671
+ if (digit < 0) continue;
3672
+ value += (digit & 31) << shift;
3673
+ if (digit & 32) {
3674
+ shift += 5;
3675
+ continue;
3676
+ }
3677
+ fields.push(value & 1 ? -(value >>> 1) : value >>> 1);
3678
+ value = 0;
3679
+ shift = 0;
3680
+ }
3681
+ for (const line of lines) {
3682
+ if (line.columns.every((c, i) => i === 0 || c >= line.columns[i - 1])) continue;
3683
+ const order = line.columns.map((_, i) => i).sort((a, b) => line.columns[a] - line.columns[b]);
3684
+ line.columns = order.map((i) => line.columns[i]);
3685
+ line.sources = order.map((i) => line.sources[i]);
3686
+ line.originalLines = order.map((i) => line.originalLines[i]);
3687
+ }
3688
+ return lines;
3689
+ }
3690
+ function joinRoot(root, source) {
3691
+ if (!root || /^[a-z][\w+.-]*:/i.test(source) || source.startsWith("/")) return source;
3692
+ return root.endsWith("/") ? root + source : `${root}/${source}`;
3693
+ }
3694
+ function decodeLines(raw) {
3695
+ const sources = [];
3696
+ const decoded = [];
3697
+ const add = (map, lineOffset, columnOffset) => {
3698
+ if (map.sections) {
3699
+ for (const section of map.sections) {
3700
+ if (!add(section.map, section.offset.line, section.offset.column)) return false;
3701
+ }
3702
+ return true;
3703
+ }
3704
+ if (typeof map.mappings !== "string" || !Array.isArray(map.sources)) return false;
3705
+ const base = sources.length;
3706
+ for (const s of map.sources) sources.push(joinRoot(map.sourceRoot, s ?? ""));
3707
+ decodeMappings(map.mappings, base).forEach((segments, i) => {
3708
+ const at = lineOffset + i;
3709
+ const shifted = i === 0 && columnOffset ? segments.columns.map((c) => c + columnOffset) : segments.columns;
3710
+ const existing = decoded[at];
3711
+ if (!existing)
3712
+ decoded[at] = { columns: shifted, sources: segments.sources, originalLines: segments.originalLines };
3713
+ else {
3714
+ existing.columns.push(...shifted);
3715
+ existing.sources.push(...segments.sources);
3716
+ existing.originalLines.push(...segments.originalLines);
3717
+ }
3718
+ });
3719
+ return true;
3720
+ };
3721
+ return add(raw, 0, 0) ? { sources, lines: decoded } : null;
3722
+ }
3723
+ function compactSegments(lines) {
3724
+ let total = 0;
3725
+ for (const line of lines) total += line?.columns.length ?? 0;
3726
+ const segments = {
3727
+ lineStarts: new Int32Array(lines.length + 1),
3728
+ columns: new Int32Array(total),
3729
+ sources: new Int32Array(total),
3730
+ originalLines: new Int32Array(total)
3731
+ };
3732
+ let at = 0;
3733
+ for (let n = 0; n < lines.length; n++) {
3734
+ segments.lineStarts[n] = at;
3735
+ const line = lines[n];
3736
+ if (!line) continue;
3737
+ segments.columns.set(line.columns, at);
3738
+ segments.sources.set(line.sources, at);
3739
+ segments.originalLines.set(line.originalLines, at);
3740
+ at += line.columns.length;
3741
+ }
3742
+ segments.lineStarts[lines.length] = at;
3743
+ return segments;
3744
+ }
3745
+ function decodeSourceMap(raw) {
3746
+ if (raw.version !== void 0 && raw.version !== 3) return null;
3747
+ const decoded = decodeLines(raw);
3748
+ if (!decoded) return null;
3749
+ const { sources } = decoded;
3750
+ const segments = compactSegments(decoded.lines);
3751
+ const lineCount = segments.lineStarts.length - 1;
3752
+ const originalAt = (line, column) => {
3753
+ if (line < 0 || line >= lineCount) return null;
3754
+ let lo = segments.lineStarts[line];
3755
+ let hi = segments.lineStarts[line + 1] - 1;
3756
+ if (lo > hi || segments.columns[lo] > column) return null;
3757
+ while (lo < hi) {
3758
+ const mid = lo + hi + 1 >> 1;
3759
+ if (segments.columns[mid] <= column) lo = mid;
3760
+ else hi = mid - 1;
3761
+ }
3762
+ const source = segments.sources[lo];
3763
+ return source < 0 ? null : { source, line: segments.originalLines[lo] };
3764
+ };
3765
+ return {
3766
+ sources,
3767
+ sourceAt(line, column) {
3768
+ return originalAt(line, column)?.source ?? null;
3769
+ },
3770
+ originalAt,
3771
+ mapsBetween(source, start, end) {
3772
+ let found = false;
3773
+ eachSegmentBetween(segments, start, end, (i) => {
3774
+ found = segments.sources[i] === source;
3775
+ return found;
3776
+ });
3777
+ return found;
3778
+ },
3779
+ firstOriginalLineBetween(source, start, end) {
3780
+ let first = null;
3781
+ eachSegmentBetween(segments, start, end, (i) => {
3782
+ if (segments.sources[i] !== source || segments.originalLines[i] < 0) return false;
3783
+ first = segments.originalLines[i];
3784
+ return true;
3785
+ });
3786
+ return first;
3787
+ },
3788
+ originalLinesBetween(source, start, end, fromLine = 0) {
3789
+ let first = Number.POSITIVE_INFINITY;
3790
+ let last = -1;
3791
+ eachSegmentBetween(segments, start, end, (i) => {
3792
+ const line = segments.originalLines[i];
3793
+ if (segments.sources[i] === source && line >= fromLine) {
3794
+ if (line < first) first = line;
3795
+ if (line > last) last = line;
3796
+ }
3797
+ return false;
3798
+ });
3799
+ return last < 0 ? null : [first, last];
3800
+ }
3801
+ };
3802
+ }
3803
+ function eachSegmentBetween(segments, start, end, visit) {
3804
+ const lineCount = segments.lineStarts.length - 1;
3805
+ for (let line = Math.max(start.line, 0); line <= end.line && line < lineCount; line++) {
3806
+ let i = segments.lineStarts[line];
3807
+ const stop = segments.lineStarts[line + 1];
3808
+ if (line === start.line) {
3809
+ let hi = stop;
3810
+ while (i < hi) {
3811
+ const mid = i + hi >> 1;
3812
+ if (segments.columns[mid] < start.column) i = mid + 1;
3813
+ else hi = mid;
3814
+ }
3815
+ }
3816
+ for (; i < stop; i++) {
3817
+ if (line === end.line && segments.columns[i] >= end.column) return;
3818
+ if (visit(i)) return;
3819
+ }
3820
+ }
3821
+ }
3822
+ function lineStarts(text) {
3823
+ const starts = [0];
3824
+ for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) starts.push(i + 1);
3825
+ return starts;
3826
+ }
3827
+ function offsetToPosition(starts, offset) {
3828
+ let lo = 0;
3829
+ let hi = starts.length - 1;
3830
+ while (lo < hi) {
3831
+ const mid = lo + hi + 1 >> 1;
3832
+ if (starts[mid] <= offset) lo = mid;
3833
+ else hi = mid - 1;
3834
+ }
3835
+ return { line: lo, column: offset - starts[lo] };
3836
+ }
3837
+ function sourceMappingUrl(script) {
3838
+ const tail = script.slice(-4096);
3839
+ const matches = [...tail.matchAll(/(?:\/\/|\/\*)[#@]\s*sourceMappingURL=([^\s*'"]+)/g)];
3840
+ return matches.length ? matches[matches.length - 1][1] : null;
3841
+ }
3842
+ function decodeDataUrl(url) {
3843
+ const m = /^data:([^,]*?)(;base64)?,(.*)$/s.exec(url);
3844
+ if (!m) return null;
3845
+ try {
3846
+ if (!m[2]) return decodeURIComponent(m[3]);
3847
+ const binary = globalThis.atob(m[3]);
3848
+ const bytes = new Uint8Array(binary.length);
3849
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
3850
+ return new TextDecoder().decode(bytes);
3851
+ } catch {
3852
+ return null;
3853
+ }
3854
+ }
3855
+ function normalizeSourcePath(source) {
3856
+ let s = source.replace(/[?#].*$/, "");
3857
+ s = s.replace(/^webpack:\/\/[^/]*\//, "").replace(/^webpack:\/\//, "");
3858
+ s = s.replace(/^(?:vite|rollup|turbopack|ng):\/*/, "");
3859
+ s = s.replace(/^\[project\]\//, "");
3860
+ s = s.replace(/^file:\/\//, "");
3861
+ s = s.replace(/^\/@fs\//, "/");
3862
+ s = s.replace(/^\/@id\//, "");
3863
+ try {
3864
+ s = decodeURIComponent(s);
3865
+ } catch {
3866
+ }
3867
+ s = s.replace(/\\/g, "/");
3868
+ if (/^\/[A-Za-z]:\//.test(s)) s = s.slice(1);
3869
+ s = s.replace(/(^|\/)\.\//g, "$1");
3870
+ return s;
3871
+ }
3872
+
3873
+ // ../core/src/code-reach.ts
3874
+ var MAX_CODE_REACH_FILES = 2e3;
3875
+ function scriptText(source) {
3876
+ return { length: source.length, starts: Uint32Array.from(lineStarts(source)) };
3877
+ }
3878
+ var textOf = (entry, text) => text ?? (entry.source === void 0 ? void 0 : scriptText(entry.source));
3879
+ function executedFunctionRanges(entry, length, all = false) {
3880
+ length ??= entry.source?.length ?? Number.POSITIVE_INFINITY;
3881
+ const out = [];
3882
+ entry.functions.forEach((fn, i) => {
3883
+ const whole = fn.ranges[0];
3884
+ if (!whole || !all && whole.count <= 0) return;
3885
+ const topLevel = whole.startOffset === 0 && (whole.endOffset >= length || i === 0 && fn.functionName === "");
3886
+ if (topLevel) return;
3887
+ out.push({ start: whole.startOffset, end: whole.endOffset });
3888
+ });
3889
+ return out;
3890
+ }
3891
+ function scriptRan(entry, text) {
3892
+ return executedFunctionRanges(entry, text?.length).length > 0;
3893
+ }
3894
+ function reachedFunctionLines(entry, map, text) {
3895
+ return functionLines(entry, map, text, false);
3896
+ }
3897
+ function knownFunctionLines(entry, map, text) {
3898
+ return functionLines(entry, map, text, true);
3899
+ }
3900
+ function functionLines(entry, map, text, all) {
3901
+ const out = /* @__PURE__ */ new Map();
3902
+ const read = textOf(entry, text);
3903
+ const ranges = executedFunctionRanges(entry, read?.length, all);
3904
+ if (!ranges.length || !read) return out;
3905
+ const add = (source, range) => {
3906
+ const list = out.get(source);
3907
+ if (list) list.push(range);
3908
+ else out.set(source, [range]);
3909
+ };
3910
+ const starts = read.starts;
3911
+ for (const range of ranges) {
3912
+ const start = offsetToPosition(starts, range.start);
3913
+ if (!map) {
3914
+ const last = offsetToPosition(starts, Math.max(range.start, range.end - 1));
3915
+ add(-1, [start.line + 1, last.line + 1]);
3916
+ continue;
3917
+ }
3918
+ const at = map.originalAt(start.line, start.column);
3919
+ if (at === null) continue;
3920
+ const end = offsetToPosition(starts, range.end);
3921
+ const first = map.firstOriginalLineBetween(at.source, start, end);
3922
+ if (first === null) continue;
3923
+ const header = first === at.line || first === at.line + 1 ? at.line : first;
3924
+ const lines = map.originalLinesBetween(at.source, start, end, header);
3925
+ if (lines) add(at.source, [header + 1, lines[1] + 1]);
3926
+ }
3927
+ return out;
3928
+ }
3929
+ function finalizeCodeReach(files) {
3930
+ const out = /* @__PURE__ */ new Set();
3931
+ for (const raw of files) {
3932
+ const file = normalizeCoveragePath(raw);
3933
+ if (file !== null) out.add(file);
3934
+ }
3935
+ return [...out].sort().slice(0, MAX_CODE_REACH_FILES);
3936
+ }
3937
+
3504
3938
  // src/internal/capture/inspect-on-failure.ts
3505
3939
  function isCi(ci) {
3506
3940
  return ci !== void 0 && ci !== "" && ci !== "false";
@@ -3909,7 +4343,45 @@ function isServerGranularity(value) {
3909
4343
  return typeof value === "string" && SERVER_VALUES.has(value);
3910
4344
  }
3911
4345
  function browserCoverageInWorker() {
3912
- return process.env[PIWI_ENV_KEYS.captureCodeReach] === "true" || process.env[PIWI_ENV_KEYS.coverageBrowser] === "files";
4346
+ const browser = process.env[PIWI_ENV_KEYS.coverageBrowser];
4347
+ return process.env[PIWI_ENV_KEYS.captureCodeReach] === "true" || browser === "files" || browser === "functions";
4348
+ }
4349
+ function browserFunctionsInWorker() {
4350
+ return process.env[PIWI_ENV_KEYS.coverageBrowser] === "functions";
4351
+ }
4352
+ function browserFunctionCoverage(files) {
4353
+ return sanitizeCoverage({
4354
+ v: 1,
4355
+ files: [...files].map(([path14, functions]) => ({ path: path14, runtime: "browser", functions }))
4356
+ });
4357
+ }
4358
+ function newKnownFunctions(known, sent) {
4359
+ const parts = [];
4360
+ let files = [];
4361
+ let budget = MAX_COVERAGE_RANGES;
4362
+ const close = () => {
4363
+ if (files.length > 0) parts.push({ v: 1, files: files.sort((a, b) => a.path < b.path ? -1 : 1) });
4364
+ files = [];
4365
+ budget = MAX_COVERAGE_RANGES;
4366
+ };
4367
+ for (const [raw, ranges] of known) {
4368
+ const path14 = normalizeCoveragePath(raw);
4369
+ if (path14 === null) continue;
4370
+ let named = sent.get(path14);
4371
+ let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
4372
+ if (fresh.length === 0) continue;
4373
+ if (!named) sent.set(path14, named = /* @__PURE__ */ new Set());
4374
+ for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
4375
+ while (fresh.length > 0) {
4376
+ if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
4377
+ const part = fresh.slice(0, budget);
4378
+ fresh = fresh.slice(part.length);
4379
+ budget -= part.length;
4380
+ files.push({ path: path14, allFunctions: part });
4381
+ }
4382
+ }
4383
+ close();
4384
+ return parts;
3913
4385
  }
3914
4386
  function serverCoverageInWorker() {
3915
4387
  return isServerGranularity(process.env[PIWI_ENV_KEYS.coverageServer]);
@@ -3971,320 +4443,95 @@ var path8 = __toESM(require("path"));
3971
4443
  var CANDIDATE_OFFSETS = ["..", "../../.."];
3972
4444
  function findOwnPackageJson(fromDir) {
3973
4445
  for (const offset of CANDIDATE_OFFSETS) {
3974
- try {
3975
- const pkgPath = path8.resolve(fromDir, offset, "package.json");
3976
- const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
3977
- const { name, version } = pkg;
3978
- if (name === "@piwitests/reporter" && typeof version === "string")
3979
- return { version, root: path8.dirname(pkgPath) };
3980
- } catch {
3981
- }
3982
- }
3983
- return void 0;
3984
- }
3985
-
3986
- // src/internal/capture/quiet-capture.ts
3987
- var BOXED_PREFIX_EXPORTS = [
3988
- [
3989
- "playwright-core/lib/coreBundle",
3990
- (mod) => mod?.utils?.setBoxedStackPrefixes
3991
- ],
3992
- ["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
3993
- ];
3994
- var boxing;
3995
- var resolvedPlaywrightPackageJson;
3996
- function resolvePlaywrightPackageJson() {
3997
- if (resolvedPlaywrightPackageJson !== void 0) return resolvedPlaywrightPackageJson;
3998
- try {
3999
- const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
4000
- resolvedPlaywrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
4001
- } catch {
4002
- resolvedPlaywrightPackageJson = null;
4003
- }
4004
- return resolvedPlaywrightPackageJson;
4005
- }
4006
- function resolveBoxing() {
4007
- try {
4008
- const root = findOwnPackageJson(__dirname)?.root;
4009
- if (!root) return null;
4010
- const playwrightPackageJson = resolvePlaywrightPackageJson();
4011
- if (!playwrightPackageJson) return null;
4012
- const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
4013
- for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
4014
- let set;
4015
- try {
4016
- set = pick(requireFromPlaywright(moduleId));
4017
- } catch {
4018
- continue;
4019
- }
4020
- if (typeof set !== "function") continue;
4021
- return {
4022
- set,
4023
- prefixes: [path9.dirname(playwrightPackageJson), path9.join(root, "dist") + path9.sep]
4024
- };
4025
- }
4026
- } catch {
4027
- }
4028
- return null;
4029
- }
4030
- function boxCaptureFrames(extraPrefixes = []) {
4031
- const first = boxing === void 0;
4032
- if (first) boxing = resolveBoxing();
4033
- if (!boxing) return;
4034
- const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
4035
- if (!first && added.length === 0) return;
4036
- boxing.prefixes.push(...added);
4037
- try {
4038
- boxing.set([...boxing.prefixes]);
4039
- } catch {
4040
- }
4041
- }
4042
- function internalCall(owner, run) {
4043
- const wrap = owner?._wrapApiCall;
4044
- if (typeof wrap !== "function") return run();
4045
- return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
4046
- }
4047
-
4048
- // src/internal/capture/code-reach.ts
4049
- var crypto2 = __toESM(require("crypto"));
4050
- var fs6 = __toESM(require("fs"));
4051
- var path10 = __toESM(require("path"));
4052
- var import_node_child_process = require("child_process");
4053
-
4054
- // ../core/src/coverage.ts
4055
- var MAX_PATH_LENGTH = 500;
4056
- function normalizeCoveragePath(raw) {
4057
- if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
4058
- const path14 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
4059
- if (!path14 || path14.startsWith("../") || path14.startsWith("/") || /^[A-Za-z]:\//.test(path14)) return null;
4060
- if (/(^|\/)node_modules\//.test(path14)) return null;
4061
- return path14;
4062
- }
4063
-
4064
- // ../core/src/source-map.ts
4065
- var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4066
- var BASE64_VALUE = new Int8Array(128).fill(-1);
4067
- for (let i = 0; i < BASE64.length; i++) BASE64_VALUE[BASE64.charCodeAt(i)] = i;
4068
- function decodeMappings(mappings, sourceBase) {
4069
- const lines = [];
4070
- let current = { columns: [], sources: [] };
4071
- let column = 0;
4072
- let source = 0;
4073
- const fields = [];
4074
- let value = 0;
4075
- let shift = 0;
4076
- const flush = () => {
4077
- if (fields.length === 0) return;
4078
- column += fields[0];
4079
- if (fields.length >= 4) {
4080
- source += fields[1];
4081
- current.columns.push(column);
4082
- current.sources.push(sourceBase + source);
4083
- } else {
4084
- current.columns.push(column);
4085
- current.sources.push(-1);
4086
- }
4087
- fields.length = 0;
4088
- };
4089
- for (let i = 0; i <= mappings.length; i++) {
4090
- const ch = i < mappings.length ? mappings.charCodeAt(i) : 59;
4091
- if (ch === 59 || ch === 44) {
4092
- flush();
4093
- if (ch === 59) {
4094
- lines.push(current);
4095
- current = { columns: [], sources: [] };
4096
- column = 0;
4097
- }
4098
- continue;
4099
- }
4100
- const digit = ch < 128 ? BASE64_VALUE[ch] : -1;
4101
- if (digit < 0) continue;
4102
- value += (digit & 31) << shift;
4103
- if (digit & 32) {
4104
- shift += 5;
4105
- continue;
4106
- }
4107
- fields.push(value & 1 ? -(value >>> 1) : value >>> 1);
4108
- value = 0;
4109
- shift = 0;
4110
- }
4111
- for (const line of lines) {
4112
- if (line.columns.every((c, i) => i === 0 || c >= line.columns[i - 1])) continue;
4113
- const order = line.columns.map((_, i) => i).sort((a, b) => line.columns[a] - line.columns[b]);
4114
- line.columns = order.map((i) => line.columns[i]);
4115
- line.sources = order.map((i) => line.sources[i]);
4116
- }
4117
- return lines;
4118
- }
4119
- function joinRoot(root, source) {
4120
- if (!root || /^[a-z][\w+.-]*:/i.test(source) || source.startsWith("/")) return source;
4121
- return root.endsWith("/") ? root + source : `${root}/${source}`;
4122
- }
4123
- function decodeSourceMap(raw) {
4124
- const sources = [];
4125
- const lines = [];
4126
- const add = (map, lineOffset, columnOffset) => {
4127
- if (map.sections) {
4128
- for (const section of map.sections) {
4129
- if (!add(section.map, section.offset.line, section.offset.column)) return false;
4130
- }
4131
- return true;
4132
- }
4133
- if (typeof map.mappings !== "string" || !Array.isArray(map.sources)) return false;
4134
- const base = sources.length;
4135
- for (const s of map.sources) sources.push(joinRoot(map.sourceRoot, s ?? ""));
4136
- decodeMappings(map.mappings, base).forEach((segments, i) => {
4137
- const at = lineOffset + i;
4138
- const shifted = i === 0 && columnOffset ? segments.columns.map((c) => c + columnOffset) : segments.columns;
4139
- const existing = lines[at];
4140
- if (!existing) lines[at] = { columns: shifted, sources: segments.sources };
4141
- else {
4142
- existing.columns.push(...shifted);
4143
- existing.sources.push(...segments.sources);
4144
- }
4145
- });
4146
- return true;
4147
- };
4148
- if (raw.version !== void 0 && raw.version !== 3) return null;
4149
- if (!add(raw, 0, 0)) return null;
4150
- return {
4151
- sources,
4152
- sourceAt(line, column) {
4153
- const segments = lines[line];
4154
- if (!segments || segments.columns.length === 0) return null;
4155
- let lo = 0;
4156
- let hi = segments.columns.length - 1;
4157
- if (segments.columns[0] > column) return null;
4158
- while (lo < hi) {
4159
- const mid = lo + hi + 1 >> 1;
4160
- if (segments.columns[mid] <= column) lo = mid;
4161
- else hi = mid - 1;
4162
- }
4163
- const source = segments.sources[lo];
4164
- return source < 0 ? null : source;
4165
- },
4166
- mapsBetween(source, start, end) {
4167
- for (let line = start.line; line <= end.line && line < lines.length; line++) {
4168
- const segments = lines[line];
4169
- if (!segments) continue;
4170
- let i = 0;
4171
- if (line === start.line) {
4172
- let hi = segments.columns.length;
4173
- while (i < hi) {
4174
- const mid = i + hi >> 1;
4175
- if (segments.columns[mid] < start.column) i = mid + 1;
4176
- else hi = mid;
4177
- }
4178
- }
4179
- for (; i < segments.columns.length; i++) {
4180
- if (line === end.line && segments.columns[i] >= end.column) return false;
4181
- if (segments.sources[i] === source) return true;
4182
- }
4183
- }
4184
- return false;
4185
- }
4186
- };
4187
- }
4188
- function lineStarts(text) {
4189
- const starts = [0];
4190
- for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) starts.push(i + 1);
4191
- return starts;
4192
- }
4193
- function offsetToPosition(starts, offset) {
4194
- let lo = 0;
4195
- let hi = starts.length - 1;
4196
- while (lo < hi) {
4197
- const mid = lo + hi + 1 >> 1;
4198
- if (starts[mid] <= offset) lo = mid;
4199
- else hi = mid - 1;
4446
+ try {
4447
+ const pkgPath = path8.resolve(fromDir, offset, "package.json");
4448
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
4449
+ const { name, version } = pkg;
4450
+ if (name === "@piwitests/reporter" && typeof version === "string")
4451
+ return { version, root: path8.dirname(pkgPath) };
4452
+ } catch {
4453
+ }
4200
4454
  }
4201
- return { line: lo, column: offset - starts[lo] };
4202
- }
4203
- function sourceMappingUrl(script) {
4204
- const tail = script.slice(-4096);
4205
- const matches = [...tail.matchAll(/(?:\/\/|\/\*)[#@]\s*sourceMappingURL=([^\s*'"]+)/g)];
4206
- return matches.length ? matches[matches.length - 1][1] : null;
4455
+ return void 0;
4207
4456
  }
4208
- function decodeDataUrl(url) {
4209
- const m = /^data:([^,]*?)(;base64)?,(.*)$/s.exec(url);
4210
- if (!m) return null;
4457
+
4458
+ // src/internal/capture/quiet-capture.ts
4459
+ var BOXED_PREFIX_EXPORTS = [
4460
+ [
4461
+ "playwright-core/lib/coreBundle",
4462
+ (mod) => mod?.utils?.setBoxedStackPrefixes
4463
+ ],
4464
+ ["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
4465
+ ];
4466
+ var boxing;
4467
+ var resolvedPlaywrightPackageJson;
4468
+ function resolvePlaywrightPackageJson() {
4469
+ if (resolvedPlaywrightPackageJson !== void 0) return resolvedPlaywrightPackageJson;
4211
4470
  try {
4212
- if (!m[2]) return decodeURIComponent(m[3]);
4213
- const binary = globalThis.atob(m[3]);
4214
- const bytes = new Uint8Array(binary.length);
4215
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
4216
- return new TextDecoder().decode(bytes);
4471
+ const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
4472
+ resolvedPlaywrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
4217
4473
  } catch {
4218
- return null;
4474
+ resolvedPlaywrightPackageJson = null;
4219
4475
  }
4476
+ return resolvedPlaywrightPackageJson;
4220
4477
  }
4221
- function normalizeSourcePath(source) {
4222
- let s = source.replace(/[?#].*$/, "");
4223
- s = s.replace(/^webpack:\/\/[^/]*\//, "").replace(/^webpack:\/\//, "");
4224
- s = s.replace(/^(?:vite|rollup|turbopack|ng):\/*/, "");
4225
- s = s.replace(/^\[project\]\//, "");
4226
- s = s.replace(/^file:\/\//, "");
4227
- s = s.replace(/^\/@fs\//, "/");
4228
- s = s.replace(/^\/@id\//, "");
4478
+ function resolveBoxing() {
4229
4479
  try {
4230
- s = decodeURIComponent(s);
4480
+ const root = findOwnPackageJson(__dirname)?.root;
4481
+ if (!root) return null;
4482
+ const playwrightPackageJson = resolvePlaywrightPackageJson();
4483
+ if (!playwrightPackageJson) return null;
4484
+ const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
4485
+ for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
4486
+ let set;
4487
+ try {
4488
+ set = pick(requireFromPlaywright(moduleId));
4489
+ } catch {
4490
+ continue;
4491
+ }
4492
+ if (typeof set !== "function") continue;
4493
+ return {
4494
+ set,
4495
+ prefixes: [path9.dirname(playwrightPackageJson), path9.join(root, "dist") + path9.sep]
4496
+ };
4497
+ }
4231
4498
  } catch {
4232
4499
  }
4233
- s = s.replace(/\\/g, "/");
4234
- if (/^\/[A-Za-z]:\//.test(s)) s = s.slice(1);
4235
- s = s.replace(/(^|\/)\.\//g, "$1");
4236
- return s;
4237
- }
4238
-
4239
- // ../core/src/code-reach.ts
4240
- var MAX_CODE_REACH_FILES = 2e3;
4241
- function executedFunctionRanges(entry) {
4242
- const length = entry.source?.length ?? Number.POSITIVE_INFINITY;
4243
- const out = [];
4244
- entry.functions.forEach((fn, i) => {
4245
- const whole = fn.ranges[0];
4246
- if (!whole || whole.count <= 0) return;
4247
- const topLevel = whole.startOffset === 0 && (whole.endOffset >= length || i === 0 && fn.functionName === "");
4248
- if (topLevel) return;
4249
- out.push({ start: whole.startOffset, end: whole.endOffset });
4250
- });
4251
- return out;
4252
- }
4253
- function executedFunctionOffsets(entry) {
4254
- return executedFunctionRanges(entry).map((range) => range.start);
4255
- }
4256
- function scriptRan(entry) {
4257
- return executedFunctionOffsets(entry).length > 0;
4500
+ return null;
4258
4501
  }
4259
- function reachedSources(entry, map) {
4260
- const ranges = executedFunctionRanges(entry);
4261
- const reached = /* @__PURE__ */ new Set();
4262
- if (!ranges.length || entry.source === void 0) return reached;
4263
- const starts = lineStarts(entry.source);
4264
- for (const range of ranges) {
4265
- const start = offsetToPosition(starts, range.start);
4266
- const source = map.sourceAt(start.line, start.column);
4267
- if (source === null || reached.has(source)) continue;
4268
- if (map.mapsBetween(source, start, offsetToPosition(starts, range.end))) reached.add(source);
4502
+ function boxCaptureFrames(extraPrefixes = []) {
4503
+ const first = boxing === void 0;
4504
+ if (first) boxing = resolveBoxing();
4505
+ if (!boxing) return;
4506
+ const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
4507
+ if (!first && added.length === 0) return;
4508
+ boxing.prefixes.push(...added);
4509
+ try {
4510
+ boxing.set([...boxing.prefixes]);
4511
+ } catch {
4269
4512
  }
4270
- return reached;
4271
4513
  }
4272
- function finalizeCodeReach(files) {
4273
- const out = /* @__PURE__ */ new Set();
4274
- for (const raw of files) {
4275
- const file = normalizeCoveragePath(raw);
4276
- if (file !== null) out.add(file);
4277
- }
4278
- return [...out].sort().slice(0, MAX_CODE_REACH_FILES);
4514
+ function callMethod(target, prop, args) {
4515
+ return target[prop](...args);
4516
+ }
4517
+ function internalCall(owner, run) {
4518
+ const wrap = owner?._wrapApiCall;
4519
+ if (typeof wrap !== "function") return run();
4520
+ return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
4279
4521
  }
4280
4522
 
4281
4523
  // src/internal/capture/code-reach.ts
4524
+ var crypto2 = __toESM(require("crypto"));
4525
+ var fs6 = __toESM(require("fs"));
4526
+ var path10 = __toESM(require("path"));
4527
+ var import_node_child_process = require("child_process");
4282
4528
  var MAX_MAP_BYTES = 20 * 1024 * 1024;
4283
4529
  var MAP_TIMEOUT_MS = 5e3;
4284
4530
  var SOURCE_EXTENSIONS = /\.(?:[cm]?[jt]sx?|vue|svelte|astro|marko|html?)$/i;
4285
4531
  var REPLAYED_SCRIPT = /^(?:about|rsc):/i;
4532
+ var NEVER_REACHED = /\/node_modules\/|\/@vite\//;
4286
4533
  var BUILD_DIRS = /(?:^|\/)(?:dist|build|out|\.output|\.next|\.nuxt|\.svelte-kit|public|static|wwwroot)\//;
4287
- var MAX_CACHED_SCRIPTS = 500;
4534
+ var MAX_CACHED_SCRIPTS = 4e3;
4288
4535
  var LatestByUrl = class {
4289
4536
  constructor() {
4290
4537
  this.entries = /* @__PURE__ */ new Map();
@@ -4324,8 +4571,32 @@ function repositoryRoot(from) {
4324
4571
  }
4325
4572
  function codeReachRoots(configDir, configured) {
4326
4573
  const repoRoot = repositoryRoot(configDir);
4327
- const roots = configured?.length ? configured.map((r) => path10.resolve(configDir, r)) : [configDir];
4328
- return { roots: [.../* @__PURE__ */ new Set([...roots, repoRoot])], repoRoot };
4574
+ const resolve3 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path10.resolve(configDir, r));
4575
+ const global = Array.isArray(configured) ? resolve3(configured) : resolve3(configured?.["*"]);
4576
+ const byOrigin = /* @__PURE__ */ new Map();
4577
+ if (configured && !Array.isArray(configured)) {
4578
+ for (const [key, dirs] of Object.entries(configured)) {
4579
+ const origin = key === "*" ? null : originOf2(key);
4580
+ const resolved = resolve3(dirs);
4581
+ if (origin && resolved.length) byOrigin.set(origin, resolved);
4582
+ }
4583
+ }
4584
+ const roots = global.length ? global : [configDir];
4585
+ return { roots: [.../* @__PURE__ */ new Set([...roots, repoRoot])], byOrigin, repoRoot };
4586
+ }
4587
+ function originOf2(url) {
4588
+ if (!url) return null;
4589
+ try {
4590
+ const origin = new URL(url).origin;
4591
+ return origin === "null" ? null : origin;
4592
+ } catch {
4593
+ return null;
4594
+ }
4595
+ }
4596
+ function scriptRoots(roots, scriptUrl, pageUrl) {
4597
+ if (!roots.byOrigin?.size) return roots.roots;
4598
+ const own = roots.byOrigin.get(originOf2(scriptUrl) ?? "") ?? roots.byOrigin.get(originOf2(pageUrl) ?? "");
4599
+ return own ? [.../* @__PURE__ */ new Set([...own, ...roots.roots])] : roots.roots;
4329
4600
  }
4330
4601
  function isFile(file) {
4331
4602
  try {
@@ -4409,50 +4680,96 @@ async function resolveScript(entry, roots, bases, fetchMap) {
4409
4680
  const version = scriptVersion(entry);
4410
4681
  const cached = scriptCache.get(entry.url, version);
4411
4682
  if (cached) return cached;
4412
- const file = fileForUrl(entry.url, roots.roots, bases);
4683
+ const file = fileForUrl(entry.url, roots, bases);
4413
4684
  const lookup = await loadMap(entry, fetchMap);
4414
4685
  let resolution;
4415
- if (file) resolution = { kind: "file", file, lookup, source: entry.source };
4416
- else resolution = lookup ? { kind: "map", lookup, files: [], source: entry.source } : { kind: "none" };
4417
- if (entry.source !== void 0) scriptCache.set(entry.url, version, resolution);
4686
+ const text = entry.source === void 0 ? void 0 : scriptText(entry.source);
4687
+ if (file) resolution = { kind: "file", file, lookup, text };
4688
+ else resolution = lookup ? { kind: "map", lookup, files: [], text } : { kind: "none" };
4689
+ if (text) scriptCache.set(entry.url, version, resolution);
4418
4690
  return resolution;
4419
4691
  }
4420
- function withSource(entry, resolution) {
4421
- if (entry.source !== void 0 || resolution.source === void 0) return entry;
4422
- return { ...entry, source: resolution.source };
4692
+ function textOf2(entry, resolution) {
4693
+ return resolution.text ?? (entry.source === void 0 ? void 0 : scriptText(entry.source));
4694
+ }
4695
+ async function resolveCodeReach(entries, roots, fetchMap, pageUrl) {
4696
+ return finalizeCodeReach((await resolveReach(entries, roots, fetchMap, pageUrl)).keys());
4423
4697
  }
4424
- async function resolveCodeReach(entries, roots, fetchMap) {
4425
- const files = /* @__PURE__ */ new Set();
4698
+ async function resolveCodeReachFunctions(entries, roots, fetchMap, pageUrl, options = {}) {
4699
+ return resolveFunctions(entries, roots, fetchMap, pageUrl, options.known === true);
4700
+ }
4701
+ async function resolveReach(entries, roots, fetchMap, pageUrl) {
4702
+ return (await resolveFunctions(entries, roots, fetchMap, pageUrl, false)).reached;
4703
+ }
4704
+ var knownCache = new LatestByUrl();
4705
+ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
4706
+ const files = /* @__PURE__ */ new Map();
4707
+ const known = /* @__PURE__ */ new Map();
4708
+ const addTo = (into, file, functions) => {
4709
+ const path14 = relativeToRepo(file, roots.repoRoot);
4710
+ const listed = into.get(path14);
4711
+ if (listed) listed.push(...functions);
4712
+ else into.set(path14, [...functions]);
4713
+ };
4714
+ const add = (file, functions) => addTo(files, file, functions);
4426
4715
  const bases = viteBases(entries);
4427
4716
  const scripts = entries.filter((entry) => entry.url && entry.functions?.length && !REPLAYED_SCRIPT.test(entry.url));
4428
- const known = scripts.map((entry) => scriptCache.get(entry.url, scriptVersion(entry)));
4717
+ const cached = scripts.map((entry) => scriptCache.get(entry.url, scriptVersion(entry)));
4429
4718
  for (const [i, covered] of scripts.entries()) {
4430
- const resolution = known[i] ?? await resolveScript(covered, roots, bases, fetchMap);
4719
+ const dirs = scriptRoots(roots, covered.url, covered.pageUrl ?? pageUrl);
4720
+ const resolution = cached[i] ?? await resolveScript(covered, dirs, bases, fetchMap);
4721
+ const mapFile = (resolved, index) => {
4722
+ if (resolved.files[index] === void 0) {
4723
+ const source = resolved.lookup.sources[index] ?? "";
4724
+ let absolute = resolveSourcePath(source, dirs);
4725
+ if (!absolute && !/^[a-z][\w+.-]*:/i.test(source)) {
4726
+ try {
4727
+ absolute = fileForUrl(new URL(source, covered.url).href, dirs, bases);
4728
+ } catch {
4729
+ absolute = null;
4730
+ }
4731
+ }
4732
+ resolved.files[index] = absolute;
4733
+ }
4734
+ return resolved.files[index] ?? null;
4735
+ };
4431
4736
  if (resolution.kind === "file") {
4432
- const entry = withSource(covered, resolution);
4433
- const ran = resolution.lookup ? reachedSources(entry, resolution.lookup).size > 0 : scriptRan(entry);
4434
- if (resolution.file && ran) files.add(relativeToRepo(resolution.file, roots.repoRoot));
4737
+ if (!resolution.file) continue;
4738
+ const text = textOf2(covered, resolution);
4739
+ if (resolution.lookup) {
4740
+ const reached = reachedFunctionLines(covered, resolution.lookup, text);
4741
+ if (reached.size > 0) add(resolution.file, [...reached.values()].flat());
4742
+ } else if (scriptRan(covered, text)) {
4743
+ add(resolution.file, reachedFunctionLines(covered, null, text).get(-1) ?? []);
4744
+ }
4435
4745
  } else if (resolution.kind === "map") {
4436
- const entry = withSource(covered, resolution);
4437
- for (const index of reachedSources(entry, resolution.lookup)) {
4438
- if (resolution.files[index] === void 0) {
4439
- const source = resolution.lookup.sources[index] ?? "";
4440
- let absolute = resolveSourcePath(source, roots.roots);
4441
- if (!absolute && !/^[a-z][\w+.-]*:/i.test(source)) {
4442
- try {
4443
- absolute = fileForUrl(new URL(source, entry.url).href, roots.roots, bases);
4444
- } catch {
4445
- absolute = null;
4446
- }
4447
- }
4448
- resolution.files[index] = absolute;
4746
+ for (const [index, functions] of reachedFunctionLines(covered, resolution.lookup, textOf2(covered, resolution))) {
4747
+ const file = mapFile(resolution, index);
4748
+ if (file) add(file, functions);
4749
+ }
4750
+ }
4751
+ if (!withKnown || resolution.kind === "none") continue;
4752
+ const version = scriptVersion(covered);
4753
+ let listed = knownCache.get(covered.url, version);
4754
+ if (!listed || listed.count < covered.functions.length) {
4755
+ const byFile = /* @__PURE__ */ new Map();
4756
+ const text = textOf2(covered, resolution);
4757
+ if (resolution.kind === "file" && resolution.file) {
4758
+ const lines = knownFunctionLines(covered, resolution.lookup, text);
4759
+ const all = resolution.lookup ? [...lines.values()].flat() : lines.get(-1) ?? [];
4760
+ if (all.length > 0) byFile.set(resolution.file, all);
4761
+ } else if (resolution.kind === "map") {
4762
+ for (const [index, functions] of knownFunctionLines(covered, resolution.lookup, text)) {
4763
+ const file = mapFile(resolution, index);
4764
+ if (file) byFile.set(file, [...byFile.get(file) ?? [], ...functions]);
4449
4765
  }
4450
- const file = resolution.files[index];
4451
- if (file) files.add(relativeToRepo(file, roots.repoRoot));
4452
4766
  }
4767
+ listed = { count: covered.functions.length, files: byFile };
4768
+ knownCache.set(covered.url, version, listed);
4453
4769
  }
4770
+ for (const [file, functions] of listed.files) addTo(known, file, functions);
4454
4771
  }
4455
- return finalizeCodeReach(files);
4772
+ return { reached: files, known };
4456
4773
  }
4457
4774
  function pageMapFetcher(page) {
4458
4775
  return async (url) => {
@@ -4476,12 +4793,57 @@ function isChromiumPage(page) {
4476
4793
  }
4477
4794
  }
4478
4795
  var captures = /* @__PURE__ */ new WeakMap();
4796
+ var MARK_WORLD = "piwi-code-reach";
4797
+ var MARK_FUNCTION = "piwiCodeReachMark";
4798
+ var MARK_SOURCE = `addEventListener('beforeunload', function ${MARK_FUNCTION}() {});`;
4799
+ var markedDocuments = /* @__PURE__ */ new WeakMap();
4800
+ function marksOf(page) {
4801
+ let marks = markedDocuments.get(page);
4802
+ if (!marks) {
4803
+ marks = /* @__PURE__ */ new Set();
4804
+ markedDocuments.set(page, marks);
4805
+ }
4806
+ return marks;
4807
+ }
4808
+ async function markCurrentDocument(page, cdp) {
4809
+ try {
4810
+ const { frameTree } = await internalCall(page, () => cdp.send("Page.getFrameTree"));
4811
+ const marks = marksOf(page);
4812
+ if (marks.has(frameTree.frame.loaderId)) return;
4813
+ const world = await internalCall(
4814
+ page,
4815
+ () => cdp.send("Page.createIsolatedWorld", { frameId: frameTree.frame.id, worldName: MARK_WORLD })
4816
+ );
4817
+ await internalCall(
4818
+ page,
4819
+ () => cdp.send("Runtime.evaluate", { expression: MARK_SOURCE, contextId: world.executionContextId })
4820
+ );
4821
+ marks.add(frameTree.frame.loaderId);
4822
+ } catch {
4823
+ }
4824
+ }
4825
+ function onPaused(page, capture, event) {
4826
+ const leaving = event.reason === "EventListener" && event.data?.eventName === "listener:beforeunload" && (event.callFrames?.[0]?.functionName ?? MARK_FUNCTION) === MARK_FUNCTION;
4827
+ const read = leaving ? readCoverage(page, capture.cdp) : null;
4828
+ if (read) {
4829
+ const merging = read.then(
4830
+ (coverage) => mergeCoverageRead(capture.ran, capture.scripts, coverage, capture.keepIdle)
4831
+ );
4832
+ capture.reading.add(merging);
4833
+ void merging.finally(() => capture.reading.delete(merging));
4834
+ }
4835
+ void (read ?? Promise.resolve()).finally(() => {
4836
+ internalCall(page, () => capture.cdp.send("Debugger.resume")).catch(() => {
4837
+ });
4838
+ });
4839
+ }
4479
4840
  function versionKey(url, hash) {
4480
4841
  return `${hash} ${url}`;
4481
4842
  }
4482
4843
  function onScriptParsed(page, capture, event) {
4483
4844
  if (!event.url || REPLAYED_SCRIPT.test(event.url)) return;
4484
- capture.scripts.set(event.scriptId, { url: event.url, hash: event.hash });
4845
+ capture.scripts.set(event.scriptId, { url: event.url, hash: event.hash, pageUrl: capture.pageUrl });
4846
+ if (NEVER_REACHED.test(event.url)) return;
4485
4847
  const key = versionKey(event.url, event.hash);
4486
4848
  if (capture.sources.has(key) || scriptCache.get(event.url, event.hash)) return;
4487
4849
  const source = internalCall(
@@ -4496,6 +4858,44 @@ function onScriptParsed(page, capture, event) {
4496
4858
  );
4497
4859
  capture.sources.set(key, source);
4498
4860
  }
4861
+ var READ_WAIT_MS = 5e3;
4862
+ function readCoverage(page, cdp) {
4863
+ let timer;
4864
+ const late = new Promise((resolve3) => {
4865
+ timer = setTimeout(() => resolve3([]), READ_WAIT_MS);
4866
+ });
4867
+ const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
4868
+ (answer) => answer.result,
4869
+ () => []
4870
+ );
4871
+ return Promise.race([taken, late]).finally(() => clearTimeout(timer));
4872
+ }
4873
+ function mergeCoverageRead(ran, scripts, read, keepIdle = true) {
4874
+ for (const covered of read) {
4875
+ const script = scripts.get(covered.scriptId);
4876
+ if (!script) continue;
4877
+ const key = versionKey(script.url, script.hash);
4878
+ let known = ran.get(key);
4879
+ if (!known) {
4880
+ known = { script, functions: [], byRange: /* @__PURE__ */ new Map() };
4881
+ ran.set(key, known);
4882
+ }
4883
+ for (const fn of covered.functions) {
4884
+ const whole = fn.ranges[0];
4885
+ if (!whole) continue;
4886
+ if (!keepIdle && whole.count <= 0) continue;
4887
+ const range = `${whole.startOffset}:${whole.endOffset}`;
4888
+ const seen = known.byRange.get(range);
4889
+ if (seen) {
4890
+ if (whole.count > 0 && seen.ranges[0].count <= 0) seen.ranges[0].count = whole.count;
4891
+ continue;
4892
+ }
4893
+ const copy = { ...fn, ranges: fn.ranges.map((r) => ({ ...r })) };
4894
+ known.byRange.set(range, copy);
4895
+ known.functions.push(copy);
4896
+ }
4897
+ }
4898
+ }
4499
4899
  async function detach(page, cdp) {
4500
4900
  try {
4501
4901
  await internalCall(page, () => cdp.detach());
@@ -4507,21 +4907,35 @@ async function startCodeReach(page) {
4507
4907
  let cdp = null;
4508
4908
  try {
4509
4909
  cdp = await internalCall(page, () => page.context().newCDPSession(page));
4510
- const capture = { cdp, scripts: /* @__PURE__ */ new Map(), sources: /* @__PURE__ */ new Map() };
4910
+ const capture = {
4911
+ cdp,
4912
+ scripts: /* @__PURE__ */ new Map(),
4913
+ sources: /* @__PURE__ */ new Map(),
4914
+ ran: /* @__PURE__ */ new Map(),
4915
+ keepIdle: browserFunctionsInWorker(),
4916
+ reading: /* @__PURE__ */ new Set(),
4917
+ pageUrl: page.url()
4918
+ };
4919
+ const marks = marksOf(page);
4511
4920
  cdp.on("Debugger.scriptParsed", (event) => onScriptParsed(page, capture, event));
4512
- cdp.on("Debugger.paused", () => {
4513
- internalCall(page, () => capture.cdp.send("Debugger.resume")).catch(() => {
4514
- });
4921
+ cdp.on("Debugger.paused", (event) => onPaused(page, capture, event));
4922
+ cdp.on("Page.frameNavigated", (event) => {
4923
+ if (event.frame.parentId) return;
4924
+ marks.add(event.frame.loaderId);
4925
+ capture.pageUrl = event.frame.url;
4515
4926
  });
4516
4927
  await internalCall(
4517
4928
  page,
4518
4929
  () => Promise.all([
4519
4930
  capture.cdp.send("Debugger.enable"),
4520
- capture.cdp.send("Debugger.setSkipAllPauses", { skip: true }),
4521
4931
  capture.cdp.send("Profiler.enable"),
4522
- capture.cdp.send("Profiler.startPreciseCoverage", { callCount: false, detailed: false })
4932
+ capture.cdp.send("Profiler.startPreciseCoverage", { callCount: false, detailed: false }),
4933
+ capture.cdp.send("Page.enable"),
4934
+ capture.cdp.send("Page.addScriptToEvaluateOnNewDocument", { source: MARK_SOURCE, worldName: MARK_WORLD }),
4935
+ capture.cdp.send("DOMDebugger.setEventListenerBreakpoint", { eventName: "beforeunload" })
4523
4936
  ])
4524
4937
  );
4938
+ await markCurrentDocument(page, capture.cdp);
4525
4939
  captures.set(page, capture);
4526
4940
  return true;
4527
4941
  } catch {
@@ -4535,17 +4949,17 @@ async function stopCodeReach(page) {
4535
4949
  captures.delete(page);
4536
4950
  const { cdp } = capture;
4537
4951
  try {
4538
- const coverage = await internalCall(page, async () => {
4952
+ const last = await internalCall(page, async () => {
4539
4953
  const taken = await cdp.send("Profiler.takePreciseCoverage");
4540
4954
  await cdp.send("Profiler.stopPreciseCoverage");
4541
- return taken;
4955
+ return taken.result;
4542
4956
  });
4957
+ await Promise.all(capture.reading);
4958
+ mergeCoverageRead(capture.ran, capture.scripts, last, capture.keepIdle);
4543
4959
  const entries = [];
4544
- for (const script of coverage.result) {
4545
- const parsed2 = capture.scripts.get(script.scriptId);
4546
- if (!parsed2) continue;
4547
- const entry = { url: parsed2.url, hash: parsed2.hash, functions: script.functions };
4548
- const source = await capture.sources.get(versionKey(parsed2.url, parsed2.hash));
4960
+ for (const [key, { script, functions }] of capture.ran) {
4961
+ const entry = { url: script.url, hash: script.hash, functions, pageUrl: script.pageUrl };
4962
+ const source = await capture.sources.get(key);
4549
4963
  if (source != null) entry.source = source;
4550
4964
  entries.push(entry);
4551
4965
  }
@@ -5522,6 +5936,143 @@ function readPageInteraction() {
5522
5936
  return out;
5523
5937
  }
5524
5938
 
5939
+ // src/internal/capture/api-requests.ts
5940
+ var SENDING_METHODS = /* @__PURE__ */ new Set(["fetch", "get", "post", "put", "patch", "delete", "head"]);
5941
+ var PROXIES = /* @__PURE__ */ new WeakMap();
5942
+ var OBSERVED = /* @__PURE__ */ new WeakSet();
5943
+ var PATCHED_REQUESTS = /* @__PURE__ */ new WeakSet();
5944
+ function observeApiRequests(playwright, recorder) {
5945
+ const apiRequest = playwright?.request;
5946
+ if (!apiRequest || typeof apiRequest !== "object" || typeof apiRequest.newContext !== "function") return;
5947
+ if (PATCHED_REQUESTS.has(apiRequest)) return;
5948
+ PATCHED_REQUESTS.add(apiRequest);
5949
+ const create = apiRequest.newContext.bind(apiRequest);
5950
+ apiRequest.newContext = async function newContext(...args) {
5951
+ const context = await create(...args);
5952
+ return observeApiRequestContext(context, recorder, baseUrlOption(args[0]));
5953
+ };
5954
+ }
5955
+ function observeApiRequestContext(context, recorder, baseURL = null) {
5956
+ if (!context || typeof context !== "object" || OBSERVED.has(context)) return context;
5957
+ const known = PROXIES.get(context);
5958
+ if (known) return known;
5959
+ const forwarders = /* @__PURE__ */ new Map();
5960
+ const proxy = new Proxy(context, {
5961
+ get(target, prop) {
5962
+ const value = Reflect.get(target, prop);
5963
+ if (typeof value !== "function" || prop === "constructor") return value;
5964
+ const cached = forwarders.get(prop);
5965
+ if (cached?.method === value) return cached.forward;
5966
+ const forward = typeof prop === "string" && SENDING_METHODS.has(prop) ? sendingMethod(target, prop, recorder, baseURL) : value.bind(target);
5967
+ forwarders.set(prop, { method: value, forward });
5968
+ return forward;
5969
+ }
5970
+ });
5971
+ PROXIES.set(context, proxy);
5972
+ OBSERVED.add(proxy);
5973
+ return proxy;
5974
+ }
5975
+ function sendingMethod(target, prop, recorder, baseURL) {
5976
+ return function send(...args) {
5977
+ const record = recorder();
5978
+ if (!record) return callMethod(target, prop, args);
5979
+ const startedAt = Date.now();
5980
+ const result = callMethod(target, prop, args);
5981
+ if (typeof result?.then !== "function") return result;
5982
+ const report = (response, error) => {
5983
+ try {
5984
+ record({
5985
+ method: methodOf(prop, args),
5986
+ url: urlOf(args[0]),
5987
+ baseURL,
5988
+ startedAt,
5989
+ endedAt: Date.now(),
5990
+ response,
5991
+ error
5992
+ });
5993
+ } catch {
5994
+ }
5995
+ };
5996
+ return result.then(
5997
+ (response) => {
5998
+ report(response);
5999
+ return response;
6000
+ },
6001
+ (error) => {
6002
+ report(null, error);
6003
+ throw error;
6004
+ }
6005
+ );
6006
+ };
6007
+ }
6008
+ function methodOf(prop, args) {
6009
+ if (prop !== "fetch") return prop.toUpperCase();
6010
+ const option = args[1]?.method;
6011
+ if (typeof option === "string" && option) return option.toUpperCase();
6012
+ const request = args[0];
6013
+ const method = typeof request?.method === "function" ? request.method() : null;
6014
+ return typeof method === "string" && method ? method.toUpperCase() : "GET";
6015
+ }
6016
+ function urlOf(target) {
6017
+ if (typeof target === "string") return target;
6018
+ const url = typeof target?.url === "function" ? target.url() : null;
6019
+ return typeof url === "string" ? url : null;
6020
+ }
6021
+ function baseUrlOption(options) {
6022
+ const baseURL = options?.baseURL;
6023
+ return typeof baseURL === "string" && baseURL ? baseURL : null;
6024
+ }
6025
+ var MAX_FAILURE_LENGTH = 300;
6026
+ var API_ERROR_PREFIX = /^apiRequestContext\.\w+: /;
6027
+ var STATUS_FAILURE = /^(\d{3}) /;
6028
+ function apiCallEntry(call, baseURL) {
6029
+ const response = call.response;
6030
+ if (response) {
6031
+ const timing = responseTiming(response) ?? { startTime: call.startedAt, duration: call.endedAt - call.startedAt };
6032
+ return {
6033
+ method: call.method,
6034
+ url: response.url(),
6035
+ status: response.status(),
6036
+ duration: timing.duration,
6037
+ startTime: timing.startTime
6038
+ };
6039
+ }
6040
+ const url = resolveUrl(call.url, call.baseURL ?? baseURL);
6041
+ if (!url) return null;
6042
+ const failure = failureText(call.error);
6043
+ const status = STATUS_FAILURE.exec(failure);
6044
+ return {
6045
+ method: call.method,
6046
+ url,
6047
+ status: status ? Number(status[1]) : 0,
6048
+ duration: Math.max(0, call.endedAt - call.startedAt),
6049
+ startTime: call.startedAt,
6050
+ ...status ? {} : { failure }
6051
+ };
6052
+ }
6053
+ function responseTiming(response) {
6054
+ if (typeof response.timing !== "function") return null;
6055
+ const timing = response.timing();
6056
+ if (!(timing.startTime > 0) || !(timing.responseEnd >= 0)) return null;
6057
+ return {
6058
+ startTime: timing.startTime,
6059
+ duration: Math.max(0, Math.round(timing.responseEnd - Math.max(0, timing.requestStart)))
6060
+ };
6061
+ }
6062
+ function resolveUrl(url, baseURL) {
6063
+ if (!url) return null;
6064
+ try {
6065
+ return new URL(url, baseURL ?? void 0).toString();
6066
+ } catch {
6067
+ return url;
6068
+ }
6069
+ }
6070
+ function failureText(error) {
6071
+ const message = error instanceof Error ? error.message : String(error);
6072
+ const line = message.split("\n", 1)[0].replace(API_ERROR_PREFIX, "").trim();
6073
+ return (line || "failed").slice(0, MAX_FAILURE_LENGTH);
6074
+ }
6075
+
5525
6076
  // src/internal/capture/capture-fixtures.ts
5526
6077
  function computeCoreVitals(lcpEntries, shiftEntries, eventEntries) {
5527
6078
  const lastLcp = lcpEntries && lcpEntries.length > 0 ? lcpEntries[lcpEntries.length - 1] : null;
@@ -5581,51 +6132,89 @@ function createSink() {
5581
6132
  userPick: null,
5582
6133
  pause: null,
5583
6134
  locatorPages: new LocatorPageLog(),
5584
- codeReach: null,
5585
- serverCoverage: /* @__PURE__ */ new Map()
6135
+ codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map() },
6136
+ serverCoverage: /* @__PURE__ */ new Map(),
6137
+ baseURL: null
5586
6138
  };
5587
6139
  }
5588
- function maybeStartCodeReach(page) {
6140
+ var MAX_CODE_REACH_PAGES = 16;
6141
+ var CODE_REACH_START_WAIT_MS = 2e3;
6142
+ function maybeStartCodeReach(page, alreadyOpen = false) {
5589
6143
  const sink = currentSink;
5590
- if (!sink || !browserCoverageInWorker()) return Promise.resolve(false);
5591
- if (sink.codeReach) return sink.codeReach.page === page ? sink.codeReach.started : Promise.resolve(false);
6144
+ if (!sink || !browserCoverageInWorker() || isPageClosed(page)) return Promise.resolve(false);
6145
+ const known = sink.codeReach.sessions.get(page);
6146
+ if (known) return known.started;
6147
+ if (alreadyOpen) {
6148
+ let recording = 0;
6149
+ for (const [other, session] of sink.codeReach.sessions) if (!session.stopped && !isPageClosed(other)) recording++;
6150
+ if (recording >= MAX_CODE_REACH_PAGES) return Promise.resolve(false);
6151
+ }
5592
6152
  const started = startCodeReach(page);
5593
- sink.codeReach = { page, started, stopped: false, files: null };
6153
+ sink.codeReach.sessions.set(page, { started, stopped: false });
5594
6154
  return started;
5595
6155
  }
6156
+ async function startCodeReachOnOpenPages() {
6157
+ if (!browserCoverageInWorker() || OPEN_PAGES.size === 0) return;
6158
+ const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
6159
+ await Promise.race([started, new Promise((resolve3) => setTimeout(resolve3, CODE_REACH_START_WAIT_MS).unref())]);
6160
+ }
5596
6161
  function configuredCodeReachRoots() {
5597
6162
  const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
5598
6163
  if (!raw) return null;
6164
+ const dirs = (value) => (Array.isArray(value) ? value : [value]).filter((r) => typeof r === "string" && r !== "");
5599
6165
  try {
5600
6166
  const parsed2 = JSON.parse(raw);
5601
- if (Array.isArray(parsed2)) return parsed2.filter((r) => typeof r === "string" && r !== "");
6167
+ if (Array.isArray(parsed2)) return dirs(parsed2);
6168
+ if (parsed2 && typeof parsed2 === "object") {
6169
+ return Object.fromEntries(Object.entries(parsed2).map(([origin, value]) => [origin, dirs(value)]));
6170
+ }
5602
6171
  } catch {
5603
6172
  }
5604
6173
  return raw.split(path13.delimiter).filter(Boolean);
5605
6174
  }
5606
6175
  async function stopSinkCodeReach(sink, closing) {
5607
- const session = sink.codeReach;
5608
- if (!session || session.stopped) return;
5609
- if (closing && closing.page !== session.page && (!closing.context || pageContext(session.page) !== closing.context)) {
5610
- return;
6176
+ const stopping = [];
6177
+ const underway = [];
6178
+ for (const [page, session] of sink.codeReach.sessions) {
6179
+ if (session.stopped) {
6180
+ if (!closing && session.stopping) underway.push(session.stopping);
6181
+ continue;
6182
+ }
6183
+ if (closing && closing.page !== page && (!closing.context || pageContext(page) !== closing.context)) continue;
6184
+ session.stopped = true;
6185
+ stopping.push([page, session]);
5611
6186
  }
5612
- session.stopped = true;
5613
- try {
5614
- if (!await session.started || isPageClosed(session.page)) return;
5615
- const entries = await stopCodeReach(session.page);
5616
- if (!entries) return;
6187
+ if (stopping.length > 0) {
5617
6188
  const info = sink.testInfo;
5618
6189
  const configFile = info?.config.configFile;
5619
6190
  const configDir = configFile ? path13.dirname(configFile) : info?.config.rootDir ?? process.cwd();
5620
- session.files = await resolveCodeReach(
5621
- entries,
5622
- codeReachRoots(configDir, configuredCodeReachRoots()),
5623
- pageMapFetcher(session.page)
5624
- );
6191
+ const roots = codeReachRoots(configDir, configuredCodeReachRoots());
6192
+ for (const [page, session] of stopping) {
6193
+ session.stopping = stopPageCodeReach(sink, page, session, roots);
6194
+ underway.push(session.stopping);
6195
+ }
6196
+ }
6197
+ await Promise.all(underway);
6198
+ }
6199
+ async function stopPageCodeReach(sink, page, session, roots) {
6200
+ try {
6201
+ if (!await session.started || isPageClosed(page)) return;
6202
+ const entries = await stopCodeReach(page);
6203
+ if (!entries) return;
6204
+ const { reached, known } = await resolveCodeReachFunctions(entries, roots, pageMapFetcher(page), page.url(), {
6205
+ known: browserFunctionsInWorker()
6206
+ });
6207
+ for (const [file, functions] of reached) {
6208
+ sink.codeReach.files.set(file, [...sink.codeReach.files.get(file) ?? [], ...functions]);
6209
+ }
6210
+ for (const [file, functions] of known) {
6211
+ sink.codeReach.known.set(file, [...sink.codeReach.known.get(file) ?? [], ...functions]);
6212
+ }
5625
6213
  } catch {
5626
6214
  }
5627
6215
  }
5628
6216
  var currentSink = null;
6217
+ var KNOWN_FUNCTIONS_SENT = /* @__PURE__ */ new Map();
5629
6218
  var PENDING_PROBES = /* @__PURE__ */ new Set();
5630
6219
  async function drainPendingProbes(capMs) {
5631
6220
  if (PENDING_PROBES.size === 0) return;
@@ -5922,6 +6511,7 @@ async function maybeOpenPicker(sink, closing) {
5922
6511
  applyPickToSnapshots(sink.capturedLocators, pick);
5923
6512
  }
5924
6513
  var INSTRUMENTED_PAGES = /* @__PURE__ */ new WeakSet();
6514
+ var OPEN_PAGES = /* @__PURE__ */ new Set();
5925
6515
  var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
5926
6516
  var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
5927
6517
  var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
@@ -6037,9 +6627,6 @@ function startElementCapture(sink, page, target, seq, callerLocation, used) {
6037
6627
  })();
6038
6628
  sink.capturePromises.push(resolveAttrs);
6039
6629
  }
6040
- function callMethod(target, prop, args) {
6041
- return target[prop](...args);
6042
- }
6043
6630
  function recordLocatorPage(sink, page, locator, location, arrival) {
6044
6631
  try {
6045
6632
  sink.locatorPages.record({ location, locator: String(locator), url: page.url(), arrival });
@@ -6205,7 +6792,12 @@ var CANCELLED_REQUEST = /^net::ERR_ABORTED$|^NS_BINDING_ABORTED$|^NS_ERROR_ABORT
6205
6792
  function instrumentPage(page) {
6206
6793
  if (!page || INSTRUMENTED_PAGES.has(page)) return;
6207
6794
  INSTRUMENTED_PAGES.add(page);
6795
+ if (typeof page.once === "function") {
6796
+ OPEN_PAGES.add(page);
6797
+ page.once("close", () => OPEN_PAGES.delete(page));
6798
+ }
6208
6799
  void maybeStartCodeReach(page);
6800
+ observeOwnApiRequests(page);
6209
6801
  const probeSink = currentSink;
6210
6802
  if (probeSink?.probeItem && !probeSink.probeInterception) {
6211
6803
  void installProbeInterception(page, probeSink.probeItem).then((interception) => {
@@ -6365,25 +6957,7 @@ function recordRequest(sink, request, end, response) {
6365
6957
  if (timing.requestStart < 0 && typeof response.serverAddr === "function") {
6366
6958
  if (await response.serverAddr().catch(() => void 0) === null) entry.fulfilled = true;
6367
6959
  }
6368
- const headers = response.headers();
6369
- const contentType = headers["content-type"];
6370
- if (contentType) entry.contentType = contentType.split(";")[0].trim();
6371
- const logHeader = headers["x-piwi-logs"];
6372
- if (logHeader) {
6373
- try {
6374
- entry.serverLogs = JSON.parse((0, import_node_zlib2.gunzipSync)(Buffer.from(logHeader, "base64")).toString("utf-8"));
6375
- } catch {
6376
- }
6377
- }
6378
- const coverageHeader = headers["x-piwi-coverage"];
6379
- if (coverageHeader && serverCoverageInWorker()) recordServerCoverageIds(sink, url, coverageHeader);
6380
- const traceHeader = headers["x-piwi-trace"];
6381
- if (traceHeader && process.env.PIWI_CAPTURE_SERVER_TRACES !== "false") {
6382
- try {
6383
- entry.serverTraces = JSON.parse((0, import_node_zlib2.gunzipSync)(Buffer.from(traceHeader, "base64")).toString("utf-8"));
6384
- } catch {
6385
- }
6386
- }
6960
+ readResponseHeaders(sink, entry, url, response.headers());
6387
6961
  }
6388
6962
  sink.networkRequests.push(entry);
6389
6963
  } catch {
@@ -6391,6 +6965,48 @@ function recordRequest(sink, request, end, response) {
6391
6965
  })();
6392
6966
  sink.pendingHandlers.push(p);
6393
6967
  }
6968
+ function readResponseHeaders(sink, entry, url, headers) {
6969
+ const contentType = headers["content-type"];
6970
+ if (contentType) entry.contentType = contentType.split(";")[0].trim();
6971
+ const logHeader = headers["x-piwi-logs"];
6972
+ if (logHeader) {
6973
+ try {
6974
+ entry.serverLogs = JSON.parse((0, import_node_zlib2.gunzipSync)(Buffer.from(logHeader, "base64")).toString("utf-8"));
6975
+ } catch {
6976
+ }
6977
+ }
6978
+ const coverageHeader = headers["x-piwi-coverage"];
6979
+ if (coverageHeader && serverCoverageInWorker()) recordServerCoverageIds(sink, url, coverageHeader);
6980
+ const traceHeader = headers["x-piwi-trace"];
6981
+ if (traceHeader && process.env.PIWI_CAPTURE_SERVER_TRACES !== "false") {
6982
+ try {
6983
+ entry.serverTraces = JSON.parse((0, import_node_zlib2.gunzipSync)(Buffer.from(traceHeader, "base64")).toString("utf-8"));
6984
+ } catch {
6985
+ }
6986
+ }
6987
+ }
6988
+ function recordApiCall(sink, call) {
6989
+ try {
6990
+ const entry = apiCallEntry(call, sink.baseURL);
6991
+ if (!entry) return;
6992
+ if (call.response) readResponseHeaders(sink, entry, String(entry.url), call.response.headers());
6993
+ sink.networkRequests.push(entry);
6994
+ } catch {
6995
+ }
6996
+ }
6997
+ function apiCallRecorder() {
6998
+ const sink = currentSink;
6999
+ return sink ? (call) => recordApiCall(sink, call) : null;
7000
+ }
7001
+ function observeOwnApiRequests(owner) {
7002
+ try {
7003
+ const holder = owner;
7004
+ if (holder.request && typeof holder.request === "object") {
7005
+ holder.request = observeApiRequestContext(holder.request, apiCallRecorder);
7006
+ }
7007
+ } catch {
7008
+ }
7009
+ }
6394
7010
  function recordOpenRequests(sink) {
6395
7011
  for (const [request, response] of sink.openResponses) {
6396
7012
  RECORDED_OPEN.add(request);
@@ -6404,6 +7020,7 @@ async function awaitFlakeConditions() {
6404
7020
  function instrumentContext(context) {
6405
7021
  if (!context || INSTRUMENTED_CONTEXTS.has(context)) return;
6406
7022
  INSTRUMENTED_CONTEXTS.add(context);
7023
+ observeOwnApiRequests(context);
6407
7024
  if (process.env.PIWI_CAPTURE_LOCATORS !== "false" && typeof context.addInitScript === "function") {
6408
7025
  void Promise.resolve(context.addInitScript({ content: PROBE_INIT_SCRIPT })).catch(() => {
6409
7026
  });
@@ -6515,10 +7132,24 @@ async function flushSink(sink, testInfo) {
6515
7132
  });
6516
7133
  }
6517
7134
  await stopSinkCodeReach(sink);
6518
- if (sink.codeReach?.files?.length) {
7135
+ if (sink.codeReach.files.size > 0) {
6519
7136
  await testInfo.attach(ATTACHMENT_NAMES.codeReach, {
6520
7137
  contentType: "application/json",
6521
- body: Buffer.from(JSON.stringify(sink.codeReach.files))
7138
+ body: Buffer.from(JSON.stringify(finalizeCodeReach(sink.codeReach.files.keys())))
7139
+ });
7140
+ const functions = browserFunctionsInWorker() ? browserFunctionCoverage(sink.codeReach.files) : null;
7141
+ if (functions) {
7142
+ await testInfo.attach(ATTACHMENT_NAMES.browserCoverage, {
7143
+ contentType: "application/json",
7144
+ body: Buffer.from(JSON.stringify(functions))
7145
+ });
7146
+ }
7147
+ }
7148
+ const known = browserFunctionsInWorker() ? newKnownFunctions(sink.codeReach.known, KNOWN_FUNCTIONS_SENT) : [];
7149
+ for (const part of known) {
7150
+ await testInfo.attach(ATTACHMENT_NAMES.browserFunctions, {
7151
+ contentType: "application/json",
7152
+ body: Buffer.from(JSON.stringify(part))
6522
7153
  });
6523
7154
  }
6524
7155
  if (sink.serverCoverage.size > 0) {
@@ -6658,14 +7289,17 @@ async function flushSink(sink, testInfo) {
6658
7289
  }
6659
7290
  }
6660
7291
  var piwiFixtures = {
6661
- // Auto, worker-scoped: the worker's resource ledger. Set up before any hook
6662
- // (worker fixtures come first), so a `beforeAll` context is in it too, and
6663
- // torn down after the browser closed, so its census sees what only the
6664
- // worker's shutdown closed. Depends on `playwright` alone, which launches nothing.
7292
+ // Auto, worker-scoped: the worker's resource ledger, and the API request
7293
+ // contexts its tests create, handed out behind the call proxy. Set up before
7294
+ // any hook (worker fixtures come first), so a `beforeAll` context is in the
7295
+ // ledger and records the calls the tests make through it, and torn down after
7296
+ // the browser closed, so its census sees what only the worker's shutdown
7297
+ // closed. Depends on `playwright` alone, which launches nothing.
6665
7298
  piwiResources: [
6666
7299
  async ({ playwright }, use, workerInfo) => {
6667
7300
  boxCaptureFrames();
6668
7301
  startResourceLedger(playwright, workerInfo.workerIndex);
7302
+ observeApiRequests(playwright, apiCallRecorder);
6669
7303
  try {
6670
7304
  await use();
6671
7305
  } finally {
@@ -6696,12 +7330,13 @@ var piwiFixtures = {
6696
7330
  // (attach the collected data) at teardown. Runs for every test without being
6697
7331
  // requested, so suites that never destructure `page` are still captured.
6698
7332
  piwiCapture: [
6699
- async ({ screenshot }, use, testInfo) => {
7333
+ async ({ screenshot, baseURL }, use, testInfo) => {
6700
7334
  boxCaptureFrames();
6701
7335
  const conflict = labModeConflict();
6702
7336
  if (conflict) throw new Error(conflict);
6703
7337
  const sink = createSink();
6704
7338
  sink.testInfo = testInfo;
7339
+ sink.baseURL = baseURL ?? null;
6705
7340
  if (keepsFailureScreenshots(screenshot, testInfo.retry)) sink.failureScreenshot = screenshot;
6706
7341
  const pauseSet = currentPauseSet();
6707
7342
  if (pauseSet.size) {
@@ -6731,6 +7366,7 @@ var piwiFixtures = {
6731
7366
  activeResourceLedger()?.testStarted(testInfo);
6732
7367
  currentSink = sink;
6733
7368
  try {
7369
+ await startCodeReachOnOpenPages();
6734
7370
  await use();
6735
7371
  } finally {
6736
7372
  currentSink = null;