capybara-screenshot-diff 1.3.1 → 1.4.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/.gitattributes +4 -0
- data/.github/workflows/test.yml +129 -0
- data/.gitignore +1 -1
- data/.standard.yml +11 -0
- data/Dockerfile +60 -0
- data/README.md +86 -2
- data/Rakefile +10 -8
- data/bin/console +3 -3
- data/bin/install-vips +11 -0
- data/bin/standardrb +29 -0
- data/capybara-screenshot-diff.gemspec +18 -26
- data/gemfiles/rails42.gemfile +4 -2
- data/gemfiles/rails50.gemfile +3 -2
- data/gemfiles/rails51.gemfile +3 -2
- data/gemfiles/rails52.gemfile +3 -2
- data/gemfiles/rails60_gems.rb +8 -0
- data/gemfiles/rails61_gems.rb +7 -0
- data/gems.rb +29 -0
- data/lib/capybara/screenshot/diff.rb +24 -14
- data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +355 -0
- data/lib/capybara/screenshot/diff/drivers/utils.rb +24 -0
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +180 -0
- data/lib/capybara/screenshot/diff/image_compare.rb +142 -293
- data/lib/capybara/screenshot/diff/os.rb +7 -7
- data/lib/capybara/screenshot/diff/stabilization.rb +78 -46
- data/lib/capybara/screenshot/diff/test_methods.rb +46 -55
- data/lib/capybara/screenshot/diff/vcs.rb +8 -3
- data/lib/capybara/screenshot/diff/version.rb +1 -1
- data/matrix_test.rb +20 -22
- metadata +20 -111
- data/.rubocop.yml +0 -65
- data/.rubocop_todo.yml +0 -52
- data/.travis.yml +0 -30
- data/Gemfile +0 -6
- data/gemfiles/common.gemfile +0 -12
- data/gemfiles/rails60.gemfile +0 -5
@@ -3,16 +3,16 @@
|
|
3
3
|
module Capybara
|
4
4
|
module Screenshot
|
5
5
|
module Os
|
6
|
-
ON_WINDOWS = !!(RbConfig::CONFIG[
|
7
|
-
ON_MAC = !!(RbConfig::CONFIG[
|
8
|
-
ON_LINUX = !!(RbConfig::CONFIG[
|
6
|
+
ON_WINDOWS = !!(RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/)
|
7
|
+
ON_MAC = !!(RbConfig::CONFIG["host_os"] =~ /darwin/)
|
8
|
+
ON_LINUX = !!(RbConfig::CONFIG["host_os"] =~ /linux/)
|
9
9
|
|
10
10
|
def os_name
|
11
|
-
return
|
12
|
-
return
|
13
|
-
return
|
11
|
+
return "windows" if ON_WINDOWS
|
12
|
+
return "macos" if ON_MAC
|
13
|
+
return "linux" if ON_LINUX
|
14
14
|
|
15
|
-
|
15
|
+
"unknown"
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "os"
|
4
4
|
|
5
5
|
module Capybara
|
6
6
|
module Screenshot
|
@@ -20,16 +20,13 @@ module Stabilization
|
|
20
20
|
}()
|
21
21
|
JS
|
22
22
|
|
23
|
-
def take_stable_screenshot(comparison,
|
24
|
-
area_size_limit:, skip_area:, stability_time_limit:, wait:)
|
25
|
-
blurred_input = prepare_page_for_screenshot(timeout: wait)
|
23
|
+
def take_stable_screenshot(comparison, stability_time_limit:, wait:)
|
26
24
|
previous_file_name = comparison.old_file_name
|
27
25
|
screenshot_started_at = last_image_change_at = Time.now
|
26
|
+
clean_stabilization_images(comparison.new_file_name)
|
27
|
+
|
28
28
|
1.step do |i|
|
29
29
|
take_right_size_screenshot(comparison)
|
30
|
-
|
31
|
-
break unless stability_time_limit
|
32
|
-
|
33
30
|
if comparison.quick_equal?
|
34
31
|
clean_stabilization_images(comparison.new_file_name)
|
35
32
|
break
|
@@ -37,10 +34,11 @@ def take_stable_screenshot(comparison, color_distance_limit:, shift_distance_lim
|
|
37
34
|
comparison.reset
|
38
35
|
|
39
36
|
if previous_file_name
|
40
|
-
stabilization_comparison =
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
stabilization_comparison = make_stabilization_comparison_from(
|
38
|
+
comparison,
|
39
|
+
comparison.new_file_name,
|
40
|
+
previous_file_name
|
41
|
+
)
|
44
42
|
if stabilization_comparison.quick_equal?
|
45
43
|
if (Time.now - last_image_change_at) > stability_time_limit
|
46
44
|
clean_stabilization_images(comparison.new_file_name)
|
@@ -52,39 +50,54 @@ def take_stable_screenshot(comparison, color_distance_limit:, shift_distance_lim
|
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
55
|
-
previous_file_name = "#{comparison.new_file_name.chomp(
|
56
|
-
"_x#{format(
|
57
|
-
"_#{stabilization_comparison.
|
53
|
+
previous_file_name = "#{comparison.new_file_name.chomp(".png")}" \
|
54
|
+
"_x#{format("%02i", i)}_#{(Time.now - screenshot_started_at).round(1)}s" \
|
55
|
+
"_#{stabilization_comparison.difference_region&.to_s&.gsub(", ", "_") || :initial}.png~"
|
58
56
|
FileUtils.mv comparison.new_file_name, previous_file_name
|
59
57
|
|
60
|
-
check_max_wait_time(
|
61
|
-
|
58
|
+
check_max_wait_time(
|
59
|
+
comparison,
|
60
|
+
screenshot_started_at,
|
61
|
+
max_wait_time: max_wait_time(comparison.shift_distance_limit, wait)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def notice_how_to_avoid_this
|
67
|
+
unless @_csd_retina_warned
|
68
|
+
warn "Halving retina screenshot. " \
|
69
|
+
'You should add "force-device-scale-factor=1" to your Chrome chromeOptions args.'
|
70
|
+
@_csd_retina_warned = true
|
62
71
|
end
|
63
|
-
ensure
|
64
|
-
blurred_input&.click
|
65
72
|
end
|
66
73
|
|
67
74
|
private
|
68
75
|
|
69
|
-
def
|
76
|
+
def make_stabilization_comparison_from(comparison, new_file_name, previous_file_name)
|
77
|
+
ImageCompare.new(new_file_name, previous_file_name, **comparison.driver_options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def reduce_retina_image_size(file_name, driver)
|
70
81
|
return if !ON_MAC || !selenium? || !Capybara::Screenshot.window_size
|
71
82
|
|
72
|
-
|
73
|
-
|
74
|
-
return if saved_image
|
83
|
+
expected_image_width = Capybara::Screenshot.window_size[0]
|
84
|
+
saved_image = driver.from_file(file_name)
|
85
|
+
return if driver.width_for(saved_image) < expected_image_width * 2
|
75
86
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
87
|
+
notice_how_to_avoid_this
|
88
|
+
|
89
|
+
new_height = expected_image_width * driver.height_for(saved_image) / driver.width_for(saved_image)
|
90
|
+
resized_image = driver.resize_image_to(saved_image, expected_image_width, new_height)
|
91
|
+
|
92
|
+
Dir.mktmpdir do |dir|
|
93
|
+
resized_image_file = "#{dir}/resized.png"
|
94
|
+
driver.save_image_to(resized_image, resized_image_file)
|
95
|
+
FileUtils.mv(resized_image_file, file_name)
|
80
96
|
end
|
81
|
-
height = (width * saved_image.height) / saved_image.width
|
82
|
-
resized_image = saved_image.resample_bilinear(width, height)
|
83
|
-
resized_image.save(file_name)
|
84
97
|
end
|
85
98
|
|
86
99
|
def stabilization_images(base_file)
|
87
|
-
Dir["#{base_file.chomp(
|
100
|
+
Dir["#{base_file.chomp(".png")}_x*.png~"].sort
|
88
101
|
end
|
89
102
|
|
90
103
|
def clean_stabilization_images(base_file)
|
@@ -104,7 +117,15 @@ def prepare_page_for_screenshot(timeout:)
|
|
104
117
|
JS
|
105
118
|
blurred_input = page.driver.send :unwrap_script_result, active_element
|
106
119
|
end
|
107
|
-
|
120
|
+
if Capybara::Screenshot.hide_caret && !@hid_caret
|
121
|
+
execute_script(<<~JS)
|
122
|
+
var style = document.createElement('style');
|
123
|
+
document.head.appendChild(style);
|
124
|
+
var styleSheet = style.sheet;
|
125
|
+
styleSheet.insertRule("* { caret-color: transparent !important; }", 0);
|
126
|
+
JS
|
127
|
+
@hid_caret = true
|
128
|
+
end
|
108
129
|
blurred_input
|
109
130
|
end
|
110
131
|
|
@@ -112,32 +133,40 @@ def take_right_size_screenshot(comparison)
|
|
112
133
|
save_screenshot(comparison.new_file_name)
|
113
134
|
|
114
135
|
# TODO(uwe): Remove when chromedriver takes right size screenshots
|
115
|
-
reduce_retina_image_size(comparison.new_file_name)
|
136
|
+
reduce_retina_image_size(comparison.new_file_name, comparison.driver)
|
116
137
|
# ODOT
|
117
138
|
end
|
118
139
|
|
119
|
-
def check_max_wait_time(comparison, screenshot_started_at,
|
120
|
-
shift_factor = shift_distance_limit ? (shift_distance_limit * 2 + 1) ^ 2 : 1
|
121
|
-
max_wait_time = wait * shift_factor
|
140
|
+
def check_max_wait_time(comparison, screenshot_started_at, max_wait_time:)
|
122
141
|
return if (Time.now - screenshot_started_at) < max_wait_time
|
123
142
|
|
143
|
+
annotate_stabilization_images(comparison)
|
124
144
|
# FIXME(uwe): Change to store the failure and only report if the test succeeds functionally.
|
145
|
+
fail("Could not get stable screenshot within #{max_wait_time}s\n" \
|
146
|
+
"#{stabilization_images(comparison.new_file_name).join("\n")}")
|
147
|
+
end
|
148
|
+
|
149
|
+
def annotate_stabilization_images(comparison)
|
125
150
|
previous_file = comparison.old_file_name
|
126
151
|
stabilization_images(comparison.new_file_name).each do |file_name|
|
127
152
|
if File.exist? previous_file
|
128
|
-
stabilization_comparison =
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
153
|
+
stabilization_comparison = make_stabilization_comparison_from(
|
154
|
+
comparison,
|
155
|
+
file_name,
|
156
|
+
previous_file
|
157
|
+
)
|
158
|
+
if stabilization_comparison.different?
|
159
|
+
FileUtils.mv stabilization_comparison.annotated_new_file_name, file_name
|
160
|
+
end
|
135
161
|
FileUtils.rm stabilization_comparison.annotated_old_file_name
|
136
162
|
end
|
137
163
|
previous_file = file_name
|
138
164
|
end
|
139
|
-
|
140
|
-
|
165
|
+
end
|
166
|
+
|
167
|
+
def max_wait_time(shift_distance_limit, wait)
|
168
|
+
shift_factor = shift_distance_limit ? (shift_distance_limit * 2 + 1) ^ 2 : 1
|
169
|
+
wait * shift_factor
|
141
170
|
end
|
142
171
|
|
143
172
|
def assert_images_loaded(timeout:)
|
@@ -148,8 +177,11 @@ def assert_images_loaded(timeout:)
|
|
148
177
|
pending_image = evaluate_script IMAGE_WAIT_SCRIPT
|
149
178
|
break unless pending_image
|
150
179
|
|
151
|
-
assert(
|
152
|
-
|
180
|
+
assert(
|
181
|
+
(Time.now - start) < timeout,
|
182
|
+
"Images not loaded after #{timeout}s: #{pending_image.inspect}"
|
183
|
+
)
|
184
|
+
|
153
185
|
sleep 0.1
|
154
186
|
end
|
155
187
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require_relative
|
9
|
-
require_relative
|
10
|
-
require_relative
|
3
|
+
require "English"
|
4
|
+
require "capybara"
|
5
|
+
require "action_controller"
|
6
|
+
require "action_dispatch"
|
7
|
+
require "active_support/core_ext/string/strip"
|
8
|
+
require_relative "image_compare"
|
9
|
+
require_relative "stabilization"
|
10
|
+
require_relative "vcs"
|
11
11
|
|
12
12
|
# Add the `screenshot` method to ActionDispatch::IntegrationTest
|
13
13
|
module Capybara
|
@@ -42,19 +42,13 @@ def screenshot_dir
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def current_capybara_driver_class
|
45
|
-
Capybara.
|
45
|
+
Capybara.current_session.driver.class
|
46
46
|
end
|
47
47
|
|
48
48
|
def selenium?
|
49
49
|
current_capybara_driver_class <= Capybara::Selenium::Driver
|
50
50
|
end
|
51
51
|
|
52
|
-
def poltergeist?
|
53
|
-
return false unless defined?(Capybara::Poltergeist::Driver)
|
54
|
-
|
55
|
-
current_capybara_driver_class <= Capybara::Poltergeist::Driver
|
56
|
-
end
|
57
|
-
|
58
52
|
def screenshot_section(name)
|
59
53
|
@screenshot_section = name.to_s
|
60
54
|
end
|
@@ -68,48 +62,59 @@ def screenshot_group(name)
|
|
68
62
|
end
|
69
63
|
|
70
64
|
# @return [Boolean] wether a screenshot was taken
|
71
|
-
def screenshot(
|
65
|
+
def screenshot(
|
66
|
+
name,
|
67
|
+
stability_time_limit: Screenshot.stability_time_limit,
|
68
|
+
wait: Capybara.default_max_wait_time,
|
69
|
+
**driver_options
|
70
|
+
)
|
71
|
+
return false unless Screenshot.active?
|
72
|
+
return false if window_size_is_wrong?
|
73
|
+
|
74
|
+
driver_options = {
|
75
|
+
area_size_limit: Diff.area_size_limit,
|
72
76
|
color_distance_limit: Diff.color_distance_limit,
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
driver: Diff.driver,
|
78
|
+
shift_distance_limit: Diff.shift_distance_limit,
|
79
|
+
skip_area: Diff.skip_area,
|
80
|
+
tolerance: Diff.tolerance
|
81
|
+
}.merge(driver_options)
|
82
|
+
|
83
|
+
# Allow nil or single or multiple areas
|
84
|
+
if driver_options[:skip_area]
|
85
|
+
driver_options[:skip_area] = driver_options[:skip_area].compact.flatten&.each_cons(4)&.to_a
|
86
|
+
end
|
80
87
|
|
81
88
|
if @screenshot_counter
|
82
|
-
name = "#{format(
|
89
|
+
name = "#{format("%02i", @screenshot_counter)}_#{name}"
|
83
90
|
@screenshot_counter += 1
|
84
91
|
end
|
85
92
|
name = full_name(name)
|
86
93
|
file_name = "#{Screenshot.screenshot_area_abs}/#{name}.png"
|
87
94
|
|
88
95
|
FileUtils.mkdir_p File.dirname(file_name)
|
89
|
-
comparison = ImageCompare.new(file_name,
|
90
|
-
dimensions: Screenshot.window_size, color_distance_limit: color_distance_limit,
|
91
|
-
area_size_limit: area_size_limit, shift_distance_limit: shift_distance_limit,
|
92
|
-
skip_area: skip_area)
|
96
|
+
comparison = ImageCompare.new(file_name, **driver_options)
|
93
97
|
checkout_vcs(name, comparison)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
begin
|
99
|
+
blurred_input = prepare_page_for_screenshot(timeout: wait)
|
100
|
+
if stability_time_limit
|
101
|
+
take_stable_screenshot(comparison, stability_time_limit: stability_time_limit, wait: wait)
|
102
|
+
else
|
103
|
+
take_right_size_screenshot(comparison)
|
104
|
+
end
|
105
|
+
ensure
|
106
|
+
blurred_input&.click
|
107
|
+
end
|
108
|
+
|
109
|
+
return false unless comparison.old_file_exists?
|
101
110
|
|
102
111
|
(@test_screenshots ||= []) << [caller(1..1).first, name, comparison]
|
112
|
+
|
103
113
|
true
|
104
114
|
end
|
105
115
|
|
106
116
|
def window_size_is_wrong?
|
107
117
|
selenium? && Screenshot.window_size &&
|
108
|
-
|
109
|
-
# FIXME(uwe): This happens with headless chrome. Why?!
|
110
|
-
page.driver.browser.manage.window.size.width &&
|
111
|
-
# EMXIF
|
112
|
-
|
113
118
|
page.driver.browser.manage.window.size !=
|
114
119
|
::Selenium::WebDriver::Dimension.new(*Screenshot.window_size)
|
115
120
|
end
|
@@ -117,21 +122,7 @@ def window_size_is_wrong?
|
|
117
122
|
def assert_image_not_changed(caller, name, comparison)
|
118
123
|
return unless comparison.different?
|
119
124
|
|
120
|
-
|
121
|
-
max_color_distance = if RUBY_VERSION >= '2.4'
|
122
|
-
comparison.max_color_distance.ceil(1)
|
123
|
-
else
|
124
|
-
comparison.max_color_distance.ceil
|
125
|
-
end
|
126
|
-
# ODOT
|
127
|
-
|
128
|
-
max_shift_distance = comparison.max_shift_distance
|
129
|
-
"Screenshot does not match for '#{name}' (area: #{comparison.size}px #{comparison.dimensions}" \
|
130
|
-
", max_color_distance: #{max_color_distance}" \
|
131
|
-
"#{", max_shift_distance: #{max_shift_distance}" if max_shift_distance})\n" \
|
132
|
-
"#{comparison.new_file_name}\n#{comparison.annotated_old_file_name}\n" \
|
133
|
-
"#{comparison.annotated_new_file_name}\n" \
|
134
|
-
"at #{caller}"
|
125
|
+
"Screenshot does not match for '#{name}' #{comparison.error_message}\nat #{caller}"
|
135
126
|
end
|
136
127
|
end
|
137
128
|
end
|
@@ -1,15 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "os"
|
4
4
|
module Capybara
|
5
5
|
module Screenshot
|
6
6
|
module Diff
|
7
7
|
module Vcs
|
8
|
-
SILENCE_ERRORS = Os::ON_WINDOWS ?
|
8
|
+
SILENCE_ERRORS = Os::ON_WINDOWS ? "2>nul" : "2>/dev/null"
|
9
9
|
|
10
10
|
def restore_git_revision(name, target_file_name)
|
11
11
|
redirect_target = "#{target_file_name} #{SILENCE_ERRORS}"
|
12
|
-
|
12
|
+
show_command = "git show HEAD~0:./#{Capybara::Screenshot.screenshot_area}/#{name}.png"
|
13
|
+
if Capybara::Screenshot.use_lfs
|
14
|
+
`#{show_command} | git lfs smudge > #{redirect_target}`
|
15
|
+
else
|
16
|
+
`#{show_command} > #{redirect_target}`
|
17
|
+
end
|
13
18
|
FileUtils.rm_f(target_file_name) unless $CHILD_STATUS == 0
|
14
19
|
end
|
15
20
|
|
data/matrix_test.rb
CHANGED
@@ -1,31 +1,29 @@
|
|
1
1
|
#!/usr/bin/env ruby -w
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
4
|
+
update_gemfiles = ARGV.delete("--update")
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
require 'yaml'
|
9
|
-
travis = YAML.safe_load(File.read('.travis.yml'))
|
6
|
+
require "yaml"
|
7
|
+
travis = YAML.safe_load(File.read(".travis.yml"))
|
10
8
|
|
11
9
|
def run_script(ruby, env, gemfile)
|
12
10
|
env.scan(/\b(?<key>[A-Z_]+)="(?<value>.+?)"/) do |key, value|
|
13
11
|
ENV[key] = value
|
14
12
|
end
|
15
|
-
puts
|
13
|
+
puts "*" * 80
|
16
14
|
puts "Testing #{ruby} #{gemfile} #{env}"
|
17
15
|
puts
|
18
16
|
system("chruby-exec #{ruby} -- bundle exec rake") || exit(1)
|
19
17
|
puts "Testing #{ruby} #{gemfile} OK"
|
20
|
-
puts
|
18
|
+
puts "*" * 80
|
21
19
|
end
|
22
20
|
|
23
21
|
def use_gemfile(ruby, gemfile, update_gemfiles)
|
24
|
-
puts
|
25
|
-
ENV[
|
22
|
+
puts "$" * 80
|
23
|
+
ENV["BUNDLE_GEMFILE"] = gemfile
|
26
24
|
|
27
25
|
bundler_version = `grep -A1 "BUNDLED WITH" #{gemfile}.lock | tail -n 1`
|
28
|
-
bundler_version =
|
26
|
+
bundler_version = "~> 2.0" if bundler_version.strip.empty?
|
29
27
|
|
30
28
|
version_arg = "-v '#{bundler_version}'"
|
31
29
|
bundler_gem_check_cmd = "chruby-exec #{ruby} -- gem query -i -n bundler #{version_arg} >/dev/null"
|
@@ -37,33 +35,33 @@ def use_gemfile(ruby, gemfile, update_gemfiles)
|
|
37
35
|
system "chruby-exec #{ruby} -- bundle check >/dev/null || chruby-exec #{ruby} -- bundle install"
|
38
36
|
end || exit(1)
|
39
37
|
yield
|
40
|
-
puts
|
38
|
+
puts "$" * 80
|
41
39
|
end
|
42
40
|
|
43
|
-
travis[
|
44
|
-
next if
|
41
|
+
travis["rvm"].each do |ruby|
|
42
|
+
next if /head/.match?(ruby) # ruby-install does not support HEAD installation
|
45
43
|
|
46
|
-
puts
|
44
|
+
puts "#" * 80
|
47
45
|
puts "Testing #{ruby}"
|
48
46
|
puts
|
49
47
|
system "ruby-install --no-reinstall #{ruby}" || exit(1)
|
50
|
-
travis[
|
51
|
-
if travis[
|
52
|
-
(travis[
|
53
|
-
.any? { |f| f[
|
54
|
-
puts
|
48
|
+
travis["gemfile"].each do |gemfile|
|
49
|
+
if travis["matrix"] &&
|
50
|
+
(travis["matrix"]["exclude"].to_a + travis["matrix"]["allow_failures"].to_a)
|
51
|
+
.any? { |f| f["rvm"] == ruby && (f["gemfile"].nil? || f["gemfile"] == gemfile) }
|
52
|
+
puts "Skipping known failure."
|
55
53
|
next
|
56
54
|
end
|
57
55
|
use_gemfile(ruby, gemfile, update_gemfiles) do
|
58
|
-
travis[
|
56
|
+
travis["env"].each do |env|
|
59
57
|
run_script(ruby, env, gemfile)
|
60
58
|
end
|
61
59
|
end
|
62
60
|
end
|
63
61
|
puts "Testing #{ruby} OK"
|
64
|
-
puts
|
62
|
+
puts "#" * 80
|
65
63
|
end
|
66
64
|
|
67
65
|
print "\033[0;32m"
|
68
|
-
print
|
66
|
+
print " TESTS PASSED OK!"
|
69
67
|
puts "\033[0m"
|