capybara-screenshot-diff 1.8.3 → 1.9.2
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 +1 -11
- data/capybara-screenshot-diff.gemspec +2 -3
- data/gems.rb +4 -4
- data/lib/capybara/screenshot/diff/area_calculator.rb +56 -0
- data/lib/capybara/screenshot/diff/browser_helpers.rb +5 -5
- data/lib/capybara/screenshot/diff/comparison.rb +6 -0
- data/lib/capybara/screenshot/diff/cucumber.rb +1 -9
- data/lib/capybara/screenshot/diff/difference.rb +8 -4
- data/lib/capybara/screenshot/diff/drivers/base_driver.rb +0 -5
- data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +10 -5
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +14 -3
- data/lib/capybara/screenshot/diff/drivers.rb +1 -1
- data/lib/capybara/screenshot/diff/image_compare.rb +80 -114
- data/lib/capybara/screenshot/diff/region.rb +28 -7
- data/lib/capybara/screenshot/diff/reporters/default.rb +117 -0
- data/lib/capybara/screenshot/diff/screenshot_matcher.rb +36 -74
- data/lib/capybara/screenshot/diff/screenshoter.rb +46 -54
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +65 -63
- data/lib/capybara/screenshot/diff/test_methods.rb +76 -9
- data/lib/capybara/screenshot/diff/{drivers/utils.rb → utils.rb} +0 -7
- data/lib/capybara/screenshot/diff/vcs.rb +25 -23
- data/lib/capybara/screenshot/diff/version.rb +1 -1
- data/lib/capybara/screenshot/diff.rb +1 -111
- data/lib/capybara-screenshot-diff.rb +1 -1
- data/lib/capybara_screenshot_diff/attempts_reporter.rb +49 -0
- data/lib/capybara_screenshot_diff/cucumber.rb +12 -0
- data/lib/capybara_screenshot_diff/dsl.rb +11 -0
- data/lib/capybara_screenshot_diff/minitest.rb +45 -0
- data/lib/capybara_screenshot_diff/rspec.rb +32 -0
- data/lib/capybara_screenshot_diff/snap.rb +55 -0
- data/lib/capybara_screenshot_diff/snap_manager.rb +76 -0
- data/lib/capybara_screenshot_diff.rb +86 -0
- metadata +20 -39
- data/lib/capybara/screenshot/diff/stabilization.rb +0 -0
- data/sig/capybara/screenshot/diff/diff.rbs +0 -28
- data/sig/capybara/screenshot/diff/difference.rbs +0 -33
- data/sig/capybara/screenshot/diff/drivers/base_driver.rbs +0 -63
- data/sig/capybara/screenshot/diff/drivers/browser_helpers.rbs +0 -36
- data/sig/capybara/screenshot/diff/drivers/chunky_png_driver.rbs +0 -89
- data/sig/capybara/screenshot/diff/drivers/utils.rbs +0 -13
- data/sig/capybara/screenshot/diff/drivers/vips_driver.rbs +0 -25
- data/sig/capybara/screenshot/diff/image_compare.rbs +0 -93
- data/sig/capybara/screenshot/diff/os.rbs +0 -11
- data/sig/capybara/screenshot/diff/region.rbs +0 -43
- data/sig/capybara/screenshot/diff/screenshot_matcher.rbs +0 -60
- data/sig/capybara/screenshot/diff/screenshoter.rbs +0 -48
- data/sig/capybara/screenshot/diff/stable_screenshoter.rbs +0 -29
- data/sig/capybara/screenshot/diff/test_methods.rbs +0 -39
- data/sig/capybara/screenshot/diff/vcs.rbs +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87328c5152c18c006da12b39c30f0389ac0f7b71174530473681ac98e035b884
|
4
|
+
data.tar.gz: ec54883ea6de4b75fa5be917fee79e5098a18cd1c5ea3ebbfceb567661e148d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e5f89855ddaf008feab8705c6e13f495b654a7dd382fe209ac5e5917fca00553765dfcf411b90cd83aecd931fec6055502dcf3cf7ee0d69c21e8edaa891cbdc
|
7
|
+
data.tar.gz: 9f402d5ff1ed20d02c0e6fd3d7d025fc7e82287e7d159a6fb892c21cf0f8bf6db74257ba22560631ef179f191cce2de78f099b4a47b637a9d6f91eaededa7ddd
|
data/Rakefile
CHANGED
@@ -17,23 +17,13 @@ Rake::TestTask.new("test:integration") do |t|
|
|
17
17
|
t.test_files = FileList["test/integration/**/*_test.rb"]
|
18
18
|
end
|
19
19
|
|
20
|
-
Rake::TestTask.new("test:signatures") do |t|
|
21
|
-
ENV["RBS_TEST_DOUBLE_SUITE"] ||= "minitest"
|
22
|
-
ENV["RBS_TEST_TARGET"] ||= "Capybara::Screenshot::Diff::*"
|
23
|
-
ENV["RBS_TEST_OPT"] ||= "-rset -rpathname -Isig"
|
24
|
-
|
25
|
-
t.libs << "test"
|
26
|
-
t.ruby_opts << "-r rbs/test/setup"
|
27
|
-
t.test_files = FileList["test/**/*_test.rb"]
|
28
|
-
end
|
29
|
-
|
30
20
|
task "clobber" do
|
31
21
|
puts "Cleanup tmp/*.png"
|
32
22
|
FileUtils.rm_rf(Dir["./tmp/*"])
|
33
23
|
end
|
34
24
|
|
35
25
|
task "test:benchmark" do
|
36
|
-
require_relative "
|
26
|
+
require_relative "scripts/benchmark/find_region_benchmark"
|
37
27
|
benchmark = Capybara::Screenshot::Diff::Drivers::FindRegionBenchmark.new
|
38
28
|
|
39
29
|
puts "For Medium Screen Size: 800x600"
|
@@ -16,14 +16,13 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.license = "MIT"
|
17
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org/"
|
18
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
-
f.match(%r{(^(\.|tmp|bin|test|spec|features|gemfiles|scripts)/)|(^(\.|Dockerfile|CONTRIBUTING|README))})
|
19
|
+
f.match(%r{(^(\.|tmp|bin|test|spec|features|gemfiles|scripts|foo)/)|(^(\.|Dockerfile|CONTRIBUTING|README))})
|
20
20
|
end
|
21
21
|
|
22
22
|
spec.bindir = "exe"
|
23
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ["lib"]
|
25
25
|
|
26
|
-
spec.add_runtime_dependency "actionpack", ">= 6.1", "<
|
26
|
+
spec.add_runtime_dependency "actionpack", ">= 6.1", "< 9"
|
27
27
|
spec.add_runtime_dependency "capybara", ">= 2", "< 4"
|
28
|
-
spec.add_runtime_dependency "chunky_png", "~> 1.3"
|
29
28
|
end
|
data/gems.rb
CHANGED
@@ -8,13 +8,15 @@ gemspec path: __dir__
|
|
8
8
|
gem "rake"
|
9
9
|
|
10
10
|
# Image processing libraries
|
11
|
-
gem "
|
11
|
+
gem "chunky_png", ">= 1.3", require: false
|
12
|
+
gem "oily_png", platform: :ruby, git: "https://github.com/wvanbergen/oily_png", ref: "44042006e79efd42ce4b52c1d78a4c70f0b4b1b2"
|
12
13
|
gem "ruby-vips", require: false
|
13
14
|
|
14
15
|
# Test
|
15
16
|
gem "minitest", require: false
|
16
17
|
gem "minitest-stub-const", require: false
|
17
18
|
gem "simplecov", require: false
|
19
|
+
gem "rspec", require: false
|
18
20
|
|
19
21
|
# Capybara Server
|
20
22
|
gem "puma", require: false
|
@@ -22,8 +24,7 @@ gem "rackup", require: false
|
|
22
24
|
|
23
25
|
# Capybara Drivers
|
24
26
|
gem "cuprite", require: false
|
25
|
-
gem "selenium-webdriver", require: false
|
26
|
-
gem "webdrivers", "~> 5.0", require: false
|
27
|
+
gem "selenium-webdriver", ">= 4.11", require: false
|
27
28
|
|
28
29
|
# Test Frameworks
|
29
30
|
# gem "cucumber", require: false
|
@@ -31,5 +32,4 @@ gem "webdrivers", "~> 5.0", require: false
|
|
31
32
|
|
32
33
|
group :tools do
|
33
34
|
gem "standard", require: false
|
34
|
-
gem "rbs", require: false, platform: :ruby
|
35
35
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Screenshot
|
5
|
+
module Diff
|
6
|
+
class AreaCalculator
|
7
|
+
def initialize(crop_coordinates, skip_area)
|
8
|
+
@crop_coordinates = crop_coordinates
|
9
|
+
@skip_area = skip_area
|
10
|
+
end
|
11
|
+
|
12
|
+
def calculate_crop
|
13
|
+
return @_calculated_crop if defined?(@_calculated_crop)
|
14
|
+
return @_calculated_crop = nil unless @crop_coordinates
|
15
|
+
|
16
|
+
# TODO: Move out from this class, this should be done on before screenshot and should not depend on Browser
|
17
|
+
@crop_coordinates = BrowserHelpers.bounds_for_css(@crop_coordinates).first if @crop_coordinates.is_a?(String)
|
18
|
+
@_calculated_crop = Region.from_edge_coordinates(*@crop_coordinates)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Cast skip areas params into Region
|
22
|
+
# and if there is crop then makes absolute coordinates to eb relative to crop top left corner
|
23
|
+
def calculate_skip_area
|
24
|
+
return nil unless @skip_area
|
25
|
+
|
26
|
+
crop_region = calculate_crop
|
27
|
+
skip_area = Array(@skip_area)
|
28
|
+
|
29
|
+
css_selectors, coords_list = skip_area.compact.partition { |region| region.is_a? String }
|
30
|
+
regions, coords_list = coords_list.partition { |region| region.is_a? Region }
|
31
|
+
|
32
|
+
regions.concat(build_regions_for(BrowserHelpers.bounds_for_css(*css_selectors))) unless css_selectors.empty?
|
33
|
+
regions.concat(build_regions_for(coords_list.flatten.each_slice(4))) unless coords_list.empty?
|
34
|
+
|
35
|
+
regions.compact!
|
36
|
+
|
37
|
+
if crop_region
|
38
|
+
regions
|
39
|
+
.map! { |region| crop_region.find_relative_intersect(region) }
|
40
|
+
.filter! { |region| region&.present? }
|
41
|
+
end
|
42
|
+
|
43
|
+
regions
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def build_regions_for(coordinates)
|
49
|
+
coordinates
|
50
|
+
.map { |entry| Region.from_edge_coordinates(*entry) }
|
51
|
+
.tap { |it| it.compact! }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -30,13 +30,13 @@ module Capybara
|
|
30
30
|
|
31
31
|
IMAGE_WAIT_SCRIPT = <<~JS
|
32
32
|
function pending_image() {
|
33
|
-
|
33
|
+
const images = document.images
|
34
34
|
for (var i = 0; i < images.length; i++) {
|
35
|
-
if (!images[i].complete) {
|
36
|
-
return images[i].src
|
35
|
+
if (!images[i].complete && images[i].loading !== "lazy") {
|
36
|
+
return images[i].src
|
37
37
|
}
|
38
38
|
}
|
39
|
-
return false
|
39
|
+
return false
|
40
40
|
}(window)
|
41
41
|
JS
|
42
42
|
|
@@ -83,7 +83,7 @@ module Capybara
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def self.region_for(element)
|
86
|
-
element.evaluate_script(GET_BOUNDING_CLIENT_RECT_SCRIPT).map { |point| point.negative? ? 0 : point.to_i }
|
86
|
+
element.evaluate_script(GET_BOUNDING_CLIENT_RECT_SCRIPT).map { |point| point.negative? ? 0 : point.ceil.to_i }
|
87
87
|
end
|
88
88
|
|
89
89
|
def self.session
|
@@ -1,11 +1,3 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "capybara/screenshot/diff/test_methods"
|
5
|
-
|
6
|
-
World(Capybara::Screenshot::Diff::TestMethods)
|
7
|
-
|
8
|
-
Before do
|
9
|
-
Capybara::Screenshot::Diff.delayed = false
|
10
|
-
Capybara::Screenshot::BrowserHelpers.resize_to(Capybara::Screenshot.window_size) if Capybara::Screenshot.window_size
|
11
|
-
end
|
3
|
+
require "capybara_screenshot_diff/cucumber"
|
@@ -5,13 +5,17 @@ require "json"
|
|
5
5
|
module Capybara
|
6
6
|
module Screenshot
|
7
7
|
module Diff
|
8
|
-
class Difference < Struct.new(:region, :meta, :comparison)
|
8
|
+
class Difference < Struct.new(:region, :meta, :comparison, :failed_by)
|
9
9
|
def different?
|
10
|
-
!(blank? || tolerable?)
|
10
|
+
failed? || !(blank? || tolerable?)
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
|
13
|
+
def equal?
|
14
|
+
!different?
|
15
|
+
end
|
16
|
+
|
17
|
+
def failed?
|
18
|
+
!!failed_by
|
15
19
|
end
|
16
20
|
|
17
21
|
def options
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "chunky_png"
|
4
3
|
require "capybara/screenshot/diff/difference"
|
5
4
|
|
6
5
|
module Capybara
|
@@ -31,10 +30,6 @@ module Capybara
|
|
31
30
|
def dimension(image)
|
32
31
|
[width_for(image), height_for(image)]
|
33
32
|
end
|
34
|
-
|
35
|
-
def inscribed?(dimensions, i)
|
36
|
-
width_for(i) < dimensions[0] || height_for(i) < dimensions[1]
|
37
|
-
end
|
38
33
|
end
|
39
34
|
end
|
40
35
|
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
begin
|
4
|
+
require "chunky_png"
|
5
|
+
rescue LoadError => e
|
6
|
+
raise 'Required chunky_png gem is missing. Add `gem "chunky_png"` to Gemfile' if e.message.match?(/chunky_png/i)
|
7
|
+
raise
|
8
|
+
end
|
4
9
|
|
5
10
|
require "capybara/screenshot/diff/drivers/base_driver"
|
6
11
|
|
@@ -35,12 +40,12 @@ module Capybara
|
|
35
40
|
i.crop(*region.to_top_left_corner_coordinates)
|
36
41
|
end
|
37
42
|
|
38
|
-
def from_file(
|
39
|
-
ChunkyPNG::Image.from_file(
|
43
|
+
def from_file(filename_or_path)
|
44
|
+
ChunkyPNG::Image.from_file(filename_or_path.to_s)
|
40
45
|
end
|
41
46
|
|
42
47
|
def save_image_to(image, filename)
|
43
|
-
image.save(filename)
|
48
|
+
image.save(filename, :fast_rgba)
|
44
49
|
end
|
45
50
|
|
46
51
|
def resize_image_to(image, new_width, new_height)
|
@@ -48,7 +53,7 @@ module Capybara
|
|
48
53
|
end
|
49
54
|
|
50
55
|
def load_image_files(old_file_name, file_name)
|
51
|
-
[
|
56
|
+
[old_file_name.binread, file_name.binread]
|
52
57
|
end
|
53
58
|
|
54
59
|
def draw_rectangles(images, region, (r, g, b), offset: 0)
|
@@ -15,6 +15,8 @@ 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
|
+
DEFAULT_HIGHLIGHT_COLOR = [255, 0, 0, 255].freeze
|
19
|
+
|
18
20
|
class VipsDriver < BaseDriver
|
19
21
|
def find_difference_region(comparison)
|
20
22
|
new_image, base_image, options = comparison.new_image, comparison.base_image, comparison.options
|
@@ -26,9 +28,8 @@ module Capybara
|
|
26
28
|
result = Difference.new(region, {}, comparison)
|
27
29
|
|
28
30
|
unless result.blank?
|
29
|
-
meta =
|
30
|
-
meta[:
|
31
|
-
result.meta = meta
|
31
|
+
result.meta[:difference_level] = difference_level(diff_mask, base_image) if comparison.options[:tolerance]
|
32
|
+
result.meta[:diff_mask] = diff_mask
|
32
33
|
end
|
33
34
|
|
34
35
|
result
|
@@ -49,6 +50,8 @@ module Capybara
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def add_black_box(memo, region)
|
53
|
+
return memo unless region
|
54
|
+
|
52
55
|
memo.draw_rect([0, 0, 0, 0], *region.to_top_left_corner_coordinates, fill: true)
|
53
56
|
end
|
54
57
|
|
@@ -100,6 +103,14 @@ module Capybara
|
|
100
103
|
(comparison.new_image == comparison.base_image).min == 255
|
101
104
|
end
|
102
105
|
|
106
|
+
def merge(new_image, base_image)
|
107
|
+
base_image.composite2(new_image, :over)
|
108
|
+
end
|
109
|
+
|
110
|
+
def highlight_mask(diff_mask, merged_image, color: DEFAULT_HIGHLIGHT_COLOR)
|
111
|
+
diff_mask.ifthenelse(color, merged_image * 0.75)
|
112
|
+
end
|
113
|
+
|
103
114
|
private
|
104
115
|
|
105
116
|
def same_as?(region, base_image)
|
@@ -5,7 +5,7 @@ module Capybara
|
|
5
5
|
module Diff
|
6
6
|
module Drivers
|
7
7
|
def self.for(driver_options = {})
|
8
|
-
driver_option = driver_options.fetch(:driver, :chunky_png)
|
8
|
+
driver_option = driver_options.is_a?(Hash) ? driver_options.fetch(:driver, :chunky_png) : driver_options
|
9
9
|
return driver_option unless driver_option.is_a?(Symbol)
|
10
10
|
|
11
11
|
Utils.find_driver_class_for(driver_option).new
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "capybara/screenshot/diff/comparison"
|
4
|
+
|
3
5
|
module Capybara
|
4
6
|
module Screenshot
|
5
7
|
module Diff
|
@@ -11,22 +13,13 @@ module Capybara
|
|
11
13
|
TOLERABLE_OPTIONS = [:tolerance, :color_distance_limit, :shift_distance_limit, :area_size_limit].freeze
|
12
14
|
|
13
15
|
attr_reader :driver, :driver_options
|
14
|
-
|
15
|
-
attr_reader :
|
16
|
-
:image_path, :base_image_path,
|
17
|
-
:new_file_name, :old_file_name
|
16
|
+
attr_reader :image_path, :base_image_path
|
17
|
+
attr_reader :difference, :error_message
|
18
18
|
|
19
19
|
def initialize(image_path, base_image_path, options = {})
|
20
20
|
@image_path = Pathname.new(image_path)
|
21
|
-
|
22
|
-
@new_file_name = @image_path.to_s
|
23
|
-
@annotated_image_path = @image_path.sub_ext(".diff.png")
|
24
|
-
|
25
21
|
@base_image_path = Pathname.new(base_image_path)
|
26
22
|
|
27
|
-
@old_file_name = @base_image_path.to_s
|
28
|
-
@annotated_base_image_path = @base_image_path.sub_ext(".diff.png")
|
29
|
-
|
30
23
|
@driver_options = options.dup
|
31
24
|
|
32
25
|
@driver = Drivers.for(@driver_options)
|
@@ -35,104 +28,104 @@ module Capybara
|
|
35
28
|
# Compare the two image files and return `true` or `false` as quickly as possible.
|
36
29
|
# Return falsely if the old file does not exist or the image dimensions do not match.
|
37
30
|
def quick_equal?
|
38
|
-
|
39
|
-
|
40
|
-
#
|
31
|
+
require_images_exists!
|
32
|
+
|
33
|
+
# NOTE: This is very fuzzy logic, but so far it's helps to support current performance.
|
41
34
|
return true if new_file_size == old_file_size
|
42
35
|
|
43
36
|
comparison = load_and_process_images
|
44
37
|
|
45
38
|
unless driver.same_dimension?(comparison)
|
46
|
-
|
39
|
+
self.difference = build_failed_difference(comparison, {different_dimensions: true})
|
47
40
|
return false
|
48
41
|
end
|
49
42
|
|
50
|
-
|
43
|
+
if driver.same_pixels?(comparison)
|
44
|
+
self.difference = build_no_difference(comparison)
|
45
|
+
return true
|
46
|
+
end
|
51
47
|
|
52
|
-
# Could not make any difference to be tolerable, so skip and return as not equal
|
48
|
+
# NOTE: Could not make any difference to be tolerable, so skip and return as not equal.
|
53
49
|
return false if without_tolerable_options?
|
54
50
|
|
55
|
-
|
56
|
-
return true unless @difference.different?
|
51
|
+
self.difference = driver.find_difference_region(comparison)
|
57
52
|
|
58
|
-
|
59
|
-
false
|
53
|
+
!difference.different?
|
60
54
|
end
|
61
55
|
|
62
56
|
# Compare the two image referenced by this object, and return `true` if they are different,
|
63
57
|
# and `false` if they are the same.
|
64
58
|
def different?
|
65
|
-
|
59
|
+
processed.difference.different?
|
60
|
+
end
|
66
61
|
|
67
|
-
|
62
|
+
def reporter
|
63
|
+
@reporter ||= begin
|
64
|
+
current_difference = difference || build_no_difference(nil)
|
65
|
+
Capybara::Screenshot::Diff::Reporters::Default.new(current_difference)
|
66
|
+
end
|
67
|
+
end
|
68
68
|
|
69
|
-
|
69
|
+
def processed?
|
70
|
+
!!difference
|
71
|
+
end
|
70
72
|
|
71
|
-
|
73
|
+
def processed
|
74
|
+
self.difference = find_difference unless processed?
|
75
|
+
@error_message ||= reporter.generate
|
76
|
+
self
|
72
77
|
end
|
73
78
|
|
74
|
-
|
75
|
-
change_msg = [comparison.base_image, comparison.new_image]
|
76
|
-
.map { |i| driver.dimension(i).join("x") }
|
77
|
-
.join(" => ")
|
79
|
+
private
|
78
80
|
|
79
|
-
|
81
|
+
def find_difference
|
82
|
+
require_images_exists!
|
83
|
+
|
84
|
+
comparison = load_and_process_images
|
85
|
+
|
86
|
+
unless driver.same_dimension?(comparison)
|
87
|
+
return build_failed_difference(comparison, {different_dimensions: true})
|
88
|
+
end
|
89
|
+
|
90
|
+
if driver.same_pixels?(comparison)
|
91
|
+
build_no_difference(comparison)
|
92
|
+
else
|
93
|
+
driver.find_difference_region(comparison)
|
94
|
+
end
|
80
95
|
end
|
81
96
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
97
|
+
def require_images_exists!
|
98
|
+
raise ArgumentError, "There is no original (base) screenshot version to compare, located: #{base_image_path}" unless base_image_path.exist?
|
99
|
+
raise ArgumentError, "There is no new screenshot version to compare, located: #{image_path}" unless image_path.exist?
|
85
100
|
end
|
86
101
|
|
87
|
-
def
|
88
|
-
|
102
|
+
def difference=(new_difference)
|
103
|
+
@error_message = nil
|
104
|
+
@reporter = nil
|
105
|
+
@difference = new_difference
|
89
106
|
end
|
90
107
|
|
91
108
|
def image_files_exist?
|
92
109
|
@base_image_path.exist? && @image_path.exist?
|
93
110
|
end
|
94
111
|
|
95
|
-
NEW_LINE = "\n"
|
96
|
-
|
97
|
-
attr_reader :error_message
|
98
|
-
|
99
|
-
private
|
100
|
-
|
101
112
|
def without_tolerable_options?
|
102
113
|
(@driver_options.keys & TOLERABLE_OPTIONS).empty?
|
103
114
|
end
|
104
115
|
|
105
|
-
def
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
return build_error_for_different_dimensions(comparison)
|
113
|
-
end
|
114
|
-
|
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)
|
116
|
+
def build_failed_difference(comparison, failed_by)
|
117
|
+
Difference.new(
|
118
|
+
nil,
|
119
|
+
{difference_level: nil, max_color_distance: 0},
|
120
|
+
comparison,
|
121
|
+
failed_by
|
122
|
+
)
|
121
123
|
end
|
122
124
|
|
123
125
|
def load_and_process_images
|
124
|
-
images = driver.load_images(
|
126
|
+
images = driver.load_images(base_image_path, image_path)
|
125
127
|
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)
|
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)
|
128
|
+
Comparison.new(new_image, base_image, @driver_options, driver, image_path, base_image_path)
|
136
129
|
end
|
137
130
|
|
138
131
|
def skip_area
|
@@ -143,15 +136,6 @@ module Capybara
|
|
143
136
|
@driver_options[:median_filter_window_size]
|
144
137
|
end
|
145
138
|
|
146
|
-
def dimensions
|
147
|
-
@driver_options[:dimensions]
|
148
|
-
end
|
149
|
-
|
150
|
-
def different(difference)
|
151
|
-
annotate_and_save_images(difference)
|
152
|
-
build_error_message(difference)
|
153
|
-
end
|
154
|
-
|
155
139
|
def preprocess_images(images)
|
156
140
|
images.map { |image| preprocess_image(image) }
|
157
141
|
end
|
@@ -159,17 +143,19 @@ module Capybara
|
|
159
143
|
def preprocess_image(image)
|
160
144
|
result = image
|
161
145
|
|
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
|
166
|
-
|
167
146
|
if skip_area
|
168
147
|
result = ignore_skipped_area(result)
|
169
148
|
end
|
170
149
|
|
171
150
|
if median_filter_window_size
|
172
|
-
|
151
|
+
if driver.is_a?(Drivers::VipsDriver)
|
152
|
+
result = blur_image_by(image, median_filter_window_size)
|
153
|
+
else
|
154
|
+
warn(
|
155
|
+
"[capybara-screenshot-diff] Median filter has been skipped for #{image_path} " \
|
156
|
+
"because it is not supported by #{driver.class.name}"
|
157
|
+
)
|
158
|
+
end
|
173
159
|
end
|
174
160
|
|
175
161
|
result
|
@@ -180,48 +166,28 @@ module Capybara
|
|
180
166
|
end
|
181
167
|
|
182
168
|
def ignore_skipped_area(image)
|
183
|
-
skip_area
|
169
|
+
skip_area&.reduce(image) { |memo, region| driver.add_black_box(memo, region) }
|
184
170
|
end
|
185
171
|
|
186
172
|
def old_file_size
|
187
|
-
|
173
|
+
base_image_path.size
|
188
174
|
end
|
189
175
|
|
190
176
|
def new_file_size
|
191
|
-
|
192
|
-
end
|
193
|
-
|
194
|
-
def not_different
|
195
|
-
nil
|
177
|
+
image_path.size
|
196
178
|
end
|
197
179
|
|
198
|
-
def
|
199
|
-
|
200
|
-
|
180
|
+
def build_no_difference(comparison = nil)
|
181
|
+
Difference.new(
|
182
|
+
nil,
|
183
|
+
{difference_level: nil, max_color_distance: 0},
|
184
|
+
comparison || build_comparison
|
185
|
+
).freeze
|
201
186
|
end
|
202
187
|
|
203
|
-
def
|
204
|
-
|
205
|
-
image = annotate_skip_areas(image, difference.skip_area) if difference.skip_area
|
206
|
-
save(image, image_path.to_path)
|
207
|
-
end
|
208
|
-
|
209
|
-
DIFF_COLOR = [255, 0, 0, 255].freeze
|
210
|
-
|
211
|
-
def annotate_difference(image, region)
|
212
|
-
driver.draw_rectangles(Array[image], region, DIFF_COLOR, offset: 1).first
|
188
|
+
def build_comparison
|
189
|
+
Capybara::Screenshot::Diff::Comparison.new(nil, nil, driver_options, driver, image_path, base_image_path).freeze
|
213
190
|
end
|
214
|
-
|
215
|
-
SKIP_COLOR = [255, 192, 0, 255].freeze
|
216
|
-
|
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
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
class Comparison < Struct.new(:new_image, :base_image, :options)
|
225
191
|
end
|
226
192
|
end
|
227
193
|
end
|
@@ -7,13 +7,6 @@ class Region
|
|
7
7
|
@x, @y, @width, @height = x, y, width, height
|
8
8
|
end
|
9
9
|
|
10
|
-
def self.from_top_left_corner_coordinates(x, y, width, height)
|
11
|
-
return nil unless x && y && width && height
|
12
|
-
return nil if width < 0 || height < 0
|
13
|
-
|
14
|
-
Region.new(x, y, width, height)
|
15
|
-
end
|
16
|
-
|
17
10
|
def self.from_edge_coordinates(left, top, right, bottom)
|
18
11
|
return nil unless left && top && right && bottom
|
19
12
|
return nil if right < left || bottom < top
|
@@ -83,4 +76,32 @@ class Region
|
|
83
76
|
def cover?(x, y)
|
84
77
|
left <= x && x <= right && top <= y && y <= bottom
|
85
78
|
end
|
79
|
+
|
80
|
+
def empty?
|
81
|
+
width.zero? || height.zero?
|
82
|
+
end
|
83
|
+
|
84
|
+
def blank?
|
85
|
+
empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
def present?
|
89
|
+
!empty?
|
90
|
+
end
|
91
|
+
|
92
|
+
def inspect
|
93
|
+
"Region(x: #{x}, y: #{y}, width: #{width}, height: #{height})"
|
94
|
+
end
|
95
|
+
|
96
|
+
# need to add this method to make it work with assert_equal
|
97
|
+
def ==(other)
|
98
|
+
case other
|
99
|
+
when Region
|
100
|
+
x == other.x && y == other.y && width == other.width && height == other.height
|
101
|
+
when Array
|
102
|
+
to_a == other
|
103
|
+
else
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
86
107
|
end
|