capybara-screenshot-diff 1.10.2 → 1.10.3

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
  SHA256:
3
- metadata.gz: ffbb0b50c61fc1dcab03db91256897bed64caa09d65d37ee39765aec52e3d6fb
4
- data.tar.gz: 1c762e2084bf2629eae25fb5e38792ce2232bdd62edbdbf1bff5381c074b3edd
3
+ metadata.gz: 24a35d04ca598ca2d4acd2ebff19dc3d839a6440de5b4744ec6f7a376d113df0
4
+ data.tar.gz: 5f1072b91792ce8266e74b9a916bd1f86ae5de04204e666f5d59111605480b3c
5
5
  SHA512:
6
- metadata.gz: 97356f772b607b1ff67b146d1726baabbef696b362187c5aa5833657a4a443e8a6e8a30dedec49a07bda4d115e2a7a5c8482e4ba64a958072d1aaf6cd9dab8a3
7
- data.tar.gz: '05880832a40006772c5fd72b28a4ebe9c28ca36ef49e70f132cab55acf1b97135bcd86bdb4d1b19893961fb344b0268d1c5d708354412c9c99efad03d3da85ab'
6
+ metadata.gz: bf944436f2e42f0e9c45bae1ec25e61822d92dac0ccfba259d91dc5bea18233f8397dcd6fa93ac4ef1d8157d0302f512ffa5930031f05656c8a2d2b3fd385a42
7
+ data.tar.gz: ee75582ecad3092d278ec3d4565b4501483cf71dff433a11f1af912c6848523f6a38223e983de1c7b4b629d88a4e14409051b5ea59490b5719cb9e4487ca8175
@@ -5,6 +5,12 @@ require_relative "region"
5
5
  module Capybara
6
6
  module Screenshot
7
7
  module BrowserHelpers
8
+ def self.resize_window_if_needed
9
+ if ::Capybara::Screenshot.respond_to?(:window_size) && ::Capybara::Screenshot.window_size
10
+ resize_to(::Capybara::Screenshot.window_size)
11
+ end
12
+ end
13
+
8
14
  def self.resize_to(window_size)
9
15
  if session.driver.respond_to?(:resize)
10
16
  session.driver.resize(*window_size)
@@ -74,7 +74,7 @@ class Region
74
74
  end
75
75
 
76
76
  def cover?(x, y)
77
- left <= x && x <= right && top <= y && y <= bottom
77
+ x.between?(left, right) && y.between?(top, bottom)
78
78
  end
79
79
 
80
80
  def empty?
@@ -100,7 +100,7 @@ module Capybara
100
100
  attempts_reporter = CapybaraScreenshotDiff::AttemptsReporter.new(snapshot, @comparison_options, {wait: wait, stability_time_limit: stability_time_limit})
101
101
 
102
102
  # TODO: Move fail to the queue after tests passed
103
- fail(attempts_reporter.generate)
103
+ raise CapybaraScreenshotDiff::UnstableImage, attempts_reporter.generate
104
104
  end
105
105
  end
106
106
  end
@@ -35,26 +35,6 @@ module Capybara
35
35
  @screenshot_group = nil
36
36
  @screenshot_section = nil
37
37
  @test_screenshot_errors = nil
38
- @test_screenshots = []
39
- end
40
-
41
- # Verifies that all scheduled screenshots do not show any unintended differences.
42
- #
43
- # @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
44
- # @return [Array, nil] Returns an array of error messages if there are screenshot differences, otherwise nil.
45
- # @note This method is typically called at the end of a test to assert all screenshots are as expected.
46
- def verify_screenshots!(screenshots = @test_screenshots)
47
- return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
48
-
49
- test_screenshot_errors = screenshots.map do |caller, name, compare|
50
- assert_image_not_changed(caller, name, compare)
51
- end
52
-
53
- test_screenshot_errors.compact!
54
-
55
- test_screenshot_errors.presence
56
- ensure
57
- screenshots.clear
58
38
  end
59
39
 
60
40
  # Builds the full name for a screenshot, incorporating counters and group names for uniqueness.
