lookbook_visual_tester 0.1.5 → 0.3.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 +25 -3
- data/README.md +96 -32
- data/RELEASING.md +31 -0
- data/Rakefile +12 -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/railtie.rb +3 -1
- data/lib/lookbook_visual_tester/report_generator.rb +25 -48
- data/lib/lookbook_visual_tester/runner.rb +192 -0
- data/lib/lookbook_visual_tester/scenario_finder.rb +7 -2
- data/lib/lookbook_visual_tester/scenario_run.rb +2 -2
- data/lib/lookbook_visual_tester/services/image_comparator.rb +66 -0
- data/lib/lookbook_visual_tester/templates/report.html.erb +206 -0
- data/lib/lookbook_visual_tester/update_previews.rb +11 -3
- data/lib/lookbook_visual_tester/version.rb +1 -1
- data/lib/lookbook_visual_tester.rb +11 -3
- data/lib/tasks/lookbook_visual_tester.rake +264 -58
- metadata +27 -35
- 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: d40b1ba348d95a09c7ea9997d965e00026e9873cf5a8e2faebeed31d6898b6e9
|
|
4
|
+
data.tar.gz: 725d2b67ae419319e79ac56c52cb74f1d3f5354099fa30e670cf292c2e25b07d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c8fac94fb8e61906506b8dd48f87d9b6bb9cb01ec238a2b805a4f98ceadde02cf8555b45c80f1daef4b1710392b0fcd05a36f7420fe279440cd2491d3f63ad2
|
|
7
|
+
data.tar.gz: 3025ca8c334a4185dcbcdc8d2c03288fb46bb07c582d8a2c363cc69929a6be2044246c1d7bc313f06787970e49541872bee57e18cf6b800f8a1afb41b5375aec
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## [0.3.0] - 2026-01-03
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### ✨ New Features & Improvements
|
|
6
|
+
- **Concurrent Screenshot Capture**: Speed up your test suite significantly by running screenshot capture in parallel.
|
|
7
|
+
- Enabled by default with 4 threads.
|
|
8
|
+
- Configurable via `LOOKBOOK_THREADS` environment variable or Rails configuration.
|
|
9
|
+
- **Configurable Concurrency**: Added `threads` configuration option.
|
|
10
|
+
|
|
11
|
+
## [0.2.0] - 2026-01-02
|
|
12
|
+
|
|
13
|
+
### ✨ New Features & Improvements
|
|
14
|
+
- **Visual Diff Aesthetics**: Unchanged context in diff images now rendered with a light blue tint for better human readability.
|
|
15
|
+
- **Standalone Test Harness**: Integrated a dummy Rails application within `spec/dummy` for self-contained development and testing.
|
|
16
|
+
- **Full-Flow Integration Tests**: Added RSpec integration tests that boot a local server and verify the entire screenshot/comparison pipeline.
|
|
17
|
+
- **Lookbook 2.x Compatibility**: Added robust support for Lookbook 2.x `scenarios` while maintaining compatibility with 1.x `examples`.
|
|
18
|
+
- **Filename Normalization**: Improved screenshot naming to handle nested preview paths and avoid directory conflicts.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Fixed Ferrum driver compatibility issue with `traffic_factor` argument in `wait_for_idle`.
|
|
22
|
+
- Fixed various unit tests to correctly mock configuration and scenario run state.
|
|
23
|
+
|
|
24
|
+
## [0.1.6] - 2025-03-04
|
|
25
|
+
|
|
26
|
+
### Better search
|
|
27
|
+
|
|
28
|
+
- better search logic, add components_folder config
|
|
6
29
|
|
|
7
|
-
# Changelog
|
|
8
30
|
|
|
9
31
|
## [0.1.5] - 2025-02-26
|
|
10
32
|
|
data/README.md
CHANGED
|
@@ -2,70 +2,134 @@
|
|
|
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
|
|
24
25
|
```
|
|
25
26
|
|
|
27
|
+
### Gem Installation
|
|
26
28
|
|
|
29
|
+
Add to your application's Gemfile:
|
|
30
|
+
```ruby
|
|
31
|
+
group :test do
|
|
32
|
+
gem 'lookbook_visual_tester'
|
|
33
|
+
end
|
|
34
|
+
```
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
Then install:
|
|
37
|
+
```bash
|
|
38
|
+
bundle install
|
|
39
|
+
```
|
|
29
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
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Running Visual Tests
|
|
57
|
+
|
|
58
|
+
The gem provides Rake tasks to execute the visual regression suite.
|
|
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
|
+
### Baseline Management
|
|
41
79
|
|
|
42
|
-
|
|
43
|
-
|
|
80
|
+
1. **First Run**: When you run the tests for the first time, all screenshots are saved as **Baselines**.
|
|
81
|
+
2. **Subsequent Runs**: New screenshots are compared against the baselines.
|
|
82
|
+
3. **Mismatches**: If a change is detected, a **Diff** image is generated.
|
|
83
|
+
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.
|
|
44
84
|
|
|
45
|
-
###
|
|
85
|
+
### Reporting
|
|
46
86
|
|
|
47
|
-
|
|
87
|
+
After running `rake lookbook:test`, a detailed HTML report is generated at:
|
|
88
|
+
`coverage/visual_report.html`
|
|
48
89
|
|
|
49
|
-
|
|
90
|
+
The report allows you to:
|
|
91
|
+
- See side-by-side comparisons of Baseline vs. Actual.
|
|
92
|
+
- View the diff highlighting changes in neon red.
|
|
93
|
+
- Quickly copy terminal commands to approve changes.
|
|
50
94
|
|
|
51
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
52
95
|
|
|
53
|
-
|
|
96
|
+
### Human-Readable Diffs
|
|
54
97
|
|
|
55
|
-
|
|
98
|
+
When a difference is detected, a diff image is generated where:
|
|
99
|
+
- **Neon Red**: Parts of the component that changed.
|
|
100
|
+
- **Blue Tint**: The unchanged context, making it easy to identify where the change occurred.
|
|
101
|
+
|
|
102
|
+
## Internal Testing (Development)
|
|
103
|
+
|
|
104
|
+
The project includes an internal dummy Rails application for testing the gem itself.
|
|
105
|
+
|
|
106
|
+
### Running the Test Suite
|
|
107
|
+
```bash
|
|
108
|
+
bundle exec rspec
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Running the Full Flow Integration Test
|
|
112
|
+
```bash
|
|
113
|
+
bundle exec rspec spec/integration/full_flow_spec.rb
|
|
114
|
+
```
|
|
56
115
|
|
|
57
|
-
|
|
116
|
+
## Next Steps
|
|
58
117
|
|
|
118
|
+
- **Multi-Viewport Support**: Add ability to capture screenshots at different screen widths (Mobile, Tablet, Desktop).
|
|
119
|
+
- **CI/CD Integration**: Provide recipes for GitHub Actions to run visual regression on PRs.
|
|
120
|
+
- **Reporting Dashboard**: Generate a static HTML report to easily browse all diffs in a single view.
|
|
121
|
+
- [x] **Concurrent Captures**: Optimize execution speed by parallelizing screenshot taking across multiple browser instances.
|
|
59
122
|
|
|
60
|
-
##
|
|
123
|
+
## Contributing
|
|
61
124
|
|
|
62
|
-
|
|
125
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/muriloime/lookbook_visual_tester.
|
|
63
126
|
|
|
64
|
-
|
|
127
|
+
## Deployment
|
|
65
128
|
|
|
66
|
-
|
|
67
|
-
|
|
129
|
+
To release a new version:
|
|
130
|
+
1. Update the version in `lib/lookbook_visual_tester/version.rb`.
|
|
131
|
+
2. Run `bundle exec rake release`.
|
|
68
132
|
|
|
69
|
-
##
|
|
133
|
+
## License
|
|
70
134
|
|
|
71
|
-
|
|
135
|
+
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
|
@@ -10,3 +10,15 @@ require 'rubocop/rake_task'
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
12
|
task default: %i[test rubocop]
|
|
13
|
+
|
|
14
|
+
namespace :release do
|
|
15
|
+
desc 'Release with OTP (MFA) support'
|
|
16
|
+
task :otp do
|
|
17
|
+
require 'io/console'
|
|
18
|
+
print 'Enter OTP code: '
|
|
19
|
+
otp = $stdin.noecho(&:gets).strip
|
|
20
|
+
puts "\n"
|
|
21
|
+
ENV['GEM_HOST_OTP_CODE'] = otp
|
|
22
|
+
Rake::Task['release'].invoke
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -1,8 +1,13 @@
|
|
|
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
|
-
:history_keep_last_n, :threads, :copy_to_clipboard
|
|
6
|
+
:history_keep_last_n, :threads, :copy_to_clipboard,
|
|
7
|
+
:components_folder,
|
|
8
|
+
:automatic_run,
|
|
9
|
+
:mask_selectors, :driver_adapter,
|
|
10
|
+
:logger
|
|
6
11
|
|
|
7
12
|
DEFAULT_THREADS = 4
|
|
8
13
|
|
|
@@ -19,11 +24,29 @@ module LookbookVisualTester
|
|
|
19
24
|
@current_dir = @base_path.join('current_run')
|
|
20
25
|
@diff_dir = @base_path.join('diff')
|
|
21
26
|
@history_dir = @base_path.join('history')
|
|
22
|
-
@threads = DEFAULT_THREADS
|
|
27
|
+
@threads = ENV.fetch('LOOKBOOK_THREADS', DEFAULT_THREADS).to_i
|
|
23
28
|
@history_keep_last_n = 5
|
|
24
29
|
@copy_to_clipboard = true
|
|
30
|
+
@components_folder = 'app/components'
|
|
31
|
+
@automatic_run = ENV.fetch('LOOKBOOK_AUTOMATIC_RUN', false)
|
|
32
|
+
@mask_selectors = []
|
|
33
|
+
@driver_adapter = :ferrum
|
|
34
|
+
@logger = if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
35
|
+
Rails.logger
|
|
36
|
+
else
|
|
37
|
+
require 'logger'
|
|
38
|
+
Logger.new($stdout).tap { |l| l.level = Logger::INFO }
|
|
39
|
+
end
|
|
25
40
|
|
|
26
|
-
@lookbook_host = ENV.fetch('LOOKBOOK_HOST', '
|
|
41
|
+
@lookbook_host = ENV.fetch('LOOKBOOK_HOST', 'http://localhost:5000')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def base_path=(value)
|
|
45
|
+
@base_path = Pathname.new(value)
|
|
46
|
+
@baseline_dir = @base_path.join('baseline')
|
|
47
|
+
@current_dir = @base_path.join('current_run')
|
|
48
|
+
@diff_dir = @base_path.join('diff')
|
|
49
|
+
@history_dir = @base_path.join('history')
|
|
27
50
|
end
|
|
28
51
|
|
|
29
52
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# lib/lookbook_visual_tester/railtie.rb
|
|
2
2
|
|
|
3
|
-
require 'lookbook_visual_tester/capybara_setup'
|
|
3
|
+
# require 'lookbook_visual_tester/capybara_setup'
|
|
4
4
|
require 'lookbook_visual_tester/update_previews'
|
|
5
5
|
|
|
6
6
|
module LookbookVisualTester
|
|
@@ -12,6 +12,8 @@ module LookbookVisualTester
|
|
|
12
12
|
initializer 'LookbookVisualTester.lookbook_after_change' do |_app|
|
|
13
13
|
Rails.logger.info "LookbookVisualTester initialized with host: #{LookbookVisualTester.config.lookbook_host}"
|
|
14
14
|
Lookbook.after_change do |app, changes|
|
|
15
|
+
next unless LookbookVisualTester.config.automatic_run
|
|
16
|
+
|
|
15
17
|
# get hash of content of modified files to see if has changed
|
|
16
18
|
modified = changes[:modified]
|
|
17
19
|
my_hash = modified.sort.map { |f| File.read(f) }.hash
|
|
@@ -1,57 +1,34 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
1
4
|
module LookbookVisualTester
|
|
2
5
|
class ReportGenerator
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
TEMPLATE_PATH = File.expand_path("../templates/report.html.erb", __FILE__)
|
|
7
|
+
OUTPUT_PATH = "coverage/visual_report.html"
|
|
8
|
+
|
|
9
|
+
def initialize(results)
|
|
10
|
+
@results = results
|
|
11
|
+
@stats = {
|
|
12
|
+
total: results.size,
|
|
13
|
+
failed: results.count { |r| r.status == :failed },
|
|
14
|
+
new: results.count { |r| r.status == :new },
|
|
15
|
+
passed: results.count { |r| r.status == :passed }
|
|
16
|
+
}
|
|
11
17
|
end
|
|
12
18
|
|
|
13
|
-
def
|
|
14
|
-
File.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
file.puts "<head><meta charset='UTF-8'><title>Visual Regression Report</title></head>"
|
|
18
|
-
file.puts '<body>'
|
|
19
|
-
file.puts '<h1>Visual Regression Report</h1>'
|
|
20
|
-
file.puts '<ul>'
|
|
21
|
-
|
|
22
|
-
diff_files = Dir.glob(diff_dir.join('*_diff.png'))
|
|
23
|
-
diff_files.each do |diff_file|
|
|
24
|
-
filename = File.basename(diff_file)
|
|
25
|
-
# Extract preview and scenario names
|
|
26
|
-
preview_scenario = filename.sub('_diff.png', '')
|
|
27
|
-
preview, scenario = preview_scenario.split('_', 2)
|
|
28
|
-
|
|
29
|
-
baseline_image = baseline_dir.join("#{preview}_#{scenario}.png")
|
|
30
|
-
current_image = current_dir.join("#{preview}_#{scenario}.png")
|
|
19
|
+
def call
|
|
20
|
+
template = File.read(TEMPLATE_PATH)
|
|
21
|
+
renderer = ERB.new(template)
|
|
22
|
+
html = renderer.result(binding)
|
|
31
23
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
baseline_image_path = Pathname.new(baseline_image)
|
|
37
|
-
current_image_path = Pathname.new(current_image)
|
|
38
|
-
diff_file_path = Pathname.new(diff_file)
|
|
39
|
-
|
|
40
|
-
file.puts "<div><h3>Baseline</h3><img src='#{baseline_image_path.relative_path_from(base_path)}' alt='Baseline'></div>"
|
|
41
|
-
file.puts "<div><h3>Current</h3><img src='#{current_image_path.relative_path_from(base_path)}' alt='Current'></div>"
|
|
42
|
-
file.puts "<div><h3>Diff</h3><img src='#{diff_file_path.relative_path_from(base_path)}' alt='Diff'></div>"
|
|
43
|
-
file.puts '</div>'
|
|
44
|
-
file.puts '</li>'
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
file.puts '<li>No differences found!</li>' if diff_files.empty?
|
|
48
|
-
|
|
49
|
-
file.puts '</ul>'
|
|
50
|
-
file.puts '</body>'
|
|
51
|
-
file.puts '</html>'
|
|
52
|
-
end
|
|
24
|
+
FileUtils.mkdir_p("coverage")
|
|
25
|
+
File.write(OUTPUT_PATH, html)
|
|
26
|
+
puts "📊 Report generated at: #{OUTPUT_PATH}"
|
|
27
|
+
end
|
|
53
28
|
|
|
54
|
-
|
|
29
|
+
# Helper to generate the terminal command for the user
|
|
30
|
+
def approve_command(result)
|
|
31
|
+
"cp \"#{result.current_path}\" \"#{result.baseline_path}\""
|
|
55
32
|
end
|
|
56
33
|
end
|
|
57
34
|
end
|