capybara-screenshot-diff 1.7.1 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +29 -0
  3. data/capybara-screenshot-diff.gemspec +6 -3
  4. data/gems.rb +8 -2
  5. data/lib/capybara/screenshot/diff/browser_helpers.rb +29 -28
  6. data/lib/capybara/screenshot/diff/cucumber.rb +11 -0
  7. data/lib/capybara/screenshot/diff/difference.rb +63 -0
  8. data/lib/capybara/screenshot/diff/drivers/base_driver.rb +42 -0
  9. data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +188 -260
  10. data/lib/capybara/screenshot/diff/drivers/utils.rb +18 -0
  11. data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +54 -104
  12. data/lib/capybara/screenshot/diff/drivers.rb +16 -0
  13. data/lib/capybara/screenshot/diff/image_compare.rb +125 -154
  14. data/lib/capybara/screenshot/diff/os.rb +1 -1
  15. data/lib/capybara/screenshot/diff/screenshot_matcher.rb +128 -0
  16. data/lib/capybara/screenshot/diff/screenshoter.rb +136 -0
  17. data/lib/capybara/screenshot/diff/stabilization.rb +0 -184
  18. data/lib/capybara/screenshot/diff/stable_screenshoter.rb +106 -0
  19. data/lib/capybara/screenshot/diff/test_methods.rb +51 -90
  20. data/lib/capybara/screenshot/diff/vcs.rb +44 -22
  21. data/lib/capybara/screenshot/diff/version.rb +1 -1
  22. data/lib/capybara/screenshot/diff.rb +13 -17
  23. data/sig/capybara/screenshot/diff/diff.rbs +28 -0
  24. data/sig/capybara/screenshot/diff/difference.rbs +33 -0
  25. data/sig/capybara/screenshot/diff/drivers/base_driver.rbs +63 -0
  26. data/sig/capybara/screenshot/diff/drivers/browser_helpers.rbs +36 -0
  27. data/sig/capybara/screenshot/diff/drivers/chunky_png_driver.rbs +89 -0
  28. data/sig/capybara/screenshot/diff/drivers/utils.rbs +13 -0
  29. data/sig/capybara/screenshot/diff/drivers/vips_driver.rbs +25 -0
  30. data/sig/capybara/screenshot/diff/image_compare.rbs +93 -0
  31. data/sig/capybara/screenshot/diff/os.rbs +11 -0
  32. data/sig/capybara/screenshot/diff/region.rbs +43 -0
  33. data/sig/capybara/screenshot/diff/screenshot_matcher.rbs +60 -0
  34. data/sig/capybara/screenshot/diff/screenshoter.rbs +48 -0
  35. data/sig/capybara/screenshot/diff/stable_screenshoter.rbs +29 -0
  36. data/sig/capybara/screenshot/diff/test_methods.rbs +39 -0
  37. data/sig/capybara/screenshot/diff/vcs.rbs +17 -0
  38. metadata +28 -25
  39. data/.gitattributes +0 -4
  40. data/.github/dependabot.yml +0 -8
  41. data/.github/workflows/lint.yml +0 -25
  42. data/.github/workflows/test.yml +0 -138
  43. data/.gitignore +0 -14
  44. data/.standard.yml +0 -12
  45. data/CONTRIBUTING.md +0 -24
  46. data/Dockerfile +0 -59
  47. data/README.md +0 -567
  48. data/bin/bundle +0 -114
  49. data/bin/console +0 -15
  50. data/bin/install-vips +0 -11
  51. data/bin/rake +0 -27
  52. data/bin/setup +0 -8
  53. data/bin/standardrb +0 -29
  54. data/gemfiles/rails60_gems.rb +0 -8
  55. data/gemfiles/rails61_gems.rb +0 -7
  56. data/gemfiles/rails70_gems.rb +0 -7
  57. data/tmp/.keep +0 -0
@@ -3,11 +3,11 @@
3
3
  begin
4
4
  require "vips"
5
5
  rescue LoadError => e
