-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathscreenshot_assertion.rb
More file actions
165 lines (127 loc) · 4.79 KB
/
Copy pathscreenshot_assertion.rb
File metadata and controls
165 lines (127 loc) · 4.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# frozen_string_literal: true
require "fileutils"
module CapybaraScreenshotDiff
class ScreenshotAssertion
attr_reader :name, :args
attr_accessor :compare, :caller
def initialize(name, **args)
@name = name
@args = args
end
def validate
return unless compare
self.class.assert_image_not_changed(caller, name, compare)
end
def validate!
error_msg = validate
if error_msg
raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg, caller)
end
end
# Verifies that all scheduled screenshots do not show any unintended differences.
#
# @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
# @return [Array, nil] Returns an array of error messages if there are screenshot differences, otherwise nil.
# @note This method is typically called at the end of a test to assert all screenshots are as expected.
def self.verify_screenshots!(screenshots)
return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
test_screenshot_errors = screenshots.map do |assertion|
assertion.validate
end
test_screenshot_errors.compact!
test_screenshot_errors.empty? ? nil : test_screenshot_errors
end
# Asserts that an image has not changed compared to its baseline.
#
# @param backtrace [Array(String)] The caller context, used for error reporting.
# @param name [String] The name of the screenshot being verified.
# @param comparison [Object] The comparison object containing the result and details of the comparison.
# @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
# @note This method is used internally to verify individual screenshots.
def self.assert_image_not_changed(backtrace, name, comparison)
result = comparison.different?
# Cleanup after comparisons
if !result && comparison.base_image_path.exist?
FileUtils.mv(comparison.base_image_path, comparison.image_path, force: true)
end
return unless result
"Screenshot does not match for '#{name}': #{comparison.error_message}\n#{backtrace.join("\n")}"
end
end
class AssertionRegistry
attr_reader :assertions, :screenshot_namer
def initialize
@assertions = []
@screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
end
def add_assertion(assertion)
return unless assertion&.compare
@assertions.push(assertion)
assertion
end
def assertions_present?
!@assertions.empty?
end
def verify(screenshots = CapybaraScreenshotDiff.assertions)
return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
failed_assertions = CapybaraScreenshotDiff.registry.failed_assertions
failed_screenshot = failed_assertions.first
result = ScreenshotAssertion.verify_screenshots!(screenshots)
if result
raise CapybaraScreenshotDiff::ExpectationNotMet.new(result.join("\n\n"), failed_screenshot.caller)
end
end
def failed_assertions
assertions.filter { |screenshot_assert| screenshot_assert.compare&.different? }
end
def reset
@assertions.clear
@screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
end
end
end
module CapybaraScreenshotDiff
class << self
require "forwardable"
extend Forwardable
def registry
Thread.current[:capybara_screenshot_diff_registry] ||= AssertionRegistry.new
end
def_delegator :registry, :add_assertion
def_delegator :registry, :assertions
def_delegator :registry, :assertions_present?
def_delegator :registry, :failed_assertions
def reset
notify_reporters(registry.assertions)
registry.reset
end
def reporters
@reporters ||= []
end
attr_reader :reporters_mutex
def finalize_reporters!
reporters_mutex.synchronize { reporters.dup }.each do |reporter|
reporter.finalize
if (msg = reporter.summary)
$stdout.puts msg
end
rescue => e
warn "[snap_diff] Reporter #{reporter.class} failed (#{e.class}: #{e.message})"
end
end
def_delegator :registry, :screenshot_namer
def_delegator :registry, :verify
private
def notify_reporters(assertions)
return if assertions.nil? || assertions.empty?
reporters_snapshot = reporters_mutex.synchronize { reporters.dup }
return if reporters_snapshot.empty?
reporters_snapshot.each do |reporter|
reporter.record(assertions)
rescue => e
warn "[capybara-screenshot-diff] Reporter failed: #{e.message}"
end
end
end
@reporters_mutex = Mutex.new
end