diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2958c4ad..1208e936 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -37,7 +37,7 @@ If applicable, add the `.diff.png` or `.heatmap.diff.png` files to help explain - **Ruby version:** (e.g., 3.4.1) - **Rails version:** (e.g., 8.0) or N/A (non-Rails project) - **`capybara-screenshot-diff` version:** (e.g., 1.12.0) -- **Image processing driver:** (`:vips` or `:chunky_png`) +- **libvips version:** (`vips --version`; the only image backend since 2.1) - **Capybara driver:** (e.g., `selenium_chrome_headless`, `cuprite`) - **Operating system:** (e.g., macOS 14, Ubuntu 24.04) - **CI environment:** (e.g., GitHub Actions, local only) @@ -60,5 +60,5 @@ DEBUG=1 bundle exec rake test Add any other context about the problem here. For example: - Is it specific to CI vs local? -- Does it reproduce with both VIPS and ChunkyPNG drivers? +- Does it reproduce with a different libvips version? - Is this a regression from a previous version? diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index da99c0dc..40c98cb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: - name: Verify version run: | GEM_VERSION="${{ github.event.inputs.version }}" - CODE_VERSION=$(ruby -I lib -r capybara/screenshot/diff/version -e "puts Capybara::Screenshot::Diff::VERSION") + CODE_VERSION=$(ruby -I lib -r snap_diff/version -e "puts SnapDiff::VERSION") if [ "$GEM_VERSION" != "$CODE_VERSION" ]; then echo "Version mismatch: input=$GEM_VERSION code=$CODE_VERSION" exit 1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c01c8dd..9f1f77d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,8 +57,6 @@ jobs: ruby-version: "4.0" - run: bin/rake test - env: - SCREENSHOT_DRIVER: vips functional-test: name: Functional Test @@ -81,7 +79,6 @@ jobs: env: COVERAGE: enabled DISABLE_SKIP_TESTS: 1 - SCREENSHOT_DRIVER: vips - uses: ./.github/actions/upload-screenshots if: failure() @@ -137,13 +134,9 @@ jobs: env: BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }} - # The JRuby cells are the only place the vips driver runs on a non-MRI - # engine, and `bin/rake test` otherwise leaves them on chunky_png (the - # default in test/system_test_case.rb) -- so ruby-vips' FFI path was - # only ever loaded there, never driven end to end. Test Drivers covers - # both drivers on CRuby; chunky_png stays covered on JRuby by its own - # unit tests, which do not read this variable. - SCREENSHOT_DRIVER: ${{ contains(matrix.ruby-version, 'jruby') && 'vips' || 'chunky_png' }} + # SCREENSHOT_DRIVER is gone: 2.1 removed the driver abstraction, so every + # cell -- JRuby included -- runs libvips. ruby-vips' FFI path on JRuby is + # now a property of the whole matrix rather than of one variable. steps: - uses: actions/checkout@v7 @@ -173,7 +166,10 @@ jobs: max_attempts: 2 command: bin/rake test - matrix-screenshot-driver: + # Was `matrix-screenshot-driver`, a capybara-driver x screenshot-driver grid. + # 2.1 removed the screenshot-driver axis, so this is a capybara-driver matrix + # and nothing else -- half the cells, same coverage. + matrix-capybara-driver: name: Test Drivers # Cost-intentional: full matrix stays off PRs by default (free-tier # Actions minutes). Runs on master pushes, manual dispatch, the weekly @@ -188,7 +184,6 @@ jobs: strategy: matrix: capybara-driver: [ selenium_headless, selenium_chrome_headless, cuprite ] - screenshot-driver: [ vips, chunky_png ] runs-on: ubuntu-latest @@ -200,7 +195,8 @@ jobs: - uses: ./.github/actions/setup-ruby-and-dependencies with: ruby-version: "4.0" - cache-apt-packages: ${{ matrix.screenshot-driver == 'vips' }} + # libvips is required now, not one of two options. + cache-apt-packages: true - name: Cache Selenium uses: actions/cache@v6 @@ -211,12 +207,11 @@ jobs: - run: bin/rake test:integration env: CAPYBARA_DRIVER: ${{ matrix.capybara-driver }} - SCREENSHOT_DRIVER: ${{ matrix.screenshot-driver }} - uses: ./.github/actions/upload-screenshots if: failure() with: - name: screenshots-${{ matrix.capybara-driver }}-${{ matrix.screenshot-driver }} + name: screenshots-${{ matrix.capybara-driver }} test-report-upload: name: Test Report Upload diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 581f276e..923a079d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ to adhere to the [Contributor Covenant](CODE_OF_CONDUCT.md) code of conduct. ### Prerequisites - **Ruby 3.2+** (the project tests against 3.2–4.0) -- **libvips 8.9+** (optional, for the VIPS driver). Install with: +- **libvips 8.9+** — required. It is the only image backend, and `ruby-vips` is a runtime dependency of the gem. Install the system library with: - macOS: `brew install vips` - Ubuntu: `sudo apt-get install libvips-dev` - **Chrome** (for integration tests with `selenium_chrome_headless` or `cuprite`) @@ -37,9 +37,6 @@ 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 @@ -90,9 +87,9 @@ The gem supports Ruby 3.2 through 4.0 (including JRuby). When adding features: ### Architecture patterns -- **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) +- **Value objects** — immutable data carriers (e.g., `ComparisonResult`, `Comparison::Images`, `Region`) +- **Layered comparison** — fast-then-slow strategy in `SnapDiff::Comparison` (byte → pixel → region) +- **One image backend** — `SnapDiff::Drivers::VipsDriver`. 2.1 removed the driver abstraction; do not reintroduce a strategy layer around it - **Thread safety** — thread-local state for per-test data, mutex for shared state - **Test doubles** — use `TestDoubles::TestDriver` and `TestDoubles::TestPath` (see `test/support/test_doubles.rb`) @@ -130,29 +127,21 @@ Include: - **Unit tests** go in `test/unit/` and test a single class in isolation. Use test doubles from `test/support/test_doubles.rb` rather 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::Screenshot` and `Capybara::Screenshot::Diff` global state. Don't mutate globals outside of setup/teardown. - -## Adding a New Driver - -1. Create `lib/capybara/screenshot/diff/drivers/new_driver.rb` inheriting from `BaseDriver` -2. Implement required methods: `load_images`, `from_file`, `save_image_to`, `same_pixels?`, `find_difference_region`, `crop`, `add_black_box`, `draw_rectangles`, `resize_image_to` -3. Register in `Utils.detect_available_drivers` and `Utils.find_driver_class_for` -4. Add driver contract tests in `test/unit/drivers/new_driver_test.rb` -5. Add integration tests exercising the new driver +- **Driver contract tests** (`test/support/driver_contract_tests.rb`) pin the interface `Comparison`, `ImagePreprocessor`, `Screenshoter` and `AnnotationService` all call on `VipsDriver` — signatures, arity, and `load_images` slot order. It is no longer a *shared* contract (2.1 left one backend), but drift in any of those would break the callers silently. Extend it when you change a method the callers use. +- **Test environment isolation:** each unit test snapshots and restores `SnapDiff.config` by instance variable. Don't mutate global config outside of setup/teardown. ## Adding a New Reporter -1. Create `lib/capybara_screenshot_diff/reporters/new_reporter.rb` -2. Implement `record(assertions)` and `finalize` methods -3. Register with `CapybaraScreenshotDiff.reporters << MyReporter.new` +1. Create `lib/snap_diff/reporters/new_reporter.rb` +2. Implement `record(assertions)`, `finalize` and `summary` methods +3. Register with `SnapDiff::Reporting.register(MyReporter.new)` 4. Add tests in `test/unit/reporters/` ## Releasing To release a new version: -1. Update the version number in [lib/capybara/screenshot/diff/version.rb](lib/capybara/screenshot/diff/version.rb) +1. Update the version number in [lib/snap_diff/version.rb](lib/snap_diff/version.rb) 2. Update [CHANGELOG.md](CHANGELOG.md) with the new version and date 3. Create a GitHub Release: - Go to [Actions → Release](https://github.com/snap-diff/snap_diff-capybara/actions/workflows/release.yml) diff --git a/README.md b/README.md index 909e337a..b27d9229 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Test](https://github.com/snap-diff/snap_diff-capybara/actions/workflows/test.yml/badge.svg)](https://github.com/snap-diff/snap_diff-capybara/actions/workflows/test.yml) [![DeepWiki](https://img.shields.io/badge/DeepWiki-snap--diff%2Fsnap__diff--capybara-blue.svg?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTEyIDJhMTAgMTAgMCAxIDAgMCAyMCAxMCAxMCAwIDAgMCAwLTIweiIvPjxwYXRoIGQ9Ik0xMiA2djEyIi8+PHBhdGggZD0iTTYgMTJoMTIiLz48L3N2Zz4=)](https://deepwiki.com/snap-diff/snap_diff-capybara) -# Capybara::Screenshot::Diff +# SnapDiff for Capybara 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. @@ -11,9 +11,9 @@ Stop shipping UI bugs. Take screenshots in your Capybara tests, commit baselines **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. -> **2.0 experiment (beta):** the gem is moving to a `SnapDiff` canonical namespace. Opt in with `gem "capybara-screenshot-diff", "2.0.0.beta3"` (or the latest 2.0.0 prerelease; prereleases are never installed by default — normal installs stay on 1.x). Legacy names keep working; the first legacy API a process touches prints one migration notice (lazily shimmed constants also warn once each — see [which names warn](docs/UPGRADING.md#deprecation-warnings)), silenceable via `SnapDiff.silence_deprecations = true` or `SNAP_DIFF_SILENCE_DEPRECATIONS=1`. Writing new code? Start from [SnapDiff — the canonical API](docs/snapdiff.md), which uses canonical names only. Migrating an existing suite? See the [upgrade guide](docs/UPGRADING.md). Share feedback on [#166](https://github.com/snap-diff/snap_diff-capybara/issues/166). +> **2.1 removed the v1 API.** Everything lives under `SnapDiff` now — the `Capybara::Screenshot::Diff` and `CapybaraScreenshotDiff` namespaces, the `capybara_screenshot_diff/*` require paths, the ChunkyPNG driver, the `driver:` setting and `shift_distance_limit` are gone, not deprecated. 2.0 was the transitional release where both APIs worked and everything that died warned. Coming from 1.x or 2.0? The [upgrade guide](docs/UPGRADING.md) has the change list; the real-world migration was 17 lines across two files. > -> Starting with the 2.0 prereleases the gem is also published as [`snap_diff-capybara`](https://rubygems.org/gems/snap_diff-capybara) — identical content and versions under the forward-looking name, matching this repository. Install either; don't install both. +> The gem is published under two names — [`snap_diff-capybara`](https://rubygems.org/gems/snap_diff-capybara) (matching this repository) and [`capybara-screenshot-diff`](https://rubygems.org/gems/capybara-screenshot-diff) — with identical content and versions. Install either; don't install both. ## Quick Start (5 minutes) @@ -21,19 +21,20 @@ Stop shipping UI bugs. Take screenshots in your Capybara tests, commit baselines ```ruby # Gemfile -gem 'capybara-screenshot-diff' -gem 'ruby-vips' # Optional: 10x faster comparisons +gem 'snap_diff-capybara' +# ruby-vips comes with the gem since 2.1; libvips itself is a system package +# (brew install vips / apt-get install libvips). ``` ```ruby # test/test_helper.rb -require 'capybara_screenshot_diff/minitest' +require 'snap_diff/integrations/minitest' ``` ```ruby # test/application_system_test_case.rb class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - include CapybaraScreenshotDiff::Minitest::Assertions + include SnapDiff::Minitest::Assertions end ``` @@ -86,8 +87,8 @@ For RSpec, Cucumber, or non-Rails setup, see [Framework Setup](docs/framework-se ### For Non-Rails Projects (Hugo, Jekyll, Static Sites) ```ruby -require 'capybara_screenshot_diff/static' -CapybaraScreenshotDiff.serve("_site") # or "public", "build", "dist" +require 'snap_diff/static' +SnapDiff.serve("_site") # or "public", "build", "dist" ``` Then commit baselines to git just like Rails. [Full setup](docs/ci-integration.md#non-rails-projects-hugo-jekyll-static-sites). @@ -98,7 +99,7 @@ The test fails with a clear message and generates diff files: ```text Screenshot does not match for 'homepage': -({"area_size":1250,"region":[0,19,199,83],"max_color_distance":42.5}) +({"area_size":684.0,"region":[11.0,3.0,49.0,21.0],"difference_level":0.0653125}) ``` 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. @@ -115,7 +116,7 @@ Add one line to get an interactive dashboard for reviewing all screenshot differ ```ruby # test/test_helper.rb -require 'capybara_screenshot_diff/reporters/html' +require 'snap_diff/reporters/html' ``` After tests run, open `doc/screenshots/snap_diff_report.html`: @@ -129,7 +130,9 @@ See [Web UI & Custom Reporters](docs/reporters.md) for full feature details and Works without a browser — PDFs, generated images, CI artifacts: ```ruby -result = Capybara::Screenshot::Diff.compare("baseline.png", "current.png") +require 'snap_diff' + +result = SnapDiff.compare("baseline.png", "current.png") result.different? # => true if visually different result.quick_equal? # => true if byte-identical ``` @@ -138,8 +141,8 @@ result.quick_equal? # => true if byte-identical - **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]` +- **Disable animations:** `SnapDiff.config.disable_animations = true` +- **Set window size:** `SnapDiff.config.window_size = [1280, 1024]` ## Handling Flaky Tests @@ -148,10 +151,10 @@ Defaults work for most Rails apps — `blur_active_element`, `hide_caret`, and ` If screenshots differ between CI and local, set a comparison threshold: ```ruby -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) +SnapDiff.configure do |config| + config.window_size = [1280, 1024] # consistent viewport + config.perceptual_threshold = 2.0 # ignore anti-aliasing + # or: config.tolerance = 0.001 # percentage-based (the default) end ``` @@ -174,7 +177,7 @@ Delete the baseline file and re-run tests: `rm doc/screenshots/homepage.png && b
CSS animations make my screenshots flaky -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. +Enable `SnapDiff.config.disable_animations = true` to freeze CSS animations/transitions before each capture. Or use `stability_time_limit: 1` to wait for animations to finish.
@@ -186,7 +189,7 @@ Set `window_size` for consistent dimensions and use `perceptual_threshold: 2.0`
Will this slow down my tests? -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. +Comparisons add ~50ms per image. `stability_time_limit` adds wait time — keep it low (0.1-0.5s) or use `disable_animations` instead.
@@ -197,15 +200,15 @@ Comparisons add ~50ms per image with VIPS. Without `ruby-vips`, ChunkyPNG is use ## 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+](https://libvips.github.io/libvips/install.html). On macOS: `brew install vips`. On Ubuntu: `apt-get install libvips-dev`. +**Requirements:** Ruby 3.2+. Rails 7.1+ for Rails integration; non-Rails projects supported via `SnapDiff.serve()`. Comparison runs on [libvips](https://libvips.github.io/libvips/install.html) (8.9+), a system package: `brew install vips` on macOS, `apt-get install libvips-dev` on Ubuntu. The `ruby-vips` binding is a runtime dependency of this gem since 2.1, so Bundler installs it for you. ## Docs -- [SnapDiff — the canonical API](docs/snapdiff.md) — setup, config, object map, custom drivers & reporters, canonical names only +- [SnapDiff — the canonical API](docs/snapdiff.md) — setup, config, object map, custom reporters - [Framework Setup](docs/framework-setup.md) — Minitest, RSpec, Cucumber - [CI & Non-Rails Integration](docs/ci-integration.md) — GitHub Actions, reusable action, static sites, baseline updates - [Configuration Reference](docs/configuration.md) — all options explained -- [Image Processing Drivers](docs/drivers.md) — VIPS, ChunkyPNG, perceptual threshold +- [Image Processing](docs/drivers.md) — libvips, perceptual threshold, tolerance - [Screenshot Organization](docs/organization.md) — groups, sections, cropping, multi-browser - [Web UI & Custom Reporters](docs/reporters.md) — interactive report, custom reporters @@ -215,7 +218,7 @@ After checking out the repo, run `bin/setup` then `rake test`. See [Docker Testi ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) +See [CONTRIBUTING.md](https://github.com/snap-diff/snap_diff-capybara/blob/master/CONTRIBUTING.md) ## License diff --git a/Rakefile b/Rakefile index c0c705ed..60cafba5 100644 --- a/Rakefile +++ b/Rakefile @@ -5,46 +5,24 @@ require "rake/testtask" task default: :test -# THE 3.0 SPLIT. +# `test:canonical` was "everything except test/legacy/", i.e. what had to +# still pass once the v1 surface was deleted. 2.1 deleted it, test/legacy/ +# went with it, and the two tasks converged on the same file list -- so the +# second name is gone rather than kept as an alias for one thing. # -# test/legacy/ holds every test whose SUBJECT is the v1 compatibility surface -# -- the old Capybara::Screenshot / CapybaraScreenshotDiff namespaces, their -# deprecation warnings, and the gates that keep lib/capybara* alias-only. -# Those tests guard the v1 contract for the whole 2.x line, so they stay and -# stay green; in 3.0 they are deleted by the same commit that deletes what -# they test: -# -# git rm -r lib/capybara* lib/capybara_screenshot_diff.rb \ -# lib/snap_diff/legacy_shims.rb lib/snap_diff/deprecation.rb \ -# test/legacy -# -# A directory rather than a list in this file: there is nothing to keep in -# sync, and the deletion is one `git rm -r`. -# -# `rake test` -- everything, today's gate. -# `rake test:canonical` -- exactly what must still pass once test/legacy and -# the v1 trees are gone. THE 3.0 GATE. -# `rake test:unit` -- unit-sized tests; test/legacy is unit-sized too -# (legacy/ marks lifetime, not kind), so it is in. -LEGACY_SURFACE_TESTS = "test/legacy/**/*_test.rb" - +# `rake test` -- THE gate. +# `rake test:unit` -- unit-sized tests. +# `rake test:integration` -- browser-driven tests. Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" t.test_files = FileList["test/**/*_test.rb"] end -desc "Run every test that must survive the 3.0 deletion of the v1 surface" -Rake::TestTask.new("test:canonical") do |t| - t.libs << "test" - t.libs << "lib" - t.test_files = FileList["test/**/*_test.rb"].exclude(LEGACY_SURFACE_TESTS) -end - Rake::TestTask.new("test:unit") do |t| t.libs << "test" t.libs << "lib" - t.test_files = FileList["test/unit/**/*_test.rb", LEGACY_SURFACE_TESTS] + t.test_files = FileList["test/unit/**/*_test.rb"] end Rake::TestTask.new("test:integration") do |t| @@ -79,17 +57,7 @@ task "clobber" do puts "Cleanup tmp/" FileUtils.rm_rf(Dir["./tmp/*"]) end - -task "test:benchmark" do - require_relative "scripts/benchmark/find_region_benchmark" - benchmark = Capybara::Screenshot::Diff::Drivers::FindRegionBenchmark.new - - puts "For Medium Screen Size: 800x600" - benchmark.for_medium_size_screens - - puts "" - puts "*" * 100 - - puts "For Small Screen Size: 80x60" - benchmark.for_small_images -end +# `test:benchmark` is deleted rather than repointed: it required +# scripts/benchmark/find_region_benchmark, which is not in this repo, so the +# task raised LoadError on every invocation -- and its body named a v1 +# constant this release removes. diff --git a/bin/console b/bin/console index 0c77c72b..85227017 100755 --- a/bin/console +++ b/bin/console @@ -2,7 +2,7 @@ # frozen_string_literal: true require "bundler/setup" -require "capybara/screenshot/diff" +require "snap_diff" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. diff --git a/bin/dtest b/bin/dtest index c5ecef7c..101b1e0e 100755 --- a/bin/dtest +++ b/bin/dtest @@ -7,7 +7,7 @@ export DOCKER_DEFAULT_PLATFORM=linux/amd64 # Define allowed environment variables to pass to Docker ALLOWED_ENV_VARS=( "CI" "DEBUG" "TEST_ENV" "RAILS_ENV" "RACK_ENV" "COVERAGE" "DISABLE_ROLLBACK_COMPARISON_RUNTIME_FILES" - "RECORD_SCREENSHOTS" "TEST" "TESTOPTS" "SCREENSHOT_DRIVER" + "RECORD_SCREENSHOTS" "TEST" "TESTOPTS" ) # Build the Docker env args string diff --git a/capybara-screenshot-diff.gemspec b/capybara-screenshot-diff.gemspec index fea3dbc2..495c5b4c 100644 --- a/capybara-screenshot-diff.gemspec +++ b/capybara-screenshot-diff.gemspec @@ -28,4 +28,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency "actionpack", ">= 7.1", "< 9" spec.add_development_dependency "activesupport", ">= 7.1", "< 9" spec.add_runtime_dependency "capybara", ">= 2", "< 4" + # 2.1 removed the driver abstraction: libvips is the only backend, so the + # gem that binds it is a hard dependency rather than something the user is + # told to add. Without this an install resolves fine and then dies at the + # first comparison -- a resolver error is the better failure. + # + # ruby-vips 2.x is the current major line; the gem itself needs system + # libvips >= 8.2, which no gemspec constraint can express -- see + # docs/drivers.md for the system package. + spec.add_runtime_dependency "ruby-vips", ">= 2.0", "< 3" end diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index e7b623e0..9b758491 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -1,6 +1,258 @@ # Upgrading -## Upgrading to v2.0 (alpha) +## Upgrading to v2.1 + +**2.0 was the transitional release: both APIs worked, and everything that was going to die warned +about it. 2.1 is the cleanup — it removes all of it, in one release. There is no 3.0.** + +Nothing here is deprecated. The v1 *implementation* is gone; touching what it held is a +`NameError` or a `NoMethodError`, not a warning. + +**Your entry point and your settings are kept on purpose, permanently.** We went looking for who +actually uses this gem before finalising the deletion, and the answer changed the plan: + +1. **`CapybaraScreenshotDiff` and `Capybara::Screenshot::Diff` still resolve.** They are eager + aliases of `SnapDiff` — the same module object, so `CapybaraScreenshotDiff::DSL` *is* + `SnapDiff::DSL`, `const_defined?` and `defined?` keep answering, and `rescue + CapybaraScreenshotDiff::CapybaraScreenshotDiffError` still catches. The v1 `require` paths + (`capybara_screenshot_diff/minitest`, `.../rspec`, `.../cucumber`, `.../dsl`, + `capybara/screenshot/diff`) still load, as one-line entries. +2. **Every setting still answers on the old holders.** `Capybara::Screenshot::Diff.tolerance =`, + `Capybara::Screenshot.window_size =`, `include Capybara::Screenshot::Diff`, + `Capybara::Screenshot::Diff.configure { |screenshot, diff| … }` — all of it works, delegating + to the one storage in `SnapDiff.config`. Aliasing the names you *import* without the names you + *call* would be worse than aliasing neither: the constant resolves, you think you are fine, + and line 2 of your test helper explodes. +3. **`driver` and `shift_distance_limit` raise instead of vanishing.** A removed setter that + simply disappears is the worst kind of removal — code guarded by `respond_to?` keeps running + with the setting silently doing nothing. Both now raise `ArgumentError` with a message naming + the replacement (except `driver = :vips`/`:auto`, which are accepted and ignored — see + [Image processing](#2-image-processing)). + +What is genuinely gone is the machinery: the driver abstraction, the chunky_png backend, the +deprecation channel, `SnapDiff.start`, `SnapDiff::Drivers.loaded`/`.available`, and the v1 +implementation trees under `lib/capybara*`. Roughly 3,700 lines. + +You should still migrate to `SnapDiff::*` — that is the name the docs, the errors and every +future release use — but nothing about your suite has to move in the same commit as the upgrade. + +**Estimated upgrade time:** 15 minutes. A real consumer (a Jekyll/Rails site with committed +baselines, running its suite in Docker) was upgraded end to end against this release: **17 lines +across two files, zero blockers** — 38 runs, 0 failures, 55 screenshots compared, byte-identical +before and after. Every v1 API had a canonical equivalent. + +> **Upgrading from 1.x?** Do it in two steps. Go to 2.0 first, run your suite, fix what the +> deprecation warnings point at, *then* come here. The [2.0 section](#upgrading-to-v20-from-v1x) +> below is still the guide for that first step. + +### 1. Namespaces + +#### Requires + +| Before | After | +|---|---| +| `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"` | +| `require "capybara_screenshot_diff/static"` | `require "snap_diff/static"` | +| `require "capybara/screenshot/diff"` | `require "snap_diff"` | + +Note the `integrations/` segment: `require "snap_diff/minitest"` is a `LoadError`. + +Every "Before" line above still works — they are kept as one-line entries that require the +canonical path for you. The exceptions are `capybara_screenshot_diff/reporters/html` and +`capybara_screenshot_diff/static`, which are gone: nothing was found using them, and unlike the +integration entries they were never the line a test helper opens with. + +#### Gemfile + +The gem is published under two names with identical content and versions. Both still install and +both auto-require. + +```ruby +gem "snap_diff-capybara" # the forward-looking name +gem "capybara-screenshot-diff" # equally fine — same content, same version +``` + +Pick **one**. Having both in a bundle activates two gems shipping identical `lib/` paths, so every +`require` resolves from whichever activated first and the two versions can drift apart invisibly — +the lockfile shows nothing wrong. 2.1 refuses that setup at load with a +`SnapDiff::DualInstallError` naming both gems, rather than letting it run. + +#### Configuration + +Both v1 holders collapsed into one object. `SnapDiff.configure` is the single entry point. + +| Before | After | +|---|---| +| `Capybara::Screenshot. = …` | `SnapDiff.config. = …` | +| `Capybara::Screenshot::Diff. = …` | `SnapDiff.config. = …` | +| `Capybara::Screenshot::Diff.configure { \|screenshot, diff\| … }` | `SnapDiff.configure { \|config\| … }` | +| `SnapDiff.start { \|screenshot, diff\| … }` | `SnapDiff.configure { \|config\| … }` | + +The setting names are unchanged; only the receiver moves. The one rename: +`Capybara::Screenshot.enabled` is `SnapDiff.config.screenshot_enabled`, because +`SnapDiff.config.enabled` is the old `Capybara::Screenshot::Diff.enabled`. They are two genuinely +different settings feeding different branches of `active?`, and 2.1 does not collapse them — +`Capybara::Screenshot.enabled` and `Capybara::Screenshot::Diff.enabled` keep meaning what they +always meant. All settings are listed in the [Configuration Reference](configuration.md). + +**Every "Before" row above still works.** The old accessors are generated from the setting list +itself and delegate to `SnapDiff.config`, so there is one storage and two views — a write through +either is visible through the other structurally, not by synchronisation. That includes the +instance-level form: `include Capybara::Screenshot::Diff` still brings the settings in as instance +methods, and `Capybara::Screenshot::Diff.configure { |screenshot, diff| … }` still yields two +usable holders (both are the one config object now). + +`SnapDiff.start` is the one config entry point that could **not** survive: it yielded the two v1 +*modules*, not settings, so there was nothing left for it to hand you. Use +`Capybara::Screenshot::Diff.configure` (unchanged) or `SnapDiff.configure`. Same for +`SnapDiff.silence_deprecations` and `SNAP_DIFF_SILENCE_DEPRECATIONS`: with no deprecations left to +emit, there is nothing to silence. + +#### Constants and includes + +| Before | After | +|---|---| +| `Capybara::Screenshot::Os.name` | `SnapDiff::Os.name` | +| `Capybara::Screenshot::Diff::ImageCompare` | `SnapDiff::Comparison` | +| `Capybara::Screenshot::Diff::Difference` | `SnapDiff::ComparisonResult` | +| `CapybaraScreenshotDiff::Reporters::HTML` | `SnapDiff::Reporters::HTML` | +| `CapybaraScreenshotDiff::SnapManager` / `::Snap` | `SnapDiff::SnapManager` / `SnapDiff::Snap` | +| `CapybaraScreenshotDiff.serve` | `SnapDiff.serve` | +| `CapybaraScreenshotDiff.reporters <<` | `SnapDiff::Reporting.register` | +| `CapybaraScreenshotDiff.finalize_reporters!` | `SnapDiff::Reporting.finalize!` | +| `include CapybaraScreenshotDiff::DSL`
`include CapybaraScreenshotDiff::Minitest::Assertions` | `include SnapDiff::Minitest::Assertions` — **the two collapse into one**. Both old spellings keep working: they are aliases of the same modules | +| `rescue CapybaraScreenshotDiff::CapybaraScreenshotDiffError` | `rescue SnapDiff::Error` — the old name is kept as an alias. A `NameError` inside a `rescue` clause fires only when an exception is already in flight, which is the worst possible moment to discover it | + +**`Capybara::Screenshot::Os` is the one to grep for.** In the real upgrade it was the only hard +crash. On 2.0 it raises `NameError` from the shim internals once the require line has been +migrated but the constant has not — a half-migrated setup looks fine (the config setters keep +working) until `Os` aborts the whole suite before a single test runs. On 2.1 it is simply gone. + +**What did not change:** the DSL. `screenshot`, `assert_matches_screenshot`, `capture_screenshot`, +`assert_no_screenshot_changes`, `screenshot_group`, `screenshot_section` and every per-screenshot +option work exactly as before. Your baselines are unchanged and do not need re-recording. + +### 2. Image processing + +**libvips is the only backend, and `ruby-vips` is a gemspec runtime dependency (`>= 2.0, < 3`).** +You no longer add it yourself; Bundler installs it. libvips itself is still a system package +(`brew install vips`, `apt-get install libvips`) — see [Image Processing](drivers.md). + +Before this, *neither* driver was declared, so a machine without either got a runtime error deep +in a test run. Now it is a resolver error at `bundle install`. + +| Before | After | +|---|---| +| `gem "ruby-vips"` in your Gemfile | delete it (harmless to keep) | +| `SnapDiff.config.driver = :vips` / `= :auto` | accepted and ignored — delete it when convenient | +| `SnapDiff.config.driver = :chunky_png` | **`ArgumentError`** — install libvips + `ruby-vips` and delete the line | +| `screenshot "index", driver: :vips` | accepted and ignored; `driver: :chunky_png` raises | +| `shift_distance_limit` (anywhere) | **`ArgumentError`** — no equivalent, see below | +| `SnapDiff::Drivers.available` to branch on what is installed | nothing to branch on | +| `SnapDiff::Drivers.loaded[:mine] = MyDriver` | **no replacement** | +| `include SnapDiff::Driver` in your own driver | **no replacement** | + +#### Nothing here breaks quietly + +An earlier draft of 2.1 deleted `driver` and `shift_distance_limit` outright. That turned out to +be the worst available option: a deleted setter makes `respond_to?`-guarded code evaporate, and a +deleted per-screenshot option is inert because the options hash is free-form. Either way the +setting stops applying and nothing says so. So: + +- **`driver` is accepted and ignored** — globally and per screenshot — for every value except + `:chunky_png`. If you were already asking for `:vips`, you get exactly what you asked for and + no noise. `:chunky_png` raises `ArgumentError` naming libvips, `ruby-vips` and this guide, + because it is the one value the gem can no longer honour. +- **`shift_distance_limit` raises `ArgumentError`** — globally and per screenshot, whichever way + you set it, including through a `respond_to?` guard. There is no value that quietly does + nothing. +- The per-screenshot options hash is checked at the one place every option passes through, so + `screenshot "index", shift_distance_limit: 5` raises the same error as the global setter. + +#### `shift_distance_limit` has no replacement + +It was implemented **only** by the ChunkyPNG driver, and libvips has no shift-distance +comparison. Use one of: + +| Instead | Why | +|---|---| +| `median_filter_window_size` | The same idea and far faster — smooths the image before comparing. Per-screenshot only; there is no global setting for it | +| `tolerance` | Allows a ratio of pixels to differ, wherever they are | +| `color_distance_limit` | Allows each pixel to differ by a colour distance | + +See [Allowed shift distance](configuration.md#allowed-shift-distance-removed-in-21). + +#### Numbers in failure messages change + +If you assert on comparison output, note that libvips reports differently from ChunkyPNG: +`area_size` and `region` come out as floats, and there is no `max_color_distance`. For the gem's +own `a`/`c` fixtures: + +``` +before (ChunkyPNG): ({"area_size":629,"region":[11,3,48,20],"max_color_distance":187.4}) +after (libvips): ({"area_size":684.0,"region":[11.0,3.0,49.0,21.0],"difference_level":0.0653125}) +``` + +This only surfaced now because a `Comparison` built without an explicit `driver:` defaulted to +ChunkyPNG. Anyone going through the normal DSL was already on libvips via `driver: :auto`. + +#### Custom drivers: there is no migration path + +The abstraction is removed whole — the `SnapDiff::Driver` mixin, the `SnapDiff::Drivers` registry +(`.loaded`, `.available`, `.for`, `.registry`, `.detect_available`), `AVAILABLE_DRIVERS`, +`SnapDiff::Utils.detect_available_drivers`, and `:auto` selection. **A third-party driver stops +working on 2.1 and nothing replaces it.** This is a deliberate call, not an oversight: one +backend is what keeps every option meaning one thing. If you maintain one, say so on the +[issue tracker](https://github.com/snap-diff/snap_diff-capybara/issues) — that is the only thing +that can reopen it. + +`SnapDiff::Drivers::VipsDriver` survives, and `SnapDiff::Drivers` survives as its namespace — not +as a registry. + +### The whole diff, from the real upgrade + +Two files, seventeen lines: + +```diff + # test/test_helper.rb +- require "capybara_screenshot_diff/minitest" +- require "capybara_screenshot_diff/reporters/html" ++ require "snap_diff/integrations/minitest" ++ require "snap_diff/reporters/html" + +- Capybara::Screenshot.window_size = [1400, 1400] +- Capybara::Screenshot::Diff.tolerance = 0.001 ++ SnapDiff.config.window_size = [1400, 1400] ++ SnapDiff.config.tolerance = 0.001 + +- Capybara::Screenshot::Os.name ++ SnapDiff::Os.name + + # test/application_system_test_case.rb +- include CapybaraScreenshotDiff::DSL +- include CapybaraScreenshotDiff::Minitest::Assertions ++ include SnapDiff::Minitest::Assertions +``` + +### Checklist + +- [ ] `grep -rn 'capybara_screenshot_diff\|capybara/screenshot/diff' test/ spec/ features/` — the require lines +- [ ] `grep -rn 'Capybara::Screenshot\|CapybaraScreenshotDiff' test/ spec/ features/ config/` — constants and settings. These keep working, so this one is a rename-at-leisure list, not a blocker. The exception is `Capybara::Screenshot::Os`, which is genuinely gone +- [ ] `grep -rn 'driver:\|shift_distance_limit\|silence_deprecations\|SnapDiff.start' .` — the driver-era settings. `shift_distance_limit` and `driver: :chunky_png` now raise rather than going quiet, so the suite will find them for you if you skip this step +- [ ] Remove `gem "ruby-vips"` and any `chunky_png` / `oily_png` lines from your Gemfile +- [ ] `bundle install`, then run the suite — baselines do not need re-recording + +--- + +## Upgrading to v2.0 (from v1.x) + +> **History.** This section describes **2.0**, the transitional release, where the v1 names still +> worked and warned. On 2.1 the machinery behind them is gone — only the two entry namespaces +> survive, as aliases; see [Upgrading to v2.1](#upgrading-to-v21) above. Keep reading only if you +> are stepping from 1.x through 2.0. ### Overview @@ -210,7 +462,7 @@ warns once per process per subject, through the same channel and the same silenc |---|---|---| | 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](configuration.md#allowed-shift-distance) | +| set `shift_distance_limit` — globally or per screenshot | `shift_distance_limit` (ChunkyPNG-only) | `median_filter_window_size`, `tolerance`, or `color_distance_limit` — see [Configuration](configuration.md#allowed-shift-distance-removed-in-21) | | 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 | @@ -793,7 +1045,7 @@ All screenshot baselines are compatible — no data loss. ## Need Help? - **Documentation:** [README.md](../README.md) -- **Changelog:** [CHANGELOG.md](CHANGELOG.md) +- **Changelog:** [CHANGELOG.md](../CHANGELOG.md) - **Issues:** [GitHub Issues](https://github.com/snap-diff/snap_diff-capybara/issues) - **DeepWiki:** [Code Documentation](https://deepwiki.com/snap-diff/snap_diff-capybara) diff --git a/docs/architecture.md b/docs/architecture.md index dc864310..29c2c3f1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,8 +1,8 @@ # Architecture -This document describes the internal architecture of `capybara-screenshot-diff` — how screenshots are captured, compared, and reported, and how the components fit together. +This document describes the internal architecture of the gem — how screenshots are captured, compared, and reported, and how the components fit together. -Since the v2 namespace move (ADR-004), the implementation lives in `lib/snap_diff/` under the `SnapDiff` namespace. The old file paths (`lib/capybara/screenshot/diff/`, `lib/capybara_screenshot_diff/`) remain as thin forwarders, and the old constants resolve to the same objects via `lib/snap_diff/legacy_shims.rb` with a one-time deprecation warning. Class names below use the canonical `SnapDiff::` names, with legacy names noted where they differ. For the user-facing view of the same surface, see [SnapDiff — the canonical API](snapdiff.md). +The implementation lives in `lib/snap_diff/` under the `SnapDiff` namespace, and since 2.1 that is the only namespace: the v1 file trees (`lib/capybara/screenshot/diff/`, `lib/capybara_screenshot_diff/`), the `legacy_shims.rb` that resolved their constants, and the deprecation machinery around them were deleted. 34 files ship under `lib/`, listed in [File Layout](#file-layout) below. Class names below are the canonical ones, with the v1 name noted in passing where the history explains a shape. For the user-facing view of the same surface, see [SnapDiff — the canonical API](snapdiff.md). ## Overview @@ -42,11 +42,10 @@ Since the v2 namespace move (ADR-004), the implementation lives in `lib/snap_dif │ ▼ ┌──────────────────────────────────────────┐ -│ Drivers (image processing backends) │ -│ ┌──────────┐ ┌──────────────────────┐ │ -│ │ Vips │ │ ChunkyPNG │ │ -│ │ (fast) │ │ (no native deps) │ │ -│ └──────────┘ └──────────────────────┘ │ +│ VipsDriver (the image backend) │ +│ ┌────────────────────────────────────┐ │ +│ │ libvips via ruby-vips │ │ +│ └────────────────────────────────────┘ │ └──────────────────────────────────────────┘ ``` @@ -54,8 +53,6 @@ Since the v2 namespace move (ADR-004), the implementation lives in `lib/snap_dif ### 1. DSL Layer (`lib/snap_diff/dsl.rb`) -`SnapDiff::DSL` — `CapybaraScreenshotDiff::DSL` remains an eager same-object alias. - The entry point for test code. `assert_matches_screenshot` is the primary assertion method (captures and compares). `screenshot` is a convenience wrapper with a `compare:` option — `compare: true` (default) delegates to `assert_matches_screenshot`, while `compare: false` delegates to the new `capture_screenshot` method. `capture_screenshot` takes screenshots without assertions. `assert_no_screenshot_changes` keeps its behavior but now delegates to `assert_matches_screenshot` (that redirect is part of the #191 fix). Users can safely override `screenshot` in their test classes without affecting internal gem flow. **Flow:** @@ -92,7 +89,7 @@ The orchestrator that coordinates capture and comparison: ### 4. Comparison (`lib/snap_diff/comparison.rb`) -`SnapDiff::Comparison` (legacy name: `ImageCompare`); its result value object is `SnapDiff::ComparisonResult` (`lib/snap_diff/comparison_result.rb`, legacy name: `Difference`). +`SnapDiff::Comparison` (v1 name: `ImageCompare`); its result value object is `SnapDiff::ComparisonResult` (`lib/snap_diff/comparison_result.rb`, v1 name: `Difference`). The comparison engine uses a **layered optimization strategy** to balance speed and accuracy: @@ -108,41 +105,35 @@ The comparison engine uses a **layered optimization strategy** to balance speed - `processed` guarantees the comparison is complete and returns the result with all metadata - `Comparison#analyze_difference` handles the actual pixel analysis, delegating to the driver -### 5. Drivers (`lib/snap_diff/drivers/`) +### 5. The image backend (`lib/snap_diff/drivers/vips_driver.rb`) -Drivers abstract image processing operations. Shared default behavior lives in the `SnapDiff::Driver` mixin (`lib/snap_diff/driver.rb`) — it replaced the old `Drivers::BaseDriver` superclass, so concrete drivers `include SnapDiff::Driver` instead of inheriting. Each driver implements: +`SnapDiff::Drivers::VipsDriver` does the image work. 2.1 removed the abstraction that used to sit around it — the `SnapDiff::Driver` mixin, the `SnapDiff::Drivers` registry (`.loaded` / `.available` / `.for` / `.detect_available`), the `driver:` setting and `driver: :auto`. `ruby-vips` is a gemspec runtime dependency, so there is nothing to detect and nothing to select; `Comparison` and `Screenshoter` each construct a `VipsDriver` directly (it is stateless). `Drivers` survives only as the namespace the class is published under. -| Operation | VipsDriver | ChunkyPNGDriver | -|-----------|-----------|-----------------| -| `load_images` | Vips::Image from file | ChunkyPNG::Image from blob | -| `same_dimension?` | Compare width × height | Same | -| `same_pixels?` | Pixel-level equality | Same | -| `find_difference_region` | Difference mask → Region | Row-by-row scan → Region | -| `crop` | Vips image crop | ChunkyPNG crop | -| `save_image_to` | Vips write_to_file | PNG save | -| `filter_image_with_median` | Vips median filter | Not supported | -| `add_black_box` | Draw filled rect | No-op (handled differently) | -| `merge` | Composite images | Not applicable | -| `highlight_mask` | Conditional color overlay | Not applicable | +| Operation | VipsDriver | +|-----------|-----------| +| `load_images` | `Vips::Image` from file | +| `same_dimension?` | Compare width × height | +| `same_pixels?` | Pixel-level equality | +| `find_difference_region` | Difference mask → Region | +| `crop` | Vips image crop | +| `save_image_to` | Vips `write_to_file` | +| `filter_image_with_median` | Vips median filter | +| `add_black_box` | Draw filled rect | +| `merge` | Composite images | +| `highlight_mask` | Conditional color overlay | -**Auto-detection:** `SnapDiff::Drivers.detect_available` tries to load `:vips` first (via `ruby-vips` gem), then `:chunky_png`. The `:auto` driver mode picks the first available. `Utils.detect_available_drivers` is the older name and one-lines into it. +**Loader cache:** `#from_file` passes `revalidate: true`. libvips caches loader operations on filename + mtime, and mtime has one-second resolution — without this, rewriting a screenshot path and re-reading it within the same second serves the PREVIOUS image. See the regression test in `test/unit/drivers/vips_driver_test.rb`. -**Registry (ADR-008 step 5b):** `SnapDiff::Drivers.loaded` is the canonical driver-class cache — a `name => class` hash filled lazily by `Utils.find_driver_class_for`, and the registration point for custom drivers (the legacy `Capybara::Screenshot::Diff::LOADED_DRIVERS` is an eager same-object alias, so registrations through either land in the same hash). `SnapDiff::Drivers.available` is the canonical read API for the detected list, and since the 3.0-readiness pass the value lives with it, as `SnapDiff::Drivers::AVAILABLE_DRIVERS` — that constant is now the published stubbing point, and the legacy `Capybara::Screenshot::Diff::AVAILABLE_DRIVERS` is an eager same-object alias of it. `SnapDiff::Drivers.for` resolves an options hash to a driver instance. See [Custom drivers](snapdiff.md#custom-drivers). +There is no custom-driver path; see [SnapDiff — the canonical API](snapdiff.md) and [Image Processing](drivers.md). ### 6. Difference Region Detection -**VipsDriver** uses a **difference mask** approach: +`VipsDriver` uses a **difference mask** approach: 1. Compute absolute difference between images: `(new - base).abs` 2. Optional: apply perceptual color distance (CIE dE00) instead of raw RGB 3. Project the mask to find the bounding region of non-zero pixels 4. Return the tight bounding box of all differences -**ChunkyPNGDriver** uses **row-by-row scanning**: -1. Scan top-to-bottom, left-to-right for first differing pixel -2. Expand left/right boundaries within each differing row -3. Extend bottom boundary to cover all differing rows -4. Supports shift detection (expensive neighbor pixel search) - ### 7. SnapManager & Snap (`lib/snap_diff/snap_manager.rb`, `lib/snap_diff/snap.rb`) **Snap** represents a single screenshot file with path management: @@ -175,7 +166,7 @@ Handles baseline retrieval from git. Uses `git show HEAD:` to extract the - Keyboard navigation and shortcuts - Responsive layout for mobile -**Custom reporters:** Implement `record(assertions)`, `finalize` and `summary`, then register via `SnapDiff::Reporting.register(reporter)` — the canonical way in, because the append happens under the mutex. The process-global reporter lifecycle (registration, notification, finalization) is owned by `SnapDiff::Reporting` (`lib/snap_diff/reporting.rb`); `CapybaraScreenshotDiff.reporters` / `.finalize_reporters!` are thin public shims over it, and `reporters` stays a mutable array for compatibility (appending directly still works, it just skips the lock). See [Custom reporters](snapdiff.md#custom-reporters). +**Custom reporters:** Implement `record(assertions)`, `finalize` and `summary`, then register via `SnapDiff::Reporting.register(reporter)` — the way in, because the append happens under the mutex. The process-global reporter lifecycle (registration, notification, finalization) is owned by `SnapDiff::Reporting` (`lib/snap_diff/reporting.rb`); `SnapDiff::Reporting.reporters` stays a mutable array (appending directly still works, it just skips the lock). See [Custom reporters](snapdiff.md#custom-reporters). ### 10. Assertion Lifecycle @@ -221,57 +212,50 @@ Test begins Since ADR-008 step 1 the storage ownership is inverted from the original v2 consolidation: **`SnapDiff::Config` (`lib/snap_diff/config.rb`) IS the storage** — one eagerly-created instance, reachable as `SnapDiff.config`, holding every setting as a plain `attr_accessor`. It is the leaf of the config require graph and requires nothing that leads back to either entry point. -The legacy `Capybara::Screenshot.*` / `Capybara::Screenshot::Diff.*` accessors are thin delegators generated from `SnapDiff::LegacyShims::CONFIG_MAPPING` (both singleton and instance methods, matching what `mattr_accessor` used to define) that forward to that one object. One storage, two views — a write through either surface is visible through the other structurally, not by synchronization. - -Since the 3.0-readiness pass, `lib/snap_diff/legacy_shims.rb` is the single file that holds the v1 surface as code: the `const_missing` forwarders, `CONFIG_MAPPING` and its generator, the derived forwarders (`Screenshot.active?`, `Diff.configure`, `Diff.default_options`, …) and `SnapDiff.start`. `Config` itself names nothing from the v1 namespaces — it declares its settings in `Config::SETTINGS`, and `LegacyShims::CONFIG_MAPPING` says which legacy holder each one is exposed on (an invariant pinned by `snap_diff_config_test.rb`). `lib/capybara/screenshot/diff/config_legacy.rb` remains at the old path as a pair of requires. +2.1 removed the second view. `lib/snap_diff/legacy_shims.rb` — the single file that held the v1 surface as code (the `const_missing` forwarders, `CONFIG_MAPPING` and its delegator generator, the derived forwarders, `SnapDiff.start`) — is deleted, along with `lib/capybara/screenshot/diff/config_legacy.rb`. `Config` declares its settings in `Config::SETTINGS` and nothing maps them onto anything else. -The two legacy views are organized into two namespaces: +All 25 are flat on one object; `Config::SETTINGS` still declares them in two informal groups, capture first and comparison second: -**`Capybara::Screenshot`** — capture settings: -- `window_size`, `stability_time_limit`, `blur_active_element`, `hide_caret`, `disable_animations` -- `save_path`, `root`, `screenshot_format`, `add_driver_path`, `add_os_path` -- `enabled`, `capybara_screenshot_options` +**Capture (13):** `add_driver_path`, `add_os_path`, `blur_active_element`, `screenshot_enabled`, `hide_caret`, `disable_animations`, `root`, `stability_time_limit`, `window_size`, `save_path`, `use_lfs`, `screenshot_format`, `capybara_screenshot_options` -**`Capybara::Screenshot::Diff`** — comparison settings: -- `driver`, `tolerance`, `color_distance_limit`, `perceptual_threshold`, `shift_distance_limit` -- `area_size_limit`, `skip_area`, `fail_if_new`, `fail_on_difference`, `delayed` +**Comparison (12):** `delayed`, `area_size_limit`, `fail_if_new`, `pending_if_new`, `fail_on_difference`, `color_distance_limit`, `enabled`, `skip_area`, `tolerance`, `perceptual_threshold`, `screenshoter`, `manager` -The canonical way in is `SnapDiff.configure { |config| ... }` (all 27 settings flat on one object). `SnapDiff.start` and `Capybara::Screenshot::Diff.configure` are the two-holder block shape over the same storage — since ADR-008 step 7b, `Diff.configure` forwards to `SnapDiff.start` rather than the other way round. +`SnapDiff.configure { |config| ... }` is the only block form — the two-holder shape (`SnapDiff.start`, `Capybara::Screenshot::Diff.configure`) went with the holders it yielded. `screenshot_enabled` is the one setting whose name differs from its v1 spelling, because a flat object cannot carry two attributes called `enabled`. -`Config` also owns the derived values that used to live on the legacy modules: `active?` (ex `Capybara::Screenshot.active?`), `screenshot_area` / `screenshot_area_abs`, and `default_options` (ex `Capybara::Screenshot::Diff.default_options`, the option hash handed to `SnapDiff::Comparison`). The legacy module methods one-line forward here. +`Config` also owns the derived values that used to live on the v1 modules: `active?` (ex `Capybara::Screenshot.active?`, which reads both `enabled` flags), `screenshot_area` / `screenshot_area_abs`, and `default_options` (ex `Capybara::Screenshot::Diff.default_options`, the option hash handed to `SnapDiff::Comparison`). **Default timing contract:** every default is evaluated once, in `Config#initialize`, which runs at require time of `config.rb` — the same load moment the old `mattr_accessor` default blocks evaluated at. `fail_if_new` (from `ENV["CI"]`) and `root` (from `Rails.root`) must never become lazy read-time defaults. The one deliberately live value is `default_options[:wait]`, a method-body read of `Capybara.default_max_wait_time`. ## File Layout +All 34 packaged files, one namespace: + ``` lib/ - snap_diff.rb # SnapDiff module: compare/start/configure/config - snap_diff/ # Canonical implementation (v2) + snap_diff.rb # SnapDiff module: compare/configure/config + snap_diff-capybara.rb # Bundler auto-require entry for gem "snap_diff-capybara" + snap_diff/ dsl.rb # screenshot(), screenshot_group(), etc. - config.rb # SnapDiff::Config — THE storage for all 27 settings + config.rb # SnapDiff::Config — THE storage for all 25 settings errors.rb # Error / ExpectationNotMet / UnstableImage / WindowSizeMismatchError - region.rb # SnapDiff::Region — bounding box (+ eager top-level ::Region alias) - deprecation.rb # Warn-once-per-constant machinery - legacy_shims.rb # const_missing forwarders for the old namespaces + error_with_filtered_backtrace.rb # Error with filtered stack + region.rb # SnapDiff::Region — bounding box comparison.rb # Layered comparison engine (ex-ImageCompare) comparison_result.rb # Comparison result value object (ex-Difference) - driver.rb # SnapDiff::Driver mixin (ex-BaseDriver superclass) - drivers.rb # Driver factory drivers/ - vips_driver.rb # VIPS image processing - chunky_png_driver.rb # ChunkyPNG image processing + vips_driver.rb # THE image backend (libvips) capture/ viewport.rb # Per-capture viewport preparation seam screenshoter.rb # Basic browser screenshot capture stable_screenshoter.rb # Stability detection wrapper screenshot_matcher.rb # Orchestrator for capture + compare - screenshot_assertion.rb # Assertion + registry objects + screenshot_assertion.rb # Assertion + registry objects, session lifecycle screenshot_namer.rb # Name/path generation with sections/groups snap_manager.rb # Screenshot file management snap.rb # Single screenshot file abstraction reporting.rb # Process-global reporter lifecycle reporters/ + default.rb # Annotated diff images + failure message html.rb # Interactive HTML report reporter templates/report.html.erb # HTML report template annotation_service.rb # Diff-image annotation (RED_RGBA / ORANGE_RGBA) @@ -279,29 +263,16 @@ lib/ area_calculator.rb # Crop/skip area coordinate resolution browser_helpers.rb # DOM manipulation helpers attempts_reporter.rb # Debug reporting for unstable captures - error_with_filtered_backtrace.rb # Error with filtered stack vcs.rb # Git baseline checkout - utils.rb # Driver detection os.rb # OS detection static.rb # Non-Rails static site serving - version.rb # Gem version + version.rb # Gem version (the gemspec reads it) integrations/ minitest.rb # Minitest assertions integration rspec.rb # RSpec matcher integration cucumber.rb # Cucumber World integration - capybara_screenshot_diff.rb # Umbrella entry point + eager error-class aliases - capybara_screenshot_diff/ # Legacy paths — mostly thin forwarders - minitest.rb / rspec.rb / cucumber.rb # Legacy entry points (load the full gem) - screenshot_assertion.rb # CapybaraScreenshotDiff session/reporter shims - ... # Everything else forwards to snap_diff/ - capybara/screenshot/diff.rb # Convenience require (loads minitest) - capybara/screenshot/diff/ - config_legacy.rb # Legacy accessor surface, delegating to SnapDiff::Config - region.rb # Forwarder to snap_diff/region.rb - version.rb # Capybara::Screenshot::Diff::VERSION (gemspec reads it) - ... # Everything else forwards to snap_diff/ ``` -Most legacy `Capybara::Screenshot::Diff::*` and `CapybaraScreenshotDiff::*` constants resolve lazily via `snap_diff/legacy_shims.rb` (`const_missing`), pointing at the same objects with a one-time deprecation warning. The error classes are the deliberate exception: they are **eager** same-object aliases, because `rescue` clauses and `defined?` / `const_defined?` feature detection in adopter code must keep behaving exactly as before (`const_defined?` never triggers `const_missing`). Same for `LOADED_DRIVERS`, pinned as an eager alias of `SnapDiff::Drivers.loaded` so user registrations through the old constant are not silently dropped. +Gone in 2.1, and worth knowing if you are reading an older commit or an old stack trace: `lib/capybara_screenshot_diff.rb` and its tree, `lib/capybara/screenshot/diff.rb` and its tree, `snap_diff/legacy_shims.rb`, `snap_diff/deprecation.rb`, `snap_diff/removal.rb`, `snap_diff/driver.rb`, `snap_diff/drivers.rb` (the registry — `drivers/` survives as a directory holding one class), `snap_diff/drivers/chunky_png_driver.rb` and `snap_diff/utils.rb` (driver detection). See [SnapDiff — the canonical API](snapdiff.md) for the canonical surface, and [UPGRADING.md](UPGRADING.md) for the migration guide. diff --git a/docs/ci-integration.md b/docs/ci-integration.md index 65d0806f..1878cd02 100644 --- a/docs/ci-integration.md +++ b/docs/ci-integration.md @@ -9,15 +9,6 @@ require 'snap_diff/static' SnapDiff.serve("_site") # or "public", "build", "dist" ``` -
-Legacy names (still supported) - -```ruby -require 'capybara_screenshot_diff/static' -CapybaraScreenshotDiff.serve("_site") -``` -
- This sets up Capybara to serve static files and configures screenshot paths automatically. ## .gitignore Setup @@ -33,8 +24,7 @@ Only commit the baseline screenshots (e.g., `homepage.png`). The `.base.png`, `. Add to your test helper: ```ruby -require 'snap_diff/reporters/html' # canonical -# require 'capybara_screenshot_diff/reporters/html' # legacy, same thing +require 'snap_diff/reporters/html' ``` ### 2. Reusable composite action (recommended) diff --git a/docs/configuration.md b/docs/configuration.md index 2406f5ed..7c91337a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -2,7 +2,8 @@ ## Quick Setup -**Canonical (v2):** every setting lives on one flat object, `SnapDiff.config`. +Every setting lives on one flat object, `SnapDiff.config` (a `SnapDiff::Config`), and +`SnapDiff.configure` is the single way in. ```ruby # In test_helper.rb or rails_helper.rb @@ -11,54 +12,40 @@ SnapDiff.configure do |config| config.stability_time_limit = 1 config.blur_active_element = true config.hide_caret = true - config.driver = :vips config.tolerance = 0.0005 config.color_distance_limit = 15 end ``` -**Legacy (still supported):** the two-holder block, split across `Capybara::Screenshot` and -`Capybara::Screenshot::Diff`. - -```ruby -Capybara::Screenshot::Diff.configure do |screenshot, diff| - screenshot.window_size = [1280, 1024] - screenshot.stability_time_limit = 1 - screenshot.blur_active_element = true - screenshot.hide_caret = true - diff.driver = :vips - diff.tolerance = 0.0005 - diff.color_distance_limit = 15 -end -``` - -`SnapDiff::Config` **is** the storage; the legacy accessors are thin delegators onto it. There is -one source of truth, so a write through either surface is visible through the other — mixing them -is safe, and you can migrate a suite one line at a time: +Or set them one at a time: ```ruby SnapDiff.config.window_size = [1280, 1024] -Capybara::Screenshot.window_size # => [1280, 1024] +SnapDiff.config.tolerance # => 0.0005 ``` -Every option name below is identical on both surfaces — only the receiver changes. The one -exception: `Capybara::Screenshot.enabled` is `SnapDiff.config.screenshot_enabled`, because -`SnapDiff.config.enabled` is taken by `Capybara::Screenshot::Diff.enabled`. See -[SnapDiff — the canonical API](snapdiff.md) for the full SnapDiff-native surface. +There are 25 settings, all of them on this object. The v1 two-holder form — the +`Capybara::Screenshot` / `Capybara::Screenshot::Diff` accessors and the +`Diff.configure { |screenshot, diff| }` block — was removed in 2.1; see +[UPGRADING.md](UPGRADING.md#upgrading-to-v21). The option names did not change, only the +receiver. The one exception: the old `Capybara::Screenshot.enabled` is +`SnapDiff.config.screenshot_enabled`, because `SnapDiff.config.enabled` is the old +`Capybara::Screenshot::Diff.enabled`. See +[SnapDiff — the canonical API](snapdiff.md) for the full object map. **Note:** `fail_if_new` defaults to `true` in CI environments (when `ENV['CI']` is set). New screenshots are allowed locally but rejected in CI — no configuration needed. -**Note:** Setting `Capybara::Screenshot.enabled = false` is sufficient to disable all screenshots. There is no need to define no-op modules or monkey-patch the gem. +**Note:** Setting `SnapDiff.config.screenshot_enabled = false` is sufficient to disable all screenshots. There is no need to define no-op modules or monkey-patch the gem. ## Recommended tolerance values -| Use Case | VIPS `tolerance` | ChunkyPNG `color_distance_limit` | `stability_time_limit` | -|----------|-----------------|--------------------------------|----------------------| +| Use Case | `tolerance` | `color_distance_limit` | `stability_time_limit` | +|----------|-------------|------------------------|----------------------| | Animated/complex pages | 0.01 | 30 | 2s | | Standard Rails apps | 0.001 (default) | 15 | 1s | | Pixel-perfect design tests | 0.0001 | 5 | 1s | -**Note:** VIPS defaults to `tolerance: 0.001` (allows 0.1% pixel difference). ChunkyPNG has no default tolerance. +**Note:** `tolerance` defaults to 0.001 (allows 0.1% pixel difference). ## Choosing the Right Color Comparison Method @@ -66,20 +53,20 @@ exception: `Capybara::Screenshot.enabled` is `SnapDiff.config.screenshot_enabled ### Step 1: Choose color comparison method (pick ONE) -| Method | Scale | Driver | Best for | -|--------|-------|--------|----------| -| `perceptual_threshold` | 0-100+ (dE00) | VIPS only | Cross-OS/browser font rendering, anti-aliasing | -| `color_distance_limit` | 0-510 (RGBA Euclidean) | VIPS, ChunkyPNG | Legacy setups, fine-grained RGB control | +| Method | Scale | Best for | +|--------|-------|----------| +| `perceptual_threshold` | 0-100+ (dE00) | Cross-OS/browser font rendering, anti-aliasing | +| `color_distance_limit` | 0-510 (RGBA Euclidean) | Fine-grained RGB control | **Recommendation:** Use `perceptual_threshold: 2.0` for most cases. It matches human perception and needs less tuning. -**⚠️ Color comparison methods are exclusive:** `perceptual_threshold` and `color_distance_limit` cannot both be active — if you set both, `perceptual_threshold` wins and `color_distance_limit` is ignored. However, `tolerance` works with **both** methods and is applied by default for VIPS (0.001). This means even with `perceptual_threshold: 2.0`, the `tolerance: 0.001` default still filters results. +**⚠️ Color comparison methods are exclusive:** `perceptual_threshold` and `color_distance_limit` cannot both be active — if you set both, `perceptual_threshold` wins and `color_distance_limit` is ignored. However, `tolerance` works with **both** methods and is applied by default (0.001). This means even with `perceptual_threshold: 2.0`, the `tolerance: 0.001` default still filters results. ### Step 2: Set tolerance (optional, independent) | Setting | What it does | Scale | |---------|--------------|-------| -| `tolerance` | Maximum allowed *ratio* of different pixels (VIPS) or diff bounding box (ChunkyPNG) | 0.0-1.0 | +| `tolerance` | Maximum allowed *ratio* of different pixels | 0.0-1.0 | **Example:** `tolerance: 0.001` allows 0.1% of the image to differ (e.g., 125 pixels in a 1280×1024 screenshot). @@ -87,8 +74,6 @@ exception: `Capybara::Screenshot.enabled` is `SnapDiff.config.screenshot_enabled - `perceptual_threshold` / `color_distance_limit` → **"how different can a pixel be?"** - `tolerance` → **"how many pixels can differ?"** -**⚠️ Driver difference:** VIPS counts actual different pixels. ChunkyPNG counts the bounding box area around differences — a single pixel diff creates a box, and the entire box area counts against tolerance. This makes ChunkyPNG stricter with the same tolerance value. - ### Quick start ```ruby @@ -98,7 +83,7 @@ screenshot 'dashboard', perceptual_threshold: 2.0 # Allow small noise regions screenshot 'dashboard', perceptual_threshold: 2.0, tolerance: 0.001 -# Legacy ChunkyPNG setup +# Raw RGB distance instead of perceptual screenshot 'dashboard', color_distance_limit: 15 ``` @@ -106,7 +91,7 @@ screenshot 'dashboard', color_distance_limit: 15 **Tier 1 — Zero config (works immediately):** `blur_active_element`, `hide_caret`, and `fail_if_new` (in CI) are enabled by default. -Just `require 'snap_diff/integrations/minitest'` (legacy: `capybara_screenshot_diff/minitest`) and call `screenshot`. +Just `require 'snap_diff/integrations/minitest'` and call `screenshot`. **Tier 2 — Set when tests are flaky:** @@ -122,10 +107,9 @@ Just `require 'snap_diff/integrations/minitest'` (legacy: `capybara_screenshot_d | Setting | When to use | |---------|-------------| | `perceptual_threshold` | Anti-aliasing false positives across OS/browser versions | -| `shift_distance_limit` | Content shifts by a few pixels (ChunkyPNG only — **removed in 2.1**) | | `area_size_limit` | Allow small diff regions below a pixel count | | `color_distance_limit` | Fine-tune raw RGB channel tolerance | -| `median_filter_window_size` | Smooth noise before comparison (VIPS only) | +| `median_filter_window_size` | Smooth noise before comparison (per-screenshot option only) | --- @@ -136,7 +120,7 @@ Just `require 'snap_diff/integrations/minitest'` (legacy: `capybara_screenshot_d You can specify the desired screen size using ```ruby -Capybara::Screenshot.window_size = [1024, 768] +SnapDiff.config.window_size = [1024, 768] ``` This will force the screen shots to the given size, and skip taking screen shots @@ -147,13 +131,13 @@ unless the desired window size can be achieved. If you want to skip taking screen shots, set ```ruby -Capybara::Screenshot.enabled = false +SnapDiff.config.screenshot_enabled = false ``` You can of course set this by an environment variable ```ruby -Capybara::Screenshot.enabled = ENV['TAKE_SCREENSHOTS'] +SnapDiff.config.screenshot_enabled = ENV['TAKE_SCREENSHOTS'] ``` ### Disabling diff @@ -161,13 +145,13 @@ Capybara::Screenshot.enabled = ENV['TAKE_SCREENSHOTS'] If you want to skip the assertion for change in the screen shot, set ```ruby -Capybara::Screenshot::Diff.enabled = false +SnapDiff.config.enabled = false ``` Using an environment variable ```ruby -Capybara::Screenshot::Diff.enabled = ENV['COMPARE_SCREENSHOTS'] +SnapDiff.config.enabled = ENV['COMPARE_SCREENSHOTS'] ``` ### Tolerate screenshot differences @@ -175,7 +159,7 @@ Capybara::Screenshot::Diff.enabled = ENV['COMPARE_SCREENSHOTS'] To allow screenshot differences, but still fail on functional errors, you can set the following option: ```ruby -Capybara::Screenshot::Diff.fail_on_difference = false +SnapDiff.config.fail_on_difference = false ``` It defaults to `true`. This can be useful in continuous integration to a generate a screenshot difference @@ -186,7 +170,7 @@ report while still reporting functional errors. To fail the test if a new screenshot is taken, set the following option: ```ruby -Capybara::Screenshot::Diff.fail_if_new = true +SnapDiff.config.fail_if_new = true ``` If `fail_if_new` is set to `true`, the test will fail if a new screenshot is taken @@ -199,10 +183,10 @@ that every screenshot taken by your tests corresponds to an expected state of yo To mark tests as pending (skipped) if a new screenshot is taken without a baseline, set: ```ruby -Capybara::Screenshot::Diff.pending_if_new = true +SnapDiff.config.pending_if_new = true # Required in CI, because fail_if_new defaults to true there and raises before # the pending marker is applied. -Capybara::Screenshot::Diff.fail_if_new = false +SnapDiff.config.fail_if_new = false ``` If `pending_if_new` is set to `true`, the test will be marked as skipped in teardown @@ -212,7 +196,7 @@ This option is useful when you want to record new screenshots without blocking C ### Screen shot save path -By default, `Capybara::Screenshot::Diff` saves screenshots to a +By default, SnapDiff saves screenshots to a `doc/screenshots` folder, relative to either `Rails.root` (if you're in Rails), or your current directory otherwise. @@ -222,17 +206,17 @@ configuration options that that are relevant. The most likely one you'll want to modify is ... ```ruby -Capybara::Screenshot.save_path = "other/path" +SnapDiff.config.save_path = "other/path" ``` -The `save_path` option is relative to `Capybara::Screenshot.root`. +The `save_path` option is relative to `SnapDiff.config.root`. -`Capybara::Screenshot.root` defaults to either `Rails.root` (if you're in +`SnapDiff.config.root` defaults to either `Rails.root` (if you're in Rails) or your current directory. You can change it to something entirely different if necessary, such as when using an alternative web framework. ```ruby -Capybara::Screenshot.root = Hanami.root +SnapDiff.config.root = Hanami.root ``` ### Screen shot stability @@ -243,7 +227,7 @@ shot will be taken and compared to the first. This is repeated until two subsequent screen shots are identical. ```ruby -Capybara::Screenshot.stability_time_limit = 0.1 +SnapDiff.config.stability_time_limit = 0.1 ``` This can be overridden on a single screenshot: @@ -273,7 +257,7 @@ In Chrome the screenshot includes the blinking input cursor. This can make it i stable screenshot. To get around this you can set the `hide caret` option: ```ruby -Capybara::Screenshot.hide_caret = true +SnapDiff.config.hide_caret = true ``` This will make the cursor (caret) transparent (invisible), so the blinking does not delay the screen shot. @@ -284,7 +268,7 @@ This will make the cursor (caret) transparent (invisible), so the blinking does Another way to avoid the cursor blinking is to set the `blur_active_element` option: ```ruby -Capybara::Screenshot.blur_active_element = true +SnapDiff.config.blur_active_element = true ``` This will remove the focus from the active element, removing the blinking cursor. @@ -307,45 +291,25 @@ end The difference is calculated as the euclidean distance. You can also set this globally: ```ruby -Capybara::Screenshot::Diff.color_distance_limit = 42 +SnapDiff.config.color_distance_limit = 42 ``` -### Allowed shift distance - -> **Removed in 2.1.** `shift_distance_limit` is implemented only by the ChunkyPNG driver, -> and 2.1 removes that driver — libvips becomes the only backend. Setting it anywhere -> (`SnapDiff.config.shift_distance_limit =`, the legacy -> `Capybara::Screenshot::Diff.shift_distance_limit =`, or `screenshot 'index', -> shift_distance_limit: 2`) warns once per process in 2.0. There is no vips equivalent: -> use `median_filter_window_size` (the faster answer to the same problem — see -> [Drivers](drivers.md#median-filter-size-vips-only)), `tolerance`, or -> `color_distance_limit`. +### Allowed shift distance (removed in 2.1) -Sometimes you want to allow small movements in the images. For example, jquery-tablesorter -renders the same table slightly differently sometimes. You can set set the shift distance -threshold for the comparison using the `shift_distance_limit` option to the `screenshot` -method: - -```ruby -test 'color threshold' do - visit '/' - screenshot 'index', shift_distance_limit: 2 -end -``` - -The difference is calculated as maximum distance in either the X or the Y axis. -You can also set this globally: - -```ruby -Capybara::Screenshot::Diff.shift_distance_limit = 1 -``` +The `shift_distance_limit` option let you tolerate small movements in the image (for example, +jquery-tablesorter rendering the same table slightly differently each run). It was implemented +only by the ChunkyPNG driver, and 2.1 removed that driver — libvips is the only backend now, +and it has no shift-distance comparison. -**Note:** For each increase in `shift_distance_limit` more pixels are searched for a matching color value, and -this will impact performance **severely** if a match cannot be found. +Setting it anywhere is a `NoMethodError` on the config object and an ignored key per +screenshot. Use one of these instead: -If `shift_distance_limit` is `nil` shift distance is not measured. If `shift_distance_limit` is set, -even to `0`, shift distance is measured and reported on image differences. +| Instead of `shift_distance_limit` | Why | +|---|---| +| `median_filter_window_size` | The same idea, far faster — smooths the image before comparing. See [Image Processing](drivers.md#median-filter-size) | +| `tolerance` | Allows a ratio of the pixels to differ, wherever they are | +| `color_distance_limit` | Allows each pixel to differ by a colour distance | ### Allowed difference size @@ -362,7 +326,7 @@ end The difference is calculated as `width * height`. You can also set this globally: ```ruby -Capybara::Screenshot::Diff.area_size_limit = 42 +SnapDiff.config.area_size_limit = 42 ``` @@ -384,7 +348,7 @@ end The arguments are `[left, top, right, bottom]` for the area you want to ignore. You can also set this globally: ```ruby -Capybara::Screenshot::Diff.skip_area = [0, 0, 64, 48] +SnapDiff.config.skip_area = [0, 0, 64, 48] ``` If you need to ignore multiple areas: @@ -416,7 +380,7 @@ end You can specify the format of the screenshots taken by setting the `screenshot_format` option. By default, the format is set to `"png"`. However, you can change this to any format supported by your image processing driver. For example, to set the format to `"webp"`, you can do the following: ```ruby -Capybara::Screenshot.screenshot_format = "webp" +SnapDiff.config.screenshot_format = "webp" ``` ### Customize Capybara#screenshot options @@ -425,7 +389,7 @@ Allow to bypass screenshot options to Capybara driver. ```ruby # To create full page screenshots for Selenium -Capybara::Screenshot.capybara_screenshot_options[:full_page] = true +SnapDiff.config.capybara_screenshot_options[:full_page] = true screenshot('index', median_filter_window_size: 2, capybara_screenshot_options: {full_page: false}) ``` diff --git a/docs/docker-testing.md b/docs/docker-testing.md index 5dd861fd..6d1473d2 100644 --- a/docs/docker-testing.md +++ b/docs/docker-testing.md @@ -5,7 +5,7 @@ Screenshot tests depend on exact browser rendering, which varies across OS and browser versions. Use `bin/dtest` to run tests inside Docker for consistent, reproducible results matching CI: ```bash -bin/dtest # Run all tests with all drivers +bin/dtest # Run all tests against every Capybara driver bin/dtest test/integration/ # Run specific test directory ``` diff --git a/docs/drivers.md b/docs/drivers.md index f4faede0..282d74a4 100644 --- a/docs/drivers.md +++ b/docs/drivers.md @@ -1,47 +1,41 @@ -# Image Processing Drivers +# Image Processing -> **Canonical equivalents.** Global settings shown here as -> `Capybara::Screenshot::Diff.