From 6832d6c0d781e31d7dd9b217274c4017085f42ad Mon Sep 17 00:00:00 2001 From: mickeytgl Date: Tue, 21 Apr 2026 15:50:22 +0200 Subject: [PATCH 1/2] fix: use captureBeyondViewport for full-page screenshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the window-resize hack in capture_screenshot with CDP's native captureBeyondViewport: true flag on Page.captureScreenshot. The previous approach called Browser.setWindowBounds to put the window into windowState: "fullscreen" before capture and back to "normal" in an ensure block. On macOS Chrome this triggers *native* fullscreen, moving the window to its own Space and stealing focus from the user's foreground app on every full-page screenshot. captureBeyondViewport has been stable in Chrome since v81 and is the same flag Playwright and puppeteer use for their fullPage path. Output is pixel-identical, one CDP round-trip instead of three, no window-state mutation. Also implicitly fixes #178 — huge pages that returned blank under the resize approach (because the window can't grow past the display size) now render correctly, since the native path has no viewport ceiling. --- lib/ferrum/page/screenshot.rb | 19 ++++------------ spec/page/screenshot_spec.rb | 42 +++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/lib/ferrum/page/screenshot.rb b/lib/ferrum/page/screenshot.rb index d9f9de7d..102ec01f 100644 --- a/lib/ferrum/page/screenshot.rb +++ b/lib/ferrum/page/screenshot.rb @@ -282,22 +282,11 @@ def to_camel_case(option) end def capture_screenshot(options, full, background_color) - maybe_resize_fullscreen(full) do - with_background_color(background_color) do - command("Page.captureScreenshot", **options) - end - end.fetch("data") - end - - def maybe_resize_fullscreen(full) - if full - width, height = viewport_size.dup - resize(fullscreen: true) - end + options = options.merge(captureBeyondViewport: true) if full - yield - ensure - resize(width: width, height: height) if full + with_background_color(background_color) do + command("Page.captureScreenshot", **options) + end.fetch("data") end def with_background_color(color) diff --git a/spec/page/screenshot_spec.rb b/spec/page/screenshot_spec.rb index f068444a..a85c3e14 100644 --- a/spec/page/screenshot_spec.rb +++ b/spec/page/screenshot_spec.rb @@ -208,14 +208,15 @@ def create_screenshot(**options) expect(browser.viewport_size).to eq([800, 200]) end - it "resets to previous viewport when exception is raised" do + it "keeps previous viewport when exception is raised" do browser.go_to("/custom_html_size") browser.resize(width: 100, height: 100) allow(browser.page).to receive(:command).and_call_original expect(browser.page).to receive(:command) .with("Page.captureScreenshot", - format: "png", clip: { x: 0, y: 0, width: 1280, height: 1024, scale: 1.0 }) + format: "png", clip: { x: 0, y: 0, width: 1280, height: 1024, scale: 1.0 }, + captureBeyondViewport: true) .and_raise(StandardError) expect { browser.screenshot(path: file, full: true) } .to raise_exception(StandardError) @@ -226,6 +227,43 @@ def create_screenshot(**options) expect(File.exist?(file)).not_to be expect(browser.viewport_size).to eq([100, 100]) end + + it "does not mutate window state (no native macOS fullscreen transition)" do + browser.go_to("/long_page") + + client = browser.page.client + commands = [] + allow(client).to receive(:command).and_wrap_original do |m, name, **opts| + commands << [name, opts] + m.call(name, **opts) + end + + browser.screenshot(path: file, full: true) + + # Fix Ruby 3 `and_call_original` bug + RSpec::Mocks.space.proxy_for(client).reset + + bounds_calls = commands.select { |(name, _)| name == "Browser.setWindowBounds" } + expect(bounds_calls).to be_empty + end + + it "forwards captureBeyondViewport: true to Page.captureScreenshot" do + browser.go_to("/long_page") + + client = browser.page.client + capture_opts = nil + allow(client).to receive(:command).and_wrap_original do |m, name, **opts| + capture_opts = opts if name == "Page.captureScreenshot" + m.call(name, **opts) + end + + browser.screenshot(path: file, full: true) + + # Fix Ruby 3 `and_call_original` bug + RSpec::Mocks.space.proxy_for(client).reset + + expect(capture_opts).to include(captureBeyondViewport: true) + end end context "with area screenshot" do From dde036e5dc576b460c3568f3ef90ba6f157f71e0 Mon Sep 17 00:00:00 2001 From: mickeytgl Date: Wed, 22 Apr 2026 13:59:51 +0200 Subject: [PATCH 2/2] docs: add changelog entry for full-page screenshot fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be6191d0..eb83b329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changed ### Fixed +- Full-page screenshots no longer resize the window, preventing focus steal on macOS [#580] ### Removed