capybara-screenshot-diff 1.15.0 → 2.0.0.alpha1

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/docs/RELEASE_PREP.md +6 -6
  4. data/docs/thread_safety.md +15 -0
  5. data/lib/capybara/screenshot/diff/annotation_service.rb +5 -77
  6. data/lib/capybara/screenshot/diff/area_calculator.rb +5 -54
  7. data/lib/capybara/screenshot/diff/browser_helpers.rb +5 -119
  8. data/lib/capybara/screenshot/diff/config_legacy.rb +113 -0
  9. data/lib/capybara/screenshot/diff/cucumber.rb +1 -1
  10. data/lib/capybara/screenshot/diff/difference.rb +6 -102
  11. data/lib/capybara/screenshot/diff/drivers/base_driver.rb +7 -39
  12. data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +5 -299
  13. data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +5 -169
  14. data/lib/capybara/screenshot/diff/drivers.rb +7 -14
  15. data/lib/capybara/screenshot/diff/image_compare.rb +10 -228
  16. data/lib/capybara/screenshot/diff/image_preprocessor.rb +5 -70
  17. data/lib/capybara/screenshot/diff/os.rb +8 -13
  18. data/lib/capybara/screenshot/diff/reporters/default.rb +5 -3
  19. data/lib/capybara/screenshot/diff/screenshot_matcher.rb +5 -129
  20. data/lib/capybara/screenshot/diff/screenshoter.rb +5 -127
  21. data/lib/capybara/screenshot/diff/stable_screenshoter.rb +5 -104
  22. data/lib/capybara/screenshot/diff/utils.rb +5 -40
  23. data/lib/capybara/screenshot/diff/vcs.rb +5 -37
  24. data/lib/capybara/screenshot/diff/version.rb +7 -1
  25. data/lib/capybara_screenshot_diff/attempts_reporter.rb +5 -47
  26. data/lib/capybara_screenshot_diff/cucumber.rb +5 -17
  27. data/lib/capybara_screenshot_diff/dsl.rb +7 -129
  28. data/lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb +6 -30
  29. data/lib/capybara_screenshot_diff/minitest.rb +6 -56
  30. data/lib/capybara_screenshot_diff/reporters/html.rb +5 -135
  31. data/lib/capybara_screenshot_diff/rspec.rb +5 -63
  32. data/lib/capybara_screenshot_diff/screenshot_assertion.rb +39 -157
  33. data/lib/capybara_screenshot_diff/screenshot_namer.rb +5 -79
  34. data/lib/capybara_screenshot_diff/snap.rb +5 -64
  35. data/lib/capybara_screenshot_diff/snap_manager.rb +5 -82
  36. data/lib/capybara_screenshot_diff/static.rb +3 -5
  37. data/lib/capybara_screenshot_diff.rb +29 -100
  38. data/lib/snap_diff/annotation_service.rb +84 -0
  39. data/lib/snap_diff/area_calculator.rb +56 -0
  40. data/lib/snap_diff/attempts_reporter.rb +51 -0
  41. data/lib/snap_diff/browser_helpers.rb +121 -0
  42. data/lib/snap_diff/capture/viewport.rb +40 -0
  43. data/lib/snap_diff/comparison.rb +238 -0
  44. data/lib/snap_diff/comparison_result.rb +104 -0
  45. data/lib/snap_diff/config.rb +6 -2
  46. data/lib/snap_diff/deprecation.rb +84 -0
  47. data/lib/snap_diff/driver.rb +37 -0
  48. data/lib/snap_diff/drivers/chunky_png_driver.rb +298 -0
  49. data/lib/snap_diff/drivers/vips_driver.rb +171 -0
  50. data/lib/snap_diff/drivers.rb +14 -0
  51. data/lib/snap_diff/dsl.rb +143 -0
  52. data/lib/snap_diff/error_with_filtered_backtrace.rb +32 -0
  53. data/lib/snap_diff/image_preprocessor.rb +68 -0
  54. data/lib/snap_diff/integrations/cucumber.rb +22 -0
  55. data/lib/snap_diff/integrations/minitest.rb +70 -0
  56. data/lib/snap_diff/integrations/rspec.rb +68 -0
  57. data/lib/snap_diff/legacy_shims.rb +99 -0
  58. data/lib/snap_diff/os.rb +17 -0
  59. data/lib/snap_diff/reporters/html.rb +143 -0
  60. data/lib/snap_diff/reporting.rb +53 -0
  61. data/lib/snap_diff/screenshot_assertion.rb +143 -0
  62. data/lib/snap_diff/screenshot_matcher.rb +113 -0
  63. data/lib/snap_diff/screenshot_namer.rb +81 -0
  64. data/lib/snap_diff/screenshoter.rb +129 -0
  65. data/lib/snap_diff/snap.rb +66 -0
  66. data/lib/snap_diff/snap_manager.rb +125 -0
  67. data/lib/snap_diff/stable_screenshoter.rb +103 -0
  68. data/lib/snap_diff/static.rb +11 -0
  69. data/lib/snap_diff/utils.rb +38 -0
  70. data/lib/snap_diff/vcs.rb +35 -0
  71. data/lib/snap_diff/version.rb +5 -0
  72. data/lib/snap_diff.rb +22 -6
  73. metadata +36 -2
  74. /data/lib/{capybara_screenshot_diff → snap_diff}/reporters/templates/report.html.erb +0 -0
