lookbook_visual_tester 0.5.7 → 0.5.8
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/CHANGELOG.md +7 -0
- data/README.md +17 -1
- data/lib/lookbook_visual_tester/configuration.rb +3 -1
- data/lib/lookbook_visual_tester/drivers/ferrum_driver.rb +4 -0
- data/lib/lookbook_visual_tester/runner.rb +6 -0
- data/lib/lookbook_visual_tester/services/image_comparator.rb +7 -1
- data/lib/lookbook_visual_tester/version.rb +1 -1
- data/lib/tasks/lookbook_visual_tester.rake +133 -0
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a9ad38fc0f69bfa66579bfb8b1228a6a9df8dc9899442174162811586336814
|
|
4
|
+
data.tar.gz: c258c211b4181e49b4ec8ea13ce5fc8b00a7095c8f5bad664ad8082d6c7e4c34
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7790e3658daf75df2fe6c18892c4f20ef4738568b1e3731391be847b6857ffd2602a6ade0e7e0850adfaec1c5af8d09b08eb3fa671beda67b9a4af485b2d520c
|
|
7
|
+
data.tar.gz: 5fd17716ff8b4a1bd0d424201a96a75c0dd8c4f2ccdf69207a96c231c73d0ad7115bf047644afde6684590f40e3361d2fa684916ed526984725aaed4be114cbf
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
## [0.5.8] - 2026-01-08
|
|
3
|
+
|
|
4
|
+
### ✨ New Features & Improvements
|
|
5
|
+
- **AI-Native Inspection Tools**: Added `lookbook:inspect` and `lookbook:context` Rake tasks to assist AI agents in understanding preview structure and code context.
|
|
6
|
+
- **Debug Artifacts**: Automatically save HTML DOM snapshots alongside failed visual regression tests for easier debugging.
|
|
7
|
+
- **Visual Tolerance**: Added `tolerance` configuration option and fuzzy matching (Euclidean distance) to `ImageComparator` to reduce flakiness.
|
|
8
|
+
|
|
2
9
|
## [0.5.7] - 2026-01-06
|
|
3
10
|
|
|
4
11
|
### Fixed
|
data/README.md
CHANGED
|
@@ -51,9 +51,26 @@ LookbookVisualTester.configure do |config|
|
|
|
51
51
|
config.copy_to_clipboard = true # Enable xclip support
|
|
52
52
|
config.threads = 4 # Number of parallel threads (default: 4)
|
|
53
53
|
config.wait_time = 0.5 # Optional: Wait time (seconds) before screenshot (fixes blank screens)
|
|
54
|
+
config.tolerance = 0.05 # Optional: Mismatch tolerance (0.0 to 1.0) to ignore minor rendering differences
|
|
54
55
|
end
|
|
55
56
|
```
|
|
56
57
|
|
|
58
|
+
## AI Integration
|
|
59
|
+
|
|
60
|
+
The gem includes tools designed to help AI agents understand and debug your components.
|
|
61
|
+
|
|
62
|
+
### Inspection
|
|
63
|
+
Get JSON metadata about a preview:
|
|
64
|
+
```bash
|
|
65
|
+
bundle exec rake lookbook:inspect[example/default]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Context Dump
|
|
69
|
+
Get full source code of the component, preview, and template:
|
|
70
|
+
```bash
|
|
71
|
+
bundle exec rake lookbook:context[example/default]
|
|
72
|
+
```
|
|
73
|
+
|
|
57
74
|
## Usage
|
|
58
75
|
|
|
59
76
|
### Running Visual Tests
|
|
@@ -170,7 +187,6 @@ bundle exec rake lookbook:missing
|
|
|
170
187
|
## Next Steps
|
|
171
188
|
|
|
172
189
|
- **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.
|
|
173
|
-
- **Tolerance Configuration**: Add support for configuring pixel mismatch tolerance levels to reduce flakiness in rendering (anti-aliasing, shadow rendering differences).
|
|
174
190
|
- **Ignore Regions**: Allow defining specific areas of a component to exclude from visual comparison (useful for unavoidable dynamic content).
|
|
175
191
|
- **Markdown Reports**: Generate markdown summaries of test runs suitable for posting automatically as Pull Request comments.
|
|
176
192
|
- **Cloud Storage**: Integration with cloud storage (S3, GCS) for managing baseline images to keep the git repository light.
|
|
@@ -4,7 +4,7 @@ module LookbookVisualTester
|
|
|
4
4
|
attr_accessor :lookbook_host, :ui_comparison, :diff_dir, :baseline_dir, :current_dir,
|
|
5
5
|
:history_dir, :history_keep_last_n, :threads, :copy_to_clipboard,
|
|
6
6
|
:components_folder, :automatic_run, :mask_selectors, :driver_adapter,
|
|
7
|
-
:preview_checker_setup, :logger, :wait_time
|
|
7
|
+
:preview_checker_setup, :logger, :wait_time, :tolerance
|
|
8
8
|
|
|
9
9
|
DEFAULT_THREADS = 4
|
|
10
10
|
|
|
@@ -27,7 +27,9 @@ module LookbookVisualTester
|
|
|
27
27
|
@mask_selectors = []
|
|
28
28
|
@driver_adapter = :ferrum
|
|
29
29
|
@preview_checker_setup = nil
|
|
30
|
+
@preview_checker_setup = nil
|
|
30
31
|
@wait_time = 0.5
|
|
32
|
+
@tolerance = 0.0
|
|
31
33
|
@logger = if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
32
34
|
Rails.logger
|
|
33
35
|
else
|
|
@@ -182,6 +182,12 @@ module LookbookVisualTester
|
|
|
182
182
|
elsif result[:mismatch] > 0
|
|
183
183
|
mismatch = result[:mismatch]
|
|
184
184
|
puts " [FAIL] Mismatch: #{mismatch.round(2)}%. Diff saved to #{diff_path}"
|
|
185
|
+
|
|
186
|
+
# Save DOM snapshot for debugging
|
|
187
|
+
dom_path = diff_path.sub('.png', '.html')
|
|
188
|
+
File.write(dom_path, driver.page_source)
|
|
189
|
+
puts " DOM Snapshot saved to #{dom_path}"
|
|
190
|
+
|
|
185
191
|
status = :failed
|
|
186
192
|
|
|
187
193
|
# Clipboard (Feature parity with legacy ScreenshotTaker)
|
|
@@ -37,7 +37,13 @@ module LookbookVisualTester
|
|
|
37
37
|
pixel1 = baseline[x, y]
|
|
38
38
|
pixel2 = current[x, y]
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
score = 0.0
|
|
41
|
+
if pixel1 != pixel2
|
|
42
|
+
score = ChunkyPNG::Color.euclidean_distance_rgba(pixel1, pixel2) / 510.0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if score <= LookbookVisualTester.config.tolerance
|
|
46
|
+
# Match (including exact match where score is 0.0)
|
|
41
47
|
# Blue context for unchanged pixels to make it easier for humans
|
|
42
48
|
gray_val = ChunkyPNG::Color.r(ChunkyPNG::Color.grayscale_teint(pixel1))
|
|
43
49
|
# Keep intensity in R/G but push blue to make it the dominant tint
|
|
@@ -253,6 +253,139 @@ namespace :lookbook do
|
|
|
253
253
|
end
|
|
254
254
|
puts "\n"
|
|
255
255
|
end
|
|
256
|
+
|
|
257
|
+
desc 'Get detailed inspection data for a preview (JSON)'
|
|
258
|
+
task :inspect, [:preview_name] => :environment do |_, args|
|
|
259
|
+
require 'lookbook'
|
|
260
|
+
require 'lookbook_visual_tester/scenario_finder'
|
|
261
|
+
require 'lookbook_visual_tester/json_output_handler'
|
|
262
|
+
|
|
263
|
+
preview_name = args[:preview_name]
|
|
264
|
+
unless preview_name
|
|
265
|
+
LookbookVisualTester::JsonOutputHandler.print({ error: 'Please provide a preview name' })
|
|
266
|
+
exit 1
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
scenario_run = LookbookVisualTester::ScenarioFinder.call(preview_name)
|
|
270
|
+
|
|
271
|
+
unless scenario_run
|
|
272
|
+
LookbookVisualTester::JsonOutputHandler.print({ error: "No preview found for #{preview_name}" })
|
|
273
|
+
exit 1
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
scenario = scenario_run.scenario
|
|
277
|
+
preview = scenario.preview
|
|
278
|
+
file_path = preview.file_path
|
|
279
|
+
|
|
280
|
+
# Try to find line number
|
|
281
|
+
line_number = nil
|
|
282
|
+
if File.exist?(file_path)
|
|
283
|
+
File.foreach(file_path).with_index do |line, index|
|
|
284
|
+
if line.match?(/^\s*def\s+#{scenario.name}\b/)
|
|
285
|
+
line_number = index + 1
|
|
286
|
+
break
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Inspect component details if available
|
|
292
|
+
component_class_name = nil
|
|
293
|
+
component_file_path = nil
|
|
294
|
+
template_file_path = nil
|
|
295
|
+
|
|
296
|
+
if scenario.respond_to?(:component)
|
|
297
|
+
comp = scenario.component
|
|
298
|
+
if comp.respond_to?(:component_class) && comp.component_class
|
|
299
|
+
component_class_name = comp.component_class.name
|
|
300
|
+
# Best effort to find component source
|
|
301
|
+
if comp.component_class.instance_methods(false).include?(:initialize)
|
|
302
|
+
component_file_path = comp.component_class.instance_method(:initialize).source_location&.first
|
|
303
|
+
end
|
|
304
|
+
# Fallback to const_source_location if supported (Ruby 2.7+)
|
|
305
|
+
if component_file_path.nil? && Object.respond_to?(:const_source_location)
|
|
306
|
+
component_file_path = Object.const_source_location(component_class_name)&.first
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
template_file_path = comp.template_file_path if comp.respond_to?(:template_file_path)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
output = {
|
|
314
|
+
name: scenario.lookup_path,
|
|
315
|
+
preview_class: preview.name,
|
|
316
|
+
file_path: file_path,
|
|
317
|
+
line_number: line_number,
|
|
318
|
+
url_path: scenario.url_path,
|
|
319
|
+
component_class: component_class_name,
|
|
320
|
+
component_path: component_file_path,
|
|
321
|
+
template_path: template_file_path
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
LookbookVisualTester::JsonOutputHandler.print(output)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
desc 'Dump content of preview, component, and template for context'
|
|
328
|
+
task :context, [:preview_name] => :environment do |_, args|
|
|
329
|
+
require 'lookbook_visual_tester/json_output_handler'
|
|
330
|
+
|
|
331
|
+
require 'lookbook'
|
|
332
|
+
require 'lookbook_visual_tester/scenario_finder'
|
|
333
|
+
|
|
334
|
+
preview_name = args[:preview_name]
|
|
335
|
+
unless preview_name
|
|
336
|
+
puts 'Error: Please provide a preview name'
|
|
337
|
+
exit 1
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
scenario_run = LookbookVisualTester::ScenarioFinder.call(preview_name)
|
|
341
|
+
|
|
342
|
+
unless scenario_run
|
|
343
|
+
puts "Error: No preview found for #{preview_name}"
|
|
344
|
+
exit 1
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
scenario = scenario_run.scenario
|
|
348
|
+
preview = scenario.preview
|
|
349
|
+
|
|
350
|
+
files_to_dump = []
|
|
351
|
+
|
|
352
|
+
# 1. Preview File
|
|
353
|
+
files_to_dump << { path: preview.file_path, label: "Preview: #{preview.name}" }
|
|
354
|
+
|
|
355
|
+
# 2. Template File (if available)
|
|
356
|
+
if scenario.respond_to?(:component)
|
|
357
|
+
comp = scenario.component
|
|
358
|
+
if comp.respond_to?(:template_file_path) && comp.template_file_path
|
|
359
|
+
files_to_dump << { path: comp.template_file_path, label: 'Template' }
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# 3. Component File (best effort)
|
|
363
|
+
if comp.respond_to?(:component_class) && comp.component_class
|
|
364
|
+
component_file_path = nil
|
|
365
|
+
if comp.component_class.instance_methods(false).include?(:initialize)
|
|
366
|
+
component_file_path = comp.component_class.instance_method(:initialize).source_location&.first
|
|
367
|
+
end
|
|
368
|
+
if component_file_path.nil? && Object.respond_to?(:const_source_location)
|
|
369
|
+
component_file_path = Object.const_source_location(comp.component_class.name)&.first
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
if component_file_path
|
|
373
|
+
files_to_dump << { path: component_file_path, label: "Component: #{comp.component_class.name}" }
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
files_to_dump.each do |file|
|
|
379
|
+
next unless file[:path] && File.exist?(file[:path])
|
|
380
|
+
|
|
381
|
+
puts "\n"
|
|
382
|
+
puts '========================================'
|
|
383
|
+
puts "#{file[:label]} (#{file[:path]})"
|
|
384
|
+
puts '========================================'
|
|
385
|
+
puts File.read(file[:path])
|
|
386
|
+
puts '========================================'
|
|
387
|
+
end
|
|
388
|
+
end
|
|
256
389
|
end
|
|
257
390
|
|
|
258
391
|
# Compatibility aliases and extra tools
|
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.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Murilo Vasconcelos
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: cuprite
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
54
68
|
- !ruby/object:Gem::Dependency
|
|
55
69
|
name: ferrum
|
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|