Version 2.0 introduces a new canonical namespace (SnapDiff) for cleaner, more discoverable code. The public DSL remains unchanged — your existing screenshot and assert_matches_screenshot calls work without modification. This guide covers the optional migration path for settings and the new namespace.
Status: 2.0 is the transitional release — the v1 API and the canonical SnapDiff API both work. 2.1 removes everything 2.0 warns about (the legacy namespaces, the ChunkyPNG driver, shift_distance_limit, the driver: setting and the driver abstraction, and the fail_if_new / pending_if_new / fail_on_difference booleans that record replaces). There is no 3.0. Migrating on 2.0 is optional; doing it before 2.1 is not.
Estimated upgrade time: 5–15 minutes (most users need only the Gemfile pin)
Writing new code rather than migrating? Skip this guide and read SnapDiff — the canonical API: the same setup, configuration, and extension points with canonical names only, no legacy shapes to unlearn.
Breaking changes: None for the DSL; one migration notice per process plus a deprecation warning per legacy constant you reference (both suppressible), plus the known caveats below
# In your Gemfile -- changing the version is the whole required migration
gem "capybara-screenshot-diff", "2.0.0.beta4" # current 2.0 prerelease; 2.0.0 final is not out yetPin the exact prerelease. An unpinned gem "capybara-screenshot-diff" resolves to the 1.x
line, and "~> 2.0" does not resolve at all — Bundler never picks a prerelease from a plain
requirement, so it fails with Could not find gem 'capybara-screenshot-diff (~> 2.0)'. Once
2.0.0 ships, "~> 2.0" is the pin to use.
The identical content is published as snap_diff-capybara from 2.0.0 on, and that is the
name new projects are pointed at. Switching is not required — capybara-screenshot-diff is
not deprecated and receives the same releases.
There is one concrete reason to switch, though. In a Rails app Bundler.require requires the
gem by its name, and the name capybara-screenshot-diff is itself one of the v1 doors — so a
suite that has otherwise finished migrating still prints the migration notice, purely because
of the Gemfile line:
gem "snap_diff-capybara", "2.0.0.beta4" # same gem, and one fewer deprecation noticeDo it whenever it suits you; it is a one-line change and nothing else moves.
Install one name, never both. With both in a Gemfile the gem raises
SnapDiff::DualInstallError at require time. And pin whichever you choose: snap_diff-capybara's
only non-prerelease before 2.0.0 is a 0.0.1 placeholder with no Ruby files, so unpinned it
installs an empty gem and raises LoadError.
bundle install
bin/rails test:system # the task that actually runs your screenshot testsNot
rake test/rails test. In a Rails app those skiptest/system/, so you get0 runs, 0 assertions, 0 failures— a clean-looking pass that compared nothing. Outside Rails, run whatever task loads your Capybara tests.
That's it. Your existing code works unchanged. The old namespaces (Capybara::Screenshot::Diff, CapybaraScreenshotDiff) are shimmed with deprecation warnings; the new one (SnapDiff) is available if you want to modernize.
The implementation now lives in lib/snap_diff/ under the SnapDiff namespace. Every legacy constant still resolves to the same object. Most do so lazily and warn once each; a documented set stays eagerly defined and silent — see Deprecation Warnings for exactly which. The main renames:
| Legacy name | v2 canonical name |
|---|---|
Capybara::Screenshot::Diff::ImageCompare |
SnapDiff::Comparison |
Capybara::Screenshot::Diff::Difference |
SnapDiff::ComparisonResult |
Capybara::Screenshot::Diff::Drivers::BaseDriver |
SnapDiff::Driver (now a mixin — see below) |
Capybara::Screenshot::Os |
SnapDiff::Os |
CapybaraScreenshotDiff::SnapManager / ::Snap |
SnapDiff::SnapManager / SnapDiff::Snap |
CapybaraScreenshotDiff::RED_RGBA / ::ORANGE_RGBA |
SnapDiff::RED_RGBA / SnapDiff::ORANGE_RGBA |
CapybaraScreenshotDiff::Minitest::Assertions |
SnapDiff::Minitest::Assertions |
require "capybara_screenshot_diff/minitest" |
require "snap_diff/integrations/minitest" |
require "capybara_screenshot_diff/rspec" |
require "snap_diff/integrations/rspec" |
require "capybara_screenshot_diff/cucumber" |
require "snap_diff/integrations/cucumber" |
require "capybara_screenshot_diff/reporters/html" |
require "snap_diff/reporters/html" |
CapybaraScreenshotDiff.serve (…/static) |
SnapDiff.serve (require "snap_diff/static") |
CapybaraScreenshotDiff.reporters << |
SnapDiff::Reporting.register |
CapybaraScreenshotDiff.finalize_reporters! |
SnapDiff::Reporting.finalize! |
Note the integration require paths gain an integrations/ segment — require "snap_diff/minitest"
is a LoadError.
What stays the same:
screenshot(name)— still worksassert_matches_screenshot(name)— still works, still the recommended formcapture_screenshot(name)— still works- All
compare: false/trueflags and overrides work identically
What's new (optional):
# Old (still works; constant access now warns once per process)
Capybara::Screenshot::Diff.compare("baseline.png", "current.png")
Capybara::Screenshot::Diff.configure { |screenshot, diff| ... }
# New (recommended for new code)
SnapDiff.compare("baseline.png", "current.png")
SnapDiff.start { |screenshot, diff| ... } # same shape as old configure
SnapDiff.configure { |config| ... } # consolidated config objectInstead of scattering settings across Capybara::Screenshot and Capybara::Screenshot::Diff, v2.0 offers a single SnapDiff::Config object. Both the old and new paths read and write the same underlying storage — writes through either are visible through the other.
The DSL never changes. screenshot and assert_matches_screenshot work exactly as before.
The most commonly-used settings and how to update them:
| Setting | v1.x (still works in v2) | v2.0 (recommended) | What it does |
|---|---|---|---|
blur_active_element |
Capybara::Screenshot.blur_active_element = true |
SnapDiff.config.blur_active_element = true |
Hide cursor/focus indicator in screenshots (default: true) |
hide_caret |
Capybara::Screenshot.hide_caret = true |
SnapDiff.config.hide_caret = true |
Make input caret transparent for stable comparisons (default: true) |
tolerance |
Capybara::Screenshot::Diff.tolerance = 0.0005 |
SnapDiff.config.tolerance = 0.0005 |
Pixel-level color difference threshold (higher = less strict) |
save_path |
Capybara::Screenshot.save_path = "doc/screenshots" |
SnapDiff.config.save_path = "doc/screenshots" |
Where baseline screenshots are stored |
window_size |
Capybara::Screenshot.window_size = [1280, 1024] |
SnapDiff.config.window_size = [1280, 1024] |
Browser viewport size for consistent screenshots |
All 28 settings from both legacy namespaces are available via SnapDiff.config.<attr_name> — see the Configuration Reference for the full list. One rename to note: Capybara::Screenshot.enabled becomes SnapDiff.config.screenshot_enabled (it would otherwise collide with Capybara::Screenshot::Diff.enabled, which keeps the bare enabled name).
All three are equivalent and use the same underlying storage. Pick the one that fits your style.
# In test_helper.rb or spec_helper.rb
Capybara::Screenshot::Diff.configure do |screenshot, diff|
screenshot.window_size = [1280, 1024]
screenshot.blur_active_element = false
diff.tolerance = 0.0005
endSnapDiff.start do |screenshot, diff|
screenshot.window_size = [1280, 1024]
screenshot.blur_active_element = false
diff.tolerance = 0.0005
endSnapDiff.configure do |config|
config.window_size = [1280, 1024]
config.blur_active_element = false
config.tolerance = 0.0005
endYou don't have to wait for v2.0 to start using the new namespace. SnapDiff.compare and SnapDiff.start were added in v1.14; SnapDiff.config / SnapDiff.configure in v1.15. All of them work on the current 1.x line:
# Works TODAY on v1.15+, zero risk
SnapDiff.compare("baseline.png", "current.png")
SnapDiff.start { |screenshot, diff| ... }
SnapDiff.configure { |config| ... }This means you can migrate your codebase incrementally now, before opting into 2.0.
v2.0 emits five different things, and it is worth knowing which is which. The first two are
about the old namespaces; the third is about the driver features 2.1 removes; the fourth is
about options that never did anything; the fifth is about the new-screenshot booleans that
record replaces.
Everything 2.1 removes warns in 2.0, and every warning names 2.1. Nothing you can still write in 2.0 does nothing quietly — if a setting is on its way out, or was never read at all, you hear about it once per process.
The first time a process touches any hookable legacy API, you get a single line:
[snap_diff deprecation] This process uses the v1 `Capybara::Screenshot*` / `CapybaraScreenshotDiff*` API. It still works in 2.0 and is REMOVED in 2.1 -- see docs/UPGRADING.md for the SnapDiff replacements. Silence with `SnapDiff.silence_deprecations = true` or SNAP_DIFF_SILENCE_DEPRECATIONS=1. (shown once per process)
It fires once and never again, whichever door you came through:
- requiring a v1-named entry point —
require "capybara/screenshot/diff","capybara-screenshot-diff","capybara_screenshot_diff", or any ofcapybara_screenshot_diff/{dsl,minitest,rspec,cucumber}. Withgem "capybara-screenshot-diff"in the Gemfile,Bundler.requireopens this door for you at boot. - a legacy config accessor —
Capybara::Screenshot.window_size = ...,Capybara::Screenshot::Diff.tolerance - a lazily shimmed legacy constant (see below)
include Capybara::Screenshot/include Capybara::Screenshot::Diff
It exists because most of the v1 surface cannot warn per use, so without it a 2.x app could
be entirely silent right up to the bare NameError it would get on 2.1. The require door is
what makes the rest of the list a safety net rather than the only mechanism: a suite that
requires the gem and only calls screenshot touches none of the other four.
The canonical entry points — require "snap_diff", "snap_diff-capybara", "snap_diff/dsl",
"snap_diff/integrations/*" — never fire it, even though snap_diff-capybara loads the v1
compatibility files internally.
Silencing a require-time notice needs the env var.
SnapDiff.silence_deprecations = trueonly takes effect from the line that sets it, and you cannot set it before the require that definesSnapDiff. UnderBundler.requirethere is no earlier moment at all. UseSNAP_DIFF_SILENCE_DEPRECATIONS=1in the environment, orrequire "snap_diff/deprecation"; SnapDiff.silence_deprecations = trueahead of everything else.
Resolving a legacy constant that is shimmed through const_missing also warns, once per constant
per process:
[snap_diff deprecation] `Capybara::Screenshot::Diff::ImageCompare` is deprecated (constant); use `SnapDiff::Comparison` instead.
These appear for: Capybara::Screenshot::{BrowserHelpers, Screenshoter};
Capybara::Screenshot::Diff::{Vcs, StableScreenshoter, ImagePreprocessor, AreaCalculator, AnnotationService, Utils, ScreenshotMatcher, Drivers, ImageCompare, Difference};
Capybara::Screenshot::Diff::Drivers::BaseDriver; CapybaraScreenshotDiff::{RED_RGBA, ORANGE_RGBA, SnapManager, Snap, ScreenshotNamer, AttemptsReporter, BacktraceFilter, ErrorWithFilteredBacktrace, ScreenshotAssertion, AssertionRegistry};
CapybaraScreenshotDiff::Reporters::HTML.
CapybaraScreenshotDiff::DSL and ::Minitest::Assertions are shimmed this way only under a
canonical snap_diff* require. Under the v1 entry points — what an unmigrated app actually
uses — they are eagerly defined and silent, like everything in the next section.
The warnings above are about names. These are about features: 2.1 makes libvips the only image backend and deletes the rest of the driver machinery. 2.0 still supports all of it and warns once per process per subject, through the same channel and the same silencing switches.
| You will see it when you… | Removed in 2.1 | Do this instead |
|---|---|---|
select the ChunkyPNG driver — driver: :chunky_png, SnapDiff.config.driver = :chunky_png, or the legacy Capybara::Screenshot::Diff.driver = |
the :chunky_png driver |
add gem "ruby-vips" (plus the libvips system package) and drop the option |
run on driver: :auto without ruby-vips installed |
the :auto fallback to ChunkyPNG |
same — install libvips + ruby-vips. This is the case worth reading twice: nothing in your setup says chunky_png, so the warning is the only sign that 2.1 will break this process |
set shift_distance_limit — globally or per screenshot |
shift_distance_limit (ChunkyPNG-only) |
median_filter_window_size, tolerance, or color_distance_limit — see Configuration |
read SnapDiff::Drivers.loaded (the custom-driver registry) |
the registry | nothing — custom drivers are removed, see below |
read SnapDiff::Drivers.available |
driver detection | require ruby-vips instead of branching on a detected list |
include SnapDiff::Driver in your own driver class |
the driver mixin | nothing — see below |
set a driver at all — SnapDiff.config.driver =, the legacy Capybara::Screenshot::Diff.driver =, or screenshot "index", driver: … |
the driver setting and the driver: option |
delete the line. With libvips the only backend there is nothing to select |
driver: :vipswarns too, and that is deliberate. The warning is not about the value you picked — it is about the setting existing.SnapDiff.config.driver = :vipsraisesNoMethodError: undefined method 'driver='in 2.1, at config time before any test runs, andscreenshot "index", driver: :vipsbecomes anArgumentErrorfor an unknown option. Both are lines to delete, not lines to change.driver: :autoon a machine that has libvips is the one place the option still says something — and the:autorow above covers the case that matters, where:autosilently lands on ChunkyPNG.
[snap_diff deprecation] `driver: :auto` selected chunky_png because libvips is not available in this process. The chunky_png driver is REMOVED in 2.1, when libvips (the `ruby-vips` gem) becomes required -- install it now, or this setup stops comparing on 2.1. See docs/drivers.md. Silence with `SnapDiff.silence_deprecations = true` or SNAP_DIFF_SILENCE_DEPRECATIONS=1. (shown once per process) (called from /app/test/test_helper.rb:12)
Custom drivers have no migration path. The whole abstraction goes: the SnapDiff::Driver
mixin, the SnapDiff::Drivers.loaded registry, SnapDiff::Drivers.available /
SnapDiff::Utils.detect_available_drivers, and selecting a driver by name. Nothing replaces
them, and this guide is not going to pretend otherwise — if you maintain a third-party driver,
say so on the issue tracker before 2.1 ships.
Three spots on the same chopping block stay silent: the legacy
Capybara::Screenshot::Diff::LOADED_DRIVERS / ::AVAILABLE_DRIVERS aliases are plain constants
with nothing to hook (use SnapDiff::Drivers.loaded / .available to hear the warning);
SnapDiff::Drivers.for is not warned on at all — the gem itself calls it for every comparison,
so warning there would fire on setups that are not affected by anything on this list; and
detection (SnapDiff::Drivers.detect_available / SnapDiff::Utils.detect_available_drivers)
runs at load, before any user code.
Per-screenshot options used to be a free-form hash: anything the gem did not read was frozen,
carried around, and ignored. A misspelt tolerence: bought you a green suite that compared
nothing and never said so. 2.0 warns once per unknown key; 2.1 raises ArgumentError.
[snap_diff deprecation] `:tolerence` is not a recognised screenshot option, so it does nothing. 2.1 raises ArgumentError for it. Check the spelling against the option list in docs/configuration.md. Silence with `SnapDiff.silence_deprecations = true` or SNAP_DIFF_SILENCE_DEPRECATIONS=1. (shown once per process) (called from /app/test/features/home_test.rb:14)
It applies to every route into a comparison — screenshot, assert_matches_screenshot,
capture_screenshot and SnapDiff.compare. Recognised keys are area_size_limit,
capybara_screenshot_options, color_distance_limit, crop, delayed, driver,
median_filter_window_size, perceptual_threshold, screenshot_format,
shift_distance_limit, skip_area, stability_time_limit, tolerance and wait.
fail_if_new, pending_if_new and fail_on_difference each answered part of "what happens when
there is no baseline, or when there is a difference" — and none of them named the action people
actually want, which is accept this change. 2.0 adds the verb:
SnapDiff.config.record = :once # default. Record a screenshot that has no baseline.
SnapDiff.config.record = :none # strict. A missing baseline always fails.
SnapDiff.config.record = :all # re-record everything. THE BULK-ACCEPT MODE.| Old | New | Note |
|---|---|---|
fail_if_new = true |
record = :none |
the mode means the same thing on CI and off it |
fail_if_new = false |
record = :once |
|
pending_if_new = true |
record = :none, or :once |
:none fails with the git add command attached; :once records and lists it in the end-of-run summary |
fail_on_difference = false |
record = :all |
to accept the new rendering rather than ignore the difference |
All three keep working for the whole 2.x line (2.0 deletes nothing) and each warns once per process, from the point you set it:
[snap_diff deprecation] `fail_if_new` is REMOVED in 2.1: the record modes replace it. `SnapDiff.config.record = :none` is `fail_if_new = true`, `= :once` is `fail_if_new = false` -- and unlike the boolean, a mode means the same thing on CI and off it. See docs/configuration.md. Silence with `SnapDiff.silence_deprecations = true` or SNAP_DIFF_SILENCE_DEPRECATIONS=1. (shown once per process) (called from /app/test/test_helper.rb:9)
Nothing changes if you set none of them. With no record line, record reads back as :none
under CI and :once off it — exactly what fail_if_new already did, sniff and all. The
missing-baseline default is deliberately unchanged; :none is how you opt into strictness
explicitly. Precedence: an explicitly set mode outranks fail_if_new, and fail_if_new
decides only when no mode was set — the same rule fail_if_new itself has over the CI sniff.
record = :all refuses to run under CI: it accepts every rendering by design, so left in a
committed config file it would be a build that compares nothing and passes forever. See
Record modes.
Some legacy names never warn individually, and that is deliberate — the migration notice above is the signal for all of them:
- The DSL.
screenshot,assert_matches_screenshot,capture_screenshotare never deprecated. - Settings access.
Capybara::Screenshot.blur_active_element,Capybara::Screenshot::Diff.tolerance=and theDiff.configureblock are plain delegators ontoSnapDiff.config. There is noconst_missingto hook, so they cannot warn per call without adding one on every read. - Eagerly defined constants.
Capybara::Screenshot::Os,Capybara::Screenshot::Diff::VERSION,::Comparison,::LOADED_DRIVERS,::AVAILABLE_DRIVERS,::Reporters::Default, the top-levelRegion, theCapybaraScreenshotDifferror classes, and — under the v1 entry points —CapybaraScreenshotDiff::DSL/::Minitest::Assertions.const_defined?never triggersconst_missing, so these have to be real constants for adopter feature detection andrescueclauses to keep working — which means nothing is left to hook. - The driver leaf classes.
Drivers::VipsDriver/Drivers::ChunkyPNGDriverare autoloaded onSnapDiff::Drivers, so the leaf name itself never warns. Reaching them through the old path still warns once forCapybara::Screenshot::Diff::Drivers— that part is aconst_missingshim. Each leaf is only declared when its gem is actually installed, sodefined?(...Drivers::VipsDriver)staysnilwithoutruby-vips, exactly as in v1.
Every one of those names resolves under a canonical snap_diff* require too, so migrating your
require line first (as this guide recommends) never breaks a constant you have not renamed yet.
Warnings go through Kernel#warn, so test suites that hook Warning.warn (e.g. raise-on-warning setups) see them like any other Ruby warning.
If warnings appear in a test run and you're not ready to migrate yet:
# In test_helper.rb, before running tests
SnapDiff.silence_deprecations = true# Or as an environment variable
export SNAP_DIFF_SILENCE_DEPRECATIONS=1Two deliberate consequences of the lazy shim design. Both go away at 2.1, when the shimmed names go away with them:
-
defined?/const_defined?on lazily-shimmed legacy names returnsfalse/nil. The shims resolve viaconst_missing, which those checks never trigger. Feature detection likedefined?(Capybara::Screenshot::Diff::ImageCompare)must move to theSnapDiff::name. Everything in Silent by design is unaffected — those names are real constants, sodefined?,const_defined?andrescueall behave as they always did. -
Reopening
module Capybara::Screenshot::Diff::Driversshadows the shim. The historical custom-driver monkey-patch pattern defines a fresh, emptyDriversmodule instead of reaching the real one. Define custom drivers underSnapDiff::Driversinstead — and noteBaseDriveris gone as a superclass:class MyDriver < BaseDriverbecomesinclude SnapDiff::Driver(it's a mixin now).
Stubbing the detected-drivers list. The value moved to SnapDiff::Drivers::AVAILABLE_DRIVERS, and Capybara::Screenshot::Diff::AVAILABLE_DRIVERS is now an eager alias of it. Reading either is identical, but stubbing the legacy name only rebinds the alias — the gem keeps reading the canonical constant, so a test that stubs it to [] no longer exercises the no-drivers path and just passes for the wrong reason:
# before
Capybara::Screenshot::Diff.stub_const(:AVAILABLE_DRIVERS, []) { ... }
# now
SnapDiff::Drivers.stub_const(:AVAILABLE_DRIVERS, []) { ... }SnapDiff::Config::MAPPING is gone. It split in two: SnapDiff::Config::SETTINGS (the setting names, no legacy knowledge) and SnapDiff::LegacyShims::CONFIG_MAPPING (which legacy holder each name hangs off). If you referenced MAPPING — iterating settings in a test helper, say — use SETTINGS; CONFIG_MAPPING is @api private and disappears in 2.1 with the rest of the v1 surface.
No. Warnings are informational and fully suppressible. They're designed to catch legacy namespace references, not break existing CI. If silence is preferable for now, set SNAP_DIFF_SILENCE_DEPRECATIONS=1 and migrate at your pace.
No. screenshot, assert_matches_screenshot, and capture_screenshot are stable and unchanged. All overrides (:compare, :tolerance, etc.) work identically.
Yes. Both paths write to the same underlying storage:
Capybara::Screenshot::Diff.configure do |screenshot, diff|
screenshot.window_size = [1280, 1024]
end
SnapDiff.configure do |config|
config.tolerance = 0.0005 # Same storage, visible to the old path too
endAll settings and baselines are compatible with v1.x. Simply pin your Gemfile back to "~> 1.15" and bundle update capybara-screenshot-diff.
- Pin
gem "capybara-screenshot-diff", "2.0.0.beta4"in your Gemfile ("~> 2.0"once 2.0.0 ships) - Run
bundle install - Run your system tests (
bin/rails test:system, notrake test) to verify no regressions - Read the warnings it prints — each one names something 2.1 removes
- Add
gem "ruby-vips"if you are not already on it (2.1 makes libvips the only backend) - Drop
driver:from your config and yourscreenshotcalls — 2.0 warns about it, 2.1 removes it - (Optional, but do it before 2.1) Migrate config and constants to the
SnapDiffnamespace - (Optional) Silence deprecation warnings if not ready to migrate
- Report anything surprising on the issue tracker
Version 1.13.0 is a minor release clarifying API terminology and adding new capture methods. No breaking changes — your existing code continues to work.
Estimated upgrade time: 0 minutes (no action required for most users)
# In your Gemfile
gem 'capybara-screenshot-diff', '~> 1.13.0'bundle update capybara-screenshot-diff
bin/rails test:system # `rake test` skips test/system/ — 0 runs, no comparisonThat's it! Existing screenshot calls work unchanged. New methods available if needed.
v1.12.0 and earlier: screenshot was the primary method
v1.13.0+: assert_matches_screenshot is the primary method
Action required: None. screenshot continues to work as-is.
The method names now better reflect their behavior:
assert_matches_screenshot(name)— takes screenshot and asserts it matches baselinescreenshot(name, compare: true)— convenience wrapper (same behavior as above whencompare: true)capture_screenshot(name)— new: captures without asserting
# All three work and are safe to use:
assert_matches_screenshot "homepage" # Primary: explicit intent
screenshot "homepage" # Shorthand (familiar)
screenshot "homepage", compare: false # Capture only
capture_screenshot "homepage" # Also capture onlySafe to override: You can safely define your own screenshot method in your test base class — the gem's implementation won't interfere.
Capture without comparing to baseline:
capture_screenshot "dynamic_page" # No assertionEquivalent to: screenshot "dynamic_page", compare: false
Mark baseline-less tests pending instead of failing during initial CI runs:
# In test_helper.rb — before running tests
Capybara::Screenshot::Diff.pending_if_new = trueCI requirement: When using pending_if_new, ensure CI is configured with fail_if_new: false (see Configuration Reference):
Capybara::Screenshot::Diff.configure do |screenshot, diff|
diff.fail_if_new = false # Allow baselines to be added
endVersion 1.12.0 is a minor release with new features, performance improvements, and default behavior changes. This guide will help you upgrade smoothly.
Estimated upgrade time: 5-15 minutes depending on your setup
For most users, upgrading is as simple as:
# In your Gemfile
gem 'capybara-screenshot-diff', '~> 1.12.0'bundle update capybara-screenshot-diff
bin/rails test:system # Verify tests still pass (`rake test` skips test/system/)That's it! The zero-config setup still works out of the box. Your existing screenshot comparisons will continue to work with v1.12.0.
Three settings now have different defaults. This is the most likely source of unexpected test failures.
Before (v1.11.x): Cursor blinking could delay screenshots
After (v1.12.0): Cursor is automatically hidden
Action required: Only if you want the old behavior
# To restore v1.x behavior:
Capybara::Screenshot.blur_active_element = falseBefore (v1.11.x): Input caret visible in screenshots
After (v1.12.0): Caret is transparent for stable screenshots
Action required: Only if you want the old behavior
# To restore v1.x behavior:
Capybara::Screenshot.hide_caret = falseBefore (v1.11.x): New screenshots allowed in CI
After (v1.12.0): New screenshots fail tests in CI (when ENV['CI'] is set)
Action required: Only if you want to allow new screenshots in CI
# To allow new screenshots in CI:
Capybara::Screenshot::Diff.fail_if_new = falseWhy this changed: This prevents accidental baseline additions in CI pipelines. Most teams want this behavior.
Before (v1.11.x): Could use SVN for version control
After (v1.12.0): Git only
Action required: If using SVN, migrate to Git
# Check if you're using SVN for screenshots
git grep svn test/ # Look for svn commands in your testsIf you find SVN usage:
- Export your SVN repository to Git
- Update your CI/CD to use Git
- Re-commit all screenshot baselines with Git
Why this changed: SVN support was rarely used and added maintenance burden.
Before (v1.11.x): ActiveSupport was a runtime dependency
After (v1.12.0): Pure Ruby, no ActiveSupport required
Action required: None (this is a positive change!)
If your project only had ActiveSupport because of this gem, you can now remove it:
# In your Gemfile — can likely be removed if only used for this gem
# gem 'activesupport' # ← Remove if not used elsewhereWhy this changed: Lighter installations, faster boot times.
Before (v1.11.x): Could use internal classes like CaptureStrategy, ComparisonLoader
After (v1.12.0): These have been inlined/refactored
Action required: Only if using internal APIs
Check your codebase:
# Search for internal API usage
grep -r "CaptureStrategy" test/ lib/
grep -r "ComparisonLoader" test/ lib/
grep -r "ScreenshotCoordinator" test/ lib/
grep -r "ImagePreprocessor" test/ lib/If you find usage, these were never part of the public API and should be replaced with the documented public API.
Why this changed: Simplified architecture, better performance, easier maintenance.
Get an interactive dashboard showing all screenshot differences:
# Add to test_helper.rb or spec_helper.rb
require 'capybara_screenshot_diff/reporters/html'After running tests:
open doc/screenshots/snap_diff_report.htmlFeatures:
- Side-by-side comparison with diff toggle
- Thumbnail sidebar for navigation
- Search functionality
- Summary statistics
Compare any two images without Capybara or a browser:
result = Capybara::Screenshot::Diff.compare("baseline.png", "current.png")
result.quick_equal? # => true if byte-identical
result.different? # => true if visually differentUse cases:
- PDF regression testing
- Generated image validation
- CI artifact verification
Eliminate false positives from font rendering differences:
# Global configuration
Capybara::Screenshot::Diff.perceptual_threshold = 2.0
# Or per-screenshot
screenshot 'dashboard', perceptual_threshold: 2.0dE00 Scale Reference:
< 1.0— Not perceptible by human eyes1-2— Perceptible through close observation (anti-aliasing, font hinting)2-10— Perceptible at a glance (color shifts, layout changes)> 10— Clearly different colors
Why use this: If you see false positives from font rendering differences across CI environments.
Assert that an action produces no visual change:
test "clicking cancel doesn't change page" do
visit '/edit'
screenshot 'before_cancel'
click_button 'Cancel'
assert_no_screenshot_changes 'after_cancel'
endUse the new Diff.configure block:
# In test_helper.rb — one line, that's it
Capybara::Screenshot::Diff.configure do |screenshot, diff|
screenshot.window_size = [1280, 1024]
screenshot.stability_time_limit = 1
diff.tolerance = 0.0005
endEnjoy faster screenshot comparisons:
- ChunkyPNG: Eliminated array allocations in shift-detection (~30% faster for large images)
- VIPS: Cached computations at construction (~15% faster)
- General: Memoized region area size, replaced closures with blocks
No action required — these are automatic improvements.
- Ruby: 3.2, 3.3, 3.4, 3.5 (new!), 4.0 (new!)
- Rails: 7.1, 7.2, 8.0
Ruby 4.0: Fully compatible! If you see DSLStub ordering issues, they're fixed in v1.12.0.
Rails 8.0: Works out of the box with updated dependencies.
gem 'capybara-screenshot-diff', '~> 1.12.0'bundle update capybara-screenshot-diffbin/rails test:system # `rake test` skips test/system/ — 0 runs, no comparisonIf tests fail with new screenshot errors in CI:
- Option A: Commit the new baselines (recommended if changes are intentional)
- Option B: Set
fail_if_new = falsetemporarily (not recommended long-term)
require 'capybara_screenshot_diff/reporters/html'Run tests and open doc/screenshots/snap_diff_report.html to review differences.
Cause: fail_if_new now defaults to true in CI
Solution:
# Commit the new baselines
git add doc/screenshots/
git commit -m "Add screenshot baselines for v1.12.0 upgrade"Or temporarily allow them:
Capybara::Screenshot::Diff.fail_if_new = falseCause: blur_active_element and hide_caret now default to true
Solution: Restore v1.x behavior temporarily:
Capybara::Screenshot.blur_active_element = false
Capybara::Screenshot.hide_caret = falseThen re-record baselines with the new defaults (recommended):
# Do NOT delete the baselines — they are read from git (`git show HEAD:<path>`), so
# removing the files changes nothing. Re-recording is a commit.
# The run fails and rewrites every changed baseline in place
bin/rails test:system
# Review the diffs, then commit the new baselines
git status
git add doc/screenshots/
git commit -m "Re-record baselines with v1.12.0 defaults"Cause: Using internal APIs that were refactored
Solution: Use the public API instead. Check the documentation for the correct interface.
If you need to rollback:
# Pin to previous version
gem 'capybara-screenshot-diff', '~> 1.12.0'bundle update capybara-screenshot-diffAll screenshot baselines are compatible — no data loss.
- Documentation: README.md
- Changelog: CHANGELOG.md
- Issues: GitHub Issues
- DeepWiki: Code Documentation
- Update gem version to
~> 1.12.0 - Run
bundle update capybara-screenshot-diff - Run test suite
- Check for new screenshot failures in CI
- Decide on
fail_if_newbehavior - Decide on
blur_active_elementandhide_caretdefaults - Enable HTML reporter (optional)
- Re-record baselines if needed
- Commit changes
- Review upgrade issues in GitHub Issues
Congratulations! You're now running v1.12.0 🎉