kontrast 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ccae0e8fea2906d0f534b11feee35bd3690fc4d2
4
+ data.tar.gz: 0d94b6a934294b8ffb3e480abceb09f5263e637d
5
+ SHA512:
6
+ metadata.gz: b36e5e0f5d4edf9945f7fe6183b3a8accb2ddcbf1bbe29ded50ce0f236c7a9735e4e34e72ba0addfeaec46e3ec807f38753920401bcfaaa3be53f660133e8d35
7
+ data.tar.gz: a011155d29e1817de408fb029941856721be1fc05f2e02e1f0afdffe7b25def4b7bff43d92687f5c8952044d18a7fa4f67272037c399540f9b6def91d99c892a
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ilya Rubnich
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # Kontrast
2
+
3
+ An automated testing tool for comparing visual differences between two versions of a website.
4
+
5
+ Kontrast lets you build a test suite to run against your test and production websites. It uses [Selenium](http://www.seleniumhq.org/) to take screenshots and [ImageMagick](http://www.imagemagick.org/) to compare them. Kontrast then produces a detailed gallery of its test results.
6
+
7
+ ## Prerequisites
8
+
9
+ 1. Install ImageMagick. You can do this on OS X via brew with:
10
+
11
+ $ brew install imagemagick
12
+
13
+ 2. Make sure you have Firefox or a different Selenium-compatible browser installed. By default, Firefox is used.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ gem 'kontrast'
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install kontrast
28
+
29
+ Lastly, generate the config file:
30
+
31
+ $ kontrast generate_config
32
+
33
+ If you're in Rails, the config file will be generated in `config/initializers/kontrast.rb`.
34
+ Otherwise, the config file will be generated in your current directory.
35
+
36
+ ## Basic Configuration
37
+
38
+ Here's all the config you need to get started:
39
+
40
+ Kontrast.configure do |config|
41
+ # Set your test and production domains
42
+ config.test_domain = "http://localhost:3000"
43
+ config.production_domain = "http://www.example.com"
44
+
45
+ # Build your test suite
46
+ # These pages will open in a 1280px-wide browser
47
+ config.pages(1280) do |page|
48
+ page.home "/"
49
+ page.about "/about"
50
+ end
51
+
52
+ # These pages will open in a 320px-wide browser
53
+ config.pages(320) do |page|
54
+ page.home "/"
55
+ page.about "/about"
56
+ end
57
+ end
58
+
59
+ ## Basic Usage
60
+ Run Kontrast (use `bundle exec` and omit the --config flag if you're within a Rails app):
61
+
62
+ $ kontrast local_run --config ./kontrast_config.rb
63
+ ...
64
+ ...
65
+ ...
66
+ Kontrast is all done!
67
+ You can find the gallery at: /tmp/shots/1410309651/gallery/gallery.html
68
+
69
+ Review the gallery in your Favorite Browser:
70
+
71
+ $ open /tmp/shots/1410309651/gallery/gallery.html
72
+
73
+ ## Parallelized Usage
74
+ We designed Kontrast from the very beginning to work with multiple nodes. At Harry's, we use CircleCI for testing and Kontrast works perfectly with CircleCI's multi-container features.
75
+
76
+ ### Method of Action
77
+
78
+ Because we ultimately need to generate a gallery with all test results from all given nodes, Kontrast uploads the test images it creates plus a per-node manifest file to S3. After all the tests have run, a single node downloads the manifest files and parses them to create a single gallery.
79
+
80
+ Here's how to get set up:
81
+
82
+ ### 1. Enable Parallelization
83
+
84
+ config.run_parallel = true
85
+
86
+ ### 2. Configure Nodes
87
+ Set how many nodes you have in total and the zero-based index of the current node. Kontrast will automatically split up tests among these nodes.
88
+
89
+ config.total_nodes = 6
90
+ config.current_node = 2
91
+
92
+ ### 3. Configure Remote Options
93
+ Set your S3 details:
94
+
95
+ config.aws_bucket = "kontrast-test-results"
96
+ config.aws_key = ENV['AWS_KEY']
97
+ config.aws_secret = ENV['AWS_SECRET']
98
+
99
+ Set the **local** path where output images will be stored before they are uploaded to S3. This is also where the gallery will be saved on the node that runs the `make_gallery` command. This path will be created if it doesn't already exist.
100
+
101
+ config.local_path = "tmp/kontrast"
102
+
103
+ Set the **remote** path relative to your S3 bucket's root where Kontrast's output files will be uploaded to. It should be unique to every test.
104
+
105
+ config.remote_path = "artifacts.#{ENV['BUILD_NUMBER']}"
106
+
107
+ ### 4. Run the Tests
108
+ This command should run in parallel on every node. Use `bundle exec` and omit the --config flag if your app is `bundle`'d along with Rails.
109
+
110
+ $ kontrast run_tests --config /path/to/config.rb
111
+
112
+ ### 5. Create the Gallery
113
+ This command should only run on one node after all the other nodes have completed the previous command. Use `bundle exec` and omit the --config flag if your app is `bundle`'d along with Rails.
114
+
115
+ $ kontrast make_gallery --config /path/to/config.rb
116
+
117
+ ### 6. Review Your Results
118
+ At this point, the gallery should be saved to `config.local_path` and uploaded to `config.remote_path`. Check it out in your Favorite Browser.
119
+
120
+ ### Sample circle.yml
121
+ Here's an example of how to run Kontrast within a Rails app using CircleCI:
122
+
123
+ test:
124
+ post:
125
+ - bundle exec rails server:
126
+ background: true
127
+ parallel: true
128
+ - bundle exec kontrast run_tests:
129
+ parallel: true
130
+ - bundle exec kontrast make_gallery
131
+
132
+ ## Advanced Configuration
133
+
134
+ ### Test Suite
135
+
136
+ #### fail_build
137
+ If you want Kontrast to exit with an error code if it finds any diffs, use this option:
138
+
139
+ config.fail_build = true
140
+
141
+ ### Selenium Driver
142
+ #### browser_driver
143
+ Choose which Selenium driver you'd like to use. Kontrast has only been tested on the default Firefox driver but we would love feedback and/or pull requests for other drivers.
144
+
145
+ config.browser_driver = "firefox"
146
+
147
+ #### browser_profile
148
+ You may set a driver's profile options in this hash.
149
+
150
+ config.browser_profile = {
151
+ "general.useragent.override" => "Some Cool Kontrast User Agent",
152
+ "image.animation_mode" => "none"
153
+ }
154
+
155
+ ### Image Comparisons
156
+ #### distortion_metric
157
+ See [http://www.imagemagick.org/RMagick/doc/constants.html#MetricType]() for available values.
158
+
159
+ config.distortion_metric = "MeanAbsoluteErrorMetric"
160
+
161
+ #### highlight_color
162
+ The ImageMagick comparison tool emphasizes differences with this color.
163
+ Valid options are an RMagick color name or pixel.
164
+
165
+ config.highlight_color = "blue"
166
+
167
+ #### lowlight_color
168
+ The ImageMagick comparison tool deemphasizes differences with this color.
169
+ Valid options are an RMagick color name or pixel.
170
+
171
+ config.lowlight_color = "rgba(255, 255, 255, 0.3)"
172
+
173
+ ### Hooks
174
+ To make Kontrast even more powerful, we provided a set of hooks that you can use in your configuration.
175
+
176
+ #### before_run
177
+ Runs before the entire suite.
178
+
179
+ config.before_run do
180
+ WebMock.disable!
181
+ end
182
+
183
+ #### after_run
184
+ Runs after the entire suite.
185
+
186
+ config.after_run do
187
+ WebMock.enable!
188
+ end
189
+
190
+ #### before_gallery
191
+ Runs before the gallery creation step.
192
+
193
+ config.before_gallery do
194
+ WebMock.disable!
195
+ end
196
+
197
+ #### after_gallery
198
+ Runs after the gallery creation step.
199
+
200
+ config.after_gallery do |diffs, gallery_path|
201
+ # diffs is a hash containing all the differences that Kontrast found in your test suite
202
+ # gallery_path is where Kontrast saved the gallery
203
+ end
204
+
205
+ #### before_screenshot
206
+ Runs on every test before Selenium takes a screenshot.
207
+
208
+ config.before_screenshot do |test_driver, production_driver, test_info|
209
+ # test_driver and production_driver are instances of Selenium::WebDriver that you can control
210
+ # test_info is a hash with the current test's name and width
211
+ end
212
+
213
+ #### after_screenshot
214
+ Runs on every test after Selenium takes a screenshot.
215
+
216
+ config.after_screenshot do |test_driver, production_driver, test_info|
217
+ # same variables are available as with before_screenshot
218
+ end
219
+
220
+ ## Customizing Kontrast
221
+ Kontrast's hooks allow you to insert custom functionality into many parts of the test suite. Here are some examples of how we use hooks at Harry's:
222
+
223
+ ### Integrating with HipChat
224
+ Once a build finishes, we let HipChat know if Kontrast found any diffs using the `hipchat` gem:
225
+
226
+ config.after_gallery do |diffs, gallery_path|
227
+ hipchat_room = "Kontrast Results"
228
+ hipchat_user = "KontrastBot"
229
+
230
+ if !diffs.empty?
231
+ msg = "Kontrast Diffs: #{diffs.keys.join(', ')}. Don't push to production without reviewing these. You can find the gallery at #{gallery_path}."
232
+ client = HipChat::Client.new(ENV["HIPCHAT_TOKEN"])
233
+ client[hipchat_room].send(hipchat_user, msg, :color => "red")
234
+ end
235
+ end
236
+
237
+ ### Setting Cookies
238
+ Testing our cart page required a bit more setup before we could take a screenshot of it:
239
+
240
+ config.before_screenshot do |test_driver, production_driver, test|
241
+ if test[:name] == "cart"
242
+ # prepare our cookie value
243
+ cookie_value = super_secret_magic_cart_cookie
244
+
245
+ # write cookies using Mootools
246
+ # http://mootools.net/docs/core/Utilities/Cookie
247
+ test_driver.execute_script("Cookie.write('cart', '#{cookie_value}');")
248
+ production_driver.execute_script("Cookie.write('cart', '#{cookie_value}');")
249
+
250
+ # refresh the page
251
+ test_driver.navigate.refresh
252
+ production_driver.navigate.refresh
253
+ end
254
+ end
255
+
256
+ ## Contributing
257
+
258
+ 1. Fork it
259
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
260
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
261
+ 4. Push to the branch (`git push origin my-new-feature`)
262
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/kontrast ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #######################################
4
+ # This is the entry point to Kontrast #
5
+ #######################################
6
+
7
+ begin
8
+ require "rubygems"
9
+ require "kontrast"
10
+ rescue LoadError => e
11
+ puts "Could not load Kontrast."
12
+ raise e
13
+ end
14
+ require "thor"
15
+
16
+ module Kontrast
17
+ class CLI < Thor
18
+ class_option :config
19
+
20
+ desc "run_tests", "Run Kontrast test suite"
21
+ def run_tests
22
+ load_config(options[:config])
23
+ Kontrast.run
24
+ end
25
+
26
+ desc "make_gallery --result-path PATH", "Create gallery given an optional local path of test results"
27
+ option :result_path
28
+ def make_gallery
29
+ load_config(options[:config])
30
+
31
+ # We're only allowed to give no path in the remote case
32
+ if options[:result_path].nil? && !Kontrast.configuration.run_parallel
33
+ raise GalleryException.new("You can't omit --result-path when running in local mode")
34
+ end
35
+
36
+ result = Kontrast.make_gallery(options[:result_path])
37
+ abort if !result && Kontrast.configuration.fail_build
38
+ end
39
+
40
+ # todo: option to specify an output path
41
+ desc "local_run", "Run Kontrast locally"
42
+ def local_run
43
+ load_config(options[:config])
44
+
45
+ # Make sure config run_parallel is set to false
46
+ if Kontrast.configuration.run_parallel
47
+ raise ConfigurationException.new("You can't run in parallel locally")
48
+ end
49
+
50
+ # Run Kontrast
51
+ Kontrast.run
52
+ result = Kontrast.make_gallery(Kontrast.path)
53
+
54
+ # Print the gallery path
55
+ puts "Kontrast is all done!"
56
+ puts "You can find the gallery at: #{Kontrast.path}/gallery/gallery.html"
57
+
58
+ # Quit with an error code if appropriate
59
+ abort if !result && Kontrast.configuration.fail_build
60
+ end
61
+
62
+ desc "generate_config", "Generate a Kontrast configuration file"
63
+ def generate_config
64
+ template = File.read(Kontrast.root + '/lib/kontrast/config/template.rb')
65
+
66
+ if Kontrast.in_rails?
67
+ target_file = './config/initializers/kontrast.rb'
68
+ else
69
+ target_file = './kontrast_config.rb'
70
+ end
71
+
72
+ File.open(target_file, 'w') do |f|
73
+ f.write(template)
74
+ end
75
+
76
+ puts "Created a Kontrast config file at: #{target_file}"
77
+ end
78
+
79
+ private
80
+ def load_config(config)
81
+ # Let's check if we're within Rails.
82
+ if !Kontrast.in_rails?
83
+ begin
84
+ require config
85
+ rescue TypeError => e
86
+ raise ConfigurationException.new("Error parsing the config flag '#{config}'")
87
+ rescue LoadError => e
88
+ raise ConfigurationException.new("Could not load '#{config}'")
89
+ rescue Exception => e
90
+ raise ConfigurationException.new("An unexpected error occurred while trying to load the given config file: #{e.inspect}")
91
+ end
92
+ else
93
+ # Load Rails environment
94
+ # We will assume the config was loaded from an initializer
95
+ require './config/environment'
96
+ end
97
+
98
+ # Check that we actually got a configuration block
99
+ if !Kontrast.configuration
100
+ raise ConfigurationException.new("No configuration has been loaded")
101
+ end
102
+
103
+ # Make sure we have the bare minimum configuration to continue
104
+ Kontrast.configuration.validate
105
+
106
+ return true
107
+ end
108
+ end
109
+ end
110
+
111
+ Kontrast::CLI.start(ARGV)
data/lib/kontrast.rb ADDED
@@ -0,0 +1,97 @@
1
+ # Dependencies
2
+ require "fog"
3
+ require "bundler"
4
+
5
+ # Load classes
6
+ require "kontrast/exceptions"
7
+ require "kontrast/configuration"
8
+ require "kontrast/test_builder"
9
+ require "kontrast/selenium_handler"
10
+ require "kontrast/image_handler"
11
+ require "kontrast/gallery_creator"
12
+ require "kontrast/runner"
13
+
14
+ module Kontrast
15
+ class << self
16
+ @@path = nil
17
+
18
+ def root
19
+ File.expand_path('../..', __FILE__)
20
+ end
21
+
22
+ def in_rails?
23
+ # Logic: Rails uses Bundler, so if the Bundler environment contains Rails, return true.
24
+ # If there's any error whatsoever, return false.
25
+ begin
26
+ Bundler.environment.current_dependencies.each do |dep|
27
+ return true if dep.name == "rails"
28
+ end
29
+ rescue Exception => e
30
+ # Quietly ignore any exceptions
31
+ end
32
+ return false
33
+ end
34
+
35
+ def path
36
+ return @@path if @@path
37
+
38
+ if Kontrast.configuration.run_parallel
39
+ @@path = FileUtils.mkdir_p(Kontrast.configuration.local_path).join('')
40
+ elsif Kontrast.in_rails?
41
+ @@path = FileUtils.mkdir_p(Rails.root + "tmp/shots/#{Time.now.to_i}").join('')
42
+ else
43
+ @@path = FileUtils.mkdir_p("/tmp/shots/#{Time.now.to_i}").join('')
44
+ end
45
+
46
+ return @@path
47
+ end
48
+
49
+ def fog
50
+ return Fog::Storage.new({
51
+ :provider => 'AWS',
52
+ :aws_access_key_id => Kontrast.configuration.aws_key,
53
+ :aws_secret_access_key => Kontrast.configuration.aws_secret
54
+ })
55
+ end
56
+
57
+ def run
58
+ beginning_time = Time.now
59
+
60
+ begin
61
+ # Call "before" hook
62
+ Kontrast.configuration.before_run
63
+
64
+ runner = Runner.new
65
+ runner.run
66
+ ensure
67
+ # Call "after" hook
68
+ Kontrast.configuration.after_run
69
+ end
70
+
71
+ end_time = Time.now
72
+ puts "Time elapsed: #{(end_time - beginning_time)} seconds"
73
+ end
74
+
75
+ def make_gallery(result_path = nil)
76
+ puts "Creating gallery..."
77
+ gallery_info = {}
78
+ begin
79
+ # Call "before" hook
80
+ Kontrast.configuration.before_gallery
81
+
82
+ gallery_creator = GalleryCreator.new(result_path)
83
+ if Kontrast.configuration.run_parallel
84
+ gallery_info = gallery_creator.create_gallery(Kontrast.configuration.local_path)
85
+ else
86
+ gallery_info = gallery_creator.create_gallery(result_path)
87
+ end
88
+ ensure
89
+ # Call "after" hook
90
+ Kontrast.configuration.after_gallery(gallery_info[:diffs], gallery_info[:path])
91
+ end
92
+
93
+ # Return based on if we have diffs or not
94
+ return gallery_info[:diffs].empty?
95
+ end
96
+ end
97
+ end