capybara-screenshot-diff 0.8.0 → 0.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/.rubocop.yml +3 -0
- data/.rubocop_todo.yml +8 -27
- data/lib/capybara/screenshot/diff.rb +18 -4
- data/lib/capybara/screenshot/diff/image_compare.rb +2 -2
- data/lib/capybara/screenshot/diff/os.rb +24 -0
- data/lib/capybara/screenshot/diff/stabilization.rb +117 -0
- data/lib/capybara/screenshot/diff/test_methods.rb +135 -0
- data/lib/capybara/screenshot/diff/vcs.rb +36 -0
- data/lib/capybara/screenshot/diff/version.rb +1 -1
- data/matrix_test.rb +1 -1
- metadata +6 -3
- data/lib/capybara/screenshot/diff/capybara_setup.rb +0 -259
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69ea31d01c7bd77b12be1cc62ae4c6169e176243
|
|
4
|
+
data.tar.gz: 5730a0c6576d39fa2f71592997ad36fa5ad5b071
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 283ed868320336e6da7724961c345d62972d5ed1cc806d00e71e98166c363e786f0589503cf11a9eb7b095ef1859f673ee82dec239f806ef3570ffd0093ed625
|
|
7
|
+
data.tar.gz: 3294ad94ef9d2eaf7c2b138f641759b154cc4352cb62f6b7870023b8473b55ee70e2b3c37f96c668c3aab44d1db9db2a7c84dc9bbc196c8bb4b33b56b277e7b6
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
|
@@ -1,48 +1,29 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2017-07-
|
|
3
|
+
# on 2017-07-24 15:55:18 +0200 using RuboCop version 0.49.1.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
|
8
8
|
|
|
9
|
-
# Offense count: 1
|
|
10
|
-
# Cop supports --auto-correct.
|
|
11
|
-
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
12
|
-
# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
|
|
13
|
-
Layout/IndentHeredoc:
|
|
14
|
-
Exclude:
|
|
15
|
-
- 'lib/capybara/screenshot/diff/capybara_setup.rb'
|
|
16
|
-
|
|
17
9
|
# Offense count: 7
|
|
18
10
|
Metrics/AbcSize:
|
|
19
|
-
Max:
|
|
11
|
+
Max: 37
|
|
20
12
|
|
|
21
13
|
# Offense count: 1
|
|
22
14
|
# Configuration parameters: CountComments.
|
|
23
15
|
Metrics/ClassLength:
|
|
24
|
-
Max:
|
|
16
|
+
Max: 177
|
|
25
17
|
|
|
26
|
-
# Offense count:
|
|
18
|
+
# Offense count: 3
|
|
27
19
|
Metrics/CyclomaticComplexity:
|
|
28
20
|
Max: 8
|
|
29
21
|
|
|
30
|
-
# Offense count:
|
|
22
|
+
# Offense count: 9
|
|
31
23
|
# Configuration parameters: CountComments.
|
|
32
24
|
Metrics/MethodLength:
|
|
33
|
-
Max:
|
|
25
|
+
Max: 32
|
|
34
26
|
|
|
35
|
-
# Offense count:
|
|
27
|
+
# Offense count: 2
|
|
36
28
|
Metrics/PerceivedComplexity:
|
|
37
|
-
Max:
|
|
38
|
-
|
|
39
|
-
# Offense count: 1
|
|
40
|
-
Performance/Caller:
|
|
41
|
-
Exclude:
|
|
42
|
-
- 'lib/capybara/screenshot/diff/capybara_setup.rb'
|
|
43
|
-
|
|
44
|
-
# Offense count: 1
|
|
45
|
-
# Cop supports --auto-correct.
|
|
46
|
-
Security/YAMLLoad:
|
|
47
|
-
Exclude:
|
|
48
|
-
- 'matrix_test.rb'
|
|
29
|
+
Max: 8
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
require 'capybara/screenshot/diff/version'
|
|
2
2
|
require 'capybara/screenshot/diff/image_compare'
|
|
3
|
-
require 'capybara/screenshot/diff/
|
|
3
|
+
require 'capybara/screenshot/diff/test_methods'
|
|
4
4
|
|
|
5
5
|
module Capybara
|
|
6
6
|
module Screenshot
|
|
7
|
+
extend Os
|
|
7
8
|
mattr_accessor :add_driver_path
|
|
8
9
|
mattr_accessor :add_os_path
|
|
9
10
|
mattr_accessor :blur_active_element
|
|
10
11
|
mattr_accessor :enabled
|
|
11
|
-
mattr_accessor
|
|
12
|
+
mattr_accessor(:screenshot_root) { (defined?(Rails.root) && Rails.root) || File.expand_path('.') }
|
|
12
13
|
mattr_accessor :stability_time_limit
|
|
13
14
|
mattr_accessor :window_size
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
class << self
|
|
17
|
+
def active?
|
|
18
|
+
enabled || (enabled.nil? && Diff.enabled)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def screenshot_area
|
|
22
|
+
parts = ['doc/screenshots']
|
|
23
|
+
parts << Capybara.default_driver.to_s if Capybara::Screenshot.add_driver_path
|
|
24
|
+
parts << os_name if Capybara::Screenshot.add_os_path
|
|
25
|
+
File.join parts
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def screenshot_area_abs
|
|
29
|
+
"#{screenshot_root}/#{screenshot_area}".freeze
|
|
30
|
+
end
|
|
17
31
|
end
|
|
18
32
|
|
|
19
33
|
# Module to track screen shot changes
|
|
@@ -9,13 +9,13 @@ module Capybara
|
|
|
9
9
|
attr_reader :annotated_new_file_name, :annotated_old_file_name, :new_file_name,
|
|
10
10
|
:old_file_name
|
|
11
11
|
|
|
12
|
-
def initialize(new_file_name, dimensions: nil, color_distance_limit: nil,
|
|
12
|
+
def initialize(new_file_name, old_file_name = nil, dimensions: nil, color_distance_limit: nil,
|
|
13
13
|
area_size_limit: nil)
|
|
14
14
|
@new_file_name = new_file_name
|
|
15
15
|
@color_distance_limit = color_distance_limit
|
|
16
16
|
@area_size_limit = area_size_limit
|
|
17
17
|
@dimensions = dimensions
|
|
18
|
-
@old_file_name = "#{new_file_name}~"
|
|
18
|
+
@old_file_name = old_file_name || "#{new_file_name}~"
|
|
19
19
|
@annotated_old_file_name = "#{new_file_name.chomp('.png')}_0.png~"
|
|
20
20
|
@annotated_new_file_name = "#{new_file_name.chomp('.png')}_1.png~"
|
|
21
21
|
reset
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Capybara
|
|
2
|
+
module Screenshot
|
|
3
|
+
module Os
|
|
4
|
+
ON_WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
|
5
|
+
|
|
6
|
+
def os_name
|
|
7
|
+
case RbConfig::CONFIG['host_os']
|
|
8
|
+
when /darwin/
|
|
9
|
+
'macos'
|
|
10
|
+
when /mswin|mingw|cygwin/
|
|
11
|
+
'windows'
|
|
12
|
+
when /linux/
|
|
13
|
+
'linux'
|
|
14
|
+
else
|
|
15
|
+
'unknown'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private def macos?
|
|
20
|
+
os_name == 'macos'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require_relative 'os'
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Screenshot
|
|
5
|
+
module Diff
|
|
6
|
+
module Stabilization
|
|
7
|
+
include Os
|
|
8
|
+
|
|
9
|
+
IMAGE_WAIT_SCRIPT = <<-EOF.strip_heredoc.freeze
|
|
10
|
+
function pending_image() {
|
|
11
|
+
var images = document.images;
|
|
12
|
+
for (var i = 0; i < images.length; i++) {
|
|
13
|
+
if (!images[i].complete) {
|
|
14
|
+
return images[i].src;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}()
|
|
19
|
+
EOF
|
|
20
|
+
|
|
21
|
+
def reduce_retina_image_size(file_name)
|
|
22
|
+
return if !macos? || !selenium? || !Capybara::Screenshot.window_size
|
|
23
|
+
saved_image = ChunkyPNG::Image.from_file(file_name)
|
|
24
|
+
width = Capybara::Screenshot.window_size[0]
|
|
25
|
+
return if saved_image.width < width * 2
|
|
26
|
+
height = (width * saved_image.height) / saved_image.width
|
|
27
|
+
resized_image = saved_image.resample_bilinear(width, height)
|
|
28
|
+
resized_image.save(file_name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def stabilization_images(base_file)
|
|
32
|
+
Dir["#{base_file.chomp('.png')}_x*.png~"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def clean_stabilization_images(base_file)
|
|
36
|
+
FileUtils.rm stabilization_images(base_file)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def prepare_page_for_screenshot
|
|
40
|
+
assert_images_loaded
|
|
41
|
+
if Capybara::Screenshot.blur_active_element
|
|
42
|
+
active_element = execute_script(<<-JS)
|
|
43
|
+
ae = document.activeElement;
|
|
44
|
+
if (ae.nodeName == "INPUT" || ae.nodeName == "TEXTAREA") {
|
|
45
|
+
ae.blur();
|
|
46
|
+
return ae;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
JS
|
|
50
|
+
input = page.driver.send :unwrap_script_result, active_element
|
|
51
|
+
end
|
|
52
|
+
input
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def take_right_size_screenshot(comparison)
|
|
56
|
+
save_screenshot(comparison.new_file_name)
|
|
57
|
+
|
|
58
|
+
# TODO(uwe): Remove when chromedriver takes right size screenshots
|
|
59
|
+
reduce_retina_image_size(comparison.new_file_name)
|
|
60
|
+
# ODOT
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def take_stable_screenshot(comparison)
|
|
64
|
+
input = prepare_page_for_screenshot
|
|
65
|
+
previous_file_name = comparison.old_file_name
|
|
66
|
+
screeenshot_started_at = last_image_change_at = Time.now
|
|
67
|
+
loop.with_index do |_x, i|
|
|
68
|
+
take_right_size_screenshot(comparison)
|
|
69
|
+
|
|
70
|
+
break unless Capybara::Screenshot.stability_time_limit
|
|
71
|
+
if comparison.quick_equal?
|
|
72
|
+
clean_stabilization_images(comparison.new_file_name)
|
|
73
|
+
break
|
|
74
|
+
end
|
|
75
|
+
comparison.reset
|
|
76
|
+
|
|
77
|
+
if previous_file_name
|
|
78
|
+
stabilization_comparison =
|
|
79
|
+
Capybara::Screenshot::Diff::ImageCompare.new(comparison.new_file_name, previous_file_name)
|
|
80
|
+
if stabilization_comparison.quick_equal?
|
|
81
|
+
if (Time.now - last_image_change_at) > Capybara::Screenshot.stability_time_limit
|
|
82
|
+
clean_stabilization_images(comparison.new_file_name)
|
|
83
|
+
break
|
|
84
|
+
end
|
|
85
|
+
next
|
|
86
|
+
else
|
|
87
|
+
last_image_change_at = Time.now
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
assert (Time.now - screeenshot_started_at) < Capybara.default_max_wait_time,
|
|
91
|
+
"Could not get stable screenshot within #{Capybara.default_max_wait_time}s\n" \
|
|
92
|
+
"#{stabilization_images(comparison.new_file_name).join("\n")}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
previous_file_name = "#{comparison.new_file_name.chomp('.png')}_x#{i}.png~"
|
|
96
|
+
|
|
97
|
+
FileUtils.mv comparison.new_file_name, previous_file_name
|
|
98
|
+
end
|
|
99
|
+
ensure
|
|
100
|
+
input.click if input
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def assert_images_loaded(timeout: Capybara.default_max_wait_time)
|
|
104
|
+
return unless respond_to? :evaluate_script
|
|
105
|
+
start = Time.now
|
|
106
|
+
loop do
|
|
107
|
+
pending_image = evaluate_script IMAGE_WAIT_SCRIPT
|
|
108
|
+
break unless pending_image
|
|
109
|
+
assert (Time.now - start) < timeout,
|
|
110
|
+
"Images not loaded after #{timeout}s: #{pending_image.inspect}"
|
|
111
|
+
sleep 0.1
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require 'English'
|
|
2
|
+
require 'capybara'
|
|
3
|
+
require 'action_controller'
|
|
4
|
+
require 'action_dispatch'
|
|
5
|
+
require 'active_support/core_ext/string/strip'
|
|
6
|
+
require_relative 'image_compare'
|
|
7
|
+
require_relative 'stabilization'
|
|
8
|
+
require_relative 'vcs'
|
|
9
|
+
|
|
10
|
+
# TODO(uwe): Move this code to module Capybara::Screenshot::Diff::TestMethods,
|
|
11
|
+
# and use Module#prepend/include to insert.
|
|
12
|
+
# Add the `screenshot` method to ActionDispatch::IntegrationTest
|
|
13
|
+
module Capybara
|
|
14
|
+
module Screenshot
|
|
15
|
+
module Diff
|
|
16
|
+
module TestMethods
|
|
17
|
+
include Capybara::Screenshot::Diff::Stabilization
|
|
18
|
+
include Capybara::Screenshot::Diff::Vcs
|
|
19
|
+
|
|
20
|
+
def initialize(*)
|
|
21
|
+
super
|
|
22
|
+
@screenshot_counter = nil
|
|
23
|
+
@screenshot_group = nil
|
|
24
|
+
@screenshot_section = nil
|
|
25
|
+
@test_screenshot_errors = nil
|
|
26
|
+
@test_screenshots = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def group_parts
|
|
30
|
+
parts = []
|
|
31
|
+
parts << @screenshot_section if @screenshot_section.present?
|
|
32
|
+
parts << @screenshot_group if @screenshot_group.present?
|
|
33
|
+
parts
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def full_name(name)
|
|
37
|
+
File.join group_parts.<<(name).map(&:to_s)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def screenshot_dir
|
|
41
|
+
File.join [Capybara::Screenshot.screenshot_area] + group_parts
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def current_capybara_driver_class
|
|
45
|
+
Capybara.drivers[Capybara.current_driver].call({}).class
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def selenium?
|
|
49
|
+
current_capybara_driver_class <= Capybara::Selenium::Driver
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def poltergeist?
|
|
53
|
+
return false unless defined?(Capybara::Poltergeist::Driver)
|
|
54
|
+
current_capybara_driver_class <= Capybara::Poltergeist::Driver
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def screenshot_section(name)
|
|
58
|
+
@screenshot_section = name.to_s
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def screenshot_group(name)
|
|
62
|
+
@screenshot_group = name.to_s
|
|
63
|
+
@screenshot_counter = 0
|
|
64
|
+
return unless Capybara::Screenshot.active? && name.present?
|
|
65
|
+
FileUtils.rm_rf screenshot_dir
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def screenshot(name, color_distance_limit: Capybara::Screenshot::Diff.color_distance_limit,
|
|
69
|
+
area_size_limit: nil)
|
|
70
|
+
return unless Capybara::Screenshot.active?
|
|
71
|
+
return if window_size_is_wrong?
|
|
72
|
+
if @screenshot_counter
|
|
73
|
+
name = "#{format('%02i', @screenshot_counter)}_#{name}"
|
|
74
|
+
@screenshot_counter += 1
|
|
75
|
+
end
|
|
76
|
+
name = full_name(name)
|
|
77
|
+
file_name = "#{Capybara::Screenshot.screenshot_area_abs}/#{name}.png"
|
|
78
|
+
|
|
79
|
+
FileUtils.mkdir_p File.dirname(file_name)
|
|
80
|
+
comparison = Capybara::Screenshot::Diff::ImageCompare.new(file_name,
|
|
81
|
+
dimensions: Capybara::Screenshot.window_size, color_distance_limit: color_distance_limit,
|
|
82
|
+
area_size_limit: area_size_limit)
|
|
83
|
+
checkout_vcs(name, comparison)
|
|
84
|
+
take_stable_screenshot(comparison)
|
|
85
|
+
return unless comparison.old_file_exists?
|
|
86
|
+
(@test_screenshots ||= []) << [caller(1..1).first, name, comparison]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def window_size_is_wrong?
|
|
90
|
+
selenium? && Capybara::Screenshot.window_size &&
|
|
91
|
+
(!page.driver.chrome? || ON_WINDOWS) && # TODO(uwe): Allow for Chrome when it works
|
|
92
|
+
page.driver.browser.manage.window.size !=
|
|
93
|
+
Selenium::WebDriver::Dimension.new(*Capybara::Screenshot.window_size)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def assert_image_not_changed(caller, name, comparison)
|
|
97
|
+
return unless comparison.different?
|
|
98
|
+
"Screenshot does not match for '#{name}' (area: #{comparison.size}px #{comparison.dimensions}" \
|
|
99
|
+
", max_color_distance: #{comparison.max_color_distance.ceil(1)})\n" \
|
|
100
|
+
"#{comparison.new_file_name}\n#{comparison.annotated_old_file_name}\n" \
|
|
101
|
+
"#{comparison.annotated_new_file_name}\n" \
|
|
102
|
+
"at #{caller}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
# rubocop:enable Metrics/ClassLength
|
|
109
|
+
|
|
110
|
+
module ActionDispatch
|
|
111
|
+
class IntegrationTest
|
|
112
|
+
prepend Capybara::Screenshot::Diff::TestMethods
|
|
113
|
+
|
|
114
|
+
setup do
|
|
115
|
+
if Capybara::Screenshot.window_size
|
|
116
|
+
if selenium?
|
|
117
|
+
# TODO(uwe): Enable for Chrome and non-windows when it works)
|
|
118
|
+
if !page.driver.chrome? || ON_WINDOWS
|
|
119
|
+
page.driver.browser.manage.window.resize_to(*Capybara::Screenshot.window_size)
|
|
120
|
+
end
|
|
121
|
+
elsif poltergeist?
|
|
122
|
+
page.driver.resize(*Capybara::Screenshot.window_size)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
teardown do
|
|
128
|
+
if Capybara::Screenshot::Diff.enabled && @test_screenshots
|
|
129
|
+
test_screenshot_errors = @test_screenshots
|
|
130
|
+
.map { |caller, name, compare| assert_image_not_changed(caller, name, compare) }.compact
|
|
131
|
+
fail(test_screenshot_errors.join("\n\n")) if test_screenshot_errors.any?
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require_relative 'os'
|
|
2
|
+
module Capybara
|
|
3
|
+
module Screenshot
|
|
4
|
+
module Diff
|
|
5
|
+
module Vcs
|
|
6
|
+
SILENCE_ERRORS = Os::ON_WINDOWS ? '2>nul' : '2>/dev/null'
|
|
7
|
+
|
|
8
|
+
def restore_git_revision(name, target_file_name)
|
|
9
|
+
redirect_target = "#{target_file_name} #{SILENCE_ERRORS}"
|
|
10
|
+
`git show HEAD~0:./#{Capybara::Screenshot.screenshot_area}/#{name}.png > #{redirect_target}`
|
|
11
|
+
FileUtils.rm_f(target_file_name) unless $CHILD_STATUS == 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def checkout_vcs(name, comparison)
|
|
15
|
+
svn_file_name = "#{Capybara::Screenshot.screenshot_area_abs}/.svn/text-base/#{name}.png.svn-base"
|
|
16
|
+
if File.exist?(svn_file_name)
|
|
17
|
+
committed_file_name = svn_file_name
|
|
18
|
+
FileUtils.cp committed_file_name, comparison.old_file_name
|
|
19
|
+
else
|
|
20
|
+
svn_info = `svn info #{comparison.new_file_name} #{SILENCE_ERRORS}`
|
|
21
|
+
if svn_info.present?
|
|
22
|
+
wc_root = svn_info.slice(/(?<=Working Copy Root Path: ).*$/)
|
|
23
|
+
checksum = svn_info.slice(/(?<=Checksum: ).*$/)
|
|
24
|
+
if checksum
|
|
25
|
+
committed_file_name = "#{wc_root}/.svn/pristine/#{checksum[0..1]}/#{checksum}.svn-base"
|
|
26
|
+
FileUtils.cp committed_file_name, comparison.old_file_name
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
restore_git_revision(name, comparison.old_file_name)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/matrix_test.rb
CHANGED
|
@@ -5,7 +5,7 @@ system('rubocop --auto-correct') || exit(1)
|
|
|
5
5
|
update_gemfiles = ARGV.delete('--update')
|
|
6
6
|
|
|
7
7
|
require 'yaml'
|
|
8
|
-
travis = YAML.
|
|
8
|
+
travis = YAML.safe_load(File.read('.travis.yml'))
|
|
9
9
|
|
|
10
10
|
def run_script(ruby, env, gemfile)
|
|
11
11
|
env.scan(/\b(?<key>[A-Z_]+)="(?<value>.+?)"/) do |key, value|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capybara-screenshot-diff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Uwe Kubosch
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-07-
|
|
11
|
+
date: 2017-07-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actionpack
|
|
@@ -165,8 +165,11 @@ files:
|
|
|
165
165
|
- gemfiles/rails50.gemfile
|
|
166
166
|
- gemfiles/rails51.gemfile
|
|
167
167
|
- lib/capybara/screenshot/diff.rb
|
|
168
|
-
- lib/capybara/screenshot/diff/capybara_setup.rb
|
|
169
168
|
- lib/capybara/screenshot/diff/image_compare.rb
|
|
169
|
+
- lib/capybara/screenshot/diff/os.rb
|
|
170
|
+
- lib/capybara/screenshot/diff/stabilization.rb
|
|
171
|
+
- lib/capybara/screenshot/diff/test_methods.rb
|
|
172
|
+
- lib/capybara/screenshot/diff/vcs.rb
|
|
170
173
|
- lib/capybara/screenshot/diff/version.rb
|
|
171
174
|
- matrix_test.rb
|
|
172
175
|
homepage: https://github.com/donv/capybara-screenshot-diff
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
require 'capybara'
|
|
2
|
-
require 'capybara/screenshot/diff/image_compare'
|
|
3
|
-
require 'action_controller'
|
|
4
|
-
require 'action_dispatch'
|
|
5
|
-
|
|
6
|
-
# TODO(uwe): Move this code to module Capybara::Screenshot::Diff::TestMethods,
|
|
7
|
-
# and use Module#prepend/include to insert.
|
|
8
|
-
# Add the `screenshot` method to ActionDispatch::IntegrationTest
|
|
9
|
-
# rubocop:disable Metrics/ClassLength
|
|
10
|
-
module ActionDispatch
|
|
11
|
-
class IntegrationTest
|
|
12
|
-
ON_WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
|
13
|
-
SILENCE_ERRORS = ON_WINDOWS ? '2>nul' : '2>/dev/null'
|
|
14
|
-
|
|
15
|
-
def self.os_name
|
|
16
|
-
case RbConfig::CONFIG['host_os']
|
|
17
|
-
when /darwin/
|
|
18
|
-
'macos'
|
|
19
|
-
when /mswin|mingw|cygwin/
|
|
20
|
-
'windows'
|
|
21
|
-
when /linux/
|
|
22
|
-
'linux'
|
|
23
|
-
else
|
|
24
|
-
'unknown'
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def self.macos?
|
|
29
|
-
os_name == 'macos'
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def self.screenshot_root
|
|
33
|
-
Capybara::Screenshot.screenshot_root ||
|
|
34
|
-
(defined?(Rails.root) && Rails.root) || File.expand_path('.')
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def self.screenshot_area
|
|
38
|
-
parts = ['doc/screenshots']
|
|
39
|
-
parts << Capybara.default_driver.to_s if Capybara::Screenshot.add_driver_path
|
|
40
|
-
parts << os_name if Capybara::Screenshot.add_os_path
|
|
41
|
-
File.join parts
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def self.screenshot_area_abs
|
|
45
|
-
"#{screenshot_root}/#{screenshot_area}".freeze
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def initialize(*)
|
|
49
|
-
super
|
|
50
|
-
@screenshot_counter = nil
|
|
51
|
-
@screenshot_group = nil
|
|
52
|
-
@screenshot_section = nil
|
|
53
|
-
@test_screenshot_errors = nil
|
|
54
|
-
@test_screenshots = nil
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def group_parts
|
|
58
|
-
parts = []
|
|
59
|
-
parts << @screenshot_section if @screenshot_section.present?
|
|
60
|
-
parts << @screenshot_group if @screenshot_group.present?
|
|
61
|
-
parts
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def full_name(name)
|
|
65
|
-
File.join group_parts.<<(name).map(&:to_s)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def screenshot_dir
|
|
69
|
-
File.join [self.class.screenshot_area] + group_parts
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
private def current_capybara_driver_class
|
|
73
|
-
Capybara.drivers[Capybara.current_driver].call({}).class
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
private def selenium?
|
|
77
|
-
current_capybara_driver_class <= Capybara::Selenium::Driver
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
private def poltergeist?
|
|
81
|
-
return false unless defined?(Capybara::Poltergeist::Driver)
|
|
82
|
-
current_capybara_driver_class <= Capybara::Poltergeist::Driver
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
setup do
|
|
86
|
-
if Capybara::Screenshot.window_size
|
|
87
|
-
if selenium?
|
|
88
|
-
# TODO(uwe): Enable for Chrome and non-windows when it works)
|
|
89
|
-
if !page.driver.chrome? || ON_WINDOWS
|
|
90
|
-
page.driver.browser.manage.window.resize_to(*Capybara::Screenshot.window_size)
|
|
91
|
-
end
|
|
92
|
-
elsif poltergeist?
|
|
93
|
-
page.driver.resize(*Capybara::Screenshot.window_size)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
teardown do
|
|
99
|
-
if Capybara::Screenshot::Diff.enabled && @test_screenshots
|
|
100
|
-
test_screenshot_errors = @test_screenshots
|
|
101
|
-
.map { |caller, name, compare| assert_image_not_changed(caller, name, compare) }.compact
|
|
102
|
-
fail(test_screenshot_errors.join("\n\n")) if test_screenshot_errors.any?
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def screenshot_section(name)
|
|
107
|
-
@screenshot_section = name.to_s
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def screenshot_group(name)
|
|
111
|
-
@screenshot_group = name.to_s
|
|
112
|
-
@screenshot_counter = 0
|
|
113
|
-
return unless Capybara::Screenshot.active? && name.present?
|
|
114
|
-
FileUtils.rm_rf screenshot_dir
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def screenshot(name, color_distance_limit: Capybara::Screenshot::Diff.color_distance_limit,
|
|
118
|
-
area_size_limit: nil)
|
|
119
|
-
return unless Capybara::Screenshot.active?
|
|
120
|
-
return if window_size_is_wrong?
|
|
121
|
-
if @screenshot_counter
|
|
122
|
-
name = "#{format('%02i', @screenshot_counter)}_#{name}"
|
|
123
|
-
@screenshot_counter += 1
|
|
124
|
-
end
|
|
125
|
-
name = full_name(name)
|
|
126
|
-
file_name = "#{self.class.screenshot_area_abs}/#{name}.png"
|
|
127
|
-
|
|
128
|
-
FileUtils.mkdir_p File.dirname(file_name)
|
|
129
|
-
comparison = Capybara::Screenshot::Diff::ImageCompare.new(file_name,
|
|
130
|
-
dimensions: Capybara::Screenshot.window_size, color_distance_limit: color_distance_limit,
|
|
131
|
-
area_size_limit: area_size_limit)
|
|
132
|
-
checkout_vcs(name, comparison)
|
|
133
|
-
take_stable_screenshot(comparison)
|
|
134
|
-
return unless comparison.old_file_exists?
|
|
135
|
-
(@test_screenshots ||= []) << [caller[0], name, comparison]
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
private def window_size_is_wrong?
|
|
139
|
-
selenium? && Capybara::Screenshot.window_size &&
|
|
140
|
-
(!page.driver.chrome? || ON_WINDOWS) && # TODO(uwe): Allow for Chrome when it works
|
|
141
|
-
page.driver.browser.manage.window.size !=
|
|
142
|
-
Selenium::WebDriver::Dimension.new(*Capybara::Screenshot.window_size)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
private def checkout_vcs(name, comparison)
|
|
146
|
-
svn_file_name = "#{self.class.screenshot_area_abs}/.svn/text-base/#{name}.png.svn-base"
|
|
147
|
-
if File.exist?(svn_file_name)
|
|
148
|
-
committed_file_name = svn_file_name
|
|
149
|
-
FileUtils.cp committed_file_name, comparison.old_file_name
|
|
150
|
-
else
|
|
151
|
-
svn_info = `svn info #{comparison.new_file_name} #{SILENCE_ERRORS}`
|
|
152
|
-
if svn_info.present?
|
|
153
|
-
wc_root = svn_info.slice(/(?<=Working Copy Root Path: ).*$/)
|
|
154
|
-
checksum = svn_info.slice(/(?<=Checksum: ).*$/)
|
|
155
|
-
if checksum
|
|
156
|
-
committed_file_name = "#{wc_root}/.svn/pristine/#{checksum[0..1]}/#{checksum}.svn-base"
|
|
157
|
-
FileUtils.cp committed_file_name, comparison.old_file_name
|
|
158
|
-
end
|
|
159
|
-
else
|
|
160
|
-
restore_git_revision(name, comparison.old_file_name)
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
private def restore_git_revision(name, target_file_name)
|
|
166
|
-
redirect_target = "#{target_file_name} #{SILENCE_ERRORS}"
|
|
167
|
-
`git show HEAD~0:./#{self.class.screenshot_area}/#{name}.png > #{redirect_target}`
|
|
168
|
-
FileUtils.rm_f(target_file_name) unless $CHILD_STATUS == 0
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
IMAGE_WAIT_SCRIPT = <<EOF.freeze
|
|
172
|
-
function pending_image() {
|
|
173
|
-
var images = document.images;
|
|
174
|
-
for (var i = 0; i < images.length; i++) {
|
|
175
|
-
if (!images[i].complete) {
|
|
176
|
-
return images[i].src;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return false;
|
|
180
|
-
}()
|
|
181
|
-
EOF
|
|
182
|
-
|
|
183
|
-
def assert_images_loaded(timeout: Capybara.default_max_wait_time)
|
|
184
|
-
return unless respond_to? :evaluate_script
|
|
185
|
-
start = Time.now
|
|
186
|
-
loop do
|
|
187
|
-
pending_image = evaluate_script IMAGE_WAIT_SCRIPT
|
|
188
|
-
break unless pending_image
|
|
189
|
-
assert (Time.now - start) < timeout,
|
|
190
|
-
"Images not loaded after #{timeout}s: #{pending_image.inspect}"
|
|
191
|
-
sleep 0.1
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
private def take_stable_screenshot(comparison) # rubocop: disable Metrics/AbcSize, Metrics/MethodLength
|
|
196
|
-
assert_images_loaded
|
|
197
|
-
if Capybara::Screenshot.blur_active_element
|
|
198
|
-
active_element =
|
|
199
|
-
execute_script(<<-JS)
|
|
200
|
-
ae=document.activeElement
|
|
201
|
-
if (ae.nodeName == "INPUT" || ae.nodeName == "TEXTAREA"){ae.blur();return ae}
|
|
202
|
-
return null
|
|
203
|
-
JS
|
|
204
|
-
input = page.driver.send :unwrap_script_result, active_element
|
|
205
|
-
end
|
|
206
|
-
previous_file_size = comparison.old_file_size
|
|
207
|
-
screeenshot_started_at = last_image_change_at = Time.now
|
|
208
|
-
loop do
|
|
209
|
-
save_screenshot(comparison.new_file_name)
|
|
210
|
-
|
|
211
|
-
# TODO(uwe): Remove when chromedriver takes right size screenshots
|
|
212
|
-
reduce_retina_image_size(comparison.new_file_name)
|
|
213
|
-
# EMXIF
|
|
214
|
-
|
|
215
|
-
break unless Capybara::Screenshot.stability_time_limit
|
|
216
|
-
break if comparison.quick_equal?
|
|
217
|
-
|
|
218
|
-
new_file_size = comparison.new_file_size
|
|
219
|
-
if previous_file_size
|
|
220
|
-
if new_file_size == previous_file_size
|
|
221
|
-
if (Time.now - last_image_change_at) > Capybara::Screenshot.stability_time_limit
|
|
222
|
-
break
|
|
223
|
-
end
|
|
224
|
-
else
|
|
225
|
-
last_image_change_at = Time.now
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
assert (Time.now - screeenshot_started_at) < Capybara.default_max_wait_time,
|
|
229
|
-
"Could not get stable screenshot within #{Capybara.default_max_wait_time}s"
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
previous_file_size = new_file_size
|
|
233
|
-
comparison.reset
|
|
234
|
-
end
|
|
235
|
-
ensure
|
|
236
|
-
input.click if input
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
private def reduce_retina_image_size(file_name)
|
|
240
|
-
return if !self.class.macos? || !selenium? || !Capybara::Screenshot.window_size
|
|
241
|
-
saved_image = ChunkyPNG::Image.from_file(file_name)
|
|
242
|
-
width = Capybara::Screenshot.window_size[0]
|
|
243
|
-
return if saved_image.width < width * 2
|
|
244
|
-
height = (width * saved_image.height) / saved_image.width
|
|
245
|
-
resized_image = saved_image.resample_bilinear(width, height)
|
|
246
|
-
resized_image.save(file_name)
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def assert_image_not_changed(caller, name, comparison)
|
|
250
|
-
return unless comparison.different?
|
|
251
|
-
"Screenshot does not match for '#{name}' (area: #{comparison.size}px #{comparison.dimensions}" \
|
|
252
|
-
", max_color_distance: #{comparison.max_color_distance.round(1)})\n" \
|
|
253
|
-
"#{comparison.new_file_name}\n#{comparison.annotated_old_file_name}\n" \
|
|
254
|
-
"#{comparison.annotated_new_file_name}\n" \
|
|
255
|
-
"at #{caller}"
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
# rubocop:enable Metrics/ClassLength
|