capybara-screenshot-diff 1.9.0 → 1.9.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/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
|