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,137 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "base64"
4
- require "erb"
5
- require "fileutils"
6
- require "pathname"
7
- require "json"
8
-
9
- module CapybaraScreenshotDiff
10
- module Reporters
11
- class HTML
12
- attr_reader :failures, :total
13
-
14
- def initialize(output_path: nil, embed_images: false)
15
- @explicit_output_path = output_path
16
- @embed_images = embed_images
17
- @failures = []
18
- @total = 0
19
- @finalized = false
20
- @mutex = Mutex.new
21
- end
22
-
23
- def record(assertions)
24
- return if @finalized
25
-
26
- failures = []
27
- total = 0
28
-
29
- assertions.each do |assertion|
30
- compare = assertion.compare
31
- next unless compare
32
-
33
- total += 1
34
- next unless compare.difference&.different?
35
-
36
- failures << failure_entry_for(assertion.name, compare)
37
- rescue => e
38
- warn "[snap_diff] Reporter skipped '#{assertion.name}': #{e.message}" if ENV["DEBUG"]
39
- end
40
-
41
- @mutex.synchronize do
42
- return if @finalized
43
- @total += total
44
- @failures.concat(failures)
45
- end
46
- end
47
-
48
- def finalize
49
- @mutex.synchronize do
50
- return if @finalized
51
- return if failures.empty?
52
-
53
- write_report
54
- @finalized = true
55
- output_path
56
- end
57
- end
58
-
59
- def output_path
60
- @output_path ||= Pathname.new(@explicit_output_path || self.class.default_output_path)
61
- end
62
-
63
- def passed = total - failures.size
64
- def failed = failures.size
65
-
66
- def summary
67
- return if total.zero?
68
-
69
- screenshots_label = (total == 1) ? "1 screenshot" : "#{total} screenshots"
70
-
71
- if failures.empty?
72
- "[snap_diff] #{screenshots_label} compared, no failures."
73
- else
74
- failures_label = (failures.size == 1) ? "1 failure" : "#{failures.size} failures"
75
- "[snap_diff] #{screenshots_label} compared, #{failures_label}. Report: #{output_path}"
76
- end
77
- end
78
-
79
- def render
80
- ERB.new(File.read(self.class.template_path)).result(binding)
81
- end
82
-
83
- def self.template_path
84
- File.expand_path("templates/report.html.erb", __dir__)
85
- end
86
-
87
- def self.default_output_path
88
- root = Capybara::Screenshot.root || Pathname.pwd
89
- root / Capybara::Screenshot.save_path / "snap_diff_report.html"
90
- end
91
-
92
- private
93
-
94
- def failure_entry_for(name, compare)
95
- difference = compare.difference
96
- {
97
- name: name,
98
- original: resolve_image(compare.base_image_path),
99
- new: resolve_image(compare.image_path),
100
- base_diff: resolve_image(compare.reporter.annotated_base_image_path),
101
- diff: resolve_image(compare.reporter.annotated_image_path),
102
- heatmap: resolve_image(compare.reporter.heatmap_diff_path),
103
- diff_level: difference.ratio && (difference.ratio * 100).round(2),
104
- area_size: difference.region_area_size,
105
- max_color_distance: difference.meta[:max_color_distance]&.round(1)
106
- }
107
- end
108
-
109
- def resolve_image(path)
110
- return unless path
111
-
112
- pathname = Pathname.new(path).expand_path
113
- return unless pathname.exist?
114
-
115
- @embed_images ? data_uri(pathname) : pathname.relative_path_from(output_path.dirname.expand_path).to_s
116
- end
117
-
118
- def data_uri(pathname)
119
- ext = pathname.extname.delete_prefix(".")
120
- mime = (ext == "webp") ? "image/webp" : "image/png"
121
- "data:#{mime};base64,#{Base64.strict_encode64(pathname.binread)}"
122
- end
123
-
124
- def write_report
125
- FileUtils.mkdir_p(output_path.dirname)
126
- File.write(output_path, render)
127
- end
128
- end
129
- end
130
- end
131
-
132
- # Auto-register reporter.
133
- # Framework adapters (Minitest, RSpec, Cucumber) call finalize_reporters! via native hooks.
134
- # For custom frameworks, call CapybaraScreenshotDiff.finalize_reporters! manually.
135
- unless CapybaraScreenshotDiff.reporters.any?(CapybaraScreenshotDiff::Reporters::HTML)
136
- CapybaraScreenshotDiff.reporters << CapybaraScreenshotDiff::Reporters::HTML.new(embed_images: !!ENV["CI"])
137
- end
3
+ # Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::Reporters::HTML now
4
+ # resolves lazily via snap_diff/legacy_shims' const_missing, with a
5
+ # deprecation warning pointing at SnapDiff::Reporters::HTML.
6
+ require "snap_diff/reporters/html"
7
+ require "snap_diff/legacy_shims"
@@ -1,66 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rspec/core"
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
- RSpec::Matchers.define :match_screenshot do |name, **options|
7
- description { "match screenshot '#{name}'" }
8
-
9
- match do |_page|
10
- assert_matches_screenshot(name, **options)
11
- true
12
- end
13
-
14
- failure_message do
15
- "Expected page to match screenshot '#{name}'"
16
- end
17
-
18
- failure_message_when_negated do
19
- "Expected page not to match screenshot '#{name}'"
20
- end
21
- end
22
-
23
- RSpec.configure do |config|
24
- config.include CapybaraScreenshotDiff::DSL, type: :feature
25
- config.include CapybaraScreenshotDiff::DSL, type: :system
26
-
27
- config.before do
28
- if self.class.include?(CapybaraScreenshotDiff::DSL)
29
- Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
30
- end
31
- end
32
-
33
- # `append_after` (as opposed to the default `after`, which prepends) adds
34
- # this hook to the *end* of the after-hook chain regardless of when it's
35
- # registered relative to the user's own `after`/`config.after` hooks. RSpec
36
- # runs `after(:each)` hooks in reverse registration order, so a plain
37
- # `config.after` here would run BEFORE a user hook registered earlier in
38
- # their own spec_helper (before this file was required) -- and if that
39
- # later-running user hook raises, its exception gets folded into
40
- # `pending_exception` instead of `example.exception`, silently masking the
41
- # failure behind our pending skip. `append_after` runs after the full user
42
- # after-chain no matter the registration order, closing that gap.
43
- config.append_after do |example|
44
- if self.class.include?(CapybaraScreenshotDiff::DSL)
45
- begin
46
- CapybaraScreenshotDiff.verify
47
-
48
- # Never mask a real failure with a pending marker. Kept as
49
- # defense-in-depth: `append_after` observes failures from plain
50
- # `after`/`prepend_after` user hooks, but appended hooks run FIFO,
51
- # so a user `append_after` registered after this gem still runs
52
- # later than us — RSpec has no "run absolutely last" construct.
53
- # Mitigation for such consumers: require this gem last.
54
- if example.exception.nil? && (msg = CapybaraScreenshotDiff.pending_screenshots_message)
55
- skip(msg)
56
- end
57
- rescue CapybaraScreenshotDiff::ExpectationNotMet => e
58
- raise RSpec::Expectations::ExpectationNotMetError.new(e.message).tap { |ex| ex.set_backtrace(e.backtrace) }
59
- ensure
60
- CapybaraScreenshotDiff.reset
61
- end
62
- end
63
- end
64
-
65
- config.after(:suite) { CapybaraScreenshotDiff.finalize_reporters! }
66
- end
8
+ require "snap_diff/integrations/rspec"
@@ -1,163 +1,42 @@
1
1
  # frozen_string_literal: true
