@piwitests/reporter 0.54.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]);
@@ -4053,241 +4525,13 @@ var crypto2 = __toESM(require("crypto"));
4053
4525
  var fs6 = __toESM(require("fs"));
4054
4526
  var path10 = __toESM(require("path"));
4055
4527
  var import_node_child_process = require("child_process");
4056
-
4057
- // ../core/src/coverage.ts
4058
- var MAX_PATH_LENGTH = 500;
4059
- function normalizeCoveragePath(raw) {
4060
- if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
4061
- const path14 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
4062
- if (!path14 || path14.startsWith("../") || path14.startsWith("/") || /^[A-Za-z]:\//.test(path14)) return null;
4063
- if (/(^|\/)node_modules\//.test(path14)) return null;
4064
- return path14;
4065
- }
4066
-
4067
- // ../core/src/source-map.ts
4068
- var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4069
- var BASE64_VALUE = new Int8Array(128).fill(-1);
4070
- for (let i = 0; i < BASE64.length; i++) BASE64_VALUE[BASE64.charCodeAt(i)] = i;
4071
- function decodeMappings(mappings, sourceBase) {
4072
- const lines = [];
4073
- let current = { columns: [], sources: [] };
4074
- let column = 0;
4075
- let source = 0;
4076
- const fields = [];
4077
- let value = 0;
4078
- let shift = 0;
4079
- const flush = () => {
4080
- if (fields.length === 0) return;
4081
- column += fields[0];
4082
- if (fields.length >= 4) {
4083
- source += fields[1];
4084
- current.columns.push(column);
4085
- current.sources.push(sourceBase + source);
4086
- } else {
4087
- current.columns.push(column);
4088
- current.sources.push(-1);
4089
- }
4090
- fields.length = 0;
4091
- };
4092
- for (let i = 0; i <= mappings.length; i++) {
4093
- const ch = i < mappings.length ? mappings.charCodeAt(i) : 59;
4094
- if (ch === 59 || ch === 44) {
4095
- flush();
4096
- if (ch === 59) {
4097
- lines.push(current);
4098
- current = { columns: [], sources: [] };
4099
- column = 0;
4100
- }
4101
- continue;
4102
- }
4103
- const digit = ch < 128 ? BASE64_VALUE[ch] : -1;
4104
- if (digit < 0) continue;
4105
- value += (digit & 31) << shift;
4106
- if (digit & 32) {
4107
- shift += 5;
4108
- continue;
4109
- }
4110
- fields.push(value & 1 ? -(value >>> 1) : value >>> 1);
4111
- value = 0;
4112
- shift = 0;
4113
- }
4114
- for (const line of lines) {
4115
- if (line.columns.every((c, i) => i === 0 || c >= line.columns[i - 1])) continue;
4116
- const order = line.columns.map((_, i) => i).sort((a, b) => line.columns[a] - line.columns[b]);
4117
- line.columns = order.map((i) => line.columns[i]);
4118
- line.sources = order.map((i) => line.sources[i]);
4119
- }
4120
- return lines;
4121
- }
4122
- function joinRoot(root, source) {
4123
- if (!root || /^[a-z][\w+.-]*:/i.test(source) || source.startsWith("/")) return source;
4124
- return root.endsWith("/") ? root + source : `${root}/${source}`;
4125
- }
4126
- function decodeSourceMap(raw) {
4127
- const sources = [];
4128
- const lines = [];
4129
- const add = (map, lineOffset, columnOffset) => {
4130
- if (map.sections) {
4131
- for (const section of map.sections) {
4132
- if (!add(section.map, section.offset.line, section.offset.column)) return false;
4133
- }
4134
- return true;
4135
- }
4136
- if (typeof map.mappings !== "string" || !Array.isArray(map.sources)) return false;
4137
- const base = sources.length;
4138
- for (const s of map.sources) sources.push(joinRoot(map.sourceRoot, s ?? ""));
4139
- decodeMappings(map.mappings, base).forEach((segments, i) => {
4140
- const at = lineOffset + i;
4141
- const shifted = i === 0 && columnOffset ? segments.columns.map((c) => c + columnOffset) : segments.columns;
4142
- const existing = lines[at];
4143
- if (!existing) lines[at] = { columns: shifted, sources: segments.sources };
4144
- else {
4145
- existing.columns.push(...shifted);
4146
- existing.sources.push(...segments.sources);
4147
- }
4148
- });
4149
- return true;
4150
- };
4151
- if (raw.version !== void 0 && raw.version !== 3) return null;
4152
- if (!add(raw, 0, 0)) return null;
4153
- return {
4154
- sources,
4155
- sourceAt(line, column) {
4156
- const segments = lines[line];
4157
- if (!segments || segments.columns.length === 0) return null;
4158
- let lo = 0;
4159
- let hi = segments.columns.length - 1;
4160
- if (segments.columns[0] > column) return null;
4161
- while (lo < hi) {
4162
- const mid = lo + hi + 1 >> 1;
4163
- if (segments.columns[mid] <= column) lo = mid;
4164
- else hi = mid - 1;
4165
- }
4166
- const source = segments.sources[lo];
4167
- return source < 0 ? null : source;
4168
- },
4169
- mapsBetween(source, start, end) {
4170
- for (let line = start.line; line <= end.line && line < lines.length; line++) {
4171
- const segments = lines[line];
4172
- if (!segments) continue;
4173
- let i = 0;
4174
- if (line === start.line) {
4175
- let hi = segments.columns.length;
4176
- while (i < hi) {
4177
- const mid = i + hi >> 1;
4178
- if (segments.columns[mid] < start.column) i = mid + 1;
4179
- else hi = mid;
4180
- }
4181
- }
4182
- for (; i < segments.columns.length; i++) {
4183
- if (line === end.line && segments.columns[i] >= end.column) return false;
4184
- if (segments.sources[i] === source) return true;
4185
- }
4186
- }
4187
- return false;
4188
- }
4189
- };
4190
- }
4191
- function lineStarts(text) {
4192
- const starts = [0];
4193
- for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) starts.push(i + 1);
4194
- return starts;
4195
- }
4196
- function offsetToPosition(starts, offset) {
4197
- let lo = 0;
4198
- let hi = starts.length - 1;
4199
- while (lo < hi) {
4200
- const mid = lo + hi + 1 >> 1;
4201
- if (starts[mid] <= offset) lo = mid;
4202
- else hi = mid - 1;
4203
- }
4204
- return { line: lo, column: offset - starts[lo] };
4205
- }
4206
- function sourceMappingUrl(script) {
4207
- const tail = script.slice(-4096);
4208
- const matches = [...tail.matchAll(/(?:\/\/|\/\*)[#@]\s*sourceMappingURL=([^\s*'"]+)/g)];
4209
- return matches.length ? matches[matches.length - 1][1] : null;
4210
- }
4211
- function decodeDataUrl(url) {
4212
- const m = /^data:([^,]*?)(;base64)?,(.*)$/s.exec(url);
4213
- if (!m) return null;
4214
- try {
4215
- if (!m[2]) return decodeURIComponent(m[3]);
4216
- const binary = globalThis.atob(m[3]);
4217
- const bytes = new Uint8Array(binary.length);
4218
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
4219
- return new TextDecoder().decode(bytes);
4220
- } catch {
4221
- return null;
4222
- }
4223
- }
4224
- function normalizeSourcePath(source) {
4225
- let s = source.replace(/[?#].*$/, "");
4226
- s = s.replace(/^webpack:\/\/[^/]*\//, "").replace(/^webpack:\/\//, "");
4227
- s = s.replace(/^(?:vite|rollup|turbopack|ng):\/*/, "");
4228
- s = s.replace(/^\[project\]\//, "");
4229
- s = s.replace(/^file:\/\//, "");
4230
- s = s.replace(/^\/@fs\//, "/");
4231
- s = s.replace(/^\/@id\//, "");
4232
- try {
4233
- s = decodeURIComponent(s);
4234
- } catch {
4235
- }
4236
- s = s.replace(/\\/g, "/");
4237
- if (/^\/[A-Za-z]:\//.test(s)) s = s.slice(1);
4238
- s = s.replace(/(^|\/)\.\//g, "$1");
4239
- return s;
4240
- }
4241
-
4242
- // ../core/src/code-reach.ts
4243
- var MAX_CODE_REACH_FILES = 2e3;
4244
- function executedFunctionRanges(entry) {
4245
- const length = entry.source?.length ?? Number.POSITIVE_INFINITY;
4246
- const out = [];
4247
- entry.functions.forEach((fn, i) => {
4248
- const whole = fn.ranges[0];
4249
- if (!whole || whole.count <= 0) return;
4250
- const topLevel = whole.startOffset === 0 && (whole.endOffset >= length || i === 0 && fn.functionName === "");
4251
- if (topLevel) return;
4252
- out.push({ start: whole.startOffset, end: whole.endOffset });
4253
- });
4254
- return out;
4255
- }
4256
- function executedFunctionOffsets(entry) {
4257
- return executedFunctionRanges(entry).map((range) => range.start);
4258
- }
4259
- function scriptRan(entry) {
4260
- return executedFunctionOffsets(entry).length > 0;
4261
- }
4262
- function reachedSources(entry, map) {
4263
- const ranges = executedFunctionRanges(entry);
4264
- const reached = /* @__PURE__ */ new Set();
4265
- if (!ranges.length || entry.source === void 0) return reached;
4266
- const starts = lineStarts(entry.source);
4267
- for (const range of ranges) {
4268
- const start = offsetToPosition(starts, range.start);
4269
- const source = map.sourceAt(start.line, start.column);
4270
- if (source === null || reached.has(source)) continue;
4271
- if (map.mapsBetween(source, start, offsetToPosition(starts, range.end))) reached.add(source);
4272
- }
4273
- return reached;
4274
- }
4275
- function finalizeCodeReach(files) {
4276
- const out = /* @__PURE__ */ new Set();
4277
- for (const raw of files) {
4278
- const file = normalizeCoveragePath(raw);
4279
- if (file !== null) out.add(file);
4280
- }
4281
- return [...out].sort().slice(0, MAX_CODE_REACH_FILES);
4282
- }
4283
-
4284
- // src/internal/capture/code-reach.ts
4285
4528
  var MAX_MAP_BYTES = 20 * 1024 * 1024;
4286
4529
  var MAP_TIMEOUT_MS = 5e3;
4287
4530
  var SOURCE_EXTENSIONS = /\.(?:[cm]?[jt]sx?|vue|svelte|astro|marko|html?)$/i;
4288
4531
  var REPLAYED_SCRIPT = /^(?:about|rsc):/i;
4532
+ var NEVER_REACHED = /\/node_modules\/|\/@vite\//;
4289
4533
  var BUILD_DIRS = /(?:^|\/)(?:dist|build|out|\.output|\.next|\.nuxt|\.svelte-kit|public|static|wwwroot)\//;
4290
- var MAX_CACHED_SCRIPTS = 500;
4534
+ var MAX_CACHED_SCRIPTS = 4e3;
4291
4535
  var LatestByUrl = class {
4292
4536
  constructor() {
4293
4537
  this.entries = /* @__PURE__ */ new Map();
@@ -4327,8 +4571,32 @@ function repositoryRoot(from) {
4327
4571
  }
4328
4572
  function codeReachRoots(configDir, configured) {
4329
4573
  const repoRoot = repositoryRoot(configDir);
4330
- const roots = configured?.length ? configured.map((r) => path10.resolve(configDir, r)) : [configDir];
4331
- 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;
4332
4600
  }
4333
4601
  function isFile(file) {
4334
4602
  try {
@@ -4412,50 +4680,96 @@ async function resolveScript(entry, roots, bases, fetchMap) {
4412
4680
  const version = scriptVersion(entry);
4413
4681
  const cached = scriptCache.get(entry.url, version);
4414
4682
  if (cached) return cached;
4415
- const file = fileForUrl(entry.url, roots.roots, bases);
4683
+ const file = fileForUrl(entry.url, roots, bases);
4416
4684
  const lookup = await loadMap(entry, fetchMap);
4417
4685
  let resolution;
4418
- if (file) resolution = { kind: "file", file, lookup, source: entry.source };
4419
- else resolution = lookup ? { kind: "map", lookup, files: [], source: entry.source } : { kind: "none" };
4420
- 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);
4421
4690
  return resolution;
4422
4691
  }
4423
- function withSource(entry, resolution) {
4424
- if (entry.source !== void 0 || resolution.source === void 0) return entry;
4425
- 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());
4426
4697
  }
4427
- async function resolveCodeReach(entries, roots, fetchMap) {
4428
- 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);
4429
4715
  const bases = viteBases(entries);
4430
4716
  const scripts = entries.filter((entry) => entry.url && entry.functions?.length && !REPLAYED_SCRIPT.test(entry.url));
4431
- const known = scripts.map((entry) => scriptCache.get(entry.url, scriptVersion(entry)));
4717
+ const cached = scripts.map((entry) => scriptCache.get(entry.url, scriptVersion(entry)));
4432
4718
  for (const [i, covered] of scripts.entries()) {
4433
- 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
+ };
4434
4736
  if (resolution.kind === "file") {
4435
- const entry = withSource(covered, resolution);
4436
- const ran = resolution.lookup ? reachedSources(entry, resolution.lookup).size > 0 : scriptRan(entry);
4437
- 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
+ }
4438
4745
  } else if (resolution.kind === "map") {
4439
- const entry = withSource(covered, resolution);
4440
- for (const index of reachedSources(entry, resolution.lookup)) {
4441
- if (resolution.files[index] === void 0) {
4442
- const source = resolution.lookup.sources[index] ?? "";
4443
- let absolute = resolveSourcePath(source, roots.roots);
4444
- if (!absolute && !/^[a-z][\w+.-]*:/i.test(source)) {
4445
- try {
4446
- absolute = fileForUrl(new URL(source, entry.url).href, roots.roots, bases);
4447
- } catch {
4448
- absolute = null;
4449
- }
4450
- }
4451
- 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]);
4452
4765
  }
4453
- const file = resolution.files[index];
4454
- if (file) files.add(relativeToRepo(file, roots.repoRoot));
4455
4766
  }
4767
+ listed = { count: covered.functions.length, files: byFile };
4768
+ knownCache.set(covered.url, version, listed);
4456
4769
  }
4770
+ for (const [file, functions] of listed.files) addTo(known, file, functions);
4457
4771
  }
4458
- return finalizeCodeReach(files);
4772
+ return { reached: files, known };
4459
4773
  }
4460
4774
  function pageMapFetcher(page) {
4461
4775
  return async (url) => {
@@ -4479,12 +4793,57 @@ function isChromiumPage(page) {
4479
4793
  }
4480
4794
  }
4481
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
+ }
4482
4840
  function versionKey(url, hash) {
4483
4841
  return `${hash} ${url}`;
4484
4842
  }
4485
4843
  function onScriptParsed(page, capture, event) {
4486
4844
  if (!event.url || REPLAYED_SCRIPT.test(event.url)) return;
4487
- 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;
4488
4847
  const key = versionKey(event.url, event.hash);
4489
4848
  if (capture.sources.has(key) || scriptCache.get(event.url, event.hash)) return;
4490
4849
  const source = internalCall(
@@ -4499,6 +4858,44 @@ function onScriptParsed(page, capture, event) {
4499
4858
  );
4500
4859
  capture.sources.set(key, source);
4501
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
+ }
4502
4899
  async function detach(page, cdp) {
4503
4900
  try {
4504
4901
  await internalCall(page, () => cdp.detach());
@@ -4510,21 +4907,35 @@ async function startCodeReach(page) {
4510
4907
  let cdp = null;
4511
4908
  try {
4512
4909
  cdp = await internalCall(page, () => page.context().newCDPSession(page));
4513
- 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);
4514
4920
  cdp.on("Debugger.scriptParsed", (event) => onScriptParsed(page, capture, event));
4515
- cdp.on("Debugger.paused", () => {
4516
- internalCall(page, () => capture.cdp.send("Debugger.resume")).catch(() => {
4517
- });
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;
4518
4926
  });
4519
4927
  await internalCall(
4520
4928
  page,
4521
4929
  () => Promise.all([
4522
4930
  capture.cdp.send("Debugger.enable"),
4523
- capture.cdp.send("Debugger.setSkipAllPauses", { skip: true }),
4524
4931
  capture.cdp.send("Profiler.enable"),
4525
- 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" })
4526
4936
  ])
4527
4937
  );
4938
+ await markCurrentDocument(page, capture.cdp);
4528
4939
  captures.set(page, capture);
4529
4940
  return true;
4530
4941
  } catch {
@@ -4538,17 +4949,17 @@ async function stopCodeReach(page) {
4538
4949
  captures.delete(page);
4539
4950
  const { cdp } = capture;
4540
4951
  try {
4541
- const coverage = await internalCall(page, async () => {
4952
+ const last = await internalCall(page, async () => {
4542
4953
  const taken = await cdp.send("Profiler.takePreciseCoverage");
4543
4954
  await cdp.send("Profiler.stopPreciseCoverage");
4544
- return taken;
4955
+ return taken.result;
4545
4956
  });
4957
+ await Promise.all(capture.reading);
4958
+ mergeCoverageRead(capture.ran, capture.scripts, last, capture.keepIdle);
4546
4959
  const entries = [];
4547
- for (const script of coverage.result) {
4548
- const parsed2 = capture.scripts.get(script.scriptId);
4549
- if (!parsed2) continue;
4550
- const entry = { url: parsed2.url, hash: parsed2.hash, functions: script.functions };
4551
- 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);
4552
4963
  if (source != null) entry.source = source;
4553
4964
  entries.push(entry);
4554
4965
  }
@@ -5721,52 +6132,89 @@ function createSink() {
5721
6132
  userPick: null,
5722
6133
  pause: null,
5723
6134
  locatorPages: new LocatorPageLog(),
5724
- codeReach: null,
6135
+ codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map() },
5725
6136
  serverCoverage: /* @__PURE__ */ new Map(),
5726
6137
  baseURL: null
5727
6138
  };
5728
6139
  }
5729
- function maybeStartCodeReach(page) {
6140
+ var MAX_CODE_REACH_PAGES = 16;
6141
+ var CODE_REACH_START_WAIT_MS = 2e3;
6142
+ function maybeStartCodeReach(page, alreadyOpen = false) {
5730
6143
  const sink = currentSink;
5731
- if (!sink || !browserCoverageInWorker()) return Promise.resolve(false);
5732
- 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
+ }
5733
6152
  const started = startCodeReach(page);
5734
- sink.codeReach = { page, started, stopped: false, files: null };
6153
+ sink.codeReach.sessions.set(page, { started, stopped: false });
5735
6154
  return started;
5736
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
+ }
5737
6161
  function configuredCodeReachRoots() {
5738
6162
  const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
5739
6163
  if (!raw) return null;
6164
+ const dirs = (value) => (Array.isArray(value) ? value : [value]).filter((r) => typeof r === "string" && r !== "");
5740
6165
  try {
5741
6166
  const parsed2 = JSON.parse(raw);
5742
- 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
+ }
5743
6171
  } catch {
5744
6172
  }
5745
6173
  return raw.split(path13.delimiter).filter(Boolean);
5746
6174
  }
5747
6175
  async function stopSinkCodeReach(sink, closing) {
5748
- const session = sink.codeReach;
5749
- if (!session || session.stopped) return;
5750
- if (closing && closing.page !== session.page && (!closing.context || pageContext(session.page) !== closing.context)) {
5751
- 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]);
5752
6186
  }
5753
- session.stopped = true;
5754
- try {
5755
- if (!await session.started || isPageClosed(session.page)) return;
5756
- const entries = await stopCodeReach(session.page);
5757
- if (!entries) return;
6187
+ if (stopping.length > 0) {
5758
6188
  const info = sink.testInfo;
5759
6189
  const configFile = info?.config.configFile;
5760
6190
  const configDir = configFile ? path13.dirname(configFile) : info?.config.rootDir ?? process.cwd();
5761
- session.files = await resolveCodeReach(
5762
- entries,
5763
- codeReachRoots(configDir, configuredCodeReachRoots()),
5764
- pageMapFetcher(session.page)
5765
- );
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
+ }
5766
6213
  } catch {
5767
6214
  }
5768
6215
  }
5769
6216
  var currentSink = null;
6217
+ var KNOWN_FUNCTIONS_SENT = /* @__PURE__ */ new Map();
5770
6218
  var PENDING_PROBES = /* @__PURE__ */ new Set();
5771
6219
  async function drainPendingProbes(capMs) {
5772
6220
  if (PENDING_PROBES.size === 0) return;
@@ -6063,6 +6511,7 @@ async function maybeOpenPicker(sink, closing) {
6063
6511
  applyPickToSnapshots(sink.capturedLocators, pick);
6064
6512
  }
6065
6513
  var INSTRUMENTED_PAGES = /* @__PURE__ */ new WeakSet();
6514
+ var OPEN_PAGES = /* @__PURE__ */ new Set();
6066
6515
  var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
6067
6516
  var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
6068
6517
  var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
@@ -6343,6 +6792,10 @@ var CANCELLED_REQUEST = /^net::ERR_ABORTED$|^NS_BINDING_ABORTED$|^NS_ERROR_ABORT
6343
6792
  function instrumentPage(page) {
6344
6793
  if (!page || INSTRUMENTED_PAGES.has(page)) return;
6345
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
+ }
6346
6799
  void maybeStartCodeReach(page);
6347
6800
  observeOwnApiRequests(page);
6348
6801
  const probeSink = currentSink;
@@ -6679,10 +7132,24 @@ async function flushSink(sink, testInfo) {
6679
7132
  });
6680
7133
  }
6681
7134
  await stopSinkCodeReach(sink);
6682
- if (sink.codeReach?.files?.length) {
7135
+ if (sink.codeReach.files.size > 0) {
6683
7136
  await testInfo.attach(ATTACHMENT_NAMES.codeReach, {
6684
7137
  contentType: "application/json",
6685
- 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))
6686
7153
  });
6687
7154
  }
6688
7155
  if (sink.serverCoverage.size > 0) {
@@ -6899,6 +7366,7 @@ var piwiFixtures = {
6899
7366
  activeResourceLedger()?.testStarted(testInfo);
6900
7367
  currentSink = sink;
6901
7368
  try {
7369
+ await startCodeReachOnOpenPages();
6902
7370
  await use();
6903
7371
  } finally {
6904
7372
  currentSink = null;