diff --git a/packages/engine/src/services/frameCapture.ts b/packages/engine/src/services/frameCapture.ts index 98307ab54..4b7598548 100644 --- a/packages/engine/src/services/frameCapture.ts +++ b/packages/engine/src/services/frameCapture.ts @@ -114,8 +114,14 @@ export async function createCaptureSession( const headlessShell = resolveHeadlessShellPath(config); const isLinux = process.platform === "linux"; const forceScreenshot = config?.forceScreenshot ?? DEFAULT_CONFIG.forceScreenshot; + // BeginFrame's screenshot does not honor a viewport `deviceScaleFactor` + // (the captured surface is sized by the OS window in CSS pixels regardless + // of `Emulation.setDeviceMetricsOverride`'s DPR). When supersampling we + // need explicit clip+scale on `Page.captureScreenshot`, so fall back to + // the screenshot path for any DPR > 1. + const supersampling = (options.deviceScaleFactor ?? 1) > 1; const preMode: CaptureMode = - headlessShell && isLinux && !forceScreenshot ? "beginframe" : "screenshot"; + headlessShell && isLinux && !forceScreenshot && !supersampling ? "beginframe" : "screenshot"; const requestedGpuMode = config?.browserGpuMode ?? DEFAULT_CONFIG.browserGpuMode; const resolvedGpuMode = await resolveBrowserGpuMode(requestedGpuMode, { chromePath: headlessShell ?? undefined, diff --git a/packages/engine/src/services/screenshotService.ts b/packages/engine/src/services/screenshotService.ts index e493ffa05..e2ff30bcd 100644 --- a/packages/engine/src/services/screenshotService.ts +++ b/packages/engine/src/services/screenshotService.ts @@ -129,12 +129,20 @@ export async function beginFrameCapture( export async function pageScreenshotCapture(page: Page, options: CaptureOptions): Promise { const client = await getCdpSession(page); const isPng = options.format === "png"; + const dpr = options.deviceScaleFactor ?? 1; + // When supersampling, pass an explicit clip with `scale` so Chrome emits a + // screenshot at device-pixel dimensions (`width × height × dpr`). Without + // this, `Page.captureScreenshot` returns at CSS dimensions regardless of + // the viewport's deviceScaleFactor. + const clip = + dpr > 1 ? { x: 0, y: 0, width: options.width, height: options.height, scale: dpr } : undefined; const result = await client.send("Page.captureScreenshot", { format: isPng ? "png" : "jpeg", quality: isPng ? undefined : (options.quality ?? 80), fromSurface: true, captureBeyondViewport: false, optimizeForSpeed: !isPng, + ...(clip ? { clip } : {}), }); return Buffer.from(result.data, "base64"); }