Skip to content

Commit 40fe537

Browse files
authored
✨ Add configurable browser type for static-site and storybook (#197)
## Summary - Adds `browser.type` config option supporting `chromium`, `firefox`, and `webkit` - Adds `--browser <type>` CLI flag for both `vizzly static-site` and `vizzly storybook` - Improves error messages when Playwright browsers aren't installed with helpful CI setup instructions ## Motivation When running in CI, users were getting a confusing Playwright error when browsers weren't installed. Now they get a clear message with exactly what command to run: ``` Browser "chromium" is not installed. To fix this, run: npx playwright install chromium For CI environments, add this step before running Vizzly: npx playwright install chromium --with-deps You can cache the browser installation in CI for faster builds. See: https://playwright.dev/docs/ci ``` This also gives users control over: - Which browser to use for visual testing - Caching browser installations in CI - Browser version management ## Usage **Config file:** ```js export default { staticSite: { browser: { type: 'firefox', // chromium (default), firefox, or webkit } } } ``` **CLI:** ```bash npx vizzly static-site ./dist --browser firefox ``` ## Test plan - [x] All existing static-site tests pass (178 tests) - [x] All existing storybook tests pass (157 tests) - [x] Added tests for browser type validation - [x] Added tests for CLI option parsing
1 parent 9a1adac commit 40fe537

10 files changed

Lines changed: 249 additions & 101 deletions

File tree

clients/static-site/src/browser.js

Lines changed: 90 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,105 @@
33
* Core functions for launching and managing browsers
44
*/
55

6-
import { chromium } from 'playwright-core';
6+
import { chromium, firefox, webkit } from 'playwright-core';
7+
8+
let browsers = { chromium, firefox, webkit };
79

810
/**
911
* Launch a Playwright browser instance
1012
* @param {Object} options - Browser launch options
13+
* @param {'chromium' | 'firefox' | 'webkit'} [options.type='chromium'] - Browser type
1114
* @param {boolean} [options.headless=true] - Run in headless mode
1215
* @param {Array<string>} [options.args=[]] - Additional browser arguments
1316
* @returns {Promise<Object>} Browser instance
1417
*/
1518
export async function launchBrowser(options = {}) {
16-
let { headless = true, args = [] } = options;
17-
18-
let browser = await chromium.launch({
19-
headless,
20-
args: [
21-
// Required for running in containers/CI
22-
'--no-sandbox',
23-
'--disable-setuid-sandbox',
24-
25-
// Reduce memory usage
26-
'--disable-dev-shm-usage',
27-
28-
// Disable unnecessary features
29-
'--disable-extensions',
30-
'--disable-background-networking',
31-
'--disable-background-timer-throttling',
32-
'--disable-backgrounding-occluded-windows',
33-
'--disable-breakpad',
34-
'--disable-component-update',
35-
'--disable-default-apps',
36-
'--disable-hang-monitor',
37-
'--disable-ipc-flooding-protection',
38-
'--disable-popup-blocking',
39-
'--disable-prompt-on-repost',
40-
'--disable-renderer-backgrounding',
41-
'--disable-sync',
42-
43-
// Disable features via --disable-features (modern approach)
44-
'--disable-features=Translate,OptimizationHints,MediaRouter',
45-
46-
// Reduce resource usage
47-
'--metrics-recording-only',
48-
'--no-first-run',
49-
50-
// Screenshot consistency
51-
'--hide-scrollbars',
52-
'--mute-audio',
53-
'--force-color-profile=srgb',
54-
55-
// Memory optimizations
56-
'--js-flags=--max-old-space-size=512',
57-
58-
// User-provided args
59-
...args,
60-
],
61-
});
62-
63-
return browser;
19+
let { type = 'chromium', headless = true, args = [] } = options;
20+
21+
let browserType = browsers[type];
22+
if (!browserType) {
23+
throw new Error(
24+
`Unknown browser type: ${type}. Supported browsers: chromium, firefox, webkit`
25+
);
26+
}
27+
28+
// Chromium-specific args for CI/containers and screenshot consistency
29+
let launchArgs =
30+
type === 'chromium'
31+
? [
32+
// Required for running in containers/CI
33+
'--no-sandbox',
34+
'--disable-setuid-sandbox',
35+
36+
// Reduce memory usage
37+
'--disable-dev-shm-usage',
38+
39+
// Disable unnecessary features
40+
'--disable-extensions',
41+
'--disable-background-networking',
42+
'--disable-background-timer-throttling',
43+
'--disable-backgrounding-occluded-windows',
44+
'--disable-breakpad',
45+
'--disable-component-update',
46+
'--disable-default-apps',
47+
'--disable-hang-monitor',
48+
'--disable-ipc-flooding-protection',
49+
'--disable-popup-blocking',
50+
'--disable-prompt-on-repost',
51+
'--disable-renderer-backgrounding',
52+
'--disable-sync',
53+
54+
// Disable features via --disable-features (modern approach)
55+
'--disable-features=Translate,OptimizationHints,MediaRouter',
56+
57+
// Reduce resource usage
58+
'--metrics-recording-only',
59+
'--no-first-run',
60+
61+
// Screenshot consistency
62+
'--hide-scrollbars',
63+
'--mute-audio',
64+
'--force-color-profile=srgb',
65+
66+
// Memory optimizations
67+
'--js-flags=--max-old-space-size=512',
68+
69+
// User-provided args
70+
...args,
71+
]
72+
: args;
73+
74+
try {
75+
let browser = await browserType.launch({
76+
headless,
77+
args: launchArgs,
78+
});
79+
80+
return browser;
81+
} catch (error) {
82+
// Check if this is a missing browser error
83+
if (
84+
error.message.includes("Executable doesn't exist") ||
85+
error.message.includes('browserType.launch')
86+
) {
87+
let installCmd =
88+
type === 'chromium'
89+
? 'npx playwright install chromium'
90+
: `npx playwright install ${type}`;
91+
92+
throw new Error(
93+
`Browser "${type}" is not installed.\n\n` +
94+
`To fix this, run:\n` +
95+
` ${installCmd}\n\n` +
96+
`For CI environments, add this step before running Vizzly:\n` +
97+
` ${installCmd} --with-deps\n\n` +
98+
`You can cache the browser installation in CI for faster builds.\n` +
99+
`See: https://playwright.dev/docs/ci`
100+
);
101+
}
102+
103+
throw error;
104+
}
64105
}
65106

66107
/**

clients/static-site/src/config-schema.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ let viewportSchema = z.object({
3434
* Browser configuration schema
3535
*/
3636
let browserSchema = z.object({
37+
type: z.enum(['chromium', 'firefox', 'webkit']).default('chromium'),
3738
headless: z.boolean().default(true),
3839
args: z.array(z.string()).default([]),
3940
});
@@ -90,6 +91,7 @@ export let staticSiteConfigSchema = z
9091
.array(viewportSchema)
9192
.default([{ name: 'default', width: 1920, height: 1080 }]),
9293
browser: browserSchema.default({
94+
type: 'chromium',
9395
headless: true,
9496
args: [],
9597
}),
@@ -111,7 +113,7 @@ export let staticSiteConfigSchema = z
111113
})
112114
.default({
113115
viewports: [{ name: 'default', width: 1920, height: 1080 }],
114-
browser: { headless: true, args: [] },
116+
browser: { type: 'chromium', headless: true, args: [] },
115117
screenshot: { fullPage: false, omitBackground: false, timeout: 45_000 },
116118
concurrency: getDefaultConcurrency(),
117119
pageDiscovery: {

clients/static-site/src/config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export let defaultConfig = {
1515
buildPath: null,
1616
viewports: [{ name: 'default', width: 1920, height: 1080 }],
1717
browser: {
18+
type: 'chromium',
1819
headless: true,
1920
args: [],
2021
},
@@ -60,6 +61,10 @@ export function parseCliOptions(options) {
6061
config.exclude = options.exclude;
6162
}
6263

64+
if (options.browser) {
65+
config.browser = { ...config.browser, type: options.browser };
66+
}
67+
6368
if (options.headless !== undefined) {
6469
config.browser = { ...config.browser, headless: options.headless };
6570
}

clients/static-site/src/plugin.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default {
2020
{ name: 'desktop', width: 1920, height: 1080 },
2121
],
2222
browser: {
23+
type: 'chromium',
2324
headless: true,
2425
args: [],
2526
},
@@ -63,7 +64,11 @@ export default {
6364
)
6465
.option('--include <pattern>', 'Include page pattern (glob)')
6566
.option('--exclude <pattern>', 'Exclude page pattern (glob)')
66-
.option('--browser-args <args>', 'Additional Puppeteer browser arguments')
67+
.option(
68+
'--browser <type>',
69+
'Browser to use: chromium, firefox, webkit (default: chromium)'
70+
)
71+
.option('--browser-args <args>', 'Additional browser arguments')
6772
.option('--headless', 'Run browser in headless mode')
6873
.option('--full-page', 'Capture full page screenshots')
6974
.option(

clients/static-site/tests/config-schema.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,36 @@ describe('config-schema', () => {
106106
assert.deepStrictEqual(validated.browser.args, ['--no-sandbox']);
107107
});
108108

109+
it('validates browser type', () => {
110+
let config = {
111+
browser: {
112+
type: 'firefox',
113+
},
114+
};
115+
116+
let validated = validateStaticSiteConfig(config);
117+
118+
assert.strictEqual(validated.browser.type, 'firefox');
119+
});
120+
121+
it('defaults browser type to chromium', () => {
122+
let config = {};
123+
124+
let validated = validateStaticSiteConfig(config);
125+
126+
assert.strictEqual(validated.browser.type, 'chromium');
127+
});
128+
129+
it('rejects invalid browser type', () => {
130+
let config = {
131+
browser: {
132+
type: 'invalid-browser',
133+
},
134+
};
135+
136+
assert.throws(() => validateStaticSiteConfig(config));
137+
});
138+
109139
it('validates screenshot config', () => {
110140
let config = {
111141
screenshot: {

clients/static-site/tests/config.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ describe('config', () => {
5454
]);
5555
});
5656

57+
it('parses browser type option', () => {
58+
let options = { browser: 'firefox' };
59+
let config = parseCliOptions(options);
60+
61+
assert.strictEqual(config.browser.type, 'firefox');
62+
});
63+
5764
it('parses screenshot options', () => {
5865
let options = { fullPage: true };
5966
let config = parseCliOptions(options);

0 commit comments

Comments
 (0)