6
- warn 'Required ruby-vips gem is missing. Add `gem "ruby-vips"` to Gemfile' if e.message.include?("vips")
6
+ raise 'Required ruby-vips gem is missing. Add `gem "ruby-vips"` to Gemfile' if e.message.match?(/vips/i)
7
7
  raise
8
8
  end
9
9
 
10
- require_relative "./chunky_png_driver"
10
+ require "capybara/screenshot/diff/drivers/base_driver"
11
11
 
12
12
  module Capybara
13
13
  module Screenshot
@@ -15,67 +15,33 @@ 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
- class VipsDriver
19
- attr_reader :new_file_name, :old_file_name, :options
18
+ class VipsDriver < BaseDriver
19
+ def find_difference_region(comparison)
20
+ new_image, base_image, options = comparison.new_image, comparison.base_image, comparison.options
20
21
 
21
- def initialize(new_file_name, old_file_name = nil, options = {})
22
- options = old_file_name if old_file_name.is_a?(Hash)
23
-
24
- @new_file_name = new_file_name
25
- @old_file_name = old_file_name || "#{new_file_name}#{ImageCompare::TMP_FILE_SUFFIX}"
26
-
27
- @options = options || {}
28
-
29
- reset
30
- end
31
-
32
- def skip_area=(new_skip_area)
33
- # noop
34
- end
35
-
36
- # Resets the calculated data about the comparison with regard to the "new_image".
37
- # Data about the original image is kept.
38
- def reset
39
- end
40
-
41
- def shift_distance_equal?
42
- warn "[capybara-screenshot-diff] Instead of shift_distance_limit " \
43
- "please use median_filter_window_size and color_distance_limit options"
44
- chunky_png_comparator.quick_equal?
45
- end
46
-
47
- def shift_distance_different?
48
- warn "[capybara-screenshot-diff] Instead of shift_distance_limit " \
49
- "please use median_filter_window_size and color_distance_limit options"
50
- chunky_png_comparator.different?
51
- end
52
-
53
- def find_difference_region(new_image, old_image, color_distance_limit, _shift_distance_limit, _area_size_limit, fast_fail: false)
54
- diff_mask = VipsUtil.difference_mask(color_distance_limit, old_image, new_image)
22
+ diff_mask = VipsUtil.difference_mask(base_image, new_image, options[:color_distance_limit])
55
23
  region = VipsUtil.difference_region_by(diff_mask)
24
+ region = nil if region && same_as?(region, base_image)
56
25
 
57
- [region, diff_mask]
58
- end
59
-
60
- def adds_error_details_to(_log)
61
- end
26
+ result = Difference.new(region, {}, comparison)
62
27
 
63
- # old private
28
+ unless result.blank?
29
+ meta = {}
30
+ meta[:difference_level] = difference_level(diff_mask, base_image) if comparison.options[:tolerance]
31
+ result.meta = meta
32
+ end
64
33
 
65
- def inscribed?(dimensions, i)
66
- dimension(i) == dimensions || i.width < dimensions[0] || i.height < dimensions[1]
34
+ result
67
35
  end
68
36
 
69
37
  def crop(region, i)
70
- result = i.crop(*region.to_top_left_corner_coordinates)
71
-
72
- # FIXME: Vips is caching operations, and if we ware going to read the same file, he will use cached version for this
73
- # so after we cropped files and stored in the same file, the next load will recover old version instead of cropped
74
- # Workaround to make vips works with cropped versions
75
- Vips.cache_set_max(0)
76
- Vips.vips_cache_set_max(1000)
77
-
78
- result
38
+ i.crop(*region.to_top_left_corner_coordinates)
39
+ rescue Vips::Error => e
40
+ warn(
41
+ "[capybara-screenshot-diff] Crop has been failed for " \
42
+ "{ region: #{region.to_top_left_corner_coordinates.inspect}, image: #{dimension(i).join("x")} }"
43
+ )
44
+ raise e
79
45
  end
80
46
 
81
47
  def filter_image_with_median(image, median_filter_window_size)
@@ -86,84 +52,66 @@ module Capybara
86
52
  memo.draw_rect([0, 0, 0, 0], *region.to_top_left_corner_coordinates, fill: true)
