lookbook_visual_tester 0.1.6 → 0.5.0
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/.rubocop.yml +0 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +39 -3
- data/README.md +144 -32
- data/RELEASING.md +31 -0
- data/Rakefile +16 -5
- data/lib/lookbook_visual_tester/check_reporter.rb +81 -0
- data/lib/lookbook_visual_tester/configuration.rb +27 -4
- data/lib/lookbook_visual_tester/driver.rb +51 -0
- data/lib/lookbook_visual_tester/drivers/ferrum_driver.rb +111 -0
- data/lib/lookbook_visual_tester/json_output_handler.rb +9 -0
- data/lib/lookbook_visual_tester/preview_checker.rb +260 -0
- data/lib/lookbook_visual_tester/railtie.rb +5 -2
- data/lib/lookbook_visual_tester/report_generator.rb +25 -48
- data/lib/lookbook_visual_tester/runner.rb +224 -0
- data/lib/lookbook_visual_tester/scenario_finder.rb +7 -2
- data/lib/lookbook_visual_tester/scenario_run.rb +26 -8
- data/lib/lookbook_visual_tester/services/image_comparator.rb +66 -0
- data/lib/lookbook_visual_tester/templates/preview_check_report.html.tt +63 -0
- data/lib/lookbook_visual_tester/templates/report.html.erb +206 -0
- data/lib/lookbook_visual_tester/update_previews.rb +3 -2
- data/lib/lookbook_visual_tester/variant_resolver.rb +62 -0
- data/lib/lookbook_visual_tester/version.rb +1 -1
- data/lib/lookbook_visual_tester.rb +11 -3
- data/lib/tasks/lookbook_visual_tester.rake +293 -58
- metadata +32 -22
- data/tasks/lookbook_visual_tester.rake +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60819c43209d30cab93b22e923f369c0fdb85ae2f0cbc5780a9670ca26571677
|
|
4
|
+
data.tar.gz: 231ec3a33592037451d4f8a9354c702971314405b4ab13a6332c911baa915681
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 782a83e7388499ededdc31453a708969a839b060e09b24de481f55c48f193397d30c873a8137bbec40e4da0f30688cb4485f66d3e93b07ecb19f9d7dc0517bb4
|
|
7
|
+
data.tar.gz: 699056dca68df2a0961547c639da77b62bda599dd5a44013e4b7717d6dd8afeb02195b34c447bb66a72fb8ef8a0b35168cce7bc12cffaa4570a4b9629b8dbc21
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.4.
|
|
1
|
+
3.4.7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
## [0.5.0] - 2026-01-03
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
### ✨ New Features & Improvements
|
|
5
|
+
- **Preview Health Checks**: Added new Rake tasks to verify preview integrity:
|
|
6
|
+
- `lookbook:check`: Rapidly checks if previews load and instantiate without errors.
|
|
7
|
+
- `lookbook:deep_check`: Verified previews by effectively rendering them to catch runtime and template errors.
|
|
8
|
+
- `lookbook:missing`: Identifies ViewComponents that lack a corresponding preview.
|
|
9
|
+
- **Parallel Preview Checks**: Health checks run in parallel using `concurrent-ruby`.
|
|
10
|
+
- **Comprehensive Reporting**: Checks generate colored terminal output and a detailed HTML report (`coverage/preview_check_report.html`) including timing stats and slowest previews.
|
|
11
|
+
- **Custom Deep Check Setup**: Added `config.preview_checker_setup` to allow defining mocks (e.g., User, Warden) required for deep checking.
|
|
4
12
|
|
|
5
|
-
|
|
13
|
+
## [0.4.0] - 2026-01-03
|
|
6
14
|
|
|
7
|
-
|
|
15
|
+
### ✨ New Features & Improvements
|
|
16
|
+
- **Multiple Screenshot Variants**: Support for defining screenshot variants (e.g., specific viewports, themes) using Lookbook's `preview_display_options`.
|
|
17
|
+
- Configurable via `VARIANTS` environment variable (JSON array).
|
|
18
|
+
- Screenshots are saved in subdirectories corresponding to the variant options.
|
|
19
|
+
- Automatic browser resizing based on `width` options.
|
|
20
|
+
|
|
21
|
+
### 🧹 Housekeeping
|
|
22
|
+
- **Removed Minitest**: Switched completely to RSpec for internal testing. Deleted unused Minitest files and configuration.
|
|
23
|
+
|
|
24
|
+
## [0.3.0] - 2026-01-03
|
|
25
|
+
|
|
26
|
+
### ✨ New Features & Improvements
|
|
27
|
+
- **Concurrent Screenshot Capture**: Speed up your test suite significantly by running screenshot capture in parallel.
|
|
28
|
+
- Enabled by default with 4 threads.
|
|
29
|
+
- Configurable via `LOOKBOOK_THREADS` environment variable or Rails configuration.
|
|
30
|
+
- **Configurable Concurrency**: Added `threads` configuration option.
|
|
31
|
+
|
|
32
|
+
## [0.2.0] - 2026-01-02
|
|
33
|
+
|
|
34
|
+
### ✨ New Features & Improvements
|
|
35
|
+
- **Visual Diff Aesthetics**: Unchanged context in diff images now rendered with a light blue tint for better human readability.
|
|
36
|
+
- **Standalone Test Harness**: Integrated a dummy Rails application within `spec/dummy` for self-contained development and testing.
|
|
37
|
+
- **Full-Flow Integration Tests**: Added RSpec integration tests that boot a local server and verify the entire screenshot/comparison pipeline.
|
|
38
|
+
- **Lookbook 2.x Compatibility**: Added robust support for Lookbook 2.x `scenarios` while maintaining compatibility with 1.x `examples`.
|
|
39
|
+
- **Filename Normalization**: Improved screenshot naming to handle nested preview paths and avoid directory conflicts.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
- Fixed Ferrum driver compatibility issue with `traffic_factor` argument in `wait_for_idle`.
|
|
43
|
+
- Fixed various unit tests to correctly mock configuration and scenario run state.
|
|
8
44
|
|
|
9
45
|
## [0.1.6] - 2025-03-04
|
|
10
46
|
|
data/README.md
CHANGED
|
@@ -2,70 +2,182 @@
|
|
|
2
2
|
|
|
3
3
|
# Lookbook Visual Tester
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A powerful visual regression testing tool for [ViewComponent](https://viewcomponent.org/) via [Lookbook](https://lookbook.build/). It automates the process of capturing screenshots of your components, comparing them against baselines, and highlighting differences with human-friendly aesthetics.
|
|
6
6
|
|
|
7
|
+
### Key Features
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- Very useful for AI coding (e.g. aider, etc.)
|
|
9
|
+
- **Automated Visual Regression**: Captures and compares screenshots of all Lookbook previews.
|
|
10
|
+
- **Intelligent Diffing**: High-quality image comparison using `chunky_png`.
|
|
11
|
+
- **Human-Friendly Aesthetics**: Context is rendered with a light blue tint to make red highlights of differences pop.
|
|
12
|
+
- **Robust Capture**: Automatically disables animations, waits for network idle, and allows masking/cropping.
|
|
13
|
+
- **Lookbook Support**: Compatible with both Lookbook 1.x (`examples`) and 2.x (`scenarios`).
|
|
14
|
+
- **Internal Test Harness**: Includes a dummy Rails app for self-contained testing and development.
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
### System Dependencies
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
The gem requires `imagemagick` for image processing and `xclip` for clipboard integration (Linux).
|
|
21
21
|
|
|
22
|
+
For Ubuntu-based systems:
|
|
22
23
|
```bash
|
|
23
|
-
sudo apt-get install xclip
|
|
24
|
+
sudo apt-get install imagemagick xclip
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Gem Installation
|
|
28
|
+
|
|
29
|
+
Add to your application's Gemfile:
|
|
30
|
+
```ruby
|
|
31
|
+
group :test do
|
|
32
|
+
gem 'lookbook_visual_tester'
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then install:
|
|
37
|
+
```bash
|
|
38
|
+
bundle install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
You can configure the tester in a Rails initializer:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
LookbookVisualTester.configure do |config|
|
|
47
|
+
config.lookbook_host = "http://localhost:3000" # Where your rails app is running
|
|
48
|
+
config.base_path = "spec/visual_regression" # Root for screenshots
|
|
49
|
+
config.copy_to_clipboard = true # Enable xclip support
|
|
50
|
+
config.threads = 4 # Number of parallel threads (default: 4)
|
|
51
|
+
end
|
|
24
52
|
```
|
|
25
53
|
|
|
54
|
+
## Usage
|
|
26
55
|
|
|
56
|
+
### Running Visual Tests
|
|
27
57
|
|
|
28
|
-
|
|
58
|
+
The gem provides Rake tasks to execute the visual regression suite.
|
|
29
59
|
|
|
60
|
+
#### Run All Tests
|
|
61
|
+
Runs all Lookbook previews, generates a terminal summary, and creates an HTML report.
|
|
30
62
|
```bash
|
|
31
|
-
bundle
|
|
63
|
+
bundle exec rake lookbook:test
|
|
32
64
|
```
|
|
33
65
|
|
|
34
|
-
|
|
66
|
+
#### Test a Specific Preview
|
|
67
|
+
Filter previews by name or label.
|
|
68
|
+
```bash
|
|
69
|
+
bundle exec rake lookbook:screenshot[Button]
|
|
70
|
+
```
|
|
35
71
|
|
|
72
|
+
#### Configuration Overrides
|
|
73
|
+
You can override the host or other settings inline:
|
|
36
74
|
```bash
|
|
37
|
-
|
|
75
|
+
LOOKBOOK_HOST=http://localhost:5000 LOOKBOOK_THREADS=8 bundle exec rake lookbook:test
|
|
38
76
|
```
|
|
39
77
|
|
|
40
|
-
|
|
78
|
+
#### Screenshot Variants
|
|
41
79
|
|
|
42
|
-
|
|
43
|
-
bundle exec rake lookbook_visual_tester:run LOOKBOOK_HOST=https://localhost:5000
|
|
80
|
+
You can run your visual tests against multiple configurations (variants), such as different themes or viewports, by leveraging Lookbook's `preview_display_options`.
|
|
44
81
|
|
|
45
|
-
|
|
82
|
+
1. **Define Options in Lookbook**:
|
|
83
|
+
Ensure your Rails app has display options configured:
|
|
84
|
+
```ruby
|
|
85
|
+
# config/lookbook.rb
|
|
86
|
+
Lookbook.config.preview_display_options = {
|
|
87
|
+
theme: ["light", "dark"],
|
|
88
|
+
width: [["Mobile", "375px"], ["Desktop", "1280px"]]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
46
91
|
|
|
47
|
-
|
|
92
|
+
2. **Run with Variants**:
|
|
93
|
+
Use the `VARIANTS` environment variable to define a JSON array of option sets to test.
|
|
48
94
|
|
|
49
|
-
|
|
95
|
+
*Example: Run standard tests + Dark Mode + Mobile View*
|
|
96
|
+
```bash
|
|
97
|
+
VARIANTS='[{}, {"theme":"dark"}, {"width":"Mobile"}]' bundle exec rake lookbook:test
|
|
98
|
+
```
|
|
50
99
|
|
|
51
|
-
|
|
100
|
+
* **`{}`**: Runs the default/standard preview.
|
|
101
|
+
* **`{"theme":"dark"}`**: Runs with `_display[theme]=dark`.
|
|
102
|
+
* **`{"width":"Mobile"}`**: Runs with `_display[width]=375px` AND automatically resizes the browser window to 375px width.
|
|
52
103
|
|
|
53
|
-
|
|
104
|
+
Screenshots for variants are saved in dedicated subfolders (e.g., `spec/visual_regression/baseline/theme-dark/`).
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
### Baseline Management
|
|
108
|
+
|
|
109
|
+
1. **First Run**: When you run the tests for the first time, all screenshots are saved as **Baselines**.
|
|
110
|
+
2. **Subsequent Runs**: New screenshots are compared against the baselines.
|
|
111
|
+
3. **Mismatches**: If a change is detected, a **Diff** image is generated.
|
|
112
|
+
4. **Approval**: To approve a change (update the baseline), simply copy the file from `current_run` to `baseline`. The HTML report provides a convenient "Copy Approval Command" button for this.
|
|
113
|
+
|
|
114
|
+
### Reporting
|
|
115
|
+
|
|
116
|
+
After running `rake lookbook:test`, a detailed HTML report is generated at:
|
|
117
|
+
`coverage/visual_report.html`
|
|
118
|
+
|
|
119
|
+
The report allows you to:
|
|
120
|
+
- See side-by-side comparisons of Baseline vs. Actual.
|
|
121
|
+
- View the diff highlighting changes in neon red.
|
|
122
|
+
- Quickly copy terminal commands to approve changes.
|
|
54
123
|
|
|
55
|
-
## Contributing
|
|
56
124
|
|
|
57
|
-
|
|
125
|
+
### Human-Readable Diffs
|
|
58
126
|
|
|
127
|
+
When a difference is detected, a diff image is generated where:
|
|
128
|
+
- **Neon Red**: Parts of the component that changed.
|
|
129
|
+
- **Blue Tint**: The unchanged context, making it easy to identify where the change occurred.
|
|
59
130
|
|
|
60
|
-
##
|
|
131
|
+
## Internal Testing (Development)
|
|
132
|
+
|
|
133
|
+
The project includes an internal dummy Rails application for testing the gem itself.
|
|
134
|
+
|
|
135
|
+
### Running the Test Suite
|
|
136
|
+
```bash
|
|
137
|
+
bundle exec rspec
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Running the Full Flow Integration Test
|
|
141
|
+
```bash
|
|
142
|
+
bundle exec rspec spec/integration/full_flow_spec.rb
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Preview Health Checks
|
|
146
|
+
|
|
147
|
+
The gem provides tasks to ensure your previews are healthy and up-to-date.
|
|
148
|
+
|
|
149
|
+
#### Check Load/Syntax
|
|
150
|
+
Checks if all previews can be loaded and instantiated without errors.
|
|
151
|
+
```bash
|
|
152
|
+
bundle exec rake lookbook:check
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Deep Check (Render)
|
|
156
|
+
effectively renders all previews to catch runtime and template errors.
|
|
157
|
+
```bash
|
|
158
|
+
bundle exec rake lookbook:deep_check
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Find Missing Previews
|
|
162
|
+
Identifies components that don't have a corresponding preview file.
|
|
163
|
+
```bash
|
|
164
|
+
bundle exec rake lookbook:missing
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Next Steps
|
|
168
|
+
|
|
169
|
+
- **CI/CD Integration**: Provide recipes for GitHub Actions to run visual regression on PRs.
|
|
170
|
+
|
|
171
|
+
## Contributing
|
|
61
172
|
|
|
62
|
-
|
|
173
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/muriloime/lookbook_visual_tester.
|
|
63
174
|
|
|
64
|
-
|
|
175
|
+
## Deployment
|
|
65
176
|
|
|
66
|
-
|
|
67
|
-
|
|
177
|
+
To release a new version:
|
|
178
|
+
1. Update the version in `lib/lookbook_visual_tester/version.rb`.
|
|
179
|
+
2. Run `bundle exec rake release`.
|
|
68
180
|
|
|
69
|
-
##
|
|
181
|
+
## License
|
|
70
182
|
|
|
71
|
-
|
|
183
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/RELEASING.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
This gem uses `bundler/gem_tasks` to manage releases.
|
|
4
|
+
|
|
5
|
+
## Instructions
|
|
6
|
+
|
|
7
|
+
1. Make sure your working directory is clean and you are on the `main` branch.
|
|
8
|
+
2. Update the version in `lib/lookbook_visual_tester/version.rb`.
|
|
9
|
+
3. Update the `CHANGELOG.md`.
|
|
10
|
+
4. Run the release task:
|
|
11
|
+
```bash
|
|
12
|
+
bundle exec rake release:otp
|
|
13
|
+
```
|
|
14
|
+
This will:
|
|
15
|
+
- Create a git tag for the version.
|
|
16
|
+
- Push git commits and tags.
|
|
17
|
+
- Build the gem.
|
|
18
|
+
- Push the `.gem` file to RubyGems.org.
|
|
19
|
+
|
|
20
|
+
## Troubleshooting
|
|
21
|
+
|
|
22
|
+
If you encounter issues with authentication, ensure you are logged in to RubyGems via the `gem` CLI:
|
|
23
|
+
```bash
|
|
24
|
+
gem signin
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If you need to supply a 2FA/OTP code manually or for a script, you can use the `release:otp` task or set the `GEM_HOST_OTP_CODE` environment variable:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
GEM_HOST_OTP_CODE=123456 bundle exec rake release
|
|
31
|
+
```
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'bundler/gem_tasks'
|
|
4
|
-
require '
|
|
5
|
-
|
|
6
|
-
Minitest::TestTask.create
|
|
7
|
-
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
8
5
|
require 'rubocop/rake_task'
|
|
9
6
|
|
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
8
|
+
|
|
10
9
|
RuboCop::RakeTask.new
|
|
11
10
|
|
|
12
|
-
task default: %i[
|
|
11
|
+
task default: %i[spec rubocop]
|
|
12
|
+
|
|
13
|
+
namespace :release do
|
|
14
|
+
desc 'Release with OTP (MFA) support'
|
|
15
|
+
task :otp do
|
|
16
|
+
require 'io/console'
|
|
17
|
+
print 'Enter OTP code: '
|
|
18
|
+
otp = $stdin.noecho(&:gets).strip
|
|
19
|
+
puts "\n"
|
|
20
|
+
ENV['GEM_HOST_OTP_CODE'] = otp
|
|
21
|
+
Rake::Task['release'].invoke
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'rainbow'
|
|
2
|
+
require 'thor/group'
|
|
3
|
+
|
|
4
|
+
module LookbookVisualTester
|
|
5
|
+
class CheckReporter < Thor::Group
|
|
6
|
+
include Thor::Actions
|
|
7
|
+
|
|
8
|
+
argument :results
|
|
9
|
+
|
|
10
|
+
def self.source_root
|
|
11
|
+
File.dirname(__FILE__)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def report
|
|
15
|
+
@errors = results.select { |r| r.status == :failed }
|
|
16
|
+
@success_count = results.count { |r| r.status == :passed }
|
|
17
|
+
@total_duration = results.sum { |r| r.duration.to_f }
|
|
18
|
+
|
|
19
|
+
report_terminal
|
|
20
|
+
report_html
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.report_missing(missing)
|
|
24
|
+
if missing.any?
|
|
25
|
+
puts Rainbow("\nFound #{missing.size} components missing previews:").yellow
|
|
26
|
+
missing.sort_by { |m| m.component_path }.each do |m|
|
|
27
|
+
puts " - #{m.component_path}"
|
|
28
|
+
end
|
|
29
|
+
puts "\nTotal: #{missing.size} missing previews."
|
|
30
|
+
else
|
|
31
|
+
puts Rainbow('All components have previews!').green
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
attr_reader :errors, :success_count, :total_duration
|
|
38
|
+
|
|
39
|
+
def report_terminal
|
|
40
|
+
puts "\n--- Check Results ---"
|
|
41
|
+
|
|
42
|
+
results.each do |result|
|
|
43
|
+
if result.status == :passed
|
|
44
|
+
print Rainbow('.').green
|
|
45
|
+
else
|
|
46
|
+
print Rainbow('F').red
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
puts "\n"
|
|
50
|
+
|
|
51
|
+
if errors.any?
|
|
52
|
+
puts Rainbow("\n#{errors.size} errors found:").red
|
|
53
|
+
errors.each do |err|
|
|
54
|
+
puts "\n--------------------------------------------------"
|
|
55
|
+
puts "Preview: #{err.preview_name}##{err.example_name}"
|
|
56
|
+
puts "Error: #{err.error}"
|
|
57
|
+
puts "Time: #{err.duration.to_f.round(4)}s"
|
|
58
|
+
puts "Backtrace: #{err.backtrace&.first(3)&.join("\n ")}"
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
puts Rainbow("\nAll #{results.size} previews passed!").green
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
puts "\nTotal time: #{total_duration.round(2)}s"
|
|
65
|
+
|
|
66
|
+
report_slowest_previews
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def report_slowest_previews
|
|
70
|
+
puts "\n--- Top 5 Slowest Previews ---"
|
|
71
|
+
slowest = results.sort_by { |r| -r.duration.to_f }.first(5)
|
|
72
|
+
slowest.each do |res|
|
|
73
|
+
puts "#{Rainbow("#{res.duration.to_f.round(4)}s").yellow} - #{res.preview_name}##{res.example_name}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def report_html
|
|
78
|
+
template 'templates/preview_check_report.html.tt', 'coverage/preview_check_report.html'
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
module LookbookVisualTester
|
|
2
2
|
class Configuration
|
|
3
|
-
|
|
3
|
+
attr_reader :base_path
|
|
4
|
+
attr_accessor :lookbook_host,
|
|
4
5
|
:baseline_dir, :current_dir, :diff_dir, :history_dir,
|
|
5
6
|
:history_keep_last_n, :threads, :copy_to_clipboard,
|
|
6
|
-
:components_folder
|
|
7
|
+
:components_folder,
|
|
8
|
+
:automatic_run,
|
|
9
|
+
:mask_selectors, :driver_adapter,
|
|
10
|
+
:preview_checker_setup,
|
|
11
|
+
:logger
|
|
7
12
|
|
|
8
13
|
DEFAULT_THREADS = 4
|
|
9
14
|
|
|
@@ -20,12 +25,30 @@ module LookbookVisualTester
|
|
|
20
25
|
@current_dir = @base_path.join('current_run')
|
|
21
26
|
@diff_dir = @base_path.join('diff')
|
|
22
27
|
@history_dir = @base_path.join('history')
|
|
23
|
-
@threads = DEFAULT_THREADS
|
|
28
|
+
@threads = ENV.fetch('LOOKBOOK_THREADS', DEFAULT_THREADS).to_i
|
|
24
29
|
@history_keep_last_n = 5
|
|
25
30
|
@copy_to_clipboard = true
|
|
26
31
|
@components_folder = 'app/components'
|
|
32
|
+
@automatic_run = ENV.fetch('LOOKBOOK_AUTOMATIC_RUN', false)
|
|
33
|
+
@mask_selectors = []
|
|
34
|
+
@driver_adapter = :ferrum
|
|
35
|
+
@preview_checker_setup = nil
|
|
36
|
+
@logger = if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
37
|
+
Rails.logger
|
|
38
|
+
else
|
|
39
|
+
require 'logger'
|
|
40
|
+
Logger.new($stdout).tap { |l| l.level = Logger::INFO }
|
|
41
|
+
end
|
|
27
42
|
|
|
28
|
-
@lookbook_host = ENV.fetch('LOOKBOOK_HOST', '
|
|
43
|
+
@lookbook_host = ENV.fetch('LOOKBOOK_HOST', 'http://localhost:5000')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def base_path=(value)
|
|
47
|
+
@base_path = Pathname.new(value)
|
|
48
|
+
@baseline_dir = @base_path.join('baseline')
|
|
49
|
+
@current_dir = @base_path.join('current_run')
|
|
50
|
+
@diff_dir = @base_path.join('diff')
|
|
51
|
+
@history_dir = @base_path.join('history')
|
|
29
52
|
end
|
|
30
53
|
|
|
31
54
|
class << self
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module LookbookVisualTester
|
|
2
|
+
class Driver
|
|
3
|
+
attr_reader :config
|
|
4
|
+
|
|
5
|
+
def initialize(config)
|
|
6
|
+
@config = config
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def visit(url)
|
|
10
|
+
raise NotImplementedError
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def resize_window(width, height)
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def inject_style(css)
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run_script(script)
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def mask_elements(selectors)
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Smart wait implementation
|
|
30
|
+
def wait_for_assets
|
|
31
|
+
wait_for_fonts
|
|
32
|
+
wait_for_network_idle
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def wait_for_fonts
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def wait_for_network_idle
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def save_screenshot(path)
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def cleanup
|
|
48
|
+
raise NotImplementedError
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require_relative '../driver'
|
|
2
|
+
require 'ferrum'
|
|
3
|
+
|
|
4
|
+
module LookbookVisualTester
|
|
5
|
+
module Drivers
|
|
6
|
+
class FerrumDriver < Driver
|
|
7
|
+
def initialize(config)
|
|
8
|
+
super
|
|
9
|
+
@browser = Ferrum::Browser.new(
|
|
10
|
+
headless: true,
|
|
11
|
+
window_size: [1280, 800], # Default, can be resized
|
|
12
|
+
timeout: 10 # Configurable?
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def visit(url)
|
|
17
|
+
@browser.go_to(url)
|
|
18
|
+
prepare_page
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def resize_window(width, height)
|
|
22
|
+
@browser.resize(width: width, height: height)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def inject_style(css)
|
|
26
|
+
# Ferrum allows adding style tags via JS
|
|
27
|
+
script = <<~JS
|
|
28
|
+
const style = document.createElement('style');
|
|
29
|
+
style.innerHTML = `#{css}`;
|
|
30
|
+
document.head.appendChild(style);
|
|
31
|
+
JS
|
|
32
|
+
@browser.execute(script)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run_script(script)
|
|
36
|
+
@browser.execute(script)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def mask_elements(selectors)
|
|
40
|
+
return if selectors.empty?
|
|
41
|
+
|
|
42
|
+
selectors.each do |selector|
|
|
43
|
+
script = <<~JS
|
|
44
|
+
document.querySelectorAll('#{selector}').forEach(el => {
|
|
45
|
+
el.style.visibility = 'hidden';
|
|
46
|
+
});
|
|
47
|
+
JS
|
|
48
|
+
@browser.execute(script)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def wait_for_assets
|
|
53
|
+
wait_for_network_idle
|
|
54
|
+
wait_for_fonts
|
|
55
|
+
wait_for_custom_selectors
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def wait_for_fonts
|
|
59
|
+
@browser.execute("return document.fonts.ready")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def wait_for_network_idle(timeout: 2, duration: 0.5)
|
|
63
|
+
# Ferrum has built-in network idle waiting
|
|
64
|
+
@browser.network.wait_for_idle
|
|
65
|
+
rescue Ferrum::TimeoutError
|
|
66
|
+
# Log warning but proceed?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def save_screenshot(path)
|
|
70
|
+
@browser.screenshot(path: path, full: true)
|
|
71
|
+
# Note: full: true captures the whole page.
|
|
72
|
+
# If we capture viewport only, we should remove full: true.
|
|
73
|
+
# Usually for visual testing full page is better unless specifically testing viewport.
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def cleanup
|
|
77
|
+
@browser.quit
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def prepare_page
|
|
83
|
+
disable_animations
|
|
84
|
+
mask_elements(config.mask_selectors || [])
|
|
85
|
+
wait_for_assets
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def disable_animations
|
|
89
|
+
css = "* { transition: none !important; animation: none !important; caret-color: transparent !important; }"
|
|
90
|
+
inject_style(css)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def wait_for_custom_selectors
|
|
94
|
+
# If we have specific selectors to wait for e.g. document.fonts.status check loop
|
|
95
|
+
start = Time.now
|
|
96
|
+
until @browser.execute("return document.fonts.status === 'loaded'")
|
|
97
|
+
break if Time.now - start > 5
|
|
98
|
+
sleep 0.1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Also check for images loading attributes if needed
|
|
102
|
+
# "check that no images have loading attributes active"
|
|
103
|
+
# Often checking complete property is enough
|
|
104
|
+
until @browser.execute("return Array.from(document.images).every(i => i.complete)")
|
|
105
|
+
break if Time.now - start > 5
|
|
106
|
+
sleep 0.1
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|