snap_diff-capybara 2.0.0.beta3 → 2.0.0.beta4
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 +438 -0
- data/README.md +330 -0
- data/docs/UPGRADING.md +232 -34
- data/docs/architecture.md +9 -7
- data/docs/ci-integration.md +42 -11
- data/docs/configuration.md +176 -10
- data/docs/drivers.md +48 -5
- data/docs/framework-setup.md +10 -3
- data/docs/migration-guide.md +24 -13
- data/docs/reporters.md +70 -1
- data/docs/snapdiff.md +41 -8
- data/docs/thread_safety.md +77 -86
- data/lib/capybara/screenshot/diff/annotation_service.rb +1 -3
- data/lib/capybara/screenshot/diff/area_calculator.rb +1 -3
- data/lib/capybara/screenshot/diff/browser_helpers.rb +1 -3
- data/lib/capybara/screenshot/diff/config_legacy.rb +15 -60
- data/lib/capybara/screenshot/diff/difference.rb +1 -4
- data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +3 -3
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +2 -3
- data/lib/capybara/screenshot/diff/drivers.rb +4 -5
- data/lib/capybara/screenshot/diff/image_compare.rb +11 -22
- data/lib/capybara/screenshot/diff/image_preprocessor.rb +1 -3
- data/lib/capybara/screenshot/diff/os.rb +4 -11
- data/lib/capybara/screenshot/diff/region.rb +2 -2
- data/lib/capybara/screenshot/diff/reporters/default.rb +4 -17
- data/lib/capybara/screenshot/diff/screenshot_matcher.rb +1 -3
- data/lib/capybara/screenshot/diff/screenshoter.rb +1 -3
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +1 -3
- data/lib/capybara/screenshot/diff/utils.rb +1 -3
- data/lib/capybara/screenshot/diff/vcs.rb +1 -3
- data/lib/capybara/screenshot/diff/version.rb +8 -13
- data/lib/capybara-screenshot-diff.rb +10 -1
- data/lib/capybara_screenshot_diff/attempts_reporter.rb +1 -3
- data/lib/capybara_screenshot_diff/dsl.rb +5 -0
- data/lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb +1 -4
- data/lib/capybara_screenshot_diff/reporters/html.rb +1 -3
- data/lib/capybara_screenshot_diff/screenshot_namer.rb +1 -3
- data/lib/capybara_screenshot_diff/snap.rb +1 -3
- data/lib/capybara_screenshot_diff/snap_manager.rb +1 -3
- data/lib/capybara_screenshot_diff.rb +17 -21
- data/lib/snap_diff/browser_helpers.rb +26 -5
- data/lib/snap_diff/capture/viewport.rb +2 -5
- data/lib/snap_diff/comparison.rb +54 -3
- data/lib/snap_diff/comparison_result.rb +3 -1
- data/lib/snap_diff/config.rb +189 -93
- data/lib/snap_diff/deprecation.rb +89 -28
- data/lib/snap_diff/driver.rb +18 -0
- data/lib/snap_diff/drivers/vips_driver.rb +12 -6
- data/lib/snap_diff/drivers.rb +94 -12
- data/lib/snap_diff/dsl.rb +45 -47
- data/lib/snap_diff/integrations/cucumber.rb +1 -1
- data/lib/snap_diff/integrations/minitest.rb +45 -9
- data/lib/snap_diff/integrations/rspec.rb +11 -0
- data/lib/snap_diff/legacy_shims.rb +285 -27
- data/lib/snap_diff/removal.rb +159 -0
- data/lib/snap_diff/reporters/default.rb +86 -23
- data/lib/snap_diff/reporters/html.rb +29 -13
- data/lib/snap_diff/reporting.rb +329 -3
- data/lib/snap_diff/screenshot_assertion.rb +28 -23
- data/lib/snap_diff/screenshot_matcher.rb +121 -14
- data/lib/snap_diff/screenshot_namer.rb +1 -19
- data/lib/snap_diff/screenshoter.rb +5 -7
- data/lib/snap_diff/snap.rb +6 -1
- data/lib/snap_diff/snap_manager.rb +2 -3
- data/lib/snap_diff/stable_screenshoter.rb +2 -2
- data/lib/snap_diff/static.rb +1 -1
- data/lib/snap_diff/utils.rb +35 -17
- data/lib/snap_diff/vcs.rb +40 -7
- data/lib/snap_diff/version.rb +1 -1
- data/lib/snap_diff-capybara.rb +33 -3
- data/lib/snap_diff.rb +27 -37
- metadata +12 -10
- data/CODE_OF_CONDUCT.md +0 -129
- data/Rakefile +0 -65
- data/capybara-screenshot-diff.gemspec +0 -29
- data/docs/RELEASE_PREP.md +0 -44
- data/docs/docker-testing.md +0 -24
- data/gems.rb +0 -39
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# The v1 umbrella, and the file every legacy entry point except
|
|
4
|
+
# capybara_screenshot_diff/dsl reaches. Marked here rather than in each of
|
|
5
|
+
# them; snap_diff-capybara.rb (which loads this umbrella for canonical
|
|
6
|
+
# users) claims the process first, so this stays quiet for them.
|
|
7
|
+
require "snap_diff/deprecation"
|
|
8
|
+
SnapDiff::Deprecation.legacy_entry_point!
|
|
9
|
+
|
|
3
10
|
require "capybara/dsl"
|
|
4
11
|
require "capybara/screenshot/diff/config_legacy"
|
|
5
12
|
require "capybara/screenshot/diff/version"
|
|
@@ -24,24 +31,15 @@ require "capybara/screenshot/diff/reporters/default"
|
|
|
24
31
|
require "capybara_screenshot_diff/error_with_filtered_backtrace"
|
|
25
32
|
require "snap_diff/errors"
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# triggers const_missing).
|
|
37
|
-
CapybaraScreenshotDiffError = SnapDiff::Error
|
|
38
|
-
|
|
39
|
-
ExpectationNotMet = SnapDiff::ExpectationNotMet
|
|
40
|
-
|
|
41
|
-
UnstableImage = SnapDiff::UnstableImage
|
|
42
|
-
|
|
43
|
-
WindowSizeMismatchError = SnapDiff::WindowSizeMismatchError
|
|
44
|
-
end
|
|
34
|
+
# RED_RGBA / ORANGE_RGBA moved to SnapDiff (snap_diff/annotation_service) so
|
|
35
|
+
# the bare "snap_diff" entry gets them too; the old names resolve via
|
|
36
|
+
# snap_diff/legacy_shims with a deprecation warning.
|
|
37
|
+
#
|
|
38
|
+
# The four error classes (CapybaraScreenshotDiffError, ExpectationNotMet,
|
|
39
|
+
# UnstableImage, WindowSizeMismatchError) used to be assigned here as EAGER
|
|
40
|
+
# same-object aliases. They still are eager -- just from
|
|
41
|
+
# snap_diff/legacy_shims, so a canonical-only require gets them too.
|
|
42
|
+
require "snap_diff/legacy_shims"
|
|
45
43
|
|
|
46
44
|
require "capybara_screenshot_diff/dsl"
|
|
47
45
|
|
|
@@ -50,7 +48,5 @@ require "capybara_screenshot_diff/dsl"
|
|
|
50
48
|
# any registered `autoload :SnapDiff` before it ever fires (Ruby resolves
|
|
51
49
|
# the constant the first time anything reopens it, autoload or not) --
|
|
52
50
|
# so SnapDiff.start/.compare/.config would silently never be defined
|
|
53
|
-
# without this. Safe
|
|
54
|
-
# moved: snap_diff.rb no longer requires this file back (it only needs
|
|
55
|
-
# the leaf config_legacy.rb + image_compare.rb), so there is no cycle.
|
|
51
|
+
# without this. Safe eagerly: snap_diff.rb never requires this file back.
|
|
56
52
|
require "snap_diff"
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "snap_diff/region"
|
|
4
|
+
require "snap_diff/reporting"
|
|
4
5
|
|
|
5
6
|
module SnapDiff
|
|
6
7
|
module BrowserHelpers
|
|
7
8
|
def self.resize_window_if_needed
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
# The respond_to? guard this replaced existed because the legacy
|
|
10
|
+
# mattr_accessor might not be installed yet; Config always has the
|
|
11
|
+
# attribute, so only the value matters now.
|
|
12
|
+
window_size = SnapDiff.config.window_size
|
|
13
|
+
resize_to(window_size) if window_size
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def self.resize_to(window_size)
|
|
@@ -27,9 +30,16 @@ module SnapDiff
|
|
|
27
30
|
session.driver.browser.manage.window.size != ::Selenium::WebDriver::Dimension.new(*expected_window_size)
|
|
28
31
|
end
|
|
29
32
|
|
|
33
|
+
# The one seam that knows, per selector, whether it found anything --
|
|
34
|
+
# `all_visible_regions_for` is called once per selector and the caller
|
|
35
|
+
# gets back one flattened list. The run-level tally is fed here (#277b)
|
|
36
|
+
# rather than in AreaCalculator for exactly that reason: by the time
|
|
37
|
+
# the regions are concatenated the attribution is gone.
|
|
30
38
|
def self.bounds_for_css(*css_selectors)
|
|
31
39
|
css_selectors.reduce([]) do |regions, selector|
|
|
32
|
-
|
|
40
|
+
found = all_visible_regions_for(selector)
|
|
41
|
+
SnapDiff::Reporting.record_selector_use(selector, matched: !found.empty?)
|
|
42
|
+
regions.concat(found)
|
|
33
43
|
end
|
|
34
44
|
end
|
|
35
45
|
|
|
@@ -96,8 +106,19 @@ module SnapDiff
|
|
|
96
106
|
]
|
|
97
107
|
JS
|
|
98
108
|
|
|
109
|
+
# `minimum: 0` is load-bearing (issue #272). Capybara's `all` defaults to
|
|
110
|
+
# `minimum: 1` and blocks in `synchronize` until the count is satisfied,
|
|
111
|
+
# so every `skip_area`/`crop` selector matching nothing burned a full
|
|
112
|
+
# `Capybara.default_max_wait_time` -- 5s by default, per selector, per
|
|
113
|
+
# screenshot. One project measured `%w[picture img]` on an image-less
|
|
114
|
+
# page at 10s per screenshot, 44% of their suite.
|
|
115
|
+
#
|
|
116
|
+
# Waiting is the wrong semantic here regardless of the cost: these
|
|
117
|
+
# selectors describe a MASK over whatever is currently on the page. A
|
|
118
|
+
# selector that matches nothing has nothing to mask, and that answer is
|
|
119
|
+
# available immediately.
|
|
99
120
|
def self.all_visible_regions_for(selector)
|
|
100
|
-
BrowserHelpers.session.all(selector, visible: true).map { |el| region_for(el) }
|
|
121
|
+
BrowserHelpers.session.all(selector, visible: true, minimum: 0).map { |el| region_for(el) }
|
|
101
122
|
end
|
|
102
123
|
|
|
103
124
|
def self.region_for(element)
|
|
@@ -7,11 +7,8 @@ module SnapDiff
|
|
|
7
7
|
# Per-capture viewport preparation seam.
|
|
8
8
|
#
|
|
9
9
|
# Called exactly once per capture, before the screenshoter runs and outside
|
|
10
|
-
# any stability retry loop.
|
|
11
|
-
# (raise-only, never resizes)
|
|
12
|
-
# inline before. When v3 adds scroll preservation / element-anchored
|
|
13
|
-
# capture it designs its own parameters here; adding an optional kwarg
|
|
14
|
-
# later is non-breaking, so none is reserved up front.
|
|
10
|
+
# any stability retry loop. It only validates the window size
|
|
11
|
+
# (raise-only, never resizes).
|
|
15
12
|
module Viewport
|
|
16
13
|
module_function
|
|
17
14
|
|
data/lib/snap_diff/comparison.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "fileutils"
|
|
|
6
6
|
require "snap_diff/comparison_result"
|
|
7
7
|
require "snap_diff/drivers"
|
|
8
8
|
require "snap_diff/image_preprocessor"
|
|
9
|
+
require "snap_diff/removal"
|
|
9
10
|
require "snap_diff/reporters/default"
|
|
10
11
|
|
|
11
12
|
module SnapDiff
|
|
@@ -42,6 +43,31 @@ module SnapDiff
|
|
|
42
43
|
|
|
43
44
|
TOLERABLE_OPTIONS = [:tolerance, :color_distance_limit, :shift_distance_limit, :area_size_limit].freeze
|
|
44
45
|
|
|
46
|
+
# Every key anything downstream of here actually reads -- capture
|
|
47
|
+
# options included, because ScreenshotMatcher hands the same hash to the
|
|
48
|
+
# screenshoter and only carves :crop / :stability_time_limit / :wait out
|
|
49
|
+
# of the copy it passes on.
|
|
50
|
+
#
|
|
51
|
+
# Written out rather than derived from Config#default_options: that hash
|
|
52
|
+
# is what the gem merges in, so deriving from it would make the check
|
|
53
|
+
# agree with itself and validate nothing about the keys a USER adds.
|
|
54
|
+
KNOWN_OPTIONS = %i[
|
|
55
|
+
area_size_limit
|
|
56
|
+
capybara_screenshot_options
|
|
57
|
+
color_distance_limit
|
|
58
|
+
crop
|
|
59
|
+
delayed
|
|
60
|
+
driver
|
|
61
|
+
median_filter_window_size
|
|
62
|
+
perceptual_threshold
|
|
63
|
+
screenshot_format
|
|
64
|
+
shift_distance_limit
|
|
65
|
+
skip_area
|
|
66
|
+
stability_time_limit
|
|
67
|
+
tolerance
|
|
68
|
+
wait
|
|
69
|
+
].freeze
|
|
70
|
+
|
|
45
71
|
attr_reader :driver, :driver_options
|
|
46
72
|
attr_reader :image_path, :base_image_path
|
|
47
73
|
attr_reader :difference, :error_message
|
|
@@ -53,6 +79,20 @@ module SnapDiff
|
|
|
53
79
|
ensure_files_exist!
|
|
54
80
|
|
|
55
81
|
@driver_options = options.freeze
|
|
82
|
+
# THE no-silent-no-op check (ADR-010). Every option hash in the gem
|
|
83
|
+
# reaches this constructor, so a key nothing reads is caught here
|
|
84
|
+
# whichever entry point produced it. Frozen-but-unvalidated is how a
|
|
85
|
+
# misspelt `tolerence:` bought a green suite that compared nothing.
|
|
86
|
+
(options.keys - KNOWN_OPTIONS).each do |key|
|
|
87
|
+
Removal.warn_once(:"unknown_option_#{key}", Removal.unknown_option(key))
|
|
88
|
+
end
|
|
89
|
+
# The per-comparison half of the shift_distance_limit removal (the
|
|
90
|
+
# global half is Config#shift_distance_limit=). Presence is not enough:
|
|
91
|
+
# config.default_options carries the key on EVERY comparison, nil for
|
|
92
|
+
# everyone who never set it.
|
|
93
|
+
if options[:shift_distance_limit]
|
|
94
|
+
Removal.warn_once(:shift_distance_limit, Removal::SHIFT_DISTANCE_LIMIT_REMOVED)
|
|
95
|
+
end
|
|
56
96
|
@driver = Drivers.for(@driver_options)
|
|
57
97
|
@without_tolerable_options = (driver_options.keys & TOLERABLE_OPTIONS).empty?
|
|
58
98
|
end
|
|
@@ -68,9 +108,7 @@ module SnapDiff
|
|
|
68
108
|
#
|
|
69
109
|
# @note This method will raise ArgumentError if either image file is missing.
|
|
70
110
|
def quick_equal?
|
|
71
|
-
|
|
72
|
-
return true if files_identical?(base_image_path, image_path)
|
|
73
|
-
end
|
|
111
|
+
return true if identical_files?
|
|
74
112
|
|
|
75
113
|
result, difference = find_difference(quick_mode: true)
|
|
76
114
|
self.difference = difference
|
|
@@ -134,6 +172,13 @@ module SnapDiff
|
|
|
134
172
|
# Validate images exist
|
|
135
173
|
return build_null_difference("missing_image") unless images_exist?
|
|
136
174
|
|
|
175
|
+
# Step 1 of the layered strategy documented on this class: byte-identical
|
|
176
|
+
# files cannot have different pixels. quick_equal? has always checked
|
|
177
|
+
# this; the full path did not, and paid a decode of BOTH PNGs (11ms at
|
|
178
|
+
# 1440x900, 25ms at 2880x1800) to reach the same "no difference" answer
|
|
179
|
+
# on every screenshot that matches its baseline byte for byte.
|
|
180
|
+
return build_null_difference if !quick_mode && identical_files?
|
|
181
|
+
|
|
137
182
|
# Create comparison with preprocessed images
|
|
138
183
|
comparison = load_comparison(base_image_path, image_path, driver_options)
|
|
139
184
|
|
|
@@ -215,6 +260,12 @@ module SnapDiff
|
|
|
215
260
|
base_image_path.exist? && image_path.exist?
|
|
216
261
|
end
|
|
217
262
|
|
|
263
|
+
# Are the two screenshots the same bytes? Sizes first: a stat each rules
|
|
264
|
+
# out most pairs without reading either file.
|
|
265
|
+
def identical_files?
|
|
266
|
+
base_image_path.size == image_path.size && files_identical?(base_image_path, image_path)
|
|
267
|
+
end
|
|
268
|
+
|
|
218
269
|
# Check if files are identical by content
|
|
219
270
|
def files_identical?(file1, file2)
|
|
220
271
|
FileUtils.compare_file(file1, file2)
|
|
@@ -67,8 +67,10 @@ module SnapDiff
|
|
|
67
67
|
meta[:difference_level]
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
# Serializable difference metrics. The raw diff mask image is excluded —
|
|
71
|
+
# it is an image object, not a metric, and is reachable via #diff_mask.
|
|
70
72
|
def to_h
|
|
71
|
-
{area_size: region_area_size, region: coordinates}.merge!(meta)
|
|
73
|
+
{area_size: region_area_size, region: coordinates}.merge!(meta.except(:diff_mask))
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
def coordinates
|
data/lib/snap_diff/config.rb
CHANGED
|
@@ -4,21 +4,15 @@ require "pathname"
|
|
|
4
4
|
|
|
5
5
|
# This file is the LEAF of the config require graph (ADR-008 step 1):
|
|
6
6
|
# config_legacy.rb requires it, so it must never require config_legacy nor
|
|
7
|
-
# anything that leads back to either entry point.
|
|
8
|
-
# the
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# accessors from MAPPING.
|
|
12
|
-
module Capybara
|
|
13
|
-
module Screenshot
|
|
14
|
-
module Diff
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
7
|
+
# anything that leads back to either entry point. It also names nothing from
|
|
8
|
+
# the v1 trees at all (3.0 readiness): which legacy accessor each setting is
|
|
9
|
+
# exposed as is snap_diff/legacy_shims' business, and that file is deleted
|
|
10
|
+
# together with lib/capybara* -- see LegacyShims::CONFIG_MAPPING.
|
|
18
11
|
|
|
19
12
|
# Referenced by Config#initialize (screenshoter/manager defaults), which
|
|
20
13
|
# runs at the eager Config.new at the bottom of this file, so they must be
|
|
21
14
|
# real, already-loaded classes first. Neither requires back here.
|
|
15
|
+
require "snap_diff/removal"
|
|
22
16
|
require "snap_diff/screenshoter"
|
|
23
17
|
require "snap_diff/snap_manager"
|
|
24
18
|
|
|
@@ -30,71 +24,101 @@ module SnapDiff
|
|
|
30
24
|
# Storage ownership (ADR-008 step 1, inverted from the original v2
|
|
31
25
|
# consolidation): Config IS the single storage. The legacy accessors on
|
|
32
26
|
# +Capybara::Screenshot+ / +Capybara::Screenshot::Diff+ are thin
|
|
33
|
-
# delegators
|
|
34
|
-
# two views, so a write through either surface is visible
|
|
35
|
-
# other structurally, not by synchronization.
|
|
27
|
+
# delegators generated by snap_diff/legacy_shims from its CONFIG_MAPPING
|
|
28
|
+
# -- one storage, two views, so a write through either surface is visible
|
|
29
|
+
# through the other structurally, not by synchronization.
|
|
36
30
|
#
|
|
37
31
|
# Default timing contract (pinned by config_default_timing_test.rb):
|
|
38
|
-
# every default below is evaluated ONCE, in #initialize, which runs
|
|
39
|
-
# require time of this file (the eager +Config.new+ at the bottom) --
|
|
40
|
-
# same load moment the old +mattr_accessor+ default blocks evaluated
|
|
41
|
-
# In particular +
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# +
|
|
32
|
+
# every stored default below is evaluated ONCE, in #initialize, which runs
|
|
33
|
+
# at require time of this file (the eager +Config.new+ at the bottom) --
|
|
34
|
+
# the same load moment the old +mattr_accessor+ default blocks evaluated
|
|
35
|
+
# at. In particular +root+ (from +Rails.root+ / pwd) must never become a
|
|
36
|
+
# lazy read-time default, memoized or not.
|
|
37
|
+
#
|
|
38
|
+
# Two values are deliberately LIVE and are not storage at all:
|
|
39
|
+
# +default_options[:wait]+ stays a method-body read of
|
|
40
|
+
# +Capybara.default_max_wait_time+ in +#default_options+, and
|
|
41
|
+
# +fail_if_new+ falls back to <tt>ENV["CI"]</tt> in its reader whenever
|
|
42
|
+
# nothing explicit was set -- see {#fail_if_new} for why freezing that
|
|
43
|
+
# sniff into storage let the environment outrank the user.
|
|
46
44
|
class Config
|
|
47
|
-
#
|
|
45
|
+
# Every setting this object stores, in the order the two legacy holders
|
|
46
|
+
# used to declare them.
|
|
48
47
|
#
|
|
49
|
-
#
|
|
50
|
-
# +Capybara::Screenshot.enabled+ and
|
|
51
|
-
# are independent settings (see
|
|
52
|
-
# reads both) that
|
|
53
|
-
# flat Config can't expose two attributes both
|
|
54
|
-
# Screenshot-side one is renamed here; Diff's
|
|
55
|
-
# name since it's the one most existing
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
add_driver_path
|
|
59
|
-
add_os_path
|
|
60
|
-
blur_active_element
|
|
61
|
-
screenshot_enabled
|
|
62
|
-
hide_caret
|
|
63
|
-
disable_animations
|
|
64
|
-
root
|
|
65
|
-
stability_time_limit
|
|
66
|
-
window_size
|
|
67
|
-
save_path
|
|
68
|
-
use_lfs
|
|
69
|
-
screenshot_format
|
|
70
|
-
capybara_screenshot_options
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
fail_if_new
|
|
75
|
-
pending_if_new
|
|
76
|
-
fail_on_difference
|
|
77
|
-
color_distance_limit
|
|
78
|
-
enabled
|
|
79
|
-
shift_distance_limit
|
|
80
|
-
skip_area
|
|
81
|
-
driver
|
|
82
|
-
tolerance
|
|
83
|
-
perceptual_threshold
|
|
84
|
-
screenshoter
|
|
85
|
-
manager
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
48
|
+
# +screenshot_enabled+ is the one name that differs from its legacy
|
|
49
|
+
# spelling: +Capybara::Screenshot.enabled+ and
|
|
50
|
+
# +Capybara::Screenshot::Diff.enabled+ are independent settings (see
|
|
51
|
+
# {#active?}, which reads both) that happened to share a bare name in
|
|
52
|
+
# their own modules. A flat Config can't expose two attributes both
|
|
53
|
+
# called +enabled+, so the Screenshot-side one is renamed here; Diff's
|
|
54
|
+
# keeps the bare +enabled+ name since it's the one most existing
|
|
55
|
+
# configuration touches directly.
|
|
56
|
+
SETTINGS = %i[
|
|
57
|
+
add_driver_path
|
|
58
|
+
add_os_path
|
|
59
|
+
blur_active_element
|
|
60
|
+
screenshot_enabled
|
|
61
|
+
hide_caret
|
|
62
|
+
disable_animations
|
|
63
|
+
root
|
|
64
|
+
stability_time_limit
|
|
65
|
+
window_size
|
|
66
|
+
save_path
|
|
67
|
+
use_lfs
|
|
68
|
+
screenshot_format
|
|
69
|
+
capybara_screenshot_options
|
|
70
|
+
delayed
|
|
71
|
+
area_size_limit
|
|
72
|
+
record
|
|
73
|
+
fail_if_new
|
|
74
|
+
pending_if_new
|
|
75
|
+
fail_on_difference
|
|
76
|
+
color_distance_limit
|
|
77
|
+
enabled
|
|
78
|
+
shift_distance_limit
|
|
79
|
+
skip_area
|
|
80
|
+
driver
|
|
81
|
+
tolerance
|
|
82
|
+
perceptual_threshold
|
|
83
|
+
screenshoter
|
|
84
|
+
manager
|
|
85
|
+
].freeze
|
|
86
|
+
|
|
87
|
+
# The record modes, in the order they escalate: do nothing new, refuse
|
|
88
|
+
# to record, record everything. See {#record}.
|
|
89
|
+
RECORD_MODES = %i[once none all].freeze
|
|
90
|
+
|
|
91
|
+
# @api private
|
|
92
|
+
#
|
|
93
|
+
# Shared by {#record=} and the per-screenshot `record:` option, which is
|
|
94
|
+
# resolved in {SnapDiff::ScreenshotMatcher}. A misspelt mode must not
|
|
95
|
+
# read back as "nobody said" and silently mean today's behaviour --
|
|
96
|
+
# that is the exact silent no-op ADR-010 exists to stop.
|
|
97
|
+
def self.validate_record_mode!(mode)
|
|
98
|
+
return mode if mode.nil? || RECORD_MODES.include?(mode)
|
|
99
|
+
|
|
100
|
+
raise ArgumentError,
|
|
101
|
+
"unknown record mode #{mode.inspect} -- one of #{RECORD_MODES.map(&:inspect).join(", ")} (or nil)"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# shift_distance_limit, driver and the three older new-screenshot
|
|
105
|
+
# booleans are excluded from the generated writers and hand written
|
|
106
|
+
# below (they announce their 2.1 removal, or validate); fail_if_new and
|
|
107
|
+
# record are excluded from the generated READER (both fall back rather
|
|
108
|
+
# than returning storage). Generating them here too would print Ruby's
|
|
109
|
+
# "method redefined" warning on every load.
|
|
110
|
+
attr_accessor(*(SETTINGS - %i[
|
|
111
|
+
root shift_distance_limit driver record fail_if_new pending_if_new fail_on_difference
|
|
112
|
+
]))
|
|
113
|
+
attr_reader :root, :shift_distance_limit, :driver, :pending_if_new, :fail_on_difference
|
|
90
114
|
|
|
91
115
|
def initialize
|
|
92
|
-
# Every
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
|
|
116
|
+
# Every setting gets its ivar up front (nil-defaulted ones included)
|
|
117
|
+
# so the full set always exists -- test_helper's per-test isolation
|
|
118
|
+
# snapshots/restores config by instance variable, and an ivar that
|
|
119
|
+
# only appears on first write would escape that snapshot and leak
|
|
120
|
+
# between tests.
|
|
121
|
+
SETTINGS.each { |key| instance_variable_set(:"@#{key}", nil) }
|
|
98
122
|
# Capybara::Screenshot side.
|
|
99
123
|
@blur_active_element = true
|
|
100
124
|
@hide_caret = true
|
|
@@ -106,7 +130,7 @@ module SnapDiff
|
|
|
106
130
|
@capybara_screenshot_options = {}
|
|
107
131
|
# Capybara::Screenshot::Diff side.
|
|
108
132
|
@delayed = true
|
|
109
|
-
|
|
133
|
+
# No stored default for fail_if_new on purpose -- see the reader.
|
|
110
134
|
@pending_if_new = false
|
|
111
135
|
@fail_on_difference = true
|
|
112
136
|
@enabled = true
|
|
@@ -119,12 +143,105 @@ module SnapDiff
|
|
|
119
143
|
@root = Pathname(path).expand_path
|
|
120
144
|
end
|
|
121
145
|
|
|
146
|
+
# An explicit setting outranks the environment. nil means nobody said,
|
|
147
|
+
# and only then does the CI sniff answer -- read live, so a CI variable
|
|
148
|
+
# that appears at any point is honoured, not just one that happened to
|
|
149
|
+
# be exported before the gem was required. Assigning nil hands the
|
|
150
|
+
# setting back to the environment.
|
|
151
|
+
#
|
|
152
|
+
# This is a precedence rule, not a change of default: failing only under
|
|
153
|
+
# CI stays (a locally recorded baseline is often worthless across OS).
|
|
154
|
+
# Storing the sniff instead, as this did, made the two indistinguishable
|
|
155
|
+
# -- `fail_if_new = false` and "CI was absent at require time" were the
|
|
156
|
+
# same false, so the environment could win. Same fix as insta#924
|
|
157
|
+
# ("normally, CLI flags take precedence over environment variables") and
|
|
158
|
+
# the inverse of jest#12288.
|
|
159
|
+
def fail_if_new
|
|
160
|
+
@fail_if_new.nil? ? !ENV["CI"].to_s.empty? : @fail_if_new
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Announces the 2.1 removal. The writer, not the reader: {#record} reads
|
|
164
|
+
# +fail_if_new+ on every screenshot for everyone, and #initialize stores
|
|
165
|
+
# no default at all, so only a user who sets it hears about it.
|
|
166
|
+
def fail_if_new=(value)
|
|
167
|
+
Removal.warn_once(:fail_if_new, Removal::FAIL_IF_NEW_REMOVED)
|
|
168
|
+
@fail_if_new = value
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Same shape, same reason: SnapDiff.pending_screenshots_message reads
|
|
172
|
+
# this for everyone, and #initialize seeds the +false+ default straight
|
|
173
|
+
# into the ivar.
|
|
174
|
+
def pending_if_new=(value)
|
|
175
|
+
Removal.warn_once(:pending_if_new, Removal::PENDING_IF_NEW_REMOVED)
|
|
176
|
+
@pending_if_new = value
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Same: ScreenshotAssertion.verify_screenshots! reads it once per test
|
|
180
|
+
# for everyone, and #initialize seeds the +true+ default.
|
|
181
|
+
def fail_on_difference=(value)
|
|
182
|
+
Removal.warn_once(:fail_on_difference, Removal::FAIL_ON_DIFFERENCE_REMOVED)
|
|
183
|
+
@fail_on_difference = value
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# THE ACCEPT WORKFLOW (#259). What to do about a screenshot whose
|
|
187
|
+
# baseline is missing -- or, for +:all+, about every screenshot there
|
|
188
|
+
# is. VCR-shaped: modes in config, because there is no runner to hang a
|
|
189
|
+
# CLI flag on.
|
|
190
|
+
#
|
|
191
|
+
# +:once+:: (default) record a screenshot that has no committed
|
|
192
|
+
# baseline; compare against the baseline when there is one.
|
|
193
|
+
# +:none+:: strict. A missing baseline always fails.
|
|
194
|
+
# +:all+:: re-record. Every screenshot is written as the new baseline
|
|
195
|
+
# and nothing is compared -- the bulk-accept verb, for the
|
|
196
|
+
# redesign that changed forty screenshots at once. Refused
|
|
197
|
+
# under CI (see {SnapDiff::ScreenshotMatcher}).
|
|
198
|
+
#
|
|
199
|
+
# PRECEDENCE: an explicitly set mode outranks +fail_if_new+; nil means
|
|
200
|
+
# nobody said, and only then does +fail_if_new+ answer -- so a setup
|
|
201
|
+
# with no +record+ line behaves EXACTLY as it did before this setting
|
|
202
|
+
# existed, CI sniff and all. +:none+ is the mode spelling of
|
|
203
|
+
# <tt>fail_if_new = true</tt> and +:once+ of <tt>= false</tt>, which is
|
|
204
|
+
# why the fallback can express today's behaviour without a special case
|
|
205
|
+
# anywhere downstream: the matcher branches on the mode alone.
|
|
206
|
+
#
|
|
207
|
+
# The missing-baseline DEFAULT is deliberately unchanged -- failing only
|
|
208
|
+
# under CI is what Jest, AVA, Vitest, testthat and jest-image-snapshot
|
|
209
|
+
# all chose, and a locally recorded screenshot baseline is often
|
|
210
|
+
# worthless across OS. +:none+ makes strictness an explicit choice
|
|
211
|
+
# instead; that is the whole point of having the mode.
|
|
212
|
+
def record
|
|
213
|
+
@record || (fail_if_new ? :none : :once)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def record=(mode)
|
|
217
|
+
@record = Config.validate_record_mode!(mode)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Overrides the generated accessor above to announce the 2.1 removal
|
|
221
|
+
# (chunky_png-only, and chunky_png goes too). The writer, not the reader:
|
|
222
|
+
# the reader runs on every comparison through #default_options, including
|
|
223
|
+
# for the overwhelming majority who never set this. #initialize seeds the
|
|
224
|
+
# ivar directly, so booting the gem stays silent.
|
|
225
|
+
def shift_distance_limit=(value)
|
|
226
|
+
Removal.warn_once(:shift_distance_limit, Removal::SHIFT_DISTANCE_LIMIT_REMOVED) unless value.nil?
|
|
227
|
+
@shift_distance_limit = value
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Same shape, same reason: the writer only. #default_options reads
|
|
231
|
+
# +driver+ on every comparison for everyone, and #initialize seeds the
|
|
232
|
+
# +:auto+ default straight into the ivar, so only a user deliberately
|
|
233
|
+
# picking a backend hears about it.
|
|
234
|
+
def driver=(value)
|
|
235
|
+
Removal.warn_once(:driver_setting, Removal::DRIVER_REMOVED)
|
|
236
|
+
@driver = value
|
|
237
|
+
end
|
|
238
|
+
|
|
122
239
|
# --- Derived config (ADR-008 step 7b) -------------------------------
|
|
123
240
|
# Read-only values computed from the storage above. They used to live
|
|
124
241
|
# on the legacy modules; those now one-line forward here.
|
|
125
242
|
|
|
126
243
|
# ex +Capybara::Screenshot.active?+. The two +enabled+ settings are
|
|
127
|
-
# independent (see {
|
|
244
|
+
# independent (see {SETTINGS}): the Screenshot-side one wins whenever it
|
|
128
245
|
# was set at all, and only a nil there falls through to the Diff-side
|
|
129
246
|
# one.
|
|
130
247
|
def active?
|
|
@@ -176,25 +293,4 @@ module SnapDiff
|
|
|
176
293
|
def self.config
|
|
177
294
|
@config
|
|
178
295
|
end
|
|
179
|
-
|
|
180
|
-
# Installs the old mattr_accessor surface onto the legacy modules,
|
|
181
|
-
# delegating to the single storage above. mattr_accessor used to define
|
|
182
|
-
# both singleton and instance accessors (the instance ones are what
|
|
183
|
-
# `include Capybara::Screenshot::Diff` picks up), so both are installed.
|
|
184
|
-
# root keeps its historical asymmetry -- readable everywhere, writable
|
|
185
|
-
# only at module level (it was mattr_reader plus a custom module-level
|
|
186
|
-
# writer) -- with the Pathname coercion living in Config#root=.
|
|
187
|
-
#
|
|
188
|
-
# Generated here rather than in config_legacy.rb (ADR-008 step 7b) for
|
|
189
|
-
# the same reason legacy_shims.rb generates the legacy constants here:
|
|
190
|
-
# the generator is code, and the v1 trees must stay alias-only so 3.0 is
|
|
191
|
-
# a `git rm`. Same technique, same side of the fence.
|
|
192
|
-
Config::MAPPING.each do |name, (mod, mattr)|
|
|
193
|
-
[mod, mod.singleton_class].each do |target|
|
|
194
|
-
target.define_method(mattr) { SnapDiff.config.public_send(name) }
|
|
195
|
-
next if name == :root && target == mod
|
|
196
|
-
|
|
197
|
-
target.define_method(:"#{mattr}=") { |value| SnapDiff.config.public_send(:"#{name}=", value) }
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
296
|
end
|