87
53
  end
88
54
 
89
- def chunky_png_comparator
90
- @chunky_png_comparator ||= ImageCompare.new(
91
- @new_file_name,
92
- @old_file_name,
93
- **@options.merge(driver: :chunky_png, tolerance: nil, median_filter_window_size: nil)
94
- )
95
- end
96
-
97
55
  def difference_level(diff_mask, old_img, _region = nil)
98
56
  VipsUtil.difference_area_size_by(diff_mask).to_f / image_area_size(old_img)
99
57
  end
100
58
 
101
- def image_area_size(old_img)
102
- width_for(old_img) * height_for(old_img)
103
- end
104
-
105
- def height_for(image)
106
- image.height
107
- end
108
-
109
- def width_for(image)
110
- image.width
111
- end
112
-
113
- PNG_EXTENSION = ".png"
59
+ MAX_FILENAME_LENGTH = 200
114
60
 
115
61
  # Vips could not work with the same file. Per each process we require to create new file
116
62
  def save_image_to(image, filename)
117
- ::Dir::Tmpname.create([filename, PNG_EXTENSION]) do |tmp_image_filename|
63
+ # Dir::Tmpname will happily produce tempfile names that are too long for most unix filesystems,
64
+ # which leads to "unix error: File name too long". Apply a limit to avoid this.
65
+ limited_filename = filename.to_s[-MAX_FILENAME_LENGTH..] || filename.to_s
66
+ ::Dir::Tmpname.create([limited_filename, PNG_EXTENSION]) do |tmp_image_filename|
118
67
  image.write_to_file(tmp_image_filename)
119
68
  FileUtils.mv(tmp_image_filename, filename)
120
69
  end
121
70
  end
122
71
 
123
72
  def resize_image_to(image, new_width, new_height)
124
- image.resize(1.* new_width / new_height)
73
+ image.resize(new_width.to_f / new_height)
125
74
  end
126
75
 
127
- def load_images(old_file_name, new_file_name, driver = self)
128
- [driver.from_file(old_file_name), driver.from_file(new_file_name)]
76
+ def load_images(old_file_name, new_file_name)
77
+ [from_file(old_file_name), from_file(new_file_name)]
129
78
  end
130
79
 
131
80
  def from_file(filename)
132
- result = ::Vips::Image.new_from_file(filename)
81
+ result = ::Vips::Image.new_from_file(filename.to_s)
133
82
 
134
- result = result.colourspace("srgb") if result.bands < 3
83
+ result = result.colourspace(:srgb) if result.bands < 3
135
84
  result = result.bandjoin(255) if result.bands == 3
136
85
 
137
86
  result
138
87
  end
139
88
 
140
- def dimension_changed?(old_image, new_image)
141
- return false if dimension(old_image) == dimension(new_image)
142
-
143
- change_msg = [old_image, new_image].map { |i| "#{i.width}x#{i.height}" }.join(" => ")
144
- warn "Image size has changed for #{@new_file_name}: #{change_msg}"
145
-
146
- true
147
- end
148
-
149
89
  def dimension(image)
150
- [image.width, image.height]
90
+ [width_for(image), height_for(image)]
151
91
  end
152
92
 
153
- def draw_rectangles(images, region, rgba)
93
+ def draw_rectangles(images, region, rgba, offset: 0)
154
94
  images.map do |image|
155
- image.draw_rect(rgba, region.left - 1, region.top - 1, region.width + 2, region.height + 2)
95
+ image.draw_rect(rgba, region.left - offset, region.top - offset, region.width + (offset * 2), region.height + (offset * 2))
156
96
  end
157
97
  end
158
98
 
159
- class VipsUtil
160
- def self.difference(old_image, new_image, color_distance: 0)
161
- diff_mask = difference_mask(color_distance, new_image, old_image)
162
- difference_region_by(diff_mask).to_edge_coordinates
163
- end
99
+ def same_pixels?(comparison)
100
+ (comparison.new_image == comparison.base_image).min == 255
101
+ end
164
102
 