2
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)
11
- @name = name
12
- @args = args
13
- end
14
-
15
- def validate
16
- return unless compare
17
-
18
- self.class.assert_image_not_changed(caller, name, compare)
19
- end
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
-
29
- # Verifies that all scheduled screenshots do not show any unintended differences.
30
- #
31
- # @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
32
- # @return [Array, nil] Returns an array of error messages if there are screenshot differences, otherwise nil.
33
- # @note This method is typically called at the end of a test to assert all screenshots are as expected.
34
- def self.verify_screenshots!(screenshots)
35
- return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
36
-
37
- test_screenshot_errors = screenshots.map do |assertion|
38
- assertion.validate
39
- end
40
-
41
- test_screenshot_errors.compact!
42
-
43
- test_screenshot_errors.empty? ? nil : test_screenshot_errors
44
- end
45
-
46
- # Asserts that an image has not changed compared to its baseline.
47
- #
48
- # @param backtrace [Array(String)] The caller context, used for error reporting.
49
- # @param name [String] The name of the screenshot being verified.
50
- # @param comparison [Object] The comparison object containing the result and details of the comparison.
51
- # @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
52
- # @note This method is used internally to verify individual screenshots.
53
- def self.assert_image_not_changed(backtrace, name, comparison)
54
- result = comparison.different?
55
-
56
- # Cleanup after comparisons
57
- if !result && comparison.base_image_path.exist?
58
- FileUtils.mv(comparison.base_image_path, comparison.image_path, force: true)
59
- end
60
-
61
- return unless result
62
-
63
- "Screenshot does not match for '#{name}': #{comparison.error_message}\n#{backtrace.join("\n")}"
64
- end
65
- end
66
-
67
- class AssertionRegistry
68
- attr_reader :assertions, :screenshot_namer, :new_screenshots
69
-
70
- def initialize
71
- @assertions = []
72
- @new_screenshots = []
73
- @screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
74
- end
75
-
76
- def add_assertion(assertion)
77
- return unless assertion&.compare
78
-
79
- @assertions.push(assertion)
80
-
81
- assertion
82
- end
83
-
84
- def assertions_present?
85
- !@assertions.empty?
86
- end
87
-
88
- def record_new_screenshot(name)
89
- @new_screenshots.push(name)
90
- end
91
-
92
- def new_screenshots_present?
93
- !@new_screenshots.empty?
94
- end
95
-
96
- def verify(screenshots = CapybaraScreenshotDiff.assertions)
97
- return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
98
-
99
- failed_assertions = CapybaraScreenshotDiff.registry.failed_assertions
100
- failed_screenshot = failed_assertions.first
101
- result = ScreenshotAssertion.verify_screenshots!(screenshots)
102
-
103
- if result
104
- raise CapybaraScreenshotDiff::ExpectationNotMet.new(result.join("\n\n"), failed_screenshot.caller)
105
- end
106
- end
107
-
108
- def failed_assertions
109
- assertions.filter { |screenshot_assert| screenshot_assert.compare&.different? }
110
- end
111
-
112
- def reset
113
- @assertions.clear
114
- @new_screenshots.clear
115
- @screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
116
- end
117
- end
118
- end
119
-
3
+ require "snap_diff/screenshot_assertion"
4
+ require "snap_diff/reporting"
5
+ # ScreenshotAssertion / AssertionRegistry forward lazily (with deprecation
6
+ # warnings) via snap_diff/legacy_shims' const_missing since v2 step 6.
7
+ require "snap_diff/legacy_shims"
8
+
9
+ # CapybaraScreenshotDiff's module methods cover two lifecycles with
10
+ # different scopes, kept separate below:
11
+ #
12
+ # - Session lifecycle: thread-local, per-test. The AssertionRegistry holds
13
+ # the assertions and screenshot names of the test running on this thread;
14
+ # `reset` clears it between tests.
15
+ # - Reporter lifecycle: process-global, suite-long. Owned by
16
+ # SnapDiff::Reporting; the methods here are thin public shims over it.
17
+ #
18
+ # `reset` is the one deliberate bridge between the two: a finished test's
19
+ # assertions are handed to the reporters before the registry is cleared.
120
20
  module CapybaraScreenshotDiff
