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