capybara-screenshot-diff 1.10.3 → 1.12.0

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -0
  3. data/Rakefile +29 -1
  4. data/capybara-screenshot-diff.gemspec +4 -3
  5. data/docs/RELEASE_PREP.md +58 -0
  6. data/docs/UPGRADING.md +390 -0
  7. data/docs/ci-integration.md +208 -0
  8. data/docs/configuration.md +379 -0
  9. data/docs/docker-testing.md +24 -0
  10. data/docs/drivers.md +102 -0
  11. data/docs/framework-setup.md +87 -0
  12. data/docs/images/snap_diff_web_ui.png +0 -0
  13. data/docs/organization.md +226 -0
  14. data/docs/reporters.md +46 -0
  15. data/docs/thread_safety.md +97 -0
  16. data/gems.rb +2 -1
  17. data/lib/capybara/screenshot/diff/area_calculator.rb +1 -1
  18. data/lib/capybara/screenshot/diff/browser_helpers.rb +14 -1
  19. data/lib/capybara/screenshot/diff/comparison.rb +3 -0
  20. data/lib/capybara/screenshot/diff/difference.rb +40 -3
  21. data/lib/capybara/screenshot/diff/difference_finder.rb +97 -0
  22. data/lib/capybara/screenshot/diff/drivers/base_driver.rb +4 -0
  23. data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +22 -24
  24. data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +40 -27
  25. data/lib/capybara/screenshot/diff/image_compare.rb +112 -123
  26. data/lib/capybara/screenshot/diff/image_preprocessor.rb +72 -0
  27. data/lib/capybara/screenshot/diff/reporters/default.rb +10 -11
  28. data/lib/capybara/screenshot/diff/screenshot_matcher.rb +63 -36
  29. data/lib/capybara/screenshot/diff/screenshoter.rb +9 -8
  30. data/lib/capybara/screenshot/diff/stable_screenshoter.rb +7 -9
  31. data/lib/capybara/screenshot/diff/vcs.rb +19 -52
  32. data/lib/capybara/screenshot/diff/version.rb +1 -1
  33. data/lib/capybara_screenshot_diff/backtrace_filter.rb +20 -0
  34. data/lib/capybara_screenshot_diff/cucumber.rb +2 -0
  35. data/lib/capybara_screenshot_diff/dsl.rb +102 -7
  36. data/lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb +15 -0
  37. data/lib/capybara_screenshot_diff/minitest.rb +4 -2
  38. data/lib/capybara_screenshot_diff/reporters/html.rb +137 -0
  39. data/lib/capybara_screenshot_diff/reporters/templates/report.html.erb +463 -0
  40. data/lib/capybara_screenshot_diff/rspec.rb +12 -2
  41. data/lib/capybara_screenshot_diff/screenshot_assertion.rb +61 -23
  42. data/lib/capybara_screenshot_diff/screenshot_namer.rb +81 -0
  43. data/lib/capybara_screenshot_diff/snap.rb +14 -3
  44. data/lib/capybara_screenshot_diff/snap_manager.rb +10 -2
  45. data/lib/capybara_screenshot_diff/static.rb +11 -0
  46. data/lib/capybara_screenshot_diff.rb +30 -5
  47. metadata +47 -8
  48. data/lib/capybara/screenshot/diff/test_methods.rb +0 -157
@@ -7,21 +7,9 @@ module CapybaraScreenshotDiff
7
7
  attr_reader :name, :args
8
8
  attr_accessor :compare, :caller
9
9
 
10
- def initialize(name, **args, &block)
10
+ def initialize(name, **args)
11
11
  @name = name
12
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
13
  end
26
14
 
27
15
  def validate
@@ -30,6 +18,14 @@ module CapybaraScreenshotDiff
30
18
  self.class.assert_image_not_changed(caller, name, compare)
31
19
  end
32
20
 
21
+ def validate!
22
+ error_msg = validate
23
+
24
+ if error_msg
25
+ raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg, caller)
26
+ end
27
+ end
28
+
33
29
  # Verifies that all scheduled screenshots do not show any unintended differences.
34
30
  #
35
31
  # @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