121
21
  class << self
122
22
  require "forwardable"
123
23
  extend Forwardable
124
24
 
25
+ # --- Session lifecycle (thread-local, per-test) ---
26
+
125
27
  def registry
126
- Thread.current[:capybara_screenshot_diff_registry] ||= AssertionRegistry.new
28
+ Thread.current[:capybara_screenshot_diff_registry] ||= SnapDiff::AssertionRegistry.new
127
29
  end
128
30
 
129
- def_delegator :registry, :add_assertion
130
- def_delegator :registry, :assertions
131
- def_delegator :registry, :assertions_present?
132
- def_delegator :registry, :failed_assertions
133
- def_delegator :registry, :record_new_screenshot
134
- def_delegator :registry, :new_screenshots
135
- def_delegator :registry, :new_screenshots_present?
31
+ def_delegators :registry, :add_assertion, :assertions, :assertions_present?,
32
+ :failed_assertions, :record_new_screenshot, :new_screenshots,
33
+ :new_screenshots_present?, :screenshot_namer, :verify
34
+
136
35
  def reset
137
36
  notify_reporters(registry.assertions)
138
37
  registry.reset
139
38
  end
140
39
 
141
- def reporters
142
- @reporters ||= []
143
- end
144
-
145
- attr_reader :reporters_mutex
146
-
147
- def finalize_reporters!
148
- reporters_mutex.synchronize { reporters.dup }.each do |reporter|
149
- reporter.finalize
150
- if (msg = reporter.summary)
151
- $stdout.puts msg
152
- end
153
- rescue => e
154
- warn "[snap_diff] Reporter #{reporter.class} failed (#{e.class}: #{e.message})"
155
- end
156
- end
157
-
158
- def_delegator :registry, :screenshot_namer
159
- def_delegator :registry, :verify
160
-
161
40
  # Message to skip the test with when a new screenshot has no baseline yet
