capybara-screenshot-diff 1.8.3 → 1.9.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 +2 -3
  4. data/gems.rb +4 -4
  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 +80 -114
  15. data/lib/capybara/screenshot/diff/region.rb +28 -7
  16. data/lib/capybara/screenshot/diff/reporters/default.rb +117 -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 +76 -9
  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 +45 -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 -39
  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: 87328c5152c18c006da12b39c30f0389ac0f7b71174530473681ac98e035b884
4
+ data.tar.gz: ec54883ea6de4b75fa5be917fee79e5098a18cd1c5ea3ebbfceb567661e148d3
5
5
  SHA512:
6
- metadata.gz: c9c7868fe6d13263db039cf2a7fbb85234abee8f3bc05dd51fc5cc78579a96c1de3f2275782f5c806e9b154472ad51a991cfad1847a54d4811460597a3a4b8fe
7
- data.tar.gz: 209f71e33b2b4bdead447293c0e286de6b6e1d945f4e12889e1b4f94675bb8bc035956f19a9897ceeedf278999598bb095991ecd34f2eaffa219d088ea19d577
6
+ metadata.gz: 7e5f89855ddaf008feab8705c6e13f495b654a7dd382fe209ac5e5917fca00553765dfcf411b90cd83aecd931fec6055502dcf3cf7ee0d69c21e8edaa891cbdc
7
+ data.tar.gz: 9f402d5ff1ed20d02c0e6fd3d7d025fc7e82287e7d159a6fb892c21cf0f8bf6db74257ba22560631ef179f191cce2de78f099b4a47b637a9d6f91eaededa7ddd
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"
@@ -16,14 +16,13 @@ Gem::Specification.new do |spec|
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", ">= 6.1", "< 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,15 @@ 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
15
  # Test
15
16
  gem "minitest", require: false
16
17
  gem "minitest-stub-const", require: false
17
18
  gem "simplecov", require: false
19
+ gem "rspec", require: false
18
20
 
19
21
  # Capybara Server
20
22
  gem "puma", require: false
@@ -22,8 +24,7 @@ gem "rackup", require: false
22
24
 
23
25
  # Capybara Drivers
24
26
  gem "cuprite", require: false
25
- gem "selenium-webdriver", require: false
26
- gem "webdrivers", "~> 5.0", require: false
27
+ gem "selenium-webdriver", ">= 4.11", require: false
27
28
 
28
29
  # Test Frameworks
29
30
  # gem "cucumber", require: false
@@ -31,5 +32,4 @@ gem "webdrivers", "~> 5.0", require: false
31
32
 
32
33
  group :tools do
33
34
  gem "standard", require: false
34
- gem "rbs", require: false, platform: :ruby
35
35
  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,104 +28,104 @@ 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
59
+ processed.difference.different?
60
+ end
66
61
 
67
- @error_message = _different?
62
+ def reporter
63
+ @reporter ||= begin
64
+ current_difference = difference || build_no_difference(nil)
65
+ Capybara::Screenshot::Diff::Reporters::Default.new(current_difference)
66
+ end
67
+ end
68
68
 
69
- clean_tmp_files unless @error_message
69
+ def processed?
70
+ !!difference
71
+ end
70
72
 
71
- !@error_message.nil?
73
+ def processed
74
+ self.difference = find_difference unless processed?
75
+ @error_message ||= reporter.generate
76
+ self
72
77
  end
73
78
 
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(" => ")
79
+ private
78
80
 
79
- "Screenshot dimension has been changed for #{@new_file_name}: #{change_msg}"
81
+ def find_difference
82
+ require_images_exists!
83
+
84
+ comparison = load_and_process_images
85
+
86
+ unless driver.same_dimension?(comparison)
87
+ return build_failed_difference(comparison, {different_dimensions: true})
88
+ end
89
+
90
+ if driver.same_pixels?(comparison)
91
+ build_no_difference(comparison)
92
+ else
93
+ driver.find_difference_region(comparison)
94
+ end
80
95
  end
81
96
 
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?
97
+ def require_images_exists!
98
+ raise ArgumentError, "There is no original (base) screenshot version to compare, located: #{base_image_path}" unless base_image_path.exist?
99
+ raise ArgumentError, "There is no new screenshot version to compare, located: #{image_path}" unless image_path.exist?
85
100
  end
86
101
 
87
- def save(image, image_path)
88
- driver.save_image_to(image, image_path.to_s)
102
+ def difference=(new_difference)
103
+ @error_message = nil
104
+ @reporter = nil
105
+ @difference = new_difference
89
106
  end
90
107
 
91
108
  def image_files_exist?
92
109
  @base_image_path.exist? && @image_path.exist?
93
110
  end
94
111
 
95
- NEW_LINE = "\n"
96
-
97
- attr_reader :error_message
98
-
99
- private
100
-
101
112
  def without_tolerable_options?
102
113
  (@driver_options.keys & TOLERABLE_OPTIONS).empty?
103
114
  end