@@ -83,8 +63,9 @@ module Capybara
83
63
 
84
64
  def screenshot_group(name)
85
65
  @screenshot_group = name.to_s
86
- @screenshot_counter = @screenshot_group.present? ? 0 : nil
87
- return unless Screenshot.active? && name.present?
66
+ @screenshot_counter = (@screenshot_group.nil? || @screenshot_group.empty?) ? nil : 0
67
+ name_present = !(name.nil? || name.empty?)
68
+ return unless Screenshot.active? && name_present
88
69
 
89
70
  FileUtils.rm_rf screenshot_dir
90
71
  end
@@ -97,14 +78,14 @@ module Capybara
97
78
  # @param job [Array(Array(String), String, ImageCompare)] The job to be scheduled, consisting of the caller context, screenshot name, and comparison object.
98
79
  # @return [Boolean] Always returns true, indicating the job was successfully scheduled.
99
80
  def schedule_match_job(job)
100
- (@test_screenshots ||= []) << job
81
+ CapybaraScreenshotDiff.add_assertion(job)
101
82
  true
102
83
  end
103
84
 
104
85
  def group_parts
105
86
  parts = []
106
- parts << @screenshot_section if @screenshot_section.present?
107
- parts << @screenshot_group if @screenshot_group.present?
87
+ parts << @screenshot_section unless @screenshot_section.nil? || @screenshot_section.empty?
88
+ parts << @screenshot_group unless @screenshot_group.nil? || @screenshot_group.empty?
108
89
  parts
109
90
  end
110
91
 
@@ -120,13 +101,18 @@ module Capybara
120
101
  def screenshot(name, skip_stack_frames: 0, **options)
121
102
  return false unless Screenshot.active?
122
103
 
104
+ # setup
123
105
  screenshot_full_name = build_full_name(name)
124
- job = build_screenshot_matches_job(screenshot_full_name, options)
125
106
 
126
- caller = caller(skip_stack_frames + 1).reject { |l| l =~ /gems\/(activesupport|minitest|railties)/ }
127
- unless job
107
+ # exercise
108
+ match_changes_job = build_screenshot_matches_job(screenshot_full_name, options)
109
+
110
+ # verify
111
+ backtrace = caller(skip_stack_frames + 1).reject { |l| l =~ /gems\/(activesupport|minitest|railties)/ }
112
+
113
+ unless match_changes_job
128
114
  if Screenshot::Diff.fail_if_new
129
- _raise_error(<<-ERROR.strip_heredoc, caller)
115
+ _raise_error(<<-ERROR.strip_heredoc, backtrace)
130
116
  No existing screenshot found for #{screenshot_full_name}!
131
117
  To stop seeing this error disable by `Capybara::Screenshot::Diff.fail_if_new=false`
132
118
  ERROR
@@ -135,39 +121,26 @@ module Capybara
135
121
  return false
136
122
  end
137
123
 
138
- job.prepend(caller)
124
+ match_changes_job.prepend(backtrace)
139
125
 
140
126
  if Screenshot::Diff.delayed
141
- schedule_match_job(job)
127
+ schedule_match_job(match_changes_job)
142
128
  else
143
- error_msg = assert_image_not_changed(*job)
144
- _raise_error(error_msg, caller(2)) if error_msg
129
+ invoke_match_job(match_changes_job)
145
130
  end
146
131
  end
147
132
 
148
- # Asserts that an image has not changed compared to its baseline.
149
- #
150
- # @param caller [Array(String)] The caller context, used for error reporting.
151
- # @param name [String] The name of the screenshot being verified.
152
- # @param comparison [Object] The comparison object containing the result and details of the comparison.
153
- # @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
154
- # @note This method is used internally to verify individual screenshots.
155
- def assert_image_not_changed(caller, name, comparison)
156
- result = comparison.different?
157
-
158
- # Cleanup after comparisons
159
- if !result && comparison.base_image_path.exist?
160
- FileUtils.mv(comparison.base_image_path, comparison.image_path, force: true)
161
- elsif !comparison.dimensions_changed?
162
- FileUtils.rm_rf(comparison.base_image_path)
163
- end
133
+ private
164
134
 
