Skip to content

Latest commit

 

History

History
931 lines (643 loc) · 37.6 KB

File metadata and controls

931 lines (643 loc) · 37.6 KB

Upgrading

Upgrading to v2.0

Overview

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


The Short Version (Most Users)

# 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 yet

Pin 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.

Optional, and worth one line of thought: the gem name

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 notice

Do 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 tests

Not rake test / rails test. In a Rails app those skip test/system/, so you get 0 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.


What Changed

1. New Canonical Namespace: SnapDiff

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 works
  • assert_matches_screenshot(name) — still works, still the recommended form
  • capture_screenshot(name) — still works
  • All compare: false/true flags 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 object

2. Consolidated Configuration: SnapDiff.config

Instead 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.


Settings Migration Table

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).


Three Ways to Configure

All three are equivalent and use the same underlying storage. Pick the one that fits your style.

Option 1: Traditional block (v1 shape, still works)

# 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
end

Option 2: SnapDiff block with old shape (backward-compatible)

SnapDiff.start do |screenshot, diff|
  screenshot.window_size = [1280, 1024]
  screenshot.blur_active_element = false
  diff.tolerance = 0.0005
end

Option 3: Consolidated config (cleanest)

SnapDiff.configure do |config|
  config.window_size = [1280, 1024]
  config.blur_active_element = false
  config.tolerance = 0.0005
end

Prepare Today on v1.x (Zero Risk)

You 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.


Deprecation Warnings

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.

1. The migration notice — one line 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 pointrequire "capybara/screenshot/diff", "capybara-screenshot-diff", "capybara_screenshot_diff", or any of capybara_screenshot_diff/{dsl,minitest,rspec,cucumber}. With gem "capybara-screenshot-diff" in the Gemfile, Bundler.require opens 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 = true only takes effect from the line that sets it, and you cannot set it before the require that defines SnapDiff. Under Bundler.require there is no earlier moment at all. Use SNAP_DIFF_SILENCE_DEPRECATIONS=1 in the environment, or require "snap_diff/deprecation"; SnapDiff.silence_deprecations = true ahead of everything else.

2. Per-constant warnings — one line per lazily shimmed constant

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.

3. Removal warnings — the driver half, removed in 2.1

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 allSnapDiff.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: :vips warns too, and that is deliberate. The warning is not about the value you picked — it is about the setting existing. SnapDiff.config.driver = :vips raises NoMethodError: undefined method 'driver=' in 2.1, at config time before any test runs, and screenshot "index", driver: :vips becomes an ArgumentError for an unknown option. Both are lines to delete, not lines to change. driver: :auto on a machine that has libvips is the one place the option still says something — and the :auto row above covers the case that matters, where :auto silently 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.

4. Unknown screenshot options — warned in 2.0, raised in 2.1

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.

5. The new-screenshot booleans — superseded by record, removed in 2.1

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.

Silent by design

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_screenshot are never deprecated.
  • Settings access. Capybara::Screenshot.blur_active_element, Capybara::Screenshot::Diff.tolerance= and the Diff.configure block are plain delegators onto SnapDiff.config. There is no const_missing to 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-level Region, the CapybaraScreenshotDiff error classes, and — under the v1 entry points — CapybaraScreenshotDiff::DSL / ::Minitest::Assertions. const_defined? never triggers const_missing, so these have to be real constants for adopter feature detection and rescue clauses to keep working — which means nothing is left to hook.
  • The driver leaf classes. Drivers::VipsDriver / Drivers::ChunkyPNGDriver are autoloaded on SnapDiff::Drivers, so the leaf name itself never warns. Reaching them through the old path still warns once for Capybara::Screenshot::Diff::Drivers — that part is a const_missing shim. Each leaf is only declared when its gem is actually installed, so defined?(...Drivers::VipsDriver) stays nil without ruby-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.

Silencing Warnings

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=1

Known Caveats

Two deliberate consequences of the lazy shim design. Both go away at 2.1, when the shimmed names go away with them:

  1. defined? / const_defined? on lazily-shimmed legacy names returns false/nil. The shims resolve via const_missing, which those checks never trigger. Feature detection like defined?(Capybara::Screenshot::Diff::ImageCompare) must move to the SnapDiff:: name. Everything in Silent by design is unaffected — those names are real constants, so defined?, const_defined? and rescue all behave as they always did.

  2. Reopening module Capybara::Screenshot::Diff::Drivers shadows the shim. The historical custom-driver monkey-patch pattern defines a fresh, empty Drivers module instead of reaching the real one. Define custom drivers under SnapDiff::Drivers instead — and note BaseDriver is gone as a superclass: class MyDriver < BaseDriver becomes include SnapDiff::Driver (it's a mixin now).

Two moves that fail silently if you miss them

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.


FAQ

"My tests pass but I see warnings. Should I worry?"

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.

"Does the DSL change at all?"

No. screenshot, assert_matches_screenshot, and capture_screenshot are stable and unchanged. All overrides (:compare, :tolerance, etc.) work identically.

"Can I mix old and new config in the same suite?"

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
end

"What if I need to roll back?"

All settings and baselines are compatible with v1.x. Simply pin your Gemfile back to "~> 1.15" and bundle update capybara-screenshot-diff.


Summary Checklist

  • 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, not rake 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 your screenshot calls — 2.0 warns about it, 2.1 removes it
  • (Optional, but do it before 2.1) Migrate config and constants to the SnapDiff namespace
  • (Optional) Silence deprecation warnings if not ready to migrate
  • Report anything surprising on the issue tracker

Upgrading to v1.13.0

Overview

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)


