Capybara::Screenshot::Diff
Stop shipping UI bugs. Take screenshots in your Capybara tests, commit baselines to git, and let CI catch visual regressions in pull requests — no cloud service, no subscription, runs entirely in your test suite.

Why this gem? Baselines live in git — review UI changes in pull requests like you review code. Runs offline, works in CI, zero vendor lock-in. Unlike Percy/Chromatic (paid SaaS), nothing to sign up for. Unlike BackstopJS, no Node required.
Quick Start (5 minutes)
Already using Capybara for system tests? Add the gem and you're ready. New to system tests? See Rails System Testing guide.
# Gemfile
gem 'capybara-screenshot-diff'
gem 'ruby-vips' # Optional: 10x faster comparisons# test/test_helper.rb
require 'capybara_screenshot_diff/minitest'# test/application_system_test_case.rb
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
include CapybaraScreenshotDiff::Minitest::Assertions
end# test/system/homepage_test.rb
class HomepageTest < ApplicationSystemTestCase
test "homepage" do
visit "/"
screenshot "homepage"
end
endThen run these steps in order:
# Step 1: Save baselines (first run always passes)
bundle exec rake test
# Step 2: Commit baselines to git
git add doc/screenshots/
git commit -m "chore: add screenshot baselines"
# Step 3: Now comparisons work — change your UI and re-run
bundle exec rake testAfter Step 1, you'll see:
doc/screenshots/
homepage.png <- your baseline (commit this)
Add diff artifacts to .gitignore — these are generated at runtime and should not be committed:
# Screenshot diff artifacts (generated, not committed)
*.diff.png
*.base.png
*.diff.webp
*.base.webp
snap_diff_report.htmlIf you skip Step 2 and push to CI, the build will fail — fail_if_new is true by default in CI.
For RSpec, Cucumber, or non-Rails setup, see Framework Setup.
For Non-Rails Projects (Hugo, Jekyll, Static Sites)
require 'capybara_screenshot_diff/static'
CapybaraScreenshotDiff.serve("_site") # or "public", "build", "dist"Then commit baselines to git just like Rails. Full setup.
What Happens When a Screenshot Changes
The test fails with a clear message and generates diff files:
Screenshot does not match for 'homepage':
({"area_size":1250,"region":[0,19,199,83],"max_color_distance":42.5})
Open doc/screenshots/homepage.diff.png to see exactly what changed. If the change is intentional, delete the baseline and re-run to update it.
| File | Description |
|---|---|
homepage.png |
Committed baseline |
homepage.diff.png |
Visual diff with changes highlighted in red |
homepage.heatmap.diff.png |
Heatmap of pixel differences |
Web UI for Reviewing Screenshot Changes
Add one line to get an interactive dashboard for reviewing all screenshot differences:
# test/test_helper.rb
require 'capybara_screenshot_diff/reporters/html'After tests run, open doc/screenshots/snap_diff_report.html:
Review all visual changes in one place — no need to hunt through .diff.png files. 4 view modes (both/base/new/heatmap), per-image zoom, annotation toggle, keyboard navigation, and search.
In GitHub Actions, one step uploads the report, posts a PR comment with the link, and adds a job summary:
- name: Upload screenshot report
if: failure()
uses: snap-diff/snap_diff-capybara/.github/actions/upload-screenshots@master
with:
name: screenshots
pr-comment: 'true'See CI Integration for full setup including Ruby + libvips action and baseline update workflow.
Compare Any Two Images
Works without a browser — PDFs, generated images, CI artifacts:
result = Capybara::Screenshot::Diff.compare("baseline.png", "current.png")
result.different? # => true if visually different
result.quick_equal? # => true if byte-identicalNext Steps
-
Crop to element:
screenshot "form", crop: "#main-form" -
Ignore regions:
screenshot "dashboard", skip_area: [".timestamp"] -
Disable animations:
Capybara::Screenshot.disable_animations = true -
Set window size:
Capybara::Screenshot.window_size = [1280, 1024]
Handling Flaky Tests
Defaults work for most Rails apps — blur_active_element, hide_caret, and fail_if_new (in CI) are enabled automatically.
If screenshots differ between CI and local, set a comparison threshold:
Capybara::Screenshot::Diff.configure do |screenshot, diff|
screenshot.window_size = [1280, 1024] # consistent viewport
diff.perceptual_threshold = 2.0 # ignore anti-aliasing (VIPS only)
# or: diff.tolerance = 0.001 # percentage-based (default for VIPS)
endSee Choosing the Right Method for detailed comparison options.
FAQ
Yes. First run saves baselines and always passes. Run tests again to compare against committed baselines.
Delete the baseline file and re-run tests: rm doc/screenshots/homepage.png && bundle exec rake test. Or update all: rm -rf doc/screenshots/ && bundle exec rake test.
Enable Capybara::Screenshot.disable_animations = true to freeze CSS animations/transitions before each capture. Or use stability_time_limit: 1 to wait for animations to finish.
Set window_size for consistent dimensions and use perceptual_threshold: 2.0 to ignore anti-aliasing differences across environments.
Comparisons add ~50ms per image with VIPS. Without ruby-vips, ChunkyPNG is used (slower but no system dependency). stability_time_limit adds wait time — keep it low (0.1-0.5s) or use disable_animations instead.
DEBUG=1 bundle exec rake test keeps .diff.png files for inspection.
Installation
Requirements: Ruby 3.2+. Rails 7.1+ for Rails integration; non-Rails projects supported via CapybaraScreenshotDiff.serve(). For the :vips driver: libvips 8.9+. On macOS: brew install vips. On Ubuntu: apt-get install libvips-dev.
Docs
- Framework Setup — Minitest, RSpec, Cucumber
- CI & Non-Rails Integration — GitHub Actions, reusable action, static sites, baseline updates
- Configuration Reference — all options explained
- Image Processing Drivers — VIPS, ChunkyPNG, perceptual threshold
- Screenshot Organization — groups, sections, cropping, multi-browser
- Web UI & Custom Reporters — interactive report, custom reporters
Development
After checking out the repo, run bin/setup then rake test. See Docker Testing for reproducible CI-matching test runs.
Contributing
See CONTRIBUTING.md
License
The gem is available as open source under the terms of the MIT License.
