capybara-screenshot-diff 1.15.0 → 2.0.0.alpha1
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 +59 -0
- data/docs/RELEASE_PREP.md +6 -6
- data/docs/thread_safety.md +15 -0
- data/lib/capybara/screenshot/diff/annotation_service.rb +5 -77
- data/lib/capybara/screenshot/diff/area_calculator.rb +5 -54
- data/lib/capybara/screenshot/diff/browser_helpers.rb +5 -119
- data/lib/capybara/screenshot/diff/config_legacy.rb +113 -0
- data/lib/capybara/screenshot/diff/cucumber.rb +1 -1
- data/lib/capybara/screenshot/diff/difference.rb +6 -102
- data/lib/capybara/screenshot/diff/drivers/base_driver.rb +7 -39
- data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +5 -299
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +5 -169
- data/lib/capybara/screenshot/diff/drivers.rb +7 -14
- data/lib/capybara/screenshot/diff/image_compare.rb +10 -228
- data/lib/capybara/screenshot/diff/image_preprocessor.rb +5 -70
- data/lib/capybara/screenshot/diff/os.rb +8 -13
- data/lib/capybara/screenshot/diff/reporters/default.rb +5 -3
- data/lib/capybara/screenshot/diff/screenshot_matcher.rb +5 -129
- data/lib/capybara/screenshot/diff/screenshoter.rb +5 -127
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +5 -104
- data/lib/capybara/screenshot/diff/utils.rb +5 -40
- data/lib/capybara/screenshot/diff/vcs.rb +5 -37
- data/lib/capybara/screenshot/diff/version.rb +7 -1
- data/lib/capybara_screenshot_diff/attempts_reporter.rb +5 -47
- data/lib/capybara_screenshot_diff/cucumber.rb +5 -17
- data/lib/capybara_screenshot_diff/dsl.rb +7 -129
- data/lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb +6 -30
- data/lib/capybara_screenshot_diff/minitest.rb +6 -56
- data/lib/capybara_screenshot_diff/reporters/html.rb +5 -135
- data/lib/capybara_screenshot_diff/rspec.rb +5 -63
- data/lib/capybara_screenshot_diff/screenshot_assertion.rb +39 -157
- data/lib/capybara_screenshot_diff/screenshot_namer.rb +5 -79
- data/lib/capybara_screenshot_diff/snap.rb +5 -64
- data/lib/capybara_screenshot_diff/snap_manager.rb +5 -82
- data/lib/capybara_screenshot_diff/static.rb +3 -5
- data/lib/capybara_screenshot_diff.rb +29 -100
- data/lib/snap_diff/annotation_service.rb +84 -0
- data/lib/snap_diff/area_calculator.rb +56 -0
- data/lib/snap_diff/attempts_reporter.rb +51 -0
- data/lib/snap_diff/browser_helpers.rb +121 -0
- data/lib/snap_diff/capture/viewport.rb +40 -0
- data/lib/snap_diff/comparison.rb +238 -0
- data/lib/snap_diff/comparison_result.rb +104 -0
- data/lib/snap_diff/config.rb +6 -2
- data/lib/snap_diff/deprecation.rb +84 -0
- data/lib/snap_diff/driver.rb +37 -0
- data/lib/snap_diff/drivers/chunky_png_driver.rb +298 -0
- data/lib/snap_diff/drivers/vips_driver.rb +171 -0
- data/lib/snap_diff/drivers.rb +14 -0
- data/lib/snap_diff/dsl.rb +143 -0
- data/lib/snap_diff/error_with_filtered_backtrace.rb +32 -0
- data/lib/snap_diff/image_preprocessor.rb +68 -0
- data/lib/snap_diff/integrations/cucumber.rb +22 -0
- data/lib/snap_diff/integrations/minitest.rb +70 -0
- data/lib/snap_diff/integrations/rspec.rb +68 -0
- data/lib/snap_diff/legacy_shims.rb +99 -0
- data/lib/snap_diff/os.rb +17 -0
- data/lib/snap_diff/reporters/html.rb +143 -0
- data/lib/snap_diff/reporting.rb +53 -0
- data/lib/snap_diff/screenshot_assertion.rb +143 -0
- data/lib/snap_diff/screenshot_matcher.rb +113 -0
- data/lib/snap_diff/screenshot_namer.rb +81 -0
- data/lib/snap_diff/screenshoter.rb +129 -0
- data/lib/snap_diff/snap.rb +66 -0
- data/lib/snap_diff/snap_manager.rb +125 -0
- data/lib/snap_diff/stable_screenshoter.rb +103 -0
- data/lib/snap_diff/static.rb +11 -0
- data/lib/snap_diff/utils.rb +38 -0
- data/lib/snap_diff/vcs.rb +35 -0
- data/lib/snap_diff/version.rb +5 -0
- data/lib/snap_diff.rb +22 -6
- metadata +36 -2
- /data/lib/{capybara_screenshot_diff → snap_diff}/reporters/templates/report.html.erb +0 -0
|
@@ -1,106 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
attr_reader :stability_time_limit, :wait
|
|
10
|
-
|
|
11
|
-
# Initializes a new instance of StableScreenshoter
|
|
12
|
-
#
|
|
13
|
-
# This method sets up a new screenshoter with specific capture and comparison options. It validates the presence of
|
|
14
|
-
# `:stability_time_limit` and `:wait` in capture options and ensures that `:stability_time_limit` is less than or equal to `:wait`.
|
|
15
|
-
#
|
|
16
|
-
# @param capture_options [Hash] The options for capturing screenshots, must include `:stability_time_limit` and `:wait`.
|
|
17
|
-
# @param comparison_options [Hash] The options for comparing screenshots, defaults to `{}`. Same signature as {Capybara::Screenshot::Screenshoter#initialize}.
|
|
18
|
-
# @raise [ArgumentError] If `:wait` or `:stability_time_limit` are not provided, or if `:stability_time_limit` is greater than `:wait`.
|
|
19
|
-
def initialize(capture_options, comparison_options = {})
|
|
20
|
-
@stability_time_limit, @wait = capture_options.fetch_values(*STABILITY_OPTIONS)
|
|
21
|
-
|
|
22
|
-
raise ArgumentError, "wait should be provided for stable screenshots" unless wait
|
|
23
|
-
raise ArgumentError, "stability_time_limit should be provided for stable screenshots" unless stability_time_limit
|
|
24
|
-
raise ArgumentError, "stability_time_limit (#{stability_time_limit}) should be less or equal than wait (#{wait}) for stable screenshots" unless stability_time_limit <= wait
|
|
25
|
-
|
|
26
|
-
@comparison_options = comparison_options
|
|
27
|
-
|
|
28
|
-
@screenshoter = Diff.screenshoter.new(capture_options.except(:stability_time_limit), @comparison_options)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Takes a comparison screenshot ensuring page stability
|
|
32
|
-
#
|
|
33
|
-
# Attempts to take a stable screenshot of the page by comparing several screenshot attempts until the page stops updating
|
|
34
|
-
# or the `:wait` limit is reached. If unable to achieve a stable state within the time limit, it annotates the attempts
|
|
35
|
-
# to aid debugging.
|
|
36
|
-
#
|
|
37
|
-
# @param snapshot Snap The snapshot details to take a stable screenshot of.
|
|
38
|
-
# @return [void]
|
|
39
|
-
# @raise [RuntimeError] If a stable screenshot cannot be obtained within the specified `:wait` time.
|
|
40
|
-
def take_comparison_screenshot(snapshot)
|
|
41
|
-
result = take_stable_screenshot(snapshot)
|
|
42
|
-
|
|
43
|
-
# We failed to get stable browser state! Generate difference between attempts to overview moving parts!
|
|
44
|
-
unless result
|
|
45
|
-
# FIXME(uwe): Change to store the failure and only report if the test succeeds functionally.
|
|
46
|
-
annotate_attempts_and_fail!(snapshot)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# store success attempt as actual screenshot
|
|
50
|
-
snapshot.commit_last_attempt
|
|
51
|
-
|
|
52
|
-
# cleanup all previous attempts
|
|
53
|
-
snapshot.cleanup_attempts!
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def take_stable_screenshot(snapshot)
|
|
57
|
-
# We try to compare first attempt with checkout version, in order to not run next screenshots
|
|
58
|
-
deadline_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + wait
|
|
59
|
-
|
|
60
|
-
# Cleanup all previous attempts for sure
|
|
61
|
-
snapshot.cleanup_attempts!
|
|
62
|
-
|
|
63
|
-
loop do
|
|
64
|
-
attempt_next_screenshot(snapshot)
|
|
65
|
-
|
|
66
|
-
return true if attempt_successful?(snapshot)
|
|
67
|
-
return false if timeout?(deadline_at)
|
|
68
|
-
|
|
69
|
-
sleep(stability_time_limit)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
private
|
|
74
|
-
|
|
75
|
-
def attempt_successful?(snapshot)
|
|
76
|
-
return false unless snapshot.prev_attempt_path
|
|
77
|
-
|
|
78
|
-
build_last_attempts_comparison_for(snapshot).quick_equal?
|
|
79
|
-
rescue ArgumentError
|
|
80
|
-
false
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def attempt_next_screenshot(snapshot)
|
|
84
|
-
@screenshoter.take_screenshot(snapshot.next_attempt_path!)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def timeout?(deadline_at)
|
|
88
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline_at
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def build_last_attempts_comparison_for(snapshot)
|
|
92
|
-
ImageCompare.new(snapshot.attempt_path, snapshot.prev_attempt_path, @comparison_options)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# TODO: Move to the HistoricalReporter
|
|
96
|
-
def annotate_attempts_and_fail!(snapshot)
|
|
97
|
-
require "capybara_screenshot_diff/attempts_reporter"
|
|
98
|
-
attempts_reporter = CapybaraScreenshotDiff::AttemptsReporter.new(snapshot, @comparison_options, {wait: wait, stability_time_limit: stability_time_limit})
|
|
99
|
-
|
|
100
|
-
# TODO: Move fail to the queue after tests passed
|
|
101
|
-
raise CapybaraScreenshotDiff::UnstableImage.new(attempts_reporter.generate, caller)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): Capybara::Screenshot::Diff::StableScreenshoter
|
|
4
|
+
# now resolves lazily via snap_diff/legacy_shims' const_missing, with a
|
|
5
|
+
# deprecation warning pointing at SnapDiff::StableScreenshoter.
|
|
6
|
+
require "snap_diff/stable_screenshoter"
|
|
7
|
+
require "snap_diff/legacy_shims"
|
|
@@ -1,42 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
result = []
|
|
9
|
-
begin
|
|
10
|
-
result << :vips if defined?(Vips) || require("vips")
|
|
11
|
-
rescue LoadError
|
|
12
|
-
# vips not present
|
|
13
|
-
Object.send(:remove_const, :Vips) if defined?(Vips)
|
|
14
|
-
end
|
|
15
|
-
begin
|
|
16
|
-
result << :chunky_png if defined?(ChunkyPNG) || require("chunky_png")
|
|
17
|
-
rescue LoadError
|
|
18
|
-
# chunky_png not present
|
|
19
|
-
Object.send(:remove_const, :ChunkyPNG) if defined?(ChunkyPNG)
|
|
20
|
-
end
|
|
21
|
-
result
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def self.find_driver_class_for(driver)
|
|
25
|
-
driver = AVAILABLE_DRIVERS.first if driver == :auto
|
|
26
|
-
|
|
27
|
-
LOADED_DRIVERS[driver] ||=
|
|
28
|
-
case driver
|
|
29
|
-
when :chunky_png
|
|
30
|
-
require "capybara/screenshot/diff/drivers/chunky_png_driver"
|
|
31
|
-
Drivers::ChunkyPNGDriver
|
|
32
|
-
when :vips
|
|
33
|
-
require "capybara/screenshot/diff/drivers/vips_driver"
|
|
34
|
-
Drivers::VipsDriver
|
|
35
|
-
else
|
|
36
|
-
fail "Wrong adapter #{driver.inspect}. Available adapters: #{AVAILABLE_DRIVERS.inspect}"
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): Capybara::Screenshot::Diff::Utils now
|
|
4
|
+
# resolves lazily via snap_diff/legacy_shims' const_missing, with a
|
|
5
|
+
# deprecation warning pointing at SnapDiff::Utils.
|
|
6
|
+
require "snap_diff/utils"
|
|
7
|
+
require "snap_diff/legacy_shims"
|
|
@@ -1,39 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
module Diff
|
|
9
|
-
module Vcs
|
|
10
|
-
def self.checkout_vcs(root, screenshot_path, checkout_path)
|
|
11
|
-
root_path = root.to_s
|
|
12
|
-
git_root, _, status = Open3.capture3("git", "-C", root_path, "rev-parse", "--show-toplevel")
|
|
13
|
-
return false unless status.success?
|
|
14
|
-
|
|
15
|
-
git_root = git_root.chomp
|
|
16
|
-
vcs_file_path = Pathname.new(screenshot_path).expand_path.relative_path_from(Pathname.new(git_root)).to_s
|
|
17
|
-
|
|
18
|
-
if Screenshot.use_lfs
|
|
19
|
-
tmp_path = "#{checkout_path}.tmp"
|
|
20
|
-
success = system("git", "-C", root_path, "show", "HEAD:#{vcs_file_path}", out: tmp_path, err: File::NULL)
|
|
21
|
-
if success
|
|
22
|
-
system("git", "-C", root_path, "lfs", "smudge", in: tmp_path, out: checkout_path.to_s, err: File::NULL)
|
|
23
|
-
end
|
|
24
|
-
File.delete(tmp_path) if File.exist?(tmp_path)
|
|
25
|
-
else
|
|
26
|
-
success = system("git", "-C", root_path, "show", "HEAD:#{vcs_file_path}", out: checkout_path.to_s, err: File::NULL)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
unless success
|
|
30
|
-
checkout_path.delete if checkout_path.exist?
|
|
31
|
-
return false
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
true
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): Capybara::Screenshot::Diff::Vcs now
|
|
4
|
+
# resolves lazily via snap_diff/legacy_shims' const_missing, with a
|
|
5
|
+
# deprecation warning pointing at SnapDiff::Vcs.
|
|
6
|
+
require "snap_diff/vcs"
|
|
7
|
+
require "snap_diff/legacy_shims"
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "snap_diff/version"
|
|
4
|
+
|
|
5
|
+
# Deliberately EAGER and silent (v2 step 6 exception): the gemspec resolves
|
|
6
|
+
# Capybara::Screenshot::Diff::VERSION at build time, so a lazy warning shim
|
|
7
|
+
# would make every `gem build` warn. See snap_diff/legacy_shims.rb for the
|
|
8
|
+
# full exception list.
|
|
3
9
|
module Capybara
|
|
4
10
|
module Screenshot
|
|
5
11
|
module Diff
|
|
6
|
-
VERSION =
|
|
12
|
+
VERSION = SnapDiff::VERSION
|
|
7
13
|
end
|
|
8
14
|
end
|
|
9
15
|
end
|
|
@@ -1,49 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@snapshot = snapshot
|
|
9
|
-
@comparison_options = comparison_options
|
|
10
|
-
@wait = stability_options[:wait]
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def generate
|
|
14
|
-
attempts_screenshot_paths = @snapshot.find_attempts_paths
|
|
15
|
-
|
|
16
|
-
annotate_attempts(attempts_screenshot_paths)
|
|
17
|
-
|
|
18
|
-
"Could not get stable screenshot within #{@wait}s:\n#{attempts_screenshot_paths.join("\n")}"
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def build_comparison_for(attempt_path, previous_attempt_path)
|
|
22
|
-
Capybara::Screenshot::Diff::ImageCompare.new(attempt_path, previous_attempt_path, @comparison_options)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
def annotate_attempts(attempts_screenshot_paths)
|
|
28
|
-
previous_file = nil
|
|
29
|
-
attempts_screenshot_paths.reverse_each do |file_name|
|
|
30
|
-
if previous_file && File.exist?(previous_file)
|
|
31
|
-
attempts_comparison = build_comparison_for(file_name, previous_file)
|
|
32
|
-
|
|
33
|
-
if attempts_comparison.different?
|
|
34
|
-
FileUtils.mv(attempts_comparison.reporter.annotated_base_image_path, previous_file, force: true)
|
|
35
|
-
else
|
|
36
|
-
warn "[capybara-screenshot-diff] Some attempts was stable, but mistakenly marked as not: " \
|
|
37
|
-
"#{previous_file} and #{file_name} are equal"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
FileUtils.rm(attempts_comparison.reporter.annotated_image_path, force: true)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
previous_file = file_name
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
previous_file
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::AttemptsReporter now
|
|
4
|
+
# resolves lazily via snap_diff/legacy_shims' const_missing, with a
|
|
5
|
+
# deprecation warning pointing at SnapDiff::AttemptsReporter.
|
|
6
|
+
require "snap_diff/attempts_reporter"
|
|
7
|
+
require "snap_diff/legacy_shims"
|
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Legacy entry point: pre-move, this path transitively loaded the whole gem
|
|
4
|
+
# (via dsl -> umbrella). Preserve that contract for consumers who require
|
|
5
|
+
# only this file.
|
|
6
|
+
require "capybara_screenshot_diff"
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Before do
|
|
8
|
-
Capybara::Screenshot::Diff.delayed = false
|
|
9
|
-
Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
After do |scenario|
|
|
13
|
-
if !scenario.failed? && (msg = CapybaraScreenshotDiff.pending_screenshots_message)
|
|
14
|
-
skip_this_scenario(msg)
|
|
15
|
-
end
|
|
16
|
-
ensure
|
|
17
|
-
CapybaraScreenshotDiff.reset
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
AfterAll { CapybaraScreenshotDiff.finalize_reporters! }
|
|
8
|
+
require "snap_diff/integrations/cucumber"
|
|
@@ -1,134 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
4
|
-
require "capybara/screenshot/diff/drivers"
|
|
5
|
-
require "capybara/screenshot/diff/image_compare"
|
|
6
|
-
require "capybara/screenshot/diff/screenshot_matcher"
|
|
7
|
-
require "capybara_screenshot_diff/screenshot_namer"
|
|
8
|
-
require "capybara_screenshot_diff/screenshot_assertion"
|
|
3
|
+
require "snap_diff/dsl"
|
|
9
4
|
|
|
5
|
+
# Deliberately EAGER and silent (v2 step 6 exception): DSL is an advertised
|
|
6
|
+
# entry-point constant probed with Object.const_defined? by
|
|
7
|
+
# support_load_probe_test.rb, and const_defined? never triggers
|
|
8
|
+
# const_missing -- a lazy shim would break that contract. See
|
|
9
|
+
# snap_diff/legacy_shims.rb for the full exception list.
|
|
10
10
|
module CapybaraScreenshotDiff
|
|
11
|
-
|
|
12
|
-
# This module provides methods for taking screenshots, comparing them against baselines,
|
|
13
|
-
# and managing the comparison process with various configuration options.
|
|
14
|
-
#
|
|
15
|
-
# The DSL is designed to be included in your test context (e.g., RSpec, Minitest)
|
|
16
|
-
# to provide screenshot comparison capabilities.
|
|
17
|
-
module DSL
|
|
18
|
-
include Capybara::DSL
|
|
19
|
-
|
|
20
|
-
def screenshot_section(name)
|
|
21
|
-
screenshot_namer.section = name
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def screenshot_group(name)
|
|
25
|
-
screenshot_namer.group = name
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Takes a screenshot and compares it against a baseline image.
|
|
29
|
-
#
|
|
30
|
-
# The method follows a layered optimization strategy for comparison:
|
|
31
|
-
# 1. First checks if screenshot functionality is active
|
|
32
|
-
# 2. Builds a full screenshot name using the current context
|
|
33
|
-
# 3. Creates a screenshot assertion object
|
|
34
|
-
# 4. Either validates immediately or defers validation based on options
|
|
35
|
-
#
|
|
36
|
-
# @param name [String] The base name of the screenshot, used to generate the filename.
|
|
37
|
-
# @param skip_stack_frames [Integer] The number of stack frames to skip when reporting errors.
|
|
38
|
-
# @param options [Hash] Additional options for taking the screenshot and comparison.
|
|
39
|
-
# @option options [Boolean] :delayed (Capybara::Screenshot::Diff.delayed)
|
|
40
|
-
# Whether to validate the screenshot immediately or delay validation.
|
|
41
|
-
# @option options [Array<Integer>] :crop [left, top, right, bottom] Edge coordinates to crop the screenshot to.
|
|
42
|
-
# @option options [Array<Array<Integer>>] :skip_area Array of [left, top, right, bottom] edge coordinates to ignore.
|
|
43
|
-
# @option options [Numeric] :tolerance (0.001 for :vips driver) Color tolerance for comparison.
|
|
44
|
-
# Represents the maximum allowed ratio of different pixels (0.0-1.0 scale).
|
|
45
|
-
# @option options [Numeric] :color_distance_limit Maximum allowed color distance between pixels.
|
|
46
|
-
# Uses Euclidean RGBA distance (0-510 scale). Mutually exclusive with :perceptual_threshold.
|
|
47
|
-
# @option options [Numeric] :perceptual_threshold Maximum perceptual color difference (CIE dE00).
|
|
48
|
-
# Uses human perception-based scale (0-100+). VIPS only. Takes priority over :color_distance_limit if both set.
|
|
49
|
-
# @option options [Numeric] :shift_distance_limit Maximum allowed shift distance for pixels.
|
|
50
|
-
# @option options [Numeric] :area_size_limit Maximum allowed difference area size in pixels.
|
|
51
|
-
# @option options [Symbol] :driver (:auto) The image processing driver to use (:auto, :chunky_png, :vips).
|
|
52
|
-
# @return [Boolean] True if the screenshot was successfully captured and processed.
|
|
53
|
-
# @raise [CapybaraScreenshotDiff::ExpectationNotMet] If comparison fails and immediate validation is enabled.
|
|
54
|
-
# @raise [CapybaraScreenshotDiff::UnstableImage] If the image comparison is unstable.
|
|
55
|
-
# @raise [CapybaraScreenshotDiff::WindowSizeMismatchError] If the window size doesn't match expectations.
|
|
56
|
-
def assert_matches_screenshot(name, skip_stack_frames: 0, **options)
|
|
57
|
-
return false unless Capybara::Screenshot.active?
|
|
58
|
-
|
|
59
|
-
# Get the full name with section and group information
|
|
60
|
-
full_name = CapybaraScreenshotDiff.screenshot_namer.full_name(name)
|
|
61
|
-
|
|
62
|
-
# Build the screenshot assertion
|
|
63
|
-
assertion = build_screenshot_assertion(full_name, options, skip_stack_frames: skip_stack_frames + 1)
|
|
64
|
-
|
|
65
|
-
return false unless assertion
|
|
66
|
-
|
|
67
|
-
# Determine if validation should be delayed or immediate
|
|
68
|
-
delayed = options.fetch(:delayed, Capybara::Screenshot::Diff.delayed)
|
|
69
|
-
|
|
70
|
-
if delayed
|
|
71
|
-
CapybaraScreenshotDiff.add_assertion(assertion)
|
|
72
|
-
else
|
|
73
|
-
assertion.validate!
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
true
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Convenience wrapper around {#assert_matches_screenshot} and {#capture_screenshot}.
|
|
80
|
-
# @param compare [Boolean] When false, only captures the screenshot without comparing it to a baseline.
|
|
81
|
-
# @see #assert_matches_screenshot
|
|
82
|
-
# @see #capture_screenshot
|
|
83
|
-
def screenshot(name, skip_stack_frames: 0, compare: true, **options)
|
|
84
|
-
if compare
|
|
85
|
-
assert_matches_screenshot(name, skip_stack_frames: skip_stack_frames + 1, **options)
|
|
86
|
-
else
|
|
87
|
-
capture_screenshot(name, **options)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Captures a screenshot without comparing it to a baseline.
|
|
92
|
-
# @param name [String] The base name of the screenshot, used to generate the filename.
|
|
93
|
-
# @param options [Hash] Additional options for taking the screenshot. See {#assert_matches_screenshot}.
|
|
94
|
-
# @return [Boolean] True if the screenshot was successfully captured.
|
|
95
|
-
def capture_screenshot(name, **options)
|
|
96
|
-
return false unless Capybara::Screenshot.active?
|
|
97
|
-
|
|
98
|
-
full_name = CapybaraScreenshotDiff.screenshot_namer.full_name(name)
|
|
99
|
-
Capybara::Screenshot::Diff::ScreenshotMatcher.new(full_name, options).capture
|
|
100
|
-
|
|
101
|
-
true
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Asserts the current page has no visual changes from the baseline.
|
|
105
|
-
# Override in your base test class to add project-specific behavior
|
|
106
|
-
# (e.g., waiting for Turbo, default skip areas).
|
|
107
|
-
def assert_no_screenshot_changes(name, skip_stack_frames: 0, **opts)
|
|
108
|
-
assert_matches_screenshot(name, skip_stack_frames: skip_stack_frames + 1, **opts)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
private
|
|
112
|
-
|
|
113
|
-
# Builds a screenshot assertion object that can be validated immediately or later.
|
|
114
|
-
#
|
|
115
|
-
# This method constructs a screenshot assertion that encapsulates the comparison logic.
|
|
116
|
-
# The actual comparison is deferred until {ScreenshotAssertion#validate!} is called.
|
|
117
|
-
#
|
|
118
|
-
# @param name [String] The full name of the screenshot, including any section/group context.
|
|
119
|
-
# @param options [Hash] Options for screenshot taking and comparison.
|
|
120
|
-
# See {#assert_matches_screenshot} for available options.
|
|
121
|
-
# @param skip_stack_frames [Integer] Number of stack frames to skip for error reporting.
|
|
122
|
-
# @return [ScreenshotAssertion, nil] The assertion object or nil if no assertion is needed.
|
|
123
|
-
# @see ScreenshotAssertion
|
|
124
|
-
def build_screenshot_assertion(name, options, skip_stack_frames: 0)
|
|
125
|
-
Capybara::Screenshot::Diff::ScreenshotMatcher
|
|
126
|
-
.new(name, options)
|
|
127
|
-
.build_screenshot_assertion(skip_stack_frames: skip_stack_frames + 1)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def screenshot_namer
|
|
131
|
-
CapybaraScreenshotDiff.screenshot_namer
|
|
132
|
-
end
|
|
133
|
-
end
|
|
11
|
+
DSL = SnapDiff::DSL
|
|
134
12
|
end
|
|
@@ -1,32 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# so "/app/lib" does not also reject "/app/library".
|
|
10
|
-
@lib_directory = lib_directory.end_with?(File::SEPARATOR) ? lib_directory : lib_directory + File::SEPARATOR
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
# Filters out any backtrace lines originating from the library directory or from gems such as ActiveSupport, Minitest, and Railties
|
|
14
|
-
# @param backtrace [Array<String>]
|
|
15
|
-
# @return [Array<String>]
|
|
16
|
-
def filtered(backtrace)
|
|
17
|
-
backtrace
|
|
18
|
-
.reject { |location| File.expand_path(location).start_with?(@lib_directory) }
|
|
19
|
-
.reject { |l| l =~ /gems\/(activesupport|minitest|railties)/ }
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# @private
|
|
24
|
-
class ErrorWithFilteredBacktrace < StandardError
|
|
25
|
-
# @private
|
|
26
|
-
def initialize(message = nil, backtrace = [])
|
|
27
|
-
super(message)
|
|
28
|
-
filter = BacktraceFilter.new
|
|
29
|
-
set_backtrace(filter.filtered(backtrace))
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::BacktraceFilter and
|
|
4
|
+
# CapybaraScreenshotDiff::ErrorWithFilteredBacktrace now resolve lazily via
|
|
5
|
+
# snap_diff/legacy_shims' const_missing, with deprecation warnings pointing
|
|
6
|
+
# at their SnapDiff:: replacements.
|
|
7
|
+
require "snap_diff/error_with_filtered_backtrace"
|
|
8
|
+
require "snap_diff/legacy_shims"
|
|
@@ -1,64 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# Legacy entry point: pre-move, this path transitively loaded the whole gem
|
|
4
|
+
# (via dsl -> umbrella). Preserve that contract for consumers who require
|
|
5
|
+
# only this file.
|
|
6
|
+
require "capybara_screenshot_diff"
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
path.include?("capybara-screenshot-diff.rb") || path.include?("capybara/screenshot/diff.rb")
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
if used_deprecated_entrypoint
|
|
11
|
-
warn <<~MSG
|
|
12
|
-
[DEPRECATION] The default activation of `capybara_screenshot_diff/minitest` will be removed.
|
|
13
|
-
Please `require "capybara_screenshot_diff/minitest"` explicitly.
|
|
14
|
-
MSG
|
|
15
|
-
end
|
|
8
|
+
require "snap_diff/integrations/minitest"
|
|
16
9
|
|
|
17
10
|
module CapybaraScreenshotDiff
|
|
18
11
|
module Minitest
|
|
19
|
-
|
|
20
|
-
include ::CapybaraScreenshotDiff::DSL
|
|
21
|
-
|
|
22
|
-
def assert_matches_screenshot(*args, skip_stack_frames: 0, **opts)
|
|
23
|
-
self.assertions += 1
|
|
24
|
-
|
|
25
|
-
super(*args, skip_stack_frames: skip_stack_frames + 1, **opts)
|
|
26
|
-
rescue ::CapybaraScreenshotDiff::ExpectationNotMet => e
|
|
27
|
-
raise ::Minitest::Assertion, e.message
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def setup
|
|
31
|
-
super
|
|
32
|
-
::Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def before_teardown
|
|
36
|
-
super
|
|
37
|
-
CapybaraScreenshotDiff.verify
|
|
38
|
-
|
|
39
|
-
# Computed here (before teardown/reset), but the actual `skip` is
|
|
40
|
-
# deferred to `after_teardown` so a real error raised by the user's
|
|
41
|
-
# `teardown` isn't masked by a pending skip recorded before it ran.
|
|
42
|
-
@capybara_screenshot_diff_pending_message = CapybaraScreenshotDiff.pending_screenshots_message
|
|
43
|
-
rescue CapybaraScreenshotDiff::ExpectationNotMet => e
|
|
44
|
-
assertion = ::Minitest::Assertion.new(e)
|
|
45
|
-
assertion.set_backtrace(e.backtrace)
|
|
46
|
-
failures << assertion
|
|
47
|
-
ensure
|
|
48
|
-
CapybaraScreenshotDiff.reset
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def after_teardown
|
|
52
|
-
super
|
|
53
|
-
|
|
54
|
-
# Never mask a real failure (from `verify` above or from the user's
|
|
55
|
-
# own `teardown`) with a pending marker.
|
|
56
|
-
if failures.empty? && (msg = @capybara_screenshot_diff_pending_message)
|
|
57
|
-
skip(msg)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
12
|
+
Assertions = SnapDiff::Minitest::Assertions
|
|
61
13
|
end
|
|
62
14
|
end
|
|
63
|
-
|
|
64
|
-
::Minitest.after_run { CapybaraScreenshotDiff.finalize_reporters! } if ::Minitest.respond_to?(:after_run)
|