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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +135 -96
  3. data/bin/kontrast +9 -5
  4. data/lib/kontrast.rb +17 -5
  5. data/lib/kontrast/api_client.rb +56 -0
  6. data/lib/kontrast/api_endpoint_comparator.rb +126 -0
  7. data/lib/kontrast/api_endpoint_runner.rb +99 -0
  8. data/lib/kontrast/api_endpoint_test.rb +8 -0
  9. data/lib/kontrast/configuration.rb +36 -7
  10. data/lib/kontrast/exceptions.rb +2 -1
  11. data/lib/kontrast/gallery/template.erb +152 -21
  12. data/lib/kontrast/gallery_creator.rb +105 -47
  13. data/lib/kontrast/global_runner.rb +121 -0
  14. data/lib/kontrast/image_helper.rb +63 -0
  15. data/lib/kontrast/image_uploader.rb +18 -0
  16. data/lib/kontrast/page_comparator.rb +46 -0
  17. data/lib/kontrast/page_runner.rb +95 -0
  18. data/lib/kontrast/page_test.rb +32 -0
  19. data/lib/kontrast/selenium_handler.rb +18 -7
  20. data/lib/kontrast/spec.rb +21 -0
  21. data/lib/kontrast/spec_builder.rb +54 -0
  22. data/lib/kontrast/test.rb +27 -0
  23. data/lib/kontrast/test_builder.rb +25 -9
  24. data/lib/kontrast/test_suite.rb +42 -0
  25. data/lib/kontrast/thumbnail_creator.rb +18 -0
  26. data/lib/kontrast/version.rb +1 -1
  27. data/spec/api_endpoint_comparator_spec.rb +125 -0
  28. data/spec/configuration_spec.rb +19 -0
  29. data/spec/gallery_creator_spec.rb +26 -20
  30. data/spec/{image_handler_spec.rb → global_runner_spec.rb} +17 -12
  31. data/spec/page_comparator_spec.rb +31 -0
  32. data/spec/page_runner_spec.rb +45 -0
  33. data/spec/spec_builder_spec.rb +32 -0
  34. data/spec/support/fixtures/image.jpg +0 -0
  35. data/spec/support/fixtures/image_clone.jpg +0 -0
  36. data/spec/support/fixtures/img1.jpg +0 -0
  37. data/spec/support/fixtures/img2.jpg +0 -0
  38. data/spec/support/fixtures/other_image.jpg +0 -0
  39. data/spec/test_builder_spec.rb +6 -3
  40. data/spec/test_spec.rb +53 -0
  41. data/spec/test_suite_spec.rb +56 -0
  42. metadata +91 -30
  43. data/lib/kontrast/image_handler.rb +0 -119
  44. data/lib/kontrast/runner.rb +0 -141
  45. 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(width, path, name)
34
+ def run_comparison(test)
35
35
  # Create folder for this test
36
- current_output = FileUtils.mkdir_p("#{@path}/#{width}_#{name}").join('')
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
- Kontrast.configuration.before_screenshot(@test_driver[:driver], @production_driver[:driver], { width: width, name: name })
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
- Kontrast.configuration.after_screenshot(@test_driver[:driver], @production_driver[:driver], { width: width, name: name })
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
- def initialize
4
- @tests = Hash.new
5
- end
6
+ attr_reader :suite
7
+ attr_accessor :prefix, :headers, :url_params
6
8
 
7
- def add_width(width)
8
- @tests[width] = Hash.new
9
- @current_width = width
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
- @tests[@current_width][name.to_s] = args.first
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
@@ -1,3 +1,3 @@
1
1
  module Kontrast
2
- VERSION = "0.2.1"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -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
@@ -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