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.
@@ -1,92 +1,327 @@
1
1
  # lib/tasks/lookbook_visual_tester.rake
2
- require 'fileutils'
3
- require 'mini_magick'
4
- require 'ruby-prof'
5
- require 'concurrent-ruby'
6
-
2
+ require 'lookbook_visual_tester/runner'
7
3
  require 'lookbook_visual_tester/report_generator'
8
- require 'lookbook_visual_tester/screenshot_taker'
9
- require 'lookbook_visual_tester/image_comparator'
10
- require 'lookbook_visual_tester/scenario_run'
4
+ require 'lookbook_visual_tester/json_output_handler'
11
5
 
12
- namespace :lookbook_visual_tester do
13
- desc 'Profile the lookbook_visual_tester:run task'
14
- task profile: :environment do
15
- RubyProf.start
16
- Rake::Task['lookbook_visual_tester:run'].invoke
17
- result = RubyProf.stop
6
+ require 'lookbook_visual_tester/preview_checker'
7
+ require 'lookbook_visual_tester/check_reporter'
18
8
 
19
- printer = RubyProf::FlatPrinter.new(result)
20
- printer.print(STDOUT)
9
+ namespace :lookbook do
10
+ desc 'Check previews for load/syntax errors'
11
+ task check: :environment do
12
+ puts 'Checking previews...'
13
+ checker = LookbookVisualTester::PreviewChecker.new
14
+ results = checker.check
15
+ LookbookVisualTester::CheckReporter.start([results])
21
16
  end
22
17
 
23
- desc 'Run and copy to clipboard first scenario matching the given name'
24
- task :copy, [:name] => :environment do |t, args|
25
- # example on how to run: `rake lookbook_visual_tester:copy["Button"]`
18
+ desc 'Deep check previews by rendering them'
19
+ task deep_check: :environment do
20
+ puts 'Deep checking previews (render)...'
21
+ checker = LookbookVisualTester::PreviewChecker.new
22
+ results = checker.deep_check
23
+ LookbookVisualTester::CheckReporter.start([results])
24
+ end
26
25
 
27
- previews = Lookbook.previews
26
+ desc 'Find components missing previews'
27
+ task missing: :environment do
28
+ puts 'Finding missing previews...'
29
+ checker = LookbookVisualTester::PreviewChecker.new
30
+ missing = checker.missing
31
+ LookbookVisualTester::CheckReporter.report_missing(missing)
32
+ end
33
+ end
28
34
 
29
- scenario_run = LookbookVisualTester::ScenarioFinder.call(args[:name], previews)
30
- unless scenario_run
31
- screenshot_taker = LookbookVisualTester::ScreenshotTaker.new(scenario_run:)
32
- puts "No Lookbook previews found matching #{args[:name]}"
33
- exit
35
+ namespace :lookbook do
36
+ desc 'List all available previews'
37
+ task :list, [:format] => :environment do |_, args|
38
+ require 'lookbook'
39
+ previews = Lookbook.previews.flat_map do |preview|
40
+ group = preview.respond_to?(:scenarios) ? preview.scenarios : preview.examples
41
+ group.map { |scenario| scenario.lookup_path }
42
+ end.sort
43
+
44
+ if args[:format] == 'json'
45
+ LookbookVisualTester::JsonOutputHandler.print(previews)
46
+ else
47
+ puts previews
34
48
  end
35
- screenshot_taker.capture(scenario_run.preview_url)
36
- exit
37
49
  end
38
50
 
39
- desc 'Run visual regression tests for Lookbook previews'
40
- task run: :environment do
41
- image_comparator = LookbookVisualTester::ImageComparator.new
42
- report = LookbookVisualTester::ReportGenerator.new
51
+ desc 'Generate screenshots for a specific preview (and run comparison)'
52
+ task :screenshot, %i[preview_name format] => :environment do |_, args|
53
+ preview_name = args[:preview_name]
54
+ format = args[:format]
55
+
56
+ unless preview_name
57
+ if format == 'json'
58
+ LookbookVisualTester::JsonOutputHandler.print({ error: 'Please provide a preview name' })
59
+ else
60
+ puts 'Please provide a preview name: rake lookbook:screenshot[Button]'
61
+ end
62
+ exit 1
63
+ end
64
+
65
+ # Run the test (which generates screenshots)
66
+ runner = LookbookVisualTester::Runner.new(pattern: preview_name)
43
67
 
