capybara-screenshot-diff 1.8.3 → 1.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -11
  3. data/capybara-screenshot-diff.gemspec +3 -4
  4. data/gems.rb +11 -8
  5. data/lib/capybara/screenshot/diff/area_calculator.rb +56 -0
  6. data/lib/capybara/screenshot/diff/browser_helpers.rb +5 -5
  7. data/lib/capybara/screenshot/diff/comparison.rb +6 -0
  8. data/lib/capybara/screenshot/diff/cucumber.rb +1 -9
  9. data/lib/capybara/screenshot/diff/difference.rb +8 -4
  10. data/lib/capybara/screenshot/diff/drivers/base_driver.rb +0 -5
  11. data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +10 -5
  12. data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +14 -3
  13. data/lib/capybara/screenshot/diff/drivers.rb +1 -1
  14. data/lib/capybara/screenshot/diff/image_compare.rb +84 -114
  15. data/lib/capybara/screenshot/diff/region.rb +28 -7
  16. data/lib/capybara/screenshot/diff/reporters/default.rb +121 -0
  17. data/lib/capybara/screenshot/diff/screenshot_matcher.rb +36 -74
  18. data/lib/capybara/screenshot/diff/screenshoter.rb +46 -54
  19. data/lib/capybara/screenshot/diff/stable_screenshoter.rb +65 -63
  20. data/lib/capybara/screenshot/diff/test_methods.rb +78 -10
  21. data/lib/capybara/screenshot/diff/{drivers/utils.rb → utils.rb} +0 -7
  22. data/lib/capybara/screenshot/diff/vcs.rb +25 -23
  23. data/lib/capybara/screenshot/diff/version.rb +1 -1
  24. data/lib/capybara/screenshot/diff.rb +1 -111
  25. data/lib/capybara-screenshot-diff.rb +1 -1
  26. data/lib/capybara_screenshot_diff/attempts_reporter.rb +49 -0
  27. data/lib/capybara_screenshot_diff/cucumber.rb +12 -0
  28. data/lib/capybara_screenshot_diff/dsl.rb +11 -0
  29. data/lib/capybara_screenshot_diff/minitest.rb +49 -0
  30. data/lib/capybara_screenshot_diff/rspec.rb +32 -0
  31. data/lib/capybara_screenshot_diff/snap.rb +55 -0
  32. data/lib/capybara_screenshot_diff/snap_manager.rb +76 -0
  33. data/lib/capybara_screenshot_diff.rb +86 -0
  34. metadata +20 -42
  35. data/lib/capybara/screenshot/diff/stabilization.rb +0 -0
  36. data/sig/capybara/screenshot/diff/diff.rbs +0 -28
  37. data/sig/capybara/screenshot/diff/difference.rbs +0 -33
  38. data/sig/capybara/screenshot/diff/drivers/base_driver.rbs +0 -63
  39. data/sig/capybara/screenshot/diff/drivers/browser_helpers.rbs +0 -36
  40. data/sig/capybara/screenshot/diff/drivers/chunky_png_driver.rbs +0 -89
  41. data/sig/capybara/screenshot/diff/drivers/utils.rbs +0 -13
  42. data/sig/capybara/screenshot/diff/drivers/vips_driver.rbs +0 -25
  43. data/sig/capybara/screenshot/diff/image_compare.rbs +0 -93
  44. data/sig/capybara/screenshot/diff/os.rbs +0 -11
  45. data/sig/capybara/screenshot/diff/region.rbs +0 -43
  46. data/sig/capybara/screenshot/diff/screenshot_matcher.rbs +0 -60
  47. data/sig/capybara/screenshot/diff/screenshoter.rbs +0 -48
  48. data/sig/capybara/screenshot/diff/stable_screenshoter.rbs +0 -29
  49. data/sig/capybara/screenshot/diff/test_methods.rbs +0 -39
  50. data/sig/capybara/screenshot/diff/vcs.rbs +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e7e3bdfacb7c2fad912c72ade765c9ec6a50f306048a374d8056fc02c27d643
4
- data.tar.gz: c1527905de61a1d801c545729d68b811ba91db5902c1ff209b4dc212fa252971
3
+ metadata.gz: ffbb0b50c61fc1dcab03db91256897bed64caa09d65d37ee39765aec52e3d6fb
4
+ data.tar.gz: 1c762e2084bf2629eae25fb5e38792ce2232bdd62edbdbf1bff5381c074b3edd
5
5
  SHA512:
