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