@@ -45,8 +41,6 @@ module CapybaraScreenshotDiff
45
41
  test_screenshot_errors.compact!
46
42
 
47
43
  test_screenshot_errors.empty? ? nil : test_screenshot_errors
48
- ensure
49
- screenshots&.clear
50
44
  end
51
45
 
52
46
  # Asserts that an image has not changed compared to its baseline.
@@ -62,8 +56,6 @@ module CapybaraScreenshotDiff
62
56
  # Cleanup after comparisons
63
57
  if !result && comparison.base_image_path.exist?
64
58
  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
59
  end
68
60
 
69
61
  return unless result
@@ -73,15 +65,15 @@ module CapybaraScreenshotDiff
73
65
  end
74
66
 
75
67
  class AssertionRegistry
76
- attr_reader :assertions
68
+ attr_reader :assertions, :screenshot_namer
77
69
 
78
70
  def initialize
79
71
  @assertions = []
72
+ @screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
80
73
  end
81
74
 
82
75
  def add_assertion(assertion)
83
- assertion = ScreenshotAssertion.from(assertion)
84
- return unless assertion.compare
76
+ return unless assertion&.compare
85
77
 
86
78
  @assertions.push(assertion)
87
79
 
@@ -93,17 +85,24 @@ module CapybaraScreenshotDiff
93
85
  end
94
86
 
95
87
  def verify(screenshots = CapybaraScreenshotDiff.assertions)
88
+ return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
89
+
90
+ failed_assertions = CapybaraScreenshotDiff.registry.failed_assertions
91
+ failed_screenshot = failed_assertions.first
96
92
  result = ScreenshotAssertion.verify_screenshots!(screenshots)
97
93
 
98
- raise CapybaraScreenshotDiff::ExpectationNotMet, result.join("\n\n") if result
94
+ if result
95
+ raise CapybaraScreenshotDiff::ExpectationNotMet.new(result.join("\n\n"), failed_screenshot.caller)
96
+ end
99
97
  end
100
98
 
101
99
  def failed_assertions
102
- assertions.select { |screenshot_assert| screenshot_assert.compare&.different? }
100
+ assertions.filter { |screenshot_assert| screenshot_assert.compare&.different? }
103
101
  end
104
102
 
105
103
  def reset
106
104
  @assertions.clear
105
+ @screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
107
106
  end
108
107
  end
109
108
  end
@@ -121,7 +120,46 @@ module CapybaraScreenshotDiff
121
120
  def_delegator :registry, :assertions
122
121
  def_delegator :registry, :assertions_present?
123
122
  def_delegator :registry, :failed_assertions
124
- def_delegator :registry, :reset
123
+ def reset
124
+ notify_reporters(registry.assertions)
125
+ registry.reset
126
+ end
127
+
128
+ def reporters
129
+ @reporters ||= []
130
+ end
131
+
132
+ attr_reader :reporters_mutex
133
+
134
+ def finalize_reporters!
135
+ reporters_mutex.synchronize { reporters.dup }.each do |reporter|
136
+ reporter.finalize
137
+ if (msg = reporter.summary)
138
+ $stdout.puts msg
139
+ end
140
+ rescue => e
141
+ warn "[snap_diff] Reporter #{reporter.class} failed (#{e.class}: #{e.message})"
142
+ end
143
+ end
144
+
145
+ def_delegator :registry, :screenshot_namer
125
146
  def_delegator :registry, :verify
147
+
148
+ private
149
+
150
+ def notify_reporters(assertions)
151
+ return if assertions.nil? || assertions.empty?
152
+
153
+ reporters_snapshot = reporters_mutex.synchronize { reporters.dup }
154
+ return if reporters_snapshot.empty?
155
+
156
+ reporters_snapshot.each do |reporter|
157
+ reporter.record(assertions)
158
+ rescue => e
159
+ warn "[capybara-screenshot-diff] Reporter failed: #{e.message}"
160
+ end
161
+ end
126
162
  end
