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
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
module Kontrast
|
5
|
+
class PageTest < Test
|
6
|
+
|
7
|
+
attr_reader :width, :url_params
|
8
|
+
|
9
|
+
def initialize(prefix, name, path, headers: {}, url_params: {})
|
10
|
+
super(prefix, name, path, headers)
|
11
|
+
@width = prefix
|
12
|
+
@url_params = url_params
|
13
|
+
|
14
|
+
# Re-define path so it includes all URL params
|
15
|
+
@path = get_path_with_params(url_params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_path_with_params(url_params)
|
19
|
+
uri = URI(@path)
|
20
|
+
original_query = Rack::Utils.parse_query(uri.query)
|
21
|
+
new_query = url_params.merge(original_query)
|
22
|
+
uri.query = Rack::Utils.build_query(new_query)
|
23
|
+
|
24
|
+
# Ensure there's no trailing "?"
|
25
|
+
if uri.query == ""
|
26
|
+
uri.query = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
return uri.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -31,22 +31,33 @@ module Kontrast
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
def run_comparison(
|
34
|
+
def run_comparison(test)
|
35
35
|
# Create folder for this test
|
36
|
-
current_output = FileUtils.mkdir_p("#{@path}/#{
|
36
|
+
current_output = FileUtils.mkdir_p("#{@path}/#{test}").join('')
|
37
37
|
|
38
38
|
# Open test host tabs
|
39
|
-
navigate(path)
|
39
|
+
navigate(test.path)
|
40
40
|
|
41
41
|
# Resize to given width and total height
|
42
|
-
resize(width)
|
42
|
+
resize(test.width)
|
43
|
+
|
44
|
+
screenshot_args = [@test_driver[:driver], @production_driver[:driver], { width: test.width, name: test.name }]
|
43
45
|
|
44
46
|
# Take screenshot
|
45
47
|
begin
|
46
|
-
|
48
|
+
# Global callback
|
49
|
+
Kontrast.configuration.before_screenshot(*screenshot_args)
|
50
|
+
|
51
|
+
# Spec callback
|
52
|
+
test.run_callback(:before_screenshot, *screenshot_args)
|
53
|
+
|
47
54
|
screenshot(current_output)
|
48
55
|
ensure
|
49
|
-
|
56
|
+
# Global callback
|
57
|
+
Kontrast.configuration.after_screenshot(*screenshot_args)
|
58
|
+
|
59
|
+
# Spec callback
|
60
|
+
test.run_callback(:after_screenshot, *screenshot_args)
|
50
61
|
end
|
51
62
|
end
|
52
63
|
|
@@ -81,4 +92,4 @@ module Kontrast
|
|
81
92
|
end
|
82
93
|
end
|
83
94
|
end
|
84
|
-
end
|
95
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Kontrast
|
2
|
+
class Spec
|
3
|
+
attr_accessor :name, :_before_screenshot, :_after_screenshot
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def before_screenshot(test_driver, production_driver, current_test)
|
10
|
+
if @_before_screenshot
|
11
|
+
@_before_screenshot.call(test_driver, production_driver, current_test)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def after_screenshot(test_driver, production_driver, current_test)
|
16
|
+
if @_after_screenshot
|
17
|
+
@_after_screenshot.call(test_driver, production_driver, current_test)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Kontrast
|
2
|
+
class << self
|
3
|
+
attr_accessor :spec_builder
|
4
|
+
|
5
|
+
def get_spec_builder
|
6
|
+
self.spec_builder ||= SpecBuilder.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def describe(spec_name)
|
10
|
+
self.get_spec_builder.add(spec_name)
|
11
|
+
yield(self.get_spec_builder)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class SpecBuilder
|
16
|
+
attr_reader :specs
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@specs = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def add(spec_name)
|
23
|
+
@current_spec = Spec.new(spec_name)
|
24
|
+
@specs << @current_spec
|
25
|
+
end
|
26
|
+
|
27
|
+
def before_screenshot(&block)
|
28
|
+
@current_spec._before_screenshot = block
|
29
|
+
end
|
30
|
+
|
31
|
+
def after_screenshot(&block)
|
32
|
+
@current_spec._after_screenshot = block
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.load_specs(specs_path = nil)
|
36
|
+
if !specs_path.nil?
|
37
|
+
spec_folder = specs_path
|
38
|
+
elsif Kontrast.in_rails?
|
39
|
+
spec_folder = Rails.root.to_s + "/kontrast_specs"
|
40
|
+
else
|
41
|
+
spec_folder = "./kontrast_specs"
|
42
|
+
end
|
43
|
+
|
44
|
+
spec_files = Dir[spec_folder + "/**/*_spec.rb"]
|
45
|
+
spec_files.each do |file|
|
46
|
+
require file
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def clear!
|
51
|
+
@specs = []
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Kontrast
|
2
|
+
class Test
|
3
|
+
attr_reader :name, :path, :spec, :prefix, :headers
|
4
|
+
|
5
|
+
def initialize(prefix, name, path, headers: {})
|
6
|
+
@prefix, @name, @path, @headers = prefix, name, path, headers
|
7
|
+
end
|
8
|
+
|
9
|
+
def bind_spec(spec)
|
10
|
+
@spec = spec
|
11
|
+
end
|
12
|
+
|
13
|
+
# Usage: test.run_callback(:before_screenshot, arg1, arg2, arg3)
|
14
|
+
def run_callback(name, *args)
|
15
|
+
return if @spec.nil?
|
16
|
+
@spec.send(name.to_sym, *args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
return "#{@prefix}_#{@name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_str
|
24
|
+
return to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,12 +1,16 @@
|
|
1
1
|
module Kontrast
|
2
|
+
|
3
|
+
LazyTest = Struct.new(:prefix, :headers, :block)
|
4
|
+
|
2
5
|
class TestBuilder
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
+
attr_reader :suite
|
7
|
+
attr_accessor :prefix, :headers, :url_params
|
6
8
|
|
7
|
-
def
|
8
|
-
@
|
9
|
-
@
|
9
|
+
def initialize
|
10
|
+
@suite = TestSuite.new
|
11
|
+
@prefix = nil
|
12
|
+
@headers = {}
|
13
|
+
@url_params = {}
|
10
14
|
end
|
11
15
|
|
12
16
|
# Needed in case someone tries to name a test "tests"
|
@@ -14,12 +18,24 @@ module Kontrast
|
|
14
18
|
if param
|
15
19
|
raise ConfigurationException.new("'tests' is not a valid name for a test.")
|
16
20
|
end
|
17
|
-
return @tests
|
21
|
+
return @suite.tests
|
22
|
+
end
|
23
|
+
|
24
|
+
# API
|
25
|
+
# add more?
|
26
|
+
%i(get post).each do |http_method|
|
27
|
+
define_method http_method do |name, path|
|
28
|
+
@suite << ApiEndpointTest.new(@prefix, name, path, headers: headers.dup)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def lazy_api_endpoints(&block)
|
33
|
+
@suite.lazy_tests << LazyTest.new(@prefix, @headers.dup, block)
|
18
34
|
end
|
19
35
|
|
20
36
|
# Adds a given test from config to the suite
|
21
37
|
def method_missing(name, *args)
|
22
|
-
@
|
38
|
+
@suite << PageTest.new(@prefix, name.to_s, args.first, url_params: @url_params)
|
23
39
|
end
|
24
40
|
end
|
25
|
-
end
|
41
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Kontrast
|
2
|
+
class TestSuite
|
3
|
+
attr_reader :tests, :lazy_tests
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@tests = []
|
7
|
+
@lazy_tests = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(test)
|
11
|
+
if(!test.is_a?(Test))
|
12
|
+
raise TestSuiteException.new("Cannot add a #{test.class} to the test suite.")
|
13
|
+
end
|
14
|
+
@tests << test
|
15
|
+
end
|
16
|
+
|
17
|
+
# Binds specs to tests automatically by matching the test's name to the spec's name
|
18
|
+
def bind_specs
|
19
|
+
specs = Kontrast.get_spec_builder.specs
|
20
|
+
specs.each do |spec|
|
21
|
+
matched_tests = @tests.select { |t| t.to_s.include?(spec.name) }
|
22
|
+
matched_tests.each { |t| t.bind_spec(spec) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# For rspec
|
27
|
+
def to_h
|
28
|
+
suite_hash = Hash.new
|
29
|
+
|
30
|
+
@tests.each do |test|
|
31
|
+
suite_hash[test.width] ||= {}
|
32
|
+
suite_hash[test.width][test.name] = test.path
|
33
|
+
end
|
34
|
+
|
35
|
+
return suite_hash
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear!
|
39
|
+
@tests = []
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Kontrast
|
2
|
+
module ThumbnailCreator
|
3
|
+
|
4
|
+
def create_thumbnails(test, image_names)
|
5
|
+
# Load images
|
6
|
+
images = image_names.map do |image|
|
7
|
+
Magick::Image.read(File.join(Kontrast.path, test.to_s, image)).first
|
8
|
+
end
|
9
|
+
|
10
|
+
# Crop images
|
11
|
+
Workers.map(images) do |image|
|
12
|
+
filename = image.filename.split('/').last.split('.').first + "_thumb"
|
13
|
+
full_path = "#{Kontrast.path}/#{test}/#{filename}.png"
|
14
|
+
image.resize_to_fill(200, 200, Magick::NorthGravity).write(full_path)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/kontrast/version.rb
CHANGED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Kontrast::ApiEndpointComparator do
|
4
|
+
let(:app_id) { '123' }
|
5
|
+
let(:app_secret) { '123' }
|
6
|
+
let(:comparator) { Kontrast::ApiEndpointComparator.new }
|
7
|
+
let(:access_token_response) { {'token' => {'access_token' => '123'}} }
|
8
|
+
let(:test_client_connection) {
|
9
|
+
Faraday.new do |builder|
|
10
|
+
builder.adapter :test do |stub|
|
11
|
+
stub.post('/api/v2/oauth/token') { |env| [ 200, {}, access_token_response.to_json ]}
|
12
|
+
stub.get('/api/v2/screen/home') { |env| [ 200, {}, {'foo' => 'bar'}.to_json ]}
|
13
|
+
stub.get('/api/v2/screen/products') { |env| [ 200, {}, {}.to_json ]}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
}
|
17
|
+
let(:fake_test) { double('test', to_s: 'fake_test', headers: {}, path: '/screen/home', name: 'home') }
|
18
|
+
|
19
|
+
let(:prod_client_connection) {
|
20
|
+
Faraday.new do |builder|
|
21
|
+
builder.adapter :test do |stub|
|
22
|
+
stub.post('/api/v2/oauth/token') { |env| [ 200, {}, access_token_response.to_json ]}
|
23
|
+
stub.get('/api/v2/screen/home') { |env| [ 200, {}, {'foo' => 'bar'}.to_json ]}
|
24
|
+
stub.get('/api/v2/screen/products') { |env| [ 200, {}, {}.to_json ]}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
}
|
28
|
+
|
29
|
+
before do
|
30
|
+
Kontrast.configure {}
|
31
|
+
Kontrast.configuration.test_oauth_app_proc = proc { double('app', uid: '123', secret: 'abc') }
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'text data' do
|
35
|
+
context 'with similar data' do
|
36
|
+
it "returns true" do
|
37
|
+
test_home_response = {'foo' => 'bar', 'nested' => {'data' => {'should' => 'work'}}}
|
38
|
+
allow(comparator.test_client).to receive(:fetch_token)
|
39
|
+
allow(comparator.test_client).to receive(:fetch).with('/screen/home', anything)
|
40
|
+
allow(comparator.test_client.responses).to receive(:[]).with('/screen/home').and_return(test_home_response)
|
41
|
+
|
42
|
+
prod_home_response = {'foo' => 'bar', 'nested' => {'data' => {'should' => 'work'}}}
|
43
|
+
allow(comparator.prod_client).to receive(:fetch_token)
|
44
|
+
allow(comparator.prod_client).to receive(:fetch).with('/screen/home', anything)
|
45
|
+
allow(comparator.prod_client.responses).to receive(:[]).with('/screen/home').and_return(prod_home_response)
|
46
|
+
|
47
|
+
expect(comparator.diff(fake_test)).to eq({images: []})
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'with different data' do
|
52
|
+
it "returns the diff" do
|
53
|
+
test_home_response = {'foo' => 'bar'}
|
54
|
+
allow(comparator.test_client).to receive(:fetch_token)
|
55
|
+
allow(comparator.test_client).to receive(:fetch).with('/screen/home', anything)
|
56
|
+
allow(comparator.test_client.responses).to receive(:[]).with('/screen/home').and_return(test_home_response)
|
57
|
+
|
58
|
+
prod_home_response = {'foo' => 'baz'}
|
59
|
+
allow(comparator.prod_client).to receive(:fetch_token)
|
60
|
+
allow(comparator.prod_client).to receive(:fetch).with('/screen/home', anything).and_return(prod_home_response)
|
61
|
+
allow(comparator.prod_client.responses).to receive(:[]).with('/screen/home').and_return(prod_home_response)
|
62
|
+
|
63
|
+
expect(comparator.diff(fake_test)).to eq({type: 'api_endpoint', images: [], name: 'home', diff: 1})
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'images' do
|
69
|
+
let(:test_home_response) {
|
70
|
+
{
|
71
|
+
'foo' => 'bar',
|
72
|
+
'nested' => {
|
73
|
+
'data' => {
|
74
|
+
'should' => 'work',
|
75
|
+
'image' => image1.path,
|
76
|
+
}
|
77
|
+
},
|
78
|
+
'array1' => [1, 2, 'foo', {'a' => 'b'}, image1.path],
|
79
|
+
'array2' => [1,2,3],
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
let(:prod_home_response) {
|
84
|
+
{
|
85
|
+
'foo' => 'bar',
|
86
|
+
'nested' => {
|
87
|
+
'data' => {
|
88
|
+
'should' => 'work',
|
89
|
+
'image' => image2.path,
|
90
|
+
}
|
91
|
+
},
|
92
|
+
'array1' => [1, 2, 'foo', {'a' => 'b'}, image2.path],
|
93
|
+
'array2' => [1,2,3],
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
before do
|
98
|
+
allow(comparator.test_client).to receive(:fetch_token)
|
99
|
+
allow(comparator.test_client).to receive(:fetch).with('/screen/home', anything)
|
100
|
+
allow(comparator.test_client.responses).to receive(:[]).with('/screen/home').and_return(test_home_response)
|
101
|
+
|
102
|
+
allow(comparator.prod_client).to receive(:fetch_token)
|
103
|
+
allow(comparator.prod_client).to receive(:fetch).with('/screen/home', anything)
|
104
|
+
allow(comparator.prod_client.responses).to receive(:[]).with('/screen/home').and_return(prod_home_response)
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'with similar data' do
|
108
|
+
let(:image1) { File.new(File.expand_path("./spec/support/fixtures/image.jpg")) }
|
109
|
+
let(:image2) { File.new(File.expand_path("./spec/support/fixtures/image_clone.jpg")) }
|
110
|
+
|
111
|
+
it "returns true" do
|
112
|
+
expect(comparator.diff(fake_test)).to eq({images: []})
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'with different data' do
|
117
|
+
let(:image1) { File.new(File.expand_path("./spec/support/fixtures/image.jpg")) }
|
118
|
+
let(:image2) { File.new(File.expand_path("./spec/support/fixtures/other_image.jpg")) }
|
119
|
+
|
120
|
+
it "returns false" do
|
121
|
+
expect(comparator.diff(fake_test)).to eq({type: 'api_endpoint', images: [{:index=>0}, {:index=>1}], name: 'home', diff: 1})
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -50,4 +50,23 @@ describe Kontrast::Configuration do
|
|
50
50
|
gallery: "arg2"
|
51
51
|
})
|
52
52
|
end
|
53
|
+
|
54
|
+
context "workers pool size" do
|
55
|
+
before do
|
56
|
+
Kontrast.configuration = nil
|
57
|
+
Kontrast.configure {}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "defaults to 5" do
|
61
|
+
expect(Kontrast.configuration.workers_pool_size).to eq(5)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "can be overriden" do
|
65
|
+
Kontrast.configure do |config|
|
66
|
+
config.workers_pool_size = 10
|
67
|
+
end
|
68
|
+
|
69
|
+
expect(Kontrast.configuration.workers_pool_size).to eq(10)
|
70
|
+
end
|
71
|
+
end
|
53
72
|
end
|