capybara-screenshot-diff 1.8.3 → 1.9.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +1 -11
- data/capybara-screenshot-diff.gemspec +2 -3
- data/gems.rb +4 -4
- 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 +80 -114
- data/lib/capybara/screenshot/diff/region.rb +28 -7
- data/lib/capybara/screenshot/diff/reporters/default.rb +117 -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 +76 -9
- 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 +45 -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 -39
- 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,28 +108,49 @@ 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
|
+
unless job
|
127
|
+
if Screenshot::Diff.fail_if_new
|
128
|
+
raise_error(<<-ERROR.strip_heredoc, caller(2))
|
129
|
+
No existing screenshot found for #{screenshot_full_name}!
|
130
|
+
To stop seeing this error disable by `Capybara::Screenshot::Diff.fail_if_new=false`
|
131
|
+
ERROR
|
132
|
+
end
|
133
|
+
|
134
|
+
return false
|
135
|
+
end
|
76
136
|
|
77
|
-
job.prepend(caller
|
137
|
+
job.prepend(caller(skip_stack_frames))
|
78
138
|
|
79
139
|
if Screenshot::Diff.delayed
|
80
140
|
schedule_match_job(job)
|
81
141
|
else
|
82
142
|
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
|
143
|
+
raise_error(error_msg, caller(2)) if error_msg
|
88
144
|
end
|
89
145
|
end
|
90
146
|
|
147
|
+
# Asserts that an image has not changed compared to its baseline.
|
148
|
+
#
|
149
|
+
# @param caller [Array(String)] The caller context, used for error reporting.
|
150
|
+
# @param name [String] The name of the screenshot being verified.
|
151
|
+
# @param comparison [Object] The comparison object containing the result and details of the comparison.
|
152
|
+
# @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
|
153
|
+
# @note This method is used internally to verify individual screenshots.
|
91
154
|
def assert_image_not_changed(caller, name, comparison)
|
92
155
|
result = comparison.different?
|
93
156
|
|
@@ -100,11 +163,15 @@ module Capybara
|
|
100
163
|
|
101
164
|
return unless result
|
102
165
|
|
103
|
-
"Screenshot does not match for '#{name}' #{comparison.error_message}\n#{caller}"
|
166
|
+
"Screenshot does not match for '#{name}' #{comparison.error_message}\n#{caller.join(", ")}"
|
104
167
|
end
|
105
168
|
|
106
169
|
private
|
107
170
|
|
171
|
+
def raise_error(error_msg, backtrace)
|
172
|
+
raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg).tap { _1.set_backtrace(backtrace) }
|
173
|
+
end
|
174
|
+
|
108
175
|
def build_screenshot_matches_job(screenshot_full_name, options)
|
109
176
|
ScreenshotMatcher
|
110
177
|
.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,45 @@
|
|
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(*, **)
|
23
|
+
super
|
24
|
+
rescue CapybaraScreenshotDiff::ExpectationNotMet => e
|
25
|
+
raise ::Minitest::Assertion, e.message
|
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
|
+
failures << ::Minitest::Assertion.new(errors.join("\n\n")) if errors && !errors.empty?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
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
|