103
+ private
104
+
105
+ def same_as?(region, base_image)
106
+ region.x.zero? &&
107
+ region.y.zero? &&
108
+ region.height == height_for(base_image) &&
109
+ region.width == width_for(base_image)
110
+ end
111
+
112
+ class VipsUtil
165
113
  def self.difference_area(old_image, new_image, color_distance: 0)
166
- difference_mask = difference_mask(color_distance, new_image, old_image)
114
+ difference_mask = difference_mask(new_image, old_image, color_distance)
167
115
  difference_area_size_by(difference_mask)
168
116
  end
169
117
 
@@ -172,18 +120,20 @@ module Capybara
172
120
  diff_mask.hist_find.to_a[0][0].max
173
121
  end
174
122
 
175
- def self.difference_mask(color_distance, old_image, new_image)
176
- (new_image - old_image).abs > color_distance
123
+ def self.difference_mask(base_image, new_image, color_distance = nil)
124
+ result = (new_image - base_image).abs
125
+
126
+ color_distance ? result > color_distance : result
177
127
  end
178
128
 
179
129
  def self.difference_region_by(diff_mask)
180
130
  columns, rows = diff_mask.bandor.project
181
131
 
182
132
  left = columns.profile[1].min
183
- right = columns.width - columns.flip("horizontal").profile[1].min
133
+ right = columns.width - columns.flip(:horizontal).profile[1].min
184
134
 
185
135
  top = rows.profile[0].min
186
- bottom = rows.height - rows.flip("vertical").profile[0].min
136
+ bottom = rows.height - rows.flip(:vertical).profile[0].min
187
137
 
188
138
  return nil if right < left || bottom < top
189
139
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Screenshot
5
+ module Diff
6
+ module Drivers
7
+ def self.for(driver_options = {})
8
+ driver_option = driver_options.fetch(:driver, :chunky_png)
9
+ return driver_option unless driver_option.is_a?(Symbol)
10
+
11
+ Utils.find_driver_class_for(driver_option).new
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -5,211 +5,186 @@ module Capybara
5
5
  module Diff
6
6
  LOADED_DRIVERS = {}
7
7
 
8
- # Compare two images and determine if they are equal, different, or within some comparison
8
+ # Compare two image and determine if they are equal, different, or within some comparison
9
9
  # range considering color values and difference area size.
10
- class ImageCompare < SimpleDelegator
11
- TMP_FILE_SUFFIX = "~"
10
+ class ImageCompare
11
+ TOLERABLE_OPTIONS = [:tolerance, :color_distance_limit, :shift_distance_limit, :area_size_limit].freeze
12
12
 
13
13
  attr_reader :driver, :driver_options
14
14
 
15
- attr_reader :annotated_new_file_name, :annotated_old_file_name, :new_file_name, :old_file_name, :skip_area
16
- attr_accessor :shift_distance_limit, :area_size_limit, :color_distance_limit
15
+ attr_reader :annotated_image_path, :annotated_base_image_path,
16
+ :image_path, :base_image_path,
17
+ :new_file_name, :old_file_name
17
18
 
18
- def initialize(new_file_name, old_file_name = nil, options = {})
19
- options = old_file_name if old_file_name.is_a?(Hash)
19
+ def initialize(image_path, base_image_path, options = {})
20
+ @image_path = Pathname.new(image_path)
20
21
 
21
- @new_file_name = new_file_name
22
- @old_file_name = old_file_name || "#{new_file_name}#{ImageCompare::TMP_FILE_SUFFIX}"
23
- @annotated_old_file_name = "#{new_file_name.chomp(".png")}.committed.png"
24
- @annotated_new_file_name = "#{new_file_name.chomp(".png")}.latest.png"
22
+ @new_file_name = @image_path.to_s
23
+ @annotated_image_path = @image_path.sub_ext(".diff.png")
25
24
 
26
- @driver_options = options
25
+ @base_image_path = Pathname.new(base_image_path)
27
26
 
