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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3b1581dfd6dfe2bc40dfcff8a5414839ed9cffd
4
- data.tar.gz: f149dcc7655cdc88216d93de719805ba5369cc96
3
+ metadata.gz: 69ea31d01c7bd77b12be1cc62ae4c6169e176243
4
+ data.tar.gz: 5730a0c6576d39fa2f71592997ad36fa5ad5b071
5
5
  SHA512:
6
- metadata.gz: c78deed42053ef6d72df85e3d489b458eac585e363370da2753da56d69cbaded5f04c08e736402aa7bb38bbf5b64e0d7d06c08f7302491c9c77e7a27a2c5cab9
7
- data.tar.gz: 15a51febc69814816067a77ba18708e1919e96f286a01794964671a8c55cf38eded0ce35897a0a92b012d7255998d0b34b3ce14e73fb7f0d88f9fe2b1e20ac87
6
+ metadata.gz: 283ed868320336e6da7724961c345d62972d5ed1cc806d00e71e98166c363e786f0589503cf11a9eb7b095ef1859f673ee82dec239f806ef3570ffd0093ed625
7
+ data.tar.gz: 3294ad94ef9d2eaf7c2b138f641759b154cc4352cb62f6b7870023b8473b55ee70e2b3c37f96c668c3aab44d1db9db2a7c84dc9bbc196c8bb4b33b56b277e7b6
data/.rubocop.yml CHANGED
@@ -9,6 +9,9 @@ Layout/AlignParameters:
9
9
  EnforcedStyle: with_fixed_indentation
10
10
  IndentationWidth: 4
11
11
 
12
+ Layout/IndentHeredoc:
13
+ EnforcedStyle: active_support
14
+
12
15
  Layout/MultilineMethodCallIndentation:
13
16
  EnforcedStyle: indented
14
17
 
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-12 20:35:14 +0200 using RuboCop version 0.49.1.
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: 28
11
+ Max: 37
20
12
 
21
13
  # Offense count: 1
22
14
  # Configuration parameters: CountComments.
23
15
  Metrics/ClassLength:
24
- Max: 180
16
+ Max: 177
25
17
 
26
- # Offense count: 2
18
+ # Offense count: 3
27
19
  Metrics/CyclomaticComplexity:
28
20
  Max: 8
29
21
 
30
- # Offense count: 8
22
+ # Offense count: 9
31
23
  # Configuration parameters: CountComments.
32
24
  Metrics/MethodLength:
33
- Max: 21
25
+ Max: 32
34
26
 
35
- # Offense count: 1
27
+ # Offense count: 2
36
28
  Metrics/PerceivedComplexity:
37
- Max: 9
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/capybara_setup'
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 :screenshot_root
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
- def self.active?
16
- enabled || (enabled.nil? && Diff.enabled)
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
@@ -3,7 +3,7 @@
3
3
  module Capybara
4
4
  module Screenshot
5
5
  module Diff
6
- VERSION = '0.8.0'.freeze
6
+ VERSION = '0.8.1'.freeze
7
7
  end
8
8
  end
9
9
  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.load(File.read('.travis.yml'))
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.0
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-17 00:00:00.000000000 Z
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