163
+
164
+ @reporters_mutex = Mutex.new
127
165
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "pathname"
5
+
6
+ module CapybaraScreenshotDiff
7
+ # Handles the naming, path generation, and organization of screenshots.
8
+ # This class encapsulates logic related to screenshot sections, groups,
9
+ # and counters, providing a centralized way to determine screenshot filenames
10
+ # and directories.
11
+ class ScreenshotNamer
12
+ attr_reader :section, :group
13
+
14
+ def initialize(screenshot_area = nil)
15
+ @section = nil
16
+ @group = nil
17
+ @counter = nil
18
+ @screenshot_area = screenshot_area
19
+ end
20
+
21
+ def screenshot_area
22
+ @screenshot_area ||= Capybara::Screenshot.screenshot_area
23
+ end
24
+
25
+ # Sets the current section for screenshots.
26
+ # @param name [String, nil] The name of the section.
27
+ def section=(name)
28
+ @section = name&.to_s
29
+ reset_group_counter
30
+ end
31
+
32
+ # Sets the current group for screenshots and resets the counter.
33
+ # @param name [String, nil] The name of the group.
34
+ def group=(name)
35
+ @group = name&.to_s
36
+ reset_group_counter
37
+ end
38
+
39
+ # Builds the full, unique name for a screenshot, including any counter.
40
+ # @param base_name [String] The base name for the screenshot.
41
+ # @return [String] The full screenshot name.
42
+ def full_name(base_name)
43
+ name = base_name.to_s
44
+
45
+ if @counter
46
+ name = format("%02i_%s", @counter, name)
47
+ @counter += 1
48
+ end
49
+
50
+ File.join(*directory_parts.push(name.to_s))
51
+ end
52
+
53
+ # Builds the full path for a screenshot file, including section and group directories.
54
+ # @param base_name [String] The base name for the screenshot.
55
+ # @return [String] The absolute path for the screenshot file.
56
+ def full_name_with_path(base_name)
57
+ File.join(screenshot_area, full_name(base_name))
58
+ end
59
+
60
+ # Returns the directory parts (section and group) for constructing paths.
61
+ # @return [Array<String>] An array of directory names.
62
+ def directory_parts
63
+ parts = []
64
+ parts << @section unless @section.nil? || @section.empty?
65
+ parts << @group unless @group.nil? || @group.empty?
66
+ parts
67
+ end
68
+
69
+ # Calculates the directory path for the current section and group.
70
+ # @return [String] The full path to the directory.
71
+ def current_group_directory
72
+ File.join(*([screenshot_area] + directory_parts))
73
+ end
74
+
75
+ private
76
+
77
+ def reset_group_counter
78
+ @counter = (@group.nil? || @group.empty?) ? nil : 0
79
+ end
80
+ end
81
+ end
@@ -16,7 +16,8 @@ module CapybaraScreenshotDiff
16
16
  def delete!
17
17
  path.delete if path.exist?
18
18
  base_path.delete if base_path.exist?
19
- cleanup_attempts
19
+ cleanup_diff_artifacts!
20
+ cleanup_attempts!
20
21
  end
21
22
 
22
23
  def checkout_base_screenshot
@@ -43,13 +44,23 @@ module CapybaraScreenshotDiff
43
44
  @manager.move(attempt_path, path)
44
45
  end
45
46
 
46
- def cleanup_attempts
47
+ def cleanup_attempts!
47
48
  @manager.cleanup_attempts!(self)
48
49
  @attempts_count = 0
49
50
  end
50
51
 
51
52
  def find_attempts_paths
52
- Dir[@manager.abs_path_for "**/#{full_name}.attempt_*.#{format}"]
53
+ Dir[@manager.abs_path_for("**/#{full_name}.attempt_[0-9][0-9].#{format}")]
54
+ end
55
+
56
+ private
57
+
58
+ def cleanup_diff_artifacts!
59
+ [
60
+ path.sub_ext(".diff.#{format}"),
61
+ path.sub_ext(".heatmap.diff.#{format}"),
62
+ base_path.sub_ext(".diff.#{format}")
63
+ ].each { |f| f.unlink if f.exist? }
53
64
  end
54
65
  end
55
66
  end
@@ -11,10 +11,13 @@ module CapybaraScreenshotDiff
11
11
 