28
- @color_distance_limit = options[:color_distance_limit] || 0
29
- @area_size_limit = options[:area_size_limit]
30
- @shift_distance_limit = options[:shift_distance_limit]
31
- @dimensions = options[:dimensions]
32
- @skip_area = options[:skip_area]
33
- @tolerance = options[:tolerance]
34
- @median_filter_window_size = options[:median_filter_window_size]
27
+ @old_file_name = @base_image_path.to_s
28
+ @annotated_base_image_path = @base_image_path.sub_ext(".diff.png")
35
29
 
36
- driver_klass = find_driver_class_for(@driver_options.fetch(:driver, :chunky_png))
37
- @driver = driver_klass.new(@new_file_name, @old_file_name, **@driver_options)
30
+ @driver_options = options.dup
38
31
 
39
- super(@driver)
40
- end
41
-
42
- def skip_area=(new_skip_area)
43
- @skip_area = new_skip_area
44
- driver.skip_area = @skip_area
32
+ @driver = Drivers.for(@driver_options)
45
33
  end
46
34
 
47
35
  # Compare the two image files and return `true` or `false` as quickly as possible.
48
36
  # Return falsely if the old file does not exist or the image dimensions do not match.
49
37
  def quick_equal?
50
- return false unless old_file_exists?
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
51
41
  return true if new_file_size == old_file_size
52
42
 
53
- images = driver.load_images(@old_file_name, @new_file_name)
54
- old_image, new_image = preprocess_images(images, driver)
43
+ comparison = load_and_process_images
55
44
 
56
- return false if driver.dimension_changed?(old_image, new_image)
45
+ unless driver.same_dimension?(comparison)
46
+ @error_message = build_error_for_different_dimensions(comparison)
47
+ return false
48
+ end
57
49
 
58
- self.difference_region, meta = driver.find_difference_region(
59
- new_image,
60
- old_image,
61
- @color_distance_limit,
62
- @shift_distance_limit,
63
- @area_size_limit,
64
- fast_fail: true
65
- )
50
+ return true if driver.same_pixels?(comparison)
66
51
 
67
- return true if difference_region_area_size.zero? || difference_region_empty?(new_image, difference_region)
68
- return true if @area_size_limit && difference_region_area_size <= @area_size_limit
69
- return true if @tolerance && @tolerance >= driver.difference_level(meta, old_image, difference_region)
70
- # TODO: Remove this or find similar solution for vips
71
- return true if @shift_distance_limit && driver.shift_distance_equal?
52
+ # Could not make any difference to be tolerable, so skip and return as not equal
53
+ return false if without_tolerable_options?
72
54
 
55
+ @difference = driver.find_difference_region(comparison)
56
+ return true unless @difference.different?
57
+
58
+ @error_message = @difference.inspect
73
59
  false
74
60
  end
75
61
 
76
- # Compare the two images referenced by this object, and return `true` if they are different,
62
+ # Compare the two image referenced by this object, and return `true` if they are different,
77
63
  # and `false` if they are the same.
78
64
  def different?
79
- return false unless old_file_exists?
65
+ @error_message = nil
80
66
 
81
- images = driver.load_images(@old_file_name, @new_file_name)
82
- old_image, new_image = preprocess_images(images, driver)
67
+ @error_message = _different?
83
68
 
84
- if driver.dimension_changed?(old_image, new_image)
85
- self.difference_region = Region.from_edge_coordinates(
86
- 0,
87
- 0,
88
- [driver.width_for(old_image), driver.width_for(new_image)].min,
89
- [driver.height_for(old_image), driver.height_for(new_image)].min
90
- )
69
+ clean_tmp_files unless @error_message
91
70
 
92
- return different(*images)
93
- end
71
+ !@error_message.nil?
72
+ end
73
+
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(" => ")
94
78
 
95
- self.difference_region, meta = driver.find_difference_region(
96
- new_image,
97
- old_image,
98
- @color_distance_limit,
99
- @shift_distance_limit,
100
- @area_size_limit
101
- )
102
-
103
- return not_different if difference_region_area_size.zero? || difference_region_empty?(old_image, difference_region)
104
- return not_different if @area_size_limit && difference_region_area_size <= @area_size_limit
105
- return not_different if @tolerance && @tolerance > driver.difference_level(meta, old_image, difference_region)
106
- # TODO: Remove this or find similar solution for vips
107
- return not_different if @shift_distance_limit && !driver.shift_distance_different?
108
-
109
- different(*images)
79
+ "Screenshot dimension has been changed for #{@new_file_name}: #{change_msg}"
110
80
  end
