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
6 changes: 6 additions & 0 deletions lib/capybara/screenshot/diff/browser_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
module Capybara
module Screenshot
module BrowserHelpers
def self.resize_window_if_needed
if ::Capybara::Screenshot.respond_to?(:window_size) && ::Capybara::Screenshot.window_size
resize_to(::Capybara::Screenshot.window_size)
end
end

def self.resize_to(window_size)
if session.driver.respond_to?(:resize)
session.driver.resize(*window_size)
Expand Down
2 changes: 1 addition & 1 deletion lib/capybara/screenshot/diff/region.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def find_relative_intersect(region)
end

def cover?(x, y)
left <= x && x <= right && top <= y && y <= bottom
x.between?(left, right) && y.between?(top, bottom)
end

def empty?
Expand Down
2 changes: 1 addition & 1 deletion lib/capybara/screenshot/diff/stable_screenshoter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def annotate_attempts_and_fail!(snapshot)
attempts_reporter = CapybaraScreenshotDiff::AttemptsReporter.new(snapshot, @comparison_options, {wait: wait, stability_time_limit: stability_time_limit})

# TODO: Move fail to the queue after tests passed
fail(attempts_reporter.generate)
raise CapybaraScreenshotDiff::UnstableImage, attempts_reporter.generate
end
end
end
Expand Down
79 changes: 26 additions & 53 deletions lib/capybara/screenshot/diff/test_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,6 @@ def initialize(*)
@screenshot_group = nil
@screenshot_section = nil
@test_screenshot_errors = nil
@test_screenshots = []
end

# Verifies that all scheduled screenshots do not show any unintended differences.
#
# @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
# @return [Array, nil] Returns an array of error messages if there are screenshot differences, otherwise nil.
# @note This method is typically called at the end of a test to assert all screenshots are as expected.
def verify_screenshots!(screenshots = @test_screenshots)
return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference

test_screenshot_errors = screenshots.map do |caller, name, compare|
assert_image_not_changed(caller, name, compare)
end

test_screenshot_errors.compact!

test_screenshot_errors.presence
ensure
screenshots.clear
end

# Builds the full name for a screenshot, incorporating counters and group names for uniqueness.
Expand Down Expand Up @@ -83,8 +63,9 @@ def screenshot_section(name)

def screenshot_group(name)
@screenshot_group = name.to_s
@screenshot_counter = @screenshot_group.present? ? 0 : nil
return unless Screenshot.active? && name.present?
@screenshot_counter = (@screenshot_group.nil? || @screenshot_group.empty?) ? nil : 0
name_present = !(name.nil? || name.empty?)
return unless Screenshot.active? && name_present

FileUtils.rm_rf screenshot_dir
end
Expand All @@ -97,14 +78,14 @@ def screenshot_group(name)
# @param job [Array(Array(String), String, ImageCompare)] The job to be scheduled, consisting of the caller context, screenshot name, and comparison object.
# @return [Boolean] Always returns true, indicating the job was successfully scheduled.
def schedule_match_job(job)
(@test_screenshots ||= []) << job
CapybaraScreenshotDiff.add_assertion(job)
true
end

def group_parts
parts = []
parts << @screenshot_section if @screenshot_section.present?
parts << @screenshot_group if @screenshot_group.present?
parts << @screenshot_section unless @screenshot_section.nil? || @screenshot_section.empty?
parts << @screenshot_group unless @screenshot_group.nil? || @screenshot_group.empty?
parts
end

Expand All @@ -120,13 +101,18 @@ def group_parts
def screenshot(name, skip_stack_frames: 0, **options)
return false unless Screenshot.active?

# setup
screenshot_full_name = build_full_name(name)
job = build_screenshot_matches_job(screenshot_full_name, options)

caller = caller(skip_stack_frames + 1).reject { |l| l =~ /gems\/(activesupport|minitest|railties)/ }
unless job
# exercise
match_changes_job = build_screenshot_matches_job(screenshot_full_name, options)

# verify
backtrace = caller(skip_stack_frames + 1).reject { |l| l =~ /gems\/(activesupport|minitest|railties)/ }

unless match_changes_job
if Screenshot::Diff.fail_if_new
_raise_error(<<-ERROR.strip_heredoc, caller)
_raise_error(<<-ERROR.strip_heredoc, backtrace)
No existing screenshot found for #{screenshot_full_name}!
To stop seeing this error disable by `Capybara::Screenshot::Diff.fail_if_new=false`
ERROR
Expand All @@ -135,39 +121,26 @@ def screenshot(name, skip_stack_frames: 0, **options)
return false
end

job.prepend(caller)
match_changes_job.prepend(backtrace)

if Screenshot::Diff.delayed
schedule_match_job(job)
schedule_match_job(match_changes_job)
else
error_msg = assert_image_not_changed(*job)
_raise_error(error_msg, caller(2)) if error_msg
invoke_match_job(match_changes_job)
end
end

# Asserts that an image has not changed compared to its baseline.
#
# @param caller [Array(String)] The caller context, used for error reporting.
# @param name [String] The name of the screenshot being verified.
# @param comparison [Object] The comparison object containing the result and details of the comparison.
# @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
# @note This method is used internally to verify individual screenshots.
def assert_image_not_changed(caller, name, comparison)
result = comparison.different?

# Cleanup after comparisons
if !result && comparison.base_image_path.exist?
FileUtils.mv(comparison.base_image_path, comparison.image_path, force: true)
elsif !comparison.dimensions_changed?
FileUtils.rm_rf(comparison.base_image_path)
end
private

