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,137 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require "
|
|
7
|
-
require "
|
|
8
|
-
|
|
9
|
-
module CapybaraScreenshotDiff
|
|
10
|
-
module Reporters
|
|
11
|
-
class HTML
|
|
12
|
-
attr_reader :failures, :total
|
|
13
|
-
|
|
14
|
-
def initialize(output_path: nil, embed_images: false)
|
|
15
|
-
@explicit_output_path = output_path
|
|
16
|
-
@embed_images = embed_images
|
|
17
|
-
@failures = []
|
|
18
|
-
@total = 0
|
|
19
|
-
@finalized = false
|
|
20
|
-
@mutex = Mutex.new
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def record(assertions)
|
|
24
|
-
return if @finalized
|
|
25
|
-
|
|
26
|
-
failures = []
|
|
27
|
-
total = 0
|
|
28
|
-
|
|
29
|
-
assertions.each do |assertion|
|
|
30
|
-
compare = assertion.compare
|
|
31
|
-
next unless compare
|
|
32
|
-
|
|
33
|
-
total += 1
|
|
34
|
-
next unless compare.difference&.different?
|
|
35
|
-
|
|
36
|
-
failures << failure_entry_for(assertion.name, compare)
|
|
37
|
-
rescue => e
|
|
38
|
-
warn "[snap_diff] Reporter skipped '#{assertion.name}': #{e.message}" if ENV["DEBUG"]
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
@mutex.synchronize do
|
|
42
|
-
return if @finalized
|
|
43
|
-
@total += total
|
|
44
|
-
@failures.concat(failures)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def finalize
|
|
49
|
-
@mutex.synchronize do
|
|
50
|
-
return if @finalized
|
|
51
|
-
return if failures.empty?
|
|
52
|
-
|
|
53
|
-
write_report
|
|
54
|
-
@finalized = true
|
|
55
|
-
output_path
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def output_path
|
|
60
|
-
@output_path ||= Pathname.new(@explicit_output_path || self.class.default_output_path)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def passed = total - failures.size
|
|
64
|
-
def failed = failures.size
|
|
65
|
-
|
|
66
|
-
def summary
|
|
67
|
-
return if total.zero?
|
|
68
|
-
|
|
69
|
-
screenshots_label = (total == 1) ? "1 screenshot" : "#{total} screenshots"
|
|
70
|
-
|
|
71
|
-
if failures.empty?
|
|
72
|
-
"[snap_diff] #{screenshots_label} compared, no failures."
|
|
73
|
-
else
|
|
74
|
-
failures_label = (failures.size == 1) ? "1 failure" : "#{failures.size} failures"
|
|
75
|
-
"[snap_diff] #{screenshots_label} compared, #{failures_label}. Report: #{output_path}"
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def render
|
|
80
|
-
ERB.new(File.read(self.class.template_path)).result(binding)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def self.template_path
|
|
84
|
-
File.expand_path("templates/report.html.erb", __dir__)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def self.default_output_path
|
|
88
|
-
root = Capybara::Screenshot.root || Pathname.pwd
|
|
89
|
-
root / Capybara::Screenshot.save_path / "snap_diff_report.html"
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
private
|
|
93
|
-
|
|
94
|
-
def failure_entry_for(name, compare)
|
|
95
|
-
difference = compare.difference
|
|
96
|
-
{
|
|
97
|
-
name: name,
|
|
98
|
-
original: resolve_image(compare.base_image_path),
|
|
99
|
-
new: resolve_image(compare.image_path),
|
|
100
|
-
base_diff: resolve_image(compare.reporter.annotated_base_image_path),
|
|
101
|
-
diff: resolve_image(compare.reporter.annotated_image_path),
|
|
102
|
-
heatmap: resolve_image(compare.reporter.heatmap_diff_path),
|
|
103
|
-
diff_level: difference.ratio && (difference.ratio * 100).round(2),
|
|
104
|
-
area_size: difference.region_area_size,
|
|
105
|
-
max_color_distance: difference.meta[:max_color_distance]&.round(1)
|
|
106
|
-
}
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def resolve_image(path)
|
|
110
|
-
return unless path
|
|
111
|
-
|
|
112
|
-
pathname = Pathname.new(path).expand_path
|
|
113
|
-
return unless pathname.exist?
|
|
114
|
-
|
|
115
|
-
@embed_images ? data_uri(pathname) : pathname.relative_path_from(output_path.dirname.expand_path).to_s
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def data_uri(pathname)
|
|
119
|
-
ext = pathname.extname.delete_prefix(".")
|
|
120
|
-
mime = (ext == "webp") ? "image/webp" : "image/png"
|
|
121
|
-
"data:#{mime};base64,#{Base64.strict_encode64(pathname.binread)}"
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def write_report
|
|
125
|
-
FileUtils.mkdir_p(output_path.dirname)
|
|
126
|
-
File.write(output_path, render)
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# Auto-register reporter.
|
|
133
|
-
# Framework adapters (Minitest, RSpec, Cucumber) call finalize_reporters! via native hooks.
|
|
134
|
-
# For custom frameworks, call CapybaraScreenshotDiff.finalize_reporters! manually.
|
|
135
|
-
unless CapybaraScreenshotDiff.reporters.any?(CapybaraScreenshotDiff::Reporters::HTML)
|
|
136
|
-
CapybaraScreenshotDiff.reporters << CapybaraScreenshotDiff::Reporters::HTML.new(embed_images: !!ENV["CI"])
|
|
137
|
-
end
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::Reporters::HTML now
|
|
4
|
+
# resolves lazily via snap_diff/legacy_shims' const_missing, with a
|
|
5
|
+
# deprecation warning pointing at SnapDiff::Reporters::HTML.
|
|
6
|
+
require "snap_diff/reporters/html"
|
|
7
|
+
require "snap_diff/legacy_shims"
|
|
@@ -1,66 +1,8 @@
|
|
|
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
|
-
description { "match screenshot '#{name}'" }
|
|
8
|
-
|
|
9
|
-
match do |_page|
|
|
10
|
-
assert_matches_screenshot(name, **options)
|
|
11
|
-
true
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
failure_message do
|
|
15
|
-
"Expected page to match screenshot '#{name}'"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
failure_message_when_negated do
|
|
19
|
-
"Expected page not to match screenshot '#{name}'"
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
RSpec.configure do |config|
|
|
24
|
-
config.include CapybaraScreenshotDiff::DSL, type: :feature
|
|
25
|
-
config.include CapybaraScreenshotDiff::DSL, type: :system
|
|
26
|
-
|
|
27
|
-
config.before do
|
|
28
|
-
if self.class.include?(CapybaraScreenshotDiff::DSL)
|
|
29
|
-
Capybara::Screenshot::BrowserHelpers.resize_window_if_needed
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# `append_after` (as opposed to the default `after`, which prepends) adds
|
|
34
|
-
# this hook to the *end* of the after-hook chain regardless of when it's
|
|
35
|
-
# registered relative to the user's own `after`/`config.after` hooks. RSpec
|
|
36
|
-
# runs `after(:each)` hooks in reverse registration order, so a plain
|
|
37
|
-
# `config.after` here would run BEFORE a user hook registered earlier in
|
|
38
|
-
# their own spec_helper (before this file was required) -- and if that
|
|
39
|
-
# later-running user hook raises, its exception gets folded into
|
|
40
|
-
# `pending_exception` instead of `example.exception`, silently masking the
|
|
41
|
-
# failure behind our pending skip. `append_after` runs after the full user
|
|
42
|
-
# after-chain no matter the registration order, closing that gap.
|
|
43
|
-
config.append_after do |example|
|
|
44
|
-
if self.class.include?(CapybaraScreenshotDiff::DSL)
|
|
45
|
-
begin
|
|
46
|
-
CapybaraScreenshotDiff.verify
|
|
47
|
-
|
|
48
|
-
# Never mask a real failure with a pending marker. Kept as
|
|
49
|
-
# defense-in-depth: `append_after` observes failures from plain
|
|
50
|
-
# `after`/`prepend_after` user hooks, but appended hooks run FIFO,
|
|
51
|
-
# so a user `append_after` registered after this gem still runs
|
|
52
|
-
# later than us — RSpec has no "run absolutely last" construct.
|
|
53
|
-
# Mitigation for such consumers: require this gem last.
|
|
54
|
-
if example.exception.nil? && (msg = CapybaraScreenshotDiff.pending_screenshots_message)
|
|
55
|
-
skip(msg)
|
|
56
|
-
end
|
|
57
|
-
rescue CapybaraScreenshotDiff::ExpectationNotMet => e
|
|
58
|
-
raise RSpec::Expectations::ExpectationNotMetError.new(e.message).tap { |ex| ex.set_backtrace(e.backtrace) }
|
|
59
|
-
ensure
|
|
60
|
-
CapybaraScreenshotDiff.reset
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
config.after(:suite) { CapybaraScreenshotDiff.finalize_reporters! }
|
|
66
|
-
end
|
|
8
|
+
require "snap_diff/integrations/rspec"
|
|
@@ -1,163 +1,42 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def validate!
|
|
22
|
-
error_msg = validate
|
|
23
|
-
|
|
24
|
-
if error_msg
|
|
25
|
-
raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg, caller)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Verifies that all scheduled screenshots do not show any unintended differences.
|
|
30
|
-
#
|
|
31
|
-
# @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
|
|
32
|
-
# @return [Array, nil] Returns an array of error messages if there are screenshot differences, otherwise nil.
|
|
33
|
-
# @note This method is typically called at the end of a test to assert all screenshots are as expected.
|
|
34
|
-
def self.verify_screenshots!(screenshots)
|
|
35
|
-
return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
|
|
36
|
-
|
|
37
|
-
test_screenshot_errors = screenshots.map do |assertion|
|
|
38
|
-
assertion.validate
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
test_screenshot_errors.compact!
|
|
42
|
-
|
|
43
|
-
test_screenshot_errors.empty? ? nil : test_screenshot_errors
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Asserts that an image has not changed compared to its baseline.
|
|
47
|
-
#
|
|
48
|
-
# @param backtrace [Array(String)] The caller context, used for error reporting.
|
|
49
|
-
# @param name [String] The name of the screenshot being verified.
|
|
50
|
-
# @param comparison [Object] The comparison object containing the result and details of the comparison.
|
|
51
|
-
# @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
|
|
52
|
-
# @note This method is used internally to verify individual screenshots.
|
|
53
|
-
def self.assert_image_not_changed(backtrace, name, comparison)
|
|
54
|
-
result = comparison.different?
|
|
55
|
-
|
|
56
|
-
# Cleanup after comparisons
|
|
57
|
-
if !result && comparison.base_image_path.exist?
|
|
58
|
-
FileUtils.mv(comparison.base_image_path, comparison.image_path, force: true)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
return unless result
|
|
62
|
-
|
|
63
|
-
"Screenshot does not match for '#{name}': #{comparison.error_message}\n#{backtrace.join("\n")}"
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
class AssertionRegistry
|
|
68
|
-
attr_reader :assertions, :screenshot_namer, :new_screenshots
|
|
69
|
-
|
|
70
|
-
def initialize
|
|
71
|
-
@assertions = []
|
|
72
|
-
@new_screenshots = []
|
|
73
|
-
@screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def add_assertion(assertion)
|
|
77
|
-
return unless assertion&.compare
|
|
78
|
-
|
|
79
|
-
@assertions.push(assertion)
|
|
80
|
-
|
|
81
|
-
assertion
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def assertions_present?
|
|
85
|
-
!@assertions.empty?
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def record_new_screenshot(name)
|
|
89
|
-
@new_screenshots.push(name)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def new_screenshots_present?
|
|
93
|
-
!@new_screenshots.empty?
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def verify(screenshots = CapybaraScreenshotDiff.assertions)
|
|
97
|
-
return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
|
|
98
|
-
|
|
99
|
-
failed_assertions = CapybaraScreenshotDiff.registry.failed_assertions
|
|
100
|
-
failed_screenshot = failed_assertions.first
|
|
101
|
-
result = ScreenshotAssertion.verify_screenshots!(screenshots)
|
|
102
|
-
|
|
103
|
-
if result
|
|
104
|
-
raise CapybaraScreenshotDiff::ExpectationNotMet.new(result.join("\n\n"), failed_screenshot.caller)
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def failed_assertions
|
|
109
|
-
assertions.filter { |screenshot_assert| screenshot_assert.compare&.different? }
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def reset
|
|
113
|
-
@assertions.clear
|
|
114
|
-
@new_screenshots.clear
|
|
115
|
-
@screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
3
|
+
require "snap_diff/screenshot_assertion"
|
|
4
|
+
require "snap_diff/reporting"
|
|
5
|
+
# ScreenshotAssertion / AssertionRegistry forward lazily (with deprecation
|
|
6
|
+
# warnings) via snap_diff/legacy_shims' const_missing since v2 step 6.
|
|
7
|
+
require "snap_diff/legacy_shims"
|
|
8
|
+
|
|
9
|
+
# CapybaraScreenshotDiff's module methods cover two lifecycles with
|
|
10
|
+
# different scopes, kept separate below:
|
|
11
|
+
#
|
|
12
|
+
# - Session lifecycle: thread-local, per-test. The AssertionRegistry holds
|
|
13
|
+
# the assertions and screenshot names of the test running on this thread;
|
|
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.
|
|
120
20
|
module CapybaraScreenshotDiff
|
|
121
21
|
class << self
|
|
122
22
|
require "forwardable"
|
|
123
23
|
extend Forwardable
|
|
124
24
|
|
|
25
|
+
# --- Session lifecycle (thread-local, per-test) ---
|
|
26
|
+
|
|
125
27
|
def registry
|
|
126
|
-
Thread.current[:capybara_screenshot_diff_registry] ||= AssertionRegistry.new
|
|
28
|
+
Thread.current[:capybara_screenshot_diff_registry] ||= SnapDiff::AssertionRegistry.new
|
|
127
29
|
end
|
|
128
30
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def_delegator :registry, :record_new_screenshot
|
|
134
|
-
def_delegator :registry, :new_screenshots
|
|
135
|
-
def_delegator :registry, :new_screenshots_present?
|
|
31
|
+
def_delegators :registry, :add_assertion, :assertions, :assertions_present?,
|
|
32
|
+
:failed_assertions, :record_new_screenshot, :new_screenshots,
|
|
33
|
+
:new_screenshots_present?, :screenshot_namer, :verify
|
|
34
|
+
|
|
136
35
|
def reset
|
|
137
36
|
notify_reporters(registry.assertions)
|
|
138
37
|
registry.reset
|
|
139
38
|
end
|
|
140
39
|
|
|
141
|
-
def reporters
|
|
142
|
-
@reporters ||= []
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
attr_reader :reporters_mutex
|
|
146
|
-
|
|
147
|
-
def finalize_reporters!
|
|
148
|
-
reporters_mutex.synchronize { reporters.dup }.each do |reporter|
|
|
149
|
-
reporter.finalize
|
|
150
|
-
if (msg = reporter.summary)
|
|
151
|
-
$stdout.puts msg
|
|
152
|
-
end
|
|
153
|
-
rescue => e
|
|
154
|
-
warn "[snap_diff] Reporter #{reporter.class} failed (#{e.class}: #{e.message})"
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def_delegator :registry, :screenshot_namer
|
|
159
|
-
def_delegator :registry, :verify
|
|
160
|
-
|
|
161
40
|
# Message to skip the test with when a new screenshot has no baseline yet
|
|
162
41
|
# and `pending_if_new` is enabled. Adapters call this after verifying
|
|
163
42
|
# screenshots, and skip the test with the returned message when present.
|
|
@@ -169,21 +48,24 @@ module CapybaraScreenshotDiff
|
|
|
169
48
|
"No baseline for: #{new_screenshots.join(", ")}. Commit the captured screenshots to record them."
|
|
170
49
|
end
|
|
171
50
|
|
|
172
|
-
|
|
51
|
+
# --- Reporter lifecycle (process-global, suite-long) ---
|
|
173
52
|
|
|
174
|
-
def
|
|
175
|
-
|
|
53
|
+
def reporters
|
|
54
|
+
SnapDiff::Reporting.reporters
|
|
55
|
+
end
|
|
176
56
|
|
|
177
|
-
|
|
178
|
-
|
|
57
|
+
def reporters_mutex
|
|
58
|
+
SnapDiff::Reporting.mutex
|
|
59
|
+
end
|
|
179
60
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
rescue => e
|
|
183
|
-
warn "[capybara-screenshot-diff] Reporter failed: #{e.message}"
|
|
184
|
-
end
|
|
61
|
+
def finalize_reporters!
|
|
62
|
+
SnapDiff::Reporting.finalize!
|
|
185
63
|
end
|
|
186
|
-
end
|
|
187
64
|
|
|
188
|
-
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def notify_reporters(assertions)
|
|
68
|
+
SnapDiff::Reporting.notify(assertions)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
189
71
|
end
|
|
@@ -1,81 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# This class encapsulates logic related to screenshot sections, groups,
|
|
9
|
-
# and counters, providing a centralized way to determine screenshot filenames
|
|
10
|
-
# and directories.
|
|
11
|
-
class ScreenshotNamer
|
|
12
|
-
attr_reader :section, :group
|
|
13
|
-
|
|
14
|
-
def initialize(screenshot_area = nil)
|
|
15
|
-
@section = nil
|
|
16
|
-
@group = nil
|
|
17
|
-
@counter = nil
|
|
18
|
-
@screenshot_area = screenshot_area
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def screenshot_area
|
|
22
|
-
@screenshot_area ||= Capybara::Screenshot.screenshot_area
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Sets the current section for screenshots.
|
|
26
|
-
# @param name [String, nil] The name of the section.
|
|
27
|
-
def section=(name)
|
|
28
|
-
@section = name&.to_s
|
|
29
|
-
reset_group_counter
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Sets the current group for screenshots and resets the counter.
|
|
33
|
-
# @param name [String, nil] The name of the group.
|
|
34
|
-
def group=(name)
|
|
35
|
-
@group = name&.to_s
|
|
36
|
-
reset_group_counter
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Builds the full, unique name for a screenshot, including any counter.
|
|
40
|
-
# @param base_name [String] The base name for the screenshot.
|
|
41
|
-
# @return [String] The full screenshot name.
|
|
42
|
-
def full_name(base_name)
|
|
43
|
-
name = base_name.to_s
|
|
44
|
-
|
|
45
|
-
if @counter
|
|
46
|
-
name = format("%02i_%s", @counter, name)
|
|
47
|
-
@counter += 1
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
File.join(*directory_parts.push(name.to_s))
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Builds the full path for a screenshot file, including section and group directories.
|
|
54
|
-
# @param base_name [String] The base name for the screenshot.
|
|
55
|
-
# @return [String] The absolute path for the screenshot file.
|
|
56
|
-
def full_name_with_path(base_name)
|
|
57
|
-
File.join(screenshot_area, full_name(base_name))
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Returns the directory parts (section and group) for constructing paths.
|
|
61
|
-
# @return [Array<String>] An array of directory names.
|
|
62
|
-
def directory_parts
|
|
63
|
-
parts = []
|
|
64
|
-
parts << @section unless @section.nil? || @section.empty?
|
|
65
|
-
parts << @group unless @group.nil? || @group.empty?
|
|
66
|
-
parts
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Calculates the directory path for the current section and group.
|
|
70
|
-
# @return [String] The full path to the directory.
|
|
71
|
-
def current_group_directory
|
|
72
|
-
File.join(*([screenshot_area] + directory_parts))
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
private
|
|
76
|
-
|
|
77
|
-
def reset_group_counter
|
|
78
|
-
@counter = (@group.nil? || @group.empty?) ? nil : 0
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::ScreenshotNamer now
|
|
4
|
+
# resolves lazily via snap_diff/legacy_shims' const_missing, with a
|
|
5
|
+
# deprecation warning pointing at SnapDiff::ScreenshotNamer.
|
|
6
|
+
require "snap_diff/screenshot_namer"
|
|
7
|
+
require "snap_diff/legacy_shims"
|
|
@@ -1,66 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@full_name = full_name
|
|
9
|
-
@format = format
|
|
10
|
-
@path = manager.abs_path_for(Pathname.new(@full_name).sub_ext(".#{@format}"))
|
|
11
|
-
@base_path = @path.sub_ext(".base.#{@format}")
|
|
12
|
-
@manager = manager
|
|
13
|
-
@attempts_count = 0
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def delete!
|
|
17
|
-
path.delete if path.exist?
|
|
18
|
-
base_path.delete if base_path.exist?
|
|
19
|
-
cleanup_diff_artifacts!
|
|
20
|
-
cleanup_attempts!
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def checkout_base_screenshot
|
|
24
|
-
@manager.checkout_file(path, base_path)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def path_for(version = :actual)
|
|
28
|
-
case version
|
|
29
|
-
when :base
|
|
30
|
-
base_path
|
|
31
|
-
else
|
|
32
|
-
path
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def next_attempt_path!
|
|
37
|
-
@prev_attempt_path = @attempt_path
|
|
38
|
-
@attempt_path = path.sub_ext(sprintf(".attempt_%02i.#{format}", @attempts_count))
|
|
39
|
-
ensure
|
|
40
|
-
@attempts_count += 1
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def commit_last_attempt
|
|
44
|
-
@manager.move(attempt_path, path)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def cleanup_attempts!
|
|
48
|
-
@manager.cleanup_attempts!(self)
|
|
49
|
-
@attempts_count = 0
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def find_attempts_paths
|
|
53
|
-
Dir[@manager.abs_path_for("**/#{full_name}.attempt_[0-9][0-9].#{format}")]
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
def cleanup_diff_artifacts!
|
|
59
|
-
[
|
|
60
|
-
path.sub_ext(".diff.#{format}"),
|
|
61
|
-
path.sub_ext(".heatmap.diff.#{format}"),
|
|
62
|
-
base_path.sub_ext(".diff.#{format}")
|
|
63
|
-
].each { |f| f.unlink if f.exist? }
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::Snap now resolves
|
|
4
|
+
# lazily via snap_diff/legacy_shims' const_missing, with a deprecation
|
|
5
|
+
# warning pointing at SnapDiff::Snap.
|
|
6
|
+
require "snap_diff/snap"
|
|
7
|
+
require "snap_diff/legacy_shims"
|
|
@@ -1,84 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require "
|
|
7
|
-
|
|
8
|
-
module CapybaraScreenshotDiff
|
|
9
|
-
class SnapManager
|
|
10
|
-
attr_reader :root
|
|
11
|
-
|
|
12
|
-
def initialize(root)
|
|
13
|
-
@root = Pathname.new(root)
|
|
14
|
-
@snapshots = Set.new
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def snapshot(screenshot_full_name, screenshot_format = "png")
|
|
18
|
-
Snap.new(screenshot_full_name, screenshot_format, manager: self).tap do |snapshot|
|
|
19
|
-
@snapshots << snapshot
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.snapshot(screenshot_full_name, screenshot_format = "png")
|
|
24
|
-
instance.snapshot(screenshot_full_name, screenshot_format)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def abs_path_for(relative_path)
|
|
28
|
-
@root / relative_path
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def checkout_file(path, as_path)
|
|
32
|
-
create_output_directory_for(as_path) unless as_path.exist?
|
|
33
|
-
Capybara::Screenshot::Diff::Vcs.checkout_vcs(root, path, as_path)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def provision_snap_with(snap, path, version: :actual)
|
|
37
|
-
managed_path = snap.path_for(version)
|
|
38
|
-
create_output_directory_for(managed_path) unless managed_path.exist?
|
|
39
|
-
FileUtils.cp(path, managed_path)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def create_output_directory_for(path = nil)
|
|
43
|
-
path ? path.dirname.mkpath : root.mkpath
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# TODO: rename to delete!
|
|
47
|
-
def cleanup!
|
|
48
|
-
snapshots.each do |snapshot|
|
|
49
|
-
cleanup_attempts!(snapshot)
|
|
50
|
-
snapshot.delete!
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def self.cleanup!
|
|
55
|
-
instance.cleanup!
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def cleanup_attempts!(snapshot)
|
|
59
|
-
FileUtils.rm_rf snapshot.find_attempts_paths, secure: true
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def move(new_screenshot_path, screenshot_path)
|
|
63
|
-
FileUtils.mv(new_screenshot_path, screenshot_path, force: true)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def screenshots
|
|
67
|
-
root.children.map { |f| f.basename.to_s }
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
attr_reader :snapshots
|
|
71
|
-
|
|
72
|
-
def self.screenshots
|
|
73
|
-
instance.screenshots
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def self.root
|
|
77
|
-
instance.root
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def self.instance
|
|
81
|
-
Capybara::Screenshot::Diff.manager.new(Capybara::Screenshot.screenshot_area_abs)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): CapybaraScreenshotDiff::SnapManager now
|
|
4
|
+
# resolves lazily via snap_diff/legacy_shims' const_missing, with a
|
|
5
|
+
# deprecation warning pointing at SnapDiff::SnapManager.
|
|
6
|
+
require "snap_diff/snap_manager"
|
|
7
|
+
require "snap_diff/legacy_shims"
|