@@ -1,106 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Capybara
4
- module Screenshot
5
- module Diff
6
- class StableScreenshoter
7
- STABILITY_OPTIONS = [:stability_time_limit, :wait]
8
-
9
- attr_reader :stability_time_limit, :wait
10
-
11
- # Initializes a new instance of StableScreenshoter
12
- #
13
- # This method sets up a new screenshoter with specific capture and comparison options. It validates the presence of
14
- # `:stability_time_limit` and `:wait` in capture options and ensures that `:stability_time_limit` is less than or equal to `:wait`.
15
- #
16
- # @param capture_options [Hash] The options for capturing screenshots, must include `:stability_time_limit` and `:wait`.
17
- # @param comparison_options [Hash] The options for comparing screenshots, defaults to `{}`. Same signature as {Capybara::Screenshot::Screenshoter#initialize}.
18
- # @raise [ArgumentError] If `:wait` or `:stability_time_limit` are not provided, or if `:stability_time_limit` is greater than `:wait`.
19
- def initialize(capture_options, comparison_options = {})
20
- @stability_time_limit, @wait = capture_options.fetch_values(*STABILITY_OPTIONS)
21
-
22
- raise ArgumentError, "wait should be provided for stable screenshots" unless wait
23
- raise ArgumentError, "stability_time_limit should be provided for stable screenshots" unless stability_time_limit
24
- raise ArgumentError, "stability_time_limit (#{stability_time_limit}) should be less or equal than wait (#{wait}) for stable screenshots" unless stability_time_limit <= wait
25
-
26
- @comparison_options = comparison_options
27
-
28
- @screenshoter = Diff.screenshoter.new(capture_options.except(:stability_time_limit), @comparison_options)
29
- end
30
-
31
- # Takes a comparison screenshot ensuring page stability
32
- #
33
- # Attempts to take a stable screenshot of the page by comparing several screenshot attempts until the page stops updating
34
- # or the `:wait` limit is reached. If unable to achieve a stable state within the time limit, it annotates the attempts
35
- # to aid debugging.
36
- #
37
- # @param snapshot Snap The snapshot details to take a stable screenshot of.
38
- # @return [void]
39
- # @raise [RuntimeError] If a stable screenshot cannot be obtained within the specified `:wait` time.
40
- def take_comparison_screenshot(snapshot)
41
- result = take_stable_screenshot(snapshot)
42
-
43
- # We failed to get stable browser state! Generate difference between attempts to overview moving parts!
44
- unless result
45
- # FIXME(uwe): Change to store the failure and only report if the test succeeds functionally.
46
- annotate_attempts_and_fail!(snapshot)
47
- end
48
-
49
- # store success attempt as actual screenshot
50
- snapshot.commit_last_attempt
51
-
52
- # cleanup all previous attempts
53
- snapshot.cleanup_attempts!
54
- end
55
-
56
- def take_stable_screenshot(snapshot)
57
- # We try to compare first attempt with checkout version, in order to not run next screenshots
58
- deadline_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + wait
59
-
60
- # Cleanup all previous attempts for sure
61
- snapshot.cleanup_attempts!
62
-
63
- loop do
64
- attempt_next_screenshot(snapshot)
65
-
66
- return true if attempt_successful?(snapshot)
67
- return false if timeout?(deadline_at)
68
-
69
- sleep(stability_time_limit)
70
- end
71
- end
72
-
73
- private
74
-
75
- def attempt_successful?(snapshot)
76
- return false unless snapshot.prev_attempt_path
77
-
78
- build_last_attempts_comparison_for(snapshot).quick_equal?
79
- rescue ArgumentError
80
- false
81
- end
82
-
83
- def attempt_next_screenshot(snapshot)
84
- @screenshoter.take_screenshot(snapshot.next_attempt_path!)
85
- end
86
-
87
- def timeout?(deadline_at)
88
- Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline_at
89
- end
90
-
91
- def build_last_attempts_comparison_for(snapshot)
92
- ImageCompare.new(snapshot.attempt_path, snapshot.prev_attempt_path, @comparison_options)
93
- end
94
-
95
- # TODO: Move to the HistoricalReporter
96
- def annotate_attempts_and_fail!(snapshot)
97
- require "capybara_screenshot_diff/attempts_reporter"
98
- attempts_reporter = CapybaraScreenshotDiff::AttemptsReporter.new(snapshot, @comparison_options, {wait: wait, stability_time_limit: stability_time_limit})
99
-
100
- # TODO: Move fail to the queue after tests passed
101
- raise CapybaraScreenshotDiff::UnstableImage.new(attempts_reporter.generate, caller)
102
- end
103
- end
104
- end
105
- end
106
- end
3
+ # Forwarder (ADR-004 v2 step 6): Capybara::Screenshot::Diff::StableScreenshoter
4
+ # now resolves lazily via snap_diff/legacy_shims' const_missing, with a
5
+ # deprecation warning pointing at SnapDiff::StableScreenshoter.
6
+ require "snap_diff/stable_screenshoter"
7
+ require "snap_diff/legacy_shims"
@@ -1,42 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Capybara
4
- module Screenshot
5
- module Diff
6
- module Utils
7
- def self.detect_available_drivers
8
- result = []
9
- begin
10
- result << :vips if defined?(Vips) || require("vips")
11
- rescue LoadError
12
- # vips not present
13
- Object.send(:remove_const, :Vips) if defined?(Vips)
14
- end
15
- begin
16
- result << :chunky_png if defined?(ChunkyPNG) || require("chunky_png")
17
- rescue LoadError
18
- # chunky_png not present
19
- Object.send(:remove_const, :ChunkyPNG) if defined?(ChunkyPNG)
20
- end
21
- result
22
- end
23
-
24
- def self.find_driver_class_for(driver)
25
- driver = AVAILABLE_DRIVERS.first if driver == :auto
26
-
27
- LOADED_DRIVERS[driver] ||=
28
- case driver
29
- when :chunky_png
30
- require "capybara/screenshot/diff/drivers/chunky_png_driver"
31
- Drivers::ChunkyPNGDriver
32
- when :vips
33
- require "capybara/screenshot/diff/drivers/vips_driver"
34
- Drivers::VipsDriver
35
- else
36
- fail "Wrong adapter #{driver.inspect}. Available adapters: #{AVAILABLE_DRIVERS.inspect}"
37
- end
38
- end
39
- end
40
- end
41
- end
42
- end
3
+ # Forwarder (ADR-004 v2 step 6): Capybara::Screenshot::Diff::Utils now
4
+ # resolves lazily via snap_diff/legacy_shims' const_missing, with a
5
+ # deprecation warning pointing at SnapDiff::Utils.
6
+ require "snap_diff/utils"
7
+ require "snap_diff/legacy_shims"
@@ -1,39 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "open3"
4
- require_relative "os"
5
-
6
- module Capybara
7
- module Screenshot
8
- module Diff
9
- module Vcs
10
- def self.checkout_vcs(root, screenshot_path, checkout_path)
11
- root_path = root.to_s
12
- git_root, _, status = Open3.capture3("git", "-C", root_path, "rev-parse", "--show-toplevel")
13
- return false unless status.success?
14
-
15
- git_root = git_root.chomp
16
- vcs_file_path = Pathname.new(screenshot_path).expand_path.relative_path_from(Pathname.new(git_root)).to_s
17
-
18
- if Screenshot.use_lfs
19
- tmp_path = "#{checkout_path}.tmp"
20
- success = system("git", "-C", root_path, "show", "HEAD:#{vcs_file_path}", out: tmp_path, err: File::NULL)
21
- if success
22
- system("git", "-C", root_path, "lfs", "smudge", in: tmp_path, out: checkout_path.to_s, err: File::NULL)
23
- end
24
- File.delete(tmp_path) if File.exist?(tmp_path)
25
- else
26
- success = system("git", "-C", root_path, "show", "HEAD:#{vcs_file_path}", out: checkout_path.to_s, err: File::NULL)
27
- end
28
-
29
- unless success
30
- checkout_path.delete if checkout_path.exist?
31
- return false
32
- end
33
-
34
- true
35
- end
36
- end
37
- end
38
- end
39
- end
3
+ # Forwarder (ADR-004 v2 step 6): Capybara::Screenshot::Diff::Vcs now
4
+ # resolves lazily via snap_diff/legacy_shims' const_missing, with a
5
+ # deprecation warning pointing at SnapDiff::Vcs.
6
+ require "snap_diff/vcs"
7
+ require "snap_diff/legacy_shims"
@@ -1,9 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "snap_diff/version"
4
+
5
+ # Deliberately EAGER and silent (v2 step 6 exception): the gemspec resolves
6
+ # Capybara::Screenshot::Diff::VERSION at build time, so a lazy warning shim
7
+ # would make every `gem build` warn. See snap_diff/legacy_shims.rb for the
8
+ # full exception list.
3
9
  module Capybara