44
- previews = Lookbook.previews
68
+ # Silence stdout if json format, to avoid pollution
69
+ original_stdout = $stdout
70
+ $stdout = File.new('/dev/null', 'w') if format == 'json'
45
71
 
46
- if previews.empty?
47
- puts 'No Lookbook previews found.'
48
- exit
72
+ begin
73
+ results = runner.run
74
+ ensure
75
+ $stdout = original_stdout if format == 'json'
49
76
  end
50
77
 
51
- pool = Concurrent::FixedThreadPool.new(LookbookVisualTester.config.threads)
52
- previews.each do |preview|
53
- preview_name = preview.name.underscore
54
- puts "Processing Preview: #{preview_name}"
78
+ if format == 'json'
79
+ # Transform results to simple hash
80
+ json_results = results.map do |r|
81
+ {
82
+ scenario_name: r.scenario_name,
83
+ status: r.status,
84
+ mismatch: r.mismatch,
85
+ diff_path: r.diff_path,
86
+ baseline_path: r.baseline_path,
87
+ current_path: r.current_path,
88
+ error: r.error
89
+ }
90
+ end
91
+
92
+ # If single result, return just object, else array
93
+ output = json_results.size == 1 ? json_results.first : json_results
94
+ LookbookVisualTester::JsonOutputHandler.print(output)
95
+ else
96
+ print_cli_summary(results)
97
+ end
98
+ end
99
+
100
+ desc 'Run visual regression tests for all previews'
101
+ task :test, [:format] => :environment do |_, args|
102
+ runner = LookbookVisualTester::Runner.new
55
103
 
56
- preview.scenarios.each do |scenario|
57
- Concurrent::Promises.future_on(pool) do
58
- scenario_run = LookbookVisualTester::ScenarioRun.new(scenario)
59
- screenshot_taker = LookbookVisualTester::ScreenshotTaker.new(scenario_run:)
60
- screenshot_taker.capture(scenario_run.preview_url, scenario_run.current_path)
61
- puts " Visiting URL: #{preview_url}"
104
+ # Check for ENV var or arg
105
+ json_mode = args[:format] == 'json' || ENV['JSON_OUTPUT'] == 'true'
62
106
 