165
- return unless result
135
+ def invoke_match_job(job)
136
+ error_msg = CapybaraScreenshotDiff::ScreenshotAssertion.from(job).validate
166
137
 
167
- "Screenshot does not match for '#{name}': #{comparison.error_message}\n#{caller.join("\n")}"
168
- end
138
+ if error_msg
139
+ _raise_error(error_msg, job[0])
140
+ end
169
141
 
170
- private
142
+ true
143
+ end
171
144
 
172
145
  def _raise_error(error_msg, backtrace)
173
146
  raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg).tap { _1.set_backtrace(backtrace) }
@@ -53,7 +53,7 @@ module Capybara
53
53
  end
54
54
 
55
55
  svn_info = `svn info #{screenshot_path} #{SILENCE_ERRORS}`
56
- if svn_info.present?
56
+ unless svn_info.empty?
57
57
  wc_root = svn_info.slice(/(?<=Working Copy Root Path: ).*$/)
58
58
  checksum = svn_info.slice(/(?<=Checksum: ).*$/)
59
59
 
@@ -3,7 +3,7 @@
3
3
  module Capybara
4
4
  module Screenshot
5
5
  module Diff
6
- VERSION = "1.10.2"
6
+ VERSION = "1.10.3"
7
7
  end
8
8
  end
9
9
  end
@@ -6,7 +6,5 @@ World(::CapybaraScreenshotDiff::DSL)
6
6
 
7
7
  Before do
8
8
  Capybara::Screenshot::Diff.delayed = false
9
- if Capybara::Screenshot.active? && Capybara::Screenshot.window_size
10
- Capybara::Screenshot::BrowserHelpers.resize_to(Capybara::Screenshot.window_size)
11
- end
9
+ Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
12
10
  end
@@ -2,10 +2,17 @@
2
2
 
3
3
  require "capybara_screenshot_diff"
4
4
  require "capybara/screenshot/diff/test_methods"
5
+ require_relative "screenshot_assertion"
5
6
 
6
7
  module CapybaraScreenshotDiff
7
8
  module DSL
8
9
  include Capybara::DSL
9
10
  include Capybara::Screenshot::Diff::TestMethods
11
+
12
+ alias_method :_screenshot, :screenshot
13
+ def screenshot(name, **args)
14
+ assertion = CapybaraScreenshotDiff::ScreenshotAssertion.new(name, **args) { _screenshot(name, **args) }
15
+ CapybaraScreenshotDiff.add_assertion(assertion)
16
+ end
10
17
  end
11
18
  end
@@ -20,29 +20,29 @@ module CapybaraScreenshotDiff
20
20
  include ::CapybaraScreenshotDiff::DSL
21
21
 
22
22
  def screenshot(*args, skip_stack_frames: 0, **opts)
23
- assert_nothing_raised do
24
- super(*args, skip_stack_frames: skip_stack_frames + 3, **opts)
25
- end
23
+ self.assertions += 1
24
+
25
+ super(*args, skip_stack_frames: skip_stack_frames + 3, **opts)
26
+ rescue ::CapybaraScreenshotDiff::ExpectationNotMet => e
27
+ raise ::Minitest::Assertion, e.message
26
28
  end
27
29
 
28
30
  alias_method :assert_matches_screenshot, :screenshot
29
31
 
30
- def self.included(klass)
31
- klass.setup do
32
- if ::Capybara::Screenshot.window_size
33
- ::Capybara::Screenshot::BrowserHelpers.resize_to(::Capybara::Screenshot.window_size)
34
- end
35
- end
36
-
37
- klass.teardown do
38
- errors = verify_screenshots!(@test_screenshots)
39
-
40
- if errors.present?
41
- assertion = ::Minitest::Assertion.new(errors.join("\n\n"))
42
- assertion.set_backtrace []
43
- failures << assertion
44
- end
45
- end
32
+ def setup
33
+ super
34
+ ::Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
35
+ end
36
+
37
+ def before_teardown
38
+ super
39
+ CapybaraScreenshotDiff.verify
40
+ rescue CapybaraScreenshotDiff::ExpectationNotMet => e
41
+ assertion = ::Minitest::Assertion.new(e)
42
+ assertion.set_backtrace []
43
+ failures << assertion
44
+ ensure
45
+ CapybaraScreenshotDiff.reset
46
46
  end
