capybara-screenshot-diff 1.8.3 → 1.10.2
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/Rakefile +1 -11
- data/capybara-screenshot-diff.gemspec +3 -4
- data/gems.rb +11 -8
- data/lib/capybara/screenshot/diff/area_calculator.rb +56 -0
- data/lib/capybara/screenshot/diff/browser_helpers.rb +5 -5
- data/lib/capybara/screenshot/diff/comparison.rb +6 -0
- data/lib/capybara/screenshot/diff/cucumber.rb +1 -9
- data/lib/capybara/screenshot/diff/difference.rb +8 -4
- data/lib/capybara/screenshot/diff/drivers/base_driver.rb +0 -5
- data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +10 -5
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +14 -3
- data/lib/capybara/screenshot/diff/drivers.rb +1 -1
- data/lib/capybara/screenshot/diff/image_compare.rb +84 -114
- data/lib/capybara/screenshot/diff/region.rb +28 -7
- data/lib/capybara/screenshot/diff/reporters/default.rb +121 -0
- data/lib/capybara/screenshot/diff/screenshot_matcher.rb +36 -74
- data/lib/capybara/screenshot/diff/screenshoter.rb +46 -54
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +65 -63
- data/lib/capybara/screenshot/diff/test_methods.rb +78 -10
- data/lib/capybara/screenshot/diff/{drivers/utils.rb → utils.rb} +0 -7
- data/lib/capybara/screenshot/diff/vcs.rb +25 -23
- data/lib/capybara/screenshot/diff/version.rb +1 -1
- data/lib/capybara/screenshot/diff.rb +1 -111
- data/lib/capybara-screenshot-diff.rb +1 -1
- data/lib/capybara_screenshot_diff/attempts_reporter.rb +49 -0
- data/lib/capybara_screenshot_diff/cucumber.rb +12 -0
- data/lib/capybara_screenshot_diff/dsl.rb +11 -0
- data/lib/capybara_screenshot_diff/minitest.rb +49 -0
- data/lib/capybara_screenshot_diff/rspec.rb +32 -0
- data/lib/capybara_screenshot_diff/snap.rb +55 -0
- data/lib/capybara_screenshot_diff/snap_manager.rb +76 -0
- data/lib/capybara_screenshot_diff.rb +86 -0
- metadata +20 -42
- data/lib/capybara/screenshot/diff/stabilization.rb +0 -0
- data/sig/capybara/screenshot/diff/diff.rbs +0 -28
- data/sig/capybara/screenshot/diff/difference.rbs +0 -33
- data/sig/capybara/screenshot/diff/drivers/base_driver.rbs +0 -63
- data/sig/capybara/screenshot/diff/drivers/browser_helpers.rbs +0 -36
- data/sig/capybara/screenshot/diff/drivers/chunky_png_driver.rbs +0 -89
- data/sig/capybara/screenshot/diff/drivers/utils.rbs +0 -13
- data/sig/capybara/screenshot/diff/drivers/vips_driver.rbs +0 -25
- data/sig/capybara/screenshot/diff/image_compare.rbs +0 -93
- data/sig/capybara/screenshot/diff/os.rbs +0 -11
- data/sig/capybara/screenshot/diff/region.rbs +0 -43
- data/sig/capybara/screenshot/diff/screenshot_matcher.rbs +0 -60
- data/sig/capybara/screenshot/diff/screenshoter.rbs +0 -48
- data/sig/capybara/screenshot/diff/stable_screenshoter.rbs +0 -29
- data/sig/capybara/screenshot/diff/test_methods.rbs +0 -39
- data/sig/capybara/screenshot/diff/vcs.rbs +0 -17
@@ -15,11 +15,20 @@ require_relative "region"
|
|
15
15
|
|
16
16
|
require_relative "screenshot_matcher"
|
17
17
|
|
18
|
-
#
|
18
|
+
# == Capybara::Screenshot::Diff::TestMethods
|
19
|
+
#
|
20
|
+
# This module provides methods for capturing screenshots and verifying them against
|
21
|
+
# baseline images to detect visual changes. It's designed to be included in test
|
22
|
+
# classes to add visual regression testing capabilities.
|
23
|
+
|
19
24
|
module Capybara
|
20
25
|
module Screenshot
|
21
26
|
module Diff
|
22
27
|
module TestMethods
|
28
|
+
# @!attribute [rw] test_screenshots
|
29
|
+
# @return [Array(Array(Array(String), String, ImageCompare | Minitest::Mock))] An array where each element is an array containing the caller context,
|
30
|
+
# the name of the screenshot, and the comparison object. This attribute stores information about each screenshot
|
31
|
+
# scheduled for comparison to ensure they do not show any unintended differences.
|
23
32
|
def initialize(*)
|
24
33
|
super
|
25
34
|
@screenshot_counter = nil
|
@@ -29,6 +38,29 @@ module Capybara
|
|
29
38
|
@test_screenshots = []
|
30
39
|
end
|
31
40
|
|
41
|
+
# Verifies that all scheduled screenshots do not show any unintended differences.
|
42
|
+
#
|
43
|
+
# @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
|
44
|
+
# @return [Array, nil] Returns an array of error messages if there are screenshot differences, otherwise nil.
|
45
|
+
# @note This method is typically called at the end of a test to assert all screenshots are as expected.
|
46
|
+
def verify_screenshots!(screenshots = @test_screenshots)
|
47
|
+
return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
|
48
|
+
|
49
|
+
test_screenshot_errors = screenshots.map do |caller, name, compare|
|
50
|
+
assert_image_not_changed(caller, name, compare)
|
51
|
+
end
|
52
|
+
|
53
|
+
test_screenshot_errors.compact!
|
54
|
+
|
55
|
+
test_screenshot_errors.presence
|
56
|
+
ensure
|
57
|
+
screenshots.clear
|
58
|
+
end
|
59
|
+
|
60
|
+
# Builds the full name for a screenshot, incorporating counters and group names for uniqueness.
|
61
|
+
#
|
62
|
+
# @param name [String] The base name for the screenshot.
|
63
|
+
# @return [String] The full, unique name for the screenshot.
|
32
64
|
def build_full_name(name)
|
33
65
|
if @screenshot_counter
|
34
66
|
name = format("%02i_#{name}", @screenshot_counter)
|
@@ -38,6 +70,9 @@ module Capybara
|
|
38
70
|
File.join(*group_parts.push(name.to_s))
|
39
71
|
end
|
40
72
|
|
73
|
+
# Determines the directory path for saving screenshots.
|
74
|
+
#
|
75
|
+
# @return [String] The full path to the directory where screenshots are saved.
|
41
76
|
def screenshot_dir
|
42
77
|
File.join(*([Screenshot.screenshot_area] + group_parts))
|
43
78
|
end
|
@@ -54,6 +89,13 @@ module Capybara
|
|
54
89
|
FileUtils.rm_rf screenshot_dir
|
55
90
|
end
|
56
91
|
|
92
|
+
# Schedules a screenshot comparison job for later execution.
|
93
|
+
#
|
94
|
+
# This method adds a job to the queue of screenshots to be matched. It's used when `Capybara::Screenshot::Diff.delayed`
|
95
|
+
# is set to true, allowing for batch processing of screenshot comparisons at a later point, typically at the end of a test.
|
96
|
+
#
|
97
|
+
# @param job [Array(Array(String), String, ImageCompare)] The job to be scheduled, consisting of the caller context, screenshot name, and comparison object.
|
98
|
+
# @return [Boolean] Always returns true, indicating the job was successfully scheduled.
|
57
99
|
def schedule_match_job(job)
|
58
100
|
(@test_screenshots ||= []) << job
|
59
101
|
true
|
@@ -66,45 +108,71 @@ module Capybara
|
|
66
108
|
parts
|
67
109
|
end
|
68
110
|
|
111
|
+
# Takes a screenshot and optionally compares it against a baseline image.
|
112
|
+
#
|
113
|
+
# @param name [String] The name of the screenshot, used to generate the filename.
|
114
|
+
# @param skip_stack_frames [Integer] The number of stack frames to skip when reporting errors, for cleaner error messages.
|
115
|
+
# @param options [Hash] Additional options for taking the screenshot, such as custom dimensions or selectors.
|
116
|
+
# @return [Boolean] Returns true if the screenshot was successfully captured and matches the baseline, false otherwise.
|
117
|
+
# @raise [CapybaraScreenshotDiff::ExpectationNotMet] If the screenshot does not match the baseline image and fail_if_new is set to true.
|
118
|
+
# @example Capture a full-page screenshot named 'login_page'
|
119
|
+
# screenshot('login_page', skip_stack_frames: 1, full: true)
|
69
120
|
def screenshot(name, skip_stack_frames: 0, **options)
|
70
121
|
return false unless Screenshot.active?
|
71
122
|
|
72
123
|
screenshot_full_name = build_full_name(name)
|
73
124
|
job = build_screenshot_matches_job(screenshot_full_name, options)
|
74
125
|
|
75
|
-
|
126
|
+
caller = caller(skip_stack_frames + 1).reject { |l| l =~ /gems\/(activesupport|minitest|railties)/ }
|
127
|
+
unless job
|
128
|
+
if Screenshot::Diff.fail_if_new
|
129
|
+
_raise_error(<<-ERROR.strip_heredoc, caller)
|
130
|
+
No existing screenshot found for #{screenshot_full_name}!
|
131
|
+
To stop seeing this error disable by `Capybara::Screenshot::Diff.fail_if_new=false`
|
132
|
+
ERROR
|
133
|
+
end
|
134
|
+
|
135
|
+
return false
|
136
|
+
end
|
76
137
|
|
77
|
-
job.prepend(caller
|
138
|
+
job.prepend(caller)
|
78
139
|
|
79
140
|
if Screenshot::Diff.delayed
|
80
141
|
schedule_match_job(job)
|
81
142
|
else
|
82
143
|
error_msg = assert_image_not_changed(*job)
|
83
|
-
if error_msg
|
84
|
-
error = ASSERTION.new(error_msg)
|
85
|
-
error.set_backtrace(caller(2))
|
86
|
-
raise error
|
87
|
-
end
|
144
|
+
_raise_error(error_msg, caller(2)) if error_msg
|
88
145
|
end
|
89
146
|
end
|
90
147
|
|
148
|
+
# Asserts that an image has not changed compared to its baseline.
|
149
|
+
#
|
150
|
+
# @param caller [Array(String)] The caller context, used for error reporting.
|
151
|
+
# @param name [String] The name of the screenshot being verified.
|
152
|
+
# @param comparison [Object] The comparison object containing the result and details of the comparison.
|
153
|
+
# @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
|
154
|
+
# @note This method is used internally to verify individual screenshots.
|
91
155
|
def assert_image_not_changed(caller, name, comparison)
|
92
156
|
result = comparison.different?
|
93
157
|
|
94
158
|
# Cleanup after comparisons
|
95
159
|
if !result && comparison.base_image_path.exist?
|
96
160
|
FileUtils.mv(comparison.base_image_path, comparison.image_path, force: true)
|
97
|
-
|
161
|
+
elsif !comparison.dimensions_changed?
|
98
162
|
FileUtils.rm_rf(comparison.base_image_path)
|
99
163
|
end
|
100
164
|
|
101
165
|
return unless result
|
102
166
|
|
103
|
-
"Screenshot does not match for '#{name}' #{comparison.error_message}\n#{caller}"
|
167
|
+
"Screenshot does not match for '#{name}': #{comparison.error_message}\n#{caller.join("\n")}"
|
104
168
|
end
|
105
169
|
|
106
170
|
private
|
107
171
|
|
172
|
+
def _raise_error(error_msg, backtrace)
|
173
|
+
raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg).tap { _1.set_backtrace(backtrace) }
|
174
|
+
end
|
175
|
+
|
108
176
|
def build_screenshot_matches_job(screenshot_full_name, options)
|
109
177
|
ScreenshotMatcher
|
110
178
|
.new(screenshot_full_name, options)
|
@@ -36,13 +36,6 @@ module Capybara
|
|
36
36
|
fail "Wrong adapter #{driver.inspect}. Available adapters: #{AVAILABLE_DRIVERS.inspect}"
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
40
|
-
def self.detect_test_framework_assert
|
41
|
-
require "minitest"
|
42
|
-
::Minitest::Assertion
|
43
|
-
rescue
|
44
|
-
::RuntimeError
|
45
|
-
end
|
46
39
|
end
|
47
40
|
end
|
48
41
|
end
|
@@ -6,39 +6,45 @@ module Capybara
|
|
6
6
|
module Screenshot
|
7
7
|
module Diff
|
8
8
|
module Vcs
|
9
|
-
|
9
|
+
def self.checkout_vcs(root, screenshot_path, checkout_path)
|
10
|
+
if svn?(root)
|
11
|
+
restore_svn_revision(screenshot_path, checkout_path)
|
12
|
+
else
|
13
|
+
restore_git_revision(screenshot_path, checkout_path, root: root)
|
14
|
+
end
|
15
|
+
end
|
10
16
|
|
11
|
-
def self.
|
12
|
-
|
17
|
+
def self.svn?(root)
|
18
|
+
(root / ".svn").exist?
|
19
|
+
end
|
20
|
+
|
21
|
+
SILENCE_ERRORS = Os::ON_WINDOWS ? "2>nul" : "2>/dev/null"
|
13
22
|
|
23
|
+
def self.restore_git_revision(screenshot_path, checkout_path = screenshot_path, root:)
|
24
|
+
vcs_file_path = screenshot_path.relative_path_from(root)
|
14
25
|
redirect_target = "#{checkout_path} #{SILENCE_ERRORS}"
|
15
26
|
show_command = "git show HEAD~0:./#{vcs_file_path}"
|
16
|
-
|
17
|
-
|
18
|
-
if
|
19
|
-
|
27
|
+
|
28
|
+
Dir.chdir(root) do
|
29
|
+
if Screenshot.use_lfs
|
30
|
+
system("#{show_command} > #{checkout_path}.tmp #{SILENCE_ERRORS}", exception: !!ENV["DEBUG"])
|
31
|
+
|
32
|
+
`git lfs smudge < #{checkout_path}.tmp > #{redirect_target}` if $CHILD_STATUS == 0
|
33
|
+
|
34
|
+
File.delete "#{checkout_path}.tmp"
|
35
|
+
else
|
36
|
+
system("#{show_command} > #{redirect_target}", exception: !!ENV["DEBUG"])
|
20
37
|
end
|
21
|
-
File.delete "#{checkout_path}.tmp"
|
22
|
-
else
|
23
|
-
`#{show_command} > #{redirect_target}`
|
24
38
|
end
|
25
39
|
|
26
40
|
if $CHILD_STATUS != 0
|
27
|
-
|
41
|
+
checkout_path.delete if checkout_path.exist?
|
28
42
|
false
|
29
43
|
else
|
30
44
|
true
|
31
45
|
end
|
32
46
|
end
|
33
47
|
|
34
|
-
def self.checkout_vcs(screenshot_path, checkout_path)
|
35
|
-
if svn?
|
36
|
-
restore_svn_revision(screenshot_path, checkout_path)
|
37
|
-
else
|
38
|
-
restore_git_revision(screenshot_path, checkout_path)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
48
|
def self.restore_svn_revision(screenshot_path, checkout_path)
|
43
49
|
committed_file_name = screenshot_path + "../.svn/text-base/" + "#{screenshot_path.basename}.svn-base"
|
44
50
|
if committed_file_name.exist?
|
@@ -60,10 +66,6 @@ module Capybara
|
|
60
66
|
|
61
67
|
false
|
62
68
|
end
|
63
|
-
|
64
|
-
def self.svn?
|
65
|
-
(Screenshot.screenshot_area_abs / ".svn").exist?
|
66
|
-
end
|
67
69
|
end
|
68
70
|
end
|
69
71
|
end
|
@@ -1,113 +1,3 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "capybara/screenshot/diff/version"
|
5
|
-
require "capybara/screenshot/diff/drivers/utils"
|
6
|
-
require "capybara/screenshot/diff/image_compare"
|
7
|
-
require "capybara/screenshot/diff/test_methods"
|
8
|
-
require "capybara/screenshot/diff/screenshoter"
|
9
|
-
|
10
|
-
module Capybara
|
11
|
-
module Screenshot
|
12
|
-
mattr_accessor :add_driver_path
|
13
|
-
mattr_accessor :add_os_path
|
14
|
-
mattr_accessor :blur_active_element
|
15
|
-
mattr_accessor :enabled
|
16
|
-
mattr_accessor :hide_caret
|
17
|
-
mattr_reader(:root) { (defined?(Rails.root) && Rails.root) || Pathname(".").expand_path }
|
18
|
-
mattr_accessor :stability_time_limit
|
19
|
-
mattr_accessor :window_size
|
20
|
-
mattr_accessor(:save_path) { "doc/screenshots" }
|
21
|
-
mattr_accessor(:use_lfs)
|
22
|
-
|
23
|
-
class << self
|
24
|
-
def root=(path)
|
25
|
-
@@root = Pathname(path).expand_path
|
26
|
-
end
|
27
|
-
|
28
|
-
def active?
|
29
|
-
enabled || (enabled.nil? && Diff.enabled)
|
30
|
-
end
|
31
|
-
|
32
|
-
def screenshot_area
|
33
|
-
parts = [Screenshot.save_path]
|
34
|
-
parts << Capybara.current_driver.to_s if Screenshot.add_driver_path
|
35
|
-
parts << Os.name if Screenshot.add_os_path
|
36
|
-
File.join(*parts)
|
37
|
-
end
|
38
|
-
|
39
|
-
def screenshot_area_abs
|
40
|
-
root / screenshot_area
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Module to track screen shot changes
|
45
|
-
module Diff
|
46
|
-
include Capybara::DSL
|
47
|
-
|
48
|
-
mattr_accessor(:delayed) { true }
|
49
|
-
mattr_accessor :area_size_limit
|
50
|
-
mattr_accessor :color_distance_limit
|
51
|
-
mattr_accessor(:enabled) { true }
|
52
|
-
mattr_accessor :shift_distance_limit
|
53
|
-
mattr_accessor :skip_area
|
54
|
-
mattr_accessor(:driver) { :auto }
|
55
|
-
mattr_accessor :tolerance
|
56
|
-
|
57
|
-
mattr_accessor(:screenshoter) { Screenshoter }
|
58
|
-
|
59
|
-
AVAILABLE_DRIVERS = Utils.detect_available_drivers.freeze
|
60
|
-
ASSERTION = Utils.detect_test_framework_assert
|
61
|
-
|
62
|
-
def self.default_options
|
63
|
-
{
|
64
|
-
area_size_limit: area_size_limit,
|
65
|
-
color_distance_limit: color_distance_limit,
|
66
|
-
driver: driver,
|
67
|
-
shift_distance_limit: shift_distance_limit,
|
68
|
-
skip_area: skip_area,
|
69
|
-
stability_time_limit: Screenshot.stability_time_limit,
|
70
|
-
tolerance: tolerance || ((driver == :vips) ? 0.001 : nil),
|
71
|
-
wait: Capybara.default_max_wait_time
|
72
|
-
}
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.included(klass)
|
76
|
-
klass.include TestMethods
|
77
|
-
klass.setup do
|
78
|
-
BrowserHelpers.resize_to(Screenshot.window_size) if Screenshot.window_size
|
79
|
-
end
|
80
|
-
|
81
|
-
klass.teardown do
|
82
|
-
if Screenshot.active? && @test_screenshots.present?
|
83
|
-
track_failures(@test_screenshots)
|
84
|
-
@test_screenshots.clear
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
|
91
|
-
EMPTY_LINE = "\n\n"
|
92
|
-
|
93
|
-
def track_failures(screenshots)
|
94
|
-
test_screenshot_errors = screenshots.map do |caller, name, compare|
|
95
|
-
assert_image_not_changed(caller, name, compare)
|
96
|
-
end
|
97
|
-
|
98
|
-
test_screenshot_errors.compact!
|
99
|
-
|
100
|
-
unless test_screenshot_errors.empty?
|
101
|
-
error = ASSERTION.new(test_screenshot_errors.join(EMPTY_LINE))
|
102
|
-
error.set_backtrace([])
|
103
|
-
|
104
|
-
if is_a?(::Minitest::Runnable)
|
105
|
-
failures << error
|
106
|
-
else
|
107
|
-
raise error
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
3
|
+
require "capybara_screenshot_diff/minitest"
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "capybara/screenshot/diff/image_compare"
|
4
|
+
|
5
|
+
module CapybaraScreenshotDiff
|
6
|
+
class AttemptsReporter
|
7
|
+
def initialize(snapshot, comparison_options, stability_options = {})
|
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
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "capybara_screenshot_diff/dsl"
|
4
|
+
|
5
|
+
World(::CapybaraScreenshotDiff::DSL)
|
6
|
+
|
7
|
+
Before do
|
8
|
+
Capybara::Screenshot::Diff.delayed = false
|
9
|
+
if Capybara::Screenshot.active? && Capybara::Screenshot.window_size
|
10
|
+
Capybara::Screenshot::BrowserHelpers.resize_to(Capybara::Screenshot.window_size)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "minitest"
|
4
|
+
require "capybara_screenshot_diff/dsl"
|
5
|
+
|
6
|
+
used_deprecated_entrypoint = caller.any? do |path|
|
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
|
16
|
+
|
17
|
+
module CapybaraScreenshotDiff
|
18
|
+
module Minitest
|
19
|
+
module Assertions
|
20
|
+
include ::CapybaraScreenshotDiff::DSL
|
21
|
+
|
22
|
+
def screenshot(*args, skip_stack_frames: 0, **opts)
|
23
|
+
assert_nothing_raised do
|
24
|
+
super(*args, skip_stack_frames: skip_stack_frames + 3, **opts)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
alias_method :assert_matches_screenshot, :screenshot
|
29
|
+
|
30
|
+
def self.included(klass)
|
31
|
+
klass.setup do
|
32
|
+
if ::Capybara::Screenshot.window_size
|
33
|
+
::Capybara::Screenshot::BrowserHelpers.resize_to(::Capybara::Screenshot.window_size)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
klass.teardown do
|
38
|
+
errors = verify_screenshots!(@test_screenshots)
|
39
|
+
|
40
|
+
if errors.present?
|
41
|
+
assertion = ::Minitest::Assertion.new(errors.join("\n\n"))
|
42
|
+
assertion.set_backtrace []
|
43
|
+
failures << assertion
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec/core"
|
4
|
+
require "capybara_screenshot_diff/dsl"
|
5
|
+
|
6
|
+
RSpec::Matchers.define :match_screenshot do |name, **options|
|
7
|
+
description { "match a screenshot" }
|
8
|
+
|
9
|
+
match do |_page|
|
10
|
+
screenshot(name, **options)
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.include ::CapybaraScreenshotDiff::DSL, type: :feature
|
17
|
+
config.include ::CapybaraScreenshotDiff::DSL, type: :system
|
18
|
+
|
19
|
+
config.after do
|
20
|
+
if self.class.include?(::CapybaraScreenshotDiff::DSL) && ::Capybara::Screenshot.active?
|
21
|
+
errors = verify_screenshots!(@test_screenshots)
|
22
|
+
# TODO: Use rspec/mock approach to postpone verification
|
23
|
+
raise ::CapybaraScreenshotDiff::ExpectationNotMet, errors.join("\n") if errors && !errors.empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
config.before do
|
28
|
+
if self.class.include?(::CapybaraScreenshotDiff::DSL) && ::Capybara::Screenshot.window_size
|
29
|
+
::Capybara::Screenshot::BrowserHelpers.resize_to(::Capybara::Screenshot.window_size)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CapybaraScreenshotDiff
|
4
|
+
class Snap
|
5
|
+
attr_reader :full_name, :format, :path, :base_path, :manager, :attempt_path, :prev_attempt_path, :attempts_count
|
6
|
+
|
7
|
+
def initialize(full_name, format, manager: SnapManager.instance)
|
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_attempts
|
20
|
+
end
|
21
|
+
|
22
|
+
def checkout_base_screenshot
|
23
|
+
@manager.checkout_file(path, base_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def path_for(version = :actual)
|
27
|
+
case version
|
28
|
+
when :base
|
29
|
+
base_path
|
30
|
+
else
|
31
|
+
path
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def next_attempt_path!
|
36
|
+
@prev_attempt_path = @attempt_path
|
37
|
+
@attempt_path = path.sub_ext(sprintf(".attempt_%02i.#{format}", @attempts_count))
|
38
|
+
ensure
|
39
|
+
@attempts_count += 1
|
40
|
+
end
|
41
|
+
|
42
|
+
def commit_last_attempt
|
43
|
+
@manager.move(attempt_path, path)
|
44
|
+
end
|
45
|
+
|
46
|
+
def cleanup_attempts
|
47
|
+
@manager.cleanup_attempts!(self)
|
48
|
+
@attempts_count = 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_attempts_paths
|
52
|
+
Dir[@manager.abs_path_for "**/#{full_name}.attempt_*.#{format}"]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "capybara/screenshot/diff/vcs"
|
4
|
+
require "active_support/core_ext/module/attribute_accessors"
|
5
|
+
|
6
|
+
require "capybara_screenshot_diff/snap"
|
7
|
+
|
8
|
+
module CapybaraScreenshotDiff
|
9
|
+
class SnapManager
|
10
|
+
attr_reader :root
|
11
|
+
|
12
|
+
def initialize(root)
|
13
|
+
@root = Pathname.new(root)
|
14
|
+
end
|
15
|
+
|
16
|
+
def snapshot(screenshot_full_name, screenshot_format = "png")
|
17
|
+
Snap.new(screenshot_full_name, screenshot_format, manager: self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.snapshot(screenshot_full_name, screenshot_format = "png")
|
21
|
+
instance.snapshot(screenshot_full_name, screenshot_format)
|
22
|
+
end
|
23
|
+
|
24
|
+
def abs_path_for(relative_path)
|
25
|
+
@root / relative_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def checkout_file(path, as_path)
|
29
|
+
create_output_directory_for(as_path) unless as_path.exist?
|
30
|
+
Capybara::Screenshot::Diff::Vcs.checkout_vcs(root, path, as_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def provision_snap_with(snap, path, version: :actual)
|
34
|
+
managed_path = snap.path_for(version)
|
35
|
+
create_output_directory_for(managed_path) unless managed_path.exist?
|
36
|
+
FileUtils.cp(path, managed_path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_output_directory_for(path = nil)
|
40
|
+
path ? path.dirname.mkpath : root.mkpath
|
41
|
+
end
|
42
|
+
|
43
|
+
# TODO: rename to delete!
|
44
|
+
def cleanup!
|
45
|
+
FileUtils.rm_rf root, secure: true
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.cleanup!
|
49
|
+
instance.cleanup!
|
50
|
+
end
|
51
|
+
|
52
|
+
def cleanup_attempts!(snapshot)
|
53
|
+
FileUtils.rm_rf snapshot.find_attempts_paths, secure: true
|
54
|
+
end
|
55
|
+
|
56
|
+
def move(new_screenshot_path, screenshot_path)
|
57
|
+
FileUtils.mv(new_screenshot_path, screenshot_path, force: true)
|
58
|
+
end
|
59
|
+
|
60
|
+
def screenshots
|
61
|
+
root.children.map { |f| f.basename.to_s }
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.screenshots
|
65
|
+
instance.screenshots
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.root
|
69
|
+
instance.root
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.instance
|
73
|
+
Capybara::Screenshot::Diff.manager.new(Capybara::Screenshot.screenshot_area_abs)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|