104
115
 
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?
108
-
109
- comparison = load_and_process_images
110
-
111
- unless driver.same_dimension?(comparison)
112
- return build_error_for_different_dimensions(comparison)
113
- end
114
-
115
- return not_different if driver.same_pixels?(comparison)
116
-
117
- @difference = driver.find_difference_region(comparison)
118
- return not_different unless @difference.different?
119
-
120
- different(@difference)
116
+ def build_failed_difference(comparison, failed_by)
117
+ Difference.new(
118
+ nil,
119
+ {difference_level: nil, max_color_distance: 0},
120
+ comparison,
121
+ failed_by
122
+ )
121
123
  end
122
124
 
123
125
  def load_and_process_images
124
- images = driver.load_images(old_file_name, new_file_name)
126
+ images = driver.load_images(base_image_path, image_path)
125
127
  base_image, new_image = preprocess_images(images)
126
- Comparison.new(new_image, base_image, @driver_options)
127
- end
128
-
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)
128
+ Comparison.new(new_image, base_image, @driver_options, driver, image_path, base_image_path)
136
129
  end
137
130
 
138
131
  def skip_area
@@ -143,15 +136,6 @@ module Capybara
143
136
  @driver_options[:median_filter_window_size]
144
137
  end
145
138
 
146
- def dimensions
147
- @driver_options[:dimensions]
148
- end
149
-
150
- def different(difference)
151
- annotate_and_save_images(difference)
152
- build_error_message(difference)
153
- end
154
-
155
139
  def preprocess_images(images)
156
140
  images.map { |image| preprocess_image(image) }
157
141
  end
@@ -159,17 +143,19 @@ module Capybara
159
143
  def preprocess_image(image)
160
144
  result = image
161
145
 
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
146
  if skip_area
168
147
  result = ignore_skipped_area(result)
169
148
  end
170
149
 
171
150
  if median_filter_window_size
172
- result = blur_image_by(image, median_filter_window_size)
151
+ if driver.is_a?(Drivers::VipsDriver)
152
+ result = blur_image_by(image, median_filter_window_size)
153
+ else
154
+ warn(
155
+ "[capybara-screenshot-diff] Median filter has been skipped for #{image_path} " \
156
+ "because it is not supported by #{driver.class.name}"
157
+ )
158
+ end
173
159
  end
174
160
 
175
161
  result
@@ -180,48 +166,28 @@ module Capybara
180
166
  end
181
167
 
182
168
  def ignore_skipped_area(image)
183
- skip_area.reduce(image) { |memo, region| driver.add_black_box(memo, region) }
169
+ skip_area&.reduce(image) { |memo, region| driver.add_black_box(memo, region) }
184
170
  end
185
171
 
186
172
  def old_file_size
187
- @old_file_size ||= image_files_exist? && File.size(@old_file_name)
173
+ base_image_path.size
188
174
  end
189
175
 
190
176
  def new_file_size
191
- File.size(@new_file_name)
192
- end
193
-
194
- def not_different
195
- nil
177
+ image_path.size
196
178
  end
197
179
 
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)
180
+ def build_no_difference(comparison = nil)
181
+ Difference.new(
182
+ nil,
183
+ {difference_level: nil, max_color_distance: 0},
184
+ comparison || build_comparison
185
+ ).freeze
201
186
  end
202
187
 
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
188
+ def build_comparison
189
+ Capybara::Screenshot::Diff::Comparison.new(nil, nil, driver_options, driver, image_path, base_image_path).freeze
213
190
  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
191
  end
226
192
  end
227
193
  end
@@ -7,13 +7,6 @@ class Region
7
7
  @x, @y, @width, @height = x, y, width, height
8
8
  end
9
9
 
10
- def self.from_top_left_corner_coordinates(x, y, width, height)
11
- return nil unless x && y && width && height
12
- return nil if width < 0 || height < 0
13
-
14
- Region.new(x, y, width, height)
15
- end
16
-
17
10
  def self.from_edge_coordinates(left, top, right, bottom)
18
11
  return nil unless left && top && right && bottom
19
12
  return nil if right < left || bottom < top
@@ -83,4 +76,32 @@ class Region
83
76
  def cover?(x, y)
84
77
  left <= x && x <= right && top <= y && y <= bottom
85
78
  end
79
+
80
+ def empty?
81
+ width.zero? || height.zero?
82
+ end
83
+
84
+ def blank?
85
+ empty?
86
+ end
87
+
88
+ def present?
89
+ !empty?
90
+ end
91
+
92
+ def inspect
93
+ "Region(x: #{x}, y: #{y}, width: #{width}, height: #{height})"
94
+ end
95
+
96
+ # need to add this method to make it work with assert_equal
97
+ def ==(other)
98
+ case other
99
+ when Region
100
+ x == other.x && y == other.y && width == other.width && height == other.height
101
+ when Array
102
+ to_a == other
103
+ else
104
+ false
105
+ end
106
+ end
86
107
  end