12
12
  def initialize(root)
13
13
  @root = Pathname.new(root)
14
+ @snapshots = Set.new
14
15
  end
15
16
 
16
17
  def snapshot(screenshot_full_name, screenshot_format = "png")
17
- Snap.new(screenshot_full_name, screenshot_format, manager: self)
18
+ Snap.new(screenshot_full_name, screenshot_format, manager: self).tap do |snapshot|
19
+ @snapshots << snapshot
20
+ end
18
21
  end
19
22
 
20
23
  def self.snapshot(screenshot_full_name, screenshot_format = "png")
@@ -42,7 +45,10 @@ module CapybaraScreenshotDiff
42
45
 
43
46
  # TODO: rename to delete!
44
47
  def cleanup!
45
- FileUtils.rm_rf root, secure: true
48
+ snapshots.each do |snapshot|
49
+ cleanup_attempts!(snapshot)
50
+ snapshot.delete!
51
+ end
46
52
  end
47
53
 
48
54
  def self.cleanup!
@@ -61,6 +67,8 @@ module CapybaraScreenshotDiff
61
67
  root.children.map { |f| f.basename.to_s }
62
68
  end
63
69
 
70
+ attr_reader :snapshots
71
+
64
72
  def self.screenshots
65
73
  instance.screenshots
66
74
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/files"
4
+ require "capybara_screenshot_diff/minitest"
5
+
6
+ module CapybaraScreenshotDiff
7
+ def self.serve(directory, root: Dir.pwd)
8
+ Capybara.app = Rack::Files.new(directory)
9
+ Capybara::Screenshot.root = root
10
+ end
11
+ end
@@ -5,25 +5,32 @@ require "capybara/screenshot/diff/version"
5
5
  require "capybara/screenshot/diff/utils"
6
6
  require "capybara/screenshot/diff/image_compare"
7
7
  require "capybara_screenshot_diff/snap_manager"
8
- require "capybara/screenshot/diff/test_methods"
9
8
  require "capybara/screenshot/diff/screenshoter"
10
9
  require "capybara/screenshot/diff/reporters/default"
11
10
 
11
+ require "capybara_screenshot_diff/error_with_filtered_backtrace"
12
+
12
13
  module CapybaraScreenshotDiff
13
- class CapybaraScreenshotDiffError < StandardError; end
14
+ RED_RGBA = [255, 0, 0, 255].freeze
15
+ ORANGE_RGBA = [255, 192, 0, 255].freeze
16
+
17
+ class CapybaraScreenshotDiffError < ErrorWithFilteredBacktrace; end
14
18
 
15
19
  class ExpectationNotMet < CapybaraScreenshotDiffError; end
16
20
 
17
21
  class UnstableImage < CapybaraScreenshotDiffError; end
22
+
23
+ class WindowSizeMismatchError < ErrorWithFilteredBacktrace; end
18
24
  end
19
25
 
20
26
  module Capybara
21
27
  module Screenshot
22
28
  mattr_accessor :add_driver_path
23
29
  mattr_accessor :add_os_path
24
- mattr_accessor :blur_active_element
30
+ mattr_accessor(:blur_active_element) { true }
25
31
  mattr_accessor :enabled
26
- mattr_accessor :hide_caret
32
+ mattr_accessor(:hide_caret) { true }
33
+ mattr_accessor :disable_animations
27
34
  mattr_reader(:root) { (defined?(Rails) && defined?(Rails.root) && Rails.root) || Pathname(".").expand_path }
28
35
  mattr_accessor :stability_time_limit
29
36
  mattr_accessor :window_size
@@ -57,7 +64,7 @@ module Capybara
57
64
  module Diff
58
65
  mattr_accessor(:delayed) { true }
59
66
  mattr_accessor :area_size_limit
60
- mattr_accessor(:fail_if_new) { false }
67
+ mattr_accessor(:fail_if_new) { !ENV["CI"].nil? && !ENV["CI"].empty? }
61
68
  mattr_accessor(:fail_on_difference) { true }
62
69
  mattr_accessor :color_distance_limit
63
70
  mattr_accessor(:enabled) { true }