4
10
  module Screenshot
5
11
  module Diff
6
- VERSION = "1.15.0"
12
+ VERSION = SnapDiff::VERSION
7
13
  end
8
14
  end
9
15
  end
@@ -1,49 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "capybara/screenshot/diff/image_compare"
4
-
5
- module CapybaraScreenshotDiff
6
- class AttemptsReporter
7
- def initialize(snapshot, comparison_options, stability_options = {})
8
- @snapshot = snapshot
9
- @comparison_options = comparison_options
10
- @wait = stability_options[:wait]
11
- end
12
-
13
- def generate
14
- attempts_screenshot_paths = @snapshot.find_attempts_paths
15
-
16
- annotate_attempts(attempts_screenshot_paths)
17
-
18
- "Could not get stable screenshot within #{@wait}s:\n#{attempts_screenshot_paths.join("\n")}"
19
- end
20
-
21
- def build_comparison_for(attempt_path, previous_attempt_path)
22
- Capybara::Screenshot::Diff::ImageCompare.new(attempt_path, previous_attempt_path, @comparison_options)
23
- end
24
-
25
- private
26
-
27
- def annotate_attempts(attempts_screenshot_paths)
28
- previous_file = nil
29
- attempts_screenshot_paths.reverse_each do |file_name|
30
- if previous_file && File.exist?(previous_file)
31
- attempts_comparison = build_comparison_for(file_name, previous_file)
32
-
33
- if attempts_comparison.different?
34
- FileUtils.mv(attempts_comparison.reporter.annotated_base_image_path, previous_file, force: true)
35
- else
36
- warn "[capybara-screenshot-diff] Some attempts was stable, but mistakenly marked as not: " \
37
- "#{previous_file} and #{file_name} are equal"
38
- end
39
-
40
- FileUtils.rm(attempts_comparison.reporter.annotated_image_path, force: true)
41
- end
42
-
43
- previous_file = file_name
44
- end
45
-
46
- previous_file
47
- end
48
- end
49
- end
3
+ # Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::AttemptsReporter now
4
+ # resolves lazily via snap_diff/legacy_shims' const_missing, with a
5
+ # deprecation warning pointing at SnapDiff::AttemptsReporter.
6
+ require "snap_diff/attempts_reporter"
7
+ require "snap_diff/legacy_shims"
@@ -1,20 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "capybara_screenshot_diff/dsl"
3
+ # Legacy entry point: pre-move, this path transitively loaded the whole gem
4
+ # (via dsl -> umbrella). Preserve that contract for consumers who require
5
+ # only this file.
6
+ require "capybara_screenshot_diff"
4
7
 
