First surfaced in the discussion on #580 (the captureBeyondViewport change). This is a separate, pre-existing gap, filing it as its own issue.
Summary
A viewport set with set_viewport before a navigation is silently lost once the page navigates/redirects. set_viewport issues Emulation.setDeviceMetricsOverride, which is per-document CDP state. Chrome's RenderDocument feature swaps to a fresh document (new RenderFrameHost) on navigation, discarding that override. After the swap, window.innerWidth/innerHeight revert to the browser's real window size.
Because screenshot(full: false) derives its capture clip from live window.innerWidth/innerHeight (viewport_area), the result is silently clipped to that smaller window — a top-left crop at plausible-but-wrong dimensions.
Puppeteer and Playwright both re-apply viewport emulation after each navigation; Ferrum does not — that's the underlying gap.
Why it's surfacing now
RenderDocument is rolling out in Chrome, and in puppeteer-launched setups it became active when puppeteer-core 24.41.0 stopped disabling it (puppeteer#14745, "remove RenderDocument from disabled Chrome features"). Before that, the override happened to survive navigation, so the missing re-apply was invisible. As RenderDocument becomes the default more broadly, more Ferrum users will hit this.
(Bisected against browserless/chromium with Ferrum 0.17.2: the behavior flips exactly at the browserless release that bumped puppeteer-core 24.40→24.42 — both sides running the same Chrome 147, so it's the puppeteer flag change, not a Chrome version bump.)
Reproduction
require "ferrum"
# a window smaller than the requested viewport makes the crop obvious
browser = Ferrum::Browser.new(window_size: [800, 600])
page = browser.create_page
page.set_viewport(width: 1280, height: 720)
page.go_to("http://google.com") # ends on a different origin -> document swap
p page.evaluate("[window.innerWidth, window.innerHeight]")
# expected: [1280, 720]
# actual: [800, 600] <- device-metrics override discarded
page.screenshot(path: "out.png", full: false)
# expected: a 1280x720 capture
# actual: only the top-left 800x600 region
Expected vs actual
- Expected: the viewport set via
set_viewport persists across navigations (as in Puppeteer/Playwright), and screenshot(full: false) captures the full requested viewport.
- Actual: the override is dropped on navigation; the screenshot is clipped to the browser's real window size.
Suggested fix
- Track the active viewport on the page and re-apply it after navigations (e.g. on main-frame
Page.frameNavigated / new-document events), matching Puppeteer/Playwright. Note: re-sending setDeviceMetricsOverride with identical params after the reset is a no-op in Chrome, so the re-apply must clear first (Emulation.clearDeviceMetricsOverride) or only fire when the live viewport no longer matches the requested one.
- Optionally/additionally, have
screenshot(full: false) clip to the explicitly-requested viewport rather than live window.innerWidth/innerHeight.
Workaround
Re-assert the viewport immediately before the screenshot:
page.command("Emulation.clearDeviceMetricsOverride") # clear is required — an identical re-set is a no-op
page.set_viewport(width: 1280, height: 720, scale_factor: 1)
Environment
- ferrum 0.17.2
- Chrome 147 (reproduced via browserless/chromium; also reproducible with a locally-launched browser whose window is smaller than the requested viewport)
First surfaced in the discussion on #580 (the
captureBeyondViewportchange). This is a separate, pre-existing gap, filing it as its own issue.Summary
A viewport set with
set_viewportbefore a navigation is silently lost once the page navigates/redirects.set_viewportissuesEmulation.setDeviceMetricsOverride, which is per-document CDP state. Chrome's RenderDocument feature swaps to a fresh document (newRenderFrameHost) on navigation, discarding that override. After the swap,window.innerWidth/innerHeightrevert to the browser's real window size.Because
screenshot(full: false)derives its capture clip from livewindow.innerWidth/innerHeight(viewport_area), the result is silently clipped to that smaller window — a top-left crop at plausible-but-wrong dimensions.Puppeteer and Playwright both re-apply viewport emulation after each navigation; Ferrum does not — that's the underlying gap.
Why it's surfacing now
RenderDocument is rolling out in Chrome, and in puppeteer-launched setups it became active when puppeteer-core 24.41.0 stopped disabling it (puppeteer#14745, "remove RenderDocument from disabled Chrome features"). Before that, the override happened to survive navigation, so the missing re-apply was invisible. As RenderDocument becomes the default more broadly, more Ferrum users will hit this.
(Bisected against browserless/chromium with Ferrum 0.17.2: the behavior flips exactly at the browserless release that bumped puppeteer-core 24.40→24.42 — both sides running the same Chrome 147, so it's the puppeteer flag change, not a Chrome version bump.)
Reproduction
Expected vs actual
set_viewportpersists across navigations (as in Puppeteer/Playwright), andscreenshot(full: false)captures the full requested viewport.Suggested fix
Page.frameNavigated/ new-document events), matching Puppeteer/Playwright. Note: re-sendingsetDeviceMetricsOverridewith identical params after the reset is a no-op in Chrome, so the re-apply must clear first (Emulation.clearDeviceMetricsOverride) or only fire when the live viewport no longer matches the requested one.screenshot(full: false)clip to the explicitly-requested viewport rather than livewindow.innerWidth/innerHeight.Workaround
Re-assert the viewport immediately before the screenshot:
Environment