111
81
 
112
82
  def clean_tmp_files
113
- FileUtils.cp @old_file_name, @new_file_name if old_file_exists?
114
- File.delete(@old_file_name) if old_file_exists?
115
- File.delete(@annotated_old_file_name) if File.exist?(@annotated_old_file_name)
116
- File.delete(@annotated_new_file_name) if File.exist?(@annotated_new_file_name)
83
+ @annotated_base_image_path.unlink if @annotated_base_image_path.exist?
84
+ @annotated_image_path.unlink if @annotated_image_path.exist?
117
85
  end
118
86
 
119
- def save(old_img, new_img, annotated_old_file_name, annotated_new_file_name)
120
- driver.save_image_to(old_img, annotated_old_file_name)
121
- driver.save_image_to(new_img, annotated_new_file_name)
87
+ def save(image, image_path)
88
+ driver.save_image_to(image, image_path.to_s)
122
89
  end
123
90
 
124
- def old_file_exists?
125
- @old_file_name && File.exist?(@old_file_name)
91
+ def image_files_exist?
92
+ @base_image_path.exist? && @image_path.exist?
126
93
  end
127
94
 
128
- def reset
129
- self.difference_region = nil
130
- driver.reset
95
+ NEW_LINE = "\n"
96
+
97
+ attr_reader :error_message
98
+
99
+ private
100
+
101
+ def without_tolerable_options?
102
+ (@driver_options.keys & TOLERABLE_OPTIONS).empty?
131
103
  end
132
104
 
133
- NEW_LINE = "\n"
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?
134
108
 
135
- def error_message
136
- result = {
137
- area_size: difference_region_area_size,
138
- region: difference_coordinates
139
- }
109
+ comparison = load_and_process_images
140
110
 
141
- driver.adds_error_details_to(result)
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)
142
116
 
117
+ @difference = driver.find_difference_region(comparison)
118
+ return not_different unless @difference.different?
119
+
120
+ different(@difference)
121
+ end
122
+
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)
127
+ end
128
+
129
+ def build_error_message(difference)
143
130
  [
144
- "(#{result.to_json})",
131
+ "(#{difference.inspect})",
145
132
  new_file_name,
146
- annotated_old_file_name,
147
- annotated_new_file_name
133
+ annotated_base_image_path.to_path,
134
+ annotated_image_path.to_path
148
135
  ].join(NEW_LINE)
149
136
  end
150
137
 
151
- def difference_coordinates
152
- difference_region&.to_edge_coordinates
138
+ def skip_area
139
+ @driver_options[:skip_area]
153
140
  end
154
141
 
155
- def difference_region_area_size
156
- return 0 unless difference_region
157
-
158
- difference_region.size
142
+ def median_filter_window_size
143
+ @driver_options[:median_filter_window_size]
159
144
  end
160
145
 
161
- private
162
-
163
- attr_accessor :difference_region
164
-
165
- def different(old_image, new_image)
166
- annotate_and_save([old_image, new_image], difference_region)
167
- true
146
+ def dimensions
147
+ @driver_options[:dimensions]
168
148
  end
169
149
 
170
- def find_driver_class_for(driver)
171
- driver = AVAILABLE_DRIVERS.first if driver == :auto
172
-
173
- LOADED_DRIVERS[driver] ||=
174
- case driver
175
- when :chunky_png
176
- require "capybara/screenshot/diff/drivers/chunky_png_driver"
177
- Drivers::ChunkyPNGDriver
178
- when :vips
179
- require "capybara/screenshot/diff/drivers/vips_driver"
180
- Drivers::VipsDriver
181
- else
182
- fail "Wrong adapter #{driver.inspect}. Available adapters: #{AVAILABLE_DRIVERS.inspect}"
183
- end
150
+ def different(difference)
151
+ annotate_and_save_images(difference)
152
+ build_error_message(difference)
184
153
  end
