Files
iptvnator/tools/coverage/e2e-v8-web.mjs
T
4grayand4gray ef900d0f2a [codex] Add scoped coverage reporting (#1024)
* add scoped coverage reporting

* fix coverage review feedback

---------

Co-authored-by: 4gray <fourgray@proton.me>
2026-06-06 17:41:40 +02:00

154 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import {
existsSync,
mkdirSync,
readFileSync,
readdirSync,
statSync,
writeFileSync,
} from 'node:fs';
import path from 'node:path';
import process from 'node:process';
console.warn(
'Experimental: browser V8 coverage for existing Playwright specs requires Chromium and is intended for local investigation, not CI gating.'
);
const workspaceRoot = process.cwd();
const result = spawnSync(
'pnpm',
[
'nx',
'run',
'web-e2e:e2e',
'--',
'--project=chromium',
],
{
cwd: process.cwd(),
env: {
...process.env,
IPTVNATOR_E2E_V8_COVERAGE: '1',
},
stdio: 'inherit',
}
);
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
function listFiles(directory, predicate) {
if (!existsSync(directory)) {
return [];
}
const files = [];
for (const entry of readdirSync(directory)) {
const fullPath = path.join(directory, entry);
const stats = statSync(fullPath);
if (stats.isDirectory()) {
files.push(...listFiles(fullPath, predicate));
} else if (predicate(fullPath)) {
files.push(fullPath);
}
}
return files;
}
function mergeRanges(ranges) {
const sorted = ranges
.filter((range) => range.count > 0)
.map((range) => [range.startOffset, range.endOffset])
.sort(([leftStart], [rightStart]) => leftStart - rightStart);
const merged = [];
for (const [start, end] of sorted) {
const previous = merged.at(-1);
if (previous && start <= previous[1]) {
previous[1] = Math.max(previous[1], end);
} else {
merged.push([start, end]);
}
}
return merged;
}
function summarizeJsEntry(entry) {
const sourceLength = entry.source?.length ?? 0;
const ranges = mergeRanges((entry.functions ?? []).flatMap((fn) => fn.ranges ?? []));
const usedBytes = ranges.reduce((total, [start, end]) => total + Math.max(0, end - start), 0);
return {
url: entry.url,
sourceLength,
usedBytes,
pct: sourceLength > 0 ? Math.round((usedBytes / sourceLength) * 10000) / 100 : 0,
};
}
const rawCoverageRoot = path.join(workspaceRoot, 'apps/web-e2e/test-results');
const rawFiles = listFiles(rawCoverageRoot, (file) =>
file.endsWith('.json') && file.includes(`${path.sep}v8-coverage${path.sep}`)
);
const entriesByUrl = new Map();
for (const file of rawFiles) {
const raw = JSON.parse(readFileSync(file, 'utf8'));
for (const entry of raw.js ?? []) {
if (!entry.url || entry.url.startsWith('extensions::')) {
continue;
}
const summary = summarizeJsEntry(entry);
const existing = entriesByUrl.get(summary.url);
if (!existing || summary.usedBytes > existing.usedBytes) {
entriesByUrl.set(summary.url, summary);
}
}
}
const summaries = Array.from(entriesByUrl.values()).sort((left, right) =>
left.url.localeCompare(right.url)
);
const outputDir = path.join(workspaceRoot, 'coverage/e2e-v8/web');
mkdirSync(outputDir, { recursive: true });
writeFileSync(
path.join(outputDir, 'summary.json'),
`${JSON.stringify(
{
rawCoverageRoot: path.relative(workspaceRoot, rawCoverageRoot),
rawFiles: rawFiles.length,
scripts: summaries,
},
null,
4
)}\n`
);
writeFileSync(
path.join(outputDir, 'summary.md'),
`# Web E2E V8 Coverage
Raw coverage root: ${path.relative(workspaceRoot, rawCoverageRoot)}
Raw coverage files: ${rawFiles.length}
This experimental report is Chromium/V8 byte coverage from Playwright's browser
coverage API. Use it for local investigation; rely on Tier A unit coverage and
E2E semantic coverage for CI gates.
| Script URL | Used bytes | Source bytes | Byte coverage |
| --- | ---: | ---: | ---: |
${summaries
.map(
(entry) =>
`| ${entry.url || '_anonymous_'} | ${entry.usedBytes} | ${entry.sourceLength} | ${entry.pct}% |`
)
.join('\n') || '| _none_ | 0 | 0 | 0% |'}
`
);
console.log(`Wrote coverage/e2e-v8/web/summary.md from ${rawFiles.length} raw coverage files.`);