kontrast 0.2.1

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.
@@ -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