6
- metadata.gz: c9c7868fe6d13263db039cf2a7fbb85234abee8f3bc05dd51fc5cc78579a96c1de3f2275782f5c806e9b154472ad51a991cfad1847a54d4811460597a3a4b8fe
7
- data.tar.gz: 209f71e33b2b4bdead447293c0e286de6b6e1d945f4e12889e1b4f94675bb8bc035956f19a9897ceeedf278999598bb095991ecd34f2eaffa219d088ea19d577
6
+ metadata.gz: 97356f772b607b1ff67b146d1726baabbef696b362187c5aa5833657a4a443e8a6e8a30dedec49a07bda4d115e2a7a5c8482e4ba64a958072d1aaf6cd9dab8a3
7
+ data.tar.gz: '05880832a40006772c5fd72b28a4ebe9c28ca36ef49e70f132cab55acf1b97135bcd86bdb4d1b19893961fb344b0268d1c5d708354412c9c99efad03d3da85ab'
data/Rakefile CHANGED
@@ -17,23 +17,13 @@ Rake::TestTask.new("test:integration") do |t|
17
17
  t.test_files = FileList["test/integration/**/*_test.rb"]
18
18
  end
19
19
 
20
- Rake::TestTask.new("test:signatures") do |t|
21
- ENV["RBS_TEST_DOUBLE_SUITE"] ||= "minitest"
22
- ENV["RBS_TEST_TARGET"] ||= "Capybara::Screenshot::Diff::*"
23
- ENV["RBS_TEST_OPT"] ||= "-rset -rpathname -Isig"
24
-
25
- t.libs << "test"
26
- t.ruby_opts << "-r rbs/test/setup"
27
- t.test_files = FileList["test/**/*_test.rb"]
28
- end
29
-
30
20
  task "clobber" do
31
21
  puts "Cleanup tmp/*.png"
32
22
  FileUtils.rm_rf(Dir["./tmp/*"])
33
23
  end
34
24
 
35
25
  task "test:benchmark" do
36
- require_relative "./scripts/benchmark/find_region_benchmark"
26
+ require_relative "scripts/benchmark/find_region_benchmark"
37
27
  benchmark = Capybara::Screenshot::Diff::Drivers::FindRegionBenchmark.new
38
28
 
39
29
  puts "For Medium Screen Size: 800x600"
@@ -12,18 +12,17 @@ Gem::Specification.new do |spec|
12
12
  spec.summary = "Track your GUI changes with diff assertions"
13
13
  spec.description = "Save screen shots and track changes with graphical diff"
14
14
  spec.homepage = "https://github.com/donv/capybara-screenshot-diff"
15
- spec.required_ruby_version = ">= 3.0.0"
15
+ spec.required_ruby_version = ">= 3.1"
16
16
  spec.license = "MIT"
17
17
  spec.metadata["allowed_push_host"] = "https://rubygems.org/"
18
18
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
- f.match(%r{(^(\.|tmp|bin|test|spec|features|gemfiles|scripts)/)|(^(\.|Dockerfile|CONTRIBUTING|README))})
19
+ f.match(%r{(^(\.|tmp|bin|test|spec|features|gemfiles|scripts|foo)/)|(^(\.|Dockerfile|CONTRIBUTING|README))})
20
20
  end
21
21
 
22
22
  spec.bindir = "exe"
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_runtime_dependency "actionpack", ">= 6.1", "< 8"
26
+ spec.add_runtime_dependency "actionpack", ">= 7.0", "< 9"
27
27
  spec.add_runtime_dependency "capybara", ">= 2", "< 4"
28
- spec.add_runtime_dependency "chunky_png", "~> 1.3"
29
28
  end
data/gems.rb CHANGED
@@ -8,13 +8,18 @@ gemspec path: __dir__
8
8
  gem "rake"
9
9
 
10
10
  # Image processing libraries
11
- gem "oily_png", platform: :ruby, git: "https://github.com/donv/oily_png", branch: "patch-2"
11
+ gem "chunky_png", ">= 1.3", require: false
12
+ gem "oily_png", platform: :ruby, git: "https://github.com/wvanbergen/oily_png", ref: "44042006e79efd42ce4b52c1d78a4c70f0b4b1b2"
12
13
  gem "ruby-vips", require: false
