Skip to content

Commit 45eada1

Browse files
committed
feat(@angular/build): add headless option to unit-test builder
This adds a new 'headless' option to the unit-test builder. When set to true, it forces all configured browsers to run in headless mode. This option allows users to force headless execution in non-CI environments (where it is already the default) or to explicitly control the mode regardless of the browser name. Informational messages are now logged when the option is redundant (e.g., all browsers are already headless), ignored (e.g., using the preview provider), or irrelevant (e.g., no browsers configured). The 'karma' runner does not support this option and will log a warning if it is used.
1 parent fe4c310 commit 45eada1

File tree

5 files changed

+47
-1
lines changed

5 files changed

+47
-1
lines changed

packages/angular/build/src/builders/unit-test/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export async function normalizeOptions(
9696
exclude: options.exclude,
9797
filter,
9898
runnerName: runner ?? Runner.Vitest,
99+
headless: options.headless,
99100
coverage: {
100101
enabled: options.coverage,
101102
exclude: options.coverageExclude,

packages/angular/build/src/builders/unit-test/runners/karma/executor.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ export class KarmaExecutor implements TestExecutor {
4747
);
4848
}
4949

50+
if (unitTestOptions.headless !== undefined) {
51+
context.logger.warn(
52+
'The "karma" test runner does not support the "headless" option. The option will be ignored.',
53+
);
54+
}
55+
5056
const buildTargetOptions = (await context.validateOptions(
5157
await context.getTargetOptions(unitTestOptions.buildTarget),
5258
await context.getBuilderNameForTarget(unitTestOptions.buildTarget),

packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { assertIsError } from '../../../../utils/error';
1313
export interface BrowserConfiguration {
1414
browser?: BrowserConfigOptions;
1515
errors?: string[];
16+
messages?: string[];
1617
}
1718

1819
function findBrowserProvider(
@@ -51,11 +52,18 @@ function normalizeBrowserName(browserName: string): { browser: string; headless:
5152

5253
export async function setupBrowserConfiguration(
5354
browsers: string[] | undefined,
55+
headless: boolean | undefined,
5456
debug: boolean,
5557
projectSourceRoot: string,
5658
viewport: { width: number; height: number } | undefined,
5759
): Promise<BrowserConfiguration> {
5860
if (browsers === undefined) {
61+
if (headless !== undefined) {
62+
return {
63+
messages: ['The "headless" option is ignored when no browsers are configured.'],
64+
};
65+
}
66+
5967
return {};
6068
}
6169

@@ -125,10 +133,30 @@ export async function setupBrowserConfiguration(
125133

126134
const isCI = !!process.env['CI'];
127135
const instances = browsers.map(normalizeBrowserName);
136+
const messages: string[] = [];
137+
128138
if (providerName === 'preview') {
129139
instances.forEach((instance) => {
140+
// Preview mode only supports headed execution
130141
instance.headless = false;
131142
});
143+
144+
if (headless) {
145+
messages.push('The "headless" option is ignored when using the "preview" provider.');
146+
}
147+
} else if (headless !== undefined) {
148+
if (headless) {
149+
const allHeadlessByDefault = isCI || instances.every((i) => i.headless);
150+
if (allHeadlessByDefault) {
151+
messages.push(
152+
'The "headless" option is unnecessary as all browsers are already configured to run in headless mode.',
153+
);
154+
}
155+
}
156+
157+
instances.forEach((instance) => {
158+
instance.headless = headless;
159+
});
132160
} else if (isCI) {
133161
instances.forEach((instance) => {
134162
instance.headless = true;
@@ -143,5 +171,5 @@ export async function setupBrowserConfiguration(
143171
instances,
144172
} satisfies BrowserConfigOptions;
145173

146-
return { browser };
174+
return { browser, messages };
147175
}

packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ export class VitestExecutor implements TestExecutor {
192192
// Setup vitest browser options if configured
193193
const browserOptions = await setupBrowserConfiguration(
194194
browsers,
195+
this.options.headless,
195196
debug,
196197
projectSourceRoot,
197198
browserViewport,
@@ -200,6 +201,12 @@ export class VitestExecutor implements TestExecutor {
200201
throw new Error(browserOptions.errors.join('\n'));
201202
}
202203

204+
if (browserOptions.messages?.length) {
205+
for (const message of browserOptions.messages) {
206+
this.logger.info(message);
207+
}
208+
}
209+
203210
assert(
204211
this.buildResultFiles.size > 0,
205212
'buildResult must be available before initializing vitest',

packages/angular/build/src/builders/unit-test/schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@
6060
"type": "boolean",
6161
"description": "Enables watch mode, which re-runs tests when source files change. Defaults to `true` in TTY environments and `false` otherwise."
6262
},
63+
"headless": {
64+
"type": "boolean",
65+
"description": "Forces all configured browsers to run in headless mode. When using the Vitest runner, this option is ignored if no browsers are configured. The Karma runner does not support this option."
66+
},
6367
"debug": {
6468
"type": "boolean",
6569
"description": "Enables debugging mode for tests, allowing the use of the Node Inspector.",

0 commit comments

Comments
 (0)