return unless result
def invoke_match_job(job)
error_msg = CapybaraScreenshotDiff::ScreenshotAssertion.from(job).validate

"Screenshot does not match for '#{name}': #{comparison.error_message}\n#{caller.join("\n")}"
end
if error_msg
_raise_error(error_msg, job[0])
end

private
true
end

def _raise_error(error_msg, backtrace)
raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg).tap { _1.set_backtrace(backtrace) }
Expand Down
2 changes: 1 addition & 1 deletion lib/capybara/screenshot/diff/vcs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def self.restore_svn_revision(screenshot_path, checkout_path)
end

svn_info = `svn info #{screenshot_path} #{SILENCE_ERRORS}`
if svn_info.present?
unless svn_info.empty?
wc_root = svn_info.slice(/(?<=Working Copy Root Path: ).*$/)
checksum = svn_info.slice(/(?<=Checksum: ).*$/)

Expand Down
8 changes: 6 additions & 2 deletions lib/capybara_screenshot_diff.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
require "capybara/screenshot/diff/reporters/default"

module CapybaraScreenshotDiff
class ExpectationNotMet < StandardError; end
class CapybaraScreenshotDiffError < StandardError; end

class ExpectationNotMet < CapybaraScreenshotDiffError; end

class UnstableImage < CapybaraScreenshotDiffError; end
end

module Capybara
Expand Down Expand Up @@ -49,7 +53,7 @@ def screenshot_area_abs
end
end

# Module to track screen shot changes
# Module to track screenshot changes
module Diff
mattr_accessor(:delayed) { true }
mattr_accessor :area_size_limit
Expand Down
4 changes: 1 addition & 3 deletions lib/capybara_screenshot_diff/cucumber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@

Before do
Capybara::Screenshot::Diff.delayed = false
if Capybara::Screenshot.active? && Capybara::Screenshot.window_size
Capybara::Screenshot::BrowserHelpers.resize_to(Capybara::Screenshot.window_size)
end
Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
end
7 changes: 7 additions & 0 deletions lib/capybara_screenshot_diff/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

require "capybara_screenshot_diff"
require "capybara/screenshot/diff/test_methods"
require_relative "screenshot_assertion"

module CapybaraScreenshotDiff
module DSL
include Capybara::DSL
include Capybara::Screenshot::Diff::TestMethods

alias_method :_screenshot, :screenshot
def screenshot(name, **args)
assertion = CapybaraScreenshotDiff::ScreenshotAssertion.new(name, **args) { _screenshot(name, **args) }
CapybaraScreenshotDiff.add_assertion(assertion)
end
end
end
38 changes: 19 additions & 19 deletions lib/capybara_screenshot_diff/minitest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,29 @@ module Assertions
include ::CapybaraScreenshotDiff::DSL

def screenshot(*args, skip_stack_frames: 0, **opts)
assert_nothing_raised do
super(*args, skip_stack_frames: skip_stack_frames + 3, **opts)
end
self.assertions += 1

super(*args, skip_stack_frames: skip_stack_frames + 3, **opts)
rescue ::CapybaraScreenshotDiff::ExpectationNotMet => e
raise ::Minitest::Assertion, e.message
end

alias_method :assert_matches_screenshot, :screenshot

def self.included(klass)
klass.setup do
if ::Capybara::Screenshot.window_size
::Capybara::Screenshot::BrowserHelpers.resize_to(::Capybara::Screenshot.window_size)
end
end

klass.teardown do
errors = verify_screenshots!(@test_screenshots)

if errors.present?
assertion = ::Minitest::Assertion.new(errors.join("\n\n"))
assertion.set_backtrace []
failures << assertion
end
end
def setup
super
::Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
end

def before_teardown
super
CapybaraScreenshotDiff.verify
rescue CapybaraScreenshotDiff::ExpectationNotMet => e
assertion = ::Minitest::Assertion.new(e)
assertion.set_backtrace []
failures << assertion
ensure
CapybaraScreenshotDiff.reset
end
end
end
Expand Down
24 changes: 14 additions & 10 deletions lib/capybara_screenshot_diff/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,24 @@
end

RSpec.configure do |config|
config.include ::CapybaraScreenshotDiff::DSL, type: :feature
config.include ::CapybaraScreenshotDiff::DSL, type: :system
config.include CapybaraScreenshotDiff::DSL, type: :feature
config.include CapybaraScreenshotDiff::DSL, type: :system

config.after do
if self.class.include?(::CapybaraScreenshotDiff::DSL) && ::Capybara::Screenshot.active?
errors = verify_screenshots!(@test_screenshots)
# TODO: Use rspec/mock approach to postpone verification
raise ::CapybaraScreenshotDiff::ExpectationNotMet, errors.join("\n") if errors && !errors.empty?
config.before do
if self.class.include?(CapybaraScreenshotDiff::DSL)
Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
end
end

config.before do
if self.class.include?(::CapybaraScreenshotDiff::DSL) && ::Capybara::Screenshot.window_size
::Capybara::Screenshot::BrowserHelpers.resize_to(::Capybara::Screenshot.window_size)
config.after do
if self.class.include?(CapybaraScreenshotDiff::DSL)
begin
CapybaraScreenshotDiff.verify
rescue CapybaraScreenshotDiff::ExpectationNotMet => e
raise RSpec::Expectations::ExpectationNotMetError, e.message
ensure
CapybaraScreenshotDiff.reset
end
end
end
end
Loading