capybara-screenshot-diff 1.10.3.1 → 1.13.0
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/CHANGELOG.md +77 -0
- data/CODE_OF_CONDUCT.md +129 -0
- data/Rakefile +29 -1
- data/capybara-screenshot-diff.gemspec +4 -3
- data/docs/RELEASE_PREP.md +42 -0
- data/docs/UPGRADING.md +390 -0
- data/docs/architecture.md +270 -0
- data/docs/ci-integration.md +238 -0
- data/docs/configuration.md +395 -0
- data/docs/docker-testing.md +24 -0
- data/docs/drivers.md +102 -0
- data/docs/framework-setup.md +87 -0
- data/docs/images/snap_diff_annotated.png +0 -0
- data/docs/images/snap_diff_web_ui.png +0 -0
- data/docs/migration-guide.md +286 -0
- data/docs/organization.md +204 -0
- data/docs/reporters.md +46 -0
- data/docs/thread_safety.md +97 -0
- data/gems.rb +2 -1
- data/lib/capybara/screenshot/diff/area_calculator.rb +1 -1
- data/lib/capybara/screenshot/diff/browser_helpers.rb +14 -1
- data/lib/capybara/screenshot/diff/comparison.rb +3 -0
- data/lib/capybara/screenshot/diff/difference.rb +40 -3
- data/lib/capybara/screenshot/diff/difference_finder.rb +97 -0
- data/lib/capybara/screenshot/diff/drivers/base_driver.rb +4 -0
- data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +22 -24
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +40 -27
- data/lib/capybara/screenshot/diff/image_compare.rb +112 -123
- data/lib/capybara/screenshot/diff/image_preprocessor.rb +72 -0
- data/lib/capybara/screenshot/diff/reporters/default.rb +10 -11
- data/lib/capybara/screenshot/diff/screenshot_matcher.rb +77 -36
- data/lib/capybara/screenshot/diff/screenshoter.rb +9 -8
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +7 -9
- data/lib/capybara/screenshot/diff/vcs.rb +19 -52
- data/lib/capybara/screenshot/diff/version.rb +1 -1
- data/lib/capybara_screenshot_diff/backtrace_filter.rb +20 -0
- data/lib/capybara_screenshot_diff/cucumber.rb +11 -0
- data/lib/capybara_screenshot_diff/dsl.rb +123 -7
- data/lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb +15 -0
- data/lib/capybara_screenshot_diff/minitest.rb +10 -5
- data/lib/capybara_screenshot_diff/reporters/html.rb +137 -0
- data/lib/capybara_screenshot_diff/reporters/templates/report.html.erb +463 -0
- data/lib/capybara_screenshot_diff/rspec.rb +20 -4
- data/lib/capybara_screenshot_diff/screenshot_assertion.rb +73 -22
- data/lib/capybara_screenshot_diff/screenshot_namer.rb +81 -0
- data/lib/capybara_screenshot_diff/snap.rb +14 -3
- data/lib/capybara_screenshot_diff/snap_manager.rb +10 -2
- data/lib/capybara_screenshot_diff/static.rb +11 -0
- data/lib/capybara_screenshot_diff.rb +31 -5
- metadata +51 -8
- data/lib/capybara/screenshot/diff/test_methods.rb +0 -157
|
@@ -7,21 +7,9 @@ module CapybaraScreenshotDiff
|
|
|
7
7
|
attr_reader :name, :args
|
|
8
8
|
attr_accessor :compare, :caller
|
|
9
9
|
|
|
10
|
-
def initialize(name, **args
|
|
10
|
+
def initialize(name, **args)
|
|
11
11
|
@name = name
|
|
12
12
|
@args = args
|
|
13
|
-
|
|
14
|
-
yield(self) if block_given?
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def self.from(screenshot_job)
|
|
18
|
-
return screenshot_job if screenshot_job.is_a?(ScreenshotAssertion)
|
|
19
|
-
|
|
20
|
-
caller, name, compare = screenshot_job
|
|
21
|
-
ScreenshotAssertion.new(name).tap do |it|
|
|
22
|
-
it.caller = caller
|
|
23
|
-
it.compare = compare
|
|
24
|
-
end
|
|
25
13
|
end
|
|
26
14
|
|
|
27
15
|
def validate
|
|
@@ -30,6 +18,14 @@ module CapybaraScreenshotDiff
|
|
|
30
18
|
self.class.assert_image_not_changed(caller, name, compare)
|
|
31
19
|
end
|
|
32
20
|
|
|
21
|
+
def validate!
|
|
22
|
+
error_msg = validate
|
|
23
|
+
|
|
24
|
+
if error_msg
|
|
25
|
+
raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg, caller)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
33
29
|
# Verifies that all scheduled screenshots do not show any unintended differences.
|
|
34
30
|
#
|
|
35
31
|
# @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
|
|
@@ -45,8 +41,6 @@ module CapybaraScreenshotDiff
|
|
|
45
41
|
test_screenshot_errors.compact!
|
|
46
42
|
|
|
47
43
|
test_screenshot_errors.empty? ? nil : test_screenshot_errors
|
|
48
|
-
ensure
|
|
49
|
-
screenshots&.clear
|
|
50
44
|
end
|
|
51
45
|
|
|
52
46
|
# Asserts that an image has not changed compared to its baseline.
|
|
@@ -62,8 +56,6 @@ module CapybaraScreenshotDiff
|
|
|
62
56
|
# Cleanup after comparisons
|
|
63
57
|
if !result && comparison.base_image_path.exist?
|
|
64
58
|
FileUtils.mv(comparison.base_image_path, comparison.image_path, force: true)
|
|
65
|
-
elsif !comparison.dimensions_changed?
|
|
66
|
-
FileUtils.rm_rf(comparison.base_image_path)
|
|
67
59
|
end
|
|
68
60
|
|
|
69
61
|
return unless result
|
|
@@ -73,15 +65,16 @@ module CapybaraScreenshotDiff
|
|
|
73
65
|
end
|
|
74
66
|
|
|
75
67
|
class AssertionRegistry
|
|
76
|
-
attr_reader :assertions
|
|
68
|
+
attr_reader :assertions, :screenshot_namer, :new_screenshots
|
|
77
69
|
|
|
78
70
|
def initialize
|
|
79
71
|
@assertions = []
|
|
72
|
+
@new_screenshots = []
|
|
73
|
+
@screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
|
|
80
74
|
end
|
|
81
75
|
|
|
82
76
|
def add_assertion(assertion)
|
|
83
|
-
|
|
84
|
-
return unless assertion.compare
|
|
77
|
+
return unless assertion&.compare
|
|
85
78
|
|
|
86
79
|
@assertions.push(assertion)
|
|
87
80
|
|
|
@@ -92,10 +85,24 @@ module CapybaraScreenshotDiff
|
|
|
92
85
|
!@assertions.empty?
|
|
93
86
|
end
|
|
94
87
|
|
|
88
|
+
def record_new_screenshot(name)
|
|
89
|
+
@new_screenshots.push(name)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def new_screenshots_present?
|
|
93
|
+
!@new_screenshots.empty?
|
|
94
|
+
end
|
|
95
|
+
|
|
95
96
|
def verify(screenshots = CapybaraScreenshotDiff.assertions)
|
|
97
|
+
return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
|
|
98
|
+
|
|
99
|
+
failed_assertions = CapybaraScreenshotDiff.registry.failed_assertions
|
|
100
|
+
failed_screenshot = failed_assertions.first
|
|
96
101
|
result = ScreenshotAssertion.verify_screenshots!(screenshots)
|
|
97
102
|
|
|
98
|
-
|
|
103
|
+
if result
|
|
104
|
+
raise CapybaraScreenshotDiff::ExpectationNotMet.new(result.join("\n\n"), failed_screenshot.caller)
|
|
105
|
+
end
|
|
99
106
|
end
|
|
100
107
|
|
|
101
108
|
def failed_assertions
|
|
@@ -104,6 +111,8 @@ module CapybaraScreenshotDiff
|
|
|
104
111
|
|
|
105
112
|
def reset
|
|
106
113
|
@assertions.clear
|
|
114
|
+
@new_screenshots.clear
|
|
115
|
+
@screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
|
|
107
116
|
end
|
|
108
117
|
end
|
|
109
118
|
end
|
|
@@ -121,7 +130,49 @@ module CapybaraScreenshotDiff
|
|
|
121
130
|
def_delegator :registry, :assertions
|
|
122
131
|
def_delegator :registry, :assertions_present?
|
|
123
132
|
def_delegator :registry, :failed_assertions
|
|
124
|
-
def_delegator :registry, :
|
|
133
|
+
def_delegator :registry, :record_new_screenshot
|
|
134
|
+
def_delegator :registry, :new_screenshots
|
|
135
|
+
def_delegator :registry, :new_screenshots_present?
|
|
136
|
+
def reset
|
|
137
|
+
notify_reporters(registry.assertions)
|
|
138
|
+
registry.reset
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def reporters
|
|
142
|
+
@reporters ||= []
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
attr_reader :reporters_mutex
|
|
146
|
+
|
|
147
|
+
def finalize_reporters!
|
|
148
|
+
reporters_mutex.synchronize { reporters.dup }.each do |reporter|
|
|
149
|
+
reporter.finalize
|
|
150
|
+
if (msg = reporter.summary)
|
|
151
|
+
$stdout.puts msg
|
|
152
|
+
end
|
|
153
|
+
rescue => e
|
|
154
|
+
warn "[snap_diff] Reporter #{reporter.class} failed (#{e.class}: #{e.message})"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def_delegator :registry, :screenshot_namer
|
|
125
159
|
def_delegator :registry, :verify
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def notify_reporters(assertions)
|
|
164
|
+
return if assertions.nil? || assertions.empty?
|
|
165
|
+
|
|
166
|
+
reporters_snapshot = reporters_mutex.synchronize { reporters.dup }
|
|
167
|
+
return if reporters_snapshot.empty?
|
|
168
|
+
|
|
169
|
+
reporters_snapshot.each do |reporter|
|
|
170
|
+
reporter.record(assertions)
|
|
171
|
+
rescue => e
|
|
172
|
+
warn "[capybara-screenshot-diff] Reporter failed: #{e.message}"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
126
175
|
end
|
|
176
|
+
|
|
177
|
+
@reporters_mutex = Mutex.new
|
|
127
178
|
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module CapybaraScreenshotDiff
|
|
7
|
+
# Handles the naming, path generation, and organization of screenshots.
|
|
8
|
+
# This class encapsulates logic related to screenshot sections, groups,
|
|
9
|
+
# and counters, providing a centralized way to determine screenshot filenames
|
|
10
|
+
# and directories.
|
|
11
|
+
class ScreenshotNamer
|
|
12
|
+
attr_reader :section, :group
|
|
13
|
+
|
|
14
|
+
def initialize(screenshot_area = nil)
|
|
15
|
+
@section = nil
|
|
16
|
+
@group = nil
|
|
17
|
+
@counter = nil
|
|
18
|
+
@screenshot_area = screenshot_area
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def screenshot_area
|
|
22
|
+
@screenshot_area ||= Capybara::Screenshot.screenshot_area
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Sets the current section for screenshots.
|
|
26
|
+
# @param name [String, nil] The name of the section.
|
|
27
|
+
def section=(name)
|
|
28
|
+
@section = name&.to_s
|
|
29
|
+
reset_group_counter
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Sets the current group for screenshots and resets the counter.
|
|
33
|
+
# @param name [String, nil] The name of the group.
|
|
34
|
+
def group=(name)
|
|
35
|
+
@group = name&.to_s
|
|
36
|
+
reset_group_counter
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Builds the full, unique name for a screenshot, including any counter.
|
|
40
|
+
# @param base_name [String] The base name for the screenshot.
|
|
41
|
+
# @return [String] The full screenshot name.
|
|
42
|
+
def full_name(base_name)
|
|
43
|
+
name = base_name.to_s
|
|
44
|
+
|
|
45
|
+
if @counter
|
|
46
|
+
name = format("%02i_%s", @counter, name)
|
|
47
|
+
@counter += 1
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
File.join(*directory_parts.push(name.to_s))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Builds the full path for a screenshot file, including section and group directories.
|
|
54
|
+
# @param base_name [String] The base name for the screenshot.
|
|
55
|
+
# @return [String] The absolute path for the screenshot file.
|
|
56
|
+
def full_name_with_path(base_name)
|
|
57
|
+
File.join(screenshot_area, full_name(base_name))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the directory parts (section and group) for constructing paths.
|
|
61
|
+
# @return [Array<String>] An array of directory names.
|
|
62
|
+
def directory_parts
|
|
63
|
+
parts = []
|
|
64
|
+
parts << @section unless @section.nil? || @section.empty?
|
|
65
|
+
parts << @group unless @group.nil? || @group.empty?
|
|
66
|
+
parts
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Calculates the directory path for the current section and group.
|
|
70
|
+
# @return [String] The full path to the directory.
|
|
71
|
+
def current_group_directory
|
|
72
|
+
File.join(*([screenshot_area] + directory_parts))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def reset_group_counter
|
|
78
|
+
@counter = (@group.nil? || @group.empty?) ? nil : 0
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -16,7 +16,8 @@ module CapybaraScreenshotDiff
|
|
|
16
16
|
def delete!
|
|
17
17
|
path.delete if path.exist?
|
|
18
18
|
base_path.delete if base_path.exist?
|
|
19
|
-
|
|
19
|
+
cleanup_diff_artifacts!
|
|
20
|
+
cleanup_attempts!
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def checkout_base_screenshot
|
|
@@ -43,13 +44,23 @@ module CapybaraScreenshotDiff
|
|
|
43
44
|
@manager.move(attempt_path, path)
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
def cleanup_attempts
|
|
47
|
+
def cleanup_attempts!
|
|
47
48
|
@manager.cleanup_attempts!(self)
|
|
48
49
|
@attempts_count = 0
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
def find_attempts_paths
|
|
52
|
-
Dir[@manager.abs_path_for
|
|
53
|
+
Dir[@manager.abs_path_for("**/#{full_name}.attempt_[0-9][0-9].#{format}")]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def cleanup_diff_artifacts!
|
|
59
|
+
[
|
|
60
|
+
path.sub_ext(".diff.#{format}"),
|
|
61
|
+
path.sub_ext(".heatmap.diff.#{format}"),
|
|
62
|
+
base_path.sub_ext(".diff.#{format}")
|
|
63
|
+
].each { |f| f.unlink if f.exist? }
|
|
53
64
|
end
|
|
54
65
|
end
|
|
55
66
|
end
|
|
@@ -11,10 +11,13 @@ module CapybaraScreenshotDiff
|
|
|
11
11
|
|
|
12
12
|
def initialize(root)
|
|
13
13
|
@root = Pathname.new(root)
|
|
14
|
+
@snapshots = Set.new
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def snapshot(screenshot_full_name, screenshot_format = "png")
|
|
17
|
-
Snap.new(screenshot_full_name, screenshot_format, manager: self)
|
|
18
|
+
Snap.new(screenshot_full_name, screenshot_format, manager: self).tap do |snapshot|
|
|
19
|
+
@snapshots << snapshot
|
|
20
|
+
end
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def self.snapshot(screenshot_full_name, screenshot_format = "png")
|
|
@@ -42,7 +45,10 @@ module CapybaraScreenshotDiff
|
|
|
42
45
|
|
|
43
46
|
# TODO: rename to delete!
|
|
44
47
|
def cleanup!
|
|
45
|
-
|
|
48
|
+
snapshots.each do |snapshot|
|
|
49
|
+
cleanup_attempts!(snapshot)
|
|
50
|
+
snapshot.delete!
|
|
51
|
+
end
|
|
46
52
|
end
|
|
47
53
|
|
|
48
54
|
def self.cleanup!
|
|
@@ -61,6 +67,8 @@ module CapybaraScreenshotDiff
|
|
|
61
67
|
root.children.map { |f| f.basename.to_s }
|
|
62
68
|
end
|
|
63
69
|
|
|
70
|
+
attr_reader :snapshots
|
|
71
|
+
|
|
64
72
|
def self.screenshots
|
|
65
73
|
instance.screenshots
|
|
66
74
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack/files"
|
|
4
|
+
require "capybara_screenshot_diff/minitest"
|
|
5
|
+
|
|
6
|
+
module CapybaraScreenshotDiff
|
|
7
|
+
def self.serve(directory, root: Dir.pwd)
|
|
8
|
+
Capybara.app = Rack::Files.new(directory)
|
|
9
|
+
Capybara::Screenshot.root = root
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -5,25 +5,32 @@ require "capybara/screenshot/diff/version"
|
|
|
5
5
|
require "capybara/screenshot/diff/utils"
|
|
6
6
|
require "capybara/screenshot/diff/image_compare"
|
|
7
7
|
require "capybara_screenshot_diff/snap_manager"
|
|
8
|
-
require "capybara/screenshot/diff/test_methods"
|
|
9
8
|
require "capybara/screenshot/diff/screenshoter"
|
|
10
9
|
require "capybara/screenshot/diff/reporters/default"
|
|
11
10
|
|
|
11
|
+
require "capybara_screenshot_diff/error_with_filtered_backtrace"
|
|
12
|
+
|
|
12
13
|
module CapybaraScreenshotDiff
|
|
13
|
-
|
|
14
|
+
RED_RGBA = [255, 0, 0, 255].freeze
|
|
15
|
+
ORANGE_RGBA = [255, 192, 0, 255].freeze
|
|
16
|
+
|
|
17
|
+
class CapybaraScreenshotDiffError < ErrorWithFilteredBacktrace; end
|
|
14
18
|
|
|
15
19
|
class ExpectationNotMet < CapybaraScreenshotDiffError; end
|
|
16
20
|
|
|
17
21
|
class UnstableImage < CapybaraScreenshotDiffError; end
|
|
22
|
+
|
|
23
|
+
class WindowSizeMismatchError < ErrorWithFilteredBacktrace; end
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
module Capybara
|
|
21
27
|
module Screenshot
|
|
22
28
|
mattr_accessor :add_driver_path
|
|
23
29
|
mattr_accessor :add_os_path
|
|
24
|
-
mattr_accessor
|
|
30
|
+
mattr_accessor(:blur_active_element) { true }
|
|
25
31
|
mattr_accessor :enabled
|
|
26
|
-
mattr_accessor
|
|
32
|
+
mattr_accessor(:hide_caret) { true }
|
|
33
|
+
mattr_accessor :disable_animations
|
|
27
34
|
mattr_reader(:root) { (defined?(Rails) && defined?(Rails.root) && Rails.root) || Pathname(".").expand_path }
|
|
28
35
|
mattr_accessor :stability_time_limit
|
|
29
36
|
mattr_accessor :window_size
|
|
@@ -57,7 +64,8 @@ module Capybara
|
|
|
57
64
|
module Diff
|
|
58
65
|
mattr_accessor(:delayed) { true }
|
|
59
66
|
mattr_accessor :area_size_limit
|
|
60
|
-
mattr_accessor(:fail_if_new) {
|
|
67
|
+
mattr_accessor(:fail_if_new) { !ENV["CI"].nil? && !ENV["CI"].empty? }
|
|
68
|
+
mattr_accessor(:pending_if_new) { false }
|
|
61
69
|
mattr_accessor(:fail_on_difference) { true }
|
|
62
70
|
mattr_accessor :color_distance_limit
|
|
63
71
|
mattr_accessor(:enabled) { true }
|
|
@@ -65,12 +73,29 @@ module Capybara
|
|
|
65
73
|
mattr_accessor :skip_area
|
|
66
74
|
mattr_accessor(:driver) { :auto }
|
|
67
75
|
mattr_accessor :tolerance
|
|
76
|
+
mattr_accessor :perceptual_threshold
|
|
68
77
|
|
|
69
78
|
mattr_accessor(:screenshoter) { Screenshoter }
|
|
70
79
|
mattr_accessor(:manager) { CapybaraScreenshotDiff::SnapManager }
|
|
71
80
|
|
|
72
81
|
AVAILABLE_DRIVERS = Utils.detect_available_drivers.freeze
|
|
73
82
|
|
|
83
|
+
# Configure screenshot and diff settings in one block.
|
|
84
|
+
#
|
|
85
|
+
# Capybara::Screenshot::Diff.configure do |screenshot, diff|
|
|
86
|
+
# screenshot.window_size = [1280, 1024]
|
|
87
|
+
# screenshot.stability_time_limit = 1
|
|
88
|
+
# diff.driver = :vips
|
|
89
|
+
# diff.tolerance = 0.0005
|
|
90
|
+
# end
|
|
91
|
+
def self.configure
|
|
92
|
+
yield Screenshot, self
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.compare(baseline_path, current_path, **options)
|
|
96
|
+
ImageCompare.new(current_path, baseline_path, default_options.merge(options))
|
|
97
|
+
end
|
|
98
|
+
|
|
74
99
|
def self.default_options
|
|
75
100
|
{
|
|
76
101
|
area_size_limit: area_size_limit,
|
|
@@ -78,6 +103,7 @@ module Capybara
|
|
|
78
103
|
driver: driver,
|
|
79
104
|
screenshot_format: Screenshot.screenshot_format,
|
|
80
105
|
capybara_screenshot_options: Screenshot.capybara_screenshot_options,
|
|
106
|
+
perceptual_threshold: perceptual_threshold,
|
|
81
107
|
shift_distance_limit: shift_distance_limit,
|
|
82
108
|
skip_area: skip_area,
|
|
83
109
|
stability_time_limit: Screenshot.stability_time_limit,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capybara-screenshot-diff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Uwe Kubosch
|
|
@@ -15,17 +15,37 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '7.
|
|
18
|
+
version: '7.1'
|
|
19
19
|
- - "<"
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
21
|
version: '9'
|
|
22
|
-
type: :
|
|
22
|
+
type: :development
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: activesupport
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '7.1'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '9'
|
|
42
|
+
type: :development
|
|
23
43
|
prerelease: false
|
|
24
44
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
45
|
requirements:
|
|
26
46
|
- - ">="
|
|
27
47
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: '7.
|
|
48
|
+
version: '7.1'
|
|
29
49
|
- - "<"
|
|
30
50
|
- !ruby/object:Gem::Version
|
|
31
51
|
version: '9'
|
|
@@ -56,9 +76,25 @@ executables: []
|
|
|
56
76
|
extensions: []
|
|
57
77
|
extra_rdoc_files: []
|
|
58
78
|
files:
|
|
79
|
+
- CHANGELOG.md
|
|
80
|
+
- CODE_OF_CONDUCT.md
|
|
59
81
|
- LICENSE.txt
|
|
60
82
|
- Rakefile
|
|
61
83
|
- capybara-screenshot-diff.gemspec
|
|
84
|
+
- docs/RELEASE_PREP.md
|
|
85
|
+
- docs/UPGRADING.md
|
|
86
|
+
- docs/architecture.md
|
|
87
|
+
- docs/ci-integration.md
|
|
88
|
+
- docs/configuration.md
|
|
89
|
+
- docs/docker-testing.md
|
|
90
|
+
- docs/drivers.md
|
|
91
|
+
- docs/framework-setup.md
|
|
92
|
+
- docs/images/snap_diff_annotated.png
|
|
93
|
+
- docs/images/snap_diff_web_ui.png
|
|
94
|
+
- docs/migration-guide.md
|
|
95
|
+
- docs/organization.md
|
|
96
|
+
- docs/reporters.md
|
|
97
|
+
- docs/thread_safety.md
|
|
62
98
|
- gems.rb
|
|
63
99
|
- lib/capybara-screenshot-diff.rb
|
|
64
100
|
- lib/capybara/screenshot/diff.rb
|
|
@@ -67,31 +103,38 @@ files:
|
|
|
67
103
|
- lib/capybara/screenshot/diff/comparison.rb
|
|
68
104
|
- lib/capybara/screenshot/diff/cucumber.rb
|
|
69
105
|
- lib/capybara/screenshot/diff/difference.rb
|
|
106
|
+
- lib/capybara/screenshot/diff/difference_finder.rb
|
|
70
107
|
- lib/capybara/screenshot/diff/drivers.rb
|
|
71
108
|
- lib/capybara/screenshot/diff/drivers/base_driver.rb
|
|
72
109
|
- lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb
|
|
73
110
|
- lib/capybara/screenshot/diff/drivers/vips_driver.rb
|
|
74
111
|
- lib/capybara/screenshot/diff/image_compare.rb
|
|
112
|
+
- lib/capybara/screenshot/diff/image_preprocessor.rb
|
|
75
113
|
- lib/capybara/screenshot/diff/os.rb
|
|
76
114
|
- lib/capybara/screenshot/diff/region.rb
|
|
77
115
|
- lib/capybara/screenshot/diff/reporters/default.rb
|
|
78
116
|
- lib/capybara/screenshot/diff/screenshot_matcher.rb
|
|
79
117
|
- lib/capybara/screenshot/diff/screenshoter.rb
|
|
80
118
|
- lib/capybara/screenshot/diff/stable_screenshoter.rb
|
|
81
|
-
- lib/capybara/screenshot/diff/test_methods.rb
|
|
82
119
|
- lib/capybara/screenshot/diff/utils.rb
|
|
83
120
|
- lib/capybara/screenshot/diff/vcs.rb
|
|
84
121
|
- lib/capybara/screenshot/diff/version.rb
|
|
85
122
|
- lib/capybara_screenshot_diff.rb
|
|
86
123
|
- lib/capybara_screenshot_diff/attempts_reporter.rb
|
|
124
|
+
- lib/capybara_screenshot_diff/backtrace_filter.rb
|
|
87
125
|
- lib/capybara_screenshot_diff/cucumber.rb
|
|
88
126
|
- lib/capybara_screenshot_diff/dsl.rb
|
|
127
|
+
- lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb
|
|
89
128
|
- lib/capybara_screenshot_diff/minitest.rb
|
|
129
|
+
- lib/capybara_screenshot_diff/reporters/html.rb
|
|
130
|
+
- lib/capybara_screenshot_diff/reporters/templates/report.html.erb
|
|
90
131
|
- lib/capybara_screenshot_diff/rspec.rb
|
|
91
132
|
- lib/capybara_screenshot_diff/screenshot_assertion.rb
|
|
133
|
+
- lib/capybara_screenshot_diff/screenshot_namer.rb
|
|
92
134
|
- lib/capybara_screenshot_diff/snap.rb
|
|
93
135
|
- lib/capybara_screenshot_diff/snap_manager.rb
|
|
94
|
-
|
|
136
|
+
- lib/capybara_screenshot_diff/static.rb
|
|
137
|
+
homepage: https://github.com/snap-diff/snap_diff-capybara
|
|
95
138
|
licenses:
|
|
96
139
|
- MIT
|
|
97
140
|
metadata:
|
|
@@ -103,14 +146,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
103
146
|
requirements:
|
|
104
147
|
- - ">="
|
|
105
148
|
- !ruby/object:Gem::Version
|
|
106
|
-
version: '3.
|
|
149
|
+
version: '3.2'
|
|
107
150
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
151
|
requirements:
|
|
109
152
|
- - ">="
|
|
110
153
|
- !ruby/object:Gem::Version
|
|
111
154
|
version: '0'
|
|
112
155
|
requirements: []
|
|
113
|
-
rubygems_version:
|
|
156
|
+
rubygems_version: 4.0.16
|
|
114
157
|
specification_version: 4
|
|
115
158
|
summary: Track your GUI changes with diff assertions
|
|
116
159
|
test_files: []
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "English"
|
|
4
|
-
require "capybara"
|
|
5
|
-
require "action_controller"
|
|
6
|
-
require "action_dispatch"
|
|
7
|
-
require "active_support/core_ext/string/strip"
|
|
8
|
-
require "pathname"
|
|
9
|
-
|
|
10
|
-
require_relative "drivers"
|
|
11
|
-
require_relative "image_compare"
|
|
12
|
-
require_relative "vcs"
|
|
13
|
-
require_relative "browser_helpers"
|
|
14
|
-
require_relative "region"
|
|
15
|
-
|
|
16
|
-
require_relative "screenshot_matcher"
|
|
17
|
-
|
|
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
|
-
|
|
24
|
-
module Capybara
|
|
25
|
-
module Screenshot
|
|
26
|
-
module Diff
|
|
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.
|
|
32
|
-
def initialize(*)
|
|
33
|
-
super
|
|
34
|
-
@screenshot_counter = nil
|
|
35
|
-
@screenshot_group = nil
|
|
36
|
-
@screenshot_section = nil
|
|
37
|
-
@test_screenshot_errors = nil
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Builds the full name for a screenshot, incorporating counters and group names for uniqueness.
|
|
41
|
-
#
|
|
42
|
-
# @param name [String] The base name for the screenshot.
|
|
43
|
-
# @return [String] The full, unique name for the screenshot.
|
|
44
|
-
def build_full_name(name)
|
|
45
|
-
if @screenshot_counter
|
|
46
|
-
name = format("%02i_#{name}", @screenshot_counter)
|
|
47
|
-
@screenshot_counter += 1
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
File.join(*group_parts.push(name.to_s))
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Determines the directory path for saving screenshots.
|
|
54
|
-
#
|
|
55
|
-
# @return [String] The full path to the directory where screenshots are saved.
|
|
56
|
-
def screenshot_dir
|
|
57
|
-
File.join(*([Screenshot.screenshot_area] + group_parts))
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def screenshot_section(name)
|
|
61
|
-
@screenshot_section = name.to_s
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def screenshot_group(name)
|
|
65
|
-
@screenshot_group = name.to_s
|
|
66
|
-
@screenshot_counter = (@screenshot_group.nil? || @screenshot_group.empty?) ? nil : 0
|
|
67
|
-
name_present = !(name.nil? || name.empty?)
|
|
68
|
-
return unless Screenshot.active? && name_present
|
|
69
|
-
|
|
70
|
-
FileUtils.rm_rf screenshot_dir
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Schedules a screenshot comparison job for later execution.
|
|
74
|
-
#
|
|
75
|
-
# This method adds a job to the queue of screenshots to be matched. It's used when `Capybara::Screenshot::Diff.delayed`
|
|
76
|
-
# is set to true, allowing for batch processing of screenshot comparisons at a later point, typically at the end of a test.
|
|
77
|
-
#
|
|
78
|
-
# @param job [Array(Array(String), String, ImageCompare)] The job to be scheduled, consisting of the caller context, screenshot name, and comparison object.
|
|
79
|
-
# @return [Boolean] Always returns true, indicating the job was successfully scheduled.
|
|
80
|
-
def schedule_match_job(job)
|
|
81
|
-
CapybaraScreenshotDiff.add_assertion(job)
|
|
82
|
-
true
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def group_parts
|
|
86
|
-
parts = []
|
|
87
|
-
parts << @screenshot_section unless @screenshot_section.nil? || @screenshot_section.empty?
|
|
88
|
-
parts << @screenshot_group unless @screenshot_group.nil? || @screenshot_group.empty?
|
|
89
|
-
parts
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Takes a screenshot and optionally compares it against a baseline image.
|
|
93
|
-
#
|
|
94
|
-
# @param name [String] The name of the screenshot, used to generate the filename.
|
|
95
|
-
# @param skip_stack_frames [Integer] The number of stack frames to skip when reporting errors, for cleaner error messages.
|
|
96
|
-
# @param options [Hash] Additional options for taking the screenshot, such as custom dimensions or selectors.
|
|
97
|
-
# @return [Boolean] Returns true if the screenshot was successfully captured and matches the baseline, false otherwise.
|
|
98
|
-
# @raise [CapybaraScreenshotDiff::ExpectationNotMet] If the screenshot does not match the baseline image and fail_if_new is set to true.
|
|
99
|
-
# @example Capture a full-page screenshot named 'login_page'
|
|
100
|
-
# screenshot('login_page', skip_stack_frames: 1, full: true)
|
|
101
|
-
def screenshot(name, skip_stack_frames: 0, **options)
|
|
102
|
-
return false unless Screenshot.active?
|
|
103
|
-
|
|
104
|
-
# setup
|
|
105
|
-
screenshot_full_name = build_full_name(name)
|
|
106
|
-
|
|
107
|
-
# exercise
|
|
108
|
-
match_changes_job = build_screenshot_matches_job(screenshot_full_name, options)
|
|
109
|
-
|
|
110
|
-
# verify
|
|
111
|
-
backtrace = caller(skip_stack_frames + 1).reject { |l| l =~ /gems\/(activesupport|minitest|railties)/ }
|
|
112
|
-
|
|
113
|
-
unless match_changes_job
|
|
114
|
-
if Screenshot::Diff.fail_if_new
|
|
115
|
-
_raise_error(<<-ERROR.strip_heredoc, backtrace)
|
|
116
|
-
No existing screenshot found for #{screenshot_full_name}!
|
|
117
|
-
To stop seeing this error disable by `Capybara::Screenshot::Diff.fail_if_new=false`
|
|
118
|
-
ERROR
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
return false
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
match_changes_job.prepend(backtrace)
|
|
125
|
-
|
|
126
|
-
if Screenshot::Diff.delayed
|
|
127
|
-
schedule_match_job(match_changes_job)
|
|
128
|
-
else
|
|
129
|
-
invoke_match_job(match_changes_job)
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
private
|
|
134
|
-
|
|
135
|
-
def invoke_match_job(job)
|
|
136
|
-
error_msg = CapybaraScreenshotDiff::ScreenshotAssertion.from(job).validate
|
|
137
|
-
|
|
138
|
-
if error_msg
|
|
139
|
-
_raise_error(error_msg, job[0])
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
true
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def _raise_error(error_msg, backtrace)
|
|
146
|
-
raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg).tap { _1.set_backtrace(backtrace) }
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def build_screenshot_matches_job(screenshot_full_name, options)
|
|
150
|
-
ScreenshotMatcher
|
|
151
|
-
.new(screenshot_full_name, options)
|
|
152
|
-
.build_screenshot_matches_job
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|