@@ -65,12 +72,29 @@ module Capybara
65
72
  mattr_accessor :skip_area
66
73
  mattr_accessor(:driver) { :auto }
67
74
  mattr_accessor :tolerance
75
+ mattr_accessor :perceptual_threshold
68
76
 
69
77
  mattr_accessor(:screenshoter) { Screenshoter }
70
78
  mattr_accessor(:manager) { CapybaraScreenshotDiff::SnapManager }
71
79
 
72
80
  AVAILABLE_DRIVERS = Utils.detect_available_drivers.freeze
73
81
 
82
+ # Configure screenshot and diff settings in one block.
83
+ #
84
+ # Capybara::Screenshot::Diff.configure do |screenshot, diff|
85
+ # screenshot.window_size = [1280, 1024]
86
+ # screenshot.stability_time_limit = 1
87
+ # diff.driver = :vips
88
+ # diff.tolerance = 0.0005
89
+ # end
90
+ def self.configure
91
+ yield Screenshot, self
92
+ end
93
+
94
+ def self.compare(baseline_path, current_path, **options)
95
+ ImageCompare.new(current_path, baseline_path, default_options.merge(options))
96
+ end
97
+
74
98
  def self.default_options
75
99
  {
76
100
  area_size_limit: area_size_limit,
@@ -78,6 +102,7 @@ module Capybara
78
102
  driver: driver,
79
103
  screenshot_format: Screenshot.screenshot_format,
80
104
  capybara_screenshot_options: Screenshot.capybara_screenshot_options,
105
+ perceptual_threshold: perceptual_threshold,
81
106
  shift_distance_limit: shift_distance_limit,
82
107
  skip_area: skip_area,
83
108
  stability_time_limit: Screenshot.stability_time_limit,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-screenshot-diff
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.3
4
+ version: 1.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uwe Kubosch
@@ -15,17 +15,37 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '7.0'
18
+ version: '7.1'
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
21
  version: '9'
22
- type: :runtime
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.1'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
32
+ - !ruby/object:Gem::Dependency
33
+ name: activesupport
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '7.1'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '9'
42
+ type: :development
23
43
  prerelease: false
24
44
  version_requirements: !ruby/object:Gem::Requirement
25
45
  requirements:
26
46
  - - ">="
27
47
  - !ruby/object:Gem::Version
28
- version: '7.0'
48
+ version: '7.1'
29
49
  - - "<"
30
50
  - !ruby/object:Gem::Version
31
51
  version: '9'
@@ -56,9 +76,21 @@ executables: []
56
76
  extensions: []
57
77
  extra_rdoc_files: []
58
78
  files:
79
+ - CHANGELOG.md
59
80
  - LICENSE.txt
60
81
  - Rakefile
61
82
  - capybara-screenshot-diff.gemspec
83
+ - docs/RELEASE_PREP.md
84
+ - docs/UPGRADING.md
85
+ - docs/ci-integration.md
86
+ - docs/configuration.md
87
+ - docs/docker-testing.md
88
+ - docs/drivers.md
89
+ - docs/framework-setup.md
90
+ - docs/images/snap_diff_web_ui.png
91
+ - docs/organization.md
92
+ - docs/reporters.md
93
+ - docs/thread_safety.md
62
94
  - gems.rb
63
95
  - lib/capybara-screenshot-diff.rb
64
96
  - lib/capybara/screenshot/diff.rb
@@ -67,31 +99,38 @@ files:
67
99
  - lib/capybara/screenshot/diff/comparison.rb
68
100
  - lib/capybara/screenshot/diff/cucumber.rb
69
101
  - lib/capybara/screenshot/diff/difference.rb
102
+ - lib/capybara/screenshot/diff/difference_finder.rb
70
103
  - lib/capybara/screenshot/diff/drivers.rb
71
104
  - lib/capybara/screenshot/diff/drivers/base_driver.rb
72
105
  - lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb
73
106
  - lib/capybara/screenshot/diff/drivers/vips_driver.rb
74
107
  - lib/capybara/screenshot/diff/image_compare.rb
108
+ - lib/capybara/screenshot/diff/image_preprocessor.rb
75
109
  - lib/capybara/screenshot/diff/os.rb
76
110
  - lib/capybara/screenshot/diff/region.rb
77
111
  - lib/capybara/screenshot/diff/reporters/default.rb
78
112
  - lib/capybara/screenshot/diff/screenshot_matcher.rb
79
113
  - lib/capybara/screenshot/diff/screenshoter.rb
80
114
  - lib/capybara/screenshot/diff/stable_screenshoter.rb
81
- - lib/capybara/screenshot/diff/test_methods.rb
82
115
  - lib/capybara/screenshot/diff/utils.rb
83
116
  - lib/capybara/screenshot/diff/vcs.rb
84
117
  - lib/capybara/screenshot/diff/version.rb
85
118
  - lib/capybara_screenshot_diff.rb
86
119
  - lib/capybara_screenshot_diff/attempts_reporter.rb
120
+ - lib/capybara_screenshot_diff/backtrace_filter.rb
87
121
  - lib/capybara_screenshot_diff/cucumber.rb
88
122
  - lib/capybara_screenshot_diff/dsl.rb
123
+ - lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb
89
124
  - lib/capybara_screenshot_diff/minitest.rb
125
+ - lib/capybara_screenshot_diff/reporters/html.rb
126
+ - lib/capybara_screenshot_diff/reporters/templates/report.html.erb
90
127
  - lib/capybara_screenshot_diff/rspec.rb
91
128
  - lib/capybara_screenshot_diff/screenshot_assertion.rb
129
+ - lib/capybara_screenshot_diff/screenshot_namer.rb
92
130
  - lib/capybara_screenshot_diff/snap.rb
93
131
  - lib/capybara_screenshot_diff/snap_manager.rb
94
- homepage: https://github.com/donv/capybara-screenshot-diff
132
+ - lib/capybara_screenshot_diff/static.rb
133
+ homepage: https://github.com/snap-diff/snap_diff-capybara
95
134
  licenses:
96
135
  - MIT
97
136
  metadata:
@@ -103,14 +142,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
142
  requirements:
104
143
  - - ">="
105
144
  - !ruby/object:Gem::Version
106
- version: '3.1'
145
+ version: '3.2'
107
146
  required_rubygems_version: !ruby/object:Gem::Requirement
108
147
  requirements:
109
148
  - - ">="
110
149
  - !ruby/object:Gem::Version
111
150
  version: '0'
112
151
  requirements: []
113
- rubygems_version: 3.6.7
152
+ rubygems_version: 4.0.6
114
153
  specification_version: 4
115
154
  summary: Track your GUI changes with diff assertions
116
155
  test_files: []
@@ -1,157 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "English"
4
- require "capybara"
5
- require "action_controller"
6
- require "action_dispatch"
7
- require "active_support/core_ext/string/strip"
8
- require "pathname"
9
-
10
- require_relative "drivers"
11
- require_relative "image_compare"
12
- require_relative "vcs"
13
- require_relative "browser_helpers"
14
- require_relative "region"
15
-
16
- require_relative "screenshot_matcher"
17
-
18
- # == Capybara::Screenshot::Diff::TestMethods
19
- #
20
- # This module provides methods for capturing screenshots and verifying them against
21
- # baseline images to detect visual changes. It's designed to be included in test
22
- # classes to add visual regression testing capabilities.
23
-
24
- module Capybara
25
- module Screenshot
26
- module Diff
27
- module TestMethods
28
- # @!attribute [rw] test_screenshots
29
- # @return [Array(Array(Array(String), String, ImageCompare | Minitest::Mock))] An array where each element is an array containing the caller context,
30
- # the name of the screenshot, and the comparison object. This attribute stores information about each screenshot
31
- # scheduled for comparison to ensure they do not show any unintended differences.
32
- def initialize(*)
33
- super
34
- @screenshot_counter = nil
35
- @screenshot_group = nil
36
- @screenshot_section = nil
37
- @test_screenshot_errors = nil
38
- end
39
-
40
- # Builds the full name for a screenshot, incorporating counters and group names for uniqueness.
41
- #
42
- # @param name [String] The base name for the screenshot.
43
- # @return [String] The full, unique name for the screenshot.
44
- def build_full_name(name)
45
- if @screenshot_counter
46
- name = format("%02i_#{name}", @screenshot_counter)
47
- @screenshot_counter += 1
48
- end
49
-
50
- File.join(*group_parts.push(name.to_s))
51
- end
52
-
53
- # Determines the directory path for saving screenshots.
54
- #
55
- # @return [String] The full path to the directory where screenshots are saved.
56
- def screenshot_dir
57
- File.join(*([Screenshot.screenshot_area] + group_parts))
58
- end
59
-
60
- def screenshot_section(name)
61
- @screenshot_section = name.to_s
62
- end
63
-
64
- def screenshot_group(name)
65
- @screenshot_group = name.to_s
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
69
-
70
- FileUtils.rm_rf screenshot_dir
71
- end
72
-
73
- # Schedules a screenshot comparison job for later execution.
74
- #
75
- # This method adds a job to the queue of screenshots to be matched. It's used when `Capybara::Screenshot::Diff.delayed`
76
- # is set to true, allowing for batch processing of screenshot comparisons at a later point, typically at the end of a test.
77
- #
78
- # @param job [Array(Array(String), String, ImageCompare)] The job to be scheduled, consisting of the caller context, screenshot name, and comparison object.
79
- # @return [Boolean] Always returns true, indicating the job was successfully scheduled.
80
- def schedule_match_job(job)
81
- CapybaraScreenshotDiff.add_assertion(job)
82
- true
83
- end
84
-
85
- def group_parts
86
- parts = []
87
- parts << @screenshot_section unless @screenshot_section.nil? || @screenshot_section.empty?
88
- parts << @screenshot_group unless @screenshot_group.nil? || @screenshot_group.empty?
89
- parts
90
- end
91
-
92
- # Takes a screenshot and optionally compares it against a baseline image.
93
- #
94
- # @param name [String] The name of the screenshot, used to generate the filename.
95
- # @param skip_stack_frames [Integer] The number of stack frames to skip when reporting errors, for cleaner error messages.
96
- # @param options [Hash] Additional options for taking the screenshot, such as custom dimensions or selectors.
97
- # @return [Boolean] Returns true if the screenshot was successfully captured and matches the baseline, false otherwise.
98
- # @raise [CapybaraScreenshotDiff::ExpectationNotMet] If the screenshot does not match the baseline image and fail_if_new is set to true.
99
- # @example Capture a full-page screenshot named 'login_page'
100
- # screenshot('login_page', skip_stack_frames: 1, full: true)
101
- def screenshot(name, skip_stack_frames: 0, **options)
102
- return false unless Screenshot.active?
103
-
104
- # setup
105
- screenshot_full_name = build_full_name(name)
106
-
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
114
- if Screenshot::Diff.fail_if_new
115
- _raise_error(<<-ERROR.strip_heredoc, backtrace)
116
- No existing screenshot found for #{screenshot_full_name}!
117
- To stop seeing this error disable by `Capybara::Screenshot::Diff.fail_if_new=false`
118
- ERROR
119
- end
120
-
121
- return false
122
- end
123
-
124
- match_changes_job.prepend(backtrace)
125
-
126
- if Screenshot::Diff.delayed
127
- schedule_match_job(match_changes_job)
128
- else
129
- invoke_match_job(match_changes_job)
130
- end
131
- end
132
-
133
- private
134
-
135
- def invoke_match_job(job)
136
- error_msg = CapybaraScreenshotDiff::ScreenshotAssertion.from(job).validate
137
-
138
- if error_msg
139
- _raise_error(error_msg, job[0])
140
- end
141
-
142
- true
143
- end
144
-
145
- def _raise_error(error_msg, backtrace)
146
- raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg).tap { _1.set_backtrace(backtrace) }
147
- end
148
-
149
- def build_screenshot_matches_job(screenshot_full_name, options)
150
- ScreenshotMatcher
151
- .new(screenshot_full_name, options)
152
- .build_screenshot_matches_job
153
- end
154
- end
155
- end
156
- end
157
- end