capybara-screenshot-diff 1.6.2 → 1.8.3
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 +102 -0
- 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 +193 -252
- data/lib/capybara/screenshot/diff/drivers/utils.rb +25 -0
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +65 -100
- data/lib/capybara/screenshot/diff/drivers.rb +16 -0
- data/lib/capybara/screenshot/diff/image_compare.rb +138 -154
- data/lib/capybara/screenshot/diff/os.rb +1 -1
- data/lib/capybara/screenshot/diff/region.rb +86 -0
- 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 -210
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +106 -0
- data/lib/capybara/screenshot/diff/test_methods.rb +57 -63
- data/lib/capybara/screenshot/diff/vcs.rb +48 -21
- data/lib/capybara/screenshot/diff/version.rb +1 -1
- data/lib/capybara/screenshot/diff.rb +38 -35
- 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 +30 -25
- data/.gitattributes +0 -4
- data/.github/workflows/lint.yml +0 -25
- data/.github/workflows/test.yml +0 -120
- data/.gitignore +0 -12
- data/.standard.yml +0 -12
- data/CONTRIBUTING.md +0 -22
- data/Dockerfile +0 -60
- data/README.md +0 -555
- 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/rails52.gemfile +0 -6
- 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,61 +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
|
-
# Resets the calculated data about the comparison with regard to the "new_image".
|
33
|
-
# Data about the original image is kept.
|
34
|
-
def reset
|
35
|
-
end
|
36
|
-
|
37
|
-
def shift_distance_equal?
|
38
|
-
warn "[capybara-screenshot-diff] Instead of shift_distance_limit " \
|
39
|
-
"please use median_filter_window_size and color_distance_limit options"
|
40
|
-
chunky_png_comparator.quick_equal?
|
41
|
-
end
|
42
|
-
|
43
|
-
def shift_distance_different?
|
44
|
-
warn "[capybara-screenshot-diff] Instead of shift_distance_limit " \
|
45
|
-
"please use median_filter_window_size and color_distance_limit options"
|
46
|
-
chunky_png_comparator.different?
|
47
|
-
end
|
48
|
-
|
49
|
-
def find_difference_region(new_image, old_image, color_distance_limit, _shift_distance_limit, _area_size_limit, fast_fail: false)
|
50
|
-
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])
|
51
23
|
region = VipsUtil.difference_region_by(diff_mask)
|
24
|
+
region = nil if region && same_as?(region, base_image)
|
52
25
|
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
def size(region)
|
57
|
-
return 0 unless region
|
58
|
-
|
59
|
-
(region[2] - region[0]) * (region[3] - region[1])
|
60
|
-
end
|
61
|
-
|
62
|
-
def adds_error_details_to(_log)
|
63
|
-
end
|
26
|
+
result = Difference.new(region, {}, comparison)
|
64
27
|
|
65
|
-
|
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
|
66
33
|
|
67
|
-
|
68
|
-
dimension(i) == dimensions || i.width < dimensions[0] || i.height < dimensions[1]
|
34
|
+
result
|
69
35
|
end
|
70
36
|
|
71
|
-
def crop(
|
72
|
-
i.crop(
|
37
|
+
def crop(region, i)
|
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
|
73
45
|
end
|
74
46
|
|
75
47
|
def filter_image_with_median(image, median_filter_window_size)
|
@@ -77,81 +49,69 @@ module Capybara
|
|
77
49
|
end
|
78
50
|
|
79
51
|
def add_black_box(memo, region)
|
80
|
-
memo.draw_rect([0, 0, 0, 0], *region, fill: true)
|
81
|
-
end
|
82
|
-
|
83
|
-
def chunky_png_comparator
|
84
|
-
@chunky_png_comparator ||= ImageCompare.new(
|
85
|
-
@new_file_name,
|
86
|
-
@old_file_name,
|
87
|
-
**@options.merge(driver: :chunky_png, tolerance: nil, median_filter_window_size: nil)
|
88
|
-
)
|
52
|
+
memo.draw_rect([0, 0, 0, 0], *region.to_top_left_corner_coordinates, fill: true)
|
89
53
|
end
|
90
54
|
|
91
55
|
def difference_level(diff_mask, old_img, _region = nil)
|
92
56
|
VipsUtil.difference_area_size_by(diff_mask).to_f / image_area_size(old_img)
|
93
57
|
end
|
94
58
|
|
95
|
-
|
96
|
-
width_for(old_img) * height_for(old_img)
|
97
|
-
end
|
98
|
-
|
99
|
-
def height_for(image)
|
100
|
-
image.height
|
101
|
-
end
|
102
|
-
|
103
|
-
def width_for(image)
|
104
|
-
image.width
|
105
|
-
end
|
59
|
+
MAX_FILENAME_LENGTH = 200
|
106
60
|
|
61
|
+
# Vips could not work with the same file. Per each process we require to create new file
|
107
62
|
def save_image_to(image, filename)
|
108
|
-
|
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|
|
67
|
+
image.write_to_file(tmp_image_filename)
|
68
|
+
FileUtils.mv(tmp_image_filename, filename)
|
69
|
+
end
|
109
70
|
end
|
110
71
|
|
111
72
|
def resize_image_to(image, new_width, new_height)
|
112
|
-
image.resize(
|
73
|
+
image.resize(new_width.to_f / new_height)
|
113
74
|
end
|
114
75
|
|
115
|
-
def load_images(old_file_name, new_file_name
|
116
|
-
[
|
76
|
+
def load_images(old_file_name, new_file_name)
|
77
|
+
[from_file(old_file_name), from_file(new_file_name)]
|
117
78
|
end
|
118
79
|
|
119
80
|
def from_file(filename)
|
120
|
-
result = ::Vips::Image.new_from_file(filename)
|
81
|
+
result = ::Vips::Image.new_from_file(filename.to_s)
|
121
82
|
|
122
|
-
result = result.colourspace(
|
83
|
+
result = result.colourspace(:srgb) if result.bands < 3
|
123
84
|
result = result.bandjoin(255) if result.bands == 3
|
124
85
|
|
125
86
|
result
|
126
87
|
end
|
127
88
|
|
128
|
-
def dimension_changed?(org_image, new_image)
|
129
|
-
return false if dimension(org_image) == dimension(new_image)
|
130
|
-
|
131
|
-
change_msg = [org_image, new_image].map { |i| "#{i.width}x#{i.height}" }.join(" => ")
|
132
|
-
warn "Image size has changed for #{@new_file_name}: #{change_msg}"
|
133
|
-
|
134
|
-
true
|
135
|
-
end
|
136
|
-
|
137
89
|
def dimension(image)
|
138
|
-
[image
|
90
|
+
[width_for(image), height_for(image)]
|
139
91
|
end
|
140
92
|
|
141
|
-
def draw_rectangles(images,
|
93
|
+
def draw_rectangles(images, region, rgba, offset: 0)
|
142
94
|
images.map do |image|
|
143
|
-
image.draw_rect(rgba, left -
|
95
|
+
image.draw_rect(rgba, region.left - offset, region.top - offset, region.width + (offset * 2), region.height + (offset * 2))
|
144
96
|
end
|
145
97
|
end
|
146
98
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
99
|
+
def same_pixels?(comparison)
|
100
|
+
(comparison.new_image == comparison.base_image).min == 255
|
101
|
+
end
|
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
|
152
111
|
|
112
|
+
class VipsUtil
|
153
113
|
def self.difference_area(old_image, new_image, color_distance: 0)
|
154
|
-
difference_mask = difference_mask(
|
114
|
+
difference_mask = difference_mask(new_image, old_image, color_distance)
|
155
115
|
difference_area_size_by(difference_mask)
|
156
116
|
end
|
157
117
|
|
@@ -160,19 +120,24 @@ module Capybara
|
|
160
120
|
diff_mask.hist_find.to_a[0][0].max
|
161
121
|
end
|
162
122
|
|
163
|
-
def self.difference_mask(
|
164
|
-
(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
|
165
127
|
end
|
166
128
|
|
167
129
|
def self.difference_region_by(diff_mask)
|
168
|
-
columns, rows = diff_mask.project
|
130
|
+
columns, rows = diff_mask.bandor.project
|
169
131
|
|
170
132
|
left = columns.profile[1].min
|
171
|
-
right = columns.width - columns.flip(
|
133
|
+
right = columns.width - columns.flip(:horizontal).profile[1].min
|
134
|
+
|
172
135
|
top = rows.profile[0].min
|
173
|
-
bottom = rows.height - rows.flip(
|
136
|
+
bottom = rows.height - rows.flip(:vertical).profile[0].min
|
137
|
+
|
138
|
+
return nil if right < left || bottom < top
|
174
139
|
|
175
|
-
|
140
|
+
Region.from_edge_coordinates(left, top, right, bottom)
|
176
141
|
end
|
177
142
|
end
|
178
143
|
end
|
@@ -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,186 +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
|
-
:
|
17
|
-
:
|
15
|
+
attr_reader :annotated_image_path, :annotated_base_image_path,
|
16
|
+
:image_path, :base_image_path,
|
17
|
+
:new_file_name, :old_file_name
|
18
18
|
|
19
|
-
def initialize(
|
20
|
-
|
19
|
+
def initialize(image_path, base_image_path, options = {})
|
20
|
+
@image_path = Pathname.new(image_path)
|
21
21
|
|
22
|
-
@new_file_name =
|
23
|
-
@
|
24
|
-
@annotated_old_file_name = "#{new_file_name.chomp(".png")}.committed.png"
|
25
|
-
@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")
|
26
24
|
|
27
|
-
@
|
25
|
+
@base_image_path = Pathname.new(base_image_path)
|
28
26
|
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@shift_distance_limit = options[:shift_distance_limit]
|
32
|
-
@dimensions = options[:dimensions]
|
33
|
-
@skip_area = options[:skip_area]
|
34
|
-
@tolerance = options[:tolerance]
|
35
|
-
@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")
|
36
29
|
|
37
|
-
|
38
|
-
@driver = driver_klass.new(@new_file_name, @old_file_name, **@driver_options)
|
30
|
+
@driver_options = options.dup
|
39
31
|
|
40
|
-
|
32
|
+
@driver = Drivers.for(@driver_options)
|
41
33
|
end
|
42
34
|
|
43
35
|
# Compare the two image files and return `true` or `false` as quickly as possible.
|
44
|
-
# Return
|
36
|
+
# Return falsely if the old file does not exist or the image dimensions do not match.
|
45
37
|
def quick_equal?
|
46
|
-
|
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
|
47
41
|
return true if new_file_size == old_file_size
|
48
42
|
|
49
|
-
|
50
|
-
# return true if old_bytes == new_bytes
|
43
|
+
comparison = load_and_process_images
|
51
44
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
region, meta = driver.find_difference_region(
|
58
|
-
new_image,
|
59
|
-
old_image,
|
60
|
-
@color_distance_limit,
|
61
|
-
@shift_distance_limit,
|
62
|
-
@area_size_limit,
|
63
|
-
fast_fail: true
|
64
|
-
)
|
65
|
-
|
66
|
-
self.difference_region = region
|
67
|
-
|
68
|
-
return true if difference_region_empty?(new_image, region)
|
45
|
+
unless driver.same_dimension?(comparison)
|
46
|
+
@error_message = build_error_for_different_dimensions(comparison)
|
47
|
+
return false
|
48
|
+
end
|
69
49
|
|
70
|
-
return true if
|
50
|
+
return true if driver.same_pixels?(comparison)
|
71
51
|
|
72
|
-
|
52
|
+
# Could not make any difference to be tolerable, so skip and return as not equal
|
53
|
+
return false if without_tolerable_options?
|
73
54
|
|
74
|
-
|
75
|
-
return true
|
55
|
+
@difference = driver.find_difference_region(comparison)
|
56
|
+
return true unless @difference.different?
|
76
57
|
|
58
|
+
@error_message = @difference.inspect
|
77
59
|
false
|
78
60
|
end
|
79
61
|
|
80
|
-
# Compare the two
|
62
|
+
# Compare the two image referenced by this object, and return `true` if they are different,
|
81
63
|
# and `false` if they are the same.
|
82
|
-
# Return `nil` if the old file does not exist or if the image dimensions do not match.
|
83
64
|
def different?
|
84
|
-
|
65
|
+
@error_message = nil
|
85
66
|
|
86
|
-
|
67
|
+
@error_message = _different?
|
87
68
|
|
88
|
-
|
69
|
+
clean_tmp_files unless @error_message
|
89
70
|
|
90
|
-
|
91
|
-
|
71
|
+
!@error_message.nil?
|
72
|
+
end
|
92
73
|
|
93
|
-
|
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
|
-
|
79
|
+
"Screenshot dimension has been changed for #{@new_file_name}: #{change_msg}"
|
80
|
+
end
|
97
81
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
@shift_distance_limit,
|
103
|
-
@area_size_limit
|
104
|
-
)
|
105
|
-
self.difference_region = region
|
82
|
+
def clean_tmp_files
|
83
|
+
@annotated_base_image_path.unlink if @annotated_base_image_path.exist?
|
84
|
+
@annotated_image_path.unlink if @annotated_image_path.exist?
|
85
|
+
end
|
106
86
|
|
107
|
-
|
108
|
-
|
109
|
-
|
87
|
+
def save(image, image_path)
|
88
|
+
driver.save_image_to(image, image_path.to_s)
|
89
|
+
end
|
110
90
|
|
111
|
-
|
112
|
-
|
91
|
+
def image_files_exist?
|
92
|
+
@base_image_path.exist? && @image_path.exist?
|
93
|
+
end
|
113
94
|
|
114
|
-
|
95
|
+
NEW_LINE = "\n"
|
115
96
|
|
116
|
-
|
117
|
-
end
|
97
|
+
attr_reader :error_message
|
118
98
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
File.delete(@annotated_new_file_name) if File.exist?(@annotated_new_file_name)
|
99
|
+
private
|
100
|
+
|
101
|
+
def without_tolerable_options?
|
102
|
+
(@driver_options.keys & TOLERABLE_OPTIONS).empty?
|
124
103
|
end
|
125
104
|
|
126
|
-
|
127
|
-
|
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?
|
108
|
+
|
109
|
+
comparison = load_and_process_images
|
128
110
|
|
129
|
-
|
130
|
-
|
131
|
-
@skip_area.to_a.flatten.each_slice(4) do |region|
|
132
|
-
annotated_images = driver.draw_rectangles(annotated_images, region, SKIP_COLOR)
|
111
|
+
unless driver.same_dimension?(comparison)
|
112
|
+
return build_error_for_different_dimensions(comparison)
|
133
113
|
end
|
134
|
-
save(*annotated_images, @annotated_old_file_name, @annotated_new_file_name)
|
135
|
-
end
|
136
114
|
|
137
|
-
|
138
|
-
|
139
|
-
driver.
|
115
|
+
return not_different if driver.same_pixels?(comparison)
|
116
|
+
|
117
|
+
@difference = driver.find_difference_region(comparison)
|
118
|
+
return not_different unless @difference.different?
|
119
|
+
|
120
|
+
different(@difference)
|
140
121
|
end
|
141
122
|
|
142
|
-
def
|
143
|
-
|
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)
|
144
127
|
end
|
145
128
|
|
146
|
-
def
|
147
|
-
|
148
|
-
|
129
|
+
def build_error_message(difference)
|
130
|
+
[
|
131
|
+
"(#{difference.inspect})",
|
132
|
+
new_file_name,
|
133
|
+
annotated_base_image_path.to_path,
|
134
|
+
annotated_image_path.to_path
|
135
|
+
].join(NEW_LINE)
|
149
136
|
end
|
150
137
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
region: difference_region
|
155
|
-
}
|
138
|
+
def skip_area
|
139
|
+
@driver_options[:skip_area]
|
140
|
+
end
|
156
141
|
|
157
|
-
|
142
|
+
def median_filter_window_size
|
143
|
+
@driver_options[:median_filter_window_size]
|
144
|
+
end
|
158
145
|
|
159
|
-
|
146
|
+
def dimensions
|
147
|
+
@driver_options[:dimensions]
|
160
148
|
end
|
161
149
|
|
162
|
-
def
|
163
|
-
|
150
|
+
def different(difference)
|
151
|
+
annotate_and_save_images(difference)
|
152
|
+
build_error_message(difference)
|
153
|
+
end
|
164
154
|
|
165
|
-
|
155
|
+
def preprocess_images(images)
|
156
|
+
images.map { |image| preprocess_image(image) }
|
166
157
|
end
|
167
158
|
|
168
|
-
|
159
|
+
def preprocess_image(image)
|
160
|
+
result = image
|
161
|
+
|
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)
|
165
|
+
end
|
169
166
|
|
170
|
-
|
171
|
-
|
167
|
+
if skip_area
|
168
|
+
result = ignore_skipped_area(result)
|
169
|
+
end
|
172
170
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
171
|
+
if median_filter_window_size
|
172
|
+
result = blur_image_by(image, median_filter_window_size)
|
173
|
+
end
|
174
|
+
|
175
|
+
result
|
176
|
+
end
|
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
184
|
end
|
185
185
|
|
186
186
|
def old_file_size
|
187
|
-
@old_file_size ||=
|
187
|
+
@old_file_size ||= image_files_exist? && File.size(@old_file_name)
|
188
188
|
end
|
189
189
|
|
190
190
|
def new_file_size
|
@@ -192,52 +192,36 @@ module Capybara
|
|
192
192
|
end
|
193
193
|
|
194
194
|
def not_different
|
195
|
-
|
196
|
-
false
|
195
|
+
nil
|
197
196
|
end
|
198
197
|
|
199
|
-
def
|
200
|
-
|
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)
|
201
201
|
end
|
202
202
|
|
203
|
-
def
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
[old_img, new_img]
|
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)
|
208
207
|
end
|
209
208
|
|
210
|
-
|
211
|
-
result = image
|
209
|
+
DIFF_COLOR = [255, 0, 0, 255].freeze
|
212
210
|
|
213
|
-
|
214
|
-
|
215
|
-
|
211
|
+
def annotate_difference(image, region)
|
212
|
+
driver.draw_rectangles(Array[image], region, DIFF_COLOR, offset: 1).first
|
213
|
+
end
|
216
214
|
|
217
|
-
|
218
|
-
result = driver.filter_image_with_median(image, @median_filter_window_size)
|
219
|
-
end
|
215
|
+
SKIP_COLOR = [255, 192, 0, 255].freeze
|
220
216
|
|
221
|
-
|
222
|
-
|
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
|
223
220
|
end
|
224
|
-
|
225
|
-
result
|
226
|
-
end
|
227
|
-
|
228
|
-
def difference_region=(region)
|
229
|
-
@left, @top, @right, @bottom = region
|
230
221
|
end
|
222
|
+
end
|
231
223
|
|
232
|
-
|
233
|
-
region.nil? ||
|
234
|
-
(
|
235
|
-
region[1] == height_for(new_image) &&
|
236
|
-
region[0] == width_for(new_image) &&
|
237
|
-
region[2].zero? &&
|
238
|
-
region[3].zero?
|
239
|
-
)
|
240
|
-
end
|
224
|
+
class Comparison < Struct.new(:new_image, :base_image, :options)
|
241
225
|
end
|
242
226
|
end
|
243
227
|
end
|