162
41
  # and `pending_if_new` is enabled. Adapters call this after verifying
163
42
  # screenshots, and skip the test with the returned message when present.
@@ -169,21 +48,24 @@ module CapybaraScreenshotDiff
169
48
  "No baseline for: #{new_screenshots.join(", ")}. Commit the captured screenshots to record them."
170
49
  end
171
50
 
172
- private
51
+ # --- Reporter lifecycle (process-global, suite-long) ---
173
52
 
174
- def notify_reporters(assertions)
175
- return if assertions.nil? || assertions.empty?
53
+ def reporters
54
+ SnapDiff::Reporting.reporters
55
+ end
176
56
 
177
- reporters_snapshot = reporters_mutex.synchronize { reporters.dup }
178
- return if reporters_snapshot.empty?
57
+ def reporters_mutex
58
+ SnapDiff::Reporting.mutex
59
+ end
179
60
 
180
- reporters_snapshot.each do |reporter|
181
- reporter.record(assertions)
182
- rescue => e
183
- warn "[capybara-screenshot-diff] Reporter failed: #{e.message}"
184
- end
61
+ def finalize_reporters!
62
+ SnapDiff::Reporting.finalize!
185
63
  end
186
- end
187
64
 
188
- @reporters_mutex = Mutex.new
65
+ private
66
+
67
+ def notify_reporters(assertions)
68
+ SnapDiff::Reporting.notify(assertions)
69
+ end
70
+ end
189
71
  end
@@ -1,81 +1,7 @@
1
1
  # frozen_string_literal: true
