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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a29c54b1403f4faeaea71ef267faf9aba07d033c3c0bfc79617c0bf0cf77a490
4
- data.tar.gz: 2d8cb90e20f1ad2dd92ca6c677bb4e8185c62a16b0b070721551e789575de219
3
+ metadata.gz: d40b1ba348d95a09c7ea9997d965e00026e9873cf5a8e2faebeed31d6898b6e9
4
+ data.tar.gz: 725d2b67ae419319e79ac56c52cb74f1d3f5354099fa30e670cf292c2e25b07d
5
5
  SHA512:
6
- metadata.gz: 8328f592622c337d11dca1c10811834e85f0a00cecc92eeb6f6a62c04cf2e1df50e9eed9700ac1b11041e39373710c371454c19d71db0b17c95b2790a67ebaeb
7
- data.tar.gz: 3dba60c0c0d2867b185e45c53d68569b29e87c4ac57bf649767aa6c17ada5405d1cf0f493c6b96d75a1755405c70d050b165a72fe58a9f39648609ea703adecb
6
+ metadata.gz: 1c8fac94fb8e61906506b8dd48f87d9b6bb9cb01ec238a2b805a4f98ceadde02cf8555b45c80f1daef4b1710392b0fcd05a36f7420fe279440cd2491d3f63ad2
7
+ data.tar.gz: 3025ca8c334a4185dcbcdc8d2c03288fb46bb07c582d8a2c363cc69929a6be2044246c1d7bc313f06787970e49541872bee57e18cf6b800f8a1afb41b5375aec
data/.rubocop.yml CHANGED
@@ -7,7 +7,6 @@ AllCops:
7
7
  - "vendor/**/*"
8
8
  - "app/assets/**/*"
9
9
  - "db/schema.rb"
10
- TargetRubyVersion: 3.2.1
11
10
  DisplayCopNames: true
12
11
  NewCops: enable
13
12
 
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.2.1
1
+ 3.4.7
data/CHANGELOG.md CHANGED
@@ -1,10 +1,32 @@
1
1
  # Changelog
2
2
 
3
- Here is the changelog for version **0.1.5** of *LookbookVisualTester*:
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
- This gem was built to serve as a lookbook regression tester, getting prints from previews, comparing them and generating a report of differences.
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
- ### Features
9
-
10
- - Perform visual regression testing on changes.
11
- - Integrate seamlessly with your Lookbook previews.
12
- - Automatically generate image differences.
13
- - Simplify debugging and quality checks.
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
- Tested on linux. A couple of changes to work on mac, etc. You will need tools like xclip, imagemagick
18
+ ### System Dependencies
19
19
 
20
- For Ubuntu-based systems, install the necessary dependencies by running:
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 imagemagick
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
- Install the gem and add to the application's Gemfile by executing:
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 add lookbook_visual_tester
63
+ bundle exec rake lookbook:test
32
64
  ```
33
65
 
34
- If bundler is not being used to manage dependencies, install the gem by executing:
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
- gem install lookbook_visual_tester
75
+ LOOKBOOK_HOST=http://localhost:5000 LOOKBOOK_THREADS=8 bundle exec rake lookbook:test
38
76
  ```
39
77
 
40
- ## Usage
78
+ ### Baseline Management
41
79
 
42
- To run everything
43
- bundle exec rake lookbook_visual_tester:run LOOKBOOK_HOST=https://localhost:5000
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
- ### Features
85
+ ### Reporting
46
86
 
47
- When installed this gem gets your changes and generates
87
+ After running `rake lookbook:test`, a detailed HTML report is generated at:
88
+ `coverage/visual_report.html`
48
89
 
49
- ## Development
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
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
96
+ ### Human-Readable Diffs
54
97
 
55
- ## Contributing
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
- Bug reports and pull requests are welcome on GitHub at https://github.com/muriloime/lookbook_visual_tester. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/lookbook_visual_tester/blob/main/CODE_OF_CONDUCT.md).
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
- ## Deployment
123
+ ## Contributing
61
124
 
62
- Generate artifacts for changelog :
125
+ Bug reports and pull requests are welcome on GitHub at https://github.com/muriloime/lookbook_visual_tester.
63
126
 
64
- `git log --pretty=format:"%h %ad | %s [%an]" --date=short --no-merges --name-only | xclip -selection clipboard`
127
+ ## Deployment
65
128
 
66
- Update changelog
67
- `rake realease`
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
- ## Code of Conduct
133
+ ## License
70
134
 
71
- Everyone interacting in the LookbookVisualTester project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/lookbook_visual_tester/blob/main/CODE_OF_CONDUCT.md).
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
- attr_accessor :base_path, :lookbook_host,
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', 'https://localhost:5000')
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
@@ -0,0 +1,9 @@
1
+ require 'json'
2
+
3
+ module LookbookVisualTester
4
+ class JsonOutputHandler
5
+ def self.print(data)
6
+ puts JSON.pretty_generate(data)
7
+ end
8
+ end
9
+ 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
- attr_reader :report_path, :baseline_dir, :current_dir, :diff_dir, :base_path
4
-
5
- def initialize
6
- @baseline_dir, @current_dir, @diff_dir, @base_path = LookbookVisualTester.config.then do |config|
7
- [config.baseline_dir, config.current_dir, config.diff_dir, config.base_path]
8
- end
9
-
10
- @report_path = base_path.join('report.html')
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 generate
14
- File.open(report_path, 'w') do |file|
15
- file.puts '<!DOCTYPE html>'
16
- file.puts "<html lang='en'>"
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
- file.puts '<li>'
33
- file.puts "<h2>#{preview.titleize} - #{scenario.titleize}</h2>"
34
- file.puts "<div style='display: flex; gap: 10px;'>"
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
- @report_path
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