Quick Upgrade Path (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 comparison

That's it! Existing screenshot calls work unchanged. New methods available if needed.


What Changed

API Clarification: Primary Method is assert_matches_screenshot

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 baseline
  • screenshot(name, compare: true) — convenience wrapper (same behavior as above when compare: 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 only

Safe to override: You can safely define your own screenshot method in your test base class — the gem's implementation won't interfere.


New: capture_screenshot Method

Capture without comparing to baseline:

capture_screenshot "dynamic_page"  # No assertion

Equivalent to: screenshot "dynamic_page", compare: false


New: Diff.pending_if_new Helper

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 = true

CI 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
end

Upgrading to v1.12.0

Overview

Version 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


Quick Upgrade Path (Most Users)

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.


Breaking Changes & Migration Steps

1. Default Behavior Changes (Most Important)

Three settings now have different defaults. This is the most likely source of unexpected test failures.

blur_active_element — Now defaults to true

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 = false

hide_caret — Now defaults to true

Before (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 = false

fail_if_new — Now defaults to true in CI

Before (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 = false

Why this changed: This prevents accidental baseline additions in CI pipelines. Most teams want this behavior.


2. SVN Support Removed

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 tests

If you find SVN usage:

  1. Export your SVN repository to Git
  2. Update your CI/CD to use Git
  3. Re-commit all screenshot baselines with Git

Why this changed: SVN support was rarely used and added maintenance burden.


3. ActiveSupport No Longer Required

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 elsewhere

Why this changed: Lighter installations, faster boot times.


4. Internal API Changes

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.


New Features to Try

HTML Reporter (Recommended)

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.html

Features:

  • Side-by-side comparison with diff toggle
  • Thumbnail sidebar for navigation
  • Search functionality
  • Summary statistics

Standalone Image Comparison

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 different

Use cases:

  • PDF regression testing
  • Generated image validation
  • CI artifact verification

Perceptual Color Distance (Anti-aliasing Fix)

Eliminate false positives from font rendering differences:

# Global configuration
Capybara::Screenshot::Diff.perceptual_threshold = 2.0

# Or per-screenshot
screenshot 'dashboard', perceptual_threshold: 2.0

dE00 Scale Reference:

  • < 1.0 — Not perceptible by human eyes
  • 1-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_no_screenshot_changes

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'
end

Simplified Configuration

Use 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
end

Performance Improvements

Enjoy 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 & Rails Compatibility

Supported Versions

  • Ruby: 3.2, 3.3, 3.4, 3.5 (new!), 4.0 (new!)
  • Rails: 7.1, 7.2, 8.0

Upgrade Notes

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.


Testing Your Upgrade

Step 1: Update Gemfile

gem 'capybara-screenshot-diff', '~> 1.12.0'

Step 2: Bundle Update

bundle update capybara-screenshot-diff

Step 3: Run Tests

bin/rails test:system   # `rake test` skips test/system/ — 0 runs, no comparison

Step 4: Check for New Screenshot Failures

If tests fail with new screenshot errors in CI:

  1. Option A: Commit the new baselines (recommended if changes are intentional)
  2. Option B: Set fail_if_new = false temporarily (not recommended long-term)

Step 5: Enable HTML Reporter (Optional)

require 'capybara_screenshot_diff/reporters/html'

Run tests and open doc/screenshots/snap_diff_report.html to review differences.


Troubleshooting

"Tests fail with new screenshots in CI"

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 = false

"Screenshots look different after upgrade"

Cause: 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 = false

Then 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"

"NoMethodError on internal class"

Cause: Using internal APIs that were refactored

Solution: Use the public API instead. Check the documentation for the correct interface.


Rollback Plan

If you need to rollback:

# Pin to previous version
gem 'capybara-screenshot-diff', '~> 1.12.0'
bundle update capybara-screenshot-diff

All screenshot baselines are compatible — no data loss.


Need Help?


Summary Checklist

  • 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_new behavior
  • Decide on blur_active_element and hide_caret defaults
  • 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 🎉