47
47
  end
48
48
  end
@@ -13,20 +13,24 @@ RSpec::Matchers.define :match_screenshot do |name, **options|
13
13
  end
14
14
 
15
15
  RSpec.configure do |config|
16
- config.include ::CapybaraScreenshotDiff::DSL, type: :feature
17
- config.include ::CapybaraScreenshotDiff::DSL, type: :system
16
+ config.include CapybaraScreenshotDiff::DSL, type: :feature
17
+ config.include CapybaraScreenshotDiff::DSL, type: :system
18
18
 
19
- config.after do
20
- if self.class.include?(::CapybaraScreenshotDiff::DSL) && ::Capybara::Screenshot.active?
21
- errors = verify_screenshots!(@test_screenshots)
22
- # TODO: Use rspec/mock approach to postpone verification
23
- raise ::CapybaraScreenshotDiff::ExpectationNotMet, errors.join("\n") if errors && !errors.empty?
19
+ config.before do
20
+ if self.class.include?(CapybaraScreenshotDiff::DSL)
21
+ Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
24
22
  end
25
23
  end
26
24
 
27
- config.before do
28
- if self.class.include?(::CapybaraScreenshotDiff::DSL) && ::Capybara::Screenshot.window_size
29
- ::Capybara::Screenshot::BrowserHelpers.resize_to(::Capybara::Screenshot.window_size)
25
+ config.after do
26
+ if self.class.include?(CapybaraScreenshotDiff::DSL)
27
+ begin
28
+ CapybaraScreenshotDiff.verify
29
+ rescue CapybaraScreenshotDiff::ExpectationNotMet => e
30
+ raise RSpec::Expectations::ExpectationNotMetError, e.message
31
+ ensure
32
+ CapybaraScreenshotDiff.reset
33
+ end
30
34
  end
31
35
  end
