ci(coverage): fast check that every frontend file has a coverage baseline (#271)

The frontend coverage job only reports a missing baseline entry after its
full browser run. coverage:baseline-check compares the files the gate
measures (same discoverSources) with frontend-coverage-baseline.json in
milliseconds and also flags entries for files that no longer exist. npm test
runs the same check.
This commit is contained in:
Vito
2026-10-21 13:08:38 -07:00
committed by GitHub
parent 454ac12e1d
commit 6e9d8f81fd
4 changed files with 79 additions and 0 deletions
+7
View File
@@ -109,6 +109,13 @@ the baseline updater. Raise floors with `npm run --prefix tools/vendor
coverage:update`; it reruns the suites and cannot lower an existing floor.
Review baseline changes like code changes.
`npm run --prefix tools/vendor coverage:baseline-check` runs in well under a
second without a browser and fails, naming the files, when a measured
`web/js` file has no baseline entry or the baseline lists a file that no longer
exists. It uses the gate's own `discoverSources`, and `npm test` runs the same
check, so run it before pushing a new or deleted frontend file instead of
waiting for the `frontend coverage` job.
The `viewerjs` check runs on every PR, including backend-only changes. It is an
aggregate job that passes only when the `frontend coverage (chromium and touch)`
job and both `page workflows (firefox)` and `page workflows (webkit)` jobs
+33
View File
@@ -0,0 +1,33 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { baselineGaps, measuredSources, readBaseline } from '../../tools/vendor/coverage-baseline-check.mjs';
const floor = { lines: 80, branches: 80, functions: 80, statements: 80 };
test('baseline gaps name measured files without a baseline entry', () => {
const gaps = baselineGaps(['a.js', 'nested/new.mjs'], { 'a.js': floor });
assert.deepEqual(gaps.missing, ['nested/new.mjs']);
assert.deepEqual(gaps.stale, []);
});
test('baseline gaps name baseline entries whose file no longer exists', () => {
const gaps = baselineGaps(['a.js'], { 'a.js': floor, 'gone.js': floor });
assert.deepEqual(gaps.missing, []);
assert.deepEqual(gaps.stale, ['gone.js']);
});
test('baseline gaps are empty when files and baseline agree', () => {
assert.deepEqual(baselineGaps(['a.js'], { 'a.js': floor }), { missing: [], stale: [] });
});
test('the committed baseline covers exactly the measured frontend sources', () => {
const gaps = baselineGaps(measuredSources(), readBaseline());
assert.deepEqual(gaps.missing, [], `Missing baseline (run coverage:update): ${gaps.missing.join(', ')}`);
assert.deepEqual(gaps.stale, [], `Stale baseline entries (file removed): ${gaps.stale.join(', ')}`);
});
test('the real gate and this check select files through the same function', () => {
const gate = readFileSync(new URL('../../tools/vendor/frontend-coverage.mjs', import.meta.url), 'utf8');
assert.match(gate, /discoverSources\(sourceRoot\)/);
});
+38
View File
@@ -0,0 +1,38 @@
// Fast, browser-free check that frontend-coverage-baseline.json lists exactly the
// files the coverage gate measures. CI's `frontend coverage` job reports a missing
// entry only after ~20 minutes; this reports it in milliseconds.
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { repositoryRoot } from './vendor-viewerjs.mjs';
import { discoverSources } from './frontend-coverage.mjs';
const baselinePath = join(dirname(fileURLToPath(import.meta.url)), 'frontend-coverage-baseline.json');
export function measuredSources() {
return discoverSources(join(repositoryRoot, 'web/js'));
}
export function readBaseline() {
return JSON.parse(readFileSync(baselinePath, 'utf8'));
}
export function baselineGaps(files, baseline) {
const measured = new Set(files);
return {
missing: files.filter(file => !baseline[file]),
stale: Object.keys(baseline).filter(file => !measured.has(file)).sort(),
};
}
function main() {
const { missing, stale } = baselineGaps(measuredSources(), readBaseline());
if (!missing.length && !stale.length) {
console.log('Coverage baseline matches the measured frontend files.');
return;
}
if (missing.length) console.error(`Missing baseline for:\n ${missing.join('\n ')}\nRun: npm run --prefix tools/vendor coverage:update`);
if (stale.length) console.error(`Baseline lists files that no longer exist:\n ${stale.join('\n ')}\nRemove them from tools/vendor/frontend-coverage-baseline.json`);
process.exitCode = 1;
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main();
+1
View File
@@ -15,6 +15,7 @@
"test:comfyui": "playwright test --config comfyui.config.mjs",
"test:executions": "playwright test --config playwright.execution.config.mjs",
"test:coverage": "node frontend-coverage.mjs",
"coverage:baseline-check": "node coverage-baseline-check.mjs",
"coverage:update": "node frontend-coverage.mjs --update-baseline",
"test:compat": "playwright test --config pack-compat.config.mjs"
},