compatriot 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +34 -0
- data/README.md +44 -25
- data/Rakefile +5 -0
- data/compatriot.gemspec +13 -6
- data/lib/compatriot/browser.rb +53 -27
- data/lib/compatriot/image_differ.rb +68 -35
- data/lib/compatriot/results_presenter.rb +8 -7
- data/lib/compatriot/runner.rb +28 -17
- data/lib/compatriot/version.rb +1 -1
- data/spec/integration/full_stack_spec.rb +36 -0
- data/spec/spec_helper.rb +44 -1
- data/spec/unit/browser_spec.rb +167 -0
- data/spec/unit/image_differ_spec.rb +53 -0
- data/spec/unit/runner_spec.rb +64 -0
- metadata +50 -24
- data/lib/compatriot/results.rb +0 -43
- data/spec/basic_behavior_spec.rb +0 -53
- data/spec/list_of_app_paths_spec.rb +0 -55
data/CHANGELOG.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Version 0.0.3
|
2
|
+
|
3
|
+
Release date: 2011-12-20
|
4
|
+
|
5
|
+
### Fixed
|
6
|
+
|
7
|
+
* General messiness
|
8
|
+
* Issue #1 - Test failure on OSX: due to not actually deleting files between test runs
|
9
|
+
* Issue #2 - Opens over 9000 browsers during tests: now only one integration test opens browsers.
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
|
13
|
+
* Lots of structure in response to unit test pain; clear up responsibilities somewhat.
|
14
|
+
|
15
|
+
### Added
|
16
|
+
|
17
|
+
* Unit tests!
|
18
|
+
|
19
|
+
# Version 0.0.2
|
20
|
+
|
21
|
+
Release date: 2011-12-02
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
|
25
|
+
* Low release number: no one uses v0.0.1.
|
26
|
+
* My knowledge of the `rake release` task (it creates a git tag for you)
|
27
|
+
|
28
|
+
# Version 0.0.1
|
29
|
+
|
30
|
+
Release date: 2011-12-01
|
31
|
+
|
32
|
+
### Added
|
33
|
+
|
34
|
+
* Initial proof of concept
|
data/README.md
CHANGED
@@ -1,25 +1,28 @@
|
|
1
1
|
Compatriot
|
2
|
-
|
3
|
-
**Compat**ibility + **riot**! It's your **friend** that helps you with browser compatibility!
|
2
|
+
==========
|
4
3
|
|
5
|
-
|
6
|
-
|
4
|
+
**Compat**ibility + **riot**! It's the **friend** that helps with browser compatibility!
|
5
|
+
This Ruby gem makes cross-browser testing less painful.
|
6
|
+
Its goal is to help identify pages that appear to have significant variations when rendered in different browsers.
|
7
|
+
You can spend your time fixing the cross-browser problems rather than looking for them.
|
8
|
+
Don't let your users find the inconsistencies and get to them first.
|
7
9
|
|
8
|
-
This is a Ruby gem to make cross-browser testing less painful. Its goal is to help identify pages that appear to have significant variations when rendered in different browsers. That way, you can spend your time fixing the cross-browser incompatibilities rather than spending your time looking for them, or worse, not looking for them at all and letting your users find them.
|
9
10
|
|
10
11
|
What it does now
|
11
12
|
----------------
|
12
13
|
|
13
|
-
*
|
14
|
-
* Stores the screenshot in tmp/results/_timestamp_/_browser_
|
15
|
-
* Creates tmp/results/_timestamp_/index.html that shows thumbnails of each screenshot plus a diff of the two in a table for easy comparison.
|
14
|
+
* In firefox and chrome, visits a list of paths to a Rack app and takes a screenshot on each page.
|
15
|
+
* Stores the screenshot in `tmp/results/_timestamp_/_browser_/`
|
16
|
+
* Creates `tmp/results/_timestamp_/index.html` that shows thumbnails of each screenshot plus a diff of the two in a table for easy comparison.
|
17
|
+
|
16
18
|
|
17
19
|
What it will do in the future
|
18
20
|
-----------------------------
|
21
|
+
|
19
22
|
* Have documentation
|
20
23
|
* Have a screenshot of sample results in the README
|
21
24
|
* Have unit tests and better tests
|
22
|
-
* Be on
|
25
|
+
* Be on travis-ci
|
23
26
|
* Not have a diff that's a different size than the originals
|
24
27
|
* Find the largest, darkest contiguous region in the image diff and have a threshold of pass/fail based on that
|
25
28
|
* Perform better on the image processing (by sampling/resizing, using oily_png, etc)
|
@@ -29,21 +32,22 @@ What it will do in the future
|
|
29
32
|
* Allow configuration of which browsers to use
|
30
33
|
* Connect to virtual machines so that you don't have to have all the browsers on the machine you're running the tests on
|
31
34
|
|
32
|
-
What to do to use it
|
33
|
-
--------------------
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
How To Use
|
37
|
+
----------
|
38
|
+
|
39
|
+
**Requirements**
|
40
|
+
|
41
|
+
* Ruby v1.9.2
|
42
|
+
* [Firefox](http://getfirefox.net)
|
43
|
+
* [chromedriver](http://code.google.com/p/selenium/wiki/ChromeDriver)
|
37
44
|
|
38
|
-
|
39
|
-
* Need chromedriver in your path
|
40
|
-
* From: http://code.google.com/p/chromium/downloads/list
|
41
|
-
* More info: http://code.google.com/p/selenium/wiki/ChromeDriver
|
45
|
+
When you run a file similar to the examples it will save results in `_current-directory_/tmp/results/_timestamp_/_browser_`
|
42
46
|
|
43
|
-
See examples in the examples directory. When you run a file similar to the examples, it will save results in _current-directory_/tmp/results/_timestamp_/_browser_
|
44
47
|
|
45
48
|
What to do to run its tests
|
46
49
|
---------------------------
|
50
|
+
|
47
51
|
Using at least ruby 1.9.2:
|
48
52
|
|
49
53
|
bundle install
|
@@ -51,14 +55,25 @@ Using at least ruby 1.9.2:
|
|
51
55
|
|
52
56
|
This is using minispec for testing.
|
53
57
|
|
54
|
-
What to do to contribute
|
55
|
-
------------------------
|
56
58
|
|
57
|
-
|
59
|
+
How You Can Contribute
|
60
|
+
----------------------
|
61
|
+
|
62
|
+
* [Issues](https://github.com/clnclarinet/compatriot/issues)
|
63
|
+
|
64
|
+
We'd really like to know if something is wrong, so please file an issue on the Issue List if you have a problem, suggestion, unsupported use case, etc.
|
65
|
+
|
66
|
+
This is a very rough proof-of-concept at this point, so there are many opportunities for improvement. Feel free to:
|
58
67
|
|
59
|
-
*
|
60
|
-
*
|
61
|
-
*
|
68
|
+
* **Fork** the repository
|
69
|
+
* **Clone the repository** locally, or **edit via Github**
|
70
|
+
* Create a **new branch** with a meaningful name of the issue or feature you're working on
|
71
|
+
* Commit **often** and **when important**
|
72
|
+
* **Write tests** specifically for the changes you've made (unless you're fixing a failing test. Also: just submitting new tests for untested code is a big help too!)
|
73
|
+
* **Push** your feature or bug fix branch to your Github fork
|
74
|
+
* Make a **Pull Request** from your fork to the main repo
|
75
|
+
|
76
|
+
Ryan Bates did an awesome [Railscast on contributing to open source](http://railscasts.com/episodes/300-contributing-to-open-source) that walks through this process, but please let us know if you have any questions or problems.
|
62
77
|
|
63
78
|
Standing on the shoulders of giants
|
64
79
|
-----------------------------------
|
@@ -69,12 +84,16 @@ Many thanks to the wonderful libraries that make this gem possible:
|
|
69
84
|
* [selenium-webdriver](http://seleniumhq.org/docs/01_introducing_selenium.html#selenium-2-aka-selenium-webdriver)
|
70
85
|
* [chunky_png](https://github.com/wvanbergen/chunky_png) (and especially [this blog post about using chunky_png to create image diffs](http://jeffkreeftmeijer.com/2011/comparing-images-and-creating-image-diffs/?utm_source=rubyweekly&utm_medium=email) by Jeff Kreeftmeijer)
|
71
86
|
|
87
|
+
|
72
88
|
Contributors
|
73
89
|
------------
|
74
90
|
* Carol Nichols ([twitter](http://twitter.com/carols10cents), [website](http://carol-nichols.com))
|
75
91
|
* Andrew Cox ([twitter](https://twitter.com/coxandrew), [website](http://andrewcox.org/))
|
92
|
+
* Kurtis Rainbolt-Greene ([twitter](https://twitter.com/krainboltgreene)) ([website](http://kurtisrainboltgreene.name/))
|
93
|
+
* Steve Klabnik ([twitter](https://twitter.com/steveklabnik)) ([website](http://www.steveklabnik.com/))
|
76
94
|
* You???
|
77
95
|
|
96
|
+
|
78
97
|
License
|
79
98
|
-------
|
80
99
|
|
@@ -97,4 +116,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
97
116
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
98
117
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
99
118
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
100
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
119
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
data/compatriot.gemspec
CHANGED
@@ -4,8 +4,12 @@ require File.expand_path('../lib/compatriot/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Carol Nichols"]
|
6
6
|
gem.email = ["carol.nichols@gmail.com"]
|
7
|
-
gem.description =
|
8
|
-
gem.summary = %q{
|
7
|
+
gem.description = 'Finds likely UI browser cross-compatibility issues.'
|
8
|
+
gem.summary = %q{
|
9
|
+
Runs a command in multiple browsers using selenium then compares the
|
10
|
+
screenshots and presents those likely to have cross-browser incompatibilities.
|
11
|
+
}
|
12
|
+
|
9
13
|
gem.homepage = "https://github.com/clnclarinet/compatriot"
|
10
14
|
|
11
15
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
@@ -15,9 +19,12 @@ Gem::Specification.new do |gem|
|
|
15
19
|
gem.require_paths = ["lib"]
|
16
20
|
gem.version = Compatriot::VERSION
|
17
21
|
|
18
|
-
|
19
|
-
gem.
|
20
|
-
gem.
|
22
|
+
# To get the newest minitest features!
|
23
|
+
gem.add_development_dependency 'minitest', '~> 2.8.1'
|
24
|
+
gem.add_development_dependency 'sinatra', '~> 1.3.1'
|
25
|
+
gem.add_development_dependency 'mocha', '~> 0.10.0'
|
21
26
|
|
22
|
-
gem.
|
27
|
+
gem.add_dependency 'capybara', '~> 1.1.2'
|
28
|
+
gem.add_dependency 'rake', '~> 0.9.2.2'
|
29
|
+
gem.add_dependency 'chunky_png', '~> 1.2.5'
|
23
30
|
end
|
data/lib/compatriot/browser.rb
CHANGED
@@ -5,52 +5,78 @@ module Compatriot
|
|
5
5
|
class Browser
|
6
6
|
include Capybara::DSL
|
7
7
|
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
def self.create_browsers(params = {})
|
9
|
+
params[:browser_names].collect do |b|
|
10
|
+
Compatriot::Browser.new(
|
11
|
+
:name => b,
|
12
|
+
:screenshot_directory => params[:results_directory]
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
def initialize(params = {})
|
20
|
+
@name = params[:name]
|
21
|
+
@screenshot_directory = params[:screenshot_directory]
|
22
|
+
@screenshot_locations = {}
|
23
|
+
|
24
|
+
create_screenshot_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def screenshot_path
|
28
|
+
File.join(@screenshot_directory, @name)
|
13
29
|
end
|
14
30
|
|
15
31
|
def initialize_capybara(app)
|
16
|
-
driver = "selenium_#{@
|
32
|
+
driver = "selenium_#{@name}".to_sym
|
17
33
|
Capybara.register_driver driver do |a|
|
18
|
-
Capybara::Selenium::Driver.new(a, :browser => @
|
34
|
+
Capybara::Selenium::Driver.new(a, :browser => @name.to_sym)
|
19
35
|
end
|
20
36
|
Capybara.default_driver = driver
|
21
37
|
Capybara.app = app
|
22
38
|
end
|
23
39
|
|
24
|
-
def take_screenshots(
|
25
|
-
|
26
|
-
paths.
|
27
|
-
|
28
|
-
results[path] = screenshot
|
29
|
-
end
|
40
|
+
def take_screenshots(params = {})
|
41
|
+
initialize_capybara(params[:app])
|
42
|
+
params[:paths].map { |path| take_screenshot(path) }
|
43
|
+
|
30
44
|
# Reset the selenium session to avoid timeout errors
|
31
45
|
Capybara.send(:session_pool).delete_if { |key, value| key =~ /selenium/i }
|
32
|
-
results
|
33
46
|
end
|
34
47
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
Capybara.page.driver.browser.save_screenshot(filepath)
|
40
|
-
file_base_name
|
48
|
+
def absolute_screenshot_for(path)
|
49
|
+
if @screenshot_locations[path]
|
50
|
+
absolute_dir(@screenshot_locations[path])
|
51
|
+
end
|
41
52
|
end
|
42
53
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
54
|
+
def relative_screenshot_for(path)
|
55
|
+
if @screenshot_locations[path]
|
56
|
+
File.join(name, @screenshot_locations[path])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def take_screenshot(path)
|
61
|
+
visit path
|
62
|
+
filename = next_filename
|
63
|
+
Capybara.page.driver.browser.save_screenshot(absolute_dir(filename))
|
64
|
+
@screenshot_locations[path] = filename
|
48
65
|
end
|
49
66
|
|
50
67
|
private
|
51
68
|
|
52
|
-
def
|
53
|
-
|
69
|
+
def absolute_dir(filename)
|
70
|
+
File.join(screenshot_path, filename)
|
71
|
+
end
|
72
|
+
|
73
|
+
def next_filename
|
74
|
+
@file_id = (@file_id && @file_id + 1) || 1
|
75
|
+
"#{@file_id}.png"
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_screenshot_path
|
79
|
+
FileUtils.mkdir_p(screenshot_path)
|
54
80
|
end
|
55
81
|
end
|
56
82
|
end
|
@@ -4,64 +4,97 @@ include ChunkyPNG::Color
|
|
4
4
|
module Compatriot
|
5
5
|
class ImageDiffer
|
6
6
|
|
7
|
-
|
7
|
+
attr_reader :diffs
|
8
|
+
|
9
|
+
def initialize(params = {})
|
10
|
+
@paths = params[:paths]
|
11
|
+
@browsers = params[:browsers]
|
12
|
+
@strategy = params[:strategy] || :color_difference
|
13
|
+
@diffs = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def compute!
|
17
|
+
@paths.map do |path|
|
18
|
+
images_to_diff = @browsers.map { |b| b.absolute_screenshot_for(path) }
|
19
|
+
@diffs[path] = diff(images_to_diff)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def diff(results)
|
8
24
|
images = results.map{|r| ChunkyPNG::Image.from_file(r) }
|
9
|
-
|
10
|
-
File.join(
|
11
|
-
File.basename(File.dirname(f)),
|
12
|
-
File.basename(f)
|
13
|
-
)
|
25
|
+
self.send(@strategy, images.first, images.last, results.first)
|
14
26
|
end
|
15
27
|
|
16
|
-
def
|
17
|
-
output = ChunkyPNG::Image.new(
|
28
|
+
def same_pixels_exactly(image1, image2, name)
|
29
|
+
output = ChunkyPNG::Image.new(image1.width, image2.height, WHITE)
|
18
30
|
diff = []
|
19
31
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
32
|
+
each_pixel(image1) do |x, y|
|
33
|
+
pixel1 = image1[x, y]
|
34
|
+
pixel2 = image2[x, y]
|
35
|
+
output[x,y] = pixel1
|
36
|
+
diff << [x,y] unless pixel1 == pixel2
|
25
37
|
end
|
26
38
|
|
27
|
-
pixels_total =
|
39
|
+
pixels_total = image1.pixels.length
|
28
40
|
pixels_changed = diff.length
|
29
|
-
pixels_changed_percentage = (diff.length.to_f /
|
41
|
+
pixels_changed_percentage = (diff.length.to_f / image1.pixels.length) * 100
|
30
42
|
|
31
43
|
x, y = diff.map{ |xy| xy[0] }, diff.map{ |xy| xy[1] }
|
32
44
|
|
33
45
|
output.rect(x.min, y.min, x.max, y.max, ChunkyPNG::Color.rgb(0,255,0))
|
34
46
|
filename = "#{name}-same_exactly.png"
|
35
47
|
output.save(filename)
|
36
|
-
|
48
|
+
File.join(
|
49
|
+
File.basename(File.dirname(filename)),
|
50
|
+
File.basename(filename)
|
51
|
+
)
|
37
52
|
end
|
38
53
|
|
39
|
-
def
|
40
|
-
output = ChunkyPNG::Image.new(
|
54
|
+
def color_difference(image1, image2, name)
|
55
|
+
output = ChunkyPNG::Image.new(image1.width, image1.height, WHITE)
|
41
56
|
diff = []
|
42
57
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
(b(images.last[x,y]) - b(pixel)) ** 2
|
50
|
-
) / Math.sqrt(MAX ** 2 * 3)
|
51
|
-
|
52
|
-
output[x,y] = grayscale(MAX - (score * MAX).round)
|
53
|
-
diff << score
|
54
|
-
end
|
58
|
+
each_pixel(image1) do |x, y|
|
59
|
+
pixel1 = image1[x,y]
|
60
|
+
pixel2 = image2[x,y]
|
61
|
+
unless pixel1 == pixel2
|
62
|
+
output[x,y], score = color_difference_of_pixels(pixel1, pixel2)
|
63
|
+
diff << score
|
55
64
|
end
|
56
65
|
end
|
57
66
|
|
58
|
-
pixels_total = images.first.pixels.length
|
59
|
-
pixels_changed = diff.length
|
60
|
-
pixels_changed_percentage = (diff.inject {|sum, value| sum + value} / images.first.pixels.length) * 100
|
61
|
-
|
62
67
|
filename = "#{name}-color_difference.png"
|
63
68
|
output.save(filename)
|
64
|
-
|
69
|
+
File.join(
|
70
|
+
File.basename(File.dirname(filename)),
|
71
|
+
File.basename(filename)
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def color_difference_of_pixels(pixel1, pixel2)
|
76
|
+
score = Math.sqrt(
|
77
|
+
(r(pixel2) - r(pixel1)) ** 2 +
|
78
|
+
(g(pixel2) - g(pixel1)) ** 2 +
|
79
|
+
(b(pixel2) - b(pixel1)) ** 2
|
80
|
+
) / Math.sqrt(MAX ** 2 * 3)
|
81
|
+
|
82
|
+
[grayscale(MAX - (score * MAX).round), score]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Not called anywhere
|
86
|
+
def color_difference_total_score
|
87
|
+
pixels_total = image1.width * image1.height
|
88
|
+
pixels_changed = diff.length
|
89
|
+
pixels_changed_percentage = (diff.inject {|sum, value| sum + value} / pixels_total) * 100
|
90
|
+
end
|
91
|
+
|
92
|
+
def each_pixel(image)
|
93
|
+
image.width.times do |x|
|
94
|
+
image.height.times do |y|
|
95
|
+
yield(x, y)
|
96
|
+
end
|
97
|
+
end
|
65
98
|
end
|
66
99
|
end
|
67
100
|
end
|
@@ -6,12 +6,13 @@ module Compatriot
|
|
6
6
|
@results_directory = results_directory
|
7
7
|
end
|
8
8
|
|
9
|
-
def make_index_page(
|
9
|
+
def make_index_page(params = {})
|
10
10
|
index_page_file = File.join(@results_directory, "index.html")
|
11
11
|
|
12
|
-
b
|
13
|
-
|
14
|
-
|
12
|
+
b = binding
|
13
|
+
paths = params[:paths]
|
14
|
+
browsers = params[:browsers]
|
15
|
+
differ = params[:differ]
|
15
16
|
|
16
17
|
html = ERB.new <<-EOF
|
17
18
|
<html>
|
@@ -32,7 +33,7 @@ module Compatriot
|
|
32
33
|
<tr>
|
33
34
|
<th>Path</th>
|
34
35
|
<% browsers.each do |browser| %>
|
35
|
-
<th><%= browser %></th>
|
36
|
+
<th><%= browser.name %></th>
|
36
37
|
<% end %>
|
37
38
|
<th>Diff</th>
|
38
39
|
</tr>
|
@@ -43,11 +44,11 @@ module Compatriot
|
|
43
44
|
</td>
|
44
45
|
<% browsers.each do |browser| %>
|
45
46
|
<td>
|
46
|
-
<img src="<%= browser
|
47
|
+
<img src="<%= browser.relative_screenshot_for(path) %>" />
|
47
48
|
</td>
|
48
49
|
<% end %>
|
49
50
|
<td>
|
50
|
-
<img src="<%=
|
51
|
+
<img src="<%= differ.diffs[path] %>" />
|
51
52
|
</td>
|
52
53
|
</tr>
|
53
54
|
<% end %>
|
data/lib/compatriot/runner.rb
CHANGED
@@ -4,6 +4,7 @@ module Compatriot
|
|
4
4
|
class Runner
|
5
5
|
def self.start(app, paths, clock = DateTime)
|
6
6
|
runner = new(app, paths, clock)
|
7
|
+
runner.create_results_directory
|
7
8
|
runner.take_screenshots
|
8
9
|
runner.compute_diffs
|
9
10
|
runner.make_index_page
|
@@ -12,41 +13,51 @@ module Compatriot
|
|
12
13
|
|
13
14
|
BROWSERS = ["firefox", "chrome"]
|
14
15
|
|
15
|
-
attr_reader :app, :
|
16
|
+
attr_reader :app, :results_directory
|
16
17
|
|
17
18
|
def initialize(app, paths, clock)
|
18
19
|
@app = app
|
19
20
|
@paths = paths
|
20
21
|
@clock = clock
|
21
|
-
|
22
|
+
|
23
|
+
timestamp = @clock.now.strftime("%Y-%m-%d-%H-%M-%S")
|
24
|
+
@results_directory = File.join("tmp", "results", timestamp)
|
25
|
+
|
26
|
+
@browsers = Compatriot::Browser.create_browsers(
|
27
|
+
:browser_names => BROWSERS,
|
28
|
+
:results_directory => @results_directory
|
29
|
+
)
|
22
30
|
end
|
23
31
|
|
24
32
|
def take_screenshots
|
25
|
-
|
26
|
-
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:paths => @paths,
|
30
|
-
:results_directory => results_directory
|
33
|
+
@browsers.each do |browser_object|
|
34
|
+
browser_object.take_screenshots(
|
35
|
+
:app => @app,
|
36
|
+
:paths => @paths
|
31
37
|
)
|
32
38
|
end
|
33
39
|
end
|
34
40
|
|
35
41
|
def compute_diffs
|
36
|
-
@
|
42
|
+
@differ = Compatriot::ImageDiffer.new(
|
43
|
+
:paths => @paths,
|
44
|
+
:browsers => @browsers,
|
45
|
+
:strategy => :color_difference
|
46
|
+
)
|
47
|
+
@differ.compute!
|
37
48
|
end
|
38
49
|
|
39
50
|
def make_index_page
|
40
|
-
presenter = Compatriot::ResultsPresenter.new(results_directory)
|
41
|
-
presenter.make_index_page(
|
51
|
+
presenter = Compatriot::ResultsPresenter.new(@results_directory)
|
52
|
+
presenter.make_index_page(
|
53
|
+
:paths => @paths,
|
54
|
+
:browsers => @browsers,
|
55
|
+
:differ => @differ
|
56
|
+
)
|
42
57
|
end
|
43
58
|
|
44
|
-
def
|
45
|
-
|
46
|
-
timestamp = @clock.now.strftime("%Y-%m-%d-%H-%M-%S")
|
47
|
-
directory_name = "tmp/results/#{timestamp}"
|
48
|
-
FileUtils.mkdir_p(directory_name)
|
49
|
-
@results_directory = directory_name
|
59
|
+
def create_results_directory
|
60
|
+
FileUtils.mkdir_p(@results_directory)
|
50
61
|
end
|
51
62
|
end
|
52
63
|
end
|
data/lib/compatriot/version.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'date'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
describe "Hit a list of paths for this app" do
|
7
|
+
it "takes screenshots, diffs them, and creates an index" do
|
8
|
+
root_dir = Dir.getwd
|
9
|
+
Dir.chdir(File.join(File.dirname(__FILE__), '..', 'sample_app'))
|
10
|
+
|
11
|
+
results_tmp_dir = File.join("tmp", "results")
|
12
|
+
|
13
|
+
FileUtils.remove_dir(results_tmp_dir, true)
|
14
|
+
|
15
|
+
Compatriot.app = TestApp
|
16
|
+
Compatriot.run(%w[
|
17
|
+
/
|
18
|
+
/chrome-css-bug
|
19
|
+
])
|
20
|
+
|
21
|
+
current_results_dir = (Dir.entries(results_tmp_dir) - [".", ".."]).first
|
22
|
+
current_results_dir = File.join(results_tmp_dir, current_results_dir)
|
23
|
+
|
24
|
+
firefox_dir = File.join(current_results_dir, "firefox")
|
25
|
+
chrome_dir = File.join(current_results_dir, "chrome")
|
26
|
+
|
27
|
+
Dir.glob(File.join(firefox_dir, "*.png")).size.must_equal 4
|
28
|
+
Dir.glob(File.join(chrome_dir, "*.png")).size.must_equal 2
|
29
|
+
|
30
|
+
results_index = IO.read(File.join(current_results_dir, "index.html"))
|
31
|
+
xml = Nokogiri::XML(results_index)
|
32
|
+
xml.xpath("//tr[td]").size.must_equal(2)
|
33
|
+
|
34
|
+
Dir.chdir(root_dir)
|
35
|
+
end
|
36
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,8 +1,51 @@
|
|
1
1
|
$:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
2
2
|
|
3
|
+
require 'rubygems'
|
4
|
+
gem 'minitest' # ensures you're using the gem, and not the built in MT
|
3
5
|
require 'minitest/autorun'
|
6
|
+
require 'mocha'
|
7
|
+
|
4
8
|
require 'compatriot'
|
5
9
|
|
6
10
|
require_relative "sample_app/test_app"
|
7
11
|
|
8
|
-
|
12
|
+
# A custom runner to enable before_suite and after_suite setup/teardown.
|
13
|
+
# http://bfts.rubyforge.org/minitest/index.html
|
14
|
+
# Only using it to delete the screenshots that result from running the tests
|
15
|
+
# before running the suite; it's useful to be able to look at the screenshots
|
16
|
+
# after a test run so we're not deleting them then.
|
17
|
+
|
18
|
+
module MiniTestWithHooks
|
19
|
+
class Unit < MiniTest::Unit
|
20
|
+
def before_suites
|
21
|
+
end
|
22
|
+
|
23
|
+
def after_suites
|
24
|
+
end
|
25
|
+
|
26
|
+
def _run_suites(suites, type)
|
27
|
+
begin
|
28
|
+
before_suites
|
29
|
+
super(suites, type)
|
30
|
+
ensure
|
31
|
+
after_suites
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module MiniTestRemoveScreenshots
|
38
|
+
class Unit < MiniTestWithHooks::Unit
|
39
|
+
|
40
|
+
def before_suites
|
41
|
+
super
|
42
|
+
FileUtils.remove_dir(File.join("sample_app", "tmp", "results"), true)
|
43
|
+
end
|
44
|
+
|
45
|
+
def after_suites
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
MiniTest::Unit.runner = MiniTestRemoveScreenshots::Unit.new
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Compatriot::Browser do
|
4
|
+
describe "self#create_browsers" do
|
5
|
+
it "creates a browser object for each browser name given to it" do
|
6
|
+
browsers = ["linx", "dolphin HD"] # Selenium totally doesn't work w/these
|
7
|
+
fake_directory = "/some/location"
|
8
|
+
|
9
|
+
linx = stub
|
10
|
+
dolphin = stub
|
11
|
+
|
12
|
+
Compatriot::Browser.expects(:new).with(
|
13
|
+
:name => "linx",
|
14
|
+
:screenshot_directory => fake_directory
|
15
|
+
).returns(linx)
|
16
|
+
|
17
|
+
Compatriot::Browser.expects(:new).with(
|
18
|
+
:name => "dolphin HD",
|
19
|
+
:screenshot_directory => fake_directory
|
20
|
+
).returns(dolphin)
|
21
|
+
|
22
|
+
results = Compatriot::Browser.create_browsers(
|
23
|
+
:browser_names => browsers,
|
24
|
+
:results_directory => fake_directory
|
25
|
+
)
|
26
|
+
results.must_equal([linx, dolphin])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "initialize_capybara" do
|
31
|
+
before do
|
32
|
+
Compatriot::Browser.any_instance.stubs(:create_screenshot_path)
|
33
|
+
@b = Compatriot::Browser.new(
|
34
|
+
:name => "foo",
|
35
|
+
:screenshot_directory => "bar"
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "registers a new driver" do
|
40
|
+
Capybara.expects(:register_driver).with(:selenium_foo)
|
41
|
+
|
42
|
+
@b.initialize_capybara(stub)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "sets this driver as the default" do
|
46
|
+
Capybara.expects(:default_driver=).with(:selenium_foo)
|
47
|
+
|
48
|
+
@b.initialize_capybara(stub)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "sets the Capybara app" do
|
52
|
+
app = stub
|
53
|
+
Capybara.expects(:app=).with(app)
|
54
|
+
|
55
|
+
@b.initialize_capybara(app)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "take_screenshots" do
|
60
|
+
before do
|
61
|
+
Compatriot::Browser.any_instance.stubs(:create_screenshot_path)
|
62
|
+
@b = Compatriot::Browser.new(
|
63
|
+
:name => "foo",
|
64
|
+
:screenshot_directory => "bar"
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "calls initialize_capybara once and take_screenshot for each path" do
|
69
|
+
app = stub
|
70
|
+
@b.expects(:initialize_capybara).with(app)
|
71
|
+
|
72
|
+
@b.expects(:take_screenshot).with("/")
|
73
|
+
@b.expects(:take_screenshot).with("/contact")
|
74
|
+
|
75
|
+
@b.take_screenshots(
|
76
|
+
:app => app,
|
77
|
+
:paths => ["/", "/contact"]
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "take_screenshot" do
|
83
|
+
before do
|
84
|
+
Compatriot::Browser.any_instance.stubs(:create_screenshot_path)
|
85
|
+
@b = Compatriot::Browser.new(
|
86
|
+
:name => "foo",
|
87
|
+
:screenshot_directory => "bar"
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "visits the path" do
|
92
|
+
@b.expects(:visit).with("/some_page")
|
93
|
+
|
94
|
+
capybara_browser = stub
|
95
|
+
capybara_browser.stubs(:save_screenshot)
|
96
|
+
Capybara.page.driver.stubs(:browser).returns(capybara_browser)
|
97
|
+
|
98
|
+
@b.take_screenshot("/some_page")
|
99
|
+
end
|
100
|
+
|
101
|
+
it "tells capybara to take a screenshot" do
|
102
|
+
@b.stubs(:visit)
|
103
|
+
|
104
|
+
@b.stubs(:next_filename).returns("/where_to_save")
|
105
|
+
capybara_browser = stub
|
106
|
+
capybara_browser.expects(:save_screenshot).with("bar/foo/where_to_save")
|
107
|
+
Capybara.page.driver.stubs(:browser).returns(capybara_browser)
|
108
|
+
|
109
|
+
@b.take_screenshot("/some_page")
|
110
|
+
end
|
111
|
+
|
112
|
+
it "increments the filenames" do
|
113
|
+
@b.stubs(:visit)
|
114
|
+
|
115
|
+
capybara_browser = stub
|
116
|
+
capybara_browser.stubs(:save_screenshot)
|
117
|
+
Capybara.page.driver.stubs(:browser).returns(capybara_browser)
|
118
|
+
|
119
|
+
@b.take_screenshot("/some_page")
|
120
|
+
@b.take_screenshot("/some_other_page")
|
121
|
+
|
122
|
+
@b.relative_screenshot_for("/some_page").must_equal("foo/1.png")
|
123
|
+
@b.relative_screenshot_for("/some_other_page").must_equal("foo/2.png")
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "relative_screenshot_for" do
|
129
|
+
before do
|
130
|
+
Compatriot::Browser.any_instance.stubs(:create_screenshot_path)
|
131
|
+
@b = Compatriot::Browser.new(
|
132
|
+
:name => "foo",
|
133
|
+
:screenshot_directory => "bar"
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "returns nil if there is no screenshot for that path" do
|
138
|
+
@b.relative_screenshot_for("/whatever").must_equal(nil)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "stores the screenshot location by path" do
|
142
|
+
@b.stubs(:visit)
|
143
|
+
@b.stubs(:next_filename).returns("/some/location.png")
|
144
|
+
|
145
|
+
capybara_browser = stub
|
146
|
+
capybara_browser.stubs(:save_screenshot)
|
147
|
+
Capybara.page.driver.stubs(:browser).returns(capybara_browser)
|
148
|
+
|
149
|
+
@b.take_screenshot("/")
|
150
|
+
|
151
|
+
@b.relative_screenshot_for("/").must_equal("foo/some/location.png")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "screenshot_path" do
|
156
|
+
it "adds the browser name to the screenshot dir and creates it" do
|
157
|
+
FileUtils.expects(:mkdir_p).with("foo/bar/hi")
|
158
|
+
|
159
|
+
c = Compatriot::Browser.new(
|
160
|
+
:name => "hi",
|
161
|
+
:screenshot_directory => "foo/bar"
|
162
|
+
)
|
163
|
+
|
164
|
+
c.screenshot_path.must_equal("foo/bar/hi")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Compatriot::ImageDiffer do
|
4
|
+
describe "compute!" do
|
5
|
+
it "diffs each set of images and stores the location by path" do
|
6
|
+
d = Compatriot::ImageDiffer.new(
|
7
|
+
:paths => ["/home"],
|
8
|
+
:browsers => [
|
9
|
+
stub(:absolute_screenshot_for => "1.png"),
|
10
|
+
stub(:absolute_screenshot_for => "2.png")
|
11
|
+
]
|
12
|
+
)
|
13
|
+
d.expects(:diff).with(["1.png", "2.png"]).returns("diff.png")
|
14
|
+
d.compute!
|
15
|
+
d.diffs["/home"].must_equal("diff.png")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
describe "diff" do
|
19
|
+
it "calls chunky_png on each image path" do
|
20
|
+
file_one = stub
|
21
|
+
file_two = stub
|
22
|
+
|
23
|
+
ChunkyPNG::Image.expects(:from_file).with(file_one)
|
24
|
+
ChunkyPNG::Image.expects(:from_file).with(file_two)
|
25
|
+
|
26
|
+
c = Compatriot::ImageDiffer.new
|
27
|
+
c.stubs(:color_difference)
|
28
|
+
c.diff([file_one, file_two])
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns the filename of the diff" do
|
32
|
+
end
|
33
|
+
|
34
|
+
it "uses the strategy passed in" do
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "self#color_difference" do
|
39
|
+
it "starts a new white image with the same dimensions" do
|
40
|
+
diff = stub_everything
|
41
|
+
ChunkyPNG::Image.expects(:new).with(
|
42
|
+
1,
|
43
|
+
2,
|
44
|
+
ChunkyPNG::Image::WHITE
|
45
|
+
).returns(diff)
|
46
|
+
|
47
|
+
image1 = stub_everything("1", :width => 1, :height => 2)
|
48
|
+
image2 = stub_everything("2", :width => 3, :height => 4)
|
49
|
+
|
50
|
+
Compatriot::ImageDiffer.new.color_difference(image1, image2, stub)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Compatriot::Runner do
|
4
|
+
before do
|
5
|
+
Compatriot::Runner.any_instance.stubs(:create_results_directory)
|
6
|
+
Compatriot::Browser.any_instance.stubs(:create_screenshot_path)
|
7
|
+
fake_date = DateTime.parse("2012-01-01 00:00:00 UTC")
|
8
|
+
@fake_date_dir = fake_date.strftime("%Y-%m-%d-%H-%M-%S")
|
9
|
+
@fixed_clock = OpenStruct.new(:now => fake_date)
|
10
|
+
@results_dir_name = File.join("tmp", "results", @fake_date_dir)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "self#start" do
|
14
|
+
it "creates a Runner instance and calls the other methods on it" do
|
15
|
+
runner = stub
|
16
|
+
|
17
|
+
Compatriot::Runner.expects(:new).returns(runner)
|
18
|
+
runner.expects(:create_results_directory)
|
19
|
+
runner.expects(:take_screenshots)
|
20
|
+
runner.expects(:compute_diffs)
|
21
|
+
runner.expects(:make_index_page)
|
22
|
+
|
23
|
+
Compatriot::Runner.start(stub, stub)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#take_screenshots" do
|
28
|
+
it "calls take_screenshots for each browser" do
|
29
|
+
firefox_browser = stub
|
30
|
+
chrome_browser = stub
|
31
|
+
|
32
|
+
Compatriot::Browser.expects(:create_browsers).returns([
|
33
|
+
firefox_browser,
|
34
|
+
chrome_browser
|
35
|
+
])
|
36
|
+
|
37
|
+
app = stub
|
38
|
+
paths = stub
|
39
|
+
|
40
|
+
firefox_browser.expects(:take_screenshots).with(
|
41
|
+
:app => app,
|
42
|
+
:paths => paths
|
43
|
+
)
|
44
|
+
chrome_browser.expects(:take_screenshots).with(
|
45
|
+
:app => app,
|
46
|
+
:paths => paths
|
47
|
+
)
|
48
|
+
|
49
|
+
runner = Compatriot::Runner.new(app, paths, @fixed_clock)
|
50
|
+
|
51
|
+
runner.take_screenshots
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
describe "#results_directory" do
|
57
|
+
it "names a results directory in tmp/results based on the clock" do
|
58
|
+
runner = Compatriot::Runner.new(TestApp, ["/"], @fixed_clock)
|
59
|
+
|
60
|
+
runner.results_directory.must_equal(@results_dir_name)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: compatriot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,44 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-12-
|
12
|
+
date: 2011-12-21 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: &2157131520 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.8.1
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2157131520
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sinatra
|
27
|
+
requirement: &2157131020 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.3.1
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2157131020
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: mocha
|
38
|
+
requirement: &2157130560 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.10.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2157130560
|
14
47
|
- !ruby/object:Gem::Dependency
|
15
48
|
name: capybara
|
16
|
-
requirement: &
|
49
|
+
requirement: &2157130100 !ruby/object:Gem::Requirement
|
17
50
|
none: false
|
18
51
|
requirements:
|
19
52
|
- - ~>
|
@@ -21,10 +54,10 @@ dependencies:
|
|
21
54
|
version: 1.1.2
|
22
55
|
type: :runtime
|
23
56
|
prerelease: false
|
24
|
-
version_requirements: *
|
57
|
+
version_requirements: *2157130100
|
25
58
|
- !ruby/object:Gem::Dependency
|
26
59
|
name: rake
|
27
|
-
requirement: &
|
60
|
+
requirement: &2157129640 !ruby/object:Gem::Requirement
|
28
61
|
none: false
|
29
62
|
requirements:
|
30
63
|
- - ~>
|
@@ -32,10 +65,10 @@ dependencies:
|
|
32
65
|
version: 0.9.2.2
|
33
66
|
type: :runtime
|
34
67
|
prerelease: false
|
35
|
-
version_requirements: *
|
68
|
+
version_requirements: *2157129640
|
36
69
|
- !ruby/object:Gem::Dependency
|
37
70
|
name: chunky_png
|
38
|
-
requirement: &
|
71
|
+
requirement: &2157129180 !ruby/object:Gem::Requirement
|
39
72
|
none: false
|
40
73
|
requirements:
|
41
74
|
- - ~>
|
@@ -43,18 +76,7 @@ dependencies:
|
|
43
76
|
version: 1.2.5
|
44
77
|
type: :runtime
|
45
78
|
prerelease: false
|
46
|
-
version_requirements: *
|
47
|
-
- !ruby/object:Gem::Dependency
|
48
|
-
name: sinatra
|
49
|
-
requirement: &2153230940 !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
|
-
requirements:
|
52
|
-
- - ~>
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 1.3.1
|
55
|
-
type: :development
|
56
|
-
prerelease: false
|
57
|
-
version_requirements: *2153230940
|
79
|
+
version_requirements: *2157129180
|
58
80
|
description: Finds likely UI browser cross-compatibility issues.
|
59
81
|
email:
|
60
82
|
- carol.nichols@gmail.com
|
@@ -64,6 +86,7 @@ extra_rdoc_files: []
|
|
64
86
|
files:
|
65
87
|
- .gitignore
|
66
88
|
- .rvmrc
|
89
|
+
- CHANGELOG.md
|
67
90
|
- Gemfile
|
68
91
|
- README.md
|
69
92
|
- Rakefile
|
@@ -72,15 +95,16 @@ files:
|
|
72
95
|
- lib/compatriot.rb
|
73
96
|
- lib/compatriot/browser.rb
|
74
97
|
- lib/compatriot/image_differ.rb
|
75
|
-
- lib/compatriot/results.rb
|
76
98
|
- lib/compatriot/results_presenter.rb
|
77
99
|
- lib/compatriot/runner.rb
|
78
100
|
- lib/compatriot/version.rb
|
79
|
-
- spec/
|
80
|
-
- spec/list_of_app_paths_spec.rb
|
101
|
+
- spec/integration/full_stack_spec.rb
|
81
102
|
- spec/sample_app/public/images/smileyface.jpg
|
82
103
|
- spec/sample_app/test_app.rb
|
83
104
|
- spec/spec_helper.rb
|
105
|
+
- spec/unit/browser_spec.rb
|
106
|
+
- spec/unit/image_differ_spec.rb
|
107
|
+
- spec/unit/runner_spec.rb
|
84
108
|
homepage: https://github.com/clnclarinet/compatriot
|
85
109
|
licenses: []
|
86
110
|
post_install_message:
|
@@ -107,8 +131,10 @@ specification_version: 3
|
|
107
131
|
summary: Runs a command in multiple browsers using selenium then compares the screenshots
|
108
132
|
and presents those likely to have cross-browser incompatibilities.
|
109
133
|
test_files:
|
110
|
-
- spec/
|
111
|
-
- spec/list_of_app_paths_spec.rb
|
134
|
+
- spec/integration/full_stack_spec.rb
|
112
135
|
- spec/sample_app/public/images/smileyface.jpg
|
113
136
|
- spec/sample_app/test_app.rb
|
114
137
|
- spec/spec_helper.rb
|
138
|
+
- spec/unit/browser_spec.rb
|
139
|
+
- spec/unit/image_differ_spec.rb
|
140
|
+
- spec/unit/runner_spec.rb
|
data/lib/compatriot/results.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
module Compatriot
|
2
|
-
class Results
|
3
|
-
def initialize
|
4
|
-
@data = {}
|
5
|
-
@diffs = {}
|
6
|
-
end
|
7
|
-
|
8
|
-
def take_screenshots(params)
|
9
|
-
@results_directory = params[:results_directory]
|
10
|
-
@app = params[:app]
|
11
|
-
@paths = params[:paths]
|
12
|
-
|
13
|
-
@browser = Compatriot::Browser.new(params[:browser], @results_directory)
|
14
|
-
@browser.initialize_capybara(@app)
|
15
|
-
|
16
|
-
@data[params[:browser]] = @browser.take_screenshots(@paths)
|
17
|
-
end
|
18
|
-
|
19
|
-
def compute_diffs
|
20
|
-
paths.map do |path|
|
21
|
-
@diffs[path] = Compatriot::ImageDiffer.diff(
|
22
|
-
browsers.map{|b| File.join(@results_directory, b, screenshot_for(b, path))}
|
23
|
-
)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def browsers
|
28
|
-
@data.keys
|
29
|
-
end
|
30
|
-
|
31
|
-
def paths
|
32
|
-
@data[browsers.first].keys
|
33
|
-
end
|
34
|
-
|
35
|
-
def screenshot_for(browser, path)
|
36
|
-
@data[browser][path]
|
37
|
-
end
|
38
|
-
|
39
|
-
def diff_for(path)
|
40
|
-
@diffs[path]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
data/spec/basic_behavior_spec.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
require_relative 'spec_helper'
|
2
|
-
require 'fileutils'
|
3
|
-
require 'date'
|
4
|
-
|
5
|
-
describe "The very basics" do
|
6
|
-
before do
|
7
|
-
@current_dir = Dir.getwd
|
8
|
-
Dir.chdir(File.join(File.dirname(__FILE__), 'sample_app'))
|
9
|
-
|
10
|
-
fake_date = DateTime.parse("2012-01-01 00:00:00 UTC")
|
11
|
-
fake_date_dir = fake_date.strftime("%Y-%m-%d-%H-%M-%S")
|
12
|
-
@fixed_clock = OpenStruct.new(:now => fake_date)
|
13
|
-
|
14
|
-
@results_directory = File.join("tmp", "results", fake_date_dir)
|
15
|
-
@firefox_directory = File.join(@results_directory, "firefox")
|
16
|
-
@chrome_directory = File.join(@results_directory, "chrome")
|
17
|
-
end
|
18
|
-
|
19
|
-
after do
|
20
|
-
Dir.chdir(@current_dir)
|
21
|
-
end
|
22
|
-
|
23
|
-
it "holds onto the app we give it" do
|
24
|
-
x = Compatriot::Runner.new(TestApp, ["/"], @fixed_clock)
|
25
|
-
x.app.must_equal(TestApp)
|
26
|
-
end
|
27
|
-
|
28
|
-
it "names a results directory based on the clock" do
|
29
|
-
x = Compatriot::Runner.new(TestApp, ["/"], @fixed_clock)
|
30
|
-
x.results_directory.must_equal(@results_directory)
|
31
|
-
end
|
32
|
-
|
33
|
-
describe "self#start" do
|
34
|
-
before do
|
35
|
-
Compatriot::Runner.start(TestApp, ["/"], @fixed_clock)
|
36
|
-
end
|
37
|
-
|
38
|
-
it "creates directories in which to store the results based on the date" do
|
39
|
-
assert File.exists?(@results_directory)
|
40
|
-
assert File.exists?(@firefox_directory)
|
41
|
-
assert File.exists?(@chrome_directory)
|
42
|
-
end
|
43
|
-
|
44
|
-
it "visits the home page and takes 1 screenshot per browser and diffs them" do
|
45
|
-
Dir.glob(File.join(@firefox_directory, "*.png")).size.must_equal 2
|
46
|
-
Dir.glob(File.join(@chrome_directory, "*.png")).size.must_equal 1
|
47
|
-
end
|
48
|
-
|
49
|
-
it "creates an index page that shows the screenshots" do
|
50
|
-
assert File.exists?(File.join(@results_directory, "index.html"))
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
require_relative 'spec_helper'
|
2
|
-
require 'fileutils'
|
3
|
-
require 'date'
|
4
|
-
require 'nokogiri'
|
5
|
-
|
6
|
-
describe "Hit a list of paths for this app" do
|
7
|
-
before do
|
8
|
-
@current_dir = Dir.getwd
|
9
|
-
Dir.chdir(File.join(File.dirname(__FILE__), 'sample_app'))
|
10
|
-
|
11
|
-
fake_date = DateTime.parse("2012-01-02 00:00:00 UTC")
|
12
|
-
fake_date_dir = fake_date.strftime("%Y-%m-%d-%H-%M-%S")
|
13
|
-
@fixed_clock = OpenStruct.new(:now => fake_date)
|
14
|
-
|
15
|
-
@results_directory = File.join("tmp", "results", fake_date_dir)
|
16
|
-
@firefox_directory = File.join(@results_directory, "firefox")
|
17
|
-
@chrome_directory = File.join(@results_directory, "chrome")
|
18
|
-
|
19
|
-
@x = Compatriot::Runner.start(TestApp, ["/", "/chrome-css-bug"], @fixed_clock)
|
20
|
-
end
|
21
|
-
|
22
|
-
after do
|
23
|
-
Dir.chdir(@current_dir)
|
24
|
-
end
|
25
|
-
|
26
|
-
it "takes a screenshot for each path given and diffs them" do
|
27
|
-
Dir.glob(File.join(@firefox_directory, "*.png")).size.must_equal 6
|
28
|
-
Dir.glob(File.join(@chrome_directory, "*.png")).size.must_equal 2
|
29
|
-
end
|
30
|
-
|
31
|
-
it "has 2 rows of results in the table on the results index" do
|
32
|
-
results_index = IO.read(File.join(@results_directory, "index.html"))
|
33
|
-
xml = Nokogiri::XML(results_index)
|
34
|
-
xml.xpath("//tr[td]").size.must_equal(2)
|
35
|
-
end
|
36
|
-
|
37
|
-
it "gets a list of the browsers in the results" do
|
38
|
-
@x.results.browsers.must_equal(["firefox", "chrome"])
|
39
|
-
end
|
40
|
-
|
41
|
-
it "gets a list of the paths in the results" do
|
42
|
-
@x.results.paths.must_equal(["/", "/chrome-css-bug"])
|
43
|
-
end
|
44
|
-
|
45
|
-
it "gets the filename of the screenshot for a particular browser and path" do
|
46
|
-
@x.results.screenshot_for("firefox", "/").must_equal("1.png")
|
47
|
-
@x.results.screenshot_for("firefox", "/chrome-css-bug").must_equal("2.png")
|
48
|
-
@x.results.screenshot_for("chrome", "/").must_equal("1.png")
|
49
|
-
@x.results.screenshot_for("chrome", "/chrome-css-bug").must_equal("2.png")
|
50
|
-
end
|
51
|
-
|
52
|
-
it "should have the home page row colored green and the chrome bug row colored red" do
|
53
|
-
@x.compute_diffs
|
54
|
-
end
|
55
|
-
end
|