capybara-screenshot-diff 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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