Skip to content
Closed
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
19 changes: 15 additions & 4 deletions packages/cli/src/browser/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,23 @@ export async function findBrowser(): Promise<BrowserResult | undefined> {
}

/**
* Find or download a browser.
* Resolution: env var -> cached download -> system Chrome -> auto-download.
* Find or download a browser suitable for rendering.
* Resolution: env var -> cached headless-shell -> auto-download.
*
* System Chrome is NOT used for rendering because it does not support
* the HeadlessExperimental.beginFrame CDP command required for
* deterministic frame capture, and older system Chrome versions are
* often incompatible with the version of puppeteer-core bundled in
* the engine (protocol mismatch → silent 120s timeout).
*/
export async function ensureBrowser(options?: EnsureBrowserOptions): Promise<BrowserResult> {
const existing = await findBrowser();
if (existing) return existing;
// Env override and cached headless-shell are trusted.
// System Chrome is skipped — it causes silent render timeouts.
const fromEnv = findFromEnv();
if (fromEnv) return fromEnv;

const fromCache = await findFromCache();
if (fromCache) return fromCache;

const platform = detectBrowserPlatform();
if (!platform) {
Expand Down
10 changes: 8 additions & 2 deletions packages/engine/src/services/browserManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,22 @@ export async function acquireBrowser(
const headlessShell = resolveHeadlessShellPath(config);

// BeginFrame requires chrome-headless-shell AND Linux (crashes on macOS/Windows).
// System Chrome (google-chrome, chromium) does NOT support the
// HeadlessExperimental.beginFrame CDP command — only the dedicated
// chrome-headless-shell binary does. Passing system Chrome as the
// executable causes a 120-second silent hang followed by a timeout.
const isLinux = process.platform === "linux";
const forceScreenshot = config?.forceScreenshot ?? DEFAULT_CONFIG.forceScreenshot;
const isHeadlessShellBinary = headlessShell ? /chrome-headless-shell/.test(headlessShell) : false;
let captureMode: CaptureMode;
let executablePath: string | undefined;

if (headlessShell && isLinux && !forceScreenshot) {
if (headlessShell && isLinux && isHeadlessShellBinary && !forceScreenshot) {
captureMode = "beginframe";
executablePath = headlessShell;
} else {
// Screenshot mode with renderSeek: works on all platforms.
// Screenshot mode with renderSeek: works on all platforms, and
// as a fallback when system Chrome is the only available binary.
captureMode = "screenshot";
executablePath = headlessShell ?? undefined;
}
Expand Down
Loading