snap_diff-capybara 2.0.0.beta1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/docs/UPGRADING.md +19 -4
- data/docs/architecture.md +38 -22
- data/docs/ci-integration.md +13 -3
- data/docs/configuration.md +31 -2
- data/docs/drivers.md +6 -0
- data/docs/framework-setup.md +21 -3
- data/docs/reporters.md +20 -5
- data/docs/snapdiff.md +326 -0
- data/docs/thread_safety.md +4 -3
- data/lib/capybara/screenshot/diff/config_legacy.rb +26 -72
- data/lib/capybara/screenshot/diff/image_compare.rb +5 -2
- data/lib/capybara/screenshot/diff/region.rb +3 -105
- data/lib/capybara/screenshot/diff/reporters/default.rb +4 -106
- data/lib/capybara_screenshot_diff/screenshot_assertion.rb +12 -24
- data/lib/capybara_screenshot_diff.rb +10 -4
- data/lib/snap_diff/area_calculator.rb +1 -3
- data/lib/snap_diff/browser_helpers.rb +1 -3
- data/lib/snap_diff/capture/viewport.rb +6 -7
- data/lib/snap_diff/comparison.rb +15 -28
- data/lib/snap_diff/config.rb +151 -29
- data/lib/snap_diff/deprecation.rb +26 -8
- data/lib/snap_diff/drivers.rb +20 -0
- data/lib/snap_diff/dsl.rb +12 -12
- data/lib/snap_diff/errors.rb +19 -0
- data/lib/snap_diff/integrations/cucumber.rb +5 -4
- data/lib/snap_diff/integrations/minitest.rb +11 -12
- data/lib/snap_diff/integrations/rspec.rb +7 -6
- data/lib/snap_diff/legacy_shims.rb +18 -0
- data/lib/snap_diff/region.rb +117 -0
- data/lib/snap_diff/reporters/default.rb +107 -0
- data/lib/snap_diff/reporters/html.rb +7 -9
- data/lib/snap_diff/reporting.rb +13 -4
- data/lib/snap_diff/screenshot_assertion.rb +37 -4
- data/lib/snap_diff/screenshot_matcher.rb +4 -4
- data/lib/snap_diff/screenshoter.rb +1 -1
- data/lib/snap_diff/snap_manager.rb +1 -2
- data/lib/snap_diff/stable_screenshoter.rb +2 -2
- data/lib/snap_diff/utils.rb +5 -3
- data/lib/snap_diff/version.rb +1 -1
- data/lib/snap_diff.rb +44 -15
- metadata +5 -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)
|
data/docs/thread_safety.md
CHANGED
|
@@ -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
|
|
104
|
-
|
|
105
|
-
umbrella files depend on the units, and nothing requires back up
|
|
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
|
|
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
|
-
#
|
|
10
|
-
# (
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
require "
|
|
20
|
-
|
|
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
|
-
|
|
28
|
+
SnapDiff.config.active?
|
|
46
29
|
end
|
|
47
30
|
|
|
48
31
|
def screenshot_area
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
@@ -6,7 +6,10 @@
|
|
|
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
|
|
10
|
-
#
|
|
9
|
+
# image_compare.rb did. The internal images-holder struct lives at
|
|
10
|
+
# SnapDiff::Comparison::Images (its old ::Comparison name resolves via the
|
|
11
|
+
# shims) and the driver cache at SnapDiff::Drivers.loaded, with
|
|
12
|
+
# LOADED_DRIVERS kept as an eager same-object alias by legacy_shims
|
|
13
|
+
# (ADR-008 step 5).
|
|
11
14
|
require "snap_diff/comparison"
|
|
12
15
|
require "snap_diff/legacy_shims"
|
|
@@ -1,107 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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"
|