snap_diff-capybara 2.0.0.alpha1 → 2.0.0.beta2

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -0
  3. data/docs/UPGRADING.md +243 -1
  4. data/docs/architecture.md +91 -53
  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/image_compare.rb +5 -2
  14. data/lib/capybara/screenshot/diff/region.rb +3 -105
  15. data/lib/capybara/screenshot/diff/reporters/default.rb +4 -106
  16. data/lib/capybara_screenshot_diff/screenshot_assertion.rb +12 -24
  17. data/lib/capybara_screenshot_diff.rb +10 -4
  18. data/lib/snap_diff/area_calculator.rb +1 -3
  19. data/lib/snap_diff/browser_helpers.rb +1 -3
  20. data/lib/snap_diff/capture/viewport.rb +6 -7
  21. data/lib/snap_diff/comparison.rb +15 -28
  22. data/lib/snap_diff/config.rb +151 -29
  23. data/lib/snap_diff/deprecation.rb +26 -8
  24. data/lib/snap_diff/drivers.rb +20 -0
  25. data/lib/snap_diff/dsl.rb +12 -12
  26. data/lib/snap_diff/errors.rb +19 -0
  27. data/lib/snap_diff/integrations/cucumber.rb +5 -4
  28. data/lib/snap_diff/integrations/minitest.rb +11 -12
  29. data/lib/snap_diff/integrations/rspec.rb +7 -6
  30. data/lib/snap_diff/legacy_shims.rb +18 -0
  31. data/lib/snap_diff/region.rb +117 -0
  32. data/lib/snap_diff/reporters/default.rb +107 -0
  33. data/lib/snap_diff/reporters/html.rb +7 -9
  34. data/lib/snap_diff/reporting.rb +13 -4
  35. data/lib/snap_diff/screenshot_assertion.rb +37 -4
  36. data/lib/snap_diff/screenshot_matcher.rb +4 -4
  37. data/lib/snap_diff/screenshoter.rb +1 -1
  38. data/lib/snap_diff/snap_manager.rb +1 -2
  39. data/lib/snap_diff/stable_screenshoter.rb +2 -2
  40. data/lib/snap_diff/utils.rb +5 -3
  41. data/lib/snap_diff/version.rb +1 -1
  42. data/lib/snap_diff.rb +44 -15
  43. metadata +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 544c2e3414e9b616291b0cd0e843e72d1a41652017fc2b966926665285aaf49a
4
- data.tar.gz: b2a226430eaeab196e297efc9c476eb4cf0d192334d1503ba29f330fb9bd81ed
3
+ metadata.gz: 480337577271341982e3f8eef57cf123003092ba90833e4b4a497676920c0466
4
+ data.tar.gz: e844a383454976b32c46059e4593dd0c472133d22eac3e1d531b6bca35935f6e
5
5
  SHA512:
