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
|
@@ -1,109 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
#
|
|
3
|
+
# Forwarder (ADR-008 step 4): the default reporter lives at
|
|
4
|
+
# SnapDiff::Reporters::Default; the old name now resolves lazily via
|
|
5
|
+
# snap_diff/legacy_shims' const_missing, with a deprecation warning.
|
|
6
|
+
require "snap_diff/reporters/default"
|
|
5
7
|
require "snap_diff/legacy_shims"
|
|
6
|
-
|
|
7
|
-
module Capybara::Screenshot::Diff
|
|
8
|
-
module Reporters
|
|
9
|
-
class Default
|
|
10
|
-
attr_reader :difference
|
|
11
|
-
|
|
12
|
-
def initialize(difference)
|
|
13
|
-
@difference = difference
|
|
14
|
-
@annotation_service = SnapDiff::AnnotationService.new(difference)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def annotated_image_path
|
|
18
|
-
annotation_service.annotated_image_path
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def annotated_base_image_path
|
|
22
|
-
annotation_service.annotated_base_image_path
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def heatmap_diff_path
|
|
26
|
-
annotation_service.heatmap_diff_path
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def generate
|
|
30
|
-
if difference.equal?
|
|
31
|
-
# NOTE: Delete previous run runtime files
|
|
32
|
-
clean_tmp_files
|
|
33
|
-
return nil
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
if difference.failed? && difference.failed_by[:different_dimensions]
|
|
37
|
-
return build_error_for_different_dimensions
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
annotate_and_save_images
|
|
41
|
-
build_error_message
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def clean_tmp_files
|
|
45
|
-
annotation_service.clean_tmp_files
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def annotate_and_save_images
|
|
49
|
-
annotation_service.annotate_and_save_images
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def save_annotation_for(image, image_path)
|
|
53
|
-
annotation_service.save_annotation_for(image, image_path)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def annotate_difference(image, region)
|
|
57
|
-
annotation_service.annotate_difference(image, region)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def annotate_skip_areas(image, skip_areas)
|
|
61
|
-
annotation_service.annotate_skip_areas(image, skip_areas)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def save(image, image_path)
|
|
65
|
-
annotation_service.save(image, image_path)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def build_error_for_different_dimensions
|
|
69
|
-
change_msg = [comparison.base_image, comparison.new_image]
|
|
70
|
-
.map { |image| driver.dimension(image).join("x") }
|
|
71
|
-
.join(" => ")
|
|
72
|
-
|
|
73
|
-
"Dimensions have changed: #{change_msg}\n#{base_image_path.to_path}\n#{image_path.to_path}"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
NEW_LINE = "\n"
|
|
77
|
-
|
|
78
|
-
def build_error_message
|
|
79
|
-
[
|
|
80
|
-
"(#{difference.to_h.to_json})",
|
|
81
|
-
image_path.to_path,
|
|
82
|
-
annotated_base_image_path.to_path,
|
|
83
|
-
annotated_image_path.to_path,
|
|
84
|
-
heatmap_diff_path.to_path
|
|
85
|
-
].join(NEW_LINE)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
private
|
|
89
|
-
|
|
90
|
-
attr_reader :annotation_service
|
|
91
|
-
|
|
92
|
-
def base_image_path
|
|
93
|
-
comparison.base_image_path
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def image_path
|
|
97
|
-
comparison.new_image_path
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def driver
|
|
101
|
-
@_driver ||= comparison.driver
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def comparison
|
|
105
|
-
@_comparison ||= difference.comparison
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
@@ -6,49 +6,37 @@ require "snap_diff/reporting"
|
|
|
6
6
|
# warnings) via snap_diff/legacy_shims' const_missing since v2 step 6.
|
|
7
7
|
require "snap_diff/legacy_shims"
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# -
|
|
13
|
-
#
|
|
14
|
-
# `reset` clears it between tests.
|
|
15
|
-
# - Reporter lifecycle: process-global, suite-long. Owned by
|
|
16
|
-
# SnapDiff::Reporting; the methods here are thin public shims over it.
|
|
17
|
-
#
|
|
18
|
-
# `reset` is the one deliberate bridge between the two: a finished test's
|
|
19
|
-
# assertions are handed to the reporters before the registry is cleared.
|
|
9
|
+
# Since ADR-008 step 6 every method here is a thin forwarder; the canonical
|
|
10
|
+
# homes are SnapDiff (session lifecycle: per-test, `SnapDiff.session` and
|
|
11
|
+
# friends) and SnapDiff::Reporting (reporter lifecycle: process-global,
|
|
12
|
+
# suite-long). Names, arities and object identities are unchanged -- this
|
|
13
|
+
# module stays as the compatibility surface for existing consumers.
|
|
20
14
|
module CapybaraScreenshotDiff
|
|
21
15
|
class << self
|
|
22
16
|
require "forwardable"
|
|
23
17
|
extend Forwardable
|
|
24
18
|
|
|
25
|
-
# --- Session lifecycle (
|
|
19
|
+
# --- Session lifecycle (per-test) -> SnapDiff ---
|
|
26
20
|
|
|
27
21
|
def registry
|
|
28
|
-
|
|
22
|
+
SnapDiff.session
|
|
29
23
|
end
|
|
30
24
|
|
|
31
25
|
def_delegators :registry, :add_assertion, :assertions, :assertions_present?,
|
|
32
26
|
:failed_assertions, :record_new_screenshot, :new_screenshots,
|
|
33
27
|
:new_screenshots_present?, :screenshot_namer, :verify
|
|
34
28
|
|
|
29
|
+
# Written out rather than def_delegators so the arities stay 0 (a
|
|
30
|
+
# Forwardable-generated method takes *args, **kwargs, &block).
|
|
35
31
|
def reset
|
|
36
|
-
|
|
37
|
-
registry.reset
|
|
32
|
+
SnapDiff.reset
|
|
38
33
|
end
|
|
39
34
|
|
|
40
|
-
# Message to skip the test with when a new screenshot has no baseline yet
|
|
41
|
-
# and `pending_if_new` is enabled. Adapters call this after verifying
|
|
42
|
-
# screenshots, and skip the test with the returned message when present.
|
|
43
|
-
#
|
|
44
|
-
# @return [String, nil] the pending message, or nil when there is nothing to report
|
|
45
35
|
def pending_screenshots_message
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"No baseline for: #{new_screenshots.join(", ")}. Commit the captured screenshots to record them."
|
|
36
|
+
SnapDiff.pending_screenshots_message
|
|
49
37
|
end
|
|
50
38
|
|
|
51
|
-
# --- Reporter lifecycle (process-global, suite-long) ---
|
|
39
|
+
# --- Reporter lifecycle (process-global, suite-long) -> SnapDiff::Reporting ---
|
|
52
40
|
|
|
53
41
|
def reporters
|
|
54
42
|
SnapDiff::Reporting.reporters
|
|
@@ -22,19 +22,25 @@ require "capybara/screenshot/diff/screenshot_matcher"
|
|
|
22
22
|
require "capybara/screenshot/diff/reporters/default"
|
|
23
23
|
|
|
24
24
|
require "capybara_screenshot_diff/error_with_filtered_backtrace"
|
|
25
|
+
require "snap_diff/errors"
|
|
25
26
|
|
|
26
27
|
module CapybaraScreenshotDiff
|
|
27
28
|
# RED_RGBA / ORANGE_RGBA moved to SnapDiff (snap_diff/annotation_service)
|
|
28
29
|
# so the bare "snap_diff" entry gets them too; the old names resolve via
|
|
29
30
|
# snap_diff/legacy_shims with a deprecation warning.
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
# ADR-008 step 2: the error classes moved to SnapDiff (snap_diff/errors).
|
|
33
|
+
# These are EAGER same-object aliases -- deliberately not const_missing
|
|
34
|
+
# shims -- so rescue-by-old-name and defined?/const_defined? feature
|
|
35
|
+
# detection keep working exactly as before (const_defined? never
|
|
36
|
+
# triggers const_missing).
|
|
37
|
+
CapybaraScreenshotDiffError = SnapDiff::Error
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
ExpectationNotMet = SnapDiff::ExpectationNotMet
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
UnstableImage = SnapDiff::UnstableImage
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
WindowSizeMismatchError = SnapDiff::WindowSizeMismatchError
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
require "capybara_screenshot_diff/dsl"
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
# no v2.0 move needed) so this is an absolute require, not require_relative.
|
|
5
|
-
require "capybara/screenshot/diff/region"
|
|
3
|
+
require "snap_diff/region"
|
|
6
4
|
|
|
7
5
|
module SnapDiff
|
|
8
6
|
module BrowserHelpers
|
|
@@ -9,24 +9,23 @@ module SnapDiff
|
|
|
9
9
|
# Called exactly once per capture, before the screenshoter runs and outside
|
|
10
10
|
# any stability retry loop. Today it only validates the window size
|
|
11
11
|
# (raise-only, never resizes) — the same guard ScreenshotMatcher carried
|
|
12
|
-
# inline before. v3
|
|
13
|
-
# capture
|
|
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.
|
|
14
15
|
module Viewport
|
|
15
16
|
module_function
|
|
16
17
|
|
|
17
18
|
# @param expected_window_size [Array(Integer, Integer), nil] the configured window size
|
|
18
|
-
# @
|
|
19
|
-
# element-anchored capture); accepted but unused today.
|
|
20
|
-
# @raise [CapybaraScreenshotDiff::WindowSizeMismatchError] if the browser
|
|
19
|
+
# @raise [SnapDiff::WindowSizeMismatchError] if the browser
|
|
21
20
|
# window does not match the expected size.
|
|
22
|
-
def prepare!(expected_window_size
|
|
21
|
+
def prepare!(expected_window_size)
|
|
23
22
|
return unless BrowserHelpers.window_size_is_wrong?(expected_window_size)
|
|
24
23
|
|
|
25
24
|
current_size = BrowserHelpers.selenium? ?
|
|
26
25
|
BrowserHelpers.session.driver.browser.manage.window.size.to_s :
|
|
27
26
|
"unknown"
|
|
28
27
|
|
|
29
|
-
raise
|
|
28
|
+
raise SnapDiff::WindowSizeMismatchError.new(<<~ERROR.chomp, caller)
|
|
30
29
|
Window size mismatch detected!
|
|
31
30
|
Expected: #{expected_window_size.inspect}
|
|
32
31
|
Actual: #{current_size}
|
data/lib/snap_diff/comparison.rb
CHANGED
|
@@ -6,28 +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 "
|
|
10
|
-
|
|
11
|
-
# The internal images-holder struct and the driver cache keep their legacy
|
|
12
|
-
# Capybara::Screenshot::Diff homes for now: SnapDiff::Comparison is the
|
|
13
|
-
# comparison class below (ex-ImageCompare), so the struct cannot take the
|
|
14
|
-
# same name. Its SnapDiff home arrives only when it is folded into
|
|
15
|
-
# Comparison as a nested value (v2 design section 2) -- renaming it here
|
|
16
|
-
# would exceed step 5's two approved renames.
|
|
17
|
-
module Capybara
|
|
18
|
-
module Screenshot
|
|
19
|
-
module Diff
|
|
20
|
-
LOADED_DRIVERS = {}
|
|
21
|
-
|
|
22
|
-
# Holds the two images (and their paths/options/driver) being compared.
|
|
23
|
-
class Comparison < Struct.new(:new_image, :base_image, :options, :driver, :new_image_path, :base_image_path)
|
|
24
|
-
def skip_area
|
|
25
|
-
options[:skip_area]
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
9
|
+
require "snap_diff/reporters/default"
|
|
31
10
|
|
|
32
11
|
module SnapDiff
|
|
33
12
|
# Handles comparison of two images with a focus on performance and accuracy.
|
|
@@ -53,6 +32,14 @@ module SnapDiff
|
|
|
53
32
|
# - Only performing expensive operations when absolutely necessary
|
|
54
33
|
# - Maintaining high accuracy for complex comparisons
|
|
55
34
|
class Comparison
|
|
35
|
+
# Holds the two images (and their paths/options/driver) being compared
|
|
36
|
+
# (ADR-008 step 5: ex-Capybara::Screenshot::Diff::Comparison struct).
|
|
37
|
+
Images = Struct.new(:new_image, :base_image, :options, :driver, :new_image_path, :base_image_path) do
|
|
38
|
+
def skip_area
|
|
39
|
+
options[:skip_area]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
56
43
|
TOLERABLE_OPTIONS = [:tolerance, :color_distance_limit, :shift_distance_limit, :area_size_limit].freeze
|
|
57
44
|
|
|
58
45
|
attr_reader :driver, :driver_options
|
|
@@ -136,7 +123,7 @@ module SnapDiff
|
|
|
136
123
|
|
|
137
124
|
def load_images_and_build_comparison(base_path, new_path, options)
|
|
138
125
|
base_img, new_img = driver.load_images(base_path, new_path)
|
|
139
|
-
|
|
126
|
+
Images.new(new_img, base_img, options, driver, new_path, base_path)
|
|
140
127
|
end
|
|
141
128
|
|
|
142
129
|
def image_preprocessor
|
|
@@ -155,7 +142,7 @@ module SnapDiff
|
|
|
155
142
|
|
|
156
143
|
# Analyzes the comparison and determines if images are different.
|
|
157
144
|
#
|
|
158
|
-
# @param comparison [
|
|
145
|
+
# @param comparison [Comparison::Images] The comparison object containing images to analyze.
|
|
159
146
|
# @param quick_mode [Boolean] When true, performs minimal checks and returns early.
|
|
160
147
|
# In quick mode, returns [is_equal, difference] where:
|
|
161
148
|
# - is_equal is true if images are considered equal
|
|
@@ -195,7 +182,7 @@ module SnapDiff
|
|
|
195
182
|
|
|
196
183
|
def build_reporter
|
|
197
184
|
current_difference = difference || build_null_difference
|
|
198
|
-
|
|
185
|
+
Reporters::Default.new(current_difference)
|
|
199
186
|
end
|
|
200
187
|
|
|
201
188
|
# Loads and preprocesses images for detailed comparison.
|
|
@@ -203,7 +190,7 @@ module SnapDiff
|
|
|
203
190
|
# This method is responsible for:
|
|
204
191
|
# 1. Loading both images using the configured driver
|
|
205
192
|
# 2. Applying any necessary preprocessing (cropping, normalization)
|
|
206
|
-
# 3. Creating a
|
|
193
|
+
# 3. Creating a Comparison::Images object that holds the image data
|
|
207
194
|
#
|
|
208
195
|
# @param base_path [String,Pathname] Path to the baseline/reference image
|
|
209
196
|
# @param new_path [String,Pathname] Path to the new/candidate image
|
|
@@ -211,7 +198,7 @@ module SnapDiff
|
|
|
211
198
|
# - :crop [Array<Integer>] Optional crop area [x, y, width, height]
|
|
212
199
|
# - :skip_area [Array<Array>] Areas to exclude from comparison
|
|
213
200
|
# - :tolerance [Numeric] Color tolerance threshold
|
|
214
|
-
# @return [
|
|
201
|
+
# @return [Comparison::Images] Prepared comparison object ready for analysis
|
|
215
202
|
# @raise [ArgumentError] If image files are invalid or unreadable
|
|
216
203
|
def load_comparison(base_path, new_path, options)
|
|
217
204
|
comparison = load_images_and_build_comparison(base_path, new_path, options)
|
|
@@ -219,7 +206,7 @@ module SnapDiff
|
|
|
219
206
|
end
|
|
220
207
|
|
|
221
208
|
def build_null_difference(failed_by = nil)
|
|
222
|
-
comparison =
|
|
209
|
+
comparison = Images.new(nil, nil, driver_options, driver, image_path, base_image_path).freeze
|
|
223
210
|
ComparisonResult.build_null(comparison, base_image_path, image_path, failed_by)
|
|
224
211
|
end
|
|
225
212
|
|
data/lib/snap_diff/config.rb
CHANGED
|
@@ -1,35 +1,50 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
# This file is the LEAF of the config require graph (ADR-008 step 1):
|
|
6
|
+
# config_legacy.rb requires it, so it must never require config_legacy nor
|
|
7
|
+
# anything that leads back to either entry point. The MAPPING below needs
|
|
8
|
+
# the legacy module constants to exist at class-body eval time, so the empty
|
|
9
|
+
# skeleton is predefined here (same technique as legacy_shims.rb);
|
|
10
|
+
# config_legacy.rb reopens these modules and installs the delegating
|
|
11
|
+
# accessors from MAPPING.
|
|
12
|
+
module Capybara
|
|
13
|
+
module Screenshot
|
|
14
|
+
module Diff
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Referenced by Config#initialize (screenshoter/manager defaults), which
|
|
20
|
+
# runs at the eager Config.new at the bottom of this file, so they must be
|
|
21
|
+
# real, already-loaded classes first. Neither requires back here.
|
|
22
|
+
require "snap_diff/screenshoter"
|
|
23
|
+
require "snap_diff/snap_manager"
|
|
9
24
|
|
|
10
25
|
module SnapDiff
|
|
11
|
-
# Flat
|
|
12
|
-
# +Capybara::Screenshot
|
|
13
|
-
#
|
|
26
|
+
# Flat consolidation of every legacy +Capybara::Screenshot+ /
|
|
27
|
+
# +Capybara::Screenshot::Diff+ setting behind one object:
|
|
28
|
+
# <tt>SnapDiff.config.<em>attr</em></tt>.
|
|
29
|
+
#
|
|
30
|
+
# Storage ownership (ADR-008 step 1, inverted from the original v2
|
|
31
|
+
# consolidation): Config IS the single storage. The legacy accessors on
|
|
32
|
+
# +Capybara::Screenshot+ / +Capybara::Screenshot::Diff+ are thin
|
|
33
|
+
# delegators installed by config_legacy.rb from {MAPPING} -- one storage,
|
|
34
|
+
# two views, so a write through either surface is visible through the
|
|
35
|
+
# other structurally, not by synchronization.
|
|
14
36
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# +
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# is exactly the kind of "when defaults are evaluated" divergence the v2
|
|
25
|
-
# consolidation must not introduce. Delegating sidesteps the question
|
|
26
|
-
# entirely: Config never evaluates a default, it only ever forwards to
|
|
27
|
-
# whichever accessor already owns one. Bidirectional consistency (a write
|
|
28
|
-
# through either the old accessor or Config is visible through the other)
|
|
29
|
-
# falls out for free, because both paths read and write the exact same
|
|
30
|
-
# class-variable-backed storage.
|
|
37
|
+
# Default timing contract (pinned by config_default_timing_test.rb):
|
|
38
|
+
# every default below is evaluated ONCE, in #initialize, which runs at
|
|
39
|
+
# require time of this file (the eager +Config.new+ at the bottom) -- the
|
|
40
|
+
# same load moment the old +mattr_accessor+ default blocks evaluated at.
|
|
41
|
+
# In particular +fail_if_new+ (from <tt>ENV["CI"]</tt>) and +root+ (from
|
|
42
|
+
# +Rails.root+ / pwd) must never become lazy read-time defaults, memoized
|
|
43
|
+
# or not. The one deliberately LIVE value, +default_options[:wait]+, is
|
|
44
|
+
# not storage at all: it stays a method-body read of
|
|
45
|
+
# +Capybara.default_max_wait_time+ in +#default_options+.
|
|
31
46
|
class Config
|
|
32
|
-
# config attr name => [
|
|
47
|
+
# config attr name => [legacy module, legacy accessor name].
|
|
33
48
|
#
|
|
34
49
|
# The two names differ only for +screenshot_enabled+:
|
|
35
50
|
# +Capybara::Screenshot.enabled+ and +Capybara::Screenshot::Diff.enabled+
|
|
@@ -70,9 +85,116 @@ module SnapDiff
|
|
|
70
85
|
manager: [Capybara::Screenshot::Diff, :manager]
|
|
71
86
|
}.freeze
|
|
72
87
|
|
|
73
|
-
MAPPING.
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
attr_accessor(*(MAPPING.keys - [:root]))
|
|
89
|
+
attr_reader :root
|
|
90
|
+
|
|
91
|
+
def initialize
|
|
92
|
+
# Every mapped setting gets its ivar up front (nil-defaulted ones
|
|
93
|
+
# included) so the full set always exists -- test_helper's per-test
|
|
94
|
+
# isolation snapshots/restores config by instance variable, and an
|
|
95
|
+
# ivar that only appears on first write would escape that snapshot
|
|
96
|
+
# and leak between tests.
|
|
97
|
+
MAPPING.each_key { |key| instance_variable_set(:"@#{key}", nil) }
|
|
98
|
+
# Capybara::Screenshot side.
|
|
99
|
+
@blur_active_element = true
|
|
100
|
+
@hide_caret = true
|
|
101
|
+
# Raw Rails.root (no coercion), matching the old mattr_reader default;
|
|
102
|
+
# only the writer below coerces.
|
|
103
|
+
@root = (defined?(Rails) && defined?(Rails.root) && Rails.root) || Pathname(".").expand_path
|
|
104
|
+
@save_path = "doc/screenshots"
|
|
105
|
+
@screenshot_format = "png"
|
|
106
|
+
@capybara_screenshot_options = {}
|
|
107
|
+
# Capybara::Screenshot::Diff side.
|
|
108
|
+
@delayed = true
|
|
109
|
+
@fail_if_new = !ENV["CI"].nil? && !ENV["CI"].empty?
|
|
110
|
+
@pending_if_new = false
|
|
111
|
+
@fail_on_difference = true
|
|
112
|
+
@enabled = true
|
|
113
|
+
@driver = :auto
|
|
114
|
+
@screenshoter = SnapDiff::Screenshoter
|
|
115
|
+
@manager = SnapDiff::SnapManager
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def root=(path)
|
|
119
|
+
@root = Pathname(path).expand_path
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# --- Derived config (ADR-008 step 7b) -------------------------------
|
|
123
|
+
# Read-only values computed from the storage above. They used to live
|
|
124
|
+
# on the legacy modules; those now one-line forward here.
|
|
125
|
+
|
|
126
|
+
# ex +Capybara::Screenshot.active?+. The two +enabled+ settings are
|
|
127
|
+
# independent (see {MAPPING}): the Screenshot-side one wins whenever it
|
|
128
|
+
# was set at all, and only a nil there falls through to the Diff-side
|
|
129
|
+
# one.
|
|
130
|
+
def active?
|
|
131
|
+
screenshot_enabled || (screenshot_enabled.nil? && enabled)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# ex +Capybara::Screenshot.screenshot_area+: the save_path, optionally
|
|
135
|
+
# segmented per OS and per Capybara driver.
|
|
136
|
+
def screenshot_area
|
|
137
|
+
parts = [save_path]
|
|
138
|
+
parts << Os.name if add_os_path
|
|
139
|
+
parts << Capybara.current_driver.to_s if add_driver_path
|
|
140
|
+
File.join(*parts)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# ex +Capybara::Screenshot.screenshot_area_abs+.
|
|
144
|
+
def screenshot_area_abs
|
|
145
|
+
root / screenshot_area
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# ex +Capybara::Screenshot::Diff.default_options+: the capture/compare
|
|
149
|
+
# defaults handed to {SnapDiff::Comparison}. Carries the one literal
|
|
150
|
+
# that is not a stored setting -- the vips tolerance floor.
|
|
151
|
+
def default_options
|
|
152
|
+
{
|
|
153
|
+
area_size_limit: area_size_limit,
|
|
154
|
+
color_distance_limit: color_distance_limit,
|
|
155
|
+
driver: driver,
|
|
156
|
+
screenshot_format: screenshot_format,
|
|
157
|
+
capybara_screenshot_options: capybara_screenshot_options,
|
|
158
|
+
perceptual_threshold: perceptual_threshold,
|
|
159
|
+
shift_distance_limit: shift_distance_limit,
|
|
160
|
+
skip_area: skip_area,
|
|
161
|
+
stability_time_limit: stability_time_limit,
|
|
162
|
+
tolerance: tolerance || ((driver == :vips) ? 0.001 : nil),
|
|
163
|
+
# Deliberately LIVE (pinned by config_default_timing_test.rb):
|
|
164
|
+
# read at call time, never frozen into storage.
|
|
165
|
+
wait: Capybara.default_max_wait_time
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Instantiated eagerly so the require-time defaults above are evaluated
|
|
171
|
+
# NOW, at load, not at the first SnapDiff.config call.
|
|
172
|
+
@config = Config.new
|
|
173
|
+
|
|
174
|
+
# The single consolidated settings object -- and the single storage.
|
|
175
|
+
# See {SnapDiff::Config}.
|
|
176
|
+
def self.config
|
|
177
|
+
@config
|
|
178
|
+
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) }
|
|
76
198
|
end
|
|
77
199
|
end
|
|
78
200
|
end
|
|
@@ -5,12 +5,16 @@ module SnapDiff
|
|
|
5
5
|
#
|
|
6
6
|
# Internal until the v2 namespace transition; not a public contract.
|
|
7
7
|
#
|
|
8
|
-
# Warn-once-per-subject deprecation
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
8
|
+
# Warn-once-per-subject deprecation engine for the legacy-namespace
|
|
9
|
+
# shims: snap_diff/legacy_shims routes every +const_missing+ hit on an
|
|
10
|
+
# old +Capybara::Screenshot::Diff+ / +CapybaraScreenshotDiff+ constant
|
|
11
|
+
# through {.warn}, so each deprecated name warns exactly once per
|
|
12
|
+
# process (ADR-004's v2 namespace transition).
|
|
13
13
|
module Deprecation
|
|
14
|
+
# Everything under lib/ is "the gem"; the first caller frame outside
|
|
15
|
+
# it is the user code that referenced the deprecated name (same
|
|
16
|
+
# filtering idea as BacktraceFilter in error_with_filtered_backtrace).
|
|
17
|
+
GEM_LIB_DIR = File.expand_path("..", __dir__) + File::SEPARATOR
|
|
14
18
|
# Emission channel: Kernel#warn, not a direct +$stderr.puts+.
|
|
15
19
|
#
|
|
16
20
|
# Kernel#warn delegates to +Warning.warn+ (Ruby >= 2.4), so anything
|
|
@@ -40,7 +44,7 @@ module SnapDiff
|
|
|
40
44
|
end
|
|
41
45
|
return unless first_time
|
|
42
46
|
|
|
43
|
-
Kernel.warn(message_for(subject, replacement, category))
|
|
47
|
+
Kernel.warn(message_for(subject, replacement, category, caller_locations(1)))
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
# @api private
|
|
@@ -55,9 +59,23 @@ module SnapDiff
|
|
|
55
59
|
|
|
56
60
|
private
|
|
57
61
|
|
|
58
|
-
def message_for(subject, replacement, category)
|
|
59
|
-
"[snap_diff deprecation] `#{subject}` is deprecated (#{category}); " \
|
|
62
|
+
def message_for(subject, replacement, category, locations)
|
|
63
|
+
message = "[snap_diff deprecation] `#{subject}` is deprecated (#{category}); " \
|
|
60
64
|
"use `#{replacement}` instead."
|
|
65
|
+
origin = origin_for(locations)
|
|
66
|
+
origin ? "#{message} (called from #{origin})" : message
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# First frame outside the gem's lib dir, formatted "file:line";
|
|
70
|
+
# nil when every frame is internal (or paths are unavailable).
|
|
71
|
+
def origin_for(locations)
|
|
72
|
+
(locations || []).each do |location|
|
|
73
|
+
path = location.absolute_path || location.path
|
|
74
|
+
next if path.nil? || path.start_with?(GEM_LIB_DIR)
|
|
75
|
+
|
|
76
|
+
return "#{path}:#{location.lineno}"
|
|
77
|
+
end
|
|
78
|
+
nil
|
|
61
79
|
end
|
|
62
80
|
end
|
|
63
81
|
end
|
data/lib/snap_diff/drivers.rb
CHANGED
|
@@ -10,5 +10,25 @@ module SnapDiff
|
|
|
10
10
|
|
|
11
11
|
Utils.find_driver_class_for(driver_option).new
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
# Canonical driver-class cache (ADR-008 step 5b, ex
|
|
15
|
+
# Capybara::Screenshot::Diff::LOADED_DRIVERS): driver name => driver
|
|
16
|
+
# class, filled lazily by Utils.find_driver_class_for. Mutated in
|
|
17
|
+
# place -- including by user registration through the legacy constant,
|
|
18
|
+
# which legacy_shims pins as an EAGER same-object alias of this hash
|
|
19
|
+
# (a lazy copy would silently drop such registrations).
|
|
20
|
+
def self.loaded
|
|
21
|
+
@loaded ||= {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Canonical read API for the detected-drivers list. The value itself
|
|
25
|
+
# stays on Capybara::Screenshot::Diff::AVAILABLE_DRIVERS (assigned in
|
|
26
|
+
# config_legacy.rb at load time, exactly when detection historically
|
|
27
|
+
# ran); this reads it live rather than caching, because that constant
|
|
28
|
+
# is the published stubbing point (image_compare_test stubs it to []
|
|
29
|
+
# to exercise the no-drivers error path).
|
|
30
|
+
def self.available
|
|
31
|
+
Capybara::Screenshot::Diff::AVAILABLE_DRIVERS
|
|
32
|
+
end
|
|
13
33
|
end
|
|
14
34
|
end
|