32
36
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module CapybaraScreenshotDiff
6
+ class ScreenshotAssertion
7
+ attr_reader :name, :args
8
+ attr_accessor :compare, :caller
9
+
10
+ def initialize(name, **args, &block)
11
+ @name = name
12
+ @args = args
13
+
14
+ yield(self) if block_given?
15
+ end
16
+
17
+ def self.from(screenshot_job)
18
+ return screenshot_job if screenshot_job.is_a?(ScreenshotAssertion)
19
+
20
+ caller, name, compare = screenshot_job
21
+ ScreenshotAssertion.new(name).tap do |it|
22
+ it.caller = caller
23
+ it.compare = compare
24
+ end
25
+ end
26
+
27
+ def validate
28
+ return unless compare
29
+
30
+ self.class.assert_image_not_changed(caller, name, compare)
31
+ end
32
+
33
+ # Verifies that all scheduled screenshots do not show any unintended differences.
34
+ #
35
+ # @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
36
+ # @return [Array, nil] Returns an array of error messages if there are screenshot differences, otherwise nil.
37
+ # @note This method is typically called at the end of a test to assert all screenshots are as expected.
38
+ def self.verify_screenshots!(screenshots)
39
+ return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
40
+
41
+ test_screenshot_errors = screenshots.map do |assertion|
42
+ assertion.validate
43
+ end
44
+
45
+ test_screenshot_errors.compact!
46
+
47
+ test_screenshot_errors.empty? ? nil : test_screenshot_errors
48
+ ensure
49
+ screenshots&.clear
50
+ end
51
+
52
+ # Asserts that an image has not changed compared to its baseline.
53
+ #
54
+ # @param backtrace [Array(String)] The caller context, used for error reporting.
55
+ # @param name [String] The name of the screenshot being verified.
56
+ # @param comparison [Object] The comparison object containing the result and details of the comparison.
57
+ # @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
58
+ # @note This method is used internally to verify individual screenshots.
59
+ def self.assert_image_not_changed(backtrace, name, comparison)
60
+ result = comparison.different?
61
+
62
+ # Cleanup after comparisons
63
+ if !result && comparison.base_image_path.exist?
64
+ FileUtils.mv(comparison.base_image_path, comparison.image_path, force: true)
65
+ elsif !comparison.dimensions_changed?
66
+ FileUtils.rm_rf(comparison.base_image_path)
67
+ end
68
+
69
+ return unless result
70
+
71
+ "Screenshot does not match for '#{name}': #{comparison.error_message}\n#{backtrace.join("\n")}"
72
+ end
73
+ end
74
+
75
+ class AssertionRegistry
76
+ attr_reader :assertions
77
+
78
+ def initialize
79
+ @assertions = []
80
+ end
81
+
82
+ def add_assertion(assertion)
83
+ assertion = ScreenshotAssertion.from(assertion)
84
+ return unless assertion.compare
85
+
86
+ @assertions.push(assertion)
87
+
88
+ assertion
89
+ end
90
+
91
+ def assertions_present?
92
+ !@assertions.empty?
93
+ end
94
+
95
+ def verify(screenshots = CapybaraScreenshotDiff.assertions)
96
+ result = ScreenshotAssertion.verify_screenshots!(screenshots)
97
+
98
+ raise CapybaraScreenshotDiff::ExpectationNotMet, result.join("\n\n") if result
99
+ end
100
+
101
+ def failed_assertions
102
+ assertions.select { |screenshot_assert| screenshot_assert.compare&.different? }
103
+ end
104
+
105
+ def reset
106
+ @assertions.clear
107
+ end
108
+ end
109
+ end
110
+
111
+ module CapybaraScreenshotDiff
112
+ class << self
113
+ require "forwardable"
114
+ extend Forwardable
115
+
116
+ def registry
117
+ Thread.current[:capybara_screenshot_diff_registry] ||= AssertionRegistry.new
118
+ end
119
+
120
+ def_delegator :registry, :add_assertion
121
+ def_delegator :registry, :assertions
122
+ def_delegator :registry, :assertions_present?
123
+ def_delegator :registry, :failed_assertions
124
+ def_delegator :registry, :reset
125
+ def_delegator :registry, :verify
126
+ end
127
+ end
@@ -10,7 +10,11 @@ require "capybara/screenshot/diff/screenshoter"
10
10
  require "capybara/screenshot/diff/reporters/default"
11
11
 
12
12
  module CapybaraScreenshotDiff
13
- class ExpectationNotMet < StandardError; end
13
+ class CapybaraScreenshotDiffError < StandardError; end
14
+
15
+ class ExpectationNotMet < CapybaraScreenshotDiffError; end
16
+
17
+ class UnstableImage < CapybaraScreenshotDiffError; end
14
18
  end
15
19
 
16
20
  module Capybara
@@ -49,7 +53,7 @@ module Capybara
49
53
  end
50
54
  end
51
55
 
52
- # Module to track screen shot changes
56
+ # Module to track screenshot changes
53
57
  module Diff
54
58
  mattr_accessor(:delayed) { true }
55
59
  mattr_accessor :area_size_limit
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-screenshot-diff
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.2
4
+ version: 1.10.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uwe Kubosch
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2024-12-31 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: actionpack
@@ -88,6 +88,7 @@ files:
88
88
  - lib/capybara_screenshot_diff/dsl.rb
89
89
  - lib/capybara_screenshot_diff/minitest.rb
90
90
  - lib/capybara_screenshot_diff/rspec.rb
91
+ - lib/capybara_screenshot_diff/screenshot_assertion.rb
91
92
  - lib/capybara_screenshot_diff/snap.rb
92
93
  - lib/capybara_screenshot_diff/snap_manager.rb
93
94
  homepage: https://github.com/donv/capybara-screenshot-diff
@@ -109,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
110
  - !ruby/object:Gem::Version
110
111
  version: '0'
111
112
  requirements: []
112
- rubygems_version: 3.6.2
113
+ rubygems_version: 3.6.7
113
114
  specification_version: 4
114
115
  summary: Track your GUI changes with diff assertions
115
116
  test_files: []