5
- World(::CapybaraScreenshotDiff::DSL)
6
-
7
- Before do
8
- Capybara::Screenshot::Diff.delayed = false
9
- Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
10
- end
11
-
12
- After do |scenario|
13
- if !scenario.failed? && (msg = CapybaraScreenshotDiff.pending_screenshots_message)
14
- skip_this_scenario(msg)
15
- end
16
- ensure
17
- CapybaraScreenshotDiff.reset
18
- end
19
-
20
- AfterAll { CapybaraScreenshotDiff.finalize_reporters! }
8
+ require "snap_diff/integrations/cucumber"
@@ -1,134 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "capybara_screenshot_diff"
4
- require "capybara/screenshot/diff/drivers"
5
- require "capybara/screenshot/diff/image_compare"
6
- require "capybara/screenshot/diff/screenshot_matcher"
7
- require "capybara_screenshot_diff/screenshot_namer"
8
- require "capybara_screenshot_diff/screenshot_assertion"
3
+ require "snap_diff/dsl"
9
4
 
5
+ # Deliberately EAGER and silent (v2 step 6 exception): DSL is an advertised
6
+ # entry-point constant probed with Object.const_defined? by
7
+ # support_load_probe_test.rb, and const_defined? never triggers
8
+ # const_missing -- a lazy shim would break that contract. See
9
+ # snap_diff/legacy_shims.rb for the full exception list.
10
10
  module CapybaraScreenshotDiff