63
- image_comparator.compare(scenario_run)
107
+ original_stdout = $stdout
108
+ $stdout = File.new('/dev/null', 'w') if json_mode
109
+
110
+ begin
111
+ results = runner.run
112
+ ensure
113
+ $stdout = original_stdout if json_mode
114
+ end
115
+
116
+ # Save results for retry logic
117
+ if defined?(LookbookVisualTester.config.diff_dir)
118
+ result_file = LookbookVisualTester.config.diff_dir.join('last_run.json')
119
+ FileUtils.mkdir_p(File.dirname(result_file))
120
+
121
+ simplified_results = results.map do |r|
122
+ {
123
+ scenario_name: r.scenario_name,
124
+ status: r.status,
125
+ mismatch: r.mismatch,
126
+ diff_path: r.diff_path.to_s,
127
+ current_path: r.current_path.to_s,
128
+ baseline_path: r.baseline_path.to_s
129
+ }
130
+ end
131
+ File.write(result_file, JSON.dump(simplified_results))
132
+ end
133
+
134
+ if json_mode
135
+ summary = {
136
+ total: results.size,
137
+ passed: results.count { |r| r.status == :passed },
138
+ failed: results.count { |r| r.status == :failed },
139
+ new: results.count { |r| r.status == :new },
140
+ errors: results.count { |r| r.status == :error },
141
+ results: results.map do |r|
142
+ {
143
+ name: r.scenario_name,
144
+ status: r.status,
145
+ mismatch: r.mismatch,
146
+ diff_path: r.diff_path
147
+ }
64
148
  end
149
+ }
150
+ LookbookVisualTester::JsonOutputHandler.print(summary)
151
+ else
152
+ print_cli_summary(results)
153
+
154
+ # Generate detailed HTML report
155
+ reporter = LookbookVisualTester::ReportGenerator.new(results)
156
+ reporter.call
157
+
158
+ exit 1 if results.any? { |r| r.status == :failed }
159
+ end
160
+ end
161
+
162
+ desc 'Approve a specific preview change (update baseline)'
163
+ task :approve, [:preview_name] => :environment do |_, args|
164
+ preview_name = args[:preview_name]
165
+ unless preview_name
166
+ puts 'Please provide a preview name: rake lookbook:approve[Button/default]'
167
+ exit 1
168
+ end
169
+
170
+ baseline_dir = LookbookVisualTester.config.baseline_dir
171
+ current_dir = LookbookVisualTester.config.current_dir
172
+
173
+ # Find matching files in current_dir
174
+ candidates = Dir.glob(current_dir.join('*')).select do |f|
175
+ File.basename(f).include?(preview_name.gsub('/', '_'))
176
+ end
177
+
178
+ if candidates.empty?
179
+ puts "No current runs found matching '#{preview_name}'."
180
+ exit 1
181
+ end
182
+
183
+ candidates.each do |current_file|
184
+ filename = File.basename(current_file)
185
+ next if filename.include?('_diff.png') # Don't copy diffs
186
+
187
+ baseline_file = baseline_dir.join(filename)
188
+ FileUtils.mkdir_p(baseline_dir)
189
+ FileUtils.cp(current_file, baseline_file)
190
+ puts "Approved: #{filename}"
191
+ end
192
+ end
193
+
194
+ desc 'Re-run only failing tests from the last run'
195
+ task :retry, [:format] => :environment do |_, args|
196
+ result_file = LookbookVisualTester.config.diff_dir.join('last_run.json')
197
+ unless File.exist?(result_file)
198
+ puts "No previous run data found at #{result_file}. Run 'rake lookbook:test' first."
199
+ exit 1
200
+ end
201
+
202
+ last_run = JSON.parse(File.read(result_file), symbolize_names: true)
203
+ failures = last_run.select { |r| r[:status] == 'failed' || r[:status] == 'error' }
204
+
205
+ if failures.empty?
206
+ puts 'No failures found in last run.'
207
+ exit 0
208
+ end
209
+
210
+ puts "Retrying #{failures.size} failure(s)..."
211
+
212
+ failures.each do |failure|
213
+ puts "Retrying #{failure[:scenario_name]}..."
214
+ runner = LookbookVisualTester::Runner.new(pattern: failure[:scenario_name])
215
+ runner.run
216
+ end
217
+ end
218
+
219
+ def print_cli_summary(results)
220
+ total = results.size
221
+ passed = results.count { |r| r.status == :passed }
222
+ failed = results.count { |r| r.status == :failed }
223
+ new_scenarios = results.count { |r| r.status == :new }
224
+ errors = results.count { |r| r.status == :error }
225
+
226
+ puts "\n"
227
+ puts '========================================'
228
+ puts ' Test Summary '
229
+ puts '========================================'
230
+ puts "Total Scenarios: #{total}"
231
+ puts "Passed: #{passed}"
232
+ puts "Failed: #{failed}"
233
+ puts "New (Baselines): #{new_scenarios}" if new_scenarios > 0
234
+ puts "Errors: #{errors}" if errors > 0
235
+ puts '========================================'
236
+
237
+ if failed > 0
238
+ puts "\nFailed Scenarios:"
239
+ results.select { |r| r.status == :failed }.each do |r|
240
+ puts " [FAIL] #{r.scenario_name}"
241
+ puts " Mismatch: #{r.mismatch.round(2)}%"
242
+ puts " Diff: #{r.diff_path}"
65
243
  end
66
244
  end
67
245
 
68
- pool.shutdown
69
- pool.wait_for_termination
70
- report_path = report.generate
71
- puts "Visual regression report generated at #{report_path}"
246
+ if errors > 0
247
+ puts "\nErrors:"
248
+ results.select { |r| r.status == :error }.each do |r|
249
+ puts " [ERROR] #{r.scenario_name}"
250
+ puts " Message: #{r.error}"
251
+ end
252
+ end
253
+ puts "\n"
72
254
  end
