snap_diff-capybara 2.0.0.beta1 → 2.0.0.beta3

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -0
  3. data/docs/UPGRADING.md +19 -4
  4. data/docs/architecture.md +38 -22
  5. data/docs/ci-integration.md +13 -3
  6. data/docs/configuration.md +31 -2
  7. data/docs/drivers.md +6 -0
  8. data/docs/framework-setup.md +21 -3
  9. data/docs/reporters.md +20 -5
  10. data/docs/snapdiff.md +326 -0
  11. data/docs/thread_safety.md +4 -3
  12. data/lib/capybara/screenshot/diff/config_legacy.rb +26 -72
  13. data/lib/capybara/screenshot/diff/cucumber.rb +5 -1
  14. data/lib/capybara/screenshot/diff/image_compare.rb +17 -2
  15. data/lib/capybara/screenshot/diff/region.rb +3 -105
  16. data/lib/capybara/screenshot/diff/reporters/default.rb +13 -101
  17. data/lib/capybara_screenshot_diff/screenshot_assertion.rb +12 -24
  18. data/lib/capybara_screenshot_diff/static.rb +4 -0
  19. data/lib/capybara_screenshot_diff.rb +10 -4
  20. data/lib/snap_diff/area_calculator.rb +1 -3
  21. data/lib/snap_diff/browser_helpers.rb +1 -3
  22. data/lib/snap_diff/capture/viewport.rb +6 -7
  23. data/lib/snap_diff/comparison.rb +15 -28
  24. data/lib/snap_diff/config.rb +151 -29
  25. data/lib/snap_diff/deprecation.rb +26 -8
  26. data/lib/snap_diff/drivers.rb +20 -0
  27. data/lib/snap_diff/dsl.rb +20 -12
  28. data/lib/snap_diff/errors.rb +25 -0
  29. data/lib/snap_diff/integrations/cucumber.rb +5 -4
  30. data/lib/snap_diff/integrations/minitest.rb +11 -12
  31. data/lib/snap_diff/integrations/rspec.rb +7 -6
  32. data/lib/snap_diff/legacy_shims.rb +15 -0
  33. data/lib/snap_diff/region.rb +117 -0
  34. data/lib/snap_diff/reporters/default.rb +107 -0
  35. data/lib/snap_diff/reporters/html.rb +7 -9
  36. data/lib/snap_diff/reporting.rb +14 -5
  37. data/lib/snap_diff/screenshot_assertion.rb +37 -4
  38. data/lib/snap_diff/screenshot_matcher.rb +4 -4
  39. data/lib/snap_diff/screenshoter.rb +1 -1
  40. data/lib/snap_diff/snap_manager.rb +1 -2
  41. data/lib/snap_diff/stable_screenshoter.rb +2 -2
  42. data/lib/snap_diff/utils.rb +5 -3
  43. data/lib/snap_diff/version.rb +1 -1
  44. data/lib/snap_diff-capybara.rb +8 -0
  45. data/lib/snap_diff.rb +55 -15
  46. metadata +6 -1