13
14
 
14
- # Test
15
- gem "minitest", require: false
16
- gem "minitest-stub-const", require: false
17
- gem "simplecov", require: false
15
+ group :test do
16
+ gem "capybara", ">= 3.26"
17
+ gem "mutex_m" # Needed for RubyMine debugging. Try removing it.
18
+ gem "minitest", require: false
19
+ gem "minitest-stub-const", require: false
20
+ gem "simplecov", require: false
21
+ gem "rspec", require: false
22
+ end
18
23
 
19
24
  # Capybara Server
20
25
  gem "puma", require: false
@@ -22,8 +27,7 @@ gem "rackup", require: false
22
27
 
23
28
  # Capybara Drivers
24
29
  gem "cuprite", require: false
25
- gem "selenium-webdriver", require: false
26
- gem "webdrivers", "~> 5.0", require: false
30
+ gem "selenium-webdriver", ">= 4.11", require: false
27
31
 
28
32
  # Test Frameworks
29
33
  # gem "cucumber", require: false
@@ -31,5 +35,4 @@ gem "webdrivers", "~> 5.0", require: false
31
35
 
32
36
  group :tools do
33
37
  gem "standard", require: false
34
- gem "rbs", require: false, platform: :ruby
35
38
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Screenshot
5
+ module Diff
6
+ class AreaCalculator
7
+ def initialize(crop_coordinates, skip_area)
8
+ @crop_coordinates = crop_coordinates
9
+ @skip_area = skip_area
10
+ end
11
+
12
+ def calculate_crop
13
+ return @_calculated_crop if defined?(@_calculated_crop)
14
+ return @_calculated_crop = nil unless @crop_coordinates
15
+
16
+ # TODO: Move out from this class, this should be done on before screenshot and should not depend on Browser
17
+ @crop_coordinates = BrowserHelpers.bounds_for_css(@crop_coordinates).first if @crop_coordinates.is_a?(String)
18
+ @_calculated_crop = Region.from_edge_coordinates(*@crop_coordinates)
19
+ end
20
+
21
+ # Cast skip areas params into Region
22
+ # and if there is crop then makes absolute coordinates to eb relative to crop top left corner
23
+ def calculate_skip_area
24
+ return nil unless @skip_area
25
+
26
+ crop_region = calculate_crop
27
+ skip_area = Array(@skip_area)
28
+
29
+ css_selectors, coords_list = skip_area.compact.partition { |region| region.is_a? String }
30
+ regions, coords_list = coords_list.partition { |region| region.is_a? Region }
31
+
32
+ regions.concat(build_regions_for(BrowserHelpers.bounds_for_css(*css_selectors))) unless css_selectors.empty?
33
+ regions.concat(build_regions_for(coords_list.flatten.each_slice(4))) unless coords_list.empty?
34
+
35
+ regions.compact!
36
+
37
+ if crop_region
38
+ regions
39
+ .map! { |region| crop_region.find_relative_intersect(region) }
40
+ .filter! { |region| region&.present? }
41
+ end
42
+
43
+ regions
44
+ end
45
+
46
+ private
47
+
48
+ def build_regions_for(coordinates)
49
+ coordinates
50
+ .map { |entry| Region.from_edge_coordinates(*entry) }
51
+ .tap { |it| it.compact! }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -30,13 +30,13 @@ module Capybara
30
30
 
31
31
  IMAGE_WAIT_SCRIPT = <<~JS