11
- # DSL for taking screenshots and making assertions in Capybara tests.
12
- # This module provides methods for taking screenshots, comparing them against baselines,
13
- # and managing the comparison process with various configuration options.
14
- #
15
- # The DSL is designed to be included in your test context (e.g., RSpec, Minitest)
16
- # to provide screenshot comparison capabilities.
17
- module DSL
18
- include Capybara::DSL
19
-
20
- def screenshot_section(name)
21
- screenshot_namer.section = name
22
- end
23
-
24
- def screenshot_group(name)
25
- screenshot_namer.group = name
26
- end
27
-
28
- # Takes a screenshot and compares it against a baseline image.
29
- #
30
- # The method follows a layered optimization strategy for comparison:
31
- # 1. First checks if screenshot functionality is active
32
- # 2. Builds a full screenshot name using the current context
33
- # 3. Creates a screenshot assertion object
34
- # 4. Either validates immediately or defers validation based on options
35
- #
36
- # @param name [String] The base name of the screenshot, used to generate the filename.
37
- # @param skip_stack_frames [Integer] The number of stack frames to skip when reporting errors.
38
- # @param options [Hash] Additional options for taking the screenshot and comparison.
39
- # @option options [Boolean] :delayed (Capybara::Screenshot::Diff.delayed)
40
- # Whether to validate the screenshot immediately or delay validation.
41
- # @option options [Array<Integer>] :crop [left, top, right, bottom] Edge coordinates to crop the screenshot to.
42
- # @option options [Array<Array<Integer>>] :skip_area Array of [left, top, right, bottom] edge coordinates to ignore.
43
- # @option options [Numeric] :tolerance (0.001 for :vips driver) Color tolerance for comparison.
44
- # Represents the maximum allowed ratio of different pixels (0.0-1.0 scale).
45
- # @option options [Numeric] :color_distance_limit Maximum allowed color distance between pixels.
46
- # Uses Euclidean RGBA distance (0-510 scale). Mutually exclusive with :perceptual_threshold.
47
- # @option options [Numeric] :perceptual_threshold Maximum perceptual color difference (CIE dE00).
48
- # Uses human perception-based scale (0-100+). VIPS only. Takes priority over :color_distance_limit if both set.
49
- # @option options [Numeric] :shift_distance_limit Maximum allowed shift distance for pixels.
50
- # @option options [Numeric] :area_size_limit Maximum allowed difference area size in pixels.
51
- # @option options [Symbol] :driver (:auto) The image processing driver to use (:auto, :chunky_png, :vips).
52
- # @return [Boolean] True if the screenshot was successfully captured and processed.
53
- # @raise [CapybaraScreenshotDiff::ExpectationNotMet] If comparison fails and immediate validation is enabled.
54
- # @raise [CapybaraScreenshotDiff::UnstableImage] If the image comparison is unstable.
55
- # @raise [CapybaraScreenshotDiff::WindowSizeMismatchError] If the window size doesn't match expectations.
56
- def assert_matches_screenshot(name, skip_stack_frames: 0, **options)
57
- return false unless Capybara::Screenshot.active?
58
-
59
- # Get the full name with section and group information
60
- full_name = CapybaraScreenshotDiff.screenshot_namer.full_name(name)
61
-
62
- # Build the screenshot assertion
63
- assertion = build_screenshot_assertion(full_name, options, skip_stack_frames: skip_stack_frames + 1)
64
-
65
- return false unless assertion
66
-
67
- # Determine if validation should be delayed or immediate
68
- delayed = options.fetch(:delayed, Capybara::Screenshot::Diff.delayed)
69
-
70
- if delayed
71
- CapybaraScreenshotDiff.add_assertion(assertion)
72
- else
73
- assertion.validate!
74
- end
75
-
76
- true
77
- end
78
-
79
- # Convenience wrapper around {#assert_matches_screenshot} and {#capture_screenshot}.
80
- # @param compare [Boolean] When false, only captures the screenshot without comparing it to a baseline.
81
- # @see #assert_matches_screenshot
82
- # @see #capture_screenshot
83
- def screenshot(name, skip_stack_frames: 0, compare: true, **options)
84
- if compare
85
- assert_matches_screenshot(name, skip_stack_frames: skip_stack_frames + 1, **options)
86
- else
87
- capture_screenshot(name, **options)
88
- end
89
- end
90
-
91
- # Captures a screenshot without comparing it to a baseline.
92
- # @param name [String] The base name of the screenshot, used to generate the filename.
93
- # @param options [Hash] Additional options for taking the screenshot. See {#assert_matches_screenshot}.
94
- # @return [Boolean] True if the screenshot was successfully captured.
95
- def capture_screenshot(name, **options)
96
- return false unless Capybara::Screenshot.active?
97
-
98
- full_name = CapybaraScreenshotDiff.screenshot_namer.full_name(name)
99
- Capybara::Screenshot::Diff::ScreenshotMatcher.new(full_name, options).capture
100
-
101
- true
102
- end
103
-
104
- # Asserts the current page has no visual changes from the baseline.
105
- # Override in your base test class to add project-specific behavior
106
- # (e.g., waiting for Turbo, default skip areas).
107
- def assert_no_screenshot_changes(name, skip_stack_frames: 0, **opts)
108
- assert_matches_screenshot(name, skip_stack_frames: skip_stack_frames + 1, **opts)
109
- end
110
-
111
- private
112
-
113
- # Builds a screenshot assertion object that can be validated immediately or later.
114
- #
115
- # This method constructs a screenshot assertion that encapsulates the comparison logic.
116
- # The actual comparison is deferred until {ScreenshotAssertion#validate!} is called.
117
- #
118
- # @param name [String] The full name of the screenshot, including any section/group context.
119
- # @param options [Hash] Options for screenshot taking and comparison.
120
- # See {#assert_matches_screenshot} for available options.
121
- # @param skip_stack_frames [Integer] Number of stack frames to skip for error reporting.
122
- # @return [ScreenshotAssertion, nil] The assertion object or nil if no assertion is needed.
123
- # @see ScreenshotAssertion
124
- def build_screenshot_assertion(name, options, skip_stack_frames: 0)
125
- Capybara::Screenshot::Diff::ScreenshotMatcher
126
- .new(name, options)
127
- .build_screenshot_assertion(skip_stack_frames: skip_stack_frames + 1)
128
- end
129
-
130
- def screenshot_namer
131
- CapybaraScreenshotDiff.screenshot_namer
132
- end
133
- end
11
+ DSL = SnapDiff::DSL
134
12
  end
