capybara-screenshot-diff 1.10.3 → 1.12.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 +64 -0
- data/Rakefile +29 -1
- data/capybara-screenshot-diff.gemspec +4 -3
- data/docs/RELEASE_PREP.md +58 -0
- data/docs/UPGRADING.md +390 -0
- data/docs/ci-integration.md +208 -0
- data/docs/configuration.md +379 -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_web_ui.png +0 -0
- data/docs/organization.md +226 -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 +63 -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 +2 -0
- data/lib/capybara_screenshot_diff/dsl.rb +102 -7
- data/lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb +15 -0
- data/lib/capybara_screenshot_diff/minitest.rb +4 -2
- 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 +12 -2
- data/lib/capybara_screenshot_diff/screenshot_assertion.rb +61 -23
- 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 +30 -5
- metadata +47 -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,15 @@ module CapybaraScreenshotDiff
|
|
|
73
65
|
end
|
|
74
66
|
|
|
75
67
|
class AssertionRegistry
|
|
76
|
-
attr_reader :assertions
|
|
68
|
+
attr_reader :assertions, :screenshot_namer
|
|
77
69
|
|
|
78
70
|
def initialize
|
|
79
71
|
@assertions = []
|
|
72
|
+
@screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
|
|
80
73
|
end
|
|
81
74
|
|
|
82
75
|
def add_assertion(assertion)
|
|
83
|
-
|
|
84
|
-
return unless assertion.compare
|
|
76
|
+
return unless assertion&.compare
|
|
85
77
|
|
|
86
78
|
@assertions.push(assertion)
|
|
87
79
|
|
|
@@ -93,17 +85,24 @@ module CapybaraScreenshotDiff
|
|
|
93
85
|
end
|
|
94
86
|
|
|
95
87
|
def verify(screenshots = CapybaraScreenshotDiff.assertions)
|
|
88
|
+
return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
|
|
89
|
+
|
|
90
|
+
failed_assertions = CapybaraScreenshotDiff.registry.failed_assertions
|
|
91
|
+
failed_screenshot = failed_assertions.first
|
|
96
92
|
result = ScreenshotAssertion.verify_screenshots!(screenshots)
|
|
97
93
|
|
|
98
|
-
|
|
94
|
+
if result
|
|
95
|
+
raise CapybaraScreenshotDiff::ExpectationNotMet.new(result.join("\n\n"), failed_screenshot.caller)
|
|
96
|
+
end
|
|
99
97
|
end
|
|
100
98
|
|
|
101
99
|
def failed_assertions
|
|
102
|
-
assertions.
|
|
100
|
+
assertions.filter { |screenshot_assert| screenshot_assert.compare&.different? }
|
|
103
101
|
end
|
|
104
102
|
|
|
105
103
|
def reset
|
|
106
104
|
@assertions.clear
|
|
105
|
+
@screenshot_namer = CapybaraScreenshotDiff::ScreenshotNamer.new
|
|
107
106
|
end
|
|
108
107
|
end
|
|
109
108
|
end
|
|
@@ -121,7 +120,46 @@ module CapybaraScreenshotDiff
|
|
|
121
120
|
def_delegator :registry, :assertions
|
|
122
121
|
def_delegator :registry, :assertions_present?
|
|
123
122
|
def_delegator :registry, :failed_assertions
|
|
124
|
-
|
|
123
|
+
def reset
|
|
124
|
+
notify_reporters(registry.assertions)
|
|
125
|
+
registry.reset
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def reporters
|
|
129
|
+
@reporters ||= []
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
attr_reader :reporters_mutex
|
|
133
|
+
|
|
134
|
+
def finalize_reporters!
|
|
135
|
+
reporters_mutex.synchronize { reporters.dup }.each do |reporter|
|
|
136
|
+
reporter.finalize
|
|
137
|
+
if (msg = reporter.summary)
|
|
138
|
+
$stdout.puts msg
|
|
139
|
+
end
|
|
140
|
+
rescue => e
|
|
141
|
+
warn "[snap_diff] Reporter #{reporter.class} failed (#{e.class}: #{e.message})"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def_delegator :registry, :screenshot_namer
|
|
125
146
|
def_delegator :registry, :verify
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def notify_reporters(assertions)
|
|
151
|
+
return if assertions.nil? || assertions.empty?
|
|
152
|
+
|
|
153
|
+
reporters_snapshot = reporters_mutex.synchronize { reporters.dup }
|
|
154
|
+
return if reporters_snapshot.empty?
|
|
155
|
+
|
|
156
|
+
reporters_snapshot.each do |reporter|
|
|
157
|
+
reporter.record(assertions)
|
|
158
|
+
rescue => e
|
|
159
|
+
warn "[capybara-screenshot-diff] Reporter failed: #{e.message}"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
126
162
|
end
|
|
163
|
+
|
|
164
|
+
@reporters_mutex = Mutex.new
|
|
127
165
|
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,7 @@ 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? }
|
|
61
68
|
mattr_accessor(:fail_on_difference) { true }
|
|
62
69
|
mattr_accessor :color_distance_limit
|
|
63
70
|
mattr_accessor(:enabled) { true }
|
|
@@ -65,12 +72,29 @@ module Capybara
|
|
|
65
72
|
mattr_accessor :skip_area
|
|
66
73
|
mattr_accessor(:driver) { :auto }
|
|
67
74
|
mattr_accessor :tolerance
|
|
75
|
+
mattr_accessor :perceptual_threshold
|
|
68
76
|
|
|
69
77
|
mattr_accessor(:screenshoter) { Screenshoter }
|
|
70
78
|
mattr_accessor(:manager) { CapybaraScreenshotDiff::SnapManager }
|
|
71
79
|
|
|
72
80
|
AVAILABLE_DRIVERS = Utils.detect_available_drivers.freeze
|
|
73
81
|
|
|
82
|
+
# Configure screenshot and diff settings in one block.
|
|
83
|
+
#
|
|
84
|
+
# Capybara::Screenshot::Diff.configure do |screenshot, diff|
|
|
85
|
+
# screenshot.window_size = [1280, 1024]
|
|
86
|
+
# screenshot.stability_time_limit = 1
|
|
87
|
+
# diff.driver = :vips
|
|
88
|
+
# diff.tolerance = 0.0005
|
|
89
|
+
# end
|
|
90
|
+
def self.configure
|
|
91
|
+
yield Screenshot, self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.compare(baseline_path, current_path, **options)
|
|
95
|
+
ImageCompare.new(current_path, baseline_path, default_options.merge(options))
|
|
96
|
+
end
|
|
97
|
+
|
|
74
98
|
def self.default_options
|
|
75
99
|
{
|
|
76
100
|
area_size_limit: area_size_limit,
|
|
@@ -78,6 +102,7 @@ module Capybara
|
|
|
78
102
|
driver: driver,
|
|
79
103
|
screenshot_format: Screenshot.screenshot_format,
|
|
80
104
|
capybara_screenshot_options: Screenshot.capybara_screenshot_options,
|
|
105
|
+
perceptual_threshold: perceptual_threshold,
|
|
81
106
|
shift_distance_limit: shift_distance_limit,
|
|
82
107
|
skip_area: skip_area,
|
|
83
108
|
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.12.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,21 @@ executables: []
|
|
|
56
76
|
extensions: []
|
|
57
77
|
extra_rdoc_files: []
|
|
58
78
|
files:
|
|
79
|
+
- CHANGELOG.md
|
|
59
80
|
- LICENSE.txt
|
|
60
81
|
- Rakefile
|
|
61
82
|
- capybara-screenshot-diff.gemspec
|
|
83
|
+
- docs/RELEASE_PREP.md
|
|
84
|
+
- docs/UPGRADING.md
|
|
85
|
+
- docs/ci-integration.md
|
|
86
|
+
- docs/configuration.md
|
|
87
|
+
- docs/docker-testing.md
|
|
88
|
+
- docs/drivers.md
|
|
89
|
+
- docs/framework-setup.md
|
|
90
|
+
- docs/images/snap_diff_web_ui.png
|
|
91
|
+
- docs/organization.md
|
|
92
|
+
- docs/reporters.md
|
|
93
|
+
- docs/thread_safety.md
|
|
62
94
|
- gems.rb
|
|
63
95
|
- lib/capybara-screenshot-diff.rb
|
|
64
96
|
- lib/capybara/screenshot/diff.rb
|
|
@@ -67,31 +99,38 @@ files:
|
|
|
67
99
|
- lib/capybara/screenshot/diff/comparison.rb
|
|
68
100
|
- lib/capybara/screenshot/diff/cucumber.rb
|
|
69
101
|
- lib/capybara/screenshot/diff/difference.rb
|
|
102
|
+
- lib/capybara/screenshot/diff/difference_finder.rb
|
|
70
103
|
- lib/capybara/screenshot/diff/drivers.rb
|
|
71
104
|
- lib/capybara/screenshot/diff/drivers/base_driver.rb
|
|
72
105
|
- lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb
|
|
73
106
|
- lib/capybara/screenshot/diff/drivers/vips_driver.rb
|
|
74
107
|
- lib/capybara/screenshot/diff/image_compare.rb
|
|
108
|
+
- lib/capybara/screenshot/diff/image_preprocessor.rb
|
|
75
109
|
- lib/capybara/screenshot/diff/os.rb
|
|
76
110
|
- lib/capybara/screenshot/diff/region.rb
|
|
77
111
|
- lib/capybara/screenshot/diff/reporters/default.rb
|
|
78
112
|
- lib/capybara/screenshot/diff/screenshot_matcher.rb
|
|
79
113
|
- lib/capybara/screenshot/diff/screenshoter.rb
|
|
80
114
|
- lib/capybara/screenshot/diff/stable_screenshoter.rb
|
|
81
|
-
- lib/capybara/screenshot/diff/test_methods.rb
|
|
82
115
|
- lib/capybara/screenshot/diff/utils.rb
|
|
83
116
|
- lib/capybara/screenshot/diff/vcs.rb
|
|
84
117
|
- lib/capybara/screenshot/diff/version.rb
|
|
85
118
|
- lib/capybara_screenshot_diff.rb
|
|
86
119
|
- lib/capybara_screenshot_diff/attempts_reporter.rb
|
|
120
|
+
- lib/capybara_screenshot_diff/backtrace_filter.rb
|
|
87
121
|
- lib/capybara_screenshot_diff/cucumber.rb
|
|
88
122
|
- lib/capybara_screenshot_diff/dsl.rb
|
|
123
|
+
- lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb
|
|
89
124
|
- lib/capybara_screenshot_diff/minitest.rb
|
|
125
|
+
- lib/capybara_screenshot_diff/reporters/html.rb
|
|
126
|
+
- lib/capybara_screenshot_diff/reporters/templates/report.html.erb
|
|
90
127
|
- lib/capybara_screenshot_diff/rspec.rb
|
|
91
128
|
- lib/capybara_screenshot_diff/screenshot_assertion.rb
|
|
129
|
+
- lib/capybara_screenshot_diff/screenshot_namer.rb
|
|
92
130
|
- lib/capybara_screenshot_diff/snap.rb
|
|
93
131
|
- lib/capybara_screenshot_diff/snap_manager.rb
|
|
94
|
-
|
|
132
|
+
- lib/capybara_screenshot_diff/static.rb
|
|
133
|
+
homepage: https://github.com/snap-diff/snap_diff-capybara
|
|
95
134
|
licenses:
|
|
96
135
|
- MIT
|
|
97
136
|
metadata:
|
|
@@ -103,14 +142,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
103
142
|
requirements:
|
|
104
143
|
- - ">="
|
|
105
144
|
- !ruby/object:Gem::Version
|
|
106
|
-
version: '3.
|
|
145
|
+
version: '3.2'
|
|
107
146
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
147
|
requirements:
|
|
109
148
|
- - ">="
|
|
110
149
|
- !ruby/object:Gem::Version
|
|
111
150
|
version: '0'
|
|
112
151
|
requirements: []
|
|
113
|
-
rubygems_version:
|
|
152
|
+
rubygems_version: 4.0.6
|
|
114
153
|
specification_version: 4
|
|
115
154
|
summary: Track your GUI changes with diff assertions
|
|
116
155
|
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
|