255
+ end
73
256
 
74
- desc 'Update baseline screenshots with current_run screenshots'
75
- task update_baseline: :environment do
76
- base_path = Rails.root.join('spec/visual_screenshots')
77
- baseline_dir = base_path.join('baseline')
78
- current_dir = base_path.join('current_run')
257
+ # Compatibility aliases and extra tools
258
+ namespace :lookbook_visual_tester do
259
+ desc 'Run visual regression tests (alias for lookbook:test)'
260
+ task run: 'lookbook:test'
261
+
262
+ desc 'Update baseline screenshots from current_run'
263
+ task update_baseline: 'lookbook_visual_tester:environment' do
264
+ baseline_dir = LookbookVisualTester.config.baseline_dir
265
+ current_dir = LookbookVisualTester.config.current_dir
79
266
 
80
267
  unless current_dir.exist?
81
- puts 'Current run directory does not exist. Run the visual regression tests first.'
82
- exit
268
+ puts 'Current run directory does not exist.'
269
+ exit 1
83
270
  end
84
271
 
85
272
  Dir.glob(current_dir.join('*.png')).each do |current_file|
86
273
  filename = File.basename(current_file)
87
274
  baseline_file = baseline_dir.join(filename)
275
+ FileUtils.mkdir_p(baseline_dir)
88
276
  FileUtils.cp(current_file, baseline_file)
89
- puts "Baseline updated for #{filename}"
277
+ puts "Updated baseline: #{filename}"
90
278
  end
91
279
  end
280
+
281
+ desc 'Profile the visual tester'
282
+ task profile: :environment do
283
+ require 'ruby-prof'
284
+ RubyProf.start
285
+ Rake::Task['lookbook:test'].invoke
286
+ result = RubyProf.stop
287
+ printer = RubyProf::FlatPrinter.new(result)
288
+ printer.print($stdout)
289
+ end
290
+
291
+ desc 'Get list of images for the given component'
292
+ task :images, %i[name skip_capture] => :environment do |_, args|
293
+ require 'lookbook_visual_tester/scenario_finder'
294
+ scenario_run = LookbookVisualTester::ScenarioFinder.call(args[:name])
295
+
296
+ unless scenario_run
297
+ puts "No Lookbook previews found matching #{args[:name]}"
298
+ exit 1
299
+ end
300
+
301
+ if args[:skip_capture].to_s == 'true'
302
+ # Just print existing
303
+ else
304
+ # Run it first but silenty
305
+ runner = LookbookVisualTester::Runner.new(pattern: args[:name])
306
+ $stdout = File.new('/dev/null', 'w')
307
+ runner.run
308
+ $stdout = STDOUT
309
+ end
310
+
311
+ puts scenario_run.current_path
312
+ end
313
+
314
+ desc 'Run and copy to clipboard first scenario matching the given name'
315
+ task :copy, [:name] => :environment do |_, args|
316
+ require 'lookbook_visual_tester/scenario_finder'
317
+ scenario_run = LookbookVisualTester::ScenarioFinder.call(args[:name])
318
+ unless scenario_run
319
+ puts "No Lookbook previews found matching #{args[:name]}"
320
+ exit 1
321
+ end
322
+
323
+ # Use our runner to capture it (which handles clipboard if enabled)
324
+ runner = LookbookVisualTester::Runner.new(pattern: args[:name])
325
+ runner.run
326
+ end
92
327
  end
metadata CHANGED
@@ -1,30 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lookbook_visual_tester
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Murilo Vasconcelos
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: capybara
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '0'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: '0'
26
- - !ruby/object:Gem::Dependency
27
- name: cuprite
13
+ name: chunky_png
28
14
  requirement: !ruby/object:Gem::Requirement
29
15
  requirements:
30
16
  - - ">="
@@ -52,7 +38,7 @@ dependencies:
52
38
  - !ruby/object:Gem::Version
53
39
  version: '0'
