kontrast 0.2.1 → 0.6.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/README.md +135 -96
- data/bin/kontrast +9 -5
- data/lib/kontrast.rb +17 -5
- data/lib/kontrast/api_client.rb +56 -0
- data/lib/kontrast/api_endpoint_comparator.rb +126 -0
- data/lib/kontrast/api_endpoint_runner.rb +99 -0
- data/lib/kontrast/api_endpoint_test.rb +8 -0
- data/lib/kontrast/configuration.rb +36 -7
- data/lib/kontrast/exceptions.rb +2 -1
- data/lib/kontrast/gallery/template.erb +152 -21
- data/lib/kontrast/gallery_creator.rb +105 -47
- data/lib/kontrast/global_runner.rb +121 -0
- data/lib/kontrast/image_helper.rb +63 -0
- data/lib/kontrast/image_uploader.rb +18 -0
- data/lib/kontrast/page_comparator.rb +46 -0
- data/lib/kontrast/page_runner.rb +95 -0
- data/lib/kontrast/page_test.rb +32 -0
- data/lib/kontrast/selenium_handler.rb +18 -7
- data/lib/kontrast/spec.rb +21 -0
- data/lib/kontrast/spec_builder.rb +54 -0
- data/lib/kontrast/test.rb +27 -0
- data/lib/kontrast/test_builder.rb +25 -9
- data/lib/kontrast/test_suite.rb +42 -0
- data/lib/kontrast/thumbnail_creator.rb +18 -0
- data/lib/kontrast/version.rb +1 -1
- data/spec/api_endpoint_comparator_spec.rb +125 -0
- data/spec/configuration_spec.rb +19 -0
- data/spec/gallery_creator_spec.rb +26 -20
- data/spec/{image_handler_spec.rb → global_runner_spec.rb} +17 -12
- data/spec/page_comparator_spec.rb +31 -0
- data/spec/page_runner_spec.rb +45 -0
- data/spec/spec_builder_spec.rb +32 -0
- data/spec/support/fixtures/image.jpg +0 -0
- data/spec/support/fixtures/image_clone.jpg +0 -0
- data/spec/support/fixtures/img1.jpg +0 -0
- data/spec/support/fixtures/img2.jpg +0 -0
- data/spec/support/fixtures/other_image.jpg +0 -0
- data/spec/test_builder_spec.rb +6 -3
- data/spec/test_spec.rb +53 -0
- data/spec/test_suite_spec.rb +56 -0
- metadata +91 -30
- data/lib/kontrast/image_handler.rb +0 -119
- data/lib/kontrast/runner.rb +0 -141
- data/spec/runner_spec.rb +0 -37
@@ -1,119 +0,0 @@
|
|
1
|
-
require "RMagick"
|
2
|
-
require "workers"
|
3
|
-
|
4
|
-
module Kontrast
|
5
|
-
class ImageHandler
|
6
|
-
include Magick
|
7
|
-
attr_reader :diffs, :path
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
@path = Kontrast.path
|
11
|
-
|
12
|
-
# This is where failed diffs will be stored
|
13
|
-
@diffs = {}
|
14
|
-
end
|
15
|
-
|
16
|
-
# In order for images to be diff'ed, they need to have the same dimensions
|
17
|
-
def crop_images(width, name)
|
18
|
-
# Load images
|
19
|
-
test_image = Image.read("#{@path}/#{width}_#{name}/test.png").first
|
20
|
-
production_image = Image.read("#{@path}/#{width}_#{name}/production.png").first
|
21
|
-
|
22
|
-
# Let's not do anything if the images are already the same size
|
23
|
-
return if test_image.rows == production_image.rows
|
24
|
-
|
25
|
-
# Get max height of both images
|
26
|
-
max_height = [test_image.rows, production_image.rows].max
|
27
|
-
|
28
|
-
# Crop
|
29
|
-
Workers.map([test_image, production_image]) do |image|
|
30
|
-
image.extent(width, max_height).write(image.filename)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# Uses the compare_channel function to highlight the differences between two images
|
35
|
-
# Docs: http://www.imagemagick.org/RMagick/doc/image1.html#compare_channel
|
36
|
-
def diff_images(width, name)
|
37
|
-
# Load images
|
38
|
-
test_image = Image.read("#{@path}/#{width}_#{name}/test.png").first
|
39
|
-
production_image = Image.read("#{@path}/#{width}_#{name}/production.png").first
|
40
|
-
|
41
|
-
# Compare and save diff
|
42
|
-
diff = test_image.compare_channel(production_image, Magick.const_get(Kontrast.configuration.distortion_metric)) do |options|
|
43
|
-
options.highlight_color = Kontrast.configuration.highlight_color
|
44
|
-
options.lowlight_color = Kontrast.configuration.lowlight_color
|
45
|
-
end
|
46
|
-
diff.first.write("#{@path}/#{width}_#{name}/diff.png")
|
47
|
-
|
48
|
-
# If the images are different, let the class know about it so that it gets added to the manifest
|
49
|
-
if diff.last > 0
|
50
|
-
@diffs["#{width}_#{name}"] = {
|
51
|
-
width: width,
|
52
|
-
name: name,
|
53
|
-
diff: diff.last
|
54
|
-
}
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# For the gallery. Not sure if this is really necessary.
|
59
|
-
def create_thumbnails(width, name)
|
60
|
-
# Load images
|
61
|
-
test_image = Image.read("#{@path}/#{width}_#{name}/test.png").first
|
62
|
-
production_image = Image.read("#{@path}/#{width}_#{name}/production.png").first
|
63
|
-
diff_image = Image.read("#{@path}/#{width}_#{name}/diff.png").first
|
64
|
-
|
65
|
-
# Crop images
|
66
|
-
Workers.map([test_image, production_image, diff_image]) do |image|
|
67
|
-
filename = image.filename.split('/').last.split('.').first + "_thumb"
|
68
|
-
image.resize_to_fill(200, 200, NorthGravity).write("#{@path}/#{width}_#{name}/#{filename}.png")
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# We upload the images per test
|
73
|
-
def upload_images(width, name)
|
74
|
-
Workers.map(Dir.entries("#{@path}/#{width}_#{name}")) do |file|
|
75
|
-
next if ['.', '..'].include?(file)
|
76
|
-
Kontrast.fog.directories.get(Kontrast.configuration.aws_bucket).files.create(
|
77
|
-
key: "#{Kontrast.configuration.remote_path}/#{width}_#{name}/#{file}",
|
78
|
-
body: File.open("#{@path}/#{width}_#{name}/#{file}"),
|
79
|
-
public: true
|
80
|
-
)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# The manifest is a per-node .json file that is used to create the gallery
|
85
|
-
# without having to download all assets from S3 to the test environment
|
86
|
-
def create_manifest(current_node, build = nil)
|
87
|
-
# Set up structure
|
88
|
-
manifest = {
|
89
|
-
diffs: @diffs,
|
90
|
-
files: []
|
91
|
-
}
|
92
|
-
|
93
|
-
# Dump directories
|
94
|
-
Dir.foreach(@path) do |subdir|
|
95
|
-
next if ['.', '..'].include?(subdir)
|
96
|
-
next if subdir.index('manifest_')
|
97
|
-
Dir.foreach("#{@path}/#{subdir}") do |img|
|
98
|
-
next if ['.', '..'].include?(img)
|
99
|
-
manifest[:files] << "#{subdir}/#{img}"
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
if Kontrast.configuration.run_parallel
|
104
|
-
# Upload manifest
|
105
|
-
Kontrast.fog.directories.get(Kontrast.configuration.aws_bucket).files.create(
|
106
|
-
key: "#{build}/manifest_#{current_node}.json",
|
107
|
-
body: manifest.to_json
|
108
|
-
)
|
109
|
-
else
|
110
|
-
# Write manifest
|
111
|
-
File.open("#{@path}/manifest_#{current_node}.json", 'w') do |outf|
|
112
|
-
outf.write(manifest.to_json)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
return manifest
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
data/lib/kontrast/runner.rb
DELETED
@@ -1,141 +0,0 @@
|
|
1
|
-
require "yaml"
|
2
|
-
require "net/http"
|
3
|
-
|
4
|
-
module Kontrast
|
5
|
-
class Runner
|
6
|
-
def initialize
|
7
|
-
end
|
8
|
-
|
9
|
-
def run
|
10
|
-
# Make sure the local server is running
|
11
|
-
wait_for_server
|
12
|
-
|
13
|
-
# Assign nodes
|
14
|
-
if Kontrast.configuration.run_parallel
|
15
|
-
total_nodes = Kontrast.configuration.total_nodes
|
16
|
-
current_node = Kontrast.configuration.current_node
|
17
|
-
else
|
18
|
-
# Override the config for local use
|
19
|
-
total_nodes = 1
|
20
|
-
current_node = 0
|
21
|
-
end
|
22
|
-
|
23
|
-
# Assign tests and run them
|
24
|
-
to_run = split_run(total_nodes, current_node)
|
25
|
-
parallel_run(to_run, current_node)
|
26
|
-
end
|
27
|
-
|
28
|
-
# Given the total number of nodes and the index of the current node,
|
29
|
-
# we determine which tests the current node will run
|
30
|
-
def split_run(total_nodes, current_node)
|
31
|
-
all_tests = Kontrast.test_suite.tests
|
32
|
-
tests_to_run = Hash.new
|
33
|
-
|
34
|
-
index = 0
|
35
|
-
all_tests.each do |width, pages|
|
36
|
-
next if pages.nil?
|
37
|
-
tests_to_run[width] = {}
|
38
|
-
pages.each do |name, path|
|
39
|
-
if index % total_nodes == current_node
|
40
|
-
tests_to_run[width][name] = path
|
41
|
-
end
|
42
|
-
index += 1
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
return tests_to_run
|
47
|
-
end
|
48
|
-
|
49
|
-
# Runs tests, handles all image operations, creates manifest for current node
|
50
|
-
def parallel_run(tests, current_node)
|
51
|
-
# Load test handlers
|
52
|
-
@selenium_handler = SeleniumHandler.new
|
53
|
-
@image_handler = ImageHandler.new
|
54
|
-
|
55
|
-
begin
|
56
|
-
# Run per-page tasks
|
57
|
-
tests.each do |width, pages|
|
58
|
-
next if pages.nil?
|
59
|
-
pages.each do |name, path|
|
60
|
-
print "Processing #{name} @ #{width}... "
|
61
|
-
|
62
|
-
# Run the browser and take screenshots
|
63
|
-
@selenium_handler.run_comparison(width, path, name)
|
64
|
-
|
65
|
-
# Crop images
|
66
|
-
print "Cropping... "
|
67
|
-
@image_handler.crop_images(width, name)
|
68
|
-
|
69
|
-
# Compare images
|
70
|
-
print "Diffing... "
|
71
|
-
@image_handler.diff_images(width, name)
|
72
|
-
|
73
|
-
# Create thumbnails for gallery
|
74
|
-
print "Creating thumbnails... "
|
75
|
-
@image_handler.create_thumbnails(width, name)
|
76
|
-
|
77
|
-
# Upload to S3
|
78
|
-
if Kontrast.configuration.run_parallel
|
79
|
-
print "Uploading... "
|
80
|
-
@image_handler.upload_images(width, name)
|
81
|
-
end
|
82
|
-
|
83
|
-
puts "\n", ("=" * 85)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# Log diffs
|
88
|
-
puts @image_handler.diffs
|
89
|
-
|
90
|
-
# Create manifest
|
91
|
-
puts "Creating manifest..."
|
92
|
-
if Kontrast.configuration.run_parallel
|
93
|
-
@image_handler.create_manifest(current_node, Kontrast.configuration.remote_path)
|
94
|
-
else
|
95
|
-
@image_handler.create_manifest(current_node)
|
96
|
-
end
|
97
|
-
ensure
|
98
|
-
@selenium_handler.cleanup
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
private
|
103
|
-
def wait_for_server
|
104
|
-
# Test server
|
105
|
-
tries = 30
|
106
|
-
uri = URI(Kontrast.configuration.test_domain)
|
107
|
-
begin
|
108
|
-
Net::HTTP.get(uri)
|
109
|
-
rescue Errno::ECONNREFUSED => e
|
110
|
-
tries -= 1
|
111
|
-
if tries > 0
|
112
|
-
puts "Waiting for test server..."
|
113
|
-
sleep 2
|
114
|
-
retry
|
115
|
-
else
|
116
|
-
raise RunnerException.new("Could not reach the test server at '#{uri}'.")
|
117
|
-
end
|
118
|
-
rescue Exception => e
|
119
|
-
raise RunnerException.new("An unexpected error occured while trying to reach the test server at '#{uri}': #{e.inspect}")
|
120
|
-
end
|
121
|
-
|
122
|
-
# Production server
|
123
|
-
tries = 30
|
124
|
-
uri = URI(Kontrast.configuration.production_domain)
|
125
|
-
begin
|
126
|
-
Net::HTTP.get(uri)
|
127
|
-
rescue Errno::ECONNREFUSED => e
|
128
|
-
tries -= 1
|
129
|
-
if tries > 0
|
130
|
-
puts "Waiting for production server..."
|
131
|
-
sleep 2
|
132
|
-
retry
|
133
|
-
else
|
134
|
-
raise RunnerException.new("Could not reach the production server at '#{uri}'.")
|
135
|
-
end
|
136
|
-
rescue Exception => e
|
137
|
-
raise RunnerException.new("An unexpected error occured while trying to reach the production server at '#{uri}': #{e.inspect}")
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
data/spec/runner_spec.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
describe Kontrast::Runner do
|
2
|
-
before :all do
|
3
|
-
Kontrast.configure do |config|
|
4
|
-
# Set up some tests
|
5
|
-
config.pages(1280) do |page|
|
6
|
-
page.home "/"
|
7
|
-
page.products "/"
|
8
|
-
end
|
9
|
-
config.pages(320) do |page|
|
10
|
-
page.home "/"
|
11
|
-
page.products "/"
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
before :each do
|
17
|
-
@runner = Kontrast::Runner.new
|
18
|
-
end
|
19
|
-
|
20
|
-
describe "split_run" do
|
21
|
-
it "return all tests when there is only one node" do
|
22
|
-
expect(@runner.split_run(1, 0)).to eql(Kontrast.test_suite.tests)
|
23
|
-
end
|
24
|
-
|
25
|
-
it "returns a subset of the tests when there are multiple nodes" do
|
26
|
-
tests = @runner.split_run(4, 0)
|
27
|
-
|
28
|
-
expect(tests).not_to eql Kontrast.test_suite.tests
|
29
|
-
expect(tests).to eql({
|
30
|
-
1280 => {
|
31
|
-
"home" => "/"
|
32
|
-
},
|
33
|
-
320 => {}
|
34
|
-
})
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|