lookbook_visual_tester 0.5.3 → 0.5.6
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/.agent/rules/style_guide.md +59 -0
- data/CHANGELOG.md +25 -1
- data/README.md +11 -5
- data/Rakefile +2 -0
- data/lib/lookbook_visual_tester/configuration.rb +6 -8
- data/lib/lookbook_visual_tester/preview_checker.rb +22 -0
- data/lib/lookbook_visual_tester/runner.rb +2 -10
- data/lib/lookbook_visual_tester/scenario_run.rb +7 -7
- data/lib/lookbook_visual_tester/services/image_comparator.rb +24 -20
- data/lib/lookbook_visual_tester/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c4dcc1d8c929065528b1f5ad9ee02dac8a2b0520e72d110ec105f954fcdfcf65
|
|
4
|
+
data.tar.gz: ce57354faceb6b0035fd89ef6bfbe6c394c0cc616bd8093929d68a26cd323b88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc7e7d15b48f2d4aef69244851d1bba41dd8b40dc0709562bcdf764485dee2846ec69848891671701fc89ea1f001555627aed0fe631fd823cc527a1e3ead108a
|
|
7
|
+
data.tar.gz: 593a40641363733cffbc4c578b3e9ef94c25491ccb6f48a2230ddb12e91290baaea9738387225adfe7b45a36fb95c8002ea63d098e37be8d12a2815f82990939
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Lookbook Visual Tester Style Guide & Best Practices
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
`lookbook_visual_tester` is a Ruby gem designed for visual regression testing of Lookbook previews. It uses `Ferrum` for headless browser interactions and `ChunkyPNG` for image comparison.
|
|
5
|
+
|
|
6
|
+
## Architecture & patterns
|
|
7
|
+
|
|
8
|
+
### Service Objects
|
|
9
|
+
- Isolate complex logic in service objects inheriting from `LookbookVisualTester::Service` (if applicable) or plain Ruby classes in `lib/lookbook_visual_tester/services/`.
|
|
10
|
+
- Examples: `ImageComparator`, `ScenarioFinder`.
|
|
11
|
+
|
|
12
|
+
### Configuration
|
|
13
|
+
- Use `LookbookVisualTester.configure` to set global options.
|
|
14
|
+
- Options are defined in `lib/lookbook_visual_tester/configuration.rb`.
|
|
15
|
+
- Default base path for screenshots is `coverage/screenshots` (as of v0.5.5).
|
|
16
|
+
|
|
17
|
+
### Path Handling
|
|
18
|
+
- Use `Pathname` for all file system paths.
|
|
19
|
+
- **Screenshot Structure**:
|
|
20
|
+
- `[base_path]/[baseline|current|diffs]/[folder_name]/[filename].png`
|
|
21
|
+
- `folder_name` is either the variant slug (e.g., `dark-mode`) or `default` if no variant is present.
|
|
22
|
+
- Never allow files to be dumped at the root of the screenshot directories; always enforce a subdirectory.
|
|
23
|
+
|
|
24
|
+
## Visual Testing Logic
|
|
25
|
+
|
|
26
|
+
### Image Comparison
|
|
27
|
+
- **Dimension Mismatch**: Do not raise errors for images of different sizes.
|
|
28
|
+
- Create a canvas size equal to the maximum width and height of the two images.
|
|
29
|
+
- Compare overlapping pixels.
|
|
30
|
+
- Mark non-overlapping pixels (from the larger image) as differences.
|
|
31
|
+
- Used in: `LookbookVisualTester::Services::ImageComparator`.
|
|
32
|
+
|
|
33
|
+
### Screenshot Capture
|
|
34
|
+
- Use `Ferrum` to capture screenshots.
|
|
35
|
+
- Ensure consistent viewport sizes and wait for animations/fonts if necessary (managed in `Runner` logic).
|
|
36
|
+
|
|
37
|
+
## Testing (`RSpec`)
|
|
38
|
+
|
|
39
|
+
### Structure
|
|
40
|
+
- **Unit Tests**: `spec/lib/lookbook_visual_tester/...`
|
|
41
|
+
- **Integration Tests**: `spec/integration/...`
|
|
42
|
+
- Use `spec_helper.rb` for common setup.
|
|
43
|
+
|
|
44
|
+
### Best Practices
|
|
45
|
+
- **Reproduction Scripts**: somewhat complex bugs (like visual mismatches) should be isolated in top-level `repro_*.rb` scripts before fixing.
|
|
46
|
+
- **Mocking**: Be careful with circular dependencies when mocking `Lookbook` objects (e.g., `Scenario` <-> `Preview`). Use `allow(...).to receive(...)` logic in `before` blocks rather than definitions to break cycles.
|
|
47
|
+
|
|
48
|
+
## Versioning & Changelog
|
|
49
|
+
- **Versioning**: Semantic versioning in `lib/lookbook_visual_tester/version.rb`.
|
|
50
|
+
- **Changelog**: Maintain `CHANGELOG.md` with `[Unreleased]` and version headers. Group changes by `Added`, `Changed`, `Fixed`.
|
|
51
|
+
|
|
52
|
+
## Coding Style
|
|
53
|
+
- Standard Ruby style (indentation: 2 spaces).
|
|
54
|
+
- Prefer descriptive variable names over short cryptic ones.
|
|
55
|
+
- Use `frozen_string_literal: true` where appropriate (though not strictly enforced everywhere yet).
|
|
56
|
+
|
|
57
|
+
## Versioning & Changelog
|
|
58
|
+
|
|
59
|
+
When updating the version, make sure to update the `CHANGELOG.md` file with the new version number and a list of changes since the last release. The changes should be grouped by `Added`, `Changed`, and `Fixed`. Also update `README.md` accordingly.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
-
## [0.5.
|
|
2
|
+
## [0.5.6] - 2026-01-06
|
|
3
|
+
|
|
4
|
+
### Fixed
|
|
5
|
+
- **Image Comparison**: Fixed `Dimensions mismatch` error by creating a diff canvas sized to the maximum dimensions of widely differing images.
|
|
6
|
+
- **Missing Action Error**: Resolved `ActionNotFound` for `render_scenario_to_string` by ensuring the preview controller properly includes `Lookbook::PreviewControllerActions`.
|
|
7
|
+
- **Unexpected Headers**: Fixed an issue where the `example_icons` preview in the dummy app included an unwanted `<h1>Icons</h1>` header.
|
|
8
|
+
- **Spec Fix**: Resolved a `SystemStackError` (circular dependency) in `ScenarioFinder` specs.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Folder Structure**: Refactored screenshot organization. Non-variant screenshots are now stored in a `default` subfolder within `coverage/screenshots`.
|
|
12
|
+
- **Deep Check**: Enhanced `rake lookbook:deep_check` to validate that the configured preview controller supports required Lookbook actions.
|
|
13
|
+
|
|
14
|
+
## [0.5.5] - 2026-01-06
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **Image Comparison**: Fixed `Dimensions mismatch` error. Now, when images have different dimensions, a diff image is generated on a canvas sized to the maximum dimensions, clearly showing the differences and the mismatched areas.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Folder Structure**: Screenshots are now saved in `coverage/screenshots` by default (previously `spec/visual_screenshots`).
|
|
21
|
+
- **Default Subfolder**: Non-variant screenshots are now stored in a `default` subfolder to maintain consistency with variant-based runs.
|
|
22
|
+
|
|
23
|
+
## [0.5.4] - 2026-01-06
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **Variants URL Generation**: Fixed a bug where setting `VARIANTS` caused a `JSON::ParserError` in Lookbook. Variant parameters are now correctly serialized to JSON in the preview URL.
|
|
3
27
|
|
|
4
28
|
### Fixed
|
|
5
29
|
- **Deep Check**: `rake lookbook:deep_check` now correctly detects and fails when a preview returns `nil` (implicit rendering) but the corresponding template is missing (`ViewComponent::MissingPreviewTemplateError`).
|
data/README.md
CHANGED
|
@@ -28,8 +28,8 @@ sudo apt-get install imagemagick xclip
|
|
|
28
28
|
|
|
29
29
|
Add to your application's Gemfile:
|
|
30
30
|
```ruby
|
|
31
|
-
group :test do
|
|
32
|
-
gem 'lookbook_visual_tester', '~> 0.5.
|
|
31
|
+
group :test, :development do
|
|
32
|
+
gem 'lookbook_visual_tester', '~> 0.5.4'
|
|
33
33
|
end
|
|
34
34
|
```
|
|
35
35
|
|
|
@@ -45,7 +45,7 @@ You can configure the tester in a Rails initializer:
|
|
|
45
45
|
```ruby
|
|
46
46
|
LookbookVisualTester.configure do |config|
|
|
47
47
|
config.lookbook_host = "http://localhost:3000" # Where your rails app is running
|
|
48
|
-
config.base_path = "
|
|
48
|
+
config.base_path = "coverage/screenshots" # Root for screenshots
|
|
49
49
|
config.copy_to_clipboard = true # Enable xclip support
|
|
50
50
|
config.threads = 4 # Number of parallel threads (default: 4)
|
|
51
51
|
end
|
|
@@ -101,7 +101,7 @@ You can run your visual tests against multiple configurations (variants), such a
|
|
|
101
101
|
* **`{"theme":"dark"}`**: Runs with `_display[theme]=dark`.
|
|
102
102
|
* **`{"width":"Mobile"}`**: Runs with `_display[width]=375px` AND automatically resizes the browser window to 375px width.
|
|
103
103
|
|
|
104
|
-
Screenshots for variants are saved in dedicated subfolders (e.g., `
|
|
104
|
+
Screenshots for variants are saved in dedicated subfolders (e.g., `coverage/screenshots/baseline/theme-dark/`).
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
### Baseline Management
|
|
@@ -166,7 +166,13 @@ bundle exec rake lookbook:missing
|
|
|
166
166
|
|
|
167
167
|
## Next Steps
|
|
168
168
|
|
|
169
|
-
- **
|
|
169
|
+
- **Enhanced Approval Workflow**: Implement a `rake lookbook:approve` task (and potentially an interactive mode) to verify and promote changes to baselines without manual file operations.
|
|
170
|
+
- **Tolerance Configuration**: Add support for configuring pixel mismatch tolerance levels to reduce flakiness in rendering (anti-aliasing, shadow rendering differences).
|
|
171
|
+
- **Ignore Regions**: Allow defining specific areas of a component to exclude from visual comparison (useful for unavoidable dynamic content).
|
|
172
|
+
- **Markdown Reports**: Generate markdown summaries of test runs suitable for posting automatically as Pull Request comments.
|
|
173
|
+
- **Cloud Storage**: Integration with cloud storage (S3, GCS) for managing baseline images to keep the git repository light.
|
|
174
|
+
- **CI/CD Blueprints**: Detailed guides and reusable actions/steps for integrating with GitHub Actions, GitLab CI, etc.
|
|
175
|
+
- **Multiple Browsers**: Expand driver support beyond Chrome/Ferrum (e.g. Firefox, Safari/Webkit) for cross-browser visual testing.
|
|
170
176
|
|
|
171
177
|
## Contributing
|
|
172
178
|
|
data/Rakefile
CHANGED
|
@@ -13,14 +13,12 @@ module LookbookVisualTester
|
|
|
13
13
|
DEFAULT_THREADS = 4
|
|
14
14
|
|
|
15
15
|
def initialize
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Pathname.new(Dir.pwd).join('spec/visual_screenshots')
|
|
23
|
-
end
|
|
16
|
+
root_path = if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
17
|
+
Rails.root
|
|
18
|
+
else
|
|
19
|
+
Pathname.new(Dir.pwd)
|
|
20
|
+
end
|
|
21
|
+
@base_path = root_path.join('coverage/screenshots')
|
|
24
22
|
@baseline_dir = @base_path.join('baseline')
|
|
25
23
|
@current_dir = @base_path.join('current_run')
|
|
26
24
|
@diff_dir = @base_path.join('diff')
|
|
@@ -18,6 +18,7 @@ module LookbookVisualTester
|
|
|
18
18
|
def deep_check
|
|
19
19
|
# Ensure custom setup is run before deep checks
|
|
20
20
|
run_setup
|
|
21
|
+
check_preview_controller_config
|
|
21
22
|
run_checks(:deep_render_check)
|
|
22
23
|
end
|
|
23
24
|
|
|
@@ -204,6 +205,27 @@ module LookbookVisualTester
|
|
|
204
205
|
end
|
|
205
206
|
end
|
|
206
207
|
|
|
208
|
+
def check_preview_controller_config
|
|
209
|
+
return unless defined?(Rails)
|
|
210
|
+
|
|
211
|
+
controller_name = Rails.application.config.view_component.preview_controller
|
|
212
|
+
return unless controller_name
|
|
213
|
+
|
|
214
|
+
begin
|
|
215
|
+
controller_class = controller_name.constantize
|
|
216
|
+
unless controller_class.action_methods.include?('render_scenario_to_string')
|
|
217
|
+
puts "WARNING: Configured preview controller '#{controller_name}' does not have 'render_scenario_to_string' action."
|
|
218
|
+
puts ' This is required for Lookbook to render previews correctly.'
|
|
219
|
+
puts " Please ensure your preview controller inherits from 'Lookbook::PreviewController'."
|
|
220
|
+
# We could raise an error here to fail deep_check
|
|
221
|
+
raise "Preview Controller '#{controller_name}' missing required action 'render_scenario_to_string'"
|
|
222
|
+
end
|
|
223
|
+
rescue NameError
|
|
224
|
+
puts "WARNING: Configured preview controller '#{controller_name}' could not be loaded."
|
|
225
|
+
raise "Preview Controller '#{controller_name}' could not be loaded"
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
207
229
|
def run_setup
|
|
208
230
|
if @config.preview_checker_setup
|
|
209
231
|
@config.preview_checker_setup.call
|
|
@@ -142,16 +142,8 @@ module LookbookVisualTester
|
|
|
142
142
|
# Determine paths
|
|
143
143
|
current_path = run_data.current_path
|
|
144
144
|
baseline_path = run_data.baseline_path
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# Actually ScenarioRun#diff_filename is just flat for now but let's fix that?
|
|
148
|
-
# ScenarioRun doesn't expose diff_path with slug. Let's fix that manually here if needed or update ScenarioRun.
|
|
149
|
-
# Wait, ScenarioRun stores baseline/current in folders but diff_filename is just name.
|
|
150
|
-
# We should probably put diffs in folders too.
|
|
151
|
-
# Let's adjust diff_path here:
|
|
152
|
-
if variant_slug.present?
|
|
153
|
-
diff_path = @config.diff_dir.join(variant_slug, run_data.diff_filename)
|
|
154
|
-
end
|
|
145
|
+
folder_name = variant_slug.presence || 'default'
|
|
146
|
+
diff_path = @config.diff_dir.join(folder_name, run_data.diff_filename)
|
|
155
147
|
|
|
156
148
|
FileUtils.mkdir_p(File.dirname(current_path))
|
|
157
149
|
FileUtils.mkdir_p(File.dirname(diff_path))
|
|
@@ -40,16 +40,16 @@ module LookbookVisualTester
|
|
|
40
40
|
"#{preview_name}_#{scenario_name}_diff.png"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
def folder_name
|
|
44
|
+
variant_slug.presence || 'default'
|
|
45
|
+
end
|
|
46
|
+
|
|
43
47
|
def current_path
|
|
44
|
-
|
|
45
|
-
base = base.join(variant_slug) if variant_slug.present?
|
|
46
|
-
base.join(filename)
|
|
48
|
+
LookbookVisualTester.config.current_dir.join(folder_name).join(filename)
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def baseline_path
|
|
50
|
-
|
|
51
|
-
base = base.join(variant_slug) if variant_slug.present?
|
|
52
|
-
base.join(filename)
|
|
52
|
+
LookbookVisualTester.config.baseline_dir.join(folder_name).join(filename)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def preview_url
|
|
@@ -57,7 +57,7 @@ module LookbookVisualTester
|
|
|
57
57
|
|
|
58
58
|
if display_params.any?
|
|
59
59
|
# Transform display_params { theme: 'dark' } -> { _display: { theme: 'dark' } }
|
|
60
|
-
params[:_display] = display_params
|
|
60
|
+
params[:_display] = display_params.to_json
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
Lookbook::Engine.routes.url_helpers.lookbook_preview_url(
|
|
@@ -16,41 +16,45 @@ module LookbookVisualTester
|
|
|
16
16
|
|
|
17
17
|
def call
|
|
18
18
|
unless File.exist?(baseline_path)
|
|
19
|
-
return { diff_path: nil, mismatch: 0.0, error:
|
|
19
|
+
return { diff_path: nil, mismatch: 0.0, error: 'Baseline not found' }
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
baseline = ChunkyPNG::Image.from_file(baseline_path)
|
|
23
23
|
current = ChunkyPNG::Image.from_file(current_path)
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
diff_path: nil,
|
|
28
|
-
mismatch: 100.0,
|
|
29
|
-
error: "Dimensions mismatch: #{baseline.width}x#{baseline.height} vs #{current.width}x#{current.height}"
|
|
30
|
-
}
|
|
31
|
-
end
|
|
25
|
+
max_width = [baseline.width, current.width].max
|
|
26
|
+
max_height = [baseline.height, current.height].max
|
|
32
27
|
|
|
33
28
|
diff_pixels_count = 0
|
|
34
|
-
diff_image = ChunkyPNG::Image.new(
|
|
29
|
+
diff_image = ChunkyPNG::Image.new(max_width, max_height, ChunkyPNG::Color::WHITE)
|
|
30
|
+
|
|
31
|
+
max_height.times do |y|
|
|
32
|
+
max_width.times do |x|
|
|
33
|
+
in_baseline = x < baseline.width && y < baseline.height
|
|
34
|
+
in_current = x < current.width && y < current.height
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
pixel2 = current[x, y]
|
|
36
|
+
if in_baseline && in_current
|
|
37
|
+
pixel1 = baseline[x, y]
|
|
38
|
+
pixel2 = current[x, y]
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
if pixel1 == pixel2
|
|
41
|
+
# Blue context for unchanged pixels to make it easier for humans
|
|
42
|
+
gray_val = ChunkyPNG::Color.r(ChunkyPNG::Color.grayscale_teint(pixel1))
|
|
43
|
+
# Keep intensity in R/G but push blue to make it the dominant tint
|
|
44
|
+
diff_image[x, y] = ChunkyPNG::Color.rgba(gray_val, gray_val, 255, 50)
|
|
45
|
+
else
|
|
46
|
+
diff_image[x, y] = DIFF_COLOR
|
|
47
|
+
diff_pixels_count += 1
|
|
48
|
+
end
|
|
49
|
+
elsif in_baseline || in_current
|
|
50
|
+
# Pixel exists in one but not the other -> Mismatch
|
|
42
51
|
diff_image[x, y] = DIFF_COLOR
|
|
43
52
|
diff_pixels_count += 1
|
|
44
|
-
else
|
|
45
|
-
# Blue context for unchanged pixels to make it easier for humans
|
|
46
|
-
gray_val = ChunkyPNG::Color.r(ChunkyPNG::Color.grayscale_teint(pixel1))
|
|
47
|
-
# Keep intensity in R/G but push blue to make it the dominant tint
|
|
48
|
-
diff_image[x, y] = ChunkyPNG::Color.rgba(gray_val, gray_val, 255, 50)
|
|
49
53
|
end
|
|
50
54
|
end
|
|
51
55
|
end
|
|
52
56
|
|
|
53
|
-
mismatch_percentage = (diff_pixels_count.to_f /
|
|
57
|
+
mismatch_percentage = (diff_pixels_count.to_f / (max_width * max_height)) * 100.0
|
|
54
58
|
|
|
55
59
|
if diff_pixels_count > 0
|
|
56
60
|
FileUtils.mkdir_p(File.dirname(diff_path))
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lookbook_visual_tester
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Murilo Vasconcelos
|
|
@@ -172,6 +172,7 @@ executables: []
|
|
|
172
172
|
extensions: []
|
|
173
173
|
extra_rdoc_files: []
|
|
174
174
|
files:
|
|
175
|
+
- ".agent/rules/style_guide.md"
|
|
175
176
|
- ".rubocop.yml"
|
|
176
177
|
- ".ruby-version"
|
|
177
178
|
- CHANGELOG.md
|