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.
- checksums.yaml +4 -4
- data/Rakefile +29 -0
- data/capybara-screenshot-diff.gemspec +6 -3
- 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 +18 -0
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +54 -104
- 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 +136 -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 +28 -25
- 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
@@ -3,11 +3,11 @@
|
|
3
3
|
begin
|
4
4
|
require "vips"
|
5
5
|
rescue LoadError => e
|
6
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
def adds_error_details_to(_log)
|
61
|
-
end
|
26
|
+
result = Difference.new(region, {}, comparison)
|
62
27
|
|
63
|
-
|
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
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
-
|
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(
|
73
|
+
image.resize(new_width.to_f / new_height)
|
125
74
|
end
|
126
75
|
|
127
|
-
def load_images(old_file_name, new_file_name
|
128
|
-
[
|
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(
|
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
|
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 -
|
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
|
-
|
160
|
-
|
161
|
-
|
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(
|
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(
|
176
|
-
(new_image -
|
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(
|
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(
|
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
|
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
|
11
|
-
|
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 :
|
16
|
-
|
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(
|
19
|
-
|
19
|
+
def initialize(image_path, base_image_path, options = {})
|
20
|
+
@image_path = Pathname.new(image_path)
|
20
21
|
|
21
|
-
@new_file_name =
|
22
|
-
@
|
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
|
-
@
|
25
|
+
@base_image_path = Pathname.new(base_image_path)
|
27
26
|
|
28
|
-
@
|
29
|
-
@
|
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
|
-
|
37
|
-
@driver = driver_klass.new(@new_file_name, @old_file_name, **@driver_options)
|
30
|
+
@driver_options = options.dup
|
38
31
|
|
39
|
-
|
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
|
-
|
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
|
-
|
54
|
-
old_image, new_image = preprocess_images(images, driver)
|
43
|
+
comparison = load_and_process_images
|
55
44
|
|
56
|
-
|
45
|
+
unless driver.same_dimension?(comparison)
|
46
|
+
@error_message = build_error_for_different_dimensions(comparison)
|
47
|
+
return false
|
48
|
+
end
|
57
49
|
|
58
|
-
|
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
|
-
|
68
|
-
return
|
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
|
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
|
-
|
65
|
+
@error_message = nil
|
80
66
|
|
81
|
-
|
82
|
-
old_image, new_image = preprocess_images(images, driver)
|
67
|
+
@error_message = _different?
|
83
68
|
|
84
|
-
|
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
|
-
|
93
|
-
|
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
|
-
|
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
|
-
|
114
|
-
|
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(
|
120
|
-
driver.save_image_to(
|
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
|
125
|
-
@
|
91
|
+
def image_files_exist?
|
92
|
+
@base_image_path.exist? && @image_path.exist?
|
126
93
|
end
|
127
94
|
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
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
|
-
|
136
|
-
result = {
|
137
|
-
area_size: difference_region_area_size,
|
138
|
-
region: difference_coordinates
|
139
|
-
}
|
109
|
+
comparison = load_and_process_images
|
140
110
|
|
141
|
-
driver.
|
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
|
-
"(#{
|
131
|
+
"(#{difference.inspect})",
|
145
132
|
new_file_name,
|
146
|
-
|
147
|
-
|
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
|
152
|
-
|
138
|
+
def skip_area
|
139
|
+
@driver_options[:skip_area]
|
153
140
|
end
|
154
141
|
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
-
difference_region.size
|
142
|
+
def median_filter_window_size
|
143
|
+
@driver_options[:median_filter_window_size]
|
159
144
|
end
|
160
145
|
|
161
|
-
|
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
|
171
|
-
|
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
|
187
|
-
|
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
|
159
|
+
def preprocess_image(image)
|
194
160
|
result = image
|
195
161
|
|
196
|
-
|
197
|
-
|
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
|
201
|
-
result =
|
167
|
+
if skip_area
|
168
|
+
result = ignore_skipped_area(result)
|
202
169
|
end
|
203
170
|
|
204
|
-
if
|
205
|
-
result =
|
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 ||=
|
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
|
-
|
221
|
-
false
|
195
|
+
nil
|
222
196
|
end
|
223
197
|
|
224
|
-
def
|
225
|
-
|
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
|
235
|
-
|
236
|
-
|
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(
|
244
|
-
driver.draw_rectangles(
|
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(
|
250
|
-
skip_areas.reduce(
|
251
|
-
driver.draw_rectangles(
|
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
|