goggles 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b7191925cf0b72ef1e287a2f9f5d3206c7206763
4
- data.tar.gz: af89e5f439fa552780efd31f1aea1a8f19286707
3
+ metadata.gz: 2a461694240042737a93be6b6431a638bc7add76
4
+ data.tar.gz: 5651d5328ba134942e7f9e7a5d09a162b954c247
5
5
  SHA512:
6
- metadata.gz: c4be6b93b9fac5088771f6329143d4b282e5252690929664639f44d6db4938e1204552aaaea93f80ee832abf03ff9e153ca4b043d5e1fd030be00c8cc83a6621
7
- data.tar.gz: 17fa964ae31c6a90fed3fcdf6f4bfadf46fe6f6233bf90b59429a9f06695223888f9d0470d121164c88fddd157f3debaa56a2d5bf712f8ecd5607d92e099b0af
6
+ metadata.gz: 96a924a64413611a034f06bf262ed2f0123810c5b85c2c42b6204e318a5dc5f050320fe9e3dc0482ac277d3b1d6f836483cea151ae80f14cc541363c2910f113
7
+ data.tar.gz: d9f08ed3a405f18ff049d4491a22b28d6c9e120ff1dc32e2aca34323411f469ffd9ea9bbafa1ca89d66139111cba46e5e672043544c79c2fd8320c736070bba1
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ *.png
4
+ *_data.txt
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
20
+ empty_config.yml
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.0
5
+ - 2.2.0
6
+ script: bundle exec rspec spec
@@ -0,0 +1,46 @@
1
+ # CHANGELOG
2
+
3
+ ## v0.10.0 (Apr 16, 2015)
4
+ * Throw EmptyDirectoryError on iteration with unconfigured directory setting.
5
+
6
+ ## v0.9.1 (Apr 14, 2015)
7
+ * Add YARD documentation.
8
+
9
+ ## v0.9.0 (Apr 14, 2015)
10
+ * `Goggles.each` accepts Array, Integer, String, and Symbol arguments.
11
+ * Arguments passed to `Goggles.each` extend configured settings instead of replacing them.
12
+ * Add Cucumber tests.
13
+
14
+ ## v0.8.2 (Apr 10, 2015)
15
+ * Fix another typo in the gem description.
16
+
17
+ ## v0.8.1 (Apr 10, 2015)
18
+ * Fix typo in gem description.
19
+
20
+ ## v0.8.0 (Apr 10, 2015)
21
+ * Remove `swim` executable.
22
+ * Remove YAML configuration.
23
+ * Add `Goggles.configure` for configuration through a block (like RSpec).
24
+ * Remove reserved instance variables like `@gg_url`.
25
+ * Scripts are executed via blocks passed to `Goggles.each`.
26
+ * `Goggles.each` yields a browser instance with the `#grab_screenshot` method.
27
+ * README updates.
28
+ * Add this CHANGELOG.
29
+
30
+ ## v0.1.4 (Jan 28, 2014)
31
+ * Small code enhancements with no user impact.
32
+
33
+ ## v0.1.3 (Jan 23, 2014)
34
+ * Do not delete cookies before each script.
35
+ * Make `@gg_url` variable available to scripts.
36
+
37
+ ## v0.1.2 (Jan 21, 2014)
38
+ * Add diff color configuration setting.
39
+ * Default color setting to blue.
40
+ * Prepend all instance variables with `gg_`.
41
+
42
+ ## v0.1.1 (Jan 20, 2014)
43
+ * Add command line option for generating default project
44
+
45
+ ## v0.1.0 (Jan 20, 2014)
46
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in goggles.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Johnson Denen
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.
@@ -0,0 +1,153 @@
1
+ # goggles
2
+ [![Gem Version](https://badge.fury.io/rb/goggles.png)](http://badge.fury.io/rb/goggles)
3
+ [![Build Status](https://travis-ci.org/jdenen/goggles.svg?branch=master)](https://travis-ci.org/jdenen/goggles)
4
+
5
+ Goggles is a visual testing tool inspired by [wraith](http://github.com/bbc-news/wraith) and powered by [watir-webdriver](http://github.com/watir/watir-webdriver). It compares screenshots of your web applications in different browsers at differents sizes.
6
+
7
+ ## Usage
8
+
9
+ ### Configuration
10
+
11
+ Configure Goggles with a block passed to the `Goggles.configure` method, which will yield a config object to your block for manipulation. The `directory` setting must be configured for Goggles to work. You'll also need to provide `browsers` and `sizes` that you'd like to compare, but those can be configured with the script.
12
+
13
+ The `fuzzing` and `color` attributes default to "blue" and "20%" respectively.
14
+
15
+ ```ruby
16
+ Goggles.configure do |config|
17
+ config.directory = "/path/to/my/results"
18
+ config.browsers = [:chrome, :firefox, :phantomjs]
19
+ config.sizes = [1080, 600]
20
+ config.color = "red"
21
+ end
22
+ ```
23
+
24
+ ### Scripting
25
+
26
+ Your Scripts are passed to `Goggles.each` as blocks. Goggles will iterate over the block with each combination of browser/browser size configured, and the method will yield a browser object to your script block.
27
+
28
+ ```ruby
29
+ Goggles.each do |browser|
30
+ browser.goto "http://www.google.com"
31
+ browser.text_field(id: "lst-ib").value = "Google"
32
+ end
33
+ ```
34
+
35
+ #### Browsers and sizes
36
+
37
+ You can pass additional browsers or browser sizes to `Goggles.each` as arrays. These arguments extend your configuration for this instance's script block only.
38
+
39
+ ```ruby
40
+ Goggles.configure do |c|
41
+ c.directory = "/some/directory"
42
+ c.browsers = [:chrome, :firefox]
43
+ c.sizes = [1080]
44
+ end
45
+
46
+ Goggles.each(:phantomjs, 500) do |browser|
47
+ # Script to be executed with Chrome, Firefox,
48
+ # and PhantomJS at widths of 1080 and 500.
49
+ end
50
+
51
+ Goggles.each do |browser|
52
+ # Script to be executed with Chrome and
53
+ # Firefox at a width of 1080.
54
+ end
55
+ ```
56
+
57
+ #### Screenshots
58
+
59
+ Your script blocks should include the `Watir::Browser#grab_screenshot` method, which has been patched onto the browser objects yielded to your blocks. Simply give the method a description argument and the screenshot will be saved to your configured directory.
60
+
61
+ ```ruby
62
+ Goggles.each do |browser|
63
+ browser.goto "http://www.google.com"
64
+ browser.grab_screenshot "homepage"
65
+ end
66
+ ```
67
+
68
+ #### Closing the browser
69
+
70
+ There's no need to explicitly close the browser objects in your script blocks. Goggles will handle that.
71
+
72
+ #### Results
73
+
74
+ Screenshots are saved to your configured directory. Screenshot comparison results are saved to a sub-folder based on the screenshot description and browser size. Results include a diff image and data file.
75
+
76
+ ```ruby
77
+ Goggles.configure do |c|
78
+ c.directory = "/goggles/results"
79
+ c.browsers = [:chrome, :firefox, :phantomjs]
80
+ c.sizes = [1080, 600]
81
+ end
82
+
83
+ Goggles.each do |browser|
84
+ browser.goto "http://www.google.com"
85
+ browser.grab_screenshot "google"
86
+ end
87
+ ```
88
+
89
+
90
+ ```
91
+ /goggles/results
92
+ |- google_1080_chrome.png
93
+ |- google_1080_firefox.png
94
+ |- google_1080_phantomjs.png
95
+ |- google_600_chrome.png
96
+ |- google_600_firefox.png
97
+ |- google_600_phantomjs.png
98
+ |- /google_1080
99
+ |- chrome_firefox_data.txt
100
+ |- chrome_firefox_diff.png
101
+ |- chrome_phantomjs_data.txt
102
+ |- chrome_phantomjs_diff.png
103
+ |- firefox_phantomjs_data.txt
104
+ |- firefox_phantomjs_diff.png
105
+ |- /google_600
106
+ |- chrome_firefox_data.txt
107
+ |- chrome_firefox_diff.png
108
+ |- chrome_phantomjs_data.txt
109
+ |- chrome_phantomjs_diff.png
110
+ |- firefox_phantomjs_data.txt
111
+ |- firefox_phantomjs_diff.png
112
+ ```
113
+
114
+ ## Road to 1.0.0
115
+
116
+ I've made a lot of changes recently and bumped the version up to 0.9.1. Check the [CHANGELOG](CHANGELOG.md) for more information about those changes.
117
+
118
+ ### v1.0.0
119
+
120
+ * Documentation
121
+ * Examples
122
+ * TravisCI integration for specs (but not features)
123
+
124
+ ## Installation
125
+
126
+ Install ImageMagick:
127
+
128
+ * OSX: `$ brew install imagemagick`
129
+ * Ubuntu: `$ sudo apt-get install imagemagick`
130
+ * Windows: [Download](http://www.imagemagick.org/script/binary-releases.php#windows) installer and add to your PATH.
131
+
132
+ Add this line to your application's Gemfile:
133
+
134
+ gem 'goggles'
135
+
136
+ And then execute:
137
+
138
+ $ bundle
139
+
140
+ Or install it yourself with:
141
+
142
+ $ gem install goggles
143
+
144
+ ## Contributing
145
+
146
+ 1. Fork it ( http://github.com/jdenen/goggles/fork )
147
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
148
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
149
+ 4. Push to the branch (`git push origin my-new-feature`)
150
+ 5. Create new Pull Request
151
+
152
+ ## Questions, Comments, Concerns
153
+ Find me on Twitter ([@jpdenen](http://twitter.com/jpdenen)) or write up an issue.
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :spec do
4
+ sh "bundle exec rspec spec"
5
+ end
6
+
7
+ task :feature do
8
+ sh "bundle exec cucumber"
9
+ end
@@ -0,0 +1,23 @@
1
+ Feature: Screenshot comparison
2
+ As a software professional
3
+ I want to automate the collection and comparison of screenshots on my application
4
+ So I can efficiently assess visual regression
5
+
6
+ Background:
7
+ Given I have configured Goggles with a valid directory
8
+ Given I have configured Goggles for browsers at 1080 width
9
+
10
+ Scenario: Compare screenshots in two browsers at one size
11
+ When I use "repo" to describe my screenshot of "github.com/jdenen/goggles"
12
+ Then file "repo_1080/chrome_firefox_diff.png" exists
13
+ And file "repo_1080/chrome_firefox_data.txt" exists
14
+
15
+ Scenario: Extending a script with more browsers and sizes
16
+ When I extend configuration with arguments "phantomjs, 500"
17
+ And I use "search" to describe my screenshot of "google.com"
18
+ Then file "search_1080/chrome_firefox_diff.png" exists
19
+ And file "search_1080/chrome_firefox_data.txt" exists
20
+ And file "search_1080/chrome_phantomjs_diff.png" exists
21
+ And file "search_1080/chrome_phantomjs_data.txt" exists
22
+ And file "search_1080/firefox_phantomjs_diff.png" exists
23
+ And file "search_1080/firefox_phantomjs_data.txt" exists
@@ -0,0 +1,27 @@
1
+ Given /^I have configured Goggles with a valid directory$/ do
2
+ @results = Pathname.new("./results").realdirpath.to_s
3
+ Goggles.configure { |c| c.directory = @results }
4
+ end
5
+
6
+ Given /^I have configured Goggles for browsers at (\d+) width$/ do |width|
7
+ Goggles.configure do |c|
8
+ c.browsers = [:chrome, :firefox]
9
+ c.sizes = [width]
10
+ end
11
+ end
12
+
13
+ When /^I extend configuration with arguments "(.*)"$/ do |args|
14
+ @args = args.split(/,/).map(&:strip)
15
+ end
16
+
17
+ When /^I use "(.*)" to describe my screenshot of "(.*)"$/ do |desc, site|
18
+ Goggles.each(@args) do |browser|
19
+ browser.goto "http://#{site}"
20
+ browser.grab_screenshot desc
21
+ end
22
+ end
23
+
24
+ Then /^file "(.*)" exists$/ do |file|
25
+ filepath = "#{@results}/#{file}"
26
+ File.exists? filepath
27
+ end
@@ -0,0 +1,5 @@
1
+ require "goggles"
2
+
3
+ After do
4
+ FileUtils.rm_rf @results
5
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'goggles/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "goggles"
8
+ spec.version = Goggles::VERSION
9
+ spec.authors = ["Johnson Denen"]
10
+ spec.email = ["johnson.denen@gmail.com"]
11
+ spec.summary = %q{Compare screenshots in different browsers at different sizes}
12
+ spec.description = %q{Compare screenshots in different browsers at different sizes}
13
+ spec.homepage = "http://github.com/jdenen/goggles"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.5"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "cucumber"
24
+ spec.add_development_dependency "yard"
25
+
26
+ spec.add_runtime_dependency "watir-webdriver"
27
+ spec.add_runtime_dependency "image_size"
28
+ end
@@ -0,0 +1,62 @@
1
+ require "goggles/configuration"
2
+ require "goggles/comparison"
3
+ require "goggles/iteration"
4
+
5
+ module Goggles
6
+ extend self
7
+
8
+ #
9
+ # Raise when the configuration attribute is unconfigured at runtime.
10
+ #
11
+ class EmptyDirectoryError < StandardError; end
12
+
13
+ #
14
+ # Yields the global configuration object to a block.
15
+ #
16
+ # @yield [Goggles::Configuration] global configuration
17
+ # @return [Goggles::Configuration] global configuration
18
+ #
19
+ def configure &block
20
+ configuration.tap { |conf| yield conf }
21
+ end
22
+
23
+ #
24
+ # Creates an Iteration object for each combination of browser and width derived from global
25
+ # configuration and given arguments.
26
+ #
27
+ # @param instance [Array<String,Fixnum,Symbol>, String, Fixnum, Symbol] configuration extension
28
+ # @return [Goggles::Comparison]
29
+ # @see Goggles::Iteration
30
+ #
31
+ def each *instance, &block
32
+ validate_directory_setting
33
+ args = instance.flatten.map(&:to_s)
34
+
35
+ sizes = configuration.sizes + args.grep(/\d+/).map(&:to_i)
36
+ browsers = configuration.browsers + args.grep(/[^\d+]/).map(&:to_sym)
37
+
38
+ browsers.product(sizes).each do |browser, size|
39
+ Iteration.new browser, size, configuration, &block
40
+ end
41
+
42
+ Comparison.new(configuration).tap { |comparison| comparison.make! }
43
+ end
44
+
45
+ private
46
+
47
+ #
48
+ # @api private
49
+ #
50
+ def configuration
51
+ @configuration ||= Configuration.new
52
+ end
53
+
54
+ #
55
+ # @api private
56
+ #
57
+ def validate_directory_setting
58
+ message = "Expected configured directory setting, got: #{configuration.directory}"
59
+ raise EmptyDirectoryError, message if configuration.directory.nil?
60
+ raise EmptyDirectoryError, message if configuration.directory.empty?
61
+ end
62
+ end
@@ -0,0 +1,186 @@
1
+ require "image_size"
2
+
3
+ module Goggles
4
+
5
+ #
6
+ # Generates a diff image and data file from two images.
7
+ #
8
+ # Results are saved to a new directory derived from the configured directory setting,
9
+ # a description given to `Watir::Browser.grab_screenshot`, browser size, and the
10
+ # browsers used to take the screenshots.
11
+ #
12
+ # @example Results
13
+ # Goggles.configure do |c|
14
+ # c.directory = "/path/results"
15
+ # c.browsers = [:chrome, :firefox]
16
+ # c.sizes = [100]
17
+ # end
18
+ #
19
+ # Goggles.each do |b|
20
+ # b.goto "www.google.com"
21
+ # b.grab_screenshot "search"
22
+ # end
23
+ #
24
+ # #=> /path/results
25
+ # # |- /search_100
26
+ # # |- chrome_firefox_data.txt
27
+ # # |- chrome_firefox_diff.png
28
+ #
29
+ class Comparison
30
+ attr_reader :directory, :fuzzing, :color, :groups, :results_dir
31
+
32
+ def initialize config
33
+ @directory = config.directory
34
+ @fuzzing = config.fuzzing
35
+ @color = config.color
36
+ @groups = config.groups
37
+ end
38
+
39
+ #
40
+ # Sizes and compare screenshots.
41
+ #
42
+ # @return [nil]
43
+ #
44
+ def make!
45
+ cut_to_common_size
46
+ highlight_differences
47
+ end
48
+
49
+ #
50
+ # Sizes comparable screenshots to a common height and width.
51
+ #
52
+ # @return [nil]
53
+ #
54
+ def cut_to_common_size
55
+ groups.each_with_object([]) do |group, sizes|
56
+ collection = find_comparable group
57
+
58
+ collection.each do |img|
59
+ File.open(img, 'rb'){ |file| sizes << read_size(file) }
60
+ end
61
+
62
+ cut! collection, sizes
63
+ end
64
+ end
65
+
66
+ #
67
+ # Generates diff images for comparable screenshots.
68
+ #
69
+ # @return [nil]
70
+ #
71
+ def highlight_differences
72
+ groups.each do |desc|
73
+ ensure_result_directory desc
74
+ find_comparable(desc).combination(2).to_a.each { |imgs| diff imgs[0], imgs[1] }
75
+ end
76
+ end
77
+
78
+ #
79
+ # Finds comparable screenshots for sizing and comparing.
80
+ #
81
+ # @param description [String] screenshot description
82
+ # @return [Array<String>] paths to comparable screenshots
83
+ #
84
+ def find_comparable description
85
+ Dir.glob("#{directory}/*.png").grep(/#{description}_/).sort
86
+ end
87
+
88
+ #
89
+ # Finds the smallest first element for all arrays in the given array. This is
90
+ # the screenshot width attribute. Used for resizing.
91
+ #
92
+ # @param array [Array<Array>] collection of screenshot sizes
93
+ # @return [Fixnum] smallest width
94
+ #
95
+ def find_common_width array
96
+ array.collect(&:first).sort.first
97
+ end
98
+
99
+ #
100
+ # Finds the smallest second element for all arrays in the given array. This is
101
+ # the screenshot height attribute. Used for resizing.
102
+ #
103
+ # @param array [Array<Array>] collection of screenshot sizes
104
+ # @return [Fixnum] smallest screenshot height
105
+ #
106
+ def find_common_height array
107
+ array.collect(&:last).sort.first
108
+ end
109
+
110
+ private
111
+
112
+ attr_writer :results_dir
113
+
114
+ #
115
+ # @api private
116
+ #
117
+ # Cuts comparable screenshots to smallest width by smallest height with ImageMagick.
118
+ #
119
+ # @param images [Array<String>] comparable screenshots
120
+ # @param sizes [Array<Fixnum>] sizes (width, height) of comparable screenshots
121
+ # @return [nil]
122
+ #
123
+ def cut! images, sizes
124
+ w = find_common_width sizes
125
+ h = find_common_height sizes
126
+ images.each { |img| `convert #{img} -background none -extent #{w}x#{h} #{img}` }
127
+ end
128
+
129
+ #
130
+ # @api private
131
+ #
132
+ # Generates comparison data for two screenshots with ImageMagick.
133
+ #
134
+ # @param img_one [String] first screenshot path
135
+ # @param img_two [String] second screenshot path
136
+ # @return [nil]
137
+ #
138
+ def diff img_one, img_two
139
+ b1 = diffed img_one
140
+ b2 = diffed img_two
141
+
142
+ fuzz = "#{results_dir}/#{b1}_#{b2}_diff.png"
143
+ data = "#{results_dir}/#{b1}_#{b2}_data.txt"
144
+
145
+ `compare -fuzz #{fuzzing} -metric AE -highlight-color #{color} #{img_one} #{img_two} #{fuzz} 2>#{data}`
146
+ end
147
+
148
+ #
149
+ # @api private
150
+ #
151
+ # Parses a browser from a screenshot filepath.
152
+ #
153
+ # @param img [String] screenshot path
154
+ # @return [String] browser
155
+ #
156
+ def diffed img
157
+ File.basename(img).match(/\d+_(.*)\.png/)[1]
158
+ end
159
+
160
+ #
161
+ # @api private
162
+ #
163
+ # Finds the width and height of an image.
164
+ #
165
+ # @param file [File] image
166
+ # @return [Array<Fixnum>] width and height
167
+ #
168
+ def read_size file
169
+ ImageSize.new(file.read).size
170
+ end
171
+
172
+ #
173
+ # @api private
174
+ #
175
+ # Ensures the results directory for comparable screenshots exists.
176
+ #
177
+ # @param description [String] screenshot description
178
+ # @return [nil]
179
+ #
180
+ def ensure_result_directory description
181
+ self.results_dir = "#{directory}/#{description}"
182
+ FileUtils.rm_rf results_dir
183
+ FileUtils.mkdir_p results_dir
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,37 @@
1
+ module Goggles
2
+
3
+ #
4
+ # Stores configuration information for runtime.
5
+ #
6
+ # The `directory` setting +must+ be configured.
7
+ #
8
+ # The `browsers` and `sizes` settings can be extended through `Goggles.each` as arguments.
9
+ #
10
+ # @see Goggles.each
11
+ # @see Goggles.configure
12
+ #
13
+ class Configuration
14
+ attr_accessor :browsers, :sizes, :fuzzing, :color, :groups
15
+ attr_reader :directory
16
+
17
+ def initialize
18
+ @browsers = []
19
+ @sizes = []
20
+ @groups = []
21
+ @directory = ""
22
+ @color = "blue"
23
+ @fuzzing = "20%"
24
+ end
25
+
26
+ #
27
+ # Ensures the configured path exists, but otherwise acts as a normal
28
+ # attr_accessor. Path argument must be an absolute path.
29
+ #
30
+ # @param path [String] directory path
31
+ #
32
+ def directory=(path)
33
+ @directory = path
34
+ FileUtils.mkdir_p path unless path.nil? or path.empty?
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,64 @@
1
+ require "watir-webdriver"
2
+
3
+ module Watir
4
+ class Browser
5
+ attr_accessor :goggles, :iteration
6
+
7
+ #
8
+ # Saves a screenshot as "given-description_width.png" in the configured results
9
+ # directory.
10
+ #
11
+ # @param name [String] screenshot description
12
+ # @return [nil]
13
+ #
14
+ def grab_screenshot name
15
+ description = "#{name}_#{iteration.size}"
16
+ goggles.groups << description unless goggles.groups.include? description
17
+ screenshot.save "#{goggles.directory}/#{description}_#{iteration.browser_name}.png"
18
+ end
19
+ end
20
+ end
21
+
22
+ module Goggles
23
+
24
+ #
25
+ # Executes the block passed to `Goggles.each` with every configured combination of browser
26
+ # and browser size.
27
+ #
28
+ # @see Goggles.each
29
+ #
30
+ class Iteration
31
+ attr_reader :browser, :browser_name, :size, :config
32
+
33
+ #
34
+ # Creates a script iteration instance, building yielded browser object from the
35
+ # given arguments. Closes the browser instance after yielding to the block.
36
+ #
37
+ # @param driver [String, Symbol] browser name
38
+ # @param width [Fixnum] browser width
39
+ # @param config [Goggles::Configuration] global configuration
40
+ # @yield [Watir::Browser] browser object
41
+ #
42
+ def initialize driver, width, config, &block
43
+ @browser_name = driver
44
+ @config = config
45
+ @size = width
46
+ build_browser
47
+ yield browser
48
+ browser.close
49
+ end
50
+
51
+ private
52
+
53
+ #
54
+ # @api private
55
+ #
56
+ def build_browser
57
+ @browser = Watir::Browser.new(browser_name).tap do |engine|
58
+ engine.goggles = config
59
+ engine.iteration = self
60
+ engine.driver.manage.window.resize_to size, 768
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module Goggles
2
+ VERSION = "0.10.0"
3
+ end
@@ -0,0 +1,99 @@
1
+ require "spec_helper"
2
+
3
+ describe Goggles::Comparison do
4
+ let(:config){ instance_double "configuration" }
5
+ let(:comparison){ Goggles::Comparison.new config }
6
+
7
+ before do
8
+ allow(config).to receive_messages(
9
+ directory: "dir",
10
+ fuzzing: "20%",
11
+ color: "blue",
12
+ groups: [1])
13
+ end
14
+
15
+ it "reads attributes from a given configuration object" do
16
+ expect(config).to receive(:directory)
17
+ expect(config).to receive(:fuzzing)
18
+ expect(config).to receive(:color)
19
+ Goggles::Comparison.new config
20
+ end
21
+
22
+ describe "#make!" do
23
+ it "cuts images and highlights their differences" do
24
+ expect(comparison).to receive(:cut_to_common_size)
25
+ expect(comparison).to receive(:highlight_differences)
26
+ comparison.make!
27
+ end
28
+ end
29
+
30
+ describe "#cut_to_common_size" do
31
+ it "iterates over screenshot descriptions" do
32
+ groups = instance_double "[groups]"
33
+ expect(comparison).to receive(:groups).and_return groups
34
+ expect(groups).to receive(:each_with_object).with([])
35
+ comparison.cut_to_common_size
36
+ end
37
+
38
+ it "collects comparable screenshots" do
39
+ expect(comparison).to receive(:groups).and_return [1, 2]
40
+ expect(comparison).to receive(:find_comparable).with(1).and_return([])
41
+ expect(comparison).to receive(:find_comparable).with(2).and_return([])
42
+ comparison.cut_to_common_size
43
+ end
44
+
45
+ context "with collection of comparable screenshots" do
46
+ it "iterates over the collection" do
47
+ screens = instance_double "[screenshots]"
48
+ expect(comparison).to receive(:groups).and_return ["foo"]
49
+ expect(comparison).to receive(:find_comparable).and_return screens
50
+ expect(screens).to receive(:each)
51
+ expect(comparison).to receive(:cut!).with(screens, [])
52
+ comparison.cut_to_common_size
53
+ end
54
+ end
55
+
56
+ context "while iterating over collected comparable images" do
57
+ it "opens each image in binary mode" do
58
+ expect(comparison).to receive(:find_comparable).and_return [:foo]
59
+ expect(File).to receive(:open).with(:foo, 'rb')
60
+ expect(comparison).to receive(:cut!)
61
+ comparison.cut_to_common_size
62
+ end
63
+ end
64
+ end
65
+
66
+ describe "#highlight_differences" do
67
+ it "diffs every combination of comparable screenshots" do
68
+ images = ["foo_1.png", "foo_2.png", "foo_3.png"]
69
+ expect(comparison).to receive(:groups).and_return ["foo"]
70
+ expect(comparison).to receive(:find_comparable).with("foo").and_return images
71
+ images.combination(2).to_a.each do |i|
72
+ expect(comparison).to receive(:diff).with i[0], i[1]
73
+ end
74
+ comparison.highlight_differences
75
+ end
76
+ end
77
+
78
+ describe "#find_comparable" do
79
+ it "returns an array of file paths" do
80
+ array = ["/foo_chrome.png", "/bar_chrome.png", "/foo_ff.png", "/bar_ff.png"]
81
+ expect(Dir).to receive(:glob).and_return array
82
+ expect(comparison.find_comparable "foo").to eq ["/foo_chrome.png", "/foo_ff.png"]
83
+ end
84
+ end
85
+
86
+ describe "#find_common_width" do
87
+ it "returns the smallest number from first items in an array of arrays" do
88
+ arrays = [[100,40], [68,300], [104,98]]
89
+ expect(comparison.find_common_width arrays).to eq 68
90
+ end
91
+ end
92
+
93
+ describe "#find_common_height" do
94
+ it "returns the smallest number from second items in an array of arrays" do
95
+ arrays = [[100,40], [68,300], [104,98]]
96
+ expect(comparison.find_common_height arrays).to eq 40
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe Goggles::Configuration do
4
+ let(:config) { Goggles::Configuration.new }
5
+
6
+ it "has attributes by default" do
7
+ expect(config).to have_attributes(
8
+ :browsers => [],
9
+ :sizes => [],
10
+ :groups => [],
11
+ :directory => "",
12
+ :color => "blue",
13
+ :fuzzing => "20%"
14
+ )
15
+ end
16
+
17
+ it "has attributes that can be set" do
18
+ config.browsers << "chrome"
19
+ config.sizes << 100
20
+ config.groups << "foo"
21
+ config.color = "red"
22
+ config.fuzzing = "10%"
23
+
24
+ expect(config).to have_attributes(
25
+ :browsers => ["chrome"],
26
+ :sizes => [100],
27
+ :groups => ["foo"],
28
+ :color => "red",
29
+ :fuzzing => "10%"
30
+ )
31
+ end
32
+
33
+ describe "#directory=" do
34
+ it "ensures the directory exists" do
35
+ expect(FileUtils).to receive(:mkdir_p).with "/foo/bar"
36
+ config.directory = "/foo/bar"
37
+ expect(config.directory).to eq "/foo/bar"
38
+ end
39
+
40
+ context "when attribute is empty" do
41
+ it "does not create the directory" do
42
+ expect(FileUtils).to_not receive(:mkdir_p)
43
+ config.directory = ""
44
+ end
45
+ end
46
+
47
+ context "when attribute is nil" do
48
+ it "does not create the directory" do
49
+ expect(FileUtils).to_not receive(:mkdir_p)
50
+ config.directory = nil
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ describe Goggles::Iteration do
4
+ let(:watir) { instance_double("browser") }
5
+
6
+ before do
7
+ allow(watir).to receive_messages(
8
+ :driver => watir,
9
+ :manage => watir,
10
+ :window => watir,
11
+ :goggles= => nil,
12
+ :iteration= => nil,
13
+ :resize_to => nil,
14
+ :close => nil)
15
+ end
16
+
17
+ it "yields an instantiated browser" do
18
+ expect(Watir::Browser).to receive(:new).and_return watir
19
+ expect { |b| Goggles::Iteration.new "", "", "", &b }.to yield_with_args watir
20
+ end
21
+
22
+ it "yields the browser based on the browser_name attribute" do
23
+ expect(Watir::Browser).to receive(:new).with(:foo).and_return watir
24
+ Goggles::Iteration.new(:foo, "", ""){ "bar" }
25
+ end
26
+
27
+ it "resizes the browser based on the size attribute" do
28
+ expect(Watir::Browser).to receive(:new).with(:foo).and_return watir
29
+ expect(watir).to receive(:resize_to).with(500, 768)
30
+ Goggles::Iteration.new(:foo, 500, ""){ "bar" }
31
+ end
32
+ end
@@ -0,0 +1,133 @@
1
+ require "spec_helper"
2
+
3
+ describe Goggles do
4
+ before { allow(FileUtils).to receive(:mkdir_p) }
5
+
6
+ describe ".configure" do
7
+ it "yields a configuration object" do
8
+ expect { |b| Goggles.configure &b }.to yield_with_args Goggles::Configuration
9
+ end
10
+
11
+ it "returns a configuration object" do
12
+ expect(Goggles.configure { "foo" }).to be_a Goggles::Configuration
13
+ end
14
+
15
+ it "memoizes the configuration object" do
16
+ expect(Goggles.configure { "foo" }).to equal Goggles.configure { "bar" }
17
+ end
18
+ end
19
+
20
+ describe ".each" do
21
+ let(:config) do
22
+ Goggles.configure do |conf|
23
+ conf.browsers = [:foo]
24
+ conf.sizes = [500]
25
+ end
26
+ end
27
+
28
+ before do
29
+ allow(config).to receive_messages(directory: "/dir", fuzzing: "20%", color: "blue", groups: [])
30
+ end
31
+
32
+ it "passes browser, width, and configuration to an iteration object" do
33
+ expect(Goggles::Iteration).to receive(:new).with(:foo, 500, config)
34
+ Goggles.each { "foo" }
35
+ end
36
+
37
+ context "when directory setting is nil" do
38
+ it "throws an EmptyDirectoryError" do
39
+ allow(config).to receive(:directory).and_return(nil)
40
+ expect do
41
+ Goggles.each { "foo" }
42
+ end.to raise_error(Goggles::EmptyDirectoryError)
43
+ end
44
+ end
45
+
46
+ context "when directory setting is empty" do
47
+ it "throws an EmptyDirectoryError" do
48
+ allow(config).to receive(:directory).and_return("")
49
+ expect do
50
+ Goggles.each { "foo" }
51
+ end.to raise_error(Goggles::EmptyDirectoryError)
52
+ end
53
+ end
54
+
55
+ context "when given a browser argument" do
56
+ it "extends the configured browser list with the given browser" do
57
+ expect(Goggles::Iteration).to receive(:new).with(:foo, 500, config)
58
+ expect(Goggles::Iteration).to receive(:new).with(:bar, 500, config)
59
+ Goggles.each(:bar) { "foo" }
60
+ end
61
+ end
62
+
63
+ context "when given a size argument" do
64
+ it "extends the configured size list with the given size" do
65
+ expect(Goggles::Iteration).to receive(:new).with(:foo, 500, config)
66
+ expect(Goggles::Iteration).to receive(:new).with(:foo, 250, config)
67
+ Goggles.each(250) { "foo" }
68
+ end
69
+ end
70
+
71
+ context "when given multiple browser and size arguments" do
72
+ it "extends the configuration with correctly" do
73
+ [:foo, :bar, :baz].product([500, 1000, 250]).each do |b, s|
74
+ expect(Goggles::Iteration).to receive(:new).with(b, s, config)
75
+ end
76
+ Goggles.each(:bar, :baz, 1000, 250) { "foo" }
77
+ end
78
+
79
+ context "in no particular order" do
80
+ it "extends the configuration with the correct arguments" do
81
+ [:foo, :bar, :baz].product([500, 1000, 250]).each do |b, s|
82
+ expect(Goggles::Iteration).to receive(:new).with(b, s, config)
83
+ end
84
+ Goggles.each(1000, :bar, :baz, 250) { "foo" }
85
+ end
86
+ end
87
+ end
88
+
89
+ context "when given arguments in arrays" do
90
+ it "extends the configuration correctly" do
91
+ [:foo, :bar, :baz].product([500, 1000, 250]).each do |b, s|
92
+ expect(Goggles::Iteration).to receive(:new).with(b, s, config)
93
+ end
94
+ Goggles.each([:bar, :baz], [250, 1000]) { "foo" }
95
+ end
96
+ end
97
+
98
+ context "when given arguments of various classes" do
99
+ it "extends the configuration correctly" do
100
+ [:foo, :bar, :baz].product([500, 1000, 250]).each do |b, s|
101
+ expect(Goggles::Iteration).to receive(:new).with(b, s, config)
102
+ end
103
+ Goggles.each([:bar, "baz"], 250, "1000") { "foo" }
104
+ end
105
+ end
106
+
107
+ it "returns a comparison object" do
108
+ expect(Goggles::Iteration).to receive(:new).with(:foo, 500, config)
109
+ expect(Goggles.each { "foo" }).to be_a Goggles::Comparison
110
+ end
111
+
112
+ context "when configured for browsers at one size" do
113
+ it "creates an iteration for each browser with the width" do
114
+ config.browsers << :bar
115
+ [:foo, :bar].each { |browser| expect(Goggles::Iteration).to receive(:new).with browser, 500, config }
116
+ Goggles.each { "foo" }
117
+ end
118
+ end
119
+
120
+ context "when configured for browsers at different sizes" do
121
+ it "creates an iteration for every browser and width combination" do
122
+ config.browsers << :bar
123
+ config.sizes << 1000
124
+
125
+ [:foo, :bar].product([500, 1000]).each do |browser, width|
126
+ expect(Goggles::Iteration).to receive(:new).with browser, width, Object
127
+ end
128
+
129
+ Goggles.each { "foo" }
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,2 @@
1
+ require "goggles"
2
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: goggles
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johnson Denen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-14 00:00:00.000000000 Z
11
+ date: 2015-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -114,7 +114,28 @@ email:
114
114
  executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
- files: []
117
+ files:
118
+ - .gitignore
119
+ - .travis.yml
120
+ - CHANGELOG.md
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - features/goggles.feature
126
+ - features/step_definitions/goggles_steps.rb
127
+ - features/support/env.rb
128
+ - goggles.gemspec
129
+ - lib/goggles.rb
130
+ - lib/goggles/comparison.rb
131
+ - lib/goggles/configuration.rb
132
+ - lib/goggles/iteration.rb
133
+ - lib/goggles/version.rb
134
+ - spec/goggles/comparison_spec.rb
135
+ - spec/goggles/configuration_spec.rb
136
+ - spec/goggles/iteration_spec.rb
137
+ - spec/goggles_spec.rb
138
+ - spec/spec_helper.rb
118
139
  homepage: http://github.com/jdenen/goggles
119
140
  licenses:
120
141
  - MIT
@@ -139,5 +160,13 @@ rubygems_version: 2.0.14
139
160
  signing_key:
140
161
  specification_version: 4
141
162
  summary: Compare screenshots in different browsers at different sizes
142
- test_files: []
163
+ test_files:
164
+ - features/goggles.feature
165
+ - features/step_definitions/goggles_steps.rb
166
+ - features/support/env.rb
167
+ - spec/goggles/comparison_spec.rb
168
+ - spec/goggles/configuration_spec.rb
169
+ - spec/goggles/iteration_spec.rb
170
+ - spec/goggles_spec.rb
171
+ - spec/spec_helper.rb
143
172
  has_rdoc: