mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-19 07:36:52 +00:00
3923f1978f
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
3.7 KiB
TypeScript
102 lines
3.7 KiB
TypeScript
import type { BrowserContext, Page, TestInfo } from '@playwright/test';
|
|
import { CoverageReport } from 'monocart-coverage-reports';
|
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
import {
|
|
BY_SPEC_DIR,
|
|
coverageOptions,
|
|
COVERAGE_ENABLED,
|
|
slugify,
|
|
specId,
|
|
} from '../coverage-options';
|
|
|
|
/**
|
|
* Browser-native V8 coverage collection (Chromium `page.coverage`), replacing
|
|
* the previous istanbul-instrumented build + Currents istanbul fixture.
|
|
*
|
|
* Wraps the BrowserContext: starts JS coverage on every page at creation
|
|
* (before navigation, so the initial bundle load is captured) and, on test
|
|
* teardown, hands each page's V8 coverage to monocart-coverage-reports.
|
|
*
|
|
* Coverage is accumulated TWICE: into the shared outputDir (the full shard
|
|
* report, frontend + backend) and into a PER-SPEC dir keyed by the spec file
|
|
* (`.by-spec/<slug>/`, with a `.spec` marker naming the spec). The per-spec
|
|
* data is what lets the impact map say "this spec exercised this function" for
|
|
* test selection. The extra cost is a second `add()` of data already in memory.
|
|
*
|
|
* No-op unless COVERAGE_ENABLED — normal runs pay nothing.
|
|
*/
|
|
export const v8CoverageFixtures = {
|
|
context: async (
|
|
{ context }: { context: BrowserContext },
|
|
use: (context: BrowserContext) => Promise<void>,
|
|
testInfo: TestInfo,
|
|
) => {
|
|
if (!COVERAGE_ENABLED) {
|
|
await use(context);
|
|
return;
|
|
}
|
|
|
|
const tracked = new Set<Page>();
|
|
const startCoverage = async (page: Page) => {
|
|
try {
|
|
await page.coverage.startJSCoverage({ resetOnNavigation: false });
|
|
tracked.add(page);
|
|
} catch {
|
|
// Non-Chromium browser or coverage unavailable — skip silently.
|
|
}
|
|
};
|
|
|
|
context.on('page', (page) => {
|
|
startCoverage(page).catch(() => {});
|
|
});
|
|
await Promise.all(context.pages().map(startCoverage));
|
|
|
|
await use(context);
|
|
|
|
const sharedReport = new CoverageReport(coverageOptions);
|
|
// Capture this test's RAW page.coverage for the per-spec map, cloning each
|
|
// entry — MCR's add() mutates input in place, so the same object can't be
|
|
// fed to two reports. The emitter does the MCR work later from this raw.
|
|
const perSpecRaw: unknown[] = [];
|
|
for (const page of tracked) {
|
|
if (page.isClosed()) continue;
|
|
try {
|
|
const coverage = await page.coverage.stopJSCoverage();
|
|
if (coverage?.length) {
|
|
perSpecRaw.push(...coverage.map((entry) => structuredClone(entry)));
|
|
await sharedReport.add(coverage);
|
|
}
|
|
} catch {
|
|
// Page closed before collection — ignore.
|
|
}
|
|
}
|
|
if (perSpecRaw.length) {
|
|
const spec = specId(testInfo);
|
|
const specDir = join(BY_SPEC_DIR, slugify(spec));
|
|
mkdirSync(specDir, { recursive: true });
|
|
// Unique per test so multiple tests in one spec file accumulate (don't clobber).
|
|
try {
|
|
writeFileSync(
|
|
join(specDir, `raw-${slugify(testInfo.testId)}.json`),
|
|
JSON.stringify(perSpecRaw),
|
|
);
|
|
writeFileSync(join(specDir, '.spec'), spec);
|
|
} catch (error) {
|
|
// V8 coverage entries include full script source text. Tests that navigate
|
|
// extensively (e.g. signout + signin in one test with resetOnNavigation:false)
|
|
// accumulate enough script entries that JSON.stringify hits V8's ~536MB string
|
|
// limit (RangeError: Invalid string length). The shard-level sharedReport
|
|
// already received the data above, so aggregate coverage is unaffected — only
|
|
// this test's per-spec impact map entry is dropped. Re-throw anything else: a
|
|
// real write failure (disk full, permissions) must not silently lose attribution.
|
|
if (!(error instanceof RangeError)) throw error;
|
|
console.warn(
|
|
`[coverage] per-spec raw write skipped for ${testInfo.titlePath.join(' > ')}: ${error.message}`,
|
|
);
|
|
}
|
|
}
|
|
},
|
|
};
|