Skip to content

Commit 5371709

Browse files
committed
fix(@angular/build): scope CHROME_BIN executable path to individual playwright instances
Previously, if CHROME_BIN was set in the environment and a user ran tests targeting the Playwright provider, the path was applied to the global Playwright launch options. This caused tests to crash if a user requested non-Chromium browsers (like Firefox) alongside Chromium, because Playwright would incorrectly attempt to launch the Chrome binary for the Firefox instance. This commit updates the browser configuration to map instances before providers are initialized, and selectively injects `launchOptions: { executablePath: process.env.CHROME_BIN }` at the individual instance level for chrome and chromium only. This restores parity where users can maintain CHROME_BIN variables while safely invoking alternative browsers.
1 parent 685ebae commit 5371709

File tree

2 files changed

+94
-14
lines changed

2 files changed

+94
-14
lines changed

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

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ export async function setupBrowserConfiguration(
7979
);
8080
}
8181

82+
const instances = browsers.map(
83+
(b) =>
84+
normalizeBrowserName(b) as {
85+
browser: string;
86+
headless: boolean;
87+
provider?: import('vitest/node').BrowserProviderOption;
88+
},
89+
);
90+
8291
let provider: import('vitest/node').BrowserProviderOption | undefined;
8392
if (providerName) {
8493
const providerPackage = `@vitest/browser-${providerName}`;
@@ -90,17 +99,25 @@ export async function setupBrowserConfiguration(
9099
if (typeof providerFactory === 'function') {
91100
if (providerName === 'playwright') {
92101
const executablePath = process.env['CHROME_BIN'];
93-
provider = providerFactory({
94-
launchOptions: executablePath
95-
? {
96-
executablePath,
97-
}
98-
: undefined,
102+
const baseOptions = {
99103
contextOptions: {
100104
// Enables `prefer-color-scheme` for Vitest browser instead of `light`
101105
colorScheme: null,
102106
},
103-
});
107+
};
108+
109+
provider = providerFactory(baseOptions);
110+
111+
if (executablePath) {
112+
for (const instance of instances) {
113+
if (instance.browser === 'chrome' || instance.browser === 'chromium') {
114+
instance.provider = providerFactory({
115+
...baseOptions,
116+
launchOptions: { executablePath },
117+
});
118+
}
119+
}
120+
}
104121
} else {
105122
provider = providerFactory();
106123
}
@@ -133,7 +150,6 @@ export async function setupBrowserConfiguration(
133150
}
134151

135152
const isCI = !!process.env['CI'];
136-
const instances = browsers.map(normalizeBrowserName);
137153
const messages: string[] = [];
138154

139155
if (providerName === 'preview') {

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

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ describe('setupBrowserConfiguration', () => {
4747

4848
expect(browser?.enabled).toBeTrue();
4949
expect(browser?.instances).toEqual([
50-
{ browser: 'chrome', headless: true },
51-
{ browser: 'firefox', headless: false },
50+
jasmine.objectContaining({ browser: 'chrome', headless: true }),
51+
jasmine.objectContaining({ browser: 'firefox', headless: false }),
5252
]);
5353
});
5454

@@ -66,8 +66,8 @@ describe('setupBrowserConfiguration', () => {
6666
);
6767

6868
expect(browser?.instances).toEqual([
69-
{ browser: 'chrome', headless: true },
70-
{ browser: 'firefox', headless: true },
69+
jasmine.objectContaining({ browser: 'chrome', headless: true }),
70+
jasmine.objectContaining({ browser: 'firefox', headless: true }),
7171
]);
7272
} finally {
7373
if (originalCI === undefined) {
@@ -196,8 +196,8 @@ describe('setupBrowserConfiguration', () => {
196196
);
197197

198198
expect(browser?.instances).toEqual([
199-
{ browser: 'chrome', headless: true },
200-
{ browser: 'firefox', headless: true },
199+
jasmine.objectContaining({ browser: 'chrome', headless: true }),
200+
jasmine.objectContaining({ browser: 'firefox', headless: true }),
201201
]);
202202
expect(messages).toEqual([]);
203203
});
@@ -215,4 +215,68 @@ describe('setupBrowserConfiguration', () => {
215215
'The "headless" option is unnecessary as all browsers are already configured to run in headless mode.',
216216
]);
217217
});
218+
219+
describe('CHROME_BIN usage', () => {
220+
let originalChromeBin: string | undefined;
221+
222+
beforeEach(() => {
223+
originalChromeBin = process.env['CHROME_BIN'];
224+
process.env['CHROME_BIN'] = '/custom/path/to/chrome';
225+
});
226+
227+
afterEach(() => {
228+
if (originalChromeBin === undefined) {
229+
delete process.env['CHROME_BIN'];
230+
} else {
231+
process.env['CHROME_BIN'] = originalChromeBin;
232+
}
233+
});
234+
235+
it('should set executablePath on the individual chrome instance', async () => {
236+
const { browser } = await setupBrowserConfiguration(
237+
['ChromeHeadless', 'Chromium'],
238+
undefined,
239+
false,
240+
workspaceRoot,
241+
undefined,
242+
);
243+
244+
// Verify the global provider does NOT have executablePath
245+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
246+
expect((browser?.provider as any)?.options?.launchOptions?.executablePath).toBeUndefined();
247+
248+
// Verify the individual instances have executablePath
249+
expect(
250+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
251+
(browser?.instances?.[0]?.provider as any)?.options?.launchOptions?.executablePath,
252+
).toBe('/custom/path/to/chrome');
253+
expect(
254+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
255+
(browser?.instances?.[1]?.provider as any)?.options?.launchOptions?.executablePath,
256+
).toBe('/custom/path/to/chrome');
257+
});
258+
259+
it('should set executablePath for chrome instances but not for others when mixed browsers are requested', async () => {
260+
const { browser } = await setupBrowserConfiguration(
261+
['ChromeHeadless', 'Firefox'],
262+
undefined,
263+
false,
264+
workspaceRoot,
265+
undefined,
266+
);
267+
268+
// Verify the global provider does NOT have executablePath
269+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
270+
expect((browser?.provider as any)?.options?.launchOptions?.executablePath).toBeUndefined();
271+
272+
// Verify chrome gets it
273+
expect(
274+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
275+
(browser?.instances?.[0]?.provider as any)?.options?.launchOptions?.executablePath,
276+
).toBe('/custom/path/to/chrome');
277+
278+
// Verify firefox does not
279+
expect(browser?.instances?.[1]?.provider).toBeUndefined();
280+
});
281+
});
218282
});

0 commit comments

Comments
 (0)