6
- metadata.gz: 91c3e8fd69a3f7e981f77ef8425ccc231e2b9617868f5278bb74c6865c82d59828ab708f7ecf7e57db0f891338f20704c277e8865e8f8444762ffc1b3929748d
7
- data.tar.gz: 2651d17eaceca6a6b8f5c94136e8e221639ec5cf70d70d9178d9d30a630f5014e5eb240ace31233a45a456cf3918d0e70b324c998e2b4ab3531bac7d7d21ffe0
6
+ metadata.gz: b44e77a9e696cd8f326655ba6bd19d5b4275f7f2b86ea0961c913dc30d1ecb6eb0806fa89340359357049bf70c2c015ccaf4d22fc9f6d733c84788db358435f4
7
+ data.tar.gz: 322a1b64abd4bc2234dea9f6ffcda02f7dac5e9e09c640f1f6decc588b0715953add332a08a61e685bec944358b80b999f77a17b5524663e6c107f771ad7a2fb
data/CHANGELOG.md CHANGED
@@ -5,6 +5,85 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [v2.0.0.beta2] - 2026-08-23
9
+
10
+ Third prerelease of the 2.0 opt-in experiment. This one finishes the namespace
11
+ move: `SnapDiff` no longer depends on the namespaces it deprecates. Legacy names
12
+ keep working exactly as before — see the v2.0.0.alpha1 notes below for the
13
+ opt-in and silencing basics, and [docs/snapdiff.md](docs/snapdiff.md) for the
14
+ canonical API.
15
+
16
+ ### Changed
17
+ - **Everything canonical now lives in `SnapDiff`** — configuration storage
18
+ (`SnapDiff::Config`, one storage behind every settings surface), error classes
19
+ (`SnapDiff::Error`, `ExpectationNotMet`, `UnstableImage`,
20
+ `WindowSizeMismatchError`), `SnapDiff::Region`, `SnapDiff::Reporters::Default`,
21
+ the driver registry (`SnapDiff::Drivers.loaded`/`.available`), the per-test
22
+ session (`SnapDiff.session`), and reporter registration
23
+ (`SnapDiff::Reporting.register`, mutex-guarded). Old constants remain
24
+ same-object aliases; `rescue`, `is_a?`, and `defined?` on them are unchanged
25
+ ([#224](https://github.com/snap-diff/snap_diff-capybara/pull/224)–[#230](https://github.com/snap-diff/snap_diff-capybara/pull/230))
26
+ - **Error class names in failure output** now print as `SnapDiff::…` (the class
27
+ objects are identical, so `rescue CapybaraScreenshotDiff::ExpectationNotMet`
28
+ still catches them — only the printed name differs). CI jobs that
29
+ string-match on the old class name in output need updating.
30
+ - The images-holder struct is now `SnapDiff::Comparison::Images`, ending the
31
+ two-classes-one-name collision with the comparator
32
+ ([#227](https://github.com/snap-diff/snap_diff-capybara/pull/227))
33
+
34
+ ### Added
35
+ - **Deprecation warnings now name your call site** — `(called from
36
+ your_file.rb:42)`, so migration is warning-driven instead of grep-driven
37
+ ([#222](https://github.com/snap-diff/snap_diff-capybara/pull/222))
38
+ - **Dual-install guard** — installing both `capybara-screenshot-diff` and
39
+ `snap_diff-capybara` now raises a clear error instead of silently loading
40
+ files from whichever gem activated first
41
+ ([#222](https://github.com/snap-diff/snap_diff-capybara/pull/222))
42
+ - **[docs/snapdiff.md](docs/snapdiff.md)** — the SnapDiff-native guide: quick
43
+ start for all four integrations, configuration, custom drivers and reporters,
44
+ standalone comparison ([#231](https://github.com/snap-diff/snap_diff-capybara/pull/231))
45
+
46
+ ### Removed
47
+ - The unused `anchor:` keyword on the internal viewport seam; v3's
48
+ scroll-preservation work will design its real contract
49
+ ([#229](https://github.com/snap-diff/snap_diff-capybara/pull/229))
50
+
51
+ ### Internal
52
+ - A test now mechanically enforces that the legacy namespace trees contain only
53
+ requires, aliases, and one-line forwarders — no real logic — so removing them
54
+ in 3.0 is a deletion, not a refactor
55
+ ([#229](https://github.com/snap-diff/snap_diff-capybara/pull/229),
56
+ [#230](https://github.com/snap-diff/snap_diff-capybara/pull/230))
57
+ - Release workflow is idempotent on re-run; config default-eval timing is
58
+ pinned by guards across every entry point
59
+ ([#222](https://github.com/snap-diff/snap_diff-capybara/pull/222),
60
+ [#223](https://github.com/snap-diff/snap_diff-capybara/pull/223))
61
+
62
+ ---
63
+
64
+ ## [v2.0.0.beta1] - 2026-08-22
65
+
66
+ Second prerelease of the 2.0 opt-in experiment (see the v2.0.0.alpha1 notes
67
+ below for the namespace change, deprecation warnings, and caveats). Final
68
+ 2.0.0 remains gated on adopter feedback — [#166](https://github.com/snap-diff/snap_diff-capybara/issues/166).
69
+
70
+ ### Added
71
+ - **Dual gem names** — releases now also publish as
72
+ [`snap_diff-capybara`](https://rubygems.org/gems/snap_diff-capybara):
73
+ identical content and versions under the forward-looking name matching this
74
+ repository (the alpha1 mirror was backfilled). Install either, not both.
75
+ - **Migration guide** — [docs/UPGRADING.md](docs/UPGRADING.md) covers the v2
76
+ namespace move, every renamed constant, silencing, and the alpha caveats
77
+ ([#220](https://github.com/snap-diff/snap_diff-capybara/pull/220))
78
+
79
+ ### Internal
80
+ - Test suite exercises the canonical `SnapDiff` names; legacy names remain
81
+ covered by dedicated forwarding/deprecation tests, and a strict warning
82
+ guard now fails the suite on any accidental legacy-name use
83
+ ([#221](https://github.com/snap-diff/snap_diff-capybara/pull/221))
84
+
85
+ ---
86
+
8
87
  ## [v2.0.0.alpha1] - 2026-08-22
9
88
 
10
89
  **Opt-in experiment prerelease.** RubyGems never installs prereleases by default
data/docs/UPGRADING.md CHANGED
@@ -1,5 +1,247 @@
1
1
  # Upgrading
2
2
 
3
+ ## Upgrading to v2.0 (alpha)
4
+
5
+ ### Overview
6
+
7
+ Version 2.0 introduces a new canonical namespace (`SnapDiff`) for cleaner, more discoverable code. The public DSL remains unchanged — your existing `screenshot` and `assert_matches_screenshot` calls work without modification. This guide covers the optional migration path for settings and the new namespace.
8
+
9
+ **Status:** `2.0.0.beta2` is an opt-in prerelease. RubyGems never installs prereleases by default resolution — normal `bundle update` keeps you on the 1.x line. The final 2.0.0 ships only after adopter feedback; please report anything surprising on [#166](https://github.com/snap-diff/snap_diff-capybara/issues/166).
10
+
11
+ **Estimated upgrade time:** 5–15 minutes (most users need only the Gemfile pin)
12
+
13
+ **Writing new code rather than migrating?** Skip this guide and read
14
+ [SnapDiff — the canonical API](snapdiff.md): the same setup, configuration, and extension points
15
+ with canonical names only, no legacy shapes to unlearn.
16
+
17
+ **Breaking changes:** None for the DSL; deprecation warnings if you reference legacy constants (suppressible), plus two known alpha caveats (see below)
18
+
19
+ ---
20
+
21
+ ### The Short Version (Most Users)
22
+
23
+ ```ruby
24
+ # In your Gemfile — the exact prerelease version is required to opt in
25
+ gem "capybara-screenshot-diff", "2.0.0.beta2" # or the latest 2.0.0 prerelease
26
+ ```
27
+
28
+ ```bash
29
+ bundle install
30
+ bundle exec rake test
31
+ ```
32
+
33
+ **That's it.** Your existing code works unchanged. The old namespaces (`Capybara::Screenshot::Diff`, `CapybaraScreenshotDiff`) are shimmed with deprecation warnings; the new one (`SnapDiff`) is available if you want to modernize.
34
+
35
+ ---
36
+
37
+ ### What Changed
38
+
39
+ #### 1. New Canonical Namespace: `SnapDiff`
40
+
41
+ The implementation now lives in `lib/snap_diff/` under the `SnapDiff` namespace. Every legacy constant still resolves — lazily, to the *same object* — but emits a one-time-per-constant deprecation warning. The main renames:
42
+
43
+ | Legacy name | v2 canonical name |
44
+ |-------------|-------------------|
45
+ | `Capybara::Screenshot::Diff::ImageCompare` | `SnapDiff::Comparison` |
46
+ | `Capybara::Screenshot::Diff::Difference` | `SnapDiff::ComparisonResult` |
47
+ | `Capybara::Screenshot::Diff::Drivers::BaseDriver` | `SnapDiff::Driver` (now a mixin — see below) |
48
+ | `CapybaraScreenshotDiff::SnapManager` / `::Snap` | `SnapDiff::SnapManager` / `SnapDiff::Snap` |
49
+ | `CapybaraScreenshotDiff::RED_RGBA` / `::ORANGE_RGBA` | `SnapDiff::RED_RGBA` / `SnapDiff::ORANGE_RGBA` |
50
+ | `CapybaraScreenshotDiff::Minitest::Assertions` | `SnapDiff::Minitest::Assertions` |
51
+ | `require "capybara_screenshot_diff/minitest"` | `require "snap_diff/integrations/minitest"` |
52
+ | `require "capybara_screenshot_diff/rspec"` | `require "snap_diff/integrations/rspec"` |
53
+ | `require "capybara_screenshot_diff/cucumber"` | `require "snap_diff/integrations/cucumber"` |
54
+ | `require "capybara_screenshot_diff/reporters/html"` | `require "snap_diff/reporters/html"` |
55
+ | `CapybaraScreenshotDiff.serve` (`…/static`) | `SnapDiff.serve` (`require "snap_diff/static"`) |
56
+ | `CapybaraScreenshotDiff.reporters <<` | `SnapDiff::Reporting.register` |
57
+ | `CapybaraScreenshotDiff.finalize_reporters!` | `SnapDiff::Reporting.finalize!` |
58
+
59
+ Note the integration require paths gain an `integrations/` segment — `require "snap_diff/minitest"`
60
+ is a `LoadError`.
61
+
62
+ **What stays the same:**
63
+ - `screenshot(name)` — still works
64
+ - `assert_matches_screenshot(name)` — still works, still the recommended form
65
+ - `capture_screenshot(name)` — still works
66
+ - All `compare: false/true` flags and overrides work identically
67
+
68
+ **What's new (optional):**
69
+
70
+ ```ruby
71
+ # Old (still works; constant access now warns once per process)
72
+ Capybara::Screenshot::Diff.compare("baseline.png", "current.png")
73
+ Capybara::Screenshot::Diff.configure { |screenshot, diff| ... }
74
+
75
+ # New (recommended for new code)
76
+ SnapDiff.compare("baseline.png", "current.png")
77
+ SnapDiff.start { |screenshot, diff| ... } # same shape as old configure
78
+ SnapDiff.configure { |config| ... } # consolidated config object
79
+ ```
80
+
81
+ #### 2. Consolidated Configuration: `SnapDiff.config`
82
+
83
+ Instead of scattering settings across `Capybara::Screenshot` and `Capybara::Screenshot::Diff`, v2.0 offers a single `SnapDiff::Config` object. Both the old and new paths read and write the same underlying storage — writes through either are visible through the other.
84
+
85
+ **The DSL never changes.** `screenshot` and `assert_matches_screenshot` work exactly as before.
86
+
87
+ ---
88
+
89
+ ### Settings Migration Table
90
+
91
+ The most commonly-used settings and how to update them:
92
+
93
+ | Setting | v1.x (still works in v2) | v2.0 (recommended) | What it does |
94
+ |---------|----------------------|------------------|------|
95
+ | `blur_active_element` | `Capybara::Screenshot.blur_active_element = true` | `SnapDiff.config.blur_active_element = true` | Hide cursor/focus indicator in screenshots (default: `true`) |
96
+ | `hide_caret` | `Capybara::Screenshot.hide_caret = true` | `SnapDiff.config.hide_caret = true` | Make input caret transparent for stable comparisons (default: `true`) |
97
+ | `tolerance` | `Capybara::Screenshot::Diff.tolerance = 0.0005` | `SnapDiff.config.tolerance = 0.0005` | Pixel-level color difference threshold (higher = less strict) |
98
+ | `save_path` | `Capybara::Screenshot.save_path = "doc/screenshots"` | `SnapDiff.config.save_path = "doc/screenshots"` | Where baseline screenshots are stored |
99
+ | `window_size` | `Capybara::Screenshot.window_size = [1280, 1024]` | `SnapDiff.config.window_size = [1280, 1024]` | Browser viewport size for consistent screenshots |
100
+
101
+ **All 27 settings** from both legacy namespaces are available via `SnapDiff.config.<attr_name>` — see the [Configuration Reference](configuration.md) for the full list. One rename to note: `Capybara::Screenshot.enabled` becomes `SnapDiff.config.screenshot_enabled` (it would otherwise collide with `Capybara::Screenshot::Diff.enabled`, which keeps the bare `enabled` name).
102
+
103
+ ---
104
+
105
+ ### Three Ways to Configure
106
+
107
+ All three are equivalent and use the same underlying storage. Pick the one that fits your style.
108
+
109
+ #### Option 1: Traditional block (v1 shape, still works)
110
+
111
+ ```ruby
112
+ # In test_helper.rb or spec_helper.rb
113
+ Capybara::Screenshot::Diff.configure do |screenshot, diff|
114
+ screenshot.window_size = [1280, 1024]
115
+ screenshot.blur_active_element = false
116
+ diff.tolerance = 0.0005
117
+ diff.driver = :vips
118
+ end
119
+ ```
120
+
121
+ #### Option 2: SnapDiff block with old shape (backward-compatible)
122
+
123
+ ```ruby
124
+ SnapDiff.start do |screenshot, diff|
125
+ screenshot.window_size = [1280, 1024]
126
+ screenshot.blur_active_element = false
127
+ diff.tolerance = 0.0005
128
+ diff.driver = :vips
129
+ end
130
+ ```
131
+
132
+ #### Option 3: Consolidated config (cleanest)
133
+
134
+ ```ruby
135
+ SnapDiff.configure do |config|
136
+ config.window_size = [1280, 1024]
137
+ config.blur_active_element = false
138
+ config.tolerance = 0.0005
139
+ config.driver = :vips
140
+ end
141
+ ```
142
+
143
+ ---
144
+
145
+ ### Prepare Today on v1.x (Zero Risk)
146
+
147
+ You don't have to wait for v2.0 to start using the new namespace. `SnapDiff.compare` and `SnapDiff.start` were added in v1.14; `SnapDiff.config` / `SnapDiff.configure` in v1.15. All of them work on the current 1.x line:
148
+
149
+ ```ruby
150
+ # Works TODAY on v1.15+, zero risk
151
+ SnapDiff.compare("baseline.png", "current.png")
152
+ SnapDiff.start { |screenshot, diff| ... }
153
+ SnapDiff.configure { |config| ... }
154
+ ```
155
+
156
+ This means you can migrate your codebase incrementally **now**, before opting into 2.0.
157
+
158
+ ---
159
+
160
+ ### Deprecation Warnings
161
+
162
+ In v2.0, resolving a legacy *constant* emits one deprecation warning per constant per process:
163
+
164
+ ```
165
+ [snap_diff deprecation] `Capybara::Screenshot::Diff::ImageCompare` is deprecated (constant); use `SnapDiff::Comparison` instead.
166
+ ```
167
+
168
+ **Warnings appear for:** legacy constant access — `Capybara::Screenshot::Diff::ImageCompare`, `::Difference`, `::Drivers`, `CapybaraScreenshotDiff::SnapManager`, etc.
169
+
170
+ **Warnings do NOT appear for:**
171
+ - Requiring the gem: `require "capybara_screenshot_diff/minitest"` etc. is not deprecated
172
+ - The DSL: `screenshot`, `assert_matches_screenshot`, `capture_screenshot` are never deprecated
173
+ - Settings access: `Capybara::Screenshot.blur_active_element`, `Capybara::Screenshot::Diff.tolerance=`, and the `Diff.configure` block stay silent — they remain the canonical storage that `SnapDiff.config` forwards to
174
+ - A few advertised entry-point constants that stay eagerly defined by design: `Capybara::Screenshot::Os`, `CapybaraScreenshotDiff::DSL`, `Capybara::Screenshot::Diff::VERSION`, and the driver leaf classes (`Drivers::VipsDriver`, `Drivers::ChunkyPNGDriver`)
175
+
176
+ Warnings go through `Kernel#warn`, so test suites that hook `Warning.warn` (e.g. raise-on-warning setups) see them like any other Ruby warning.
177
+
178
+ #### Silencing Warnings
179
+
180
+ If warnings appear in a test run and you're not ready to migrate yet:
181
+
182
+ ```ruby
183
+ # In test_helper.rb, before running tests
184
+ SnapDiff.silence_deprecations = true
185
+ ```
186
+
187
+ ```bash
188
+ # Or as an environment variable
189
+ export SNAP_DIFF_SILENCE_DEPRECATIONS=1
190
+ ```
191
+
192
+ ---
193
+
194
+ ### Known Alpha Caveats
195
+
196
+ Two deliberate consequences of the lazy shim design — both flagged for feedback on [#166](https://github.com/snap-diff/snap_diff-capybara/issues/166):
197
+
198
+ 1. **`defined?` / `const_defined?` on lazily-shimmed legacy names returns `false`/`nil`.** The shims resolve via `const_missing`, which those checks never trigger. Feature detection like `defined?(Capybara::Screenshot::Diff::ImageCompare)` must move to the `SnapDiff::` name. Rescuing legacy *error classes* is unaffected — they remain eagerly defined.
199
+
200
+ 2. **Reopening `module Capybara::Screenshot::Diff::Drivers` shadows the shim.** The historical custom-driver monkey-patch pattern defines a fresh, empty `Drivers` module instead of reaching the real one. Define custom drivers under `SnapDiff::Drivers` instead — and note `BaseDriver` is gone as a superclass: `class MyDriver < BaseDriver` becomes `include SnapDiff::Driver` (it's a mixin now).
201
+
202
+ ---
203
+
204
+ ### FAQ
205
+
206
+ #### "My tests pass but I see warnings. Should I worry?"
207
+
208
+ No. Warnings are informational and fully suppressible. They're designed to catch legacy namespace references, not break existing CI. If silence is preferable for now, set `SNAP_DIFF_SILENCE_DEPRECATIONS=1` and migrate at your pace.
209
+
210
+ #### "Does the DSL change at all?"
211
+
212
+ No. `screenshot`, `assert_matches_screenshot`, and `capture_screenshot` are stable and unchanged. All overrides (`:compare`, `:tolerance`, etc.) work identically.
213
+
214
+ #### "Can I mix old and new config in the same suite?"
215
+
216
+ Yes. Both paths write to the same underlying storage:
217
+
218
+ ```ruby
219
+ Capybara::Screenshot::Diff.configure do |screenshot, diff|
220
+ screenshot.window_size = [1280, 1024]
221
+ end
222
+
223
+ SnapDiff.configure do |config|
224
+ config.tolerance = 0.0005 # Same storage, visible to the old path too
225
+ end
226
+ ```
227
+
228
+ #### "What if I need to roll back?"
229
+
230
+ All settings and baselines are compatible with v1.x. Simply pin your Gemfile back to `"~> 1.15"` and `bundle update capybara-screenshot-diff`.
231
+
232
+ ---
233
+
234
+ ### Summary Checklist
235
+
236
+ - [ ] Pin `gem "capybara-screenshot-diff", "2.0.0.beta2"` (or the latest 2.0.0 prerelease) in your Gemfile
237
+ - [ ] Run `bundle install`
238
+ - [ ] Run your test suite to verify no regressions
239
+ - [ ] (Optional) Migrate config to the `SnapDiff` namespace
240
+ - [ ] (Optional) Silence deprecation warnings if not ready to migrate
241
+ - [ ] Report anything surprising on [#166](https://github.com/snap-diff/snap_diff-capybara/issues/166)
242
+
243
+ ---
244
+
3
245
  ## Upgrading to v1.13.0
4
246
 
5
247
  ### Overview
@@ -452,7 +694,7 @@ All screenshot baselines are compatible — no data loss.
452
694
 
453
695
  ## Need Help?
454
696
 
455
- - **Documentation:** [README.md](README.md)
697
+ - **Documentation:** [README.md](../README.md)
456
698
  - **Changelog:** [CHANGELOG.md](CHANGELOG.md)
457
699
  - **Issues:** [GitHub Issues](https://github.com/snap-diff/snap_diff-capybara/issues)
458
700
  - **DeepWiki:** [Code Documentation](https://deepwiki.com/snap-diff/snap_diff-capybara)
data/docs/architecture.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  This document describes the internal architecture of `capybara-screenshot-diff` — how screenshots are captured, compared, and reported, and how the components fit together.
4
4
 
5
+ Since the v2 namespace move (ADR-004), the implementation lives in `lib/snap_diff/` under the `SnapDiff` namespace. The old file paths (`lib/capybara/screenshot/diff/`, `lib/capybara_screenshot_diff/`) remain as thin forwarders, and the old constants resolve to the same objects via `lib/snap_diff/legacy_shims.rb` with a one-time deprecation warning. Class names below use the canonical `SnapDiff::` names, with legacy names noted where they differ. For the user-facing view of the same surface, see [SnapDiff — the canonical API](snapdiff.md).
6
+
5
7
  ## Overview
6
8
 
7
9
  ```
@@ -12,8 +14,8 @@ This document describes the internal architecture of `capybara-screenshot-diff`
12
14
  │ │
13
15
  ▼ ▼
14
16
  ┌──────────────────────────┐ ┌──────────────────────────┐
15
- CapybaraScreenshotDiff │ │ CapybaraScreenshotDiff
16
- ::DSL (screenshot, etc) │ │ ::AssertionRegistry
17
+ SnapDiff::DSL │ │ SnapDiff::AssertionRegistry
18
+ │ (screenshot, etc) │ │ (SnapDiff.session)
17
19
  └──────────────┬───────────┘ └──────────────┬───────────┘
18
20
  │ │
19
21
  ▼ ▼
@@ -31,7 +33,7 @@ This document describes the internal architecture of `capybara-screenshot-diff`
31
33
 
32
34
 
33
35
  ┌──────────────────────────────────────────┐
34
- ImageCompare (layered comparison)
36
+ SnapDiff::Comparison (layered compare)
35
37
  │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
36
38
  │ │1. Byte │ │2. Pixel │ │3. Region │ │
37
39
  │ │ compare │ │ compare │ │ analyze │ │
@@ -50,7 +52,9 @@ This document describes the internal architecture of `capybara-screenshot-diff`
50
52
 
51
53
  ## Component Breakdown
52
54
 
53
- ### 1. DSL Layer (`lib/capybara_screenshot_diff/dsl.rb`)
55
+ ### 1. DSL Layer (`lib/snap_diff/dsl.rb`)
56
+
57
+ `SnapDiff::DSL` — `CapybaraScreenshotDiff::DSL` remains an eager same-object alias.
54
58
 
55
59
  The entry point for test code. `assert_matches_screenshot` is the primary assertion method (captures and compares). `screenshot` is a convenience wrapper with a `compare:` option — `compare: true` (default) delegates to `assert_matches_screenshot`, while `compare: false` delegates to the new `capture_screenshot` method. `capture_screenshot` takes screenshots without assertions. `assert_no_screenshot_changes` keeps its behavior but now delegates to `assert_matches_screenshot` (that redirect is part of the #191 fix). Users can safely override `screenshot` in their test classes without affecting internal gem flow.
56
60
 
@@ -60,20 +64,20 @@ The entry point for test code. `assert_matches_screenshot` is the primary assert
60
64
  3. Delegates to `ScreenshotMatcher` to capture and prepare comparison
61
65
  4. Creates a `ScreenshotAssertion` — either adds it to the thread-local registry (delayed validation) or validates immediately
62
66
 
63
- ### 2. ScreenshotMatcher (`lib/capybara/screenshot/diff/screenshot_matcher.rb`)
67
+ ### 2. ScreenshotMatcher (`lib/snap_diff/screenshot_matcher.rb`)
64
68
 
65
69
  The orchestrator that coordinates capture and comparison:
66
70
 
67
- 1. **Window size check** — verifies browser window is the expected size
71
+ 1. **Viewport preparation** — `SnapDiff::Capture::Viewport.prepare!` (`lib/snap_diff/capture/viewport.rb`) verifies the browser window is the expected size (raise-only, never resizes); runs once per capture, outside any stability retry loop
68
72
  2. **Area calculation** — resolves crop regions and skip areas (supports CSS selectors and coordinates)
69
73
  3. **Base screenshot checkout** — retrieves the committed baseline from git via `Vcs.checkout_vcs`
70
74
  4. **Capture** — delegates to `Screenshoter` or `StableScreenshoter` depending on `stability_time_limit`
71
- 5. **Comparison** — creates an `ImageCompare` object (lazy — actual comparison happens on first access)
75
+ 5. **Comparison** — creates a `SnapDiff::Comparison` object (lazy — actual comparison happens on first access)
72
76
  6. **Assertion** — returns a `ScreenshotAssertion` with the comparison attached
73
77
 
74
78
  **Key design decision:** The "new" screenshot is taken *first*, then compared against the baseline. This means if no baseline exists (first run), we skip comparison entirely and the test passes.
75
79
 
76
- ### 3. Screenshoter & StableScreenshoter (`lib/capybara/screenshot/diff/screenshoter.rb`, `lib/capybara/screenshot/diff/stable_screenshoter.rb`)
80
+ ### 3. Screenshoter & StableScreenshoter (`lib/snap_diff/screenshoter.rb`, `lib/snap_diff/stable_screenshoter.rb`)
77
81
 
78
82
  **Screenshoter:** The basic capture flow:
79
83
  1. Prepares the page (blur active element, hide caret, disable animations, wait for images)
@@ -86,7 +90,9 @@ The orchestrator that coordinates capture and comparison:
86
90
  3. Returns once two consecutive screenshots are identical
87
91
  4. Fails with `UnstableImage` if timeout (`wait`) is reached, generating annotated attempt images for debugging
88
92
 
89
- ### 4. ImageCompare (`lib/capybara/screenshot/diff/image_compare.rb`)
93
+ ### 4. Comparison (`lib/snap_diff/comparison.rb`)
94
+
95
+ `SnapDiff::Comparison` (legacy name: `ImageCompare`); its result value object is `SnapDiff::ComparisonResult` (`lib/snap_diff/comparison_result.rb`, legacy name: `Difference`).
90
96
 
91
97
  The comparison engine uses a **layered optimization strategy** to balance speed and accuracy:
92
98
 
@@ -100,11 +106,11 @@ The comparison engine uses a **layered optimization strategy** to balance speed
100
106
  - `quick_equal?` is designed for fast rejection — it early-returns as soon as a difference is found
101
107
  - `different?` triggers the full comparison if not already processed
102
108
  - `processed` guarantees the comparison is complete and returns the result with all metadata
103
- - `ImageCompare#analyze_difference` handles the actual pixel analysis, delegating to the driver
109
+ - `Comparison#analyze_difference` handles the actual pixel analysis, delegating to the driver
104
110
 
105
- ### 5. Drivers (`lib/capybara/screenshot/diff/drivers/`)
111
+ ### 5. Drivers (`lib/snap_diff/drivers/`)
106
112
 
107
- Drivers abstract image processing operations. Each driver implements:
113
+ Drivers abstract image processing operations. Shared default behavior lives in the `SnapDiff::Driver` mixin (`lib/snap_diff/driver.rb`) — it replaced the old `Drivers::BaseDriver` superclass, so concrete drivers `include SnapDiff::Driver` instead of inheriting. Each driver implements:
108
114
 
109
115
  | Operation | VipsDriver | ChunkyPNGDriver |
110
116
  |-----------|-----------|-----------------|
@@ -121,6 +127,8 @@ Drivers abstract image processing operations. Each driver implements:
121
127
 
122
128
  **Auto-detection:** `Utils.detect_available_drivers` tries to load `:vips` first (via `ruby-vips` gem), then `:chunky_png`. The `:auto` driver mode picks the first available.
123
129
 
130
+ **Registry (ADR-008 step 5b):** `SnapDiff::Drivers.loaded` is the canonical driver-class cache — a `name => class` hash filled lazily by `Utils.find_driver_class_for`, and the registration point for custom drivers (the legacy `Capybara::Screenshot::Diff::LOADED_DRIVERS` is an eager same-object alias, so registrations through either land in the same hash). `SnapDiff::Drivers.available` is the canonical read API for the detected list; the value itself still lives on `Capybara::Screenshot::Diff::AVAILABLE_DRIVERS`, which stays the published stubbing point. `SnapDiff::Drivers.for` resolves an options hash to a driver instance. See [Custom drivers](snapdiff.md#custom-drivers).
131
+
124
132
  ### 6. Difference Region Detection
125
133
 
126
134
  **VipsDriver** uses a **difference mask** approach:
@@ -135,7 +143,7 @@ Drivers abstract image processing operations. Each driver implements:
135
143
  3. Extend bottom boundary to cover all differing rows
136
144
  4. Supports shift detection (expensive neighbor pixel search)
137
145
 
138
- ### 7. SnapManager & Snap (`lib/capybara_screenshot_diff/snap_manager.rb`, `lib/capybara_screenshot_diff/snap.rb`)
146
+ ### 7. SnapManager & Snap (`lib/snap_diff/snap_manager.rb`, `lib/snap_diff/snap.rb`)
139
147
 
140
148
  **Snap** represents a single screenshot file with path management:
141
149
  - `path` — the actual screenshot file
@@ -148,11 +156,11 @@ Drivers abstract image processing operations. Each driver implements:
148
156
  - Handles VCS checkout of baselines
149
157
  - Manages file operations (copy, move, cleanup)
150
158
 
151
- ### 8. VCS (`lib/capybara/screenshot/diff/vcs.rb`)
159
+ ### 8. VCS (`lib/snap_diff/vcs.rb`)
152
160
 
153
161
  Handles baseline retrieval from git. Uses `git show HEAD:<path>` to extract the committed version. Supports Git LFS via `git lfs smudge`. Returns `false` if the file doesn't exist in VCS (first-run scenario).
154
162
 
155
- ### 9. Reporters (`lib/capybara/screenshot/diff/reporters/`)
163
+ ### 9. Reporters (`lib/snap_diff/reporters/default.rb`, `lib/snap_diff/reporters/html.rb`)
156
164
 
157
165
  **Default reporter:** Generates annotated diff images:
158
166
  - `image.diff.png` — new screenshot with diff region outlined in red
@@ -167,7 +175,7 @@ Handles baseline retrieval from git. Uses `git show HEAD:<path>` to extract the
167
175
  - Keyboard navigation and shortcuts
168
176
  - Responsive layout for mobile
169
177
 
170
- **Custom reporters:** Implement `record(assertions)` and `finalize` methods, then add to `CapybaraScreenshotDiff.reporters`.
178
+ **Custom reporters:** Implement `record(assertions)`, `finalize` and `summary`, then register via `SnapDiff::Reporting.register(reporter)` — the canonical way in, because the append happens under the mutex. The process-global reporter lifecycle (registration, notification, finalization) is owned by `SnapDiff::Reporting` (`lib/snap_diff/reporting.rb`); `CapybaraScreenshotDiff.reporters` / `.finalize_reporters!` are thin public shims over it, and `reporters` stays a mutable array for compatibility (appending directly still works, it just skips the lock). See [Custom reporters](snapdiff.md#custom-reporters).
171
179
 
172
180
  ### 10. Assertion Lifecycle
173
181
 
@@ -183,16 +191,19 @@ Test begins
183
191
  │ └─ delayed=false → validate immediately
184
192
 
185
193
  ├─ teardown:
186
- └─ CapybaraScreenshotDiff.verify
187
- ├─ iterates thread-local assertions
188
- ├─ calls validate on each
189
- ├─ raises ExpectationNotMet on first failure
190
- └─ notifies reporters via mutex-protected snapshot
194
+ ├─ SnapDiff.session.verify
195
+ ├─ iterates the fiber-local assertions
196
+ ├─ calls validate on each
197
+ │ └─ raises SnapDiff::ExpectationNotMet if any differed
198
+
199
+ │ └─ SnapDiff.reset (always, in an ensure)
200
+ │ ├─ SnapDiff::Reporting.notify — mutex-protected reporter snapshot
201
+ │ └─ clears the session
191
202
 
192
- └─ at_exit:
193
- └─ CapybaraScreenshotDiff.finalize_reporters!
203
+ └─ end of suite (Minitest.after_run / RSpec after(:suite) / Cucumber AfterAll):
204
+ └─ SnapDiff::Reporting.finalize!
194
205
  ├─ Generates HTML report (if reporter registered)
195
- └─ Prints summary
206
+ └─ Prints each reporter's summary
196
207
  ```
197
208
 
198
209
  ### 11. Thread Safety
@@ -203,12 +214,16 @@ Test begins
203
214
  | Reporter notification | Mutex-protected snapshot of reporter list before iteration |
204
215
  | HTML reporter internals | Mutex protecting `@failures`, `@total`, `@finalized` |
205
216
  | Screenshot naming | Per-thread `ScreenshotNamer` instance |
206
- | Global configuration | `mattr_accessor` — must be set before tests run, not mutated during parallel execution |
217
+ | Global configuration | One process-wide `SnapDiff::Config` instance — must be set before tests run, not mutated during parallel execution |
207
218
  | File system | Atomic `FileUtils.mv`, unique paths per screenshot name + counter, thread-safe `mkpath` |
208
219
 
209
220
  ### 12. Configuration System
210
221
 
211
- Configuration uses Ruby's `mattr_accessor` (from ActiveSupport, or pure Ruby fallback) and is organized into two namespaces:
222
+ Since ADR-008 step 1 the storage ownership is inverted from the original v2 consolidation: **`SnapDiff::Config` (`lib/snap_diff/config.rb`) IS the storage** — one eagerly-created instance, reachable as `SnapDiff.config`, holding every setting as a plain `attr_accessor`. It is the leaf of the config require graph and requires nothing that leads back to either entry point.
223
+
224
+ The legacy `Capybara::Screenshot.*` / `Capybara::Screenshot::Diff.*` accessors are thin delegators generated from `Config::MAPPING` (both singleton and instance methods, matching what `mattr_accessor` used to define) that forward to that one object. One storage, two views — a write through either surface is visible through the other structurally, not by synchronization. `lib/capybara/screenshot/diff/config_legacy.rb` remains at the old path, but it now installs the delegating surface rather than owning the state.
225
+
226
+ The two legacy views are organized into two namespaces:
212
227
 
213
228
  **`Capybara::Screenshot`** — capture settings:
214
229
  - `window_size`, `stability_time_limit`, `blur_active_element`, `hide_caret`, `disable_animations`
@@ -219,49 +234,72 @@ Configuration uses Ruby's `mattr_accessor` (from ActiveSupport, or pure Ruby fal
219
234
  - `driver`, `tolerance`, `color_distance_limit`, `perceptual_threshold`, `shift_distance_limit`
220
235
  - `area_size_limit`, `skip_area`, `fail_if_new`, `fail_on_difference`, `delayed`
221
236
 
222
- The `Diff.configure` block helper provides a convenient way to set both namespaces at once.
237
+ The canonical way in is `SnapDiff.configure { |config| ... }` (all 27 settings flat on one object). `SnapDiff.start` and `Capybara::Screenshot::Diff.configure` are the two-holder block shape over the same storage — since ADR-008 step 7b, `Diff.configure` forwards to `SnapDiff.start` rather than the other way round.
238
+
239
+ `Config` also owns the derived values that used to live on the legacy modules: `active?` (ex `Capybara::Screenshot.active?`), `screenshot_area` / `screenshot_area_abs`, and `default_options` (ex `Capybara::Screenshot::Diff.default_options`, the option hash handed to `SnapDiff::Comparison`). The legacy module methods one-line forward here.
240
+
241
+ **Default timing contract:** every default is evaluated once, in `Config#initialize`, which runs at require time of `config.rb` — the same load moment the old `mattr_accessor` default blocks evaluated at. `fail_if_new` (from `ENV["CI"]`) and `root` (from `Rails.root`) must never become lazy read-time defaults. The one deliberately live value is `default_options[:wait]`, a method-body read of `Capybara.default_max_wait_time`.
223
242
 
224
243
  ## File Layout
225
244
 
226
245
  ```
227
246
  lib/
228
- capybara_screenshot_diff.rb # Core module, mattr_accessor definitions
229
- capybara/screenshot/diff.rb # Convenience require (loads minitest)
230
- capybara_screenshot_diff/
247
+ snap_diff.rb # SnapDiff module: compare/start/configure/config
248
+ snap_diff/ # Canonical implementation (v2)
231
249
  dsl.rb # screenshot(), screenshot_group(), etc.
232
- minitest.rb # Minitest assertions integration
233
- rspec.rb # RSpec matcher integration
234
- cucumber.rb # Cucumber World integration
235
- static.rb # Non-Rails static site serving
236
- snap_manager.rb # Screenshot file management
237
- snap.rb # Single screenshot file abstraction
238
- screenshot_namer.rb # Name/path generation with sections/groups
239
- screenshot_assertion.rb # Assertion + registry objects
240
- attempts_reporter.rb # Debug reporting for unstable captures
241
- error_with_filtered_backtrace.rb # Error with filtered stack
242
- reporters/
243
- html.rb # Interactive HTML report reporter
244
- templates/report.html.erb # HTML report template
245
- capybara/screenshot/diff/
246
- version.rb # VERSION constant
247
- utils.rb # Driver detection
250
+ config.rb # SnapDiff::Config THE storage for all 27 settings
251
+ errors.rb # Error / ExpectationNotMet / UnstableImage / WindowSizeMismatchError
252
+ region.rb # SnapDiff::Region bounding box (+ eager top-level ::Region alias)
253
+ deprecation.rb # Warn-once-per-constant machinery
254
+ legacy_shims.rb # const_missing forwarders for the old namespaces
255
+ comparison.rb # Layered comparison engine (ex-ImageCompare)
256
+ comparison_result.rb # Comparison result value object (ex-Difference)
257
+ driver.rb # SnapDiff::Driver mixin (ex-BaseDriver superclass)
248
258
  drivers.rb # Driver factory
249
259
  drivers/
250
- base_driver.rb # Abstract base driver
251
260
  vips_driver.rb # VIPS image processing
252
261
  chunky_png_driver.rb # ChunkyPNG image processing
262
+ capture/
263
+ viewport.rb # Per-capture viewport preparation seam
253
264
  screenshoter.rb # Basic browser screenshot capture
254
265
  stable_screenshoter.rb # Stability detection wrapper
255
266
  screenshot_matcher.rb # Orchestrator for capture + compare
256
- image_compare.rb # Layered comparison engine
257
- difference.rb # Difference result value object
267
+ screenshot_assertion.rb # Assertion + registry objects
268
+ screenshot_namer.rb # Name/path generation with sections/groups
269
+ snap_manager.rb # Screenshot file management
270
+ snap.rb # Single screenshot file abstraction
271
+ reporting.rb # Process-global reporter lifecycle
272
+ reporters/
273
+ html.rb # Interactive HTML report reporter
274
+ templates/report.html.erb # HTML report template
275
+ annotation_service.rb # Diff-image annotation (RED_RGBA / ORANGE_RGBA)
258
276
  image_preprocessor.rb # Pre-processing (skip areas, median filter)
259
277
  area_calculator.rb # Crop/skip area coordinate resolution
260
- region.rb # Bounding box region value object
261
278
  browser_helpers.rb # DOM manipulation helpers
279
+ attempts_reporter.rb # Debug reporting for unstable captures
280
+ error_with_filtered_backtrace.rb # Error with filtered stack
262
281
  vcs.rb # Git baseline checkout
282
+ utils.rb # Driver detection
263
283
  os.rb # OS detection
264
- cucumber.rb # Deprecated Cucumber entry point
265
- reporters/
266
- default.rb # Default annotated-image reporter
284
+ static.rb # Non-Rails static site serving
285
+ version.rb # Gem version
286
+ integrations/
287
+ minitest.rb # Minitest assertions integration
288
+ rspec.rb # RSpec matcher integration
289
+ cucumber.rb # Cucumber World integration
290
+ capybara_screenshot_diff.rb # Umbrella entry point + eager error-class aliases
291
+ capybara_screenshot_diff/ # Legacy paths — mostly thin forwarders
292
+ minitest.rb / rspec.rb / cucumber.rb # Legacy entry points (load the full gem)
293
+ screenshot_assertion.rb # CapybaraScreenshotDiff session/reporter shims
294
+ ... # Everything else forwards to snap_diff/
295
+ capybara/screenshot/diff.rb # Convenience require (loads minitest)
296
+ capybara/screenshot/diff/
297
+ config_legacy.rb # Legacy accessor surface, delegating to SnapDiff::Config
298
+ region.rb # Forwarder to snap_diff/region.rb
299
+ version.rb # Capybara::Screenshot::Diff::VERSION (gemspec reads it)
300
+ ... # Everything else forwards to snap_diff/
267
301
  ```
302
+
303
+ Most legacy `Capybara::Screenshot::Diff::*` and `CapybaraScreenshotDiff::*` constants resolve lazily via `snap_diff/legacy_shims.rb` (`const_missing`), pointing at the same objects with a one-time deprecation warning. The error classes are the deliberate exception: they are **eager** same-object aliases, because `rescue` clauses and `defined?` / `const_defined?` feature detection in adopter code must keep behaving exactly as before (`const_defined?` never triggers `const_missing`). Same for `LOADED_DRIVERS`, pinned as an eager alias of `SnapDiff::Drivers.loaded` so user registrations through the old constant are not silently dropped.
304
+
305
+ See [SnapDiff — the canonical API](snapdiff.md) for the canonical surface, and [UPGRADING.md](UPGRADING.md) for the migration guide.