54
40
  - !ruby/object:Gem::Dependency
55
- name: lookbook
41
+ name: ferrum
56
42
  requirement: !ruby/object:Gem::Requirement
57
43
  requirements:
58
44
  - - ">="
@@ -66,7 +52,7 @@ dependencies:
66
52
  - !ruby/object:Gem::Version
67
53
  version: '0'
68
54
  - !ruby/object:Gem::Dependency
69
- name: mini_magick
55
+ name: lookbook
70
56
  requirement: !ruby/object:Gem::Requirement
71
57
  requirements:
72
58
  - - ">="
@@ -94,7 +80,7 @@ dependencies:
94
80
  - !ruby/object:Gem::Version
95
81
  version: '0'
96
82
  - !ruby/object:Gem::Dependency
97
- name: ruby-prof
83
+ name: rainbow
98
84
  requirement: !ruby/object:Gem::Requirement
99
85
  requirements:
100
86
  - - ">="
@@ -149,6 +135,20 @@ dependencies:
149
135
  - - "~>"
150
136
  - !ruby/object:Gem::Version
151
137
  version: '3.10'
138
+ - !ruby/object:Gem::Dependency
139
+ name: ruby-prof
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
152
  description: A Rails gem that captures screenshots of Lookbook component previews,
153
153
  compares them against baseline images, and highlights visual differences to assist
154
154
  in safe refactoring.
@@ -163,26 +163,36 @@ files:
163
163
  - CHANGELOG.md
164
164
  - CODE_OF_CONDUCT.md
165
165
  - README.md
166
+ - RELEASING.md
166
167
  - Rakefile
167
168
  - assets/logo.jpg
168
169
  - lib/lookbook_visual_tester.rb
169
170
  - lib/lookbook_visual_tester/baseline_manager.rb
170
171
  - lib/lookbook_visual_tester/capybara_setup.rb
172
+ - lib/lookbook_visual_tester/check_reporter.rb
171
173
  - lib/lookbook_visual_tester/configuration.rb
174
+ - lib/lookbook_visual_tester/driver.rb
175
+ - lib/lookbook_visual_tester/drivers/ferrum_driver.rb
172
176
  - lib/lookbook_visual_tester/image_comparator.rb
177
+ - lib/lookbook_visual_tester/json_output_handler.rb
178
+ - lib/lookbook_visual_tester/preview_checker.rb
173
179
  - lib/lookbook_visual_tester/railtie.rb
174
180
  - lib/lookbook_visual_tester/report_generator.rb
181
+ - lib/lookbook_visual_tester/runner.rb
175
182
  - lib/lookbook_visual_tester/scenario_finder.rb
176
183
  - lib/lookbook_visual_tester/scenario_run.rb
177
184
  - lib/lookbook_visual_tester/screenshot_taker.rb
178
185
  - lib/lookbook_visual_tester/service.rb
186
+ - lib/lookbook_visual_tester/services/image_comparator.rb
179
187
  - lib/lookbook_visual_tester/session_manager.rb
180
188
  - lib/lookbook_visual_tester/store.rb
189
+ - lib/lookbook_visual_tester/templates/preview_check_report.html.tt
190
+ - lib/lookbook_visual_tester/templates/report.html.erb
181
191
  - lib/lookbook_visual_tester/update_previews.rb
192
+ - lib/lookbook_visual_tester/variant_resolver.rb
182
193
  - lib/lookbook_visual_tester/version.rb
183
194
  - lib/tasks/lookbook_visual_tester.rake
184
195
  - sig/lookbook_visual_tester.rbs
185
- - tasks/lookbook_visual_tester.rake
186
196
  homepage: https://github.com/muriloime/lookbook_visual_tester
187
197
  licenses:
188
198
  - MIT
@@ -203,7 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
203
213
  - !ruby/object:Gem::Version
204
214
  version: '0'
205
215
  requirements: []
206
- rubygems_version: 3.6.2
216
+ rubygems_version: 3.6.9
207
217
  specification_version: 4
208
218
  summary: Visual regression testing for Lookbook previews in Rails applications.
209
219
  test_files: []
File without changes