32
32
  function pending_image() {
33
- var images = document.images;
33
+ const images = document.images
34
34
  for (var i = 0; i < images.length; i++) {
35
- if (!images[i].complete) {
36
- return images[i].src;
35
+ if (!images[i].complete && images[i].loading !== "lazy") {
36
+ return images[i].src
37
37
  }
38
38
  }
39
- return false;
39
+ return false
40
40
  }(window)
41
41
  JS
42
42
 
@@ -83,7 +83,7 @@ module Capybara
83
83
  end
84
84
 
85
85
  def self.region_for(element)
86
- element.evaluate_script(GET_BOUNDING_CLIENT_RECT_SCRIPT).map { |point| point.negative? ? 0 : point.to_i }
86
+ element.evaluate_script(GET_BOUNDING_CLIENT_RECT_SCRIPT).map { |point| point.negative? ? 0 : point.ceil.to_i }
87
87
  end
88
88
 
89
89
  def self.session
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara::Screenshot::Diff
4
+ class Comparison < Struct.new(:new_image, :base_image, :options, :driver, :new_image_path, :base_image_path)
5
+ end
6
+ end
@@ -1,11 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "capybara/screenshot/diff"
4
- require "capybara/screenshot/diff/test_methods"
5
-
6
- World(Capybara::Screenshot::Diff::TestMethods)
7
-
8
- Before do
9
- Capybara::Screenshot::Diff.delayed = false
10
- Capybara::Screenshot::BrowserHelpers.resize_to(Capybara::Screenshot.window_size) if Capybara::Screenshot.window_size
11
- end
3
+ require "capybara_screenshot_diff/cucumber"
@@ -5,13 +5,17 @@ require "json"
5
5
  module Capybara
6
6
  module Screenshot
7
7
  module Diff
8
- class Difference < Struct.new(:region, :meta, :comparison)
8
+ class Difference < Struct.new(:region, :meta, :comparison, :failed_by)
9
9
  def different?
10
- !(blank? || tolerable?)
10
+ failed? || !(blank? || tolerable?)
11
11
  end
12
12
 
13
- def base_image
14
- comparison.base_image
13
+ def equal?
14
+ !different?
15
+ end
16
+
17
+ def failed?
18
+ !!failed_by
15
19
  end
16
20
 
17
21
  def options
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "chunky_png"
4
3
  require "capybara/screenshot/diff/difference"
5
4
 
6
5
  module Capybara
@@ -31,10 +30,6 @@ module Capybara
31
30
  def dimension(image)
32
31
  [width_for(image), height_for(image)]
33
32
  end
34
-
35
- def inscribed?(dimensions, i)
36
- width_for(i) < dimensions[0] || height_for(i) < dimensions[1]
37
- end
38
33
  end
39
34
  end
40
35
  end
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "chunky_png"
3
+ begin
4
+ require "chunky_png"
5
+ rescue LoadError => e
6
+ raise 'Required chunky_png gem is missing. Add `gem "chunky_png"` to Gemfile' if e.message.match?(/chunky_png/i)
7
+ raise
8
+ end
4
9
 
5
10
  require "capybara/screenshot/diff/drivers/base_driver"
6
11
 
@@ -35,12 +40,12 @@ module Capybara
35
40
  i.crop(*region.to_top_left_corner_coordinates)
36
41
  end
37
42
 
38
- def from_file(filename)
39
- ChunkyPNG::Image.from_file(filename.to_s)
43
+ def from_file(filename_or_path)
44
+ ChunkyPNG::Image.from_file(filename_or_path.to_s)
40
45
  end
41
46
 
42
47
  def save_image_to(image, filename)
43
- image.save(filename)
48
+ image.save(filename, :fast_rgba)
44
49
  end
45
50
 
46
51
  def resize_image_to(image, new_width, new_height)
@@ -48,7 +53,7 @@ module Capybara
48
53
  end
49
54
 
50
55
  def load_image_files(old_file_name, file_name)
51
- [File.binread(old_file_name), File.binread(file_name)]
56
+ [old_file_name.binread, file_name.binread]
52
57
  end
53
58
 
54
59
  def draw_rectangles(images, region, (r, g, b), offset: 0)
@@ -15,6 +15,8 @@ module Capybara
15
15
  # Compare two images and determine if they are equal, different, or within some comparison
16
16
  # range considering color values and difference area size.
17
17
  module Drivers
18
+ DEFAULT_HIGHLIGHT_COLOR = [255, 0, 0, 255].freeze
19
+
18
20
  class VipsDriver < BaseDriver
19
21
  def find_difference_region(comparison)
20
22
  new_image, base_image, options = comparison.new_image, comparison.base_image, comparison.options
@@ -26,9 +28,8 @@ module Capybara
26
28
  result = Difference.new(region, {}, comparison)
27
29
 
28
30
  unless result.blank?
29
- meta = {}
30
- meta[:difference_level] = difference_level(diff_mask, base_image) if comparison.options[:tolerance]
31
- result.meta = meta
31
+ result.meta[:difference_level] = difference_level(diff_mask, base_image) if comparison.options[:tolerance]
32
+ result.meta[:diff_mask] = diff_mask
32
33
  end
33
34
 
34
35
  result
@@ -49,6 +50,8 @@ module Capybara
49
50
  end
50
51
 
51
52
  def add_black_box(memo, region)
53
+ return memo unless region
54
+
52
55
  memo.draw_rect([0, 0, 0, 0], *region.to_top_left_corner_coordinates, fill: true)
53
56
  end
54
57
 
@@ -100,6 +103,14 @@ module Capybara
100
103
  (comparison.new_image == comparison.base_image).min == 255
101
104
  end
102
105
 
106
+ def merge(new_image, base_image)
107
+ base_image.composite2(new_image, :over)
108
+ end
109
+
110
+ def highlight_mask(diff_mask, merged_image, color: DEFAULT_HIGHLIGHT_COLOR)
111
+ diff_mask.ifthenelse(color, merged_image * 0.75)
112
+ end
113
+
103
114
  private
104
115
 
105
116
  def same_as?(region, base_image)
@@ -5,7 +5,7 @@ module Capybara
5
5
  module Diff
6
6
  module Drivers
7
7
  def self.for(driver_options = {})
8
- driver_option = driver_options.fetch(:driver, :chunky_png)
8
+ driver_option = driver_options.is_a?(Hash) ? driver_options.fetch(:driver, :chunky_png) : driver_options
9
9
  return driver_option unless driver_option.is_a?(Symbol)
10
10
 
11
11
  Utils.find_driver_class_for(driver_option).new
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "capybara/screenshot/diff/comparison"
4
+
3
5
  module Capybara
4
6
  module Screenshot
5
7
  module Diff
@@ -11,22 +13,13 @@ module Capybara
11
13
  TOLERABLE_OPTIONS = [:tolerance, :color_distance_limit, :shift_distance_limit, :area_size_limit].freeze
12
14
 
13
15
  attr_reader :driver, :driver_options
14
-
15
- attr_reader :annotated_image_path, :annotated_base_image_path,
16
- :image_path, :base_image_path,
17
- :new_file_name, :old_file_name
16
+ attr_reader :image_path, :base_image_path
17
+ attr_reader :difference, :error_message
18
18
 
19
19
  def initialize(image_path, base_image_path, options = {})
20
20
  @image_path = Pathname.new(image_path)
21
-
22
- @new_file_name = @image_path.to_s
23
- @annotated_image_path = @image_path.sub_ext(".diff.png")
24
-
25
21
  @base_image_path = Pathname.new(base_image_path)
26
22
 
27
- @old_file_name = @base_image_path.to_s
28
- @annotated_base_image_path = @base_image_path.sub_ext(".diff.png")
29
-
30
23
  @driver_options = options.dup
31
24
 
32
25
  @driver = Drivers.for(@driver_options)
@@ -35,121 +28,116 @@ module Capybara
35
28
  # Compare the two image files and return `true` or `false` as quickly as possible.
36
29
  # Return falsely if the old file does not exist or the image dimensions do not match.
37
30
  def quick_equal?
38
- @error_message = nil
39
- return false unless image_files_exist?
40
- # TODO: Confirm this change. There are screenshots with the same size, but there is a big difference
31
+ require_images_exists!
32
+
33
+ # NOTE: This is very fuzzy logic, but so far it's helps to support current performance.
41
34
  return true if new_file_size == old_file_size
42
35
 
43
36
  comparison = load_and_process_images
44
37
 
45
38
  unless driver.same_dimension?(comparison)
46
- @error_message = build_error_for_different_dimensions(comparison)
39
+ self.difference = build_failed_difference(comparison, {different_dimensions: true})
47
40
  return false
48
41
  end
49
42
 
50
- return true if driver.same_pixels?(comparison)
43
+ if driver.same_pixels?(comparison)
44
+ self.difference = build_no_difference(comparison)
45
+ return true
46
+ end
51
47
 
52
- # Could not make any difference to be tolerable, so skip and return as not equal
48
+ # NOTE: Could not make any difference to be tolerable, so skip and return as not equal.
53
49
  return false if without_tolerable_options?
54
50
 
55
- @difference = driver.find_difference_region(comparison)
56
- return true unless @difference.different?
51
+ self.difference = driver.find_difference_region(comparison)
57
52
 
58
- @error_message = @difference.inspect
59
- false
53
+ !difference.different?
60
54
  end
61
55
 
62
56
  # Compare the two image referenced by this object, and return `true` if they are different,
63
57
  # and `false` if they are the same.
64
58
  def different?
65
- @error_message = nil
66
-
67
- @error_message = _different?
68
-
69
- clean_tmp_files unless @error_message
70
-
71
- !@error_message.nil?
59
+ processed.difference.different?
72
60
  end
73
61
 
74
- def build_error_for_different_dimensions(comparison)
75
- change_msg = [comparison.base_image, comparison.new_image]
76
- .map { |i| driver.dimension(i).join("x") }
77
- .join(" => ")
78
-
79
- "Screenshot dimension has been changed for #{@new_file_name}: #{change_msg}"
62
+ def dimensions_changed?
63
+ difference.failed_by&.[](:different_dimensions)
80
64
  end
81
65
 
82
- def clean_tmp_files
83
- @annotated_base_image_path.unlink if @annotated_base_image_path.exist?
84
- @annotated_image_path.unlink if @annotated_image_path.exist?
66
+ def reporter
67
+ @reporter ||= begin
68
+ current_difference = difference || build_no_difference(nil)
69
+ Capybara::Screenshot::Diff::Reporters::Default.new(current_difference)
70
+ end
85
71
  end
86
72
 
87
- def save(image, image_path)
88
- driver.save_image_to(image, image_path.to_s)
73
+ def processed?
74
+ !!difference
89
75
  end
90
76
 
91
- def image_files_exist?
92
- @base_image_path.exist? && @image_path.exist?
77
+ def processed
78
+ self.difference = find_difference unless processed?
79
+ @error_message ||= reporter.generate
80
+ self
93
81
  end
94
82
 
95
- NEW_LINE = "\n"
96
-
97
- attr_reader :error_message
98
-
99
83
  private
100
84
 
101
- def without_tolerable_options?
102
- (@driver_options.keys & TOLERABLE_OPTIONS).empty?
103
- end
104
-
105
- def _different?
106
- raise "There is no original (base) screenshot version to compare, located: #{@base_image_path}" unless @base_image_path.exist?
107
- raise "There is no new screenshot version to compare, located: #{@image_path}" unless @image_path.exist?
85
+ def find_difference
86
+ require_images_exists!
108
87
 
109
88
  comparison = load_and_process_images
110
89
 
111
90
  unless driver.same_dimension?(comparison)
112
- return build_error_for_different_dimensions(comparison)
91
+ return build_failed_difference(comparison, {different_dimensions: true})
113
92
  end
114
93
 
115
- return not_different if driver.same_pixels?(comparison)
94
+ if driver.same_pixels?(comparison)
95
+ build_no_difference(comparison)
96
+ else
97
+ driver.find_difference_region(comparison)
98
+ end
99
+ end
116
100
 
117
- @difference = driver.find_difference_region(comparison)
118
- return not_different unless @difference.different?
101
+ def require_images_exists!
102
+ raise ArgumentError, "There is no original (base) screenshot version to compare, located: #{base_image_path}" unless base_image_path.exist?
103
+ raise ArgumentError, "There is no new screenshot version to compare, located: #{image_path}" unless image_path.exist?
104
+ end
119
105
 
120
- different(@difference)
106
+ def difference=(new_difference)
107
+ @error_message = nil
108
+ @reporter = nil
109
+ @difference = new_difference
121
110
  end
122
111
 
123
- def load_and_process_images
124
- images = driver.load_images(old_file_name, new_file_name)
125
- base_image, new_image = preprocess_images(images)
126
- Comparison.new(new_image, base_image, @driver_options)
112
+ def image_files_exist?
113
+ @base_image_path.exist? && @image_path.exist?
127
114
  end
128
115
 
129
- def build_error_message(difference)
130
- [
131
- "(#{difference.inspect})",
132
- new_file_name,
133
- annotated_base_image_path.to_path,
134
- annotated_image_path.to_path
135
- ].join(NEW_LINE)
116
+ def without_tolerable_options?
117
+ (@driver_options.keys & TOLERABLE_OPTIONS).empty?
136
118
  end
137
119
 
138
- def skip_area
139
- @driver_options[:skip_area]
120
+ def build_failed_difference(comparison, failed_by)
121
+ Difference.new(
122
+ nil,
123
+ {difference_level: nil, max_color_distance: 0},
124
+ comparison,
125
+ failed_by
126
+ )
140
127
  end
141
128
 
142
- def median_filter_window_size
143
- @driver_options[:median_filter_window_size]
129
+ def load_and_process_images
130
+ images = driver.load_images(base_image_path, image_path)
131
+ base_image, new_image = preprocess_images(images)
132
+ Comparison.new(new_image, base_image, @driver_options, driver, image_path, base_image_path)
144
133
  end
145
134
 
146
- def dimensions
147
- @driver_options[:dimensions]
135
+ def skip_area
136
+ @driver_options[:skip_area]
148
137
  end
149
138
 
150
- def different(difference)
151
- annotate_and_save_images(difference)
152
- build_error_message(difference)
139
+ def median_filter_window_size
140
+ @driver_options[:median_filter_window_size]
153
141
  end
154
142
 
155
143
  def preprocess_images(images)
@@ -159,17 +147,19 @@ module Capybara
159
147
  def preprocess_image(image)
160
148
  result = image
161
149
 
162
- # FIXME: How can we access to this method from public interface? Is this not documented feature?
163
- if dimensions && driver.inscribed?(dimensions, result)
164
- result = driver.crop(dimensions, result)
165
- end
166
-
167
150
  if skip_area
168
151
  result = ignore_skipped_area(result)
169
152
  end
170
153
 
171
154
  if median_filter_window_size
172
- result = blur_image_by(image, median_filter_window_size)
155
+ if driver.is_a?(Drivers::VipsDriver)
156
+ result = blur_image_by(image, median_filter_window_size)
157
+ else
158
+ warn(
159
+ "[capybara-screenshot-diff] Median filter has been skipped for #{image_path} " \
160
+ "because it is not supported by #{driver.class.name}"
161
+ )
162
+ end
173
163
  end
174
164
 
175
165
  result
@@ -180,48 +170,28 @@ module Capybara
180
170
  end
181
171
 
182
172
  def ignore_skipped_area(image)
183
- skip_area.reduce(image) { |memo, region| driver.add_black_box(memo, region) }
173
+ skip_area&.reduce(image) { |memo, region| driver.add_black_box(memo, region) }
184
174
  end
185
175
 
186
176
  def old_file_size
187
- @old_file_size ||= image_files_exist? && File.size(@old_file_name)
177
+ base_image_path.size
188
178
  end
189
179
 
190
180
  def new_file_size
191
- File.size(@new_file_name)
192
- end
193
-
194
- def not_different
195
- nil
181
+ image_path.size
196
182
  end
197
183
 
198
- def annotate_and_save_images(difference)
199
- annotate_and_save_image(difference, difference.comparison.new_image, @annotated_image_path)
200
- annotate_and_save_image(difference, difference.comparison.base_image, @annotated_base_image_path)
184
+ def build_no_difference(comparison = nil)
185
+ Difference.new(
186
+ nil,
187
+ {difference_level: nil, max_color_distance: 0},
188
+ comparison || build_comparison
189
+ ).freeze
201
190
  end
202
191
 
203
- def annotate_and_save_image(difference, image, image_path)
204
- image = annotate_difference(image, difference.region)
205
- image = annotate_skip_areas(image, difference.skip_area) if difference.skip_area
206
- save(image, image_path.to_path)
207
- end
208
-
209
- DIFF_COLOR = [255, 0, 0, 255].freeze
210
-
211
- def annotate_difference(image, region)
212
- driver.draw_rectangles(Array[image], region, DIFF_COLOR, offset: 1).first
192
+ def build_comparison
193
+ Capybara::Screenshot::Diff::Comparison.new(nil, nil, driver_options, driver, image_path, base_image_path).freeze
213
194
  end
214
-
215
- SKIP_COLOR = [255, 192, 0, 255].freeze
216
-
217
- def annotate_skip_areas(image, skip_areas)
218
- skip_areas.reduce(image) do |memo, region|
219
- driver.draw_rectangles(Array[memo], region, SKIP_COLOR).first
220
- end
221
- end
222
- end
223
-
224
- class Comparison < Struct.new(:new_image, :base_image, :options)
225
195
  end
226
196
  end
227
197
  end