2
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
3
+ # Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::ScreenshotNamer now
4
+ # resolves lazily via snap_diff/legacy_shims' const_missing, with a
5
+ # deprecation warning pointing at SnapDiff::ScreenshotNamer.
6
+ require "snap_diff/screenshot_namer"
7
+ require "snap_diff/legacy_shims"
@@ -1,66 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module CapybaraScreenshotDiff
4
- class Snap
5
- attr_reader :full_name, :format, :path, :base_path, :manager, :attempt_path, :prev_attempt_path, :attempts_count
6
-
7
- def initialize(full_name, format, manager: SnapManager.instance)
8
- @full_name = full_name
9
- @format = format
10
- @path = manager.abs_path_for(Pathname.new(@full_name).sub_ext(".#{@format}"))
11
- @base_path = @path.sub_ext(".base.#{@format}")
12
- @manager = manager
13
- @attempts_count = 0
14
- end
15
-
16
- def delete!
17
- path.delete if path.exist?
18
- base_path.delete if base_path.exist?
19
- cleanup_diff_artifacts!
20
- cleanup_attempts!
21
- end
22
-
23
- def checkout_base_screenshot
24
- @manager.checkout_file(path, base_path)
25
- end
26
-
27
- def path_for(version = :actual)
28
- case version
29
- when :base
30
- base_path
31
- else
32
- path
33
- end
34
- end
35
-
36
- def next_attempt_path!
37
- @prev_attempt_path = @attempt_path
38
- @attempt_path = path.sub_ext(sprintf(".attempt_%02i.#{format}", @attempts_count))
39
- ensure
40
- @attempts_count += 1
41
- end
42
-
43
- def commit_last_attempt
44
- @manager.move(attempt_path, path)
45
- end
46
-
47
- def cleanup_attempts!
48
- @manager.cleanup_attempts!(self)
49
- @attempts_count = 0
50
- end
51
-
52
- def find_attempts_paths
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? }
64
- end
65
- end
66
- end
3
+ # Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::Snap now resolves
4
+ # lazily via snap_diff/legacy_shims' const_missing, with a deprecation
5
+ # warning pointing at SnapDiff::Snap.
6
+ require "snap_diff/snap"
7
+ require "snap_diff/legacy_shims"
@@ -1,84 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "capybara/screenshot/diff/vcs"
4
- require "active_support/core_ext/module/attribute_accessors"
5
-
6
- require "capybara_screenshot_diff/snap"
7
-
8
- module CapybaraScreenshotDiff
9
- class SnapManager
10
- attr_reader :root
11
-
12
- def initialize(root)
13
- @root = Pathname.new(root)
14
- @snapshots = Set.new
15
- end
16
-
17
- def snapshot(screenshot_full_name, screenshot_format = "png")
18
- Snap.new(screenshot_full_name, screenshot_format, manager: self).tap do |snapshot|
19
- @snapshots << snapshot
20
- end
21
- end
22
-
23
- def self.snapshot(screenshot_full_name, screenshot_format = "png")
24
- instance.snapshot(screenshot_full_name, screenshot_format)
25
- end
26
-
27
- def abs_path_for(relative_path)
28
- @root / relative_path
29
- end
30
-
31
- def checkout_file(path, as_path)
32
- create_output_directory_for(as_path) unless as_path.exist?
33
- Capybara::Screenshot::Diff::Vcs.checkout_vcs(root, path, as_path)
34
- end
35
-
36
- def provision_snap_with(snap, path, version: :actual)
37
- managed_path = snap.path_for(version)
38
- create_output_directory_for(managed_path) unless managed_path.exist?
39
- FileUtils.cp(path, managed_path)
40
- end
41
-
42
- def create_output_directory_for(path = nil)
43
- path ? path.dirname.mkpath : root.mkpath
44
- end
45
-
46
- # TODO: rename to delete!
47
- def cleanup!
48
- snapshots.each do |snapshot|
49
- cleanup_attempts!(snapshot)
50
- snapshot.delete!
51
- end
52
- end
53
-
54
- def self.cleanup!
55
- instance.cleanup!
56
- end
57
-
58
- def cleanup_attempts!(snapshot)
59
- FileUtils.rm_rf snapshot.find_attempts_paths, secure: true
60
- end
61
-
62
- def move(new_screenshot_path, screenshot_path)
63
- FileUtils.mv(new_screenshot_path, screenshot_path, force: true)
64
- end
65
-
66
- def screenshots
67
- root.children.map { |f| f.basename.to_s }
68
- end
69
-
70
- attr_reader :snapshots
71
-
72
- def self.screenshots
73
- instance.screenshots
74
- end
75
-
76
- def self.root
77
- instance.root
78
- end
79
-
80
- def self.instance
81
- Capybara::Screenshot::Diff.manager.new(Capybara::Screenshot.screenshot_area_abs)
82
- end
83
- end
84
- end
3
+ # Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::SnapManager now
4
+ # resolves lazily via snap_diff/legacy_shims' const_missing, with a
5
+ # deprecation warning pointing at SnapDiff::SnapManager.
6
+ require "snap_diff/snap_manager"
7
+ require "snap_diff/legacy_shims"