185
154
 
186
- def preprocess_images(images, driver = self)
187
- old_img = preprocess_image(images.first, driver)
188
- new_img = preprocess_image(images.last, driver)
189
-
190
- [old_img, new_img]
155
+ def preprocess_images(images)
156
+ images.map { |image| preprocess_image(image) }
191
157
  end
192
158
 
193
- def preprocess_image(image, driver = self)
159
+ def preprocess_image(image)
194
160
  result = image
195
161
 
196
- if @dimensions && driver.inscribed?(@dimensions, result)
197
- result = driver.crop(@dimensions, result)
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)
198
165
  end
199
166
 
200
- if @median_filter_window_size
201
- result = driver.filter_image_with_median(image, @median_filter_window_size)
167
+ if skip_area
168
+ result = ignore_skipped_area(result)
202
169
  end
203
170
 
204
- if @skip_area
205
- result = @skip_area.reduce(result) { |image, region| driver.add_black_box(image, region) }
171
+ if median_filter_window_size
172
+ result = blur_image_by(image, median_filter_window_size)
206
173
  end
207
174
 
208
175
  result
209
176
  end
210
177
 
178
+ def blur_image_by(image, size)
179
+ driver.filter_image_with_median(image, size)
180
+ end
181
+
182
+ def ignore_skipped_area(image)
183
+ skip_area.reduce(image) { |memo, region| driver.add_black_box(memo, region) }
184
+ end
185
+
211
186
  def old_file_size
212
- @old_file_size ||= old_file_exists? && File.size(@old_file_name)
187
+ @old_file_size ||= image_files_exist? && File.size(@old_file_name)
213
188
  end
214
189
 
215
190
  def new_file_size
@@ -217,41 +192,37 @@ module Capybara
217
192
  end
218
193
 
219
194
  def not_different
220
- clean_tmp_files
221
- false
195
+ nil
222
196
  end
223
197
 
224
- def difference_region_empty?(new_image, region)
225
- region.nil? ||
226
- (
227
- region.height == height_for(new_image) &&
228
- region.width == width_for(new_image) &&
229
- region.x.zero? &&
230
- region.y.zero?
231
- )
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)
232
201
  end
233
202
 
234
- def annotate_and_save(images, region)
235
- annotated_images = annotate_difference(images, region)
236
- annotated_images = annotate_skip_areas(annotated_images, @skip_area) if @skip_area
237
-
238
- save(*annotated_images, @annotated_old_file_name, @annotated_new_file_name)
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)
239
207
  end
240
208
 
241
209
  DIFF_COLOR = [255, 0, 0, 255].freeze
242
210
 
243
- def annotate_difference(images, region)
244
- driver.draw_rectangles(images, region, DIFF_COLOR)
211
+ def annotate_difference(image, region)
212
+ driver.draw_rectangles(Array[image], region, DIFF_COLOR, offset: 1).first
245
213
  end
246
214
 
247
215
  SKIP_COLOR = [255, 192, 0, 255].freeze
248
216
 
249
- def annotate_skip_areas(annotated_images, skip_areas)
250
- skip_areas.reduce(annotated_images) do |annotated_images, region|
251
- driver.draw_rectangles(annotated_images, region, SKIP_COLOR)
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
252
220
  end
253
221
  end
254
222
  end
223
+
224
+ class Comparison < Struct.new(:new_image, :base_image, :options)
225
+ end
255
226
  end
256
227
  end
257
228
  end
@@ -7,7 +7,7 @@ module Capybara
7
7
  ON_MAC = !!(RbConfig::CONFIG["host_os"] =~ /darwin/)
8
8
  ON_LINUX = !!(RbConfig::CONFIG["host_os"] =~ /linux/)
9
9
 
10
- def os_name
10
+ def self.name
11
11
  return "windows" if ON_WINDOWS
12
12
  return "macos" if ON_MAC
13
13
  return "linux" if ON_LINUX