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
40 changes: 40 additions & 0 deletions lib/snap_diff/capture/viewport.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

require_relative "../browser_helpers"

module SnapDiff
module Capture
# Per-capture viewport preparation seam.
#
# Called exactly once per capture, before the screenshoter runs and outside
# any stability retry loop. Today it only validates the window size
# (raise-only, never resizes) — the same guard ScreenshotMatcher carried
# inline before. v3 hangs scroll-position preservation and element-anchored
# capture off this seam via the +anchor+ parameter without touching callers.
module Viewport
module_function

# @param expected_window_size [Array(Integer, Integer), nil] the configured window size
# @param anchor [Object, nil] reserved for v3 (scroll preservation /
# element-anchored capture); accepted but unused today.
# @raise [CapybaraScreenshotDiff::WindowSizeMismatchError] if the browser
# window does not match the expected size.
def prepare!(expected_window_size, anchor: nil)
return unless BrowserHelpers.window_size_is_wrong?(expected_window_size)

current_size = BrowserHelpers.selenium? ?
BrowserHelpers.session.driver.browser.manage.window.size.to_s :
"unknown"

raise CapybaraScreenshotDiff::WindowSizeMismatchError.new(<<~ERROR.chomp, caller)
Window size mismatch detected!
Expected: #{expected_window_size.inspect}
Actual: #{current_size}

Screenshots cannot be compared when window sizes don't match.
Please ensure the browser window is properly sized before taking screenshots.
ERROR
end
end
end
end
22 changes: 3 additions & 19 deletions lib/snap_diff/screenshot_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require_relative "screenshoter"
require_relative "stable_screenshoter"
require_relative "browser_helpers"
require_relative "capture/viewport"
require_relative "vcs"
require_relative "area_calculator"

Expand All @@ -20,7 +21,7 @@ def initialize(screenshot_full_name, options = {})
end

def build_screenshot_assertion(skip_stack_frames: 0)
check_window_size!
Capture::Viewport.prepare!(Capybara::Screenshot.window_size, anchor: nil)
prepare_screenshot_options
check_base_screenshot

Expand All @@ -40,7 +41,7 @@ def build_screenshot_assertion(skip_stack_frames: 0)

# Captures a screenshot without comparing it to a baseline.
def capture
check_window_size!
Capture::Viewport.prepare!(Capybara::Screenshot.window_size, anchor: nil)
prepare_screenshot_options

capture_options, comparison_options = extract_capture_and_comparison_options(driver_options)
Expand All @@ -55,23 +56,6 @@ def need_to_compare?
@snapshot.base_path.exist?
end

def check_window_size!
if BrowserHelpers.window_size_is_wrong?(Capybara::Screenshot.window_size)
current_size = BrowserHelpers.selenium? ?
BrowserHelpers.session.driver.browser.manage.window.size.to_s :
"unknown"

raise CapybaraScreenshotDiff::WindowSizeMismatchError.new(<<~ERROR.chomp, caller)
Window size mismatch detected!
Expected: #{Capybara::Screenshot.window_size.inspect}
Actual: #{current_size}

Screenshots cannot be compared when window sizes don't match.
Please ensure the browser window is properly sized before taking screenshots.
ERROR
end
end

def prepare_screenshot_options
area_calculator = AreaCalculator.new(driver_options.delete(:crop), driver_options[:skip_area])

Expand Down
36 changes: 36 additions & 0 deletions test/unit/capture/viewport_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require "test_helper"
require "capybara_screenshot_diff"

module SnapDiff
module Capture
# Direct coverage for the per-capture viewport preparation seam
# (5.5-lite item 6): raise-only window-size guard, plus the anchor:
# parameter reserved for v3 scroll preservation.
class ViewportTest < ActiveSupport::TestCase
test "prepare! is a no-op when the window size matches" do
BrowserHelpers.stub(:window_size_is_wrong?, false) do
assert_nil Viewport.prepare!([800, 600])
end
end

test "prepare! raises WindowSizeMismatchError when the window size is wrong" do
BrowserHelpers.stub(:window_size_is_wrong?, true) do
BrowserHelpers.stub(:selenium?, false) do
error = assert_raises(CapybaraScreenshotDiff::WindowSizeMismatchError) do
Viewport.prepare!([800, 600])
end
assert_includes error.message, "[800, 600]"
end
end
end

test "prepare! accepts the v3 anchor seam parameter without acting on it" do
BrowserHelpers.stub(:window_size_is_wrong?, false) do
assert_nil Viewport.prepare!([800, 600], anchor: "#some-element")
end
end
end
end
end
48 changes: 48 additions & 0 deletions test/unit/screenshot_matcher_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,54 @@ def fake_stable.take_comparison_screenshot(snapshot)
end
end
end

refute SnapDiff::SnapManager.path_for("matcher_window_size").path.exist?,
"no screenshot may be written when the window size is wrong"
end

# Same guard on the compare-free #capture path.
test "#capture raises WindowSizeMismatchError when the window size is wrong" do
SnapDiff::BrowserHelpers.stub(:window_size_is_wrong?, true) do
SnapDiff::BrowserHelpers.stub(:selenium?, false) do
assert_raises(CapybaraScreenshotDiff::WindowSizeMismatchError) do
ScreenshotMatcher.new("matcher_window_size").capture
end
end
end

refute SnapDiff::SnapManager.path_for("matcher_window_size").path.exist?,
"no screenshot may be written when the window size is wrong"
end

# Pins ScreenshotMatcher's viewport-preparation cadence: exactly one
# window-size check per capture, before the screenshoter runs. The
# stable screenshoter is stubbed here, so this guard does not police
# the real retry loop — that it stays check-free is verified by
# reading (no window-size calls in stable_screenshoter.rb).
test "window size is checked exactly once per capture even when stability retries happen" do
checks = 0
fake_stable = Object.new
def fake_stable.take_comparison_screenshot(snapshot)
# Simulates a stability loop that needed several attempts; nothing
# here may trigger another window-size check.
2.times { snapshot.next_attempt_path! }
snapshot.path.dirname.mkpath
FileUtils.cp(File.expand_path("a.png", TEST_IMAGES_DIR), snapshot.path)
end

SnapDiff::BrowserHelpers.stub(:window_size_is_wrong?, proc { |_expected|
checks += 1
false
}) do
Vcs.stub(:checkout_vcs, true) do
SnapDiff::StableScreenshoter.stub(:new, ->(*, **) { fake_stable }) do
snap = create_snapshot_for(:a, :c)
ScreenshotMatcher.new(snap.full_name, stability_time_limit: 0.1, wait: 1).build_screenshot_assertion
end
end
end

assert_equal 1, checks
end

# #capture is the compare-free path: file written, no assertion built,
Expand Down
Loading