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
139 changes: 90 additions & 49 deletions clients/static-site/src/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,64 +3,105 @@
* Core functions for launching and managing browsers
*/

import { chromium } from 'playwright-core';
import { chromium, firefox, webkit } from 'playwright-core';

let browsers = { chromium, firefox, webkit };

/**
* Launch a Playwright browser instance
* @param {Object} options - Browser launch options
* @param {'chromium' | 'firefox' | 'webkit'} [options.type='chromium'] - Browser type
* @param {boolean} [options.headless=true] - Run in headless mode
* @param {Array<string>} [options.args=[]] - Additional browser arguments
* @returns {Promise<Object>} Browser instance
*/
export async function launchBrowser(options = {}) {
let { headless = true, args = [] } = options;

let browser = await chromium.launch({
headless,
args: [
// Required for running in containers/CI
'--no-sandbox',
'--disable-setuid-sandbox',

// Reduce memory usage
'--disable-dev-shm-usage',

// Disable unnecessary features
'--disable-extensions',
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-component-update',
'--disable-default-apps',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-popup-blocking',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--disable-sync',

// Disable features via --disable-features (modern approach)
'--disable-features=Translate,OptimizationHints,MediaRouter',

// Reduce resource usage
'--metrics-recording-only',
'--no-first-run',

// Screenshot consistency
'--hide-scrollbars',
'--mute-audio',
'--force-color-profile=srgb',

// Memory optimizations
'--js-flags=--max-old-space-size=512',

// User-provided args
...args,
],
});

return browser;
let { type = 'chromium', headless = true, args = [] } = options;

let browserType = browsers[type];
if (!browserType) {
throw new Error(
`Unknown browser type: ${type}. Supported browsers: chromium, firefox, webkit`
);
}

// Chromium-specific args for CI/containers and screenshot consistency
let launchArgs =
type === 'chromium'
? [
// Required for running in containers/CI
'--no-sandbox',
'--disable-setuid-sandbox',

// Reduce memory usage
'--disable-dev-shm-usage',

// Disable unnecessary features
'--disable-extensions',
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-component-update',
'--disable-default-apps',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-popup-blocking',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--disable-sync',

// Disable features via --disable-features (modern approach)
'--disable-features=Translate,OptimizationHints,MediaRouter',

// Reduce resource usage
'--metrics-recording-only',
'--no-first-run',

// Screenshot consistency
'--hide-scrollbars',
'--mute-audio',
'--force-color-profile=srgb',

// Memory optimizations
'--js-flags=--max-old-space-size=512',

// User-provided args
...args,
]
: args;

try {
let browser = await browserType.launch({
headless,
args: launchArgs,
});

return browser;
} catch (error) {
// Check if this is a missing browser error
if (
error.message.includes("Executable doesn't exist") ||
error.message.includes('browserType.launch')
) {
let installCmd =
type === 'chromium'
? 'npx playwright install chromium'
: `npx playwright install ${type}`;

throw new Error(
`Browser "${type}" is not installed.\n\n` +
`To fix this, run:\n` +
` ${installCmd}\n\n` +
`For CI environments, add this step before running Vizzly:\n` +
` ${installCmd} --with-deps\n\n` +
`You can cache the browser installation in CI for faster builds.\n` +
`See: https://playwright.dev/docs/ci`
);
}

throw error;
}
}

/**
Expand Down
4 changes: 3 additions & 1 deletion clients/static-site/src/config-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let viewportSchema = z.object({
* Browser configuration schema
*/
let browserSchema = z.object({
type: z.enum(['chromium', 'firefox', 'webkit']).default('chromium'),
headless: z.boolean().default(true),
args: z.array(z.string()).default([]),
});
Expand Down Expand Up @@ -90,6 +91,7 @@ export let staticSiteConfigSchema = z
.array(viewportSchema)
.default([{ name: 'default', width: 1920, height: 1080 }]),
browser: browserSchema.default({
type: 'chromium',
headless: true,
args: [],
}),
Expand All @@ -111,7 +113,7 @@ export let staticSiteConfigSchema = z
})
.default({
viewports: [{ name: 'default', width: 1920, height: 1080 }],
browser: { headless: true, args: [] },
browser: { type: 'chromium', headless: true, args: [] },
screenshot: { fullPage: false, omitBackground: false, timeout: 45_000 },
concurrency: getDefaultConcurrency(),
pageDiscovery: {
Expand Down
5 changes: 5 additions & 0 deletions clients/static-site/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export let defaultConfig = {
buildPath: null,
viewports: [{ name: 'default', width: 1920, height: 1080 }],
browser: {
type: 'chromium',
headless: true,
args: [],
},
Expand Down Expand Up @@ -60,6 +61,10 @@ export function parseCliOptions(options) {
config.exclude = options.exclude;
}

if (options.browser) {
config.browser = { ...config.browser, type: options.browser };
}

if (options.headless !== undefined) {
config.browser = { ...config.browser, headless: options.headless };
}
Expand Down
7 changes: 6 additions & 1 deletion clients/static-site/src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default {
{ name: 'desktop', width: 1920, height: 1080 },
],
browser: {
type: 'chromium',
headless: true,
args: [],
},
Expand Down Expand Up @@ -63,7 +64,11 @@ export default {
)
.option('--include <pattern>', 'Include page pattern (glob)')
.option('--exclude <pattern>', 'Exclude page pattern (glob)')
.option('--browser-args <args>', 'Additional Puppeteer browser arguments')
.option(
'--browser <type>',
'Browser to use: chromium, firefox, webkit (default: chromium)'
)
.option('--browser-args <args>', 'Additional browser arguments')
.option('--headless', 'Run browser in headless mode')
.option('--full-page', 'Capture full page screenshots')
.option(
Expand Down
30 changes: 30 additions & 0 deletions clients/static-site/tests/config-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,36 @@ describe('config-schema', () => {
assert.deepStrictEqual(validated.browser.args, ['--no-sandbox']);
});

it('validates browser type', () => {
let config = {
browser: {
type: 'firefox',
},
};

let validated = validateStaticSiteConfig(config);

assert.strictEqual(validated.browser.type, 'firefox');
});

it('defaults browser type to chromium', () => {
let config = {};

let validated = validateStaticSiteConfig(config);

assert.strictEqual(validated.browser.type, 'chromium');
});

it('rejects invalid browser type', () => {
let config = {
browser: {
type: 'invalid-browser',
},
};

assert.throws(() => validateStaticSiteConfig(config));
});

it('validates screenshot config', () => {
let config = {
screenshot: {
Expand Down
7 changes: 7 additions & 0 deletions clients/static-site/tests/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ describe('config', () => {
]);
});

it('parses browser type option', () => {
let options = { browser: 'firefox' };
let config = parseCliOptions(options);

assert.strictEqual(config.browser.type, 'firefox');
});

it('parses screenshot options', () => {
let options = { fullPage: true };
let config = parseCliOptions(options);
Expand Down
Loading