lookbook_visual_tester 0.5.10 → 0.7.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/.env.example +13 -0
- data/CHANGELOG.md +31 -0
- data/README.md +18 -9
- data/Rakefile +53 -0
- data/docs/superpowers/plans/2026-08-09-lookbook-visual-tester-hardening.md +1792 -0
- data/lib/lookbook_visual_tester/browser_discovery.rb +31 -0
- data/lib/lookbook_visual_tester/configuration.rb +11 -4
- data/lib/lookbook_visual_tester/drivers/ferrum_driver.rb +8 -4
- data/lib/lookbook_visual_tester/preview_checker.rb +43 -213
- data/lib/lookbook_visual_tester/railtie.rb +2 -8
- data/lib/lookbook_visual_tester/runner.rb +108 -92
- data/lib/lookbook_visual_tester/server_test_runner.rb +102 -0
- data/lib/lookbook_visual_tester/services/image_trimmer.rb +59 -0
- data/lib/lookbook_visual_tester/update_previews.rb +3 -12
- data/lib/lookbook_visual_tester/version.rb +1 -1
- data/lib/lookbook_visual_tester.rb +4 -0
- data/lib/tasks/lookbook_visual_tester.rake +50 -51
- metadata +38 -51
- data/lib/lookbook_visual_tester/baseline_manager.rb +0 -34
- data/lib/lookbook_visual_tester/capybara_setup.rb +0 -29
- data/lib/lookbook_visual_tester/screenshot_taker.rb +0 -118
- data/lib/lookbook_visual_tester/session_manager.rb +0 -17
|
@@ -3,6 +3,7 @@ require 'json'
|
|
|
3
3
|
require_relative 'configuration'
|
|
4
4
|
require_relative 'scenario_run'
|
|
5
5
|
require_relative 'services/image_comparator'
|
|
6
|
+
require_relative 'services/image_trimmer'
|
|
6
7
|
require_relative 'drivers/ferrum_driver'
|
|
7
8
|
require_relative 'variant_resolver'
|
|
8
9
|
|
|
@@ -11,10 +12,14 @@ module LookbookVisualTester
|
|
|
11
12
|
Result = Struct.new(:scenario_name, :status, :mismatch, :diff_path, :error, :baseline_path,
|
|
12
13
|
:current_path, keyword_init: true)
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
DEFAULT_DRIVER_WIDTH = 1280
|
|
16
|
+
DEFAULT_DRIVER_HEIGHT = 800
|
|
17
|
+
|
|
18
|
+
def initialize(config = LookbookVisualTester.config, pattern: nil, force_update: false, output: $stdout)
|
|
15
19
|
@config = config
|
|
16
20
|
@pattern = pattern
|
|
17
21
|
@force_update = force_update
|
|
22
|
+
@output = output
|
|
18
23
|
@driver_pool = Queue.new
|
|
19
24
|
init_driver_pool
|
|
20
25
|
@results = []
|
|
@@ -31,8 +36,8 @@ module LookbookVisualTester
|
|
|
31
36
|
end
|
|
32
37
|
end
|
|
33
38
|
|
|
34
|
-
puts "Found #{previews.count} previews matching '#{@pattern}'."
|
|
35
|
-
puts "Running against #{@variants.size} variant(s)."
|
|
39
|
+
@output.puts "Found #{previews.count} previews matching '#{@pattern}'."
|
|
40
|
+
@output.puts "Running against #{@variants.size} variant(s)."
|
|
36
41
|
|
|
37
42
|
@variants.each do |variant_input|
|
|
38
43
|
resolver = VariantResolver.new(variant_input)
|
|
@@ -40,7 +45,7 @@ module LookbookVisualTester
|
|
|
40
45
|
variant_slug = resolver.slug
|
|
41
46
|
width = resolver.width_in_pixels
|
|
42
47
|
|
|
43
|
-
puts " Variant: #{variant_slug.presence || 'Default'}"
|
|
48
|
+
@output.puts " Variant: #{variant_slug.presence || 'Default'}"
|
|
44
49
|
|
|
45
50
|
if @config.threads > 1
|
|
46
51
|
run_concurrently(previews, variant_slug, variant_options, width)
|
|
@@ -63,15 +68,18 @@ module LookbookVisualTester
|
|
|
63
68
|
begin
|
|
64
69
|
JSON.parse(variants_json)
|
|
65
70
|
rescue JSON::ParserError
|
|
66
|
-
puts 'Invalid JSON in VARIANTS env var. Defaulting to standard run.'
|
|
71
|
+
@output.puts 'Invalid JSON in VARIANTS env var. Defaulting to standard run.'
|
|
67
72
|
[{}]
|
|
68
73
|
end
|
|
69
74
|
end
|
|
70
75
|
|
|
76
|
+
def scenarios_for(preview)
|
|
77
|
+
preview.scenarios
|
|
78
|
+
end
|
|
79
|
+
|
|
71
80
|
def run_sequentially(previews, variant_slug, variant_options, width)
|
|
72
81
|
previews.each do |preview|
|
|
73
|
-
|
|
74
|
-
group.each do |scenario|
|
|
82
|
+
scenarios_for(preview).each do |scenario|
|
|
75
83
|
driver = checkout_driver
|
|
76
84
|
begin
|
|
77
85
|
@results << run_scenario(scenario, driver, variant_slug, variant_options, width)
|
|
@@ -88,8 +96,7 @@ module LookbookVisualTester
|
|
|
88
96
|
promises = []
|
|
89
97
|
|
|
90
98
|
previews.each do |preview|
|
|
91
|
-
|
|
92
|
-
group.each do |scenario|
|
|
99
|
+
scenarios_for(preview).each do |scenario|
|
|
93
100
|
promises << Concurrent::Promises.future_on(pool) do
|
|
94
101
|
driver = checkout_driver
|
|
95
102
|
begin
|
|
@@ -101,8 +108,6 @@ module LookbookVisualTester
|
|
|
101
108
|
end
|
|
102
109
|
end
|
|
103
110
|
|
|
104
|
-
# Zip results from this concurrent batch into results
|
|
105
|
-
# Note: This aggregates results per variant loop.
|
|
106
111
|
@results.concat(Concurrent::Promises.zip(*promises).value)
|
|
107
112
|
pool.shutdown
|
|
108
113
|
pool.wait_for_termination
|
|
@@ -110,9 +115,7 @@ module LookbookVisualTester
|
|
|
110
115
|
|
|
111
116
|
def init_driver_pool
|
|
112
117
|
count = @config.threads > 1 ? @config.threads : 1
|
|
113
|
-
count.times
|
|
114
|
-
@driver_pool << LookbookVisualTester::Drivers::FerrumDriver.new(@config)
|
|
115
|
-
end
|
|
118
|
+
count.times { @driver_pool << Drivers::FerrumDriver.new(@config) }
|
|
116
119
|
end
|
|
117
120
|
|
|
118
121
|
def checkout_driver
|
|
@@ -133,98 +136,111 @@ module LookbookVisualTester
|
|
|
133
136
|
def run_scenario(scenario, driver, variant_slug, variant_options, width)
|
|
134
137
|
run_data = ScenarioRun.new(scenario, variant_slug: variant_slug,
|
|
135
138
|
display_params: variant_options)
|
|
136
|
-
puts "Running visual test for: #{run_data.name} #{variant_slug.present? ? "[#{variant_slug}]" : ''}"
|
|
139
|
+
@output.puts "Running visual test for: #{run_data.name} #{variant_slug.present? ? "[#{variant_slug}]" : ''}"
|
|
137
140
|
|
|
141
|
+
paths = prepare_paths(run_data, variant_slug)
|
|
142
|
+
Thread.current[:lookbook_visual_tester_driver] = driver
|
|
138
143
|
begin
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
capture(driver, run_data, paths, width)
|
|
145
|
+
compare_against_baseline(run_data, paths)
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
record_failure(run_data, paths, e)
|
|
148
|
+
ensure
|
|
149
|
+
Thread.current[:lookbook_visual_tester_driver] = nil
|
|
150
|
+
end
|
|
151
|
+
end
|
|
142
152
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
def prepare_paths(run_data, variant_slug)
|
|
154
|
+
folder_name = variant_slug.presence || 'default'
|
|
155
|
+
{
|
|
156
|
+
current: run_data.current_path,
|
|
157
|
+
baseline: run_data.baseline_path,
|
|
158
|
+
diff: @config.diff_dir.join(folder_name, run_data.diff_filename)
|
|
159
|
+
}
|
|
160
|
+
end
|
|
148
161
|
|
|
149
|
-
|
|
150
|
-
|
|
162
|
+
def capture(driver, run_data, paths, width)
|
|
163
|
+
driver.resize_window(width || DEFAULT_DRIVER_WIDTH, DEFAULT_DRIVER_HEIGHT)
|
|
164
|
+
driver.visit(run_data.preview_url)
|
|
151
165
|
|
|
152
|
-
|
|
166
|
+
FileUtils.mkdir_p(File.dirname(paths[:current]))
|
|
167
|
+
FileUtils.mkdir_p(File.dirname(paths[:diff]))
|
|
153
168
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
end
|
|
169
|
+
driver.save_screenshot(paths[:current].to_s)
|
|
170
|
+
ImageTrimmer.call(paths[:current].to_s) if File.exist?(paths[:current].to_s)
|
|
171
|
+
end
|
|
158
172
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
173
|
+
def compare_against_baseline(run_data, paths)
|
|
174
|
+
comparator = ImageComparator.new(paths[:baseline].to_s, paths[:current].to_s, paths[:diff].to_s)
|
|
175
|
+
result = comparator.call
|
|
176
|
+
@results << build_result(run_data, paths, result)
|
|
177
|
+
end
|
|
164
178
|
|
|
165
|
-
|
|
179
|
+
def build_result(run_data, paths, result)
|
|
180
|
+
if result[:error]
|
|
181
|
+
if result[:error] == 'Baseline not found' || @force_update
|
|
182
|
+
handle_missing_baseline(run_data, paths)
|
|
183
|
+
else
|
|
184
|
+
@output.puts " [ERROR] #{result[:error]}"
|
|
185
|
+
Result.new(scenario_name: run_data.name, status: :error, error: result[:error],
|
|
186
|
+
baseline_path: paths[:baseline].to_s, current_path: paths[:current].to_s)
|
|
187
|
+
end
|
|
188
|
+
elsif result[:mismatch] > 0
|
|
189
|
+
record_mismatch(run_data, paths, result)
|
|
190
|
+
else
|
|
191
|
+
@output.puts ' [PASS] Identical.'
|
|
192
|
+
Result.new(scenario_name: run_data.name, status: :passed, mismatch: 0.0,
|
|
193
|
+
diff_path: paths[:diff].to_s, baseline_path: paths[:baseline].to_s,
|
|
194
|
+
current_path: paths[:current].to_s)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
166
197
|
|
|
198
|
+
def handle_missing_baseline(run_data, paths)
|
|
199
|
+
if @force_update
|
|
200
|
+
@output.puts ' [UPDATE] Baseline forced update.'
|
|
167
201
|
status = :passed
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
puts ' [NEW] Baseline not found. Saved current as potential baseline.'
|
|
179
|
-
status = :new
|
|
180
|
-
end
|
|
181
|
-
FileUtils.mkdir_p(File.dirname(baseline_path))
|
|
182
|
-
FileUtils.cp(current_path, baseline_path)
|
|
183
|
-
else
|
|
184
|
-
puts " [ERROR] #{result[:error]}"
|
|
185
|
-
status = :error
|
|
186
|
-
error = result[:error]
|
|
187
|
-
end
|
|
188
|
-
elsif result[:mismatch] > 0
|
|
189
|
-
mismatch = result[:mismatch]
|
|
190
|
-
puts " [FAIL] Mismatch: #{mismatch.round(2)}%. Diff saved to #{diff_path}"
|
|
202
|
+
else
|
|
203
|
+
@output.puts ' [NEW] Baseline not found. Saved current as potential baseline.'
|
|
204
|
+
status = :new
|
|
205
|
+
end
|
|
206
|
+
FileUtils.mkdir_p(File.dirname(paths[:baseline]))
|
|
207
|
+
FileUtils.cp(paths[:current], paths[:baseline])
|
|
208
|
+
Result.new(scenario_name: run_data.name, status: status, mismatch: 0.0,
|
|
209
|
+
diff_path: nil, baseline_path: paths[:baseline].to_s,
|
|
210
|
+
current_path: paths[:current].to_s)
|
|
211
|
+
end
|
|
191
212
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
puts " DOM Snapshot saved to #{dom_path}"
|
|
213
|
+
def record_mismatch(run_data, paths, result)
|
|
214
|
+
mismatch = result[:mismatch]
|
|
215
|
+
@output.puts " [FAIL] Mismatch: #{mismatch.round(2)}%. Diff saved to #{paths[:diff]}"
|
|
196
216
|
|
|
197
|
-
|
|
217
|
+
dom_path = paths[:diff].sub('.png', '.html')
|
|
218
|
+
File.write(dom_path, driver.page_source)
|
|
219
|
+
@output.puts " DOM Snapshot saved to #{dom_path}"
|
|
198
220
|
|
|
199
|
-
|
|
200
|
-
if @config.copy_to_clipboard
|
|
201
|
-
system("xclip -selection clipboard -t image/png -i #{current_path}")
|
|
202
|
-
end
|
|
203
|
-
else
|
|
204
|
-
puts ' [PASS] Identical.'
|
|
205
|
-
status = :passed
|
|
206
|
-
end
|
|
221
|
+
copy_to_clipboard_if_enabled(paths[:current])
|
|
207
222
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
223
|
+
Result.new(scenario_name: run_data.name, status: :failed, mismatch: mismatch,
|
|
224
|
+
diff_path: paths[:diff].to_s, baseline_path: paths[:baseline].to_s,
|
|
225
|
+
current_path: paths[:current].to_s)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def copy_to_clipboard_if_enabled(current_path)
|
|
229
|
+
return unless @config.copy_to_clipboard
|
|
230
|
+
|
|
231
|
+
system("xclip -selection clipboard -t image/png -i #{current_path}")
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def record_failure(run_data, paths, error)
|
|
235
|
+
@output.puts " [ERROR] Exception: #{error.message}"
|
|
236
|
+
@output.puts error.backtrace.take(5)
|
|
237
|
+
Result.new(scenario_name: run_data.name, status: :error, error: error.message,
|
|
238
|
+
baseline_path: paths[:baseline].to_s, current_path: paths[:current].to_s)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Resolves the driver for the current scenario thread, set in run_scenario.
|
|
242
|
+
def driver
|
|
243
|
+
Thread.current[:lookbook_visual_tester_driver]
|
|
228
244
|
end
|
|
229
245
|
end
|
|
230
246
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# lib/lookbook_visual_tester/server_test_runner.rb
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'timeout'
|
|
5
|
+
require 'tempfile'
|
|
6
|
+
|
|
7
|
+
module LookbookVisualTester
|
|
8
|
+
class ServerTestRunner
|
|
9
|
+
DEFAULT_TEST_TASK = 'lookbook:test'
|
|
10
|
+
DEFAULT_TIMEOUT = 60
|
|
11
|
+
POLL_INTERVAL = 0.25
|
|
12
|
+
|
|
13
|
+
def self.call(config: LookbookVisualTester.config, timeout: DEFAULT_TIMEOUT, test_task: DEFAULT_TEST_TASK, log_path: nil)
|
|
14
|
+
new(config: config, timeout: timeout, test_task: test_task, log_path: log_path).call
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(config: LookbookVisualTester.config, timeout: DEFAULT_TIMEOUT, test_task: DEFAULT_TEST_TASK, log_path: nil)
|
|
18
|
+
@config = config
|
|
19
|
+
@timeout = timeout
|
|
20
|
+
@test_task = test_task
|
|
21
|
+
@log_path = log_path
|
|
22
|
+
@pid = nil
|
|
23
|
+
@log_file = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call
|
|
27
|
+
validate_host!
|
|
28
|
+
@pid = spawn_server
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
self.class.wait_for_server(@config.lookbook_host, timeout: @timeout)
|
|
32
|
+
puts "[LookbookVisualTester] Server ready at #{@config.lookbook_host}. Running #{@test_task}..."
|
|
33
|
+
|
|
34
|
+
task = Rake::Task[@test_task]
|
|
35
|
+
task.reenable
|
|
36
|
+
task.invoke
|
|
37
|
+
ensure
|
|
38
|
+
stop_server
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.wait_for_server(host, timeout: DEFAULT_TIMEOUT)
|
|
43
|
+
uri = URI.parse(host)
|
|
44
|
+
raise Error, "Invalid lookbook_host: #{host}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
45
|
+
|
|
46
|
+
Timeout.timeout(timeout) do
|
|
47
|
+
loop do
|
|
48
|
+
begin
|
|
49
|
+
response = Net::HTTP.get_response(uri)
|
|
50
|
+
return true if response.code.to_i < 500
|
|
51
|
+
rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, Net::OpenTimeout, Net::ReadTimeout
|
|
52
|
+
# server not ready yet
|
|
53
|
+
end
|
|
54
|
+
sleep POLL_INTERVAL
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
rescue Timeout::Error
|
|
58
|
+
raise Error, "Lookbook server at #{host} did not start within #{timeout}s"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def validate_host!
|
|
64
|
+
host = @config.lookbook_host
|
|
65
|
+
raise Error, 'lookbook_host must be configured' if host.nil? || host.empty?
|
|
66
|
+
|
|
67
|
+
uri = URI.parse(host)
|
|
68
|
+
raise Error, "Invalid lookbook_host: #{host}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
69
|
+
raise Error, "lookbook_host must include a port: #{host}" unless uri.port
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def spawn_server
|
|
73
|
+
port = URI.parse(@config.lookbook_host).port
|
|
74
|
+
env = {
|
|
75
|
+
'PORT' => port.to_s,
|
|
76
|
+
'RAILS_ENV' => ENV.fetch('RAILS_ENV', 'development')
|
|
77
|
+
}
|
|
78
|
+
command = ['bundle', 'exec', 'rails', 'server', '-p', port.to_s]
|
|
79
|
+
|
|
80
|
+
@log_file = @log_path ? File.open(@log_path, 'w') : Tempfile.create('lookbook_server')
|
|
81
|
+
Process.spawn(env, *command, out: @log_file, err: @log_file, pgroup: true)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def stop_server
|
|
85
|
+
return unless @pid
|
|
86
|
+
|
|
87
|
+
begin
|
|
88
|
+
Process.kill('-TERM', @pid) # negative pid signals the whole process group
|
|
89
|
+
rescue Errno::ESRCH
|
|
90
|
+
# already gone
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
begin
|
|
94
|
+
Process.waitpid(@pid)
|
|
95
|
+
rescue Errno::ECHILD
|
|
96
|
+
# already reaped
|
|
97
|
+
end
|
|
98
|
+
ensure
|
|
99
|
+
@log_file&.close
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# lib/lookbook_visual_tester/services/image_trimmer.rb
|
|
2
|
+
require 'chunky_png'
|
|
3
|
+
|
|
4
|
+
module LookbookVisualTester
|
|
5
|
+
module ImageTrimmer
|
|
6
|
+
DEFAULT_PADDING = 10
|
|
7
|
+
|
|
8
|
+
# Pixels matching any of these colors are considered "empty" border and trimmed.
|
|
9
|
+
BORDER_COLORS = [
|
|
10
|
+
ChunkyPNG::Color::WHITE,
|
|
11
|
+
ChunkyPNG::Color::TRANSPARENT
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def self.call(path, padding: DEFAULT_PADDING)
|
|
15
|
+
image = ChunkyPNG::Image.from_file(path)
|
|
16
|
+
|
|
17
|
+
min_x = image.width
|
|
18
|
+
max_x = -1
|
|
19
|
+
min_y = image.height
|
|
20
|
+
max_y = -1
|
|
21
|
+
|
|
22
|
+
image.height.times do |y|
|
|
23
|
+
image.width.times do |x|
|
|
24
|
+
next if border_pixel?(image[x, y])
|
|
25
|
+
|
|
26
|
+
min_x = x if x < min_x
|
|
27
|
+
max_x = x if x > max_x
|
|
28
|
+
min_y = y if y < min_y
|
|
29
|
+
max_y = y if y > max_y
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# No content found: keep the original image.
|
|
34
|
+
return path if max_x < min_x
|
|
35
|
+
|
|
36
|
+
content_width = max_x - min_x + 1
|
|
37
|
+
content_height = max_y - min_y + 1
|
|
38
|
+
new_width = content_width + (padding * 2)
|
|
39
|
+
new_height = content_height + (padding * 2)
|
|
40
|
+
|
|
41
|
+
trimmed = ChunkyPNG::Image.new(new_width, new_height, ChunkyPNG::Color::TRANSPARENT)
|
|
42
|
+
|
|
43
|
+
image.height.times do |y|
|
|
44
|
+
image.width.times do |x|
|
|
45
|
+
next if x < min_x || x > max_x || y < min_y || y > max_y
|
|
46
|
+
|
|
47
|
+
trimmed[x - min_x + padding, y - min_y + padding] = image[x, y]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
trimmed.save(path)
|
|
52
|
+
path
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.border_pixel?(color)
|
|
56
|
+
BORDER_COLORS.include?(color)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require_relative 'service'
|
|
2
|
-
require_relative 'screenshot_taker'
|
|
3
2
|
|
|
4
3
|
module LookbookVisualTester
|
|
5
4
|
class UpdatePreviews < Service
|
|
@@ -12,7 +11,7 @@ module LookbookVisualTester
|
|
|
12
11
|
|
|
13
12
|
def update_app_data
|
|
14
13
|
LookbookVisualTester.data[:last_changed_files] = changes.presence || []
|
|
15
|
-
LookbookVisualTester.data[:last_changed_previews] =
|
|
14
|
+
LookbookVisualTester.data[:last_changed_previews] = selected_previews
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def call
|
|
@@ -47,8 +46,6 @@ module LookbookVisualTester
|
|
|
47
46
|
|
|
48
47
|
def clean_file_name(file)
|
|
49
48
|
file = file.split(components_folder)[-1]
|
|
50
|
-
# '/' + file.split('/')[-1].split('.')[0]
|
|
51
|
-
puts ">>>> #{file}"
|
|
52
49
|
file.split('.')[0].gsub('_preview', '')
|
|
53
50
|
end
|
|
54
51
|
|
|
@@ -61,14 +58,8 @@ module LookbookVisualTester
|
|
|
61
58
|
def process_changes
|
|
62
59
|
Rails.logger.info "LookbookVisualTester: previews #{selected_previews.count}"
|
|
63
60
|
selected_previews.each do |preview|
|
|
64
|
-
Rails.logger.info "LookbookVisualTester:
|
|
65
|
-
|
|
66
|
-
group = preview.respond_to?(:scenarios) ? preview.scenarios : preview.examples
|
|
67
|
-
group.each do |scenario|
|
|
68
|
-
scenario_run = LookbookVisualTester::ScenarioRun.new(scenario)
|
|
69
|
-
Rails.logger.info "LookbookVisualTester: Processing scenario #{scenario_run.inspect}"
|
|
70
|
-
LookbookVisualTester::ScreenshotTaker.call(scenario_run: scenario_run)
|
|
71
|
-
end
|
|
61
|
+
Rails.logger.info "LookbookVisualTester: running Runner for #{preview.inspect}"
|
|
62
|
+
LookbookVisualTester::Runner.new(pattern: preview.name).run
|
|
72
63
|
end
|
|
73
64
|
end
|
|
74
65
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# lib/lookbook_visual_tester.rb
|
|
4
4
|
|
|
5
5
|
require_relative 'lookbook_visual_tester/version'
|
|
6
|
+
require_relative 'lookbook_visual_tester/browser_discovery'
|
|
6
7
|
require_relative 'lookbook_visual_tester/configuration'
|
|
7
8
|
require_relative 'lookbook_visual_tester/railtie' if defined?(Rails)
|
|
8
9
|
require_relative 'lookbook_visual_tester/scenario_finder'
|
|
@@ -11,6 +12,9 @@ require_relative 'lookbook_visual_tester/runner'
|
|
|
11
12
|
require_relative 'lookbook_visual_tester/driver'
|
|
12
13
|
require_relative 'lookbook_visual_tester/drivers/ferrum_driver'
|
|
13
14
|
require_relative 'lookbook_visual_tester/services/image_comparator'
|
|
15
|
+
require_relative 'lookbook_visual_tester/services/image_trimmer'
|
|
16
|
+
require_relative 'lookbook_visual_tester/server_test_runner'
|
|
17
|
+
|
|
14
18
|
|
|
15
19
|
module LookbookVisualTester
|
|
16
20
|
class Error < StandardError; end
|