Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export interface BrowserInstanceConfiguration {
provider?: BrowserProviderOption;
}

function normalizeBrowserName(browserName: string): BrowserInstanceConfiguration {
export function normalizeBrowserName(browserName: string): BrowserInstanceConfiguration {
// Normalize browser names to match Vitest's expectations for headless but also supports karma's names
// e.g., 'ChromeHeadless' -> 'chrome', 'FirefoxHeadless' -> 'firefox'
// and 'Chrome' -> 'chrome', 'Firefox' -> 'firefox'.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { createBuildAssetsMiddleware } from '../../../../tools/vite/middlewares/
import { toPosixPath } from '../../../../utils/path';
import type { ResultFile } from '../../../application/results';
import type { NormalizedUnitTestBuilderOptions } from '../../options';
import { normalizeBrowserName } from './browser-provider';

interface ExistingRawSourceMap {
sources?: string[];
Expand Down Expand Up @@ -154,6 +155,9 @@ export async function createVitestConfigPlugin(
(browser || testConfig?.browser?.enabled) &&
(options.coverage.enabled || testConfig?.coverage?.enabled)
) {
// Validate that enabled browsers support V8 coverage
validateBrowserCoverage(browser, testConfig?.browser);

projectPlugins.unshift(createSourcemapSupportPlugin());
setupFiles.unshift('virtual:source-map-support');
}
Expand Down Expand Up @@ -418,6 +422,53 @@ function createSourcemapSupportPlugin(): VitestPlugins[0] {
};
}

interface CustomBrowserConfigOptions {
instances?: { browser: string }[];
name?: string;
}

/**
* Validates that all enabled browsers support V8 coverage when coverage is enabled.
* Throws an error if an unsupported browser is detected.
*/
function validateBrowserCoverage(
browser: BrowserConfigOptions | undefined,
testConfigBrowser: BrowserConfigOptions | undefined,
): void {
const browsersToCheck: string[] = [];

// 1. Check browsers passed by the Angular CLI options
const cliBrowser = browser as CustomBrowserConfigOptions | undefined;
if (cliBrowser?.instances) {
browsersToCheck.push(...cliBrowser.instances.map((i) => i.browser));
}

// 2. Check browsers defined in the user's vitest.config.ts
const userBrowser = testConfigBrowser as CustomBrowserConfigOptions | undefined;
if (userBrowser) {
if (userBrowser.instances) {
browsersToCheck.push(...userBrowser.instances.map((i) => i.browser));
}
if (userBrowser.name) {
browsersToCheck.push(userBrowser.name);
}
}

// Normalize and filter unsupported browsers
const unsupportedBrowsers = browsersToCheck
.map((b) => normalizeBrowserName(b).browser)
.filter((b) => !['chrome', 'chromium', 'edge'].includes(b));

if (unsupportedBrowsers.length > 0) {
throw new Error(
`Code coverage is enabled, but the following configured browsers do not support the V8 coverage provider: ` +
`${unsupportedBrowsers.join(', ')}. ` +
`V8 coverage is only supported on Chromium-based browsers (e.g., Chrome, Chromium, Edge). ` +
`Please disable coverage or remove the unsupported browsers.`,
);
}
}

async function generateCoverageOption(
optionsCoverage: NormalizedUnitTestBuilderOptions['coverage'],
configCoverage: VitestCoverageOption | undefined,
Expand Down
102 changes: 102 additions & 0 deletions tests/e2e/tests/vitest/browser-coverage-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import assert from 'node:assert/strict';
import path from 'node:path';
import { applyVitestBuilder } from '../../utils/vitest';
import { execAndCaptureError } from '../../utils/process';
import { installPackage } from '../../utils/packages';
import { writeFile } from '../../utils/fs';
import { stripVTControlCharacters } from 'node:util';
import { unlink } from 'node:fs/promises';

export default async function (): Promise<void> {
await applyVitestBuilder();

// Install necessary packages to pass the provider check
await installPackage('playwright@1');
await installPackage('@vitest/browser-playwright@4');
await installPackage('@vitest/coverage-v8@4');

// === Case 1: Browser configured via CLI option ===
const error1 = await execAndCaptureError('ng', [
'test',
'--no-watch',
'--coverage',
'--browsers',
'firefox',
]);
const output1 = stripVTControlCharacters(error1.message);
assert.match(
output1,
/Code coverage is enabled, but the following configured browsers do not support the V8 coverage provider: firefox/,
'Expected validation error for unsupported browser with coverage (CLI option).',
);

const configPath = 'vitest.config.ts';
const absoluteConfigPath = path.resolve(configPath);

try {
// === Case 2: Browser configured via vitest.config.ts (name) ===
await writeFile(
configPath,
`
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
browser: {
enabled: true,
name: 'firefox',
provider: 'playwright',
},
},
});
`,
);

const error2 = await execAndCaptureError('ng', [
'test',
'--no-watch',
'--coverage',
`--runner-config=${absoluteConfigPath}`,
]);
const output2 = stripVTControlCharacters(error2.message);
assert.match(
output2,
/Code coverage is enabled, but the following configured browsers do not support the V8 coverage provider: firefox/,
'Expected validation error for unsupported browser with coverage (config name).',
);

// === Case 3: Browser configured via vitest.config.ts (instances) ===
await writeFile(
configPath,
`
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
browser: {
enabled: true,
provider: 'playwright',
instances: [{ browser: 'firefox' }],
} as any,
},
});
`,
);

const error3 = await execAndCaptureError('ng', [
'test',
'--no-watch',
'--coverage',
`--runner-config=${absoluteConfigPath}`,
]);
const output3 = stripVTControlCharacters(error3.message);
assert.match(
output3,
/Code coverage is enabled, but the following configured browsers do not support the V8 coverage provider: firefox/,
'Expected validation error for unsupported browser with coverage (config instances).',
);
} finally {
// Clean up the config file so it doesn't affect other tests
try {
await unlink(configPath);
} catch {}
}
}
Loading