Bug reports and pull requests are welcome on GitHub at https://github.com/snap-diff/snap_diff-capybara. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
- Ruby 3.2+ (the project tests against 3.2–4.0)
- libvips 8.9+ (optional, for the VIPS driver). Install with:
- macOS:
brew install vips - Ubuntu:
sudo apt-get install libvips-dev
- macOS:
- Chrome (for integration tests with
selenium_chrome_headlessorcuprite)
bin/setupThis installs gem dependencies and prepares the development environment.
# All tests
rake test
# Unit tests only (faster, no browser required)
rake test:unit
# Integration tests (require a browser)
rake test:integration
# Run specific test file
ruby -Ilib:test test/unit/image_compare_test.rb
# Run with a specific screenshot driver
SCREENSHOT_DRIVER=vips rake test
# Run with a specific Capybara driver (integration tests)
CAPYBARA_DRIVER=cuprite rake test:integration
# Record new baseline screenshots (integration tests)
RECORD_SCREENSHOTS=1 bin/dtestUse Docker for reproducible CI-matching test runs:
bin/dtest # Run all tests in Docker
bin/dtest test/integration/ # Run specific directorySee docs/docker-testing.md for details.
# Ruby linting (Standard Ruby)
bundle exec standardrb
# Auto-fix
bundle exec standardrb --fixThe full Test Ruby & Rails matrix does not run on every PR. It is off by
default to stay inside free-tier Actions minutes, which means the job that breaks
master can be a job that never ran on your PR.
A pull request runs only the critical jobs — Lint, Functional Test and the
minimal-setup check. The version matrix is the master gate; it also runs on
manual dispatch, the weekly cron, and any PR carrying the full-ci label.
Add full-ci by hand when a change is version-sensitive and you want the
answer before merging rather than after: anything conditional on defined?,
respond_to? or RUBY_VERSION, anything touching subprocess or environment
handling, and anything you would be surprised to see break on JRuby.
The matrix is a spanning set, not a cross product — every Ruby appears at
least once and every Rails appears at least once. A cross product re-proves the
same facts: TestsWithoutAssertions being Rails 7.2+ shows up on every Ruby
running 7.1, and JRuby lacking Kernel#fork shows up on every Rails. The
combinations outside the span are not run anywhere; that is the trade for the
matrix costing ~46 min instead of ~130.
Two things about reading CI results here:
cancelledis not a pass. It means a later push superseded the run, or fail-fast killed the cell before it reported. It occupies the same slot as a verdict while carrying none — a JRuby lane sat broken for 15 consecutive runs looking exactly like this.- After merging, check
master.gh run list --branch master --workflow Test --limit 1. A redmasterblocks the next merge. Pass--workflow Test: without it you get whichever workflow ran last, which has already reported a Dependabot success whileTestwas failing on the same commit.
This project uses Standard Ruby for consistent formatting. Run bundle exec standardrb --fix before committing.
The gem supports Ruby 3.2 through 4.0 (including JRuby). When adding features:
- Avoid syntax or APIs that are only available in newer Ruby versions
- Test with the full matrix (see
.github/workflows/test.yml) - Be mindful of JRuby compatibility (no C extensions, avoid platform-specific code)
- Classes/modules:
CamelCase(Ruby convention) - Methods:
snake_case - Test files:
snake_case_test.rb(matching the class they test) - Screenshot names: descriptive, kebab-case or snake_case
- Value objects — immutable data carriers (e.g.,
Difference,Comparison,Region) - Strategy pattern — interchangeable algorithms (e.g.,
VipsDriver/ChunkyPNGDriver) - Layered comparison — fast-then-slow strategy in
ImageCompare(byte → pixel → region) - Thread safety — thread-local state for per-test data, mutex for shared state
- Test doubles — use
TestDoubles::TestDriverandTestDoubles::TestPath(seetest/support/test_doubles.rb)
- Service objects — this codebase prefers explicit method calls over service object wrappers
- Global state mutation at runtime — configuration should be set once before tests
- Monkey-patching — prefer composition over patching Capybara internals
- Run the full test suite —
rake testshould pass - Run the linter —
bundle exec standardrbshould pass - Add tests for new functionality or bug fixes
- Update docs if changing behavior or adding features
- Update CHANGELOG.md under the
[Unreleased]section
Include:
- What changed (one-line summary)
- Why it changed (motivation, related issue)
- How it was tested
- Screenshots if the change affects visual output
- A maintainer will review your PR
- Address review feedback with additional commits (no force-pushing)
- Once approved, a maintainer will merge
- Unit tests go in
test/unit/and test a single class in isolation. Use test doubles fromtest/support/test_doubles.rbrather than testing with real image files or browsers. - Integration tests go in
test/integration/and exercise the full capture → compare → report pipeline with a real browser. These are slower and require Chrome. - Driver contract tests (
test/support/driver_contract_tests.rb) verify that all image processing drivers meet the same interface. Add a contract test when adding a new driver method. - Test environment isolation: each unit test snapshots and restores
Capybara::ScreenshotandCapybara::Screenshot::Diffglobal state. Don't mutate globals outside of setup/teardown.
- Create
lib/capybara/screenshot/diff/drivers/new_driver.rbinheriting fromBaseDriver - Implement required methods:
load_images,from_file,save_image_to,same_pixels?,find_difference_region,crop,add_black_box,draw_rectangles,resize_image_to - Register in
Utils.detect_available_driversandUtils.find_driver_class_for - Add driver contract tests in
test/unit/drivers/new_driver_test.rb - Add integration tests exercising the new driver
- Create
lib/capybara_screenshot_diff/reporters/new_reporter.rb - Implement
record(assertions)andfinalizemethods - Register with
CapybaraScreenshotDiff.reporters << MyReporter.new - Add tests in
test/unit/reporters/
To release a new version:
- Update the version number in lib/snap_diff/version.rb — the
only place it lives; the gemspec and the legacy
lib/capybara/screenshot/diff/version.rbboth read it - Update CHANGELOG.md with the new version and date
- Go to Actions → Release,
click Run workflow and enter the version number. The workflow verifies the version
against
lib/, tests, tags, publishes both gem names, and creates the GitHub Release.
Do not use rake release. It is inherited from bundler/gem_tasks and publishes only
capybara-screenshot-diff, skipping the snap_diff-capybara mirror — the two gems must
never diverge in version. The full runbook, including the trusted-publishing prerequisites
and what to do when a run fails halfway, is in docs/RELEASE_PREP.md.