capybara-screenshot-diff 1.6.3 → 1.8.3
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 +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 +18 -0
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +61 -102
- 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 -208
- 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 +15 -19
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e7e3bdfacb7c2fad912c72ade765c9ec6a50f306048a374d8056fc02c27d643
|
4
|
+
data.tar.gz: c1527905de61a1d801c545729d68b811ba91db5902c1ff209b4dc212fa252971
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9c7868fe6d13263db039cf2a7fbb85234abee8f3bc05dd51fc5cc78579a96c1de3f2275782f5c806e9b154472ad51a991cfad1847a54d4811460597a3a4b8fe
|
7
|
+
data.tar.gz: 209f71e33b2b4bdead447293c0e286de6b6e1d945f4e12889e1b4f94675bb8bc035956f19a9897ceeedf278999598bb095991ecd34f2eaffa219d088ea19d577
|
data/Rakefile
CHANGED
@@ -16,3 +16,32 @@ Rake::TestTask.new("test:integration") do |t|
|
|
16
16
|
t.libs << "lib"
|
17
17
|
t.test_files = FileList["test/integration/**/*_test.rb"]
|
18
18
|
end
|
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
|
+
task "clobber" do
|
31
|
+
puts "Cleanup tmp/*.png"
|
32
|
+
FileUtils.rm_rf(Dir["./tmp/*"])
|
33
|
+
end
|
34
|
+
|
35
|
+
task "test:benchmark" do
|
36
|
+
require_relative "./scripts/benchmark/find_region_benchmark"
|
37
|
+
benchmark = Capybara::Screenshot::Diff::Drivers::FindRegionBenchmark.new
|
38
|
+
|
39
|
+
puts "For Medium Screen Size: 800x600"
|
40
|
+
benchmark.for_medium_size_screens
|
41
|
+
|
42
|
+
puts ""
|
43
|
+
puts "*" * 100
|
44
|
+
|
45
|
+
puts "For Small Screen Size: 80x60"
|
46
|
+
benchmark.for_small_images
|
47
|
+
end
|
@@ -12,15 +12,18 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.summary = "Track your GUI changes with diff assertions"
|
13
13
|
spec.description = "Save screen shots and track changes with graphical diff"
|
14
14
|
spec.homepage = "https://github.com/donv/capybara-screenshot-diff"
|
15
|
-
spec.required_ruby_version = ">=
|
15
|
+
spec.required_ruby_version = ">= 3.0.0"
|
16
16
|
spec.license = "MIT"
|
17
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org/"
|
18
|
-
spec.files = `git ls-files -z`.split("\x0").reject
|
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))})
|
20
|
+
end
|
21
|
+
|
19
22
|
spec.bindir = "exe"
|
20
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
24
|
spec.require_paths = ["lib"]
|
22
25
|
|
23
|
-
spec.add_runtime_dependency "actionpack", ">=
|
26
|
+
spec.add_runtime_dependency "actionpack", ">= 6.1", "< 8"
|
24
27
|
spec.add_runtime_dependency "capybara", ">= 2", "< 4"
|
25
28
|
spec.add_runtime_dependency "chunky_png", "~> 1.3"
|
26
29
|
end
|
data/gems.rb
CHANGED
@@ -8,7 +8,7 @@ gemspec path: __dir__
|
|
8
8
|
gem "rake"
|
9
9
|
|
10
10
|
# Image processing libraries
|
11
|
-
gem "oily_png", platform: :ruby
|
11
|
+
gem "oily_png", platform: :ruby, git: "https://github.com/donv/oily_png", branch: "patch-2"
|
12
12
|
gem "ruby-vips", require: false
|
13
13
|
|
14
14
|
# Test
|
@@ -18,12 +18,18 @@ gem "simplecov", require: false
|
|
18
18
|
|
19
19
|
# Capybara Server
|
20
20
|
gem "puma", require: false
|
21
|
+
gem "rackup", require: false
|
21
22
|
|
22
23
|
# Capybara Drivers
|
23
24
|
gem "cuprite", require: false
|
24
25
|
gem "selenium-webdriver", require: false
|
25
|
-
gem "webdrivers", require: false
|
26
|
+
gem "webdrivers", "~> 5.0", require: false
|
27
|
+
|
28
|
+
# Test Frameworks
|
29
|
+
# gem "cucumber", require: false
|
30
|
+
# gem "cucumber-rails", require: false
|
26
31
|
|
27
32
|
group :tools do
|
28
33
|
gem "standard", require: false
|
34
|
+
gem "rbs", require: false, platform: :ruby
|
29
35
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "region"
|
4
|
+
|
5
|
+
module Capybara
|
6
|
+
module Screenshot
|
7
|
+
module BrowserHelpers
|
8
|
+
def self.resize_to(window_size)
|
9
|
+
if session.driver.respond_to?(:resize)
|
10
|
+
session.driver.resize(*window_size)
|
11
|
+
elsif BrowserHelpers.selenium?
|
12
|
+
session.driver.browser.manage.window.resize_to(*window_size)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.selenium?
|
17
|
+
current_capybara_driver_class <= Capybara::Selenium::Driver
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.window_size_is_wrong?(expected_window_size = nil)
|
21
|
+
selenium? && expected_window_size &&
|
22
|
+
session.driver.browser.manage.window.size != ::Selenium::WebDriver::Dimension.new(*expected_window_size)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.bounds_for_css(*css_selectors)
|
26
|
+
css_selectors.reduce([]) do |regions, selector|
|
27
|
+
regions.concat(all_visible_regions_for(selector))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
IMAGE_WAIT_SCRIPT = <<~JS
|
32
|
+
function pending_image() {
|
33
|
+
var images = document.images;
|
34
|
+
for (var i = 0; i < images.length; i++) {
|
35
|
+
if (!images[i].complete) {
|
36
|
+
return images[i].src;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
return false;
|
40
|
+
}(window)
|
41
|
+
JS
|
42
|
+
|
43
|
+
HIDE_CARET_SCRIPT = <<~JS
|
44
|
+
if (!document.getElementById('csdHideCaretStyle')) {
|
45
|
+
let style = document.createElement('style');
|
46
|
+
style.setAttribute('id', 'csdHideCaretStyle');
|
47
|
+
document.head.appendChild(style);
|
48
|
+
let styleSheet = style.sheet;
|
49
|
+
styleSheet.insertRule("* { caret-color: transparent !important; }", 0);
|
50
|
+
}
|
51
|
+
JS
|
52
|
+
|
53
|
+
def self.hide_caret
|
54
|
+
session.execute_script(HIDE_CARET_SCRIPT)
|
55
|
+
end
|
56
|
+
|
57
|
+
FIND_ACTIVE_ELEMENT_SCRIPT = <<~JS
|
58
|
+
function activeElement(){
|
59
|
+
const ae = document.activeElement;
|
60
|
+
if (ae.nodeName === "INPUT" || ae.nodeName === "TEXTAREA") {
|
61
|
+
ae.blur();
|
62
|
+
return ae;
|
63
|
+
}
|
64
|
+
return null;
|
65
|
+
}(window);
|
66
|
+
JS
|
67
|
+
|
68
|
+
def self.blur_from_focused_element
|
69
|
+
session.evaluate_script(FIND_ACTIVE_ELEMENT_SCRIPT)
|
70
|
+
end
|
71
|
+
|
72
|
+
GET_BOUNDING_CLIENT_RECT_SCRIPT = <<~JS
|
73
|
+
[
|
74
|
+
this.getBoundingClientRect().left,
|
75
|
+
this.getBoundingClientRect().top,
|
76
|
+
this.getBoundingClientRect().right,
|
77
|
+
this.getBoundingClientRect().bottom
|
78
|
+
]
|
79
|
+
JS
|
80
|
+
|
81
|
+
def self.all_visible_regions_for(selector)
|
82
|
+
BrowserHelpers.session.all(selector, visible: true).map(&method(:region_for))
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.region_for(element)
|
86
|
+
element.evaluate_script(GET_BOUNDING_CLIENT_RECT_SCRIPT).map { |point| point.negative? ? 0 : point.to_i }
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.session
|
90
|
+
Capybara.current_session
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.pending_image_to_load
|
94
|
+
BrowserHelpers.session.evaluate_script(IMAGE_WAIT_SCRIPT)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.current_capybara_driver_class
|
98
|
+
session.driver.class
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "capybara/screenshot/diff"
|
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
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Capybara
|
6
|
+
module Screenshot
|
7
|
+
module Diff
|
8
|
+
class Difference < Struct.new(:region, :meta, :comparison)
|
9
|
+
def different?
|
10
|
+
!(blank? || tolerable?)
|
11
|
+
end
|
12
|
+
|
13
|
+
def base_image
|
14
|
+
comparison.base_image
|
15
|
+
end
|
16
|
+
|
17
|
+
def options
|
18
|
+
comparison.options
|
19
|
+
end
|
20
|
+
|
21
|
+
def tolerance
|
22
|
+
options[:tolerance]
|
23
|
+
end
|
24
|
+
|
25
|
+
def skip_area
|
26
|
+
options[:skip_area]
|
27
|
+
end
|
28
|
+
|
29
|
+
def area_size_limit
|
30
|
+
options[:area_size_limit]
|
31
|
+
end
|
32
|
+
|
33
|
+
def blank?
|
34
|
+
region.nil? || region_area_size.zero?
|
35
|
+
end
|
36
|
+
|
37
|
+
def region_area_size
|
38
|
+
region&.size || 0
|
39
|
+
end
|
40
|
+
|
41
|
+
def ratio
|
42
|
+
meta[:difference_level]
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_h
|
46
|
+
{area_size: region_area_size, region: coordinates}.merge!(meta)
|
47
|
+
end
|
48
|
+
|
49
|
+
def coordinates
|
50
|
+
region&.to_edge_coordinates
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
to_h.to_json
|
55
|
+
end
|
56
|
+
|
57
|
+
def tolerable?
|
58
|
+
!!((area_size_limit && area_size_limit >= region_area_size) || (tolerance && tolerance >= ratio))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "chunky_png"
|
4
|
+
require "capybara/screenshot/diff/difference"
|
5
|
+
|
6
|
+
module Capybara
|
7
|
+
module Screenshot
|
8
|
+
module Diff
|
9
|
+
# Compare two images and determine if they are equal, different, or within some comparison
|
10
|
+
# range considering color values and difference area size.
|
11
|
+
module Drivers
|
12
|
+
class BaseDriver
|
13
|
+
PNG_EXTENSION = ".png"
|
14
|
+
|
15
|
+
def same_dimension?(comparison)
|
16
|
+
dimension(comparison.base_image) == dimension(comparison.new_image)
|
17
|
+
end
|
18
|
+
|
19
|
+
def height_for(image)
|
20
|
+
image.height
|
21
|
+
end
|
22
|
+
|
23
|
+
def width_for(image)
|
24
|
+
image.width
|
25
|
+
end
|
26
|
+
|
27
|
+
def image_area_size(image)
|
28
|
+
width_for(image) * height_for(image)
|
29
|
+
end
|
30
|
+
|
31
|
+
def dimension(image)
|
32
|
+
[width_for(image), height_for(image)]
|
33
|
+
end
|
34
|
+
|
35
|
+
def inscribed?(dimensions, i)
|
36
|
+
width_for(i) < dimensions[0] || height_for(i) < dimensions[1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|