capybara-screenshot-diff 1.6.3 → 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) 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 +102 -0
  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 +193 -252
  10. data/lib/capybara/screenshot/diff/drivers/utils.rb +18 -0
  11. data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +61 -102
  12. data/lib/capybara/screenshot/diff/drivers.rb +16 -0
  13. data/lib/capybara/screenshot/diff/image_compare.rb +138 -154
  14. data/lib/capybara/screenshot/diff/os.rb +1 -1
  15. data/lib/capybara/screenshot/diff/region.rb +86 -0
  16. data/lib/capybara/screenshot/diff/screenshot_matcher.rb +128 -0
  17. data/lib/capybara/screenshot/diff/screenshoter.rb +136 -0
  18. data/lib/capybara/screenshot/diff/stabilization.rb +0 -208
  19. data/lib/capybara/screenshot/diff/stable_screenshoter.rb +106 -0
  20. data/lib/capybara/screenshot/diff/test_methods.rb +57 -63
  21. data/lib/capybara/screenshot/diff/vcs.rb +48 -21
  22. data/lib/capybara/screenshot/diff/version.rb +1 -1
  23. data/lib/capybara/screenshot/diff.rb +15 -19
  24. data/sig/capybara/screenshot/diff/diff.rbs +28 -0
  25. data/sig/capybara/screenshot/diff/difference.rbs +33 -0
  26. data/sig/capybara/screenshot/diff/drivers/base_driver.rbs +63 -0
  27. data/sig/capybara/screenshot/diff/drivers/browser_helpers.rbs +36 -0
  28. data/sig/capybara/screenshot/diff/drivers/chunky_png_driver.rbs +89 -0
  29. data/sig/capybara/screenshot/diff/drivers/utils.rbs +13 -0
  30. data/sig/capybara/screenshot/diff/drivers/vips_driver.rbs +25 -0
  31. data/sig/capybara/screenshot/diff/image_compare.rbs +93 -0
  32. data/sig/capybara/screenshot/diff/os.rbs +11 -0
  33. data/sig/capybara/screenshot/diff/region.rbs +43 -0
  34. data/sig/capybara/screenshot/diff/screenshot_matcher.rbs +60 -0
  35. data/sig/capybara/screenshot/diff/screenshoter.rbs +48 -0
  36. data/sig/capybara/screenshot/diff/stable_screenshoter.rbs +29 -0
  37. data/sig/capybara/screenshot/diff/test_methods.rbs +39 -0
  38. data/sig/capybara/screenshot/diff/vcs.rbs +17 -0
  39. metadata +30 -25
  40. data/.gitattributes +0 -4
  41. data/.github/workflows/lint.yml +0 -25
  42. data/.github/workflows/test.yml +0 -120
  43. data/.gitignore +0 -12
  44. data/.standard.yml +0 -12
  45. data/CONTRIBUTING.md +0 -22
  46. data/Dockerfile +0 -60
  47. data/README.md +0 -555
  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/rails52.gemfile +0 -6
  55. data/gemfiles/rails60_gems.rb +0 -8
  56. data/gemfiles/rails61_gems.rb +0 -7
  57. data/gemfiles/rails70_gems.rb +0 -7
  58. data/tmp/.keep +0 -0
@@ -2,37 +2,17 @@
2
2
 
3
3
  require "chunky_png"
4
4
 
5
+ require "capybara/screenshot/diff/drivers/base_driver"
6
+
5
7
  module Capybara
6
8
  module Screenshot
7
9
  module Diff
8
10
  # Compare two images and determine if they are equal, different, or within some comparison
9
11
  # range considering color values and difference area size.
10
12
  module Drivers
11
- class ChunkyPNGDriver
13
+ class ChunkyPNGDriver < BaseDriver
12
14
  include ChunkyPNG::Color
13
15
 
14
- attr_reader :new_file_name, :old_file_name
15
-
16
- def initialize(new_file_name, old_file_name = nil, options = {})
17
- options = old_file_name if old_file_name.is_a?(Hash)
18
-
19
- @new_file_name = new_file_name
20
- @old_file_name = old_file_name || "#{new_file_name}#{ImageCompare::TMP_FILE_SUFFIX}"
21
-
22
- @color_distance_limit = options[:color_distance_limit]
23
- @shift_distance_limit = options[:shift_distance_limit]
24
- @skip_area = options[:skip_area]
25
-
26
- reset
27
- end
28
-
29
- # Resets the calculated data about the comparison with regard to the "new_image".
30
- # Data about the original image is kept.
31
- def reset
32
- @max_color_distance = @color_distance_limit ? 0 : nil
33
- @max_shift_distance = @shift_distance_limit ? 0 : nil
34
- end
35
-
36
16
  def load_images(old_file_name, new_file_name)
37
17
  old_bytes, new_bytes = load_image_files(old_file_name, new_file_name)
38
18
 
@@ -47,308 +27,269 @@ module Capybara
47
27
  image
48
28
  end
49
29
 
50
- def difference_level(_diff_mask, old_img, region)
51
- size(region).to_f / image_area_size(old_img)
52
- end
53
-
54
- def image_area_size(old_img)
55
- width_for(old_img) * height_for(old_img)
30
+ def find_difference_region(comparison)
31
+ DifferenceRegionFinder.new(comparison, self).perform
56
32
  end
57
33
 
58
- def shift_distance_equal?
59
- # Stub
60
- false
34
+ def crop(region, i)
35
+ i.crop(*region.to_top_left_corner_coordinates)
61
36
  end
62
37
 
63
- def shift_distance_different?
64
- # Stub
65
- true
66
- end
67
-
68
- def find_difference_region(new_image, old_image, color_distance_limit, shift_distance_limit, area_size_limit, fast_fail: false)
69
- return nil, nil if new_image.pixels == old_image.pixels
70
-
71
- if fast_fail && !(color_distance_limit || shift_distance_limit || area_size_limit)
72
- return [0, 0, width_for(new_image), height_for(new_image)], nil
73
- end
74
-
75
- region = find_top(old_image, new_image)
76
- region = if region.nil? || region[1].nil?
77
- nil
78
- else
79
- find_diff_rectangle(old_image, new_image, region)
80
- end
81
-
82
- [region, nil]
38
+ def from_file(filename)
39
+ ChunkyPNG::Image.from_file(filename.to_s)
83
40
  end
84
41
 
85
- def height_for(image)
86
- image.height
42
+ def save_image_to(image, filename)
43
+ image.save(filename)
87
44
  end
88
45
 
89
- def width_for(image)
90
- image.width
46
+ def resize_image_to(image, new_width, new_height)
47
+ image.resample_bilinear(new_width, new_height)
91
48
  end
92
49
 
93
- def size(region)
94
- return 0 unless region
95
-
96
- (region[2] - region[0] + 1) * (region[3] - region[1] + 1)
50
+ def load_image_files(old_file_name, file_name)
51
+ [File.binread(old_file_name), File.binread(file_name)]
97
52
  end
98
53
 
99
- def max_color_distance
100
- calculate_metrics unless @max_color_distance
101
- @max_color_distance
102
- end
54
+ def draw_rectangles(images, region, (r, g, b), offset: 0)
55
+ border_color = ChunkyPNG::Color.rgb(r, g, b)
56
+ border_shadow = ChunkyPNG::Color.rgba(r, g, b, 100)
103
57
 
104
- def max_shift_distance
105
- calculate_metrics unless @max_shift_distance || !@shift_distance_limit
106
- @max_shift_distance
58
+ images.map do |image|
59
+ new_img = image.dup
60
+ new_img.rect(region.left - offset, region.top - offset, region.right + offset, region.bottom + offset, border_color)
61
+ new_img.rect(region.left, region.top, region.right, region.bottom, border_shadow)
62
+ new_img
63
+ end
107
64
  end
108
65
 
109
- def adds_error_details_to(log)
110
- max_color_distance = self.max_color_distance.ceil(1)
111
- max_shift_distance = self.max_shift_distance
112
-
113
- log[:max_color_distance] = max_color_distance
114
- log.merge!(max_shift_distance: max_shift_distance) if max_shift_distance
66
+ def same_pixels?(comparison)
67
+ comparison.new_image == comparison.base_image
115
68
  end
116
69
 
117
- def crop(dimensions, i)
118
- i.crop(*dimensions)
119
- end
70
+ private
120
71
 
121
- def from_file(filename)
122
- ChunkyPNG::Image.from_file(filename)
72
+ def _load_images(old_file, new_file)
73
+ [ChunkyPNG::Image.from_blob(old_file), ChunkyPNG::Image.from_blob(new_file)]
123
74
  end
124
75
 
125
- # private
76
+ class DifferenceRegionFinder
77
+ attr_accessor :skip_area, :color_distance_limit, :shift_distance_limit
126
78
 
127
- def calculate_metrics
128
- old_file, new_file = load_image_files(@old_file_name, @new_file_name)
79
+ def initialize(comparison, driver = nil)
80
+ @comparison = comparison
81
+ @driver = driver
129
82
 
130
- if old_file == new_file
131
- @max_color_distance = 0
132
- @max_shift_distance = 0
133
- return
83
+ @color_distance_limit = comparison.options[:color_distance_limit]
84
+ @shift_distance_limit = comparison.options[:shift_distance_limit]
85
+ @skip_area = comparison.options[:skip_area]
134
86
  end
135
87
 
136
- old_image, new_image = _load_images(old_file, new_file)
137
- calculate_max_color_distance(new_image, old_image)
138
- calculate_max_shift_limit(new_image, old_image) if @shift_distance_limit
139
- end
88
+ def perform
89
+ find_difference_region(@comparison)
90
+ end
140
91
 
141
- def calculate_max_color_distance(new_image, old_image)
142
- pixel_pairs = old_image.pixels.zip(new_image.pixels)
143
- @max_color_distance = pixel_pairs.inject(0) { |max, (p1, p2)|
144
- next max unless p1 && p2
92
+ def find_difference_region(comparison)
93
+ new_image, base_image, = comparison.new_image, comparison.base_image
145
94
 
146
- d = ChunkyPNG::Color.euclidean_distance_rgba(p1, p2)
147
- [max, d].max
148
- }
149
- end
95
+ meta = {}
96
+ meta[:max_color_distance] = 0
97
+ meta[:max_shift_distance] = 0 if shift_distance_limit
150
98
 
151
- def calculate_max_shift_limit(new_img, old_img)
152
- (0...new_img.width).each do |x|
153
- (0...new_img.height).each do |y|
154
- shift_distance =
155
- shift_distance_at(new_img, old_img, x, y, color_distance_limit: @color_distance_limit)
156
- if shift_distance && (@max_shift_distance.nil? || shift_distance > @max_shift_distance)
157
- @max_shift_distance = shift_distance
158
- return if @max_shift_distance == Float::INFINITY # rubocop: disable Lint/NonLocalExitFromIterator
159
- end
99
+ region = find_top(base_image, new_image, cache: meta)
100
+ region = if region.nil? || region[1].nil?
101
+ nil
102
+ else
103
+ find_diff_rectangle(base_image, new_image, region, cache: meta)
160
104
  end
161
- end
162
- end
163
105
 
164
- def save_image_to(image, filename)
165
- image.save(filename)
166
- end
106
+ result = Difference.new(region, meta, comparison)
167
107
 
168
- def resize_image_to(image, new_width, new_height)
169
- image.resample_bilinear(new_width, new_height)
170
- end
108
+ unless result.blank?
109
+ meta[:max_color_distance] = meta[:max_color_distance].ceil(1) if meta[:max_color_distance]
171
110
 
172
- def load_image_files(old_file_name, file_name)
173
- old_file = File.binread(old_file_name)
174
- new_file = File.binread(file_name)
175
- [old_file, new_file]
176
- end
111
+ if comparison.options[:tolerance]
112
+ meta[:difference_level] = difference_level(nil, base_image, region)
113
+ end
114
+ end
177
115
 
178
- def dimension_changed?(old_image, new_image)
179
- return unless old_image.dimension != new_image.dimension
116
+ result
117
+ end
180
118
 
181
- change_msg = [old_image, new_image].map { |i| "#{i.width}x#{i.height}" }.join(" => ")
182
- warn "Image size has changed for #{@new_file_name}: #{change_msg}"
183
- true
184
- end
119
+ def difference_level(_diff_mask, base_image, region)
120
+ image_area_size = @driver.image_area_size(base_image)
121
+ return nil if image_area_size.zero?
185
122
 
186
- def draw_rectangles(images, (left, top, right, bottom), (r, g, b))
187
- images.map do |image|
188
- new_img = image.dup
189
- new_img.rect(left - 1, top - 1, right + 1, bottom + 1, ChunkyPNG::Color.rgb(r, g, b))
190
- new_img
123
+ region.size.to_f / image_area_size
191
124
  end
192
- end
193
125
 
194
- private
126
+ def find_diff_rectangle(org_img, new_img, area_coordinates, cache:)
127
+ left, top, right, bottom = find_left_right_and_top(org_img, new_img, area_coordinates, cache: cache)
128
+ bottom = find_bottom(org_img, new_img, left, right, bottom, cache: cache)
195
129
 
196
- def find_diff_rectangle(org_img, new_img, region)
197
- left, top, right, bottom = find_left_right_and_top(org_img, new_img, region)
198
- bottom = find_bottom(org_img, new_img, left, right, bottom)
199
- [left, top, right, bottom]
200
- end
130
+ Region.from_edge_coordinates(left, top, right, bottom)
131
+ end
201
132
 
202
- def find_top(old_img, new_img)
203
- old_img.height.times do |y|
204
- old_img.width.times do |x|
205
- return [x, y, x, y] unless same_color?(old_img, new_img, x, y)
133
+ def find_top(old_img, new_img, cache:)
134
+ old_img.height.times do |y|
135
+ old_img.width.times do |x|
136
+ return [x, y, x, y] unless same_color?(old_img, new_img, x, y, cache: cache)
137
+ end
206
138
  end
139
+ nil
207
140
  end
208
- nil
209
- end
210
141
 
211
- def find_left_right_and_top(old_img, new_img, region)
212
- left = region[0] || old_img.width - 1
213
- top = region[1]
214
- right = region[2] || 0
215
- bottom = region[3]
216
- old_img.height.times do |y|
217
- (0...left).find do |x|
218
- next if same_color?(old_img, new_img, x, y)
219
-
220
- top ||= y
221
- bottom = y
222
- left = x
223
- right = x if x > right
224
- x
225
- end
226
- (old_img.width - 1).step(right + 1, -1).find do |x|
227
- unless same_color?(old_img, new_img, x, y)
142
+ def find_left_right_and_top(old_img, new_img, region, cache:)
143
+ region = region.is_a?(Region) ? region.to_edge_coordinates : region
144
+
145
+ left = region[0] || old_img.width - 1
146
+ top = region[1]
147
+ right = region[2] || 0
148
+ bottom = region[3]
149
+
150
+ old_img.height.times do |y|
151
+ (0...left).find do |x|
152
+ next if same_color?(old_img, new_img, x, y, cache: cache)
153
+
154
+ top ||= y
228
155
  bottom = y
229
- right = x
156
+ left = x
157
+ right = x if x > right
158
+ x
159
+ end
160
+ (old_img.width - 1).step(right + 1, -1).find do |x|
161
+ unless same_color?(old_img, new_img, x, y, cache: cache)
162
+ bottom = y
163
+ right = x
164
+ end
230
165
  end
231
166
  end
167
+
168
+ [left, top, right, bottom]
232
169
  end
233
- [left, top, right, bottom]
234
- end
235
170
 
236
- def find_bottom(old_img, new_img, left, right, bottom)
237
- if bottom
238
- (old_img.height - 1).step(bottom + 1, -1).find do |y|
239
- (left..right).find do |x|
240
- bottom = y unless same_color?(old_img, new_img, x, y)
171
+ def find_bottom(old_img, new_img, left, right, bottom, cache:)
172
+ if bottom
173
+ (old_img.height - 1).step(bottom + 1, -1).find do |y|
174
+ (left..right).find do |x|
175
+ bottom = y unless same_color?(old_img, new_img, x, y, cache: cache)
176
+ end
241
177
  end
242
178
  end
243
- end
244
- bottom
245
- end
246
179
 
247
- def same_color?(old_img, new_img, x, y)
248
- @skip_area&.each do |skip_start_x, skip_start_y, skip_end_x, skip_end_y|
249
- return true if skip_start_x <= x && x <= skip_end_x && skip_start_y <= y && y <= skip_end_y
180
+ bottom
250
181
  end
251
182
 
252
- color_distance =
253
- color_distance_at(new_img, old_img, x, y, shift_distance_limit: @shift_distance_limit)
254
- if !@max_color_distance || color_distance > @max_color_distance
255
- @max_color_distance = color_distance
256
- end
257
- color_matches = color_distance == 0 || (@color_distance_limit && @color_distance_limit > 0 &&
258
- color_distance <= @color_distance_limit)
259
- return color_matches if !@shift_distance_limit || @max_shift_distance == Float::INFINITY
260
-
261
- shift_distance = (color_matches && 0) ||
262
- shift_distance_at(new_img, old_img, x, y, color_distance_limit: @color_distance_limit)
263
- if shift_distance && (@max_shift_distance.nil? || shift_distance > @max_shift_distance)
264
- @max_shift_distance = shift_distance
183
+ def same_color?(old_img, new_img, x, y, cache:)
184
+ return true if skipped_region?(x, y)
185
+
186
+ color_distance =
187
+ color_distance_at(new_img, old_img, x, y, shift_distance_limit: @shift_distance_limit)
188
+
189
+ if color_distance > cache[:max_color_distance]
190
+ cache[:max_color_distance] = color_distance
191
+ end
192
+
193
+ color_matches = color_distance == 0 ||
194
+ (!!@color_distance_limit && @color_distance_limit > 0 && color_distance <= @color_distance_limit)
195
+
196
+ return color_matches if !@shift_distance_limit || cache[:max_shift_distance] == Float::INFINITY
197
+
198
+ shift_distance = (color_matches && 0) ||
199
+ shift_distance_at(new_img, old_img, x, y, color_distance_limit: @color_distance_limit)
200
+ if shift_distance && (cache[:max_shift_distance].nil? || shift_distance > cache[:max_shift_distance])
201
+ cache[:max_shift_distance] = shift_distance
202
+ end
203
+
204
+ color_matches
265
205
  end
266
- color_matches
267
- end
268
206
 
269
- def color_distance_at(new_img, old_img, x, y, shift_distance_limit:)
270
- org_color = old_img[x, y]
271
- if shift_distance_limit
272
- start_x = [0, x - shift_distance_limit].max
273
- end_x = [x + shift_distance_limit, new_img.width - 1].min
274
- xs = (start_x..end_x).to_a
275
- start_y = [0, y - shift_distance_limit].max
276
- end_y = [y + shift_distance_limit, new_img.height - 1].min
277
- ys = (start_y..end_y).to_a
278
- new_pixels = xs.product(ys)
279
- distances = new_pixels.map { |dx, dy|
280
- new_color = new_img[dx, dy]
281
- ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_color)
282
- }
283
- distances.min
284
- else
285
- ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_img[x, y])
207
+ def skipped_region?(x, y)
208
+ return false unless @skip_area
209
+
210
+ @skip_area.any? { |region| region.cover?(x, y) }
286
211
  end
287
- end
288
212
 
289
- def shift_distance_at(new_img, old_img, x, y, color_distance_limit:)
290
- org_color = old_img[x, y]
291
- shift_distance = 0
292
- loop do
293
- bounds_breached = 0
294
- top_row = y - shift_distance
295
- if top_row >= 0 # top
296
- ([0, x - shift_distance].max..[x + shift_distance, new_img.width - 1].min).each do |dx|
297
- if color_matches(new_img, org_color, dx, top_row, color_distance_limit)
298
- return shift_distance
299
- end
213
+ def color_distance_at(new_img, old_img, x, y, shift_distance_limit:)
214
+ org_color = old_img[x, y]
215
+ if shift_distance_limit
216
+ start_x = [0, x - shift_distance_limit].max
217
+ end_x = [x + shift_distance_limit, new_img.width - 1].min
218
+ xs = (start_x..end_x).to_a
219
+ start_y = [0, y - shift_distance_limit].max
220
+ end_y = [y + shift_distance_limit, new_img.height - 1].min
221
+ ys = (start_y..end_y).to_a
222
+ new_pixels = xs.product(ys)
223
+
224
+ distances = new_pixels.map do |dx, dy|
225
+ ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_img[dx, dy])
300
226
  end
227
+ distances.min
301
228
  else
302
- bounds_breached += 1
229
+ ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_img[x, y])
303
230
  end
304
- if shift_distance > 0
305
- if (x - shift_distance) >= 0 # left
306
- ([0, top_row + 1].max..[y + shift_distance, new_img.height - 2].min)
307
- .each do |dy|
308
- if color_matches(new_img, org_color, x - shift_distance, dy, color_distance_limit)
231
+ end
232
+
233
+ def shift_distance_at(new_img, old_img, x, y, color_distance_limit:)
234
+ org_color = old_img[x, y]
235
+ shift_distance = 0
236
+ loop do
237
+ bounds_breached = 0
238
+ top_row = y - shift_distance
239
+ if top_row >= 0 # top
240
+ ([0, x - shift_distance].max..[x + shift_distance, new_img.width - 1].min).each do |dx|
241
+ if color_matches(new_img, org_color, dx, top_row, color_distance_limit)
309
242
  return shift_distance
310
243
  end
311
244
  end
312
245
  else
313
246
  bounds_breached += 1
314
247
  end
315
- if (y + shift_distance) < new_img.height # bottom
316
- ([0, x - shift_distance].max..[x + shift_distance, new_img.width - 1].min).each do |dx|
317
- if color_matches(new_img, org_color, dx, y + shift_distance, color_distance_limit)
318
- return shift_distance
248
+ if shift_distance > 0
249
+ if (x - shift_distance) >= 0 # left
250
+ ([0, top_row + 1].max..[y + shift_distance, new_img.height - 2].min)
251
+ .each do |dy|
252
+ if color_matches(new_img, org_color, x - shift_distance, dy, color_distance_limit)
253
+ return shift_distance
254
+ end
319
255
  end
256
+ else
257
+ bounds_breached += 1
320
258
  end
321
- else
322
- bounds_breached += 1
323
- end
324
- if (x + shift_distance) < new_img.width # right
325
- ([0, top_row + 1].max..[y + shift_distance, new_img.height - 2].min)
326
- .each do |dy|
327
- if color_matches(new_img, org_color, x + shift_distance, dy, color_distance_limit)
328
- return shift_distance
259
+ if (y + shift_distance) < new_img.height # bottom
260
+ ([0, x - shift_distance].max..[x + shift_distance, new_img.width - 1].min).each do |dx|
261
+ if color_matches(new_img, org_color, dx, y + shift_distance, color_distance_limit)
262
+ return shift_distance
263
+ end
329
264
  end
265
+ else
266
+ bounds_breached += 1
267
+ end
268
+ if (x + shift_distance) < new_img.width # right
269
+ ([0, top_row + 1].max..[y + shift_distance, new_img.height - 2].min)
270
+ .each do |dy|
271
+ if color_matches(new_img, org_color, x + shift_distance, dy, color_distance_limit)
272
+ return shift_distance
273
+ end
274
+ end
275
+ else
276
+ bounds_breached += 1
330
277
  end
331
- else
332
- bounds_breached += 1
333
278
  end
334
- end
335
- break if bounds_breached == 4
279
+ break if bounds_breached == 4
336
280
 
337
- shift_distance += 1
281
+ shift_distance += 1
282
+ end
283
+ Float::INFINITY
338
284
  end
339
- Float::INFINITY
340
- end
341
-
342
- def color_matches(new_img, org_color, x, y, color_distance_limit)
343
- new_color = new_img[x, y]
344
- return new_color == org_color unless color_distance_limit
345
285
 
346
- color_distance = ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_color)
347
- color_distance <= color_distance_limit
348
- end
286
+ def color_matches(new_img, org_color, x, y, color_distance_limit)
287
+ new_color = new_img[x, y]
288
+ return new_color == org_color unless color_distance_limit
349
289
 
350
- def _load_images(old_file, new_file)
351
- [ChunkyPNG::Image.from_blob(old_file), ChunkyPNG::Image.from_blob(new_file)]
290
+ color_distance = ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_color)
291
+ color_distance <= color_distance_limit
292
+ end
352
293
  end
353
294
  end
354
295
  end
@@ -10,15 +10,33 @@ module Capybara
10
10
  result << :vips if defined?(Vips) || require("vips")
11
11
  rescue LoadError
12
12
  # vips not present
13
+ Object.send(:remove_const, :Vips) if defined?(Vips)
13
14
  end
14
15
  begin
15
16
  result << :chunky_png if defined?(ChunkyPNG) || require("chunky_png")
16
17
  rescue LoadError
17
18
  # chunky_png not present
19
+ Object.send(:remove_const, :ChunkyPNG) if defined?(ChunkyPNG)
18
20
  end
19
21
  result
20
22
  end
21
23
 
24
+ def self.find_driver_class_for(driver)
25
+ driver = AVAILABLE_DRIVERS.first if driver == :auto
26
+
27
+ LOADED_DRIVERS[driver] ||=
28
+ case driver
29
+ when :chunky_png
30
+ require "capybara/screenshot/diff/drivers/chunky_png_driver"
31
+ Drivers::ChunkyPNGDriver
32
+ when :vips
33
+ require "capybara/screenshot/diff/drivers/vips_driver"
34
+ Drivers::VipsDriver
35
+ else
36
+ fail "Wrong adapter #{driver.inspect}. Available adapters: #{AVAILABLE_DRIVERS.inspect}"
37
+ end
38
+ end
39
+
22
40
  def self.detect_test_framework_assert
23
41
  require "minitest"
24
42
  ::Minitest::Assertion