Skip to content
Merged
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 @@ -7,7 +7,11 @@
*/

import { createRequire } from 'node:module';
import type { BrowserBuiltinProvider, BrowserConfigOptions } from 'vitest/node';
import type {
BrowserBuiltinProvider,
BrowserConfigOptions,
BrowserProviderOption,
} from 'vitest/node';
import { assertIsError } from '../../../../utils/error';

export interface BrowserConfiguration {
Expand Down Expand Up @@ -40,7 +44,7 @@ function findBrowserProvider(
export interface BrowserInstanceConfiguration {
browser: string;
headless: boolean;
provider?: import('vitest/node').BrowserProviderOption;
provider?: BrowserProviderOption;
}

function normalizeBrowserName(browserName: string): BrowserInstanceConfiguration {
Expand All @@ -56,6 +60,67 @@ function normalizeBrowserName(browserName: string): BrowserInstanceConfiguration
};
}

/**
* Mutates the provided browser instances to apply standard headless execution
* constraints based on the chosen provider, user options, and CI environment presence.
*
* @param instances The normalized browser instances to mutate.
* @param providerName The identifier for the chosen Vitest browser provider.
* @param headless The user-provided headless configuration option.
* @param isCI Whether the current environment is running in CI.
* @returns An array of informational messages generated during evaluation.
*/
export function applyHeadlessConfiguration(
instances: BrowserInstanceConfiguration[],
providerName: BrowserBuiltinProvider | undefined,
headless: boolean | undefined,
isCI: boolean,
): string[] {
const messages: string[] = [];

if (providerName === 'preview') {
instances.forEach((instance) => {
// Preview mode only supports headed execution
instance.headless = false;
});

if (headless) {
messages.push('The "headless" option is ignored when using the "preview" provider.');
}
} else if (headless !== undefined) {
if (headless) {
const allHeadlessByDefault = isCI || instances.every((i) => i.headless);
if (allHeadlessByDefault) {
messages.push(
'The "headless" option is unnecessary as all browsers are already configured to run in headless mode.',
);
}
}

instances.forEach((instance) => {
instance.headless = headless;
});
} else if (isCI) {
instances.forEach((instance) => {
instance.headless = true;
});
}

return messages;
}

/**
* Resolves and configures the Vitest browser provider for the unit test builder.
* Dynamically discovers and imports the necessary provider (Playwright, WebdriverIO, or Preview),
* maps the requested browser instances, and applies environment-specific execution logic.
*
* @param browsers An array of requested browser names (e.g., 'chrome', 'firefox').
* @param headless User-provided configuration for headless execution.
* @param debug Whether the builder is running in watch or debug mode.
* @param projectSourceRoot The root directory of the project being tested for resolving installed packages.
* @param viewport Optional viewport dimensions to apply to the launched browser instances.
* @returns A fully resolved Vitest browser configuration object alongside any generated warning or error messages.
*/
export async function setupBrowserConfiguration(
browsers: string[] | undefined,
headless: boolean | undefined,
Expand Down Expand Up @@ -149,35 +214,7 @@ export async function setupBrowserConfiguration(
}

const isCI = !!process.env['CI'];
const messages: string[] = [];

if (providerName === 'preview') {
instances.forEach((instance) => {
// Preview mode only supports headed execution
instance.headless = false;
});

if (headless) {
messages.push('The "headless" option is ignored when using the "preview" provider.');
}
} else if (headless !== undefined) {
if (headless) {
const allHeadlessByDefault = isCI || instances.every((i) => i.headless);
if (allHeadlessByDefault) {
messages.push(
'The "headless" option is unnecessary as all browsers are already configured to run in headless mode.',
);
}
}

instances.forEach((instance) => {
instance.headless = headless;
});
} else if (isCI) {
instances.forEach((instance) => {
instance.headless = true;
});
}
const messages = applyHeadlessConfiguration(instances, providerName, headless, isCI);

const browser = {
enabled: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { setupBrowserConfiguration } from './browser-provider';
import { applyHeadlessConfiguration, setupBrowserConfiguration } from './browser-provider';

describe('setupBrowserConfiguration', () => {
let workspaceRoot: string;
Expand Down Expand Up @@ -279,4 +279,42 @@ describe('setupBrowserConfiguration', () => {
expect(browser?.instances?.[1]?.provider).toBeUndefined();
});
});

describe('applyHeadlessConfiguration', () => {
it('should set headless false and issue warning when using preview provider with headless true', () => {
const instances = [{ browser: 'chrome', headless: true }];
const messages = applyHeadlessConfiguration(instances, 'preview', true, false);

expect(instances[0].headless).toBeFalse();
expect(messages).toEqual([
'The "headless" option is ignored when using the "preview" provider.',
]);
});

it('should force headless mode when headless option is true', () => {
const instances = [{ browser: 'chrome', headless: false }];
const messages = applyHeadlessConfiguration(instances, 'playwright', true, false);

expect(instances[0].headless).toBeTrue();
expect(messages).toEqual([]);
});

it('should return information message when headless option is redundant', () => {
const instances = [{ browser: 'chrome', headless: true }];
const messages = applyHeadlessConfiguration(instances, 'playwright', true, false);

expect(instances[0].headless).toBeTrue();
expect(messages).toEqual([
'The "headless" option is unnecessary as all browsers are already configured to run in headless mode.',
]);
});

it('should force headless mode in CI environment when headless is undefined', () => {
const instances = [{ browser: 'chrome', headless: false }];
const messages = applyHeadlessConfiguration(instances, 'playwright', undefined, true);

expect(instances[0].headless).toBeTrue();
expect(messages).toEqual([]);
});
});
});
Loading