snap_diff-capybara 0.0.1 → 2.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +190 -0
- data/CODE_OF_CONDUCT.md +129 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +65 -0
- data/capybara-screenshot-diff.gemspec +29 -0
- data/docs/RELEASE_PREP.md +44 -0
- data/docs/UPGRADING.md +702 -0
- data/docs/architecture.md +289 -0
- data/docs/ci-integration.md +238 -0
- data/docs/configuration.md +395 -0
- data/docs/docker-testing.md +24 -0
- data/docs/drivers.md +102 -0
- data/docs/framework-setup.md +87 -0
- data/docs/images/snap_diff_annotated.png +0 -0
- data/docs/images/snap_diff_web_ui.png +0 -0
- data/docs/migration-guide.md +286 -0
- data/docs/organization.md +204 -0
- data/docs/reporters.md +46 -0
- data/docs/thread_safety.md +112 -0
- data/gems.rb +39 -0
- data/lib/capybara/screenshot/diff/annotation_service.rb +7 -0
- data/lib/capybara/screenshot/diff/area_calculator.rb +7 -0
- data/lib/capybara/screenshot/diff/browser_helpers.rb +7 -0
- data/lib/capybara/screenshot/diff/config_legacy.rb +113 -0
- data/lib/capybara/screenshot/diff/cucumber.rb +3 -0
- data/lib/capybara/screenshot/diff/difference.rb +8 -0
- data/lib/capybara/screenshot/diff/drivers/base_driver.rb +9 -0
- data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +7 -0
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +7 -0
- data/lib/capybara/screenshot/diff/drivers.rb +9 -0
- data/lib/capybara/screenshot/diff/image_compare.rb +12 -0
- data/lib/capybara/screenshot/diff/image_preprocessor.rb +7 -0
- data/lib/capybara/screenshot/diff/os.rb +14 -0
- data/lib/capybara/screenshot/diff/region.rb +107 -0
- data/lib/capybara/screenshot/diff/reporters/default.rb +109 -0
- data/lib/capybara/screenshot/diff/screenshot_matcher.rb +7 -0
- data/lib/capybara/screenshot/diff/screenshoter.rb +7 -0
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +7 -0
- data/lib/capybara/screenshot/diff/utils.rb +7 -0
- data/lib/capybara/screenshot/diff/vcs.rb +7 -0
- data/lib/capybara/screenshot/diff/version.rb +15 -0
- data/lib/capybara/screenshot/diff.rb +3 -0
- data/lib/capybara-screenshot-diff.rb +3 -0
- data/lib/capybara_screenshot_diff/attempts_reporter.rb +7 -0
- data/lib/capybara_screenshot_diff/cucumber.rb +8 -0
- data/lib/capybara_screenshot_diff/dsl.rb +12 -0
- data/lib/capybara_screenshot_diff/error_with_filtered_backtrace.rb +8 -0
- data/lib/capybara_screenshot_diff/minitest.rb +14 -0
- data/lib/capybara_screenshot_diff/reporters/html.rb +7 -0
- data/lib/capybara_screenshot_diff/rspec.rb +8 -0
- data/lib/capybara_screenshot_diff/screenshot_assertion.rb +71 -0
- data/lib/capybara_screenshot_diff/screenshot_namer.rb +7 -0
- data/lib/capybara_screenshot_diff/snap.rb +7 -0
- data/lib/capybara_screenshot_diff/snap_manager.rb +7 -0
- data/lib/capybara_screenshot_diff/static.rb +9 -0
- data/lib/capybara_screenshot_diff.rb +50 -0
- data/lib/snap_diff/annotation_service.rb +84 -0
- data/lib/snap_diff/area_calculator.rb +56 -0
- data/lib/snap_diff/attempts_reporter.rb +51 -0
- data/lib/snap_diff/browser_helpers.rb +121 -0
- data/lib/snap_diff/capture/viewport.rb +40 -0
- data/lib/snap_diff/comparison.rb +238 -0
- data/lib/snap_diff/comparison_result.rb +104 -0
- data/lib/snap_diff/config.rb +78 -0
- data/lib/snap_diff/deprecation.rb +84 -0
- data/lib/snap_diff/driver.rb +37 -0
- data/lib/snap_diff/drivers/chunky_png_driver.rb +298 -0
- data/lib/snap_diff/drivers/vips_driver.rb +171 -0
- data/lib/snap_diff/drivers.rb +14 -0
- data/lib/snap_diff/dsl.rb +143 -0
- data/lib/snap_diff/error_with_filtered_backtrace.rb +32 -0
- data/lib/snap_diff/image_preprocessor.rb +68 -0
- data/lib/snap_diff/integrations/cucumber.rb +22 -0
- data/lib/snap_diff/integrations/minitest.rb +70 -0
- data/lib/snap_diff/integrations/rspec.rb +68 -0
- data/lib/snap_diff/legacy_shims.rb +99 -0
- data/lib/snap_diff/os.rb +17 -0
- data/lib/snap_diff/reporters/html.rb +143 -0
- data/lib/snap_diff/reporters/templates/report.html.erb +463 -0
- data/lib/snap_diff/reporting.rb +53 -0
- data/lib/snap_diff/screenshot_assertion.rb +143 -0
- data/lib/snap_diff/screenshot_matcher.rb +113 -0
- data/lib/snap_diff/screenshot_namer.rb +81 -0
- data/lib/snap_diff/screenshoter.rb +129 -0
- data/lib/snap_diff/snap.rb +66 -0
- data/lib/snap_diff/snap_manager.rb +125 -0
- data/lib/snap_diff/stable_screenshoter.rb +103 -0
- data/lib/snap_diff/static.rb +11 -0
- data/lib/snap_diff/utils.rb +38 -0
- data/lib/snap_diff/vcs.rb +35 -0
- data/lib/snap_diff/version.rb +5 -0
- data/lib/snap_diff.rb +65 -0
- metadata +160 -10
- data/README.md +0 -3
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Migration Guide
|
|
2
|
+
|
|
3
|
+
Migrate your visual regression testing from other tools to `capybara-screenshot-diff`. This guide covers the most common migration paths.
|
|
4
|
+
|
|
5
|
+
## Why Switch?
|
|
6
|
+
|
|
7
|
+
| Factor | Percy / Chromatic | BackstopJS | `capybara-screenshot-diff` |
|
|
8
|
+
|--------|-------------------|------------|--------------------------|
|
|
9
|
+
| Pricing | Paid SaaS (snapshot limits) | Free | Free (MIT) |
|
|
10
|
+
| Infrastructure | Cloud service, API tokens | Node + Puppeteer | Ruby gem, no external services |
|
|
11
|
+
| Baselines | Hosted on their servers | Local files | Git (committed to repo) |
|
|
12
|
+
| Review | Web dashboard | HTML report | HTML report + GitHub PR comments |
|
|
13
|
+
| PR integration | GitHub app | Manual CI steps | Reusable GitHub Action |
|
|
14
|
+
| Offline | ❌ Requires internet | ✅ | ✅ |
|
|
15
|
+
| Diff in PRs | Screenshot in comment | Manual | Upload artifact + PR comment |
|
|
16
|
+
|
|
17
|
+
## From Percy
|
|
18
|
+
|
|
19
|
+
### Setup changes
|
|
20
|
+
|
|
21
|
+
**Before (Percy):**
|
|
22
|
+
```ruby
|
|
23
|
+
# Gemfile
|
|
24
|
+
gem 'percy-capybara'
|
|
25
|
+
|
|
26
|
+
# test helper
|
|
27
|
+
require 'percy/capybara'
|
|
28
|
+
|
|
29
|
+
# test
|
|
30
|
+
def test_homepage
|
|
31
|
+
visit '/'
|
|
32
|
+
Percy::Capybara.screenshot('homepage')
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**After (capybara-screenshot-diff):**
|
|
37
|
+
```ruby
|
|
38
|
+
# Gemfile
|
|
39
|
+
gem 'capybara-screenshot-diff'
|
|
40
|
+
|
|
41
|
+
# test helper
|
|
42
|
+
require 'capybara_screenshot_diff/minitest'
|
|
43
|
+
|
|
44
|
+
# test class
|
|
45
|
+
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
|
46
|
+
include CapybaraScreenshotDiff::Minitest::Assertions
|
|
47
|
+
|
|
48
|
+
test "homepage" do
|
|
49
|
+
visit '/'
|
|
50
|
+
screenshot 'homepage'
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### What changes
|
|
56
|
+
|
|
57
|
+
| Concept | Percy | capybara-screenshot-diff |
|
|
58
|
+
|---------|-------|-------------------------|
|
|
59
|
+
| Baseline storage | Percy cloud | Committed to git (`doc/screenshots/`) |
|
|
60
|
+
| First run | Uploads to Percy | Saves locally, passes automatically |
|
|
61
|
+
| CI setup | `PERCY_TOKEN` env var | GitHub Action (3 lines) |
|
|
62
|
+
| Diff review | Percy dashboard | `snap_diff_report.html` or PR artifacts |
|
|
63
|
+
| Update baselines | Percy's "Approve" button | Delete file, re-run tests, commit |
|
|
64
|
+
| Snapshot limits | Paid plan dependent | Unlimited |
|
|
65
|
+
| Parallel builds | Built-in | Thread-safe with t-locals + mutex |
|
|
66
|
+
|
|
67
|
+
### CI migration
|
|
68
|
+
|
|
69
|
+
**Before (Percy GitHub Action):**
|
|
70
|
+
```yaml
|
|
71
|
+
- name: Percy Test
|
|
72
|
+
run: PERCY_TOKEN=${{ secrets.PERCY_TOKEN }} bundle exec rake test
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**After (capybara-screenshot-diff):**
|
|
76
|
+
```yaml
|
|
77
|
+
- uses: snap-diff/snap_diff-capybara/.github/actions/setup-ruby-and-dependencies@master
|
|
78
|
+
with:
|
|
79
|
+
ruby-version: '4.0'
|
|
80
|
+
- run: bundle exec rake test
|
|
81
|
+
- uses: snap-diff/snap_diff-capybara/.github/actions/upload-screenshots@master
|
|
82
|
+
if: failure()
|
|
83
|
+
with:
|
|
84
|
+
name: screenshots
|
|
85
|
+
pr-comment: 'true'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Migration steps
|
|
89
|
+
|
|
90
|
+
1. **Remove Percy gem and configuration**
|
|
91
|
+
2. **Add `capybara-screenshot-diff`** to your Gemfile
|
|
92
|
+
3. **Replace `Percy::Capybara.screenshot` calls** with `screenshot` (or `match_screenshot` for RSpec)
|
|
93
|
+
4. **Run tests once** to generate baselines
|
|
94
|
+
5. **Commit baselines** (`git add doc/screenshots/`)
|
|
95
|
+
6. **Set up CI** with the GitHub Actions upload step
|
|
96
|
+
7. **Remove Percy integration** from CI
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## From Chromatic
|
|
101
|
+
|
|
102
|
+
### Setup changes
|
|
103
|
+
|
|
104
|
+
**Before (Chromatic + Storybook):**
|
|
105
|
+
```js
|
|
106
|
+
// .storybook/preview.js
|
|
107
|
+
import { withScreenshot } from 'chromatic';
|
|
108
|
+
|
|
109
|
+
export const decorators = [withScreenshot];
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**After (capybara-screenshot-diff):**
|
|
113
|
+
```ruby
|
|
114
|
+
# test/system/stories_test.rb
|
|
115
|
+
class StoriesTest < ApplicationSystemTestCase
|
|
116
|
+
test "landing page story" do
|
|
117
|
+
visit '/iframe.html?id=pages-landing--default'
|
|
118
|
+
screenshot 'stories/landing-page'
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Key differences
|
|
124
|
+
|
|
125
|
+
| Concept | Chromatic | capybara-screenshot-diff |
|
|
126
|
+
|---------|-----------|-------------------------|
|
|
127
|
+
| Focus | Storybook components | Full-page system tests |
|
|
128
|
+
| Baseline | Chromatic cloud | Git-committed |
|
|
129
|
+
| Review | Chromatic web UI | HTML report + PR artifacts |
|
|
130
|
+
| CI integration | Chromatic GitHub App | GitHub Actions + PR comments |
|
|
131
|
+
| Thresholds | Visual catch (AI) | Configurable tolerance (numeric) |
|
|
132
|
+
|
|
133
|
+
### Migration approach
|
|
134
|
+
|
|
135
|
+
Chromatic is primarily for Storybook component testing. If you want to continue testing individual components:
|
|
136
|
+
|
|
137
|
+
1. **Replace with Capybara system tests** that visit each component's rendered page
|
|
138
|
+
2. **Use `crop:` option** to isolate specific elements: `screenshot 'button', crop: '.my-button'`
|
|
139
|
+
3. **Use `skip_area:` option** to ignore dynamic regions: `screenshot 'dashboard', skip_area: ['.timestamp']`
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## From BackstopJS
|
|
144
|
+
|
|
145
|
+
### Setup changes
|
|
146
|
+
|
|
147
|
+
**Before (BackstopJS):**
|
|
148
|
+
```json
|
|
149
|
+
// backstop.json
|
|
150
|
+
{
|
|
151
|
+
"id": "homepage",
|
|
152
|
+
"viewports": [{"width": 1280, "height": 1024}],
|
|
153
|
+
"scenarios": [{
|
|
154
|
+
"label": "Homepage",
|
|
155
|
+
"url": "http://localhost:3000",
|
|
156
|
+
"referenceUrl": "http://localhost:3000",
|
|
157
|
+
"selectors": ["document"]
|
|
158
|
+
}],
|
|
159
|
+
"paths": {
|
|
160
|
+
"bitmaps_reference": "backstop_data/bitmaps_reference",
|
|
161
|
+
"bitmaps_test": "backstop_data/bitmaps_test",
|
|
162
|
+
"html_report": "backstop_data/html_report"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**After (capybara-screenshot-diff):**
|
|
168
|
+
```ruby
|
|
169
|
+
class HomepageTest < ApplicationSystemTestCase
|
|
170
|
+
test "homepage" do
|
|
171
|
+
visit '/'
|
|
172
|
+
screenshot 'homepage'
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Key differences
|
|
178
|
+
|
|
179
|
+
| Concept | BackstopJS | capybara-screenshot-diff |
|
|
180
|
+
|---------|-----------|-------------------------|
|
|
181
|
+
| Language | JavaScript + Node | Ruby (runs in test suite) |
|
|
182
|
+
| Dependencies | Node, Puppeteer/Chromium | Ruby gems + optional libvips |
|
|
183
|
+
| Test runner | Standalone CLI | Minitest, RSpec, Cucumber |
|
|
184
|
+
| Selectors | CSS selectors for scenarios | CSS selectors for crop/skip_area |
|
|
185
|
+
| Viewports | Per-scenario config | Global `window_size` setting |
|
|
186
|
+
| CI report | HTML report | HTML report + GitHub Actions |
|
|
187
|
+
| Stability | `misMatchThreshold` + `delay` | `tolerance` + `stability_time_limit` |
|
|
188
|
+
|
|
189
|
+
### Configuration mapping
|
|
190
|
+
|
|
191
|
+
| BackstopJS option | capybara-screenshot-diff equivalent |
|
|
192
|
+
|-------------------|-------------------------------------|
|
|
193
|
+
| `misMatchThreshold` | `tolerance` (0.0-1.0 scale, e.g. `0.01` = 1%) |
|
|
194
|
+
| `delay` | `stability_time_limit` (seconds) |
|
|
195
|
+
| `selectors` | `crop:` option with CSS selector |
|
|
196
|
+
| `hideSelectors` | `skip_area:` option with CSS selectors |
|
|
197
|
+
| `removeSelectors` | N/A — use `skip_area` or modify DOM before screenshot |
|
|
198
|
+
| `waitTimeout` | `wait:` option (defaults to `Capybara.default_max_wait_time`) |
|
|
199
|
+
| `viewports` | `window_size: [width, height]` |
|
|
200
|
+
| `onReadyScript` | Custom setup in your test's `setup` block |
|
|
201
|
+
|
|
202
|
+
### CI migration
|
|
203
|
+
|
|
204
|
+
**Before (BackstopJS in CI):**
|
|
205
|
+
```yaml
|
|
206
|
+
- run: npx backstop test --config=backstop.json
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**After (capybara-screenshot-diff in CI):**
|
|
210
|
+
```yaml
|
|
211
|
+
- uses: snap-diff/snap_diff-capybara/.github/actions/setup-ruby-and-dependencies@master
|
|
212
|
+
- run: bundle exec rake test
|
|
213
|
+
- uses: snap-diff/snap_diff-capybara/.github/actions/upload-screenshots@master
|
|
214
|
+
if: failure()
|
|
215
|
+
with:
|
|
216
|
+
name: screenshots
|
|
217
|
+
pr-comment: 'true'
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Migration steps
|
|
221
|
+
|
|
222
|
+
1. **Remove BackstopJS configuration** (`backstop.json`, npm dependencies)
|
|
223
|
+
2. **Convert scenarios to Capybara tests** — each scenario becomes a `screenshot` call
|
|
224
|
+
3. **Map threshold and delay settings** to `tolerance` and `stability_time_limit`
|
|
225
|
+
4. **Run tests** to generate baselines
|
|
226
|
+
5. **Commit baselines** (`git add doc/screenshots/`)
|
|
227
|
+
6. **Update CI** to use the GitHub Actions setup
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## General Migration Checklist
|
|
232
|
+
|
|
233
|
+
- [ ] Remove old gem/npm dependencies
|
|
234
|
+
- [ ] Add `capybara-screenshot-diff` to Gemfile
|
|
235
|
+
- [ ] Require the appropriate adapter (`minitest`, `rspec`, or `cucumber`)
|
|
236
|
+
- [ ] Replace screenshot calls with `screenshot` / `match_screenshot`
|
|
237
|
+
- [ ] Configure `window_size` for consistent viewport dimensions
|
|
238
|
+
- [ ] Set `tolerance` or `perceptual_threshold` if your previous tool had a mismatch threshold
|
|
239
|
+
- [ ] Add `.gitignore` patterns for diff artifacts
|
|
240
|
+
- [ ] Run tests to generate baseline screenshots
|
|
241
|
+
- [ ] Commit baselines to git
|
|
242
|
+
- [ ] Set up CI with artifact upload
|
|
243
|
+
- [ ] Optional: add HTML reporter and PR commenting
|
|
244
|
+
|
|
245
|
+
## Common Gotchas
|
|
246
|
+
|
|
247
|
+
### "My baselines are on Percy/Chromatic servers"
|
|
248
|
+
|
|
249
|
+
You'll need to take fresh screenshots. Either:
|
|
250
|
+
- Visit each page and capture manually
|
|
251
|
+
- Run tests with `RECORD_SCREENSHOTS=1` to generate all baselines at once
|
|
252
|
+
|
|
253
|
+
### "I had hundreds of BackstopJS scenarios"
|
|
254
|
+
|
|
255
|
+
Start small. Migrate one test file at a time. The `screenshot_group` feature helps organize related screenshots:
|
|
256
|
+
```ruby
|
|
257
|
+
screenshot_group 'checkout'
|
|
258
|
+
screenshot 'step1'
|
|
259
|
+
screenshot 'step2'
|
|
260
|
+
# Produces: doc/screenshots/checkout/00_step1.png, 01_step2.png
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### "My tests are slow now"
|
|
264
|
+
|
|
265
|
+
Use the VIPS driver for ~50ms comparisons per image:
|
|
266
|
+
```ruby
|
|
267
|
+
gem 'ruby-vips'
|
|
268
|
+
Capybara::Screenshot::Diff.driver = :vips
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### "The diffs look different from what I'm used to"
|
|
272
|
+
|
|
273
|
+
Each tool uses different comparison algorithms:
|
|
274
|
+
- **Percy:** Proprietary pixel-level comparison with AI smoothing
|
|
275
|
+
- **Chromatic:** Visual catch algorithm (structure-aware)
|
|
276
|
+
- **BackstopJS:** Resemble.js pixel comparison
|
|
277
|
+
- **capybara-screenshot-diff:** Raw pixel difference with configurable tolerance
|
|
278
|
+
|
|
279
|
+
Start with default settings, then adjust `tolerance` or `perceptual_threshold` based on your needs.
|
|
280
|
+
|
|
281
|
+
## Need Help?
|
|
282
|
+
|
|
283
|
+
- [Architecture Overview](docs/architecture.md) — understanding how comparisons work
|
|
284
|
+
- [Configuration Reference](docs/configuration.md) — all available options
|
|
285
|
+
- [CI Integration](docs/ci-integration.md) — setting up in CI
|
|
286
|
+
- [GitHub Issues](https://github.com/snap-diff/snap_diff-capybara/issues) — ask questions
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Screenshot Organization
|
|
2
|
+
|
|
3
|
+
## Taking screenshots
|
|
4
|
+
|
|
5
|
+
Add `screenshot '<my_feature>'` to your tests. The screenshot will be saved in
|
|
6
|
+
the `doc/screenshots` directory.
|
|
7
|
+
|
|
8
|
+
Change your existing `save_screenshot` calls to `screenshot`
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
test 'my useful feature' do
|
|
12
|
+
visit '/'
|
|
13
|
+
screenshot 'welcome_index'
|
|
14
|
+
click_button 'Useful feature'
|
|
15
|
+
screenshot 'feature_index'
|
|
16
|
+
click_button 'Perform action'
|
|
17
|
+
screenshot 'action_performed'
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This will produce a sequence of images like this
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
doc
|
|
25
|
+
screenshots
|
|
26
|
+
action_performed
|
|
27
|
+
feature_index
|
|
28
|
+
welcome_index
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To store the screenshot history, add the `doc/screenshots` directory to your
|
|
32
|
+
version control system (git).
|
|
33
|
+
|
|
34
|
+
Screenshots are compared to the previously COMMITTED version of the same screenshot.
|
|
35
|
+
|
|
36
|
+
**Note:** When a screenshot differs, diff artifacts (`.diff.png`, `.heatmap.diff.png`, etc.) are generated alongside the baseline. Add `*.diff.png`, `*.base.png`, `*.diff.webp`, `*.base.webp`, and `snap_diff_report.html` to your `.gitignore`. Clean up artifacts with `rake snap_diff:clean`.
|
|
37
|
+
|
|
38
|
+
## Screenshot groups
|
|
39
|
+
|
|
40
|
+
Commonly it is useful to group screenshots around a feature, and record them as
|
|
41
|
+
a sequence. To do this, add a `screenshot_group` call to the start of your
|
|
42
|
+
test.
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
test 'my useful feature' do
|
|
46
|
+
screenshot_group 'useful_feature'
|
|
47
|
+
visit '/'
|
|
48
|
+
screenshot 'welcome_index'
|
|
49
|
+
click_button 'Useful feature'
|
|
50
|
+
screenshot 'feature_index'
|
|
51
|
+
click_button 'Perform action'
|
|
52
|
+
screenshot 'action_performed'
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
This will produce a sequence of images like this
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
doc
|
|
60
|
+
screenshots
|
|
61
|
+
useful_feature
|
|
62
|
+
00_welcome_index
|
|
63
|
+
01_feature_index
|
|
64
|
+
02_action_performed
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Note:** `screenshot_group` sets the group name for organizing screenshots. It does not delete existing files.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## Screenshot sections
|
|
71
|
+
|
|
72
|
+
You can introduce another level above the screenshot group called a
|
|
73
|
+
`screenshot_section`. The section name is inserted just before the group name
|
|
74
|
+
in the save path. If called in the setup of the test, all screenshots in
|
|
75
|
+
that test will get the same prefix:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
setup do
|
|
79
|
+
screenshot_section 'my_feature'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
test 'my subfeature' do
|
|
83
|
+
screenshot_group 'subfeature'
|
|
84
|
+
visit '/feature'
|
|
85
|
+
click_button 'Interesting button'
|
|
86
|
+
screenshot 'subfeature_index'
|
|
87
|
+
click_button 'Perform action'
|
|
88
|
+
screenshot 'action_performed'
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This will produce a sequence of images like this
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
doc
|
|
96
|
+
screenshots
|
|
97
|
+
my_feature
|
|
98
|
+
subfeature
|
|
99
|
+
00_subfeature_index
|
|
100
|
+
01_action_performed
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## Setting `screenshot_section` and/or `screenshot_group` for all tests
|
|
105
|
+
|
|
106
|
+
Setting the `screenshot_section` and/or `screenshot_group` for all tests can be
|
|
107
|
+
done in the super class setup:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
|
111
|
+
setup do
|
|
112
|
+
screenshot_section class_name.underscore.sub(/(_feature|_system)?_test$/, '')
|
|
113
|
+
screenshot_group name[5..-1]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`screenshot_section` and/or `screenshot_group` can still be overridden in each
|
|
119
|
+
test.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## Capturing one area instead of the whole page
|
|
123
|
+
|
|
124
|
+
You can crop images before comparison to be run, by providing region to crop as `[left, top, right, bottom]` or by css selector like `body .tag`
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
test 'the cool' do
|
|
128
|
+
visit '/feature'
|
|
129
|
+
screenshot 'cool_element', crop: '#my_element'
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Note:** When using a retina device screenshots dimensions might be off. If
|
|
134
|
+
you are using (headless) chrome you can prevent this by setting the
|
|
135
|
+
`force-device-scale-factor` argument to `1`.
|
|
136
|
+
|
|
137
|
+
For Rails system specs using selenium you can do so for example by using the
|
|
138
|
+
following snippet:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
driven_by :selenium, using: :chrome_headless do |options|
|
|
142
|
+
options.args << '--force-device-scale-factor=1'
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Multiple Capybara drivers
|
|
147
|
+
|
|
148
|
+
Often it is useful to test your app using different browsers. To avoid the
|
|
149
|
+
screenshots for different Capybara drivers to overwrite each other, set
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
Capybara::Screenshot.add_driver_path = true
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The example above will then save your screenshots like this
|
|
156
|
+
(for poltergeist and selenium):
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
doc
|
|
160
|
+
screenshots
|
|
161
|
+
poltergeist
|
|
162
|
+
useful_feature
|
|
163
|
+
00_welcome_index
|
|
164
|
+
01_feature_index
|
|
165
|
+
02_action_performed
|
|
166
|
+
selenium
|
|
167
|
+
useful_feature
|
|
168
|
+
00_welcome_index
|
|
169
|
+
01_feature_index
|
|
170
|
+
02_action_performed
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Multiple OSs
|
|
174
|
+
|
|
175
|
+
If you run your tests on multiple operating systems, you will most likely find
|
|
176
|
+
the screen shots differ. To avoid the screenshots for different OSs to
|
|
177
|
+
overwrite each other, set
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
Capybara::Screenshot.add_os_path = true
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The example above will then save your screenshots like this
|
|
184
|
+
(for Linux and Windows):
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
doc
|
|
188
|
+
screenshots
|
|
189
|
+
linux
|
|
190
|
+
useful_feature
|
|
191
|
+
00_welcome_index
|
|
192
|
+
01_feature_index
|
|
193
|
+
02_action_performed
|
|
194
|
+
windows
|
|
195
|
+
useful_feature
|
|
196
|
+
00_welcome_index
|
|
197
|
+
01_feature_index
|
|
198
|
+
02_action_performed
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
If you combine this config with the `add_driver_path` config, the driver will be
|
|
202
|
+
put in front of the OS name.
|
|
203
|
+
|
|
204
|
+
[← Back to README](../README.md)
|
data/docs/reporters.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Reporters
|
|
2
|
+
|
|
3
|
+
## Web UI for Reviewing Screenshot Changes
|
|
4
|
+
|
|
5
|
+
Generate an interactive Web UI report of screenshot differences:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# Add to test_helper.rb — one line, that's it
|
|
9
|
+
require 'capybara_screenshot_diff/reporters/html'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
After running tests, open the report (generated only when there are failures):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
open doc/screenshots/snap_diff_report.html
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The report includes a sidebar with thumbnails, side-by-side comparison with diff toggle, search, and summary stats. No configuration needed — just require it.
|
|
19
|
+
|
|
20
|
+
**Note:** The report is not generated when all screenshots match. In parallel test environments, each worker writes to the same file — the last worker's results will be in the report.
|
|
21
|
+
|
|
22
|
+
## Custom Reporters
|
|
23
|
+
|
|
24
|
+
Build your own reporter by implementing `record` and `finalize`:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class MyReporter
|
|
28
|
+
def record(assertions)
|
|
29
|
+
assertions.each do |assertion|
|
|
30
|
+
next unless assertion.compare&.difference&.different?
|
|
31
|
+
# process the failure — send to Slack, write JSON, etc.
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def finalize
|
|
36
|
+
# called once at process exit — write summary, upload report, etc.
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Register in test_helper.rb
|
|
41
|
+
CapybaraScreenshotDiff.reporters << MyReporter.new
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Reporters are notified before assertions are cleared on each test teardown. `finalize` is called via `at_exit`.
|
|
45
|
+
|
|
46
|
+
[← Back to README](../README.md)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Thread Safety Guide for Parallel Testing
|
|
2
|
+
|
|
3
|
+
This document explains how `snap_diff` behaves under Rails parallel tests with the `:thread` strategy.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`snap_diff` is thread safe for parallel test execution as long as global configuration is set before tests run. Per-thread state is isolated, and shared state is protected where it matters.
|
|
8
|
+
|
|
9
|
+
## Architecture Summary
|
|
10
|
+
|
|
11
|
+
### Per-thread Assertion Registry
|
|
12
|
+
|
|
13
|
+
Each thread gets its own `AssertionRegistry` stored in thread-local storage:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
def registry
|
|
17
|
+
Thread.current[:capybara_screenshot_diff_registry] ||= AssertionRegistry.new
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This prevents cross-thread leakage for assertions and screenshot naming.
|
|
22
|
+
|
|
23
|
+
### Reporters Snapshot on Notify
|
|
24
|
+
|
|
25
|
+
Reporters are notified using a snapshot protected by an eagerly initialized mutex:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
@reporters_mutex = Mutex.new
|
|
29
|
+
|
|
30
|
+
def notify_reporters(assertions)
|
|
31
|
+
reporters_snapshot = reporters_mutex.synchronize { reporters.dup }
|
|
32
|
+
reporters_snapshot.each { |reporter| reporter.record(assertions) }
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This ensures a stable list while notifying without forcing a global lock around reporter work.
|
|
37
|
+
|
|
38
|
+
### HTML Reporter Internal Lock
|
|
39
|
+
|
|
40
|
+
The HTML reporter protects `@failures`, `@total`, and `@finalized` with a mutex so `record` and `finalize` can run safely:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
return if @finalized
|
|
45
|
+
@total += total
|
|
46
|
+
@failures.concat(failures)
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`@finalized` is set only after `write_report` succeeds, so a failed write can be retried.
|
|
51
|
+
|
|
52
|
+
### Screenshot Naming Isolation
|
|
53
|
+
|
|
54
|
+
Each thread gets its own `ScreenshotNamer` via the per-thread registry, so counters, sections, and groups do not collide.
|
|
55
|
+
|
|
56
|
+
### SnapManager Per Call
|
|
57
|
+
|
|
58
|
+
`SnapManager` returns a new instance for each call, avoiding shared mutable state.
|
|
59
|
+
|
|
60
|
+
## Global Configuration
|
|
61
|
+
|
|
62
|
+
Configuration uses `mattr_accessor` and should be set once before tests run. Do not mutate config during parallel execution.
|
|
63
|
+
|
|
64
|
+
## Parallel Test Lifecycle
|
|
65
|
+
|
|
66
|
+
- Setup: per-thread registry is created, config is read
|
|
67
|
+
- Execution: assertions are added to the thread-local registry
|
|
68
|
+
- Teardown: `verify` and `reset` operate on the thread-local registry, reporters are notified
|
|
69
|
+
- Exit: reporters finalize once per process (using mutex-protected snapshot)
|
|
70
|
+
|
|
71
|
+
## Usage Examples
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
parallelize(workers: :number_of_processors, with: :threads)
|
|
75
|
+
|
|
76
|
+
Capybara::Screenshot::Diff.configure do |screenshot, diff|
|
|
77
|
+
screenshot.window_size = [1280, 1024]
|
|
78
|
+
screenshot.save_path = "doc/screenshots"
|
|
79
|
+
diff.tolerance = 0.001
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Do and Do Not
|
|
84
|
+
|
|
85
|
+
Do:
|
|
86
|
+
- Set config once in test helper
|
|
87
|
+
- Pass per-screenshot options in the call
|
|
88
|
+
|
|
89
|
+
Do not:
|
|
90
|
+
- Change global config inside tests
|
|
91
|
+
- Manually mutate registry internals
|
|
92
|
+
|
|
93
|
+
## File System Notes
|
|
94
|
+
|
|
95
|
+
- Paths are unique per screenshot name and counter
|
|
96
|
+
- `FileUtils.mv` is atomic on most file systems
|
|
97
|
+
- Directory creation uses `mkpath`
|
|
98
|
+
|
|
99
|
+
## Load-time thread safety
|
|
100
|
+
|
|
101
|
+
Runtime state is thread-local (above), but *loading* the gem is a separate
|
|
102
|
+
concern. The require graph is deliberately acyclic: `lib/snap_diff/*` units
|
|
103
|
+
depend only on the legacy-config leaf
|
|
104
|
+
(`capybara/screenshot/diff/config_legacy`) and specific sibling units, the
|
|
105
|
+
umbrella files depend on the units, and nothing requires back up the chain.
|
|
106
|
+
|
|
107
|
+
Eager mutual requires between entry points are forbidden, even guarded ones:
|
|
108
|
+
per-thread "loading" flags cannot serialize Ruby's process-global per-file
|
|
109
|
+
require locks, so two threads requiring opposite entry points first can
|
|
110
|
+
deadlock (lock-order inversion — observed deterministically before this
|
|
111
|
+
design). If two files ever need each other, extract the shared piece into a
|
|
112
|
+
leaf both can require instead.
|
data/gems.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in capybara-screenshot-diff.gemspec
|
|
6
|
+
gemspec path: __dir__
|
|
7
|
+
|
|
8
|
+
gem "rake"
|
|
9
|
+
|
|
10
|
+
# Image processing libraries
|
|
11
|
+
gem "chunky_png", ">= 1.3", require: false
|
|
12
|
+
gem "oily_png", platform: :ruby, git: "https://github.com/wvanbergen/oily_png", ref: "44042006e79efd42ce4b52c1d78a4c70f0b4b1b2"
|
|
13
|
+
gem "ruby-vips", require: false
|
|
14
|
+
|
|
15
|
+
group :test do
|
|
16
|
+
gem "capybara", ">= 3.26"
|
|
17
|
+
gem "mutex_m" # Needed for RubyMine debugging. Try removing it.
|
|
18
|
+
gem "minitest", "< 6", require: false
|
|
19
|
+
gem "minitest-mock", require: false
|
|
20
|
+
gem "minitest-stub-const", require: false
|
|
21
|
+
gem "simplecov", require: false
|
|
22
|
+
gem "rspec", require: false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Capybara Server
|
|
26
|
+
gem "puma", require: false
|
|
27
|
+
gem "rackup", require: false
|
|
28
|
+
|
|
29
|
+
# Capybara Drivers
|
|
30
|
+
gem "cuprite", require: false
|
|
31
|
+
gem "selenium-webdriver", ">= 4.11", require: false
|
|
32
|
+
|
|
33
|
+
# Test Frameworks
|
|
34
|
+
# gem "cucumber", require: false
|
|
35
|
+
# gem "cucumber-rails", require: false
|
|
36
|
+
|
|
37
|
+
group :tools do
|
|
38
|
+
gem "standard", require: false
|
|
39
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): Capybara::Screenshot::Diff::AnnotationService
|
|
4
|
+
# now resolves lazily via snap_diff/legacy_shims' const_missing, with a
|
|
5
|
+
# deprecation warning pointing at SnapDiff::AnnotationService.
|
|
6
|
+
require "snap_diff/annotation_service"
|
|
7
|
+
require "snap_diff/legacy_shims"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Forwarder (ADR-004 v2 step 6): Capybara::Screenshot::Diff::AreaCalculator
|
|
4
|
+
# now resolves lazily via snap_diff/legacy_shims' const_missing, with a
|
|
5
|
+
# deprecation warning pointing at SnapDiff::AreaCalculator.
|
|
6
|
+
require "snap_diff/area_calculator"
|
|
7
|
+
require "snap_diff/legacy_shims"
|