capybara-screenshot-diff 1.7.1 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|