@@ -1,32 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module CapybaraScreenshotDiff
4
- class BacktraceFilter
5
- LIB_DIRECTORY = File.expand_path(File.join(File.dirname(__FILE__), "..")) + File::SEPARATOR
6
-
7
- def initialize(lib_directory = LIB_DIRECTORY)
8
- # Trailing separator keeps the prefix match on a directory boundary,
9
- # so "/app/lib" does not also reject "/app/library".
10
- @lib_directory = lib_directory.end_with?(File::SEPARATOR) ? lib_directory : lib_directory + File::SEPARATOR
11
- end
12
-
13
- # Filters out any backtrace lines originating from the library directory or from gems such as ActiveSupport, Minitest, and Railties
14
- # @param backtrace [Array<String>]
15
- # @return [Array<String>]
16
- def filtered(backtrace)
17
- backtrace
18
- .reject { |location| File.expand_path(location).start_with?(@lib_directory) }
19
- .reject { |l| l =~ /gems\/(activesupport|minitest|railties)/ }
20
- end
21
- end
22
-
23
- # @private
24
- class ErrorWithFilteredBacktrace < StandardError
25
- # @private
26
- def initialize(message = nil, backtrace = [])
27
- super(message)
28
- filter = BacktraceFilter.new
29
- set_backtrace(filter.filtered(backtrace))
30
- end
31
- end
32
- end
3
+ # Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::BacktraceFilter and
4
+ # CapybaraScreenshotDiff::ErrorWithFilteredBacktrace now resolve lazily via
5
+ # snap_diff/legacy_shims' const_missing, with deprecation warnings pointing
6
+ # at their SnapDiff:: replacements.
7
+ require "snap_diff/error_with_filtered_backtrace"
8
+ require "snap_diff/legacy_shims"
@@ -1,64 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "minitest"
4
- require "capybara_screenshot_diff/dsl"
3
+ # Legacy entry point: pre-move, this path transitively loaded the whole gem
4
+ # (via dsl -> umbrella). Preserve that contract for consumers who require
5
+ # only this file.
6
+ require "capybara_screenshot_diff"
5
7
 
