snap_diff-capybara 0.0.1 → 2.0.0.beta1
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 +190 -0
- data/CODE_OF_CONDUCT.md +129 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +65 -0
- data/capybara-screenshot-diff.gemspec +29 -0
- data/docs/RELEASE_PREP.md +44 -0
- data/docs/UPGRADING.md +702 -0
- data/docs/architecture.md +289 -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 +112 -0
- data/gems.rb +39 -0
- data/lib/capybara/screenshot/diff/annotation_service.rb +7 -0
- data/lib/capybara/screenshot/diff/area_calculator.rb +7 -0
- data/lib/capybara/screenshot/diff/browser_helpers.rb +7 -0
- data/lib/capybara/screenshot/diff/config_legacy.rb +113 -0
- data/lib/capybara/screenshot/diff/cucumber.rb +3 -0
- data/lib/capybara/screenshot/diff/difference.rb +8 -0
- data/lib/capybara/screenshot/diff/drivers/base_driver.rb +9 -0
- data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +7 -0
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +7 -0
- data/lib/capybara/screenshot/diff/drivers.rb +9 -0
- data/lib/capybara/screenshot/diff/image_compare.rb +12 -0
- data/lib/capybara/screenshot/diff/image_preprocessor.rb +7 -0
- data/lib/capybara/screenshot/diff/os.rb +14 -0
- data/lib/capybara/screenshot/diff/region.rb +107 -0
- data/lib/capybara/screenshot/diff/reporters/default.rb +109 -0
- data/lib/capybara/screenshot/diff/screenshot_matcher.rb +7 -0
- data/lib/capybara/screenshot/diff/screenshoter.rb +7 -0
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +7 -0
- data/lib/capybara/screenshot/diff/utils.rb +7 -0
- data/lib/capybara/screenshot/diff/vcs.rb +7 -0
- data/lib/capybara/screenshot/diff/version.rb +15 -0
- data/lib/capybara/screenshot/diff.rb +3 -0
- data/lib/capybara-screenshot-diff.rb +3 -0
- data/lib/capybara_screenshot_diff/attempts_reporter.rb +7 -0
- data/lib/capybara_screenshot_diff/cucumber.rb +8 -0
- data/lib/capybara_screenshot_diff/dsl.rb +12 -0
- data/lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb +8 -0
- data/lib/capybara_screenshot_diff/minitest.rb +14 -0
- data/lib/capybara_screenshot_diff/reporters/html.rb +7 -0
- data/lib/capybara_screenshot_diff/rspec.rb +8 -0
- data/lib/capybara_screenshot_diff/screenshot_assertion.rb +71 -0
- data/lib/capybara_screenshot_diff/screenshot_namer.rb +7 -0
- data/lib/capybara_screenshot_diff/snap.rb +7 -0
- data/lib/capybara_screenshot_diff/snap_manager.rb +7 -0
- data/lib/capybara_screenshot_diff/static.rb +9 -0
- data/lib/capybara_screenshot_diff.rb +50 -0
- data/lib/snap_diff/annotation_service.rb +84 -0
- data/lib/snap_diff/area_calculator.rb +56 -0
- data/lib/snap_diff/attempts_reporter.rb +51 -0
- data/lib/snap_diff/browser_helpers.rb +121 -0
- data/lib/snap_diff/capture/viewport.rb +40 -0
- data/lib/snap_diff/comparison.rb +238 -0
- data/lib/snap_diff/comparison_result.rb +104 -0
- data/lib/snap_diff/config.rb +78 -0
- data/lib/snap_diff/deprecation.rb +84 -0
- data/lib/snap_diff/driver.rb +37 -0
- data/lib/snap_diff/drivers/chunky_png_driver.rb +298 -0
- data/lib/snap_diff/drivers/vips_driver.rb +171 -0
- data/lib/snap_diff/drivers.rb +14 -0
- data/lib/snap_diff/dsl.rb +143 -0
- data/lib/snap_diff/error_with_filtered_backtrace.rb +32 -0
- data/lib/snap_diff/image_preprocessor.rb +68 -0
- data/lib/snap_diff/integrations/cucumber.rb +22 -0
- data/lib/snap_diff/integrations/minitest.rb +70 -0
- data/lib/snap_diff/integrations/rspec.rb +68 -0
- data/lib/snap_diff/legacy_shims.rb +99 -0
- data/lib/snap_diff/os.rb +17 -0
- data/lib/snap_diff/reporters/html.rb +143 -0
- data/lib/snap_diff/reporters/templates/report.html.erb +463 -0
- data/lib/snap_diff/reporting.rb +53 -0
- data/lib/snap_diff/screenshot_assertion.rb +143 -0
- data/lib/snap_diff/screenshot_matcher.rb +113 -0
- data/lib/snap_diff/screenshot_namer.rb +81 -0
- data/lib/snap_diff/screenshoter.rb +129 -0
- data/lib/snap_diff/snap.rb +66 -0
- data/lib/snap_diff/snap_manager.rb +125 -0
- data/lib/snap_diff/stable_screenshoter.rb +103 -0
- data/lib/snap_diff/static.rb +11 -0
- data/lib/snap_diff/utils.rb +38 -0
- data/lib/snap_diff/vcs.rb +35 -0
- data/lib/snap_diff/version.rb +5 -0
- data/lib/snap_diff.rb +65 -0
- metadata +160 -10
- data/README.md +0 -3
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "snap_diff/deprecation"
|
|
4
|
+
require "snap_diff/drivers"
|
|
5
|
+
|
|
6
|
+
# ADR-004 v2 step 6: const_missing-based forwarders for the pre-v2
|
|
7
|
+
# namespaces. Every old-name lookup below resolves -- lazily -- to the exact
|
|
8
|
+
# same object as its SnapDiff:: replacement (identity pinned by
|
|
9
|
+
# test/unit/namespace_forwarding_test.rb) and emits a deprecation warning,
|
|
10
|
+
# once per constant per process, silenceable via
|
|
11
|
+
# SnapDiff.silence_deprecations or SNAP_DIFF_SILENCE_DEPRECATIONS=1.
|
|
12
|
+
#
|
|
13
|
+
# Deliberately eager-and-silent exceptions (plain constants, defined by
|
|
14
|
+
# their own forwarder files, never warn):
|
|
15
|
+
#
|
|
16
|
+
# - Capybara::Screenshot::Os and CapybaraScreenshotDiff::DSL (and the
|
|
17
|
+
# unmapped CapybaraScreenshotDiff::Minitest::Assertions): advertised
|
|
18
|
+
# entry-point constants probed with Object.const_defined? by
|
|
19
|
+
# support_load_probe_test.rb -- const_defined? never triggers
|
|
20
|
+
# const_missing, so a lazy shim would break that contract.
|
|
21
|
+
# - Capybara::Screenshot::Diff::VERSION: the gemspec resolves it at build
|
|
22
|
+
# time; a lazy shim would make every `gem build` warn.
|
|
23
|
+
# - Drivers::ChunkyPNGDriver / Drivers::VipsDriver: real constants on the
|
|
24
|
+
# shared SnapDiff::Drivers module (the Drivers alias is same-object by
|
|
25
|
+
# contract), so const_missing can never fire for the leaf names;
|
|
26
|
+
# resolving them through the old path still warns for ...::Drivers.
|
|
27
|
+
module SnapDiff
|
|
28
|
+
# @api private
|
|
29
|
+
module LegacyShims
|
|
30
|
+
# Installs a warn-then-forward const_missing on +namespace+.
|
|
31
|
+
#
|
|
32
|
+
# @param namespace [Module] the old namespace to hook
|
|
33
|
+
# @param old_prefix [String] how the old constant path reads to a human
|
|
34
|
+
# @param mapping [Hash{Symbol => String}] old leaf name => new full name
|
|
35
|
+
def self.install(namespace, old_prefix, mapping)
|
|
36
|
+
namespace.define_singleton_method(:const_missing) do |name|
|
|
37
|
+
target = mapping[name]
|
|
38
|
+
return super(name) unless target
|
|
39
|
+
|
|
40
|
+
Deprecation.warn("#{old_prefix}::#{name}", target, category: :constant)
|
|
41
|
+
Object.const_get(target)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module Capybara
|
|
48
|
+
module Screenshot
|
|
49
|
+
module Diff
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
module CapybaraScreenshotDiff
|
|
55
|
+
module Reporters
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
SnapDiff::LegacyShims.install(Capybara::Screenshot, "Capybara::Screenshot", {
|
|
60
|
+
BrowserHelpers: "SnapDiff::BrowserHelpers",
|
|
61
|
+
Screenshoter: "SnapDiff::Screenshoter"
|
|
62
|
+
}.freeze)
|
|
63
|
+
|
|
64
|
+
SnapDiff::LegacyShims.install(Capybara::Screenshot::Diff, "Capybara::Screenshot::Diff", {
|
|
65
|
+
Vcs: "SnapDiff::Vcs",
|
|
66
|
+
StableScreenshoter: "SnapDiff::StableScreenshoter",
|
|
67
|
+
ImagePreprocessor: "SnapDiff::ImagePreprocessor",
|
|
68
|
+
AreaCalculator: "SnapDiff::AreaCalculator",
|
|
69
|
+
AnnotationService: "SnapDiff::AnnotationService",
|
|
70
|
+
Utils: "SnapDiff::Utils",
|
|
71
|
+
ScreenshotMatcher: "SnapDiff::ScreenshotMatcher",
|
|
72
|
+
Drivers: "SnapDiff::Drivers",
|
|
73
|
+
ImageCompare: "SnapDiff::Comparison",
|
|
74
|
+
Difference: "SnapDiff::ComparisonResult"
|
|
75
|
+
}.freeze)
|
|
76
|
+
|
|
77
|
+
SnapDiff::LegacyShims.install(CapybaraScreenshotDiff, "CapybaraScreenshotDiff", {
|
|
78
|
+
RED_RGBA: "SnapDiff::RED_RGBA",
|
|
79
|
+
ORANGE_RGBA: "SnapDiff::ORANGE_RGBA",
|
|
80
|
+
SnapManager: "SnapDiff::SnapManager",
|
|
81
|
+
Snap: "SnapDiff::Snap",
|
|
82
|
+
ScreenshotNamer: "SnapDiff::ScreenshotNamer",
|
|
83
|
+
AttemptsReporter: "SnapDiff::AttemptsReporter",
|
|
84
|
+
BacktraceFilter: "SnapDiff::BacktraceFilter",
|
|
85
|
+
ErrorWithFilteredBacktrace: "SnapDiff::ErrorWithFilteredBacktrace",
|
|
86
|
+
ScreenshotAssertion: "SnapDiff::ScreenshotAssertion",
|
|
87
|
+
AssertionRegistry: "SnapDiff::AssertionRegistry"
|
|
88
|
+
}.freeze)
|
|
89
|
+
|
|
90
|
+
SnapDiff::LegacyShims.install(CapybaraScreenshotDiff::Reporters, "CapybaraScreenshotDiff::Reporters", {
|
|
91
|
+
HTML: "SnapDiff::Reporters::HTML"
|
|
92
|
+
}.freeze)
|
|
93
|
+
|
|
94
|
+
# BaseDriver dissolved into the SnapDiff::Driver mixin (v2 step 4); the
|
|
95
|
+
# Drivers alias is same-object, so the hook has to live on SnapDiff::Drivers
|
|
96
|
+
# itself. `class MyDriver < BaseDriver` becomes `include SnapDiff::Driver`.
|
|
97
|
+
SnapDiff::LegacyShims.install(SnapDiff::Drivers, "Capybara::Screenshot::Diff::Drivers", {
|
|
98
|
+
BaseDriver: "SnapDiff::Driver"
|
|
99
|
+
}.freeze)
|
data/lib/snap_diff/os.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SnapDiff
|
|
4
|
+
module Os
|
|
5
|
+
ON_WINDOWS = !!(RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/)
|
|
6
|
+
ON_MAC = !!(RbConfig::CONFIG["host_os"] =~ /darwin/)
|
|
7
|
+
ON_LINUX = !!(RbConfig::CONFIG["host_os"] =~ /linux/)
|
|
8
|
+
|
|
9
|
+
def self.name
|
|
10
|
+
return "windows" if ON_WINDOWS
|
|
11
|
+
return "macos" if ON_MAC
|
|
12
|
+
return "linux" if ON_LINUX
|
|
13
|
+
|
|
14
|
+
"unknown"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "erb"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "pathname"
|
|
7
|
+
require "json"
|
|
8
|
+
# The auto-registration block at the bottom of this file calls
|
|
9
|
+
# CapybaraScreenshotDiff.reporters, which needs the registry singleton
|
|
10
|
+
# machinery in the *old* capybara_screenshot_diff/screenshot_assertion.rb
|
|
11
|
+
# file (deliberately not moved -- see that file's own comment).
|
|
12
|
+
require "capybara_screenshot_diff/screenshot_assertion"
|
|
13
|
+
require "capybara/screenshot/diff/config_legacy"
|
|
14
|
+
|
|
15
|
+
module SnapDiff
|
|
16
|
+
module Reporters
|
|
17
|
+
class HTML
|
|
18
|
+
attr_reader :failures, :total
|
|
19
|
+
|
|
20
|
+
def initialize(output_path: nil, embed_images: false)
|
|
21
|
+
@explicit_output_path = output_path
|
|
22
|
+
@embed_images = embed_images
|
|
23
|
+
@failures = []
|
|
24
|
+
@total = 0
|
|
25
|
+
@finalized = false
|
|
26
|
+
@mutex = Mutex.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def record(assertions)
|
|
30
|
+
return if @finalized
|
|
31
|
+
|
|
32
|
+
failures = []
|
|
33
|
+
total = 0
|
|
34
|
+
|
|
35
|
+
assertions.each do |assertion|
|
|
36
|
+
compare = assertion.compare
|
|
37
|
+
next unless compare
|
|
38
|
+
|
|
39
|
+
total += 1
|
|
40
|
+
next unless compare.difference&.different?
|
|
41
|
+
|
|
42
|
+
failures << failure_entry_for(assertion.name, compare)
|
|
43
|
+
rescue => e
|
|
44
|
+
warn "[snap_diff] Reporter skipped '#{assertion.name}': #{e.message}" if ENV["DEBUG"]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@mutex.synchronize do
|
|
48
|
+
return if @finalized
|
|
49
|
+
@total += total
|
|
50
|
+
@failures.concat(failures)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def finalize
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
return if @finalized
|
|
57
|
+
return if failures.empty?
|
|
58
|
+
|
|
59
|
+
write_report
|
|
60
|
+
@finalized = true
|
|
61
|
+
output_path
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def output_path
|
|
66
|
+
@output_path ||= Pathname.new(@explicit_output_path || self.class.default_output_path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def passed = total - failures.size
|
|
70
|
+
def failed = failures.size
|
|
71
|
+
|
|
72
|
+
def summary
|
|
73
|
+
return if total.zero?
|
|
74
|
+
|
|
75
|
+
screenshots_label = (total == 1) ? "1 screenshot" : "#{total} screenshots"
|
|
76
|
+
|
|
77
|
+
if failures.empty?
|
|
78
|
+
"[snap_diff] #{screenshots_label} compared, no failures."
|
|
79
|
+
else
|
|
80
|
+
failures_label = (failures.size == 1) ? "1 failure" : "#{failures.size} failures"
|
|
81
|
+
"[snap_diff] #{screenshots_label} compared, #{failures_label}. Report: #{output_path}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def render
|
|
86
|
+
ERB.new(File.read(self.class.template_path)).result(binding)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.template_path
|
|
90
|
+
File.expand_path("templates/report.html.erb", __dir__)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.default_output_path
|
|
94
|
+
root = Capybara::Screenshot.root || Pathname.pwd
|
|
95
|
+
root / Capybara::Screenshot.save_path / "snap_diff_report.html"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def failure_entry_for(name, compare)
|
|
101
|
+
difference = compare.difference
|
|
102
|
+
{
|
|
103
|
+
name: name,
|
|
104
|
+
original: resolve_image(compare.base_image_path),
|
|
105
|
+
new: resolve_image(compare.image_path),
|
|
106
|
+
base_diff: resolve_image(compare.reporter.annotated_base_image_path),
|
|
107
|
+
diff: resolve_image(compare.reporter.annotated_image_path),
|
|
108
|
+
heatmap: resolve_image(compare.reporter.heatmap_diff_path),
|
|
109
|
+
diff_level: difference.ratio && (difference.ratio * 100).round(2),
|
|
110
|
+
area_size: difference.region_area_size,
|
|
111
|
+
max_color_distance: difference.meta[:max_color_distance]&.round(1)
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def resolve_image(path)
|
|
116
|
+
return unless path
|
|
117
|
+
|
|
118
|
+
pathname = Pathname.new(path).expand_path
|
|
119
|
+
return unless pathname.exist?
|
|
120
|
+
|
|
121
|
+
@embed_images ? data_uri(pathname) : pathname.relative_path_from(output_path.dirname.expand_path).to_s
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def data_uri(pathname)
|
|
125
|
+
ext = pathname.extname.delete_prefix(".")
|
|
126
|
+
mime = (ext == "webp") ? "image/webp" : "image/png"
|
|
127
|
+
"data:#{mime};base64,#{Base64.strict_encode64(pathname.binread)}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def write_report
|
|
131
|
+
FileUtils.mkdir_p(output_path.dirname)
|
|
132
|
+
File.write(output_path, render)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Auto-register reporter.
|
|
139
|
+
# Framework adapters (Minitest, RSpec, Cucumber) call finalize_reporters! via native hooks.
|
|
140
|
+
# For custom frameworks, call CapybaraScreenshotDiff.finalize_reporters! manually.
|
|
141
|
+
unless CapybaraScreenshotDiff.reporters.any?(SnapDiff::Reporters::HTML)
|
|
142
|
+
CapybaraScreenshotDiff.reporters << SnapDiff::Reporters::HTML.new(embed_images: !!ENV["CI"])
|
|
143
|
+
end
|