goggles 0.9.1 → 0.10.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.
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: