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