6
- used_deprecated_entrypoint = caller.any? do |path|
7
- path.include?("capybara-screenshot-diff.rb") || path.include?("capybara/screenshot/diff.rb")
8
- end
9
-
10
- if used_deprecated_entrypoint
11
- warn <<~MSG
12
- [DEPRECATION] The default activation of `capybara_screenshot_diff/minitest` will be removed.
13
- Please `require "capybara_screenshot_diff/minitest"` explicitly.
14
- MSG
15
- end
8
+ require "snap_diff/integrations/minitest"
16
9
 
17
10
  module CapybaraScreenshotDiff
18
11
  module Minitest
19
- module Assertions
20
- include ::CapybaraScreenshotDiff::DSL
21
-
22
- def assert_matches_screenshot(*args, skip_stack_frames: 0, **opts)
23
- self.assertions += 1
24
-
25
- super(*args, skip_stack_frames: skip_stack_frames + 1, **opts)
26
- rescue ::CapybaraScreenshotDiff::ExpectationNotMet => e
27
- raise ::Minitest::Assertion, e.message
28
- end
29
-
30
- def setup
31
- super
32
- ::Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
33
- end
34
-
35
- def before_teardown
36
- super
37
- CapybaraScreenshotDiff.verify
38
-
39
- # Computed here (before teardown/reset), but the actual `skip` is
40
- # deferred to `after_teardown` so a real error raised by the user's
41
- # `teardown` isn't masked by a pending skip recorded before it ran.
42
- @capybara_screenshot_diff_pending_message = CapybaraScreenshotDiff.pending_screenshots_message
43
- rescue CapybaraScreenshotDiff::ExpectationNotMet => e
44
- assertion = ::Minitest::Assertion.new(e)
45
- assertion.set_backtrace(e.backtrace)
46
- failures << assertion
47
- ensure
48
- CapybaraScreenshotDiff.reset
49
- end
50
-
51
- def after_teardown
52
- super
53
-
54
- # Never mask a real failure (from `verify` above or from the user's
55
- # own `teardown`) with a pending marker.
56
- if failures.empty? && (msg = @capybara_screenshot_diff_pending_message)
57
- skip(msg)
58
- end
59
- end
60
- end
12
+ Assertions = SnapDiff::Minitest::Assertions
61
13
  end
62
14
  end
63
-
64
- ::Minitest.after_run { CapybaraScreenshotDiff.finalize_reporters! } if ::Minitest.respond_to?(:after_run)