diff --git a/lib/capybara/screenshot/diff.rb b/lib/capybara/screenshot/diff.rb index 711623fe..60918fec 100644 --- a/lib/capybara/screenshot/diff.rb +++ b/lib/capybara/screenshot/diff.rb @@ -7,8 +7,11 @@ require "capybara/screenshot/diff/test_methods" require "capybara/screenshot/diff/screenshoter" +require "capybara/screenshot/diff/reporters/default" + module Capybara module Screenshot + mattr_accessor :add_driver_path mattr_accessor :add_os_path mattr_accessor :blur_active_element diff --git a/lib/capybara/screenshot/diff/difference.rb b/lib/capybara/screenshot/diff/difference.rb index 4d2a2cfa..eb3a1630 100644 --- a/lib/capybara/screenshot/diff/difference.rb +++ b/lib/capybara/screenshot/diff/difference.rb @@ -5,9 +5,13 @@ module Capybara module Screenshot module Diff - class Difference < Struct.new(:region, :meta, :comparison) + class Difference < Struct.new(:region, :meta, :comparison, :failed_by) def different? - !(blank? || tolerable?) + failed? || !(blank? || tolerable?) + end + + def failed? + !!failed_by end def base_image diff --git a/lib/capybara/screenshot/diff/image_compare.rb b/lib/capybara/screenshot/diff/image_compare.rb index c076982e..fc0c6a34 100644 --- a/lib/capybara/screenshot/diff/image_compare.rb +++ b/lib/capybara/screenshot/diff/image_compare.rb @@ -30,6 +30,7 @@ def initialize(image_path, base_image_path, options = {}) @driver_options = options.dup @driver = Drivers.for(@driver_options) + @reporter = Capybara::Screenshot::Diff::Reporters::Default.new(self, @annotated_image_path, @annotated_base_image_path) end def self.build(image_path, base_image_path, options = {}) @@ -68,24 +69,23 @@ def quick_equal? def different? @error_message = nil - @error_message = _different? + @difference = find_difference - clean_tmp_files unless @error_message + @error_message = reporter.generate(@difference) - !@error_message.nil? + clean_tmp_files unless @difference.different? + + @difference.different? end def build_error_for_different_dimensions(comparison) - change_msg = [comparison.base_image, comparison.new_image] - .map { |i| driver.dimension(i).join("x") } - .join(" => ") - - "Screenshot dimension has been changed for #{@new_file_name}: #{change_msg}" + reporter.build_error_for_different_dimensions( + { comparison: comparison, new_file_name: new_file_name } + ) end def clean_tmp_files - @annotated_base_image_path.unlink if @annotated_base_image_path.exist? - @annotated_image_path.unlink if @annotated_image_path.exist? + reporter.clean_tmp_files end def save(image, image_path) @@ -106,22 +106,26 @@ def without_tolerable_options? (@driver_options.keys & TOLERABLE_OPTIONS).empty? end - def _different? - raise "There is no original (base) screenshot version to compare, located: #{@base_image_path}" unless @base_image_path.exist? - raise "There is no new screenshot version to compare, located: #{@image_path}" unless @image_path.exist? + def find_difference + raise ArgumentError, "There is no original (base) screenshot version to compare, located: #{@base_image_path}" unless @base_image_path.exist? + raise ArgumentError, "There is no new screenshot version to compare, located: #{@image_path}" unless @image_path.exist? comparison = load_and_process_images unless driver.same_dimension?(comparison) - return build_error_for_different_dimensions(comparison) + return Difference.new( + nil, + { difference_level: nil, max_color_distance: 0 }, + self, + { different_dimensions: { comparison: comparison, new_file_name: new_file_name } } + ) end - return not_different if driver.same_pixels?(comparison) - - @difference = driver.find_difference_region(comparison) - return not_different unless @difference.different? - - different(@difference) + if driver.same_pixels?(comparison) + no_difference + else + driver.find_difference_region(comparison) + end end def load_and_process_images @@ -130,15 +134,6 @@ def load_and_process_images Comparison.new(new_image, base_image, @driver_options) end - def build_error_message(difference) - [ - "(#{difference.inspect})", - new_file_name, - annotated_base_image_path.to_path, - annotated_image_path.to_path - ].join(NEW_LINE) - end - def skip_area @driver_options[:skip_area] end @@ -151,11 +146,6 @@ def dimensions @driver_options[:dimensions] end - def different(difference) - annotate_and_save_images(difference) - build_error_message(difference) - end - def preprocess_images(images) images.map { |image| preprocess_image(image) } end @@ -191,33 +181,17 @@ def new_file_size end def not_different - nil - end - - def annotate_and_save_images(difference) - annotate_and_save_image(difference, difference.comparison.new_image, @annotated_image_path) - annotate_and_save_image(difference, difference.comparison.base_image, @annotated_base_image_path) + no_difference.different? || nil end - def annotate_and_save_image(difference, image, image_path) - image = annotate_difference(image, difference.region) - image = annotate_skip_areas(image, difference.skip_area) if difference.skip_area - save(image, image_path.to_path) + def no_difference + @difference = Difference.new(nil, { difference_level: nil, max_color_distance: 0 }, self).freeze end - DIFF_COLOR = [255, 0, 0, 255].freeze - - def annotate_difference(image, region) - driver.draw_rectangles([image], region, DIFF_COLOR, offset: 1).first + def reporter + @reporter ||= Capybara::Screenshot::Diff::Reporters::Default.new(self) end - SKIP_COLOR = [255, 192, 0, 255].freeze - - def annotate_skip_areas(image, skip_areas) - skip_areas.reduce(image) do |memo, region| - driver.draw_rectangles([memo], region, SKIP_COLOR).first - end - end end class Comparison < Struct.new(:new_image, :base_image, :options) diff --git a/lib/capybara/screenshot/diff/reporters/default.rb b/lib/capybara/screenshot/diff/reporters/default.rb new file mode 100644 index 00000000..9048066b --- /dev/null +++ b/lib/capybara/screenshot/diff/reporters/default.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module Capybara::Screenshot::Diff + module Reporters + class Default + attr_reader :comparer, :annotated_image_path, :annotated_base_image_path + + def initialize(comparer, annotated_image_path = nil, annotated_base_image_path = nil) + @comparer = comparer + @annotated_image_path = annotated_image_path || comparer.annotated_image_path + @annotated_base_image_path = annotated_base_image_path || comparer.annotated_base_image_path + end + + def generate(difference) + return nil unless difference.different? + + if difference.failed? && difference.failed_by[:different_dimensions] + return build_error_for_different_dimensions(difference.failed_by[:different_dimensions]) + end + + annotate_and_save_images(difference) + build_error_message(difference) + end + + + def clean_tmp_files + annotated_base_image_path.unlink if annotated_base_image_path.exist? + annotated_image_path.unlink if annotated_image_path.exist? + end + + def build_error_for_different_dimensions(failed_by) + comparison, new_file_name = failed_by[:comparison], failed_by[:new_file_name] + change_msg = [comparison.base_image, comparison.new_image] + .map { |i| driver.dimension(i).join("x") } + .join(" => ") + + "Screenshot dimension has been changed for #{new_file_name}: #{change_msg}" + end + + def annotate_and_save_images(difference) + annotate_and_save_image(difference, difference.comparison.new_image, annotated_image_path) + annotate_and_save_image(difference, difference.comparison.base_image, annotated_base_image_path) + end + + def annotate_and_save_image(difference, image, image_path) + image = annotate_difference(image, difference.region) + image = annotate_skip_areas(image, difference.skip_area) if difference.skip_area + + save(image, image_path.to_path) + end + + DIFF_COLOR = [255, 0, 0, 255].freeze + + def annotate_difference(image, region) + driver.draw_rectangles([image], region, DIFF_COLOR, offset: 1).first + end + + SKIP_COLOR = [255, 192, 0, 255].freeze + + def annotate_skip_areas(image, skip_areas) + skip_areas.reduce(image) do |memo, region| + driver.draw_rectangles([memo], region, SKIP_COLOR).first + end + end + + def save(image, image_path) + driver.save_image_to(image, image_path.to_s) + end + + NEW_LINE = "\n".freeze + + def build_error_message(difference) + [ + "(#{difference.inspect})", + new_file_name, + annotated_base_image_path.to_path, + annotated_image_path.to_path + ].join(NEW_LINE) + end + + private + + def reporter + self + end + + def driver + @comparer.driver + end + + def new_file_name + @comparer.new_file_name + end + end + end +end diff --git a/sig/capybara/screenshot/diff/image_compare.rbs b/sig/capybara/screenshot/diff/image_compare.rbs index 2c55fe28..d865e11a 100644 --- a/sig/capybara/screenshot/diff/image_compare.rbs +++ b/sig/capybara/screenshot/diff/image_compare.rbs @@ -64,7 +64,7 @@ module Capybara private - def _different?: -> String? + def find_difference: -> Difference def different: (Difference images) -> String diff --git a/test/capybara/screenshot/diff/difference_test.rb b/test/capybara/screenshot/diff/difference_test.rb new file mode 100644 index 00000000..60fc85e1 --- /dev/null +++ b/test/capybara/screenshot/diff/difference_test.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "test_helper" +require "capybara/screenshot/diff/difference" + +module Capybara::Screenshot::Diff + class DifferenceTest < ActiveSupport::TestCase + class WithFailedByTest < DifferenceTest + setup do + @difference = Difference.new(nil, {}, nil, { different_dimensions: [] }) + end + + test 'it is different' do + assert_predicate @difference, :different? + end + + test 'it is failed' do + assert_predicate @difference, :failed? + end + end + end +end