kontrast 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,141 @@
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
@@ -0,0 +1,84 @@
1
+ require "selenium-webdriver"
2
+ require "workers"
3
+
4
+ module Kontrast
5
+ class SeleniumHandler
6
+ def initialize
7
+ @path = Kontrast.path
8
+
9
+ # Configure profile
10
+ driver_name = Kontrast.configuration.browser_driver
11
+ profile = Selenium::WebDriver.const_get(driver_name.capitalize)::Profile.new
12
+ Kontrast.configuration.browser_profile.each do |option, value|
13
+ profile[option] = value
14
+ end
15
+
16
+ # Get drivers with profile
17
+ @test_driver = {
18
+ name: "test",
19
+ driver: Selenium::WebDriver.for(driver_name.to_sym, profile: profile)
20
+ }
21
+ @production_driver = {
22
+ name: "production",
23
+ driver: Selenium::WebDriver.for(driver_name.to_sym, profile: profile)
24
+ }
25
+ end
26
+
27
+ def cleanup
28
+ # Make sure windows are closed
29
+ Workers.map([@test_driver, @production_driver]) do |driver|
30
+ driver[:driver].quit
31
+ end
32
+ end
33
+
34
+ def run_comparison(width, path, name)
35
+ # Create folder for this test
36
+ current_output = FileUtils.mkdir_p("#{@path}/#{width}_#{name}").join('')
37
+
38
+ # Open test host tabs
39
+ navigate(path)
40
+
41
+ # Resize to given width and total height
42
+ resize(width)
43
+
44
+ # Take screenshot
45
+ begin
46
+ Kontrast.configuration.before_screenshot(@test_driver[:driver], @production_driver[:driver], { width: width, name: name })
47
+ screenshot(current_output)
48
+ ensure
49
+ Kontrast.configuration.after_screenshot(@test_driver[:driver], @production_driver[:driver], { width: width, name: name })
50
+ end
51
+ end
52
+
53
+ private
54
+ def navigate(path)
55
+ # Get domains
56
+ test_host = Kontrast.configuration.test_domain
57
+ production_host = Kontrast.configuration.production_domain
58
+
59
+ Workers.map([@test_driver, @production_driver]) do |driver|
60
+ if driver[:name] == "test"
61
+ driver[:driver].navigate.to("#{test_host}#{path}")
62
+ elsif driver[:name] == "production"
63
+ driver[:driver].navigate.to("#{production_host}#{path}")
64
+ end
65
+ end
66
+ end
67
+
68
+ def resize(width)
69
+ Workers.map([@test_driver, @production_driver]) do |driver|
70
+ driver[:driver].manage.window.resize_to(width, driver[:driver].manage.window.size.height)
71
+ end
72
+ end
73
+
74
+ def screenshot(output_path)
75
+ Workers.map([@test_driver, @production_driver]) do |driver|
76
+ if driver[:name] == "test"
77
+ driver[:driver].save_screenshot("#{output_path}/test.png")
78
+ elsif driver[:name] == "production"
79
+ driver[:driver].save_screenshot("#{output_path}/production.png")
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,25 @@
1
+ module Kontrast
2
+ class TestBuilder
3
+ def initialize
4
+ @tests = Hash.new
5
+ end
6
+
7
+ def add_width(width)
8
+ @tests[width] = Hash.new
9
+ @current_width = width
10
+ end
11
+
12
+ # Needed in case someone tries to name a test "tests"
13
+ def tests(param = nil)
14
+ if param
15
+ raise ConfigurationException.new("'tests' is not a valid name for a test.")
16
+ end
17
+ return @tests
18
+ end
19
+
20
+ # Adds a given test from config to the suite
21
+ def method_missing(name, *args)
22
+ @tests[@current_width][name.to_s] = args.first
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Kontrast
2
+ VERSION = "0.2.1"
3
+ end
@@ -0,0 +1,53 @@
1
+ describe Kontrast::Configuration do
2
+ it "can set basic options" do
3
+ Kontrast.configure do |config|
4
+ config.test_domain = "http://google.com"
5
+ end
6
+ expect(Kontrast.configuration.test_domain).to eql "http://google.com"
7
+ end
8
+
9
+ it "cannot set options that don't exist" do
10
+ expect {
11
+ Kontrast.configure do |config|
12
+ config.foo = "bar"
13
+ end
14
+ }.to raise_error(NoMethodError)
15
+ end
16
+
17
+ it "sets defaults" do
18
+ # Set up a config block with no options
19
+ Kontrast.configure do |config|
20
+ end
21
+
22
+ # Check that we have some defaults
23
+ expect(Kontrast.configuration.browser_driver).to eql "firefox"
24
+ expect(Kontrast.configuration.run_parallel).to eql false
25
+ expect(Kontrast.configuration.distortion_metric).to eql "MeanAbsoluteErrorMetric"
26
+ end
27
+
28
+ it "sets up and runs blocks" do
29
+ Kontrast.configure do |config|
30
+ config.before_run do
31
+ x = 1
32
+ x += 1
33
+ # Implicit return of x
34
+ end
35
+ end
36
+ expect(Kontrast.configuration.before_run).to eql 2
37
+ end
38
+
39
+ it "passes data to some blocks" do
40
+ Kontrast.configure do |config|
41
+ config.after_gallery do |diffs, gallery|
42
+ {
43
+ diffs: diffs,
44
+ gallery: gallery
45
+ }
46
+ end
47
+ end
48
+ expect(Kontrast.configuration.after_gallery("arg1", "arg2")).to eql({
49
+ diffs: "arg1",
50
+ gallery: "arg2"
51
+ })
52
+ end
53
+ end
@@ -0,0 +1,105 @@
1
+ describe Kontrast::GalleryCreator do
2
+ before :all do
3
+ Kontrast.configure do |config|
4
+ end
5
+ end
6
+
7
+ before :each do
8
+ @image_handler = Kontrast::ImageHandler.new
9
+ @gallery_creator = Kontrast::GalleryCreator.new(@image_handler.path)
10
+ end
11
+
12
+ it "can parse manifests" do
13
+ # Create fake manifests
14
+ manifest_1 = {
15
+ files: ["1280_home/diff.png", "1280_home/diff_thumb.png", "1280_home/production.png", "1280_home/production_thumb.png", "1280_home/test.png", "1280_home/test_thumb.png"],
16
+ diffs: {
17
+ "1280_home" => {
18
+ width: 1280,
19
+ name: "home",
20
+ diff: 0.1337
21
+ }
22
+ }
23
+ }
24
+
25
+ manifest_2 = {
26
+ files: ["320_home/production.png", "320_home/production_thumb.png", "320_home/test.png", "320_home/test_thumb.png", "320_home/diff.png", "320_home/diff_thumb.png"],
27
+ diffs: {}
28
+ }
29
+
30
+ File.open("#{@image_handler.path}/manifest_0.json", 'w') do |outf|
31
+ outf.write(manifest_1.to_json)
32
+ end
33
+ File.open("#{@image_handler.path}/manifest_1.json", 'w') do |outf|
34
+ outf.write(manifest_2.to_json)
35
+ end
36
+
37
+ # Test get_manifests while we're at it
38
+ manifests = @gallery_creator.get_manifests
39
+ expect(manifests).to include(@image_handler.path + "/manifest_0.json", @image_handler.path + "/manifest_1.json")
40
+
41
+ # Parse
42
+ parsed_manifests = @gallery_creator.parse_manifests(manifests)
43
+
44
+ expect(parsed_manifests[:diffs]).to eql({
45
+ "1280_home" => {
46
+ "width" => 1280,
47
+ "name" => "home",
48
+ "diff" => 0.1337
49
+ }
50
+ })
51
+
52
+ expect(parsed_manifests[:files]).to include("1280_home/diff.png",
53
+ "1280_home/diff_thumb.png",
54
+ "1280_home/production.png",
55
+ "1280_home/production_thumb.png",
56
+ "1280_home/test.png",
57
+ "1280_home/test_thumb.png",
58
+ "320_home/production.png",
59
+ "320_home/production_thumb.png",
60
+ "320_home/test.png",
61
+ "320_home/test_thumb.png",
62
+ "320_home/diff.png",
63
+ "320_home/diff_thumb.png")
64
+ end
65
+
66
+ it "can create the gallery hash" do
67
+ files = ["1280_home/diff.png",
68
+ "1280_home/diff_thumb.png",
69
+ "1280_home/production.png",
70
+ "1280_home/production_thumb.png",
71
+ "1280_home/test.png",
72
+ "1280_home/test_thumb.png",
73
+ "320_home/production.png",
74
+ "320_home/production_thumb.png",
75
+ "320_home/test.png",
76
+ "320_home/test_thumb.png",
77
+ "320_home/diff.png",
78
+ "320_home/diff_thumb.png"]
79
+ diffs = {
80
+ "1280_home" => {
81
+ "width" => 1280,
82
+ "name" => "home",
83
+ "diff" => 0.1337
84
+ }
85
+ }
86
+
87
+ dirs = @gallery_creator.parse_directories(files, diffs)
88
+ expect(dirs).to eql({
89
+ "1280" => {
90
+ "home" => {
91
+ :variants => [{:image=>"../1280_home/test.png", :thumb=>"../1280_home/test_thumb.png", :domain=>"test"},
92
+ {:image=>"../1280_home/production.png", :thumb=>"../1280_home/production_thumb.png", :domain=>"production"},
93
+ {:image=>"../1280_home/diff.png", :thumb=>"../1280_home/diff_thumb.png", :domain=>"diff", :diff_amt=>0.1337}]
94
+ }
95
+ },
96
+ "320" => {
97
+ "home" => {
98
+ :variants => [{:image=>"../320_home/test.png", :thumb=>"../320_home/test_thumb.png", :domain=>"test"},
99
+ {:image=>"../320_home/production.png", :thumb=>"../320_home/production_thumb.png", :domain=>"production"},
100
+ {:image=>"../320_home/diff.png", :thumb=>"../320_home/diff_thumb.png", :domain=>"diff", :diff_amt=>0}]
101
+ }
102
+ }
103
+ })
104
+ end
105
+ end
@@ -0,0 +1,52 @@
1
+ describe Kontrast::ImageHandler 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
+ @image_handler = Kontrast::ImageHandler.new
18
+ end
19
+
20
+ it "can create a manifest for the current node" do
21
+ # Create some files
22
+ Kontrast.test_suite.tests.each do |size, tests|
23
+ tests.each do |name, path|
24
+ test_name = "#{size}_#{name}"
25
+ path = FileUtils.mkdir_p(@image_handler.path + "/#{test_name}").join('')
26
+ Dir.chdir(path)
27
+ FileUtils.touch("diff_thumb.png")
28
+ end
29
+ end
30
+
31
+ # Create a diff
32
+ @image_handler.diffs["1280_home"] = {
33
+ width: 1280,
34
+ name: "home",
35
+ diff: 0.1337
36
+ }
37
+
38
+ # Create the manifest
39
+ contents = @image_handler.create_manifest(0)
40
+
41
+ # Expectations
42
+ expect(contents[:diffs]).to eql({
43
+ "1280_home" => {
44
+ :width => 1280,
45
+ :name=> "home" ,
46
+ :diff => 0.1337
47
+ }
48
+ })
49
+
50
+ expect(contents[:files]).to include("1280_home/diff_thumb.png", "1280_products/diff_thumb.png", "320_home/diff_thumb.png", "320_products/diff_thumb.png")
51
+ end
52
+ end