data/docs/snapdiff.md ADDED
@@ -0,0 +1,326 @@
1
+ # SnapDiff — the canonical API
2
+
3
+ Everything in this gem lives under `SnapDiff` since v2. This page is the SnapDiff-native
4
+ reference: setup, configuration, the object map, and the extension points — all using canonical
5
+ names only.
6
+
7
+ The legacy `Capybara::Screenshot::Diff` / `CapybaraScreenshotDiff` names still work (they resolve
8
+ to the same objects, with a one-time deprecation warning per constant), and the rest of the docs
9
+ still teach them. Nothing here replaces a working setup — it is what you write for **new** code.
10
+ For migrating an existing suite, see [UPGRADING.md](UPGRADING.md).
11
+
12
+ ## Quick start
13
+
14
+ ### Minitest
15
+
16
+ ```ruby
17
+ # test/test_helper.rb
18
+ require "snap_diff/integrations/minitest"
19
+ ```
20
+
21
+ ```ruby
22
+ # test/application_system_test_case.rb
23
+ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
24
+ include SnapDiff::Minitest::Assertions # brings in SnapDiff::DSL too
25
+ end
26
+ ```
27
+
28
+ ```ruby
29
+ class HomepageTest < ApplicationSystemTestCase
30
+ test "homepage" do
31
+ visit "/"
32
+ assert_matches_screenshot "homepage"
33
+ end
34
+ end
35
+ ```
36
+
37
+ `SnapDiff::Minitest::Assertions` already includes `SnapDiff::DSL`, so a separate
38
+ `include SnapDiff::DSL` is not needed (it is harmless if you have it).
39
+
40
+ > **The require path is `snap_diff/integrations/…`, not `snap_diff/…`.**
41
+ > `require "snap_diff/minitest"` raises `LoadError` — there is no such file. The integrations
42
+ > live one level down, mirroring `lib/snap_diff/integrations/`.
43
+
44
+ ### RSpec
45
+
46
+ ```ruby
47
+ # spec/rails_helper.rb
48
+ require "snap_diff/integrations/rspec"
49
+ ```
50
+
51
+ This registers the `match_screenshot` matcher and includes `SnapDiff::DSL` into
52
+ `type: :feature` and `type: :system` examples automatically:
53
+
54
+ ```ruby
55
+ RSpec.describe "Homepage", type: :system do
56
+ it "looks right" do
57
+ visit "/"
58
+ expect(page).to match_screenshot("homepage")
59
+ end
60
+ end
61
+ ```
62
+
63
+ For other example types, include the DSL yourself:
64
+
65
+ ```ruby
66
+ RSpec.describe "Admin", type: :request do
67
+ include SnapDiff::DSL
68
+ end
69
+ ```
70
+
71
+ ### Cucumber
72
+
73
+ ```ruby
74
+ # features/support/env.rb
75
+ require "snap_diff/integrations/cucumber"
76
+ ```
77
+
78
+ The DSL is added to the Cucumber `World`, so steps can call `screenshot` /
79
+ `assert_matches_screenshot` directly. This file must be loaded from inside a Cucumber run — it
80
+ calls `World`, `Before`, `After` and `AfterAll` at load time and raises `NoMethodError` if
81
+ required outside one.
82
+
83
+ ### Static sites (Hugo, Jekyll, plain HTML)
84
+
85
+ ```ruby
86
+ require "snap_diff/static"
87
+
88
+ SnapDiff.serve("_site") # or "public", "build", "dist"
89
+ SnapDiff.serve("_site", root: Dir.pwd) # root defaults to Dir.pwd
90
+ ```
91
+
92
+ `SnapDiff.serve` points Capybara at the built directory and sets the screenshot root. It also
93
+ loads the Minitest integration. See
94
+ [CI & Non-Rails Integration](ci-integration.md#non-rails-projects-hugo-jekyll-static-sites).
95
+
96
+ ## Configuration
97
+
98
+ All 27 settings live on one flat object, `SnapDiff.config` (a `SnapDiff::Config`).
99
+
100
+ ```ruby
101
+ # test_helper.rb / rails_helper.rb
102
+ SnapDiff.configure do |config|
103
+ config.window_size = [1280, 1024]
104
+ config.tolerance = 0.0005
105
+ config.driver = :vips
106
+ config.save_path = "doc/screenshots"
107
+ end
108
+
109
+ # Or set them one at a time
110
+ SnapDiff.config.hide_caret = true
111
+ SnapDiff.config.tolerance # => 0.0005
112
+ ```
113
+
114
+ `SnapDiff::Config` **is** the storage. The legacy `Capybara::Screenshot.*` and
115
+ `Capybara::Screenshot::Diff.*` accessors are thin delegators onto it — one storage, two views —
116
+ so a write through either surface is immediately visible through the other:
117
+
118
+ ```ruby
119
+ SnapDiff.config.window_size = [1280, 1024]
120
+ Capybara::Screenshot.window_size # => [1280, 1024]
121
+ ```
122
+
123
+ `SnapDiff.start` is the same call shape as the old `Capybara::Screenshot::Diff.configure`, if
124
+ you prefer the two-holder form:
125
+
126
+ ```ruby
127
+ SnapDiff.start do |screenshot, diff|
128
+ screenshot.window_size = [1280, 1024]
129
+ diff.tolerance = 0.0005
130
+ end
131
+ ```
132
+
133
+ Three derived, read-only values are computed from the settings above:
134
+
135
+ | Method | What it returns |
136
+ |--------|-----------------|
137
+ | `SnapDiff.config.active?` | Whether screenshots are taken at all (ex `Capybara::Screenshot.active?`) |
138
+ | `SnapDiff.config.screenshot_area` | `save_path`, optionally segmented per OS and per Capybara driver |
139
+ | `SnapDiff.config.default_options` | The capture/compare defaults handed to `SnapDiff::Comparison` |
140
+
141
+ Every option's meaning is documented in the
142
+ [Configuration Reference](configuration.md) — the names are identical, only the receiver differs.
143
+ The one rename: `Capybara::Screenshot.enabled` is `SnapDiff.config.screenshot_enabled`, because
144
+ `SnapDiff.config.enabled` is taken by `Capybara::Screenshot::Diff.enabled`.
145
+
146
+ ## Object map
147
+
148
+ `require "snap_diff"` gives you the compare/configure core. The test-suite pieces come with the
149
+ integration require; a few objects need their own require, noted below.
150
+
151
+ | Object | What it is for |
152
+ |--------|----------------|
153
+ | `SnapDiff.config`, `SnapDiff::Config` | Every setting, one flat object. The storage. |
154
+ | `SnapDiff.configure`, `SnapDiff.start` | Config block helpers (consolidated / v1 shape) |
155
+ | `SnapDiff.compare` | Compare two image files directly, no browser |
156
+ | `SnapDiff::Comparison` | The layered comparison engine (ex `ImageCompare`) |
157
+ | `SnapDiff::Comparison::Images` | Frozen bundle a comparison operates on: both images, their paths, the driver and the options |
158
+ | `SnapDiff::ComparisonResult` | Result value object: `different?`, region, metadata (ex `Difference`) |
159
+ | `SnapDiff::Region` | Bounding box value object — `from_edge_coordinates`, `to_edge_coordinates` |
160
+ | `SnapDiff::DSL` | `screenshot`, `assert_matches_screenshot`, `capture_screenshot`, groups/sections |
161
+ | `SnapDiff::Minitest::Assertions` | Minitest wiring (`snap_diff/integrations/minitest`) |
162
+ | `SnapDiff::Error` | Base class for every error this gem raises |
163
+ | `SnapDiff::ExpectationNotMet` | A screenshot did not match its baseline |
164
+ | `SnapDiff::UnstableImage` | No stable capture within `stability_time_limit` / `wait` |
165
+ | `SnapDiff::WindowSizeMismatchError` | Browser window is not the configured `window_size` |
166
+ | `SnapDiff::Driver` | Mixin with the shared driver defaults (`require "snap_diff/driver"`) |
167
+ | `SnapDiff::Drivers` | Driver factory and registry — `.for`, `.loaded`, `.available` |
168
+ | `SnapDiff::Reporting` | Process-global reporter lifecycle (`require "snap_diff/reporting"`) |
169
+ | `SnapDiff::Reporters::HTML` | The interactive HTML report (`require "snap_diff/reporters/html"`) |
170
+ | `SnapDiff::Reporters::Default` | Builds the annotated diff images and the failure message |
171
+ | `SnapDiff.session` | The per-test assertion registry (fiber-local) |
172
+ | `SnapDiff.reset` | Ends a test: notifies reporters, clears the session |
173
+ | `SnapDiff.pending_screenshots_message` | Skip message when a new screenshot has no baseline |
174
+ | `SnapDiff::Capture::Viewport` | Per-capture viewport check seam (`require "snap_diff/capture/viewport"`) |
175
+ | `SnapDiff.serve` | Point Capybara at a static site directory (`require "snap_diff/static"`) |
176
+
177
+ ## Compare two images without a browser
178
+
179
+ Works on anything on disk — rendered PDFs, generated charts, CI artifacts:
180
+
181
+ ```ruby
182
+ require "snap_diff"
183
+
184
+ result = SnapDiff.compare("baseline.png", "current.png")
185
+ result.quick_equal? # => true when byte-identical / pixel-identical
186
+ result.different? # => true when the difference exceeds the configured thresholds
187
+ result.difference # => SnapDiff::ComparisonResult with region and metadata
188
+
189
+ # Per-call option overrides, merged over SnapDiff.config.default_options
190
+ SnapDiff.compare("baseline.png", "current.png", tolerance: 0.5)
191
+ ```
192
+
193
+ Note the argument order: baseline first, current second ("compare baseline against current").
194
+
195
+ ## Custom reporters
196
+
197
+ A reporter is any object answering `record(assertions)`, `finalize`, and `summary`. Register it
198
+ once, for the rest of the process:
199
+
200
+ ```ruby
201
+ require "snap_diff/reporting"
202
+
203
+ class SlackReporter
204
+ def initialize = @failures = []
205
+
206
+ # Called once per finished test, with that test's assertions.
207
+ def record(assertions)
208
+ assertions.each do |assertion|
209
+ next unless assertion.compare&.difference&.different?
210
+ @failures << assertion.name
211
+ end
212
+ end
213
+
214
+ # Called once at end of suite.
215
+ def finalize
216
+ post_to_slack(@failures) unless @failures.empty?
217
+ end
218
+
219
+ # Printed to stdout after finalize. Return nil to print nothing.
220
+ def summary
221
+ @failures.empty? ? nil : "#{@failures.size} screenshot(s) changed"
222
+ end
223
+ end
224
+
225
+ SnapDiff::Reporting.register(SlackReporter.new)
226
+ ```
227
+
228
+ `register` appends under a mutex, so concurrent registrations cannot lose one — prefer it over
229
+ mutating `SnapDiff::Reporting.reporters` directly.
230
+
231
+ Do implement `summary`. `finalize!` calls it unconditionally; a reporter without it is finalized
232
+ and then warned about (`[snap_diff] Reporter … failed (NoMethodError: undefined method 'summary')`).
233
+
234
+ A reporter that raises is warned about and skipped — the other reporters still run.
235
+
236
+ The bundled HTML report is just a pre-registered reporter of this kind:
237
+
238
+ ```ruby
239
+ require "snap_diff/reporters/html" # registers SnapDiff::Reporters::HTML itself
240
+ ```
241
+
242
+ ### Frameworks other than Minitest/RSpec/Cucumber
243
+
244
+ The three bundled integrations call the lifecycle for you. Wiring another framework means calling
245
+ three things:
246
+
247
+ ```ruby
248
+ require "snap_diff/dsl" # SnapDiff::DSL, BrowserHelpers, the session accessors
249
+ require "snap_diff/reporting" # SnapDiff::Reporting
250
+
251
+ # before each test
252
+ SnapDiff::BrowserHelpers.resize_window_if_needed
253
+
254
+ # after each test
255
+ SnapDiff.session.verify # raises SnapDiff::ExpectationNotMet on a mismatch
256
+ msg = SnapDiff.pending_screenshots_message # non-nil => skip the test with this message
257
+ SnapDiff.reset # always: notifies reporters, clears the session
258
+
259
+ # after the suite
260
+ SnapDiff::Reporting.finalize!
261
+ ```
262
+
263
+ ## Custom drivers
264
+
265
+ A driver is a plain object that does the image work. Include `SnapDiff::Driver` for the shared
266
+ defaults, then implement the operations the comparison engine calls:
267
+
268
+ ```ruby
269
+ require "snap_diff/driver"
270
+
271
+ class MyDriver
272
+ include SnapDiff::Driver
273
+
274
+ # Provided by the mixin, override only if your image objects differ:
275
+ # width_for(image), height_for(image), dimension(image),
276
+ # image_area_size(image), same_dimension?(comparison), supports?(feature)
277
+
278
+ # Implement (this is what both bundled drivers implement):
279
+ # from_file(path), load_images(base_path, new_path), save_image_to(image, path)
280
+ # same_pixels?(comparison), find_difference_region(comparison)
281
+ # crop(region, image), resize_image_to(image, w, h)
282
+ # add_black_box(image, region), draw_rectangles(images, region, ...)
283
+ end
284
+ ```
285
+
286
+ `supports?(feature)` is just `respond_to?(feature)` — the engine uses it to skip optional
287
+ operations. The VIPS driver additionally implements `filter_image_with_median`, `merge`,
288
+ `highlight_mask` and `difference_level`; ChunkyPNG does not, and `supports?` is how that is
289
+ detected. Look at `lib/snap_diff/drivers/chunky_png_driver.rb` for the smaller of the two
290
+ reference implementations.
291
+
292
+ ### Registration
293
+
294
+ `SnapDiff::Drivers.loaded` is the registry: a mutable `name => driver class` hash. Register by
295
+ writing into it, then select the driver by that name:
296
+
297
+ ```ruby
298
+ SnapDiff::Drivers.loaded[:my_driver] = MyDriver
299
+
300
+ SnapDiff.config.driver = :my_driver # globally
301
+ screenshot "index", driver: :my_driver # or per screenshot
302
+ ```
303
+
304
+ Resolution goes through `SnapDiff::Drivers.for`, which looks the symbol up in `loaded` and calls
305
+ `.new` on the class — **your driver class must be instantiable with no arguments**. A pre-built
306
+ instance skips the registry entirely:
307
+
308
+ ```ruby
309
+ screenshot "index", driver: MyDriver.new # any non-Symbol is used as-is
310
+ ```
311
+
312
+ `SnapDiff::Drivers.available` is the *detected* list (`[:vips, :chunky_png]`, filled at load time
313
+ by probing for the `ruby-vips` and `chunky_png` gems). It is a read-only view of detection, not
314
+ the registry — registering a custom driver does not add it there, and `driver: :auto` picks
315
+ `available.first`. Custom drivers must always be named explicitly.
316
+
317
+ ## Related
318
+
319
+ - [Framework Setup](framework-setup.md) — the same three integrations under their legacy names
320
+ - [Configuration Reference](configuration.md) — what every option does
321
+ - [Image Processing Drivers](drivers.md) — VIPS vs ChunkyPNG, perceptual threshold
322
+ - [Web UI & Custom Reporters](reporters.md) — the HTML report in detail
323
+ - [Architecture](architecture.md) — how the pieces fit together internally
324
+ - [UPGRADING.md](UPGRADING.md) — migrating an existing suite off the legacy names
325
+
326
+ [← Back to README](../README.md)
@@ -100,9 +100,10 @@ Do not:
100
100
 
101
101
  Runtime state is thread-local (above), but *loading* the gem is a separate
102
102
  concern. The require graph is deliberately acyclic: `lib/snap_diff/*` units
103
- depend only on the legacy-config leaf
104
- (`capybara/screenshot/diff/config_legacy`) and specific sibling units, the
105
- umbrella files depend on the units, and nothing requires back up the chain.
103
+ depend only on the config-storage leaf (`snap_diff/config`, which the legacy
104
+ view `capybara/screenshot/diff/config_legacy` requires) and specific sibling
105
+ units, the umbrella files depend on the units, and nothing requires back up
106
+ the chain.
106
107
 
107
108
  Eager mutual requires between entry points are forbidden, even guarded ones:
108
109
  per-thread "loading" flags cannot serialize Ruby's process-global per-file
@@ -1,80 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Legacy Capybara::Screenshot / Capybara::Screenshot::Diff mattr_accessor
4
- # config module, extracted out of capybara_screenshot_diff.rb so it is a
5
- # leaf: it can be required by both capybara_screenshot_diff.rb (the old
6
- # umbrella entry point) and snap_diff.rb (the canonical entry point)
7
- # without either of *those* requiring the other back.
3
+ # Legacy Capybara::Screenshot / Capybara::Screenshot::Diff config surface.
8
4
  #
9
- # Why this file has to exist at all: several lib/snap_diff/* units
10
- # (Screenshoter, SnapManager, Utils) are referenced here at class-body
11
- # eval time (mattr_accessor default blocks, AVAILABLE_DRIVERS) rather
12
- # than at call time, so they must be real, already-loaded classes before
13
- # this module body runs. Since v2 step 6 they are pulled in and referenced
14
- # under their canonical SnapDiff names: the old-name aliases are lazy
15
- # const_missing shims that emit deprecation warnings, and the gem's own
16
- # code must stay warning-free. Everything else this module references
17
- # (Os, Comparison) is referenced only inside method bodies, resolved
18
- # lazily at call time by whichever entry point loaded them.
19
- require "snap_diff/screenshoter"
20
- require "snap_diff/snap_manager"
5
+ # Since ADR-008 step 1 the storage lives in SnapDiff::Config -- the require
6
+ # leaf of the config graph (see its own header) -- and since step 7b the
7
+ # DERIVED values (active?, screenshot_area, default_options) live there
8
+ # too. snap_diff/config.rb also generates the old accessor names as thin
9
+ # delegators from SnapDiff::Config::MAPPING, so nothing but forwarders is
10
+ # left here. The v1 surface (Capybara::Screenshot.window_size = ...,
11
+ # Diff.configure { ... }, Diff.compare) keeps working unchanged: one
12
+ # storage, two views.
13
+ #
14
+ # Load order: requiring snap_diff/config first also eagerly evaluates the
15
+ # require-time defaults (ENV["CI"] for fail_if_new, Rails.root/pwd for
16
+ # root) at this same load moment, exactly when the old mattr_accessor
17
+ # default blocks used to run. snap_diff/config never requires back here,
18
+ # so the graph stays acyclic.
19
+ require "snap_diff/config"
20
+ # AVAILABLE_DRIVERS below is evaluated at class-body eval time, so Utils
21
+ # must be a real, already-loaded module before this module body runs.
21
22
  require "snap_diff/utils"
22
23
 
23
24
  module Capybara
24
25
  module Screenshot
25
- mattr_accessor :add_driver_path
26
- mattr_accessor :add_os_path
27
- mattr_accessor(:blur_active_element) { true }
28
- mattr_accessor :enabled
29
- mattr_accessor(:hide_caret) { true }
30
- mattr_accessor :disable_animations
31
- mattr_reader(:root) { (defined?(Rails) && defined?(Rails.root) && Rails.root) || Pathname(".").expand_path }
32
- mattr_accessor :stability_time_limit
33
- mattr_accessor :window_size
34
- mattr_accessor(:save_path) { "doc/screenshots" }
35
- mattr_accessor(:use_lfs)
36
- mattr_accessor(:screenshot_format) { "png" }
37
- mattr_accessor(:capybara_screenshot_options) { {} }
38
-
39
26
  class << self
40
- def root=(path)
41
- @@root = Pathname(path).expand_path
42
- end
43
-
44
27
  def active?
45
- enabled || (enabled.nil? && Diff.enabled)
28
+ SnapDiff.config.active?
46
29
  end
47
30
 
48
31
  def screenshot_area
49
- parts = [Screenshot.save_path]
50
- parts << SnapDiff::Os.name if Screenshot.add_os_path
51
- parts << Capybara.current_driver.to_s if Screenshot.add_driver_path
52
- File.join(*parts)
32
+ SnapDiff.config.screenshot_area
53
33
  end
54
34
 
55
35
  def screenshot_area_abs
56
- root / screenshot_area
36
+ SnapDiff.config.screenshot_area_abs
57
37
  end
58
38
  end
59
39
 
60
40
  # Module to track screenshot changes
61
41
  module Diff
62
- mattr_accessor(:delayed) { true }
63
- mattr_accessor :area_size_limit
64
- mattr_accessor(:fail_if_new) { !ENV["CI"].nil? && !ENV["CI"].empty? }
65
- mattr_accessor(:pending_if_new) { false }
66
- mattr_accessor(:fail_on_difference) { true }
67
- mattr_accessor :color_distance_limit
68
- mattr_accessor(:enabled) { true }
69
- mattr_accessor :shift_distance_limit
70
- mattr_accessor :skip_area
71
- mattr_accessor(:driver) { :auto }
72
- mattr_accessor :tolerance
73
- mattr_accessor :perceptual_threshold
74
-
75
- mattr_accessor(:screenshoter) { SnapDiff::Screenshoter }
76
- mattr_accessor(:manager) { SnapDiff::SnapManager }
77
-
78
42
  AVAILABLE_DRIVERS = SnapDiff::Utils.detect_available_drivers.freeze
79
43
 
80
44
  # Configure screenshot and diff settings in one block.
@@ -85,28 +49,18 @@ module Capybara
85
49
  # diff.driver = :vips
86
50
  # diff.tolerance = 0.0005
87
51
  # end
52
+ # The bare `yield` (rather than an explicit &block) keeps this
53
+ # method's published arity byte-identical to what it always had.
88
54
  def self.configure
89
- yield Screenshot, self
55
+ SnapDiff.start { |screenshot, diff| yield screenshot, diff }
90
56
  end
91
57
 
92
58
  def self.compare(baseline_path, current_path, **options)
93
- SnapDiff::Comparison.new(current_path, baseline_path, default_options.merge(options))
59
+ SnapDiff.compare(baseline_path, current_path, **options)
94
60
  end
95
61
 
96
62
  def self.default_options
97
- {
98
- area_size_limit: area_size_limit,
99
- color_distance_limit: color_distance_limit,
100
- driver: driver,
101
- screenshot_format: Screenshot.screenshot_format,
102
- capybara_screenshot_options: Screenshot.capybara_screenshot_options,
103
- perceptual_threshold: perceptual_threshold,
104
- shift_distance_limit: shift_distance_limit,
105
- skip_area: skip_area,
106
- stability_time_limit: Screenshot.stability_time_limit,
107
- tolerance: tolerance || ((driver == :vips) ? 0.001 : nil),
108
- wait: Capybara.default_max_wait_time
109
- }
63
+ SnapDiff.config.default_options
110
64
  end
111
65
  end
112
66
  end
@@ -1,3 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "snap_diff/integrations/cucumber"
3
+ # Legacy (v1-documented) entry point: like every other legacy entry, it must
4
+ # load the legacy surface too -- requiring only the canonical integration
5
+ # left CapybaraScreenshotDiff half-present (module defined, but .verify /
6
+ # .reset / .reporters / ... gone) for v1 users of this path.
7
+ require "capybara_screenshot_diff/cucumber"
@@ -6,7 +6,22 @@
6
6
  # snap_diff/comparison itself pulls in the ComparisonResult and Drivers
7
7
  # units, and the shims keep the old ::Difference / ::Drivers names
8
8
  # resolvable, so this path still provides everything the pre-move
9
- # image_compare.rb did. The internal Comparison struct and LOADED_DRIVERS
10
- # keep their legacy names and are defined by snap_diff/comparison.rb itself.
9
+ # image_compare.rb did. The images-holder struct lives at
10
+ # SnapDiff::Comparison::Images and the driver cache at
11
+ # SnapDiff::Drivers.loaded, with LOADED_DRIVERS kept as an eager same-object
12
+ # alias by legacy_shims (ADR-008 step 5).
11
13
  require "snap_diff/comparison"
12
14
  require "snap_diff/legacy_shims"
15
+
16
+ # Deliberately EAGER and silent (v2 step 6 exception): Comparison is a
17
+ # documented user-facing struct, so adopters feature-detect it with
18
+ # defined?/const_defined? -- neither of which triggers const_missing, so a
19
+ # lazy shim reported it permanently absent. See snap_diff/legacy_shims.rb
20
+ # for the full exception list.
21
+ module Capybara
22
+ module Screenshot
23
+ module Diff
24
+ Comparison = SnapDiff::Comparison::Images
25
+ end
26
+ end
27
+ end
@@ -1,107 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Region
4
- attr_accessor :x, :y, :width, :height
5
-
6
- def initialize(x, y, width, height)
7
- @x, @y, @width, @height = x, y, width, height
8
- end
9
-
10
- def self.from_edge_coordinates(left, top, right, bottom)
11
- return nil unless left && top && right && bottom
12
- return nil if right < left || bottom < top
13
-
14
- Region.new(left, top, right - left, bottom - top)
15
- end
16
-
17
- def to_edge_coordinates
18
- [left, top, right, bottom]
19
- end
20
-
21
- def to_top_left_corner_coordinates
22
- [x, y, width, height]
23
- end
24
-
25
- def top
26
- y
27
- end
28
-
29
- def bottom
30
- y + height
31
- end
32
-
33
- def left
34
- x
35
- end
36
-
37
- def right
38
- x + width
39
- end
40
-
41
- def size
42
- return 0 if width < 0 || height < 0
43
-
44
- result = width * height
45
- result.zero? ? 1 : result
46
- end
47
-
48
- def to_a
49
- [@x, @y, @width, @height]
50
- end
51
-
52
- def find_intersect_with(region)
53
- return nil unless intersect?(region)
54
-
55
- new_left = [x, region.x].max
56
- new_top = [y, region.y].max
57
-
58
- Region.new(new_left, new_top, [right, region.right].min - new_left, [bottom, region.bottom].min - new_top)
59
- end
60
-
61
- def intersect?(region)
62
- left <= region.right && right >= region.left && top <= region.bottom && bottom >= region.top
63
- end
64
-
65
- def move_by(right_by, down_by)
66
- Region.new(x + right_by, y + down_by, width, height)
67
- end
68
-
69
- def find_relative_intersect(region)
70
- intersect = find_intersect_with(region)
71
- return nil unless intersect
72
-
73
- intersect.move_by(-x, -y)
74
- end
75
-
76
- def cover?(x, y)
77
- x.between?(left, right) && y.between?(top, bottom)
78
- end
79
-
80
- def empty?
81
- width.zero? || height.zero?
82
- end
83
-
84
- def blank?
85
- empty?
86
- end
87
-
88
- def present?
89
- !empty?
90
- end
91
-
92
- def inspect
93
- "Region(x: #{x}, y: #{y}, width: #{width}, height: #{height})"
94
- end
95
-
96
- # need to add this method to make it work with assert_equal
97
- def ==(other)
98
- case other
99
- when Region
100
- x == other.x && y == other.y && width == other.width && height == other.height
101
- when Array
102
- to_a == other
103
- else
104
- false
105
- end
106
- end
107
- end
3
+ # Forwarder (ADR-008 step 3): Region now lives at SnapDiff::Region;
4
+ # snap_diff/region also defines the eager top-level `Region` alias.
5
+ require "snap_diff/region"