mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 07:59:42 +02:00
build: add a browser-test exclusion check and a pre-push static-check hook
npm run check:browser-excludes finds tests that import a browser driver and asks `vitest list` whether the CI config still collects them; wired into CI. npm install now also installs a marker-owned pre-push hook that runs the static CI checks (~15s). Skip with CODEMAN_SKIP_PREPUSH=1; hand-written hooks are left alone.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @fileoverview scripts/check-browser-test-excludes.mjs: the detection side (which test
|
||||
* files need a real browser) and the leak computation. The exclusion side is vitest's own
|
||||
* `vitest list`, which `npm run check:browser-excludes` exercises for real in CI.
|
||||
*/
|
||||
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve } from 'node:path';
|
||||
import {
|
||||
findBrowserTests,
|
||||
findLeaks,
|
||||
importsBrowserDriver,
|
||||
parseVitestFileList,
|
||||
} from '../scripts/check-browser-test-excludes.mjs';
|
||||
import { BROWSER_TEST_GLOBS } from '../config/test-suites';
|
||||
|
||||
const repoRoot = resolve(import.meta.dirname, '..');
|
||||
|
||||
// Fixture sources are assembled from the module name at runtime, so THIS file never contains
|
||||
// a literal driver import and is not itself flagged by the checker it tests.
|
||||
const fromImport = (mod: string) => `import { chromium, type Browser } from '${mod}';\n`;
|
||||
|
||||
describe('importsBrowserDriver', () => {
|
||||
it.each([
|
||||
fromImport('playwright'),
|
||||
fromImport('playwright-core').replace(/'/g, '"'),
|
||||
fromImport('@playwright/test'),
|
||||
fromImport('puppeteer'),
|
||||
`import type { Page } from '${'playwright'}';`,
|
||||
`const { chromium } = require('${'playwright'}');`,
|
||||
`const pw = await import('${'playwright'}');`,
|
||||
])('flags %s', (src) => {
|
||||
expect(importsBrowserDriver(src)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"import { describe } from 'vitest';",
|
||||
"// needs ms-playwright's cache dir\nconst dir = '.cache/ms-playwright';",
|
||||
fromImport('./playwright-helpers'),
|
||||
fromImport('playwright-extra-thing'),
|
||||
])('ignores %s', (src) => {
|
||||
expect(importsBrowserDriver(src)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBrowserTests (fixture tree)', () => {
|
||||
let root: string;
|
||||
beforeAll(() => {
|
||||
root = mkdtempSync(join(tmpdir(), 'codeman-browser-excludes-'));
|
||||
const put = (rel: string, src: string) => {
|
||||
mkdirSync(join(root, rel, '..'), { recursive: true });
|
||||
writeFileSync(join(root, rel), src);
|
||||
};
|
||||
put('test/unit.test.ts', "import { it } from 'vitest';\n");
|
||||
put('test/legacy-name.test.ts', fromImport('playwright'));
|
||||
put('test/new.browser.test.ts', fromImport('playwright'));
|
||||
put('test/nested/deep.test.ts', fromImport('puppeteer'));
|
||||
put('test/helpers/browser.ts', fromImport('playwright')); // not a test file
|
||||
});
|
||||
afterAll(() => rmSync(root, { recursive: true, force: true }));
|
||||
|
||||
it('finds driver imports by content, recursively, as sorted repo-relative paths', () => {
|
||||
expect(findBrowserTests(root)).toEqual([
|
||||
'test/legacy-name.test.ts',
|
||||
'test/nested/deep.test.ts',
|
||||
'test/new.browser.test.ts',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseVitestFileList + findLeaks', () => {
|
||||
it('keeps only test paths and normalizes a leading ./', () => {
|
||||
const out = '\n./test/a.test.ts\ntest/b.test.ts\nsome banner line\n test/c.test.ts \n';
|
||||
expect([...parseVitestFileList(out)].sort()).toEqual(['test/a.test.ts', 'test/b.test.ts', 'test/c.test.ts']);
|
||||
});
|
||||
|
||||
it('reports exactly the browser tests the CI set still collects', () => {
|
||||
const ci = new Set(['test/unit.test.ts', 'test/legacy-name.test.ts']);
|
||||
expect(findLeaks(['test/legacy-name.test.ts', 'test/new.browser.test.ts'], ci)).toEqual([
|
||||
'test/legacy-name.test.ts',
|
||||
]);
|
||||
expect(findLeaks(['test/new.browser.test.ts'], ci)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('against this repository', () => {
|
||||
it('detects every file already listed in BROWSER_TEST_GLOBS', () => {
|
||||
// If detection stopped recognising a known browser test, the checker would go blind to
|
||||
// exactly the class of file it exists for.
|
||||
const detected = new Set(findBrowserTests(repoRoot));
|
||||
const literals = BROWSER_TEST_GLOBS.filter((g) => !/[*?[{]/.test(g));
|
||||
expect(literals.length).toBeGreaterThan(0);
|
||||
for (const file of literals) expect(detected, file).toContain(file);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user