capybara-screenshot-diff 1.9.0 → 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/capybara-screenshot-diff.gemspec +1 -1
- data/lib/capybara/screenshot/diff/browser_helpers.rb +4 -4
- data/lib/capybara/screenshot/diff/drivers/base_driver.rb +0 -1
- data/lib/capybara/screenshot/diff/screenshot_matcher.rb +27 -36
- data/lib/capybara/screenshot/diff/screenshoter.rb +7 -34
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +34 -54
- data/lib/capybara/screenshot/diff/test_methods.rb +3 -3
- data/lib/capybara/screenshot/diff/vcs.rb +24 -22
- data/lib/capybara/screenshot/diff/version.rb +1 -1
- data/lib/capybara_screenshot_diff/attempts_reporter.rb +49 -0
- data/lib/capybara_screenshot_diff/dsl.rb +1 -0
- data/lib/capybara_screenshot_diff/rspec.rb +1 -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 +3 -2
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87328c5152c18c006da12b39c30f0389ac0f7b71174530473681ac98e035b884
|
4
|
+
data.tar.gz: ec54883ea6de4b75fa5be917fee79e5098a18cd1c5ea3ebbfceb567661e148d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e5f89855ddaf008feab8705c6e13f495b654a7dd382fe209ac5e5917fca00553765dfcf411b90cd83aecd931fec6055502dcf3cf7ee0d69c21e8edaa891cbdc
|
7
|
+
data.tar.gz: 9f402d5ff1ed20d02c0e6fd3d7d025fc7e82287e7d159a6fb892c21cf0f8bf6db74257ba22560631ef179f191cce2de78f099b4a47b637a9d6f91eaededa7ddd
|
@@ -23,6 +23,6 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ["lib"]
|
25
25
|
|
26
|
-
spec.add_runtime_dependency "actionpack", ">= 6.1", "<
|
26
|
+
spec.add_runtime_dependency "actionpack", ">= 6.1", "< 9"
|
27
27
|
spec.add_runtime_dependency "capybara", ">= 2", "< 4"
|
28
28
|
end
|
@@ -30,13 +30,13 @@ module Capybara
|
|
30
30
|
|
31
31
|
IMAGE_WAIT_SCRIPT = <<~JS
|
32
32
|
function pending_image() {
|
33
|
-
|
33
|
+
const images = document.images
|
34
34
|
for (var i = 0; i < images.length; i++) {
|
35
|
-
if (!images[i].complete) {
|
36
|
-
return images[i].src
|
35
|
+
if (!images[i].complete && images[i].loading !== "lazy") {
|
36
|
+
return images[i].src
|
37
37
|
}
|
38
38
|
}
|
39
|
-
return false
|
39
|
+
return false
|
40
40
|
}(window)
|
41
41
|
JS
|
42
42
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "capybara_screenshot_diff/snap_manager"
|
3
4
|
require_relative "screenshoter"
|
4
5
|
require_relative "stable_screenshoter"
|
5
6
|
require_relative "browser_helpers"
|
@@ -10,15 +11,14 @@ module Capybara
|
|
10
11
|
module Screenshot
|
11
12
|
module Diff
|
12
13
|
class ScreenshotMatcher
|
13
|
-
attr_reader :screenshot_full_name, :driver_options, :
|
14
|
+
attr_reader :screenshot_full_name, :driver_options, :screenshot_format
|
14
15
|
|
15
16
|
def initialize(screenshot_full_name, options = {})
|
16
17
|
@screenshot_full_name = screenshot_full_name
|
17
18
|
@driver_options = Diff.default_options.merge(options)
|
18
19
|
|
19
20
|
@screenshot_format = @driver_options[:screenshot_format]
|
20
|
-
@
|
21
|
-
@base_screenshot_path = ScreenshotMatcher.base_image_path_from(@screenshot_path)
|
21
|
+
@snapshot = CapybaraScreenshotDiff::SnapManager.snapshot(screenshot_full_name, @screenshot_format)
|
22
22
|
end
|
23
23
|
|
24
24
|
def build_screenshot_matches_job
|
@@ -32,58 +32,49 @@ module Capybara
|
|
32
32
|
# TODO: Move this into screenshot stage, in order to re-evaluate coordinates after page updates
|
33
33
|
# Allow nil or single or multiple areas
|
34
34
|
driver_options[:skip_area] = area_calculator.calculate_skip_area
|
35
|
-
|
36
35
|
driver_options[:driver] = Drivers.for(driver_options[:driver])
|
37
36
|
|
38
|
-
|
39
|
-
create_output_directory_for(screenshot_path) unless screenshot_path.exist?
|
40
|
-
|
41
|
-
checkout_base_screenshot
|
37
|
+
@snapshot.checkout_base_screenshot
|
42
38
|
|
43
39
|
# When fail_if_new is true no need to create screenshot if base screenshot is missing
|
44
|
-
return if Capybara::Screenshot::Diff.fail_if_new &&
|
45
|
-
|
46
|
-
capture_options =
|
47
|
-
# screenshot options
|
48
|
-
capybara_screenshot_options: driver_options[:capybara_screenshot_options],
|
49
|
-
crop: driver_options.delete(:crop),
|
50
|
-
# delivery options
|
51
|
-
screenshot_format: driver_options[:screenshot_format],
|
52
|
-
# stability options
|
53
|
-
stability_time_limit: driver_options.delete(:stability_time_limit),
|
54
|
-
wait: driver_options.delete(:wait)
|
55
|
-
}
|
40
|
+
return if Capybara::Screenshot::Diff.fail_if_new && !@snapshot.base_path.exist?
|
41
|
+
|
42
|
+
capture_options, comparison_options = extract_capture_and_comparison_options!(driver_options)
|
56
43
|
|
57
44
|
# Load new screenshot from Browser
|
58
|
-
take_comparison_screenshot(capture_options,
|
45
|
+
take_comparison_screenshot(capture_options, comparison_options, @snapshot)
|
59
46
|
|
60
47
|
# Pre-computation: No need to compare without base screenshot
|
61
|
-
return unless
|
48
|
+
return unless @snapshot.base_path.exist?
|
62
49
|
|
63
50
|
# Add comparison job in the queue
|
64
|
-
[screenshot_full_name, ImageCompare.new(
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.base_image_path_from(screenshot_path)
|
68
|
-
screenshot_path.sub_ext(".base#{screenshot_path.extname}")
|
51
|
+
[screenshot_full_name, ImageCompare.new(@snapshot.path, @snapshot.base_path, comparison_options)]
|
69
52
|
end
|
70
53
|
|
71
54
|
private
|
72
55
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
56
|
+
def extract_capture_and_comparison_options!(driver_options = {})
|
57
|
+
[
|
58
|
+
{
|
59
|
+
# screenshot options
|
60
|
+
capybara_screenshot_options: driver_options[:capybara_screenshot_options],
|
61
|
+
crop: driver_options.delete(:crop),
|
62
|
+
# delivery options
|
63
|
+
screenshot_format: driver_options[:screenshot_format],
|
64
|
+
# stability options
|
65
|
+
stability_time_limit: driver_options.delete(:stability_time_limit),
|
66
|
+
wait: driver_options.delete(:wait)
|
67
|
+
},
|
68
|
+
driver_options
|
69
|
+
]
|
79
70
|
end
|
80
71
|
|
81
72
|
# Try to get screenshot from browser.
|
82
73
|
# On `stability_time_limit` it checks that page stop updating by comparison several screenshot attempts
|
83
74
|
# On reaching `wait` limit then it has been failed. On failing we annotate screenshot attempts to help to debug
|
84
|
-
def take_comparison_screenshot(capture_options,
|
85
|
-
screenshoter = build_screenshoter_for(capture_options,
|
86
|
-
screenshoter.take_comparison_screenshot(
|
75
|
+
def take_comparison_screenshot(capture_options, comparison_options, snapshot = nil)
|
76
|
+
screenshoter = build_screenshoter_for(capture_options, comparison_options)
|
77
|
+
screenshoter.take_comparison_screenshot(snapshot)
|
87
78
|
end
|
88
79
|
|
89
80
|
def build_screenshoter_for(capture_options, comparison_options = {})
|
@@ -6,11 +6,10 @@ require_relative "browser_helpers"
|
|
6
6
|
module Capybara
|
7
7
|
module Screenshot
|
8
8
|
class Screenshoter
|
9
|
-
attr_reader :capture_options, :
|
9
|
+
attr_reader :capture_options, :driver
|
10
10
|
|
11
11
|
def initialize(capture_options, driver)
|
12
12
|
@capture_options = capture_options
|
13
|
-
@comparison_options = comparison_options
|
14
13
|
@driver = driver
|
15
14
|
end
|
16
15
|
|
@@ -22,34 +21,16 @@ module Capybara
|
|
22
21
|
@capture_options[:wait]
|
23
22
|
end
|
24
23
|
|
25
|
-
def screenshot_format
|
26
|
-
@capture_options[:screenshot_format] || "png"
|
27
|
-
end
|
28
|
-
|
29
24
|
def capybara_screenshot_options
|
30
25
|
@capture_options[:capybara_screenshot_options] || {}
|
31
26
|
end
|
32
27
|
|
33
|
-
def self.attempts_screenshot_paths(base_file)
|
34
|
-
extname = Pathname.new(base_file).extname
|
35
|
-
Dir["#{base_file.to_s.chomp(extname)}.attempt_*#{extname}"].sort
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.cleanup_attempts_screenshots(base_file)
|
39
|
-
FileUtils.rm_rf attempts_screenshot_paths(base_file)
|
40
|
-
end
|
41
|
-
|
42
28
|
# Try to get screenshot from browser.
|
43
29
|
# On `stability_time_limit` it checks that page stop updating by comparison several screenshot attempts
|
44
30
|
# On reaching `wait` limit then it has been failed. On failing we annotate screenshot attempts to help to debug
|
45
|
-
def take_comparison_screenshot(
|
46
|
-
capture_screenshot_at(
|
47
|
-
|
48
|
-
Screenshoter.cleanup_attempts_screenshots(screenshot_path)
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.gen_next_attempt_path(screenshot_path, iteration)
|
52
|
-
screenshot_path.sub_ext(format(".attempt_%02i#{screenshot_path.extname}", iteration))
|
31
|
+
def take_comparison_screenshot(snapshot)
|
32
|
+
capture_screenshot_at(snapshot)
|
33
|
+
snapshot.cleanup_attempts
|
53
34
|
end
|
54
35
|
|
55
36
|
PNG_EXTENSION = ".png"
|
@@ -123,18 +104,10 @@ module Capybara
|
|
123
104
|
File.unlink(tmpfile) if tmpfile
|
124
105
|
end
|
125
106
|
|
126
|
-
def capture_screenshot_at(
|
127
|
-
|
128
|
-
take_and_process_screenshot(new_screenshot_path, screenshot_path)
|
129
|
-
end
|
130
|
-
|
131
|
-
def take_and_process_screenshot(new_screenshot_path, screenshot_path)
|
132
|
-
take_screenshot(new_screenshot_path)
|
133
|
-
move_screenshot_to(new_screenshot_path, screenshot_path)
|
134
|
-
end
|
107
|
+
def capture_screenshot_at(snapshot)
|
108
|
+
take_screenshot(snapshot.next_attempt_path!)
|
135
109
|
|
136
|
-
|
137
|
-
FileUtils.mv(new_screenshot_path, screenshot_path, force: true)
|
110
|
+
snapshot.commit_last_attempt
|
138
111
|
end
|
139
112
|
|
140
113
|
def resize_if_needed(saved_image)
|
@@ -16,17 +16,17 @@ module Capybara
|
|
16
16
|
# @param capture_options [Hash] The options for capturing screenshots, must include `:stability_time_limit` and `:wait`.
|
17
17
|
# @param comparison_options [Hash, nil] The options for comparing screenshots, defaults to `nil` which uses `Diff.default_options`.
|
18
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(
|
19
|
+
def initialize(capture_options, comparison_options = {})
|
20
|
+
@stability_time_limit, @wait = capture_options.fetch_values(*STABILITY_OPTIONS)
|
21
21
|
|
22
22
|
raise ArgumentError, "wait should be provided for stable screenshots" unless wait
|
23
23
|
raise ArgumentError, "stability_time_limit should be provided for stable screenshots" unless stability_time_limit
|
24
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
25
|
|
26
|
-
@comparison_options = comparison_options
|
26
|
+
@comparison_options = comparison_options
|
27
27
|
|
28
28
|
driver = Diff::Drivers.for(@comparison_options)
|
29
|
-
@screenshoter = Diff.screenshoter.new(capture_options.except(
|
29
|
+
@screenshoter = Diff.screenshoter.new(capture_options.except(:stability_time_limit), driver)
|
30
30
|
end
|
31
31
|
|
32
32
|
# Takes a comparison screenshot ensuring page stability
|
@@ -35,92 +35,72 @@ module Capybara
|
|
35
35
|
# or the `:wait` limit is reached. If unable to achieve a stable state within the time limit, it annotates the attempts
|
36
36
|
# to aid debugging.
|
37
37
|
#
|
38
|
-
# @param
|
38
|
+
# @param snapshot Snap The snapshot details to take a stable screenshot of.
|
39
39
|
# @return [void]
|
40
40
|
# @raise [RuntimeError] If a stable screenshot cannot be obtained within the specified `:wait` time.
|
41
|
-
def take_comparison_screenshot(
|
42
|
-
|
41
|
+
def take_comparison_screenshot(snapshot)
|
42
|
+
result = take_stable_screenshot(snapshot)
|
43
43
|
|
44
44
|
# We failed to get stable browser state! Generate difference between attempts to overview moving parts!
|
45
|
-
unless
|
45
|
+
unless result
|
46
46
|
# FIXME(uwe): Change to store the failure and only report if the test succeeds functionally.
|
47
|
-
annotate_attempts_and_fail!(
|
47
|
+
annotate_attempts_and_fail!(snapshot)
|
48
48
|
end
|
49
49
|
|
50
|
-
|
51
|
-
|
50
|
+
# store success attempt as actual screenshot
|
51
|
+
snapshot.commit_last_attempt
|
52
|
+
|
53
|
+
# cleanup all previous attempts
|
54
|
+
snapshot.cleanup_attempts
|
52
55
|
end
|
53
56
|
|
54
|
-
def take_stable_screenshot(
|
55
|
-
screenshot_path = screenshot_path.is_a?(String) ? Pathname.new(screenshot_path) : screenshot_path
|
57
|
+
def take_stable_screenshot(snapshot)
|
56
58
|
# We try to compare first attempt with checkout version, in order to not run next screenshots
|
57
|
-
attempt_path = nil
|
58
59
|
deadline_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + wait
|
59
60
|
|
60
61
|
# Cleanup all previous attempts for sure
|
61
|
-
|
62
|
+
snapshot.cleanup_attempts
|
62
63
|
|
63
64
|
0.step do |i|
|
64
65
|
# FIXME: it should be wait, and wait should be replaced with stability_time_limit
|
65
|
-
sleep(stability_time_limit) unless i == 0
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
sleep(stability_time_limit) unless i == 0 # test prev_attempt_path is nil
|
67
|
+
|
68
|
+
attempt_next_screenshot(snapshot)
|
69
|
+
|
70
|
+
return true if attempt_successful?(snapshot)
|
71
|
+
return false if timeout?(deadline_at)
|
69
72
|
end
|
70
73
|
end
|
71
74
|
|
72
75
|
private
|
73
76
|
|
74
|
-
def attempt_successful?(
|
75
|
-
return false unless prev_attempt_path
|
76
|
-
|
77
|
+
def attempt_successful?(snapshot)
|
78
|
+
return false unless snapshot.prev_attempt_path
|
79
|
+
|
80
|
+
build_last_attempts_comparison_for(snapshot).quick_equal?
|
77
81
|
rescue ArgumentError
|
78
82
|
false
|
79
83
|
end
|
80
84
|
|
81
|
-
def attempt_next_screenshot(
|
82
|
-
|
83
|
-
@screenshoter.take_screenshot(new_attempt_path)
|
84
|
-
[new_attempt_path, prev_attempt_path]
|
85
|
+
def attempt_next_screenshot(snapshot)
|
86
|
+
@screenshoter.take_screenshot(snapshot.next_attempt_path!)
|
85
87
|
end
|
86
88
|
|
87
89
|
def timeout?(deadline_at)
|
88
90
|
Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline_at
|
89
91
|
end
|
90
92
|
|
91
|
-
def
|
92
|
-
ImageCompare.new(attempt_path,
|
93
|
+
def build_last_attempts_comparison_for(snapshot)
|
94
|
+
ImageCompare.new(snapshot.attempt_path, snapshot.prev_attempt_path, @comparison_options)
|
93
95
|
end
|
94
96
|
|
95
97
|
# TODO: Move to the HistoricalReporter
|
96
|
-
def annotate_attempts_and_fail!(
|
97
|
-
|
98
|
-
|
99
|
-
annotate_stabilization_images(screenshot_attempts)
|
98
|
+
def annotate_attempts_and_fail!(snapshot)
|
99
|
+
require "capybara_screenshot_diff/attempts_reporter"
|
100
|
+
attempts_reporter = CapybaraScreenshotDiff::AttemptsReporter.new(snapshot, @comparison_options, {wait: wait, stability_time_limit: stability_time_limit})
|
100
101
|
|
101
102
|
# TODO: Move fail to the queue after tests passed
|
102
|
-
fail(
|
103
|
-
end
|
104
|
-
|
105
|
-
# TODO: Add tests that we annotate all files except first one
|
106
|
-
def annotate_stabilization_images(attempts_screenshot_paths)
|
107
|
-
previous_file = nil
|
108
|
-
attempts_screenshot_paths.reverse_each do |file_name|
|
109
|
-
if previous_file && File.exist?(previous_file)
|
110
|
-
attempts_comparison = build_comparison_for(file_name, previous_file)
|
111
|
-
|
112
|
-
if attempts_comparison.different?
|
113
|
-
FileUtils.mv(attempts_comparison.reporter.annotated_base_image_path, previous_file, force: true)
|
114
|
-
else
|
115
|
-
warn "[capybara-screenshot-diff] Some attempts was stable, but mistakenly marked as not: " \
|
116
|
-
"#{previous_file} and #{file_name} are equal"
|
117
|
-
end
|
118
|
-
|
119
|
-
FileUtils.rm(attempts_comparison.reporter.annotated_image_path, force: true)
|
120
|
-
end
|
121
|
-
|
122
|
-
previous_file = file_name
|
123
|
-
end
|
103
|
+
fail(attempts_reporter.generate)
|
124
104
|
end
|
125
105
|
end
|
126
106
|
end
|
@@ -26,7 +26,7 @@ module Capybara
|
|
26
26
|
module Diff
|
27
27
|
module TestMethods
|
28
28
|
# @!attribute [rw] test_screenshots
|
29
|
-
# @return [Array(Array(Array(String), String, ImageCompare))] An array where each element is an array containing the caller context,
|
29
|
+
# @return [Array(Array(Array(String), String, ImageCompare | Minitest::Mock))] An array where each element is an array containing the caller context,
|
30
30
|
# the name of the screenshot, and the comparison object. This attribute stores information about each screenshot
|
31
31
|
# scheduled for comparison to ensure they do not show any unintended differences.
|
32
32
|
def initialize(*)
|
@@ -146,7 +146,7 @@ module Capybara
|
|
146
146
|
|
147
147
|
# Asserts that an image has not changed compared to its baseline.
|
148
148
|
#
|
149
|
-
# @param caller [Array] The caller context, used for error reporting.
|
149
|
+
# @param caller [Array(String)] The caller context, used for error reporting.
|
150
150
|
# @param name [String] The name of the screenshot being verified.
|
151
151
|
# @param comparison [Object] The comparison object containing the result and details of the comparison.
|
152
152
|
# @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
|
@@ -163,7 +163,7 @@ module Capybara
|
|
163
163
|
|
164
164
|
return unless result
|
165
165
|
|
166
|
-
"Screenshot does not match for '#{name}' #{comparison.error_message}\n#{caller}"
|
166
|
+
"Screenshot does not match for '#{name}' #{comparison.error_message}\n#{caller.join(", ")}"
|
167
167
|
end
|
168
168
|
|
169
169
|
private
|
@@ -6,21 +6,35 @@ 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
|
@@ -31,14 +45,6 @@ module Capybara
|
|
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
|
@@ -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
|
@@ -14,6 +14,7 @@ end
|
|
14
14
|
|
15
15
|
RSpec.configure do |config|
|
16
16
|
config.include ::CapybaraScreenshotDiff::DSL, type: :feature
|
17
|
+
config.include ::CapybaraScreenshotDiff::DSL, type: :system
|
17
18
|
|
18
19
|
config.after do
|
19
20
|
if self.class.include?(::CapybaraScreenshotDiff::DSL) && ::Capybara::Screenshot.active?
|
@@ -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
|
@@ -4,9 +4,9 @@ require "capybara/dsl"
|
|
4
4
|
require "capybara/screenshot/diff/version"
|
5
5
|
require "capybara/screenshot/diff/utils"
|
6
6
|
require "capybara/screenshot/diff/image_compare"
|
7
|
+
require "capybara_screenshot_diff/snap_manager"
|
7
8
|
require "capybara/screenshot/diff/test_methods"
|
8
9
|
require "capybara/screenshot/diff/screenshoter"
|
9
|
-
|
10
10
|
require "capybara/screenshot/diff/reporters/default"
|
11
11
|
|
12
12
|
module CapybaraScreenshotDiff
|
@@ -20,7 +20,7 @@ module Capybara
|
|
20
20
|
mattr_accessor :blur_active_element
|
21
21
|
mattr_accessor :enabled
|
22
22
|
mattr_accessor :hide_caret
|
23
|
-
mattr_reader(:root) { (defined?(Rails.root) && Rails.root) || Pathname(".").expand_path }
|
23
|
+
mattr_reader(:root) { (defined?(Rails) && defined?(Rails.root) && Rails.root) || Pathname(".").expand_path }
|
24
24
|
mattr_accessor :stability_time_limit
|
25
25
|
mattr_accessor :window_size
|
26
26
|
mattr_accessor(:save_path) { "doc/screenshots" }
|
@@ -63,6 +63,7 @@ module Capybara
|
|
63
63
|
mattr_accessor :tolerance
|
64
64
|
|
65
65
|
mattr_accessor(:screenshoter) { Screenshoter }
|
66
|
+
mattr_accessor(:manager) { CapybaraScreenshotDiff::SnapManager }
|
66
67
|
|
67
68
|
AVAILABLE_DRIVERS = Utils.detect_available_drivers.freeze
|
68
69
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capybara-screenshot-diff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.9.
|
4
|
+
version: 1.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Uwe Kubosch
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: '6.1'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
22
|
+
version: '9'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +29,7 @@ dependencies:
|
|
29
29
|
version: '6.1'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
32
|
+
version: '9'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: capybara
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,10 +84,13 @@ files:
|
|
84
84
|
- lib/capybara/screenshot/diff/vcs.rb
|
85
85
|
- lib/capybara/screenshot/diff/version.rb
|
86
86
|
- lib/capybara_screenshot_diff.rb
|
87
|
+
- lib/capybara_screenshot_diff/attempts_reporter.rb
|
87
88
|
- lib/capybara_screenshot_diff/cucumber.rb
|
88
89
|
- lib/capybara_screenshot_diff/dsl.rb
|
89
90
|
- lib/capybara_screenshot_diff/minitest.rb
|
90
91
|
- lib/capybara_screenshot_diff/rspec.rb
|
92
|
+
- lib/capybara_screenshot_diff/snap.rb
|
93
|
+
- lib/capybara_screenshot_diff/snap_manager.rb
|
91
94
|
homepage: https://github.com/donv/capybara-screenshot-diff
|
92
95
|
licenses:
|
93
96
|
- MIT
|
@@ -108,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
111
|
- !ruby/object:Gem::Version
|
109
112
|
version: '0'
|
110
113
|
requirements: []
|
111
|
-
rubygems_version: 3.5.
|
114
|
+
rubygems_version: 3.5.22
|
112
115
|
signing_key:
|
113
116
|
specification_version: 4
|
114
117
|
summary: Track your GUI changes with diff assertions
|