compatriot 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rvmrc +47 -0
- data/Gemfile +4 -0
- data/README.md +100 -0
- data/Rakefile +11 -0
- data/compatriot.gemspec +23 -0
- data/examples/compatriot_tests.rb +9 -0
- data/lib/compatriot.rb +16 -0
- data/lib/compatriot/browser.rb +56 -0
- data/lib/compatriot/image_differ.rb +67 -0
- data/lib/compatriot/results.rb +43 -0
- data/lib/compatriot/results_presenter.rb +62 -0
- data/lib/compatriot/runner.rb +52 -0
- data/lib/compatriot/version.rb +3 -0
- data/spec/basic_behavior_spec.rb +53 -0
- data/spec/list_of_app_paths_spec.rb +55 -0
- data/spec/sample_app/public/images/smileyface.jpg +0 -0
- data/spec/sample_app/test_app.rb +21 -0
- data/spec/spec_helper.rb +8 -0
- metadata +114 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
7
|
+
environment_id="ruby-1.9.2@compatriot"
|
8
|
+
|
9
|
+
#
|
10
|
+
# First we attempt to load the desired environment directly from the environment
|
11
|
+
# file, this is very fast and efficicent compared to running through the entire
|
12
|
+
# CLI and selector. If you want feedback on which environment was used then
|
13
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
14
|
+
#
|
15
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
16
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] ; then
|
17
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
18
|
+
else
|
19
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
20
|
+
rvm --create "$environment_id"
|
21
|
+
fi
|
22
|
+
|
23
|
+
#
|
24
|
+
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
25
|
+
# it be automatically loaded, uncomment the following and adjust the filename if
|
26
|
+
# necessary.
|
27
|
+
#
|
28
|
+
# filename=".gems"
|
29
|
+
# if [[ -s "$filename" ]] ; then
|
30
|
+
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
31
|
+
# fi
|
32
|
+
|
33
|
+
#
|
34
|
+
# If you use bundler and would like to run bundle each time you enter the
|
35
|
+
# directory you can uncomment the following code.
|
36
|
+
#
|
37
|
+
# # Ensure that Bundler is installed, install it if it is not.
|
38
|
+
# if ! command -v bundle ; then
|
39
|
+
# printf "The rubygem 'bundler' is not installed, installing it now.\n"
|
40
|
+
# gem install bundler
|
41
|
+
# fi
|
42
|
+
#
|
43
|
+
# # Bundle while redcing excess noise.
|
44
|
+
# printf "Bundling your gems this may take a few minutes on a fresh clone.\n"
|
45
|
+
# bundle | grep -v 'Using' | grep -v 'complete' | sed '/^$/d'
|
46
|
+
#
|
47
|
+
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
Compatriot
|
2
|
+
----------
|
3
|
+
**Compat**ibility + **riot**! It's your **friend** that helps you with browser compatibility!
|
4
|
+
|
5
|
+
What this is
|
6
|
+
------------
|
7
|
+
|
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
|
+
What it does now
|
11
|
+
----------------
|
12
|
+
|
13
|
+
* Goes to the root path of a Rack app in firefox and chrome and takes a screenshot in each.
|
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.
|
16
|
+
|
17
|
+
What it will do in the future
|
18
|
+
-----------------------------
|
19
|
+
* Have documentation
|
20
|
+
* Have a screenshot of sample results in the README
|
21
|
+
* Have unit tests and better tests
|
22
|
+
* Be on travisci
|
23
|
+
* Not have a diff that's a different size than the originals
|
24
|
+
* Find the largest, darkest contiguous region in the image diff and have a threshold of pass/fail based on that
|
25
|
+
* Perform better on the image processing (by sampling/resizing, using oily_png, etc)
|
26
|
+
* Given a list of URLs/paths to visit, will take a screenshot of each and display which URL it came from in the index
|
27
|
+
* Given a test command that uses capybara, runs those tests and takes screenshots
|
28
|
+
* Automatically compare the screenshots across browsers and flags those that are more than some configurable threshold different
|
29
|
+
* Allow configuration of which browsers to use
|
30
|
+
* 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
|
+
|
32
|
+
What to do to use it
|
33
|
+
--------------------
|
34
|
+
|
35
|
+
### Firefox
|
36
|
+
* Need to have firefox installed
|
37
|
+
|
38
|
+
### Chrome
|
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
|
42
|
+
|
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
|
+
|
45
|
+
What to do to run its tests
|
46
|
+
---------------------------
|
47
|
+
Using at least ruby 1.9.2:
|
48
|
+
|
49
|
+
bundle install
|
50
|
+
bundle exec rake test
|
51
|
+
|
52
|
+
This is using minispec for testing.
|
53
|
+
|
54
|
+
What to do to contribute
|
55
|
+
------------------------
|
56
|
+
|
57
|
+
Contributions are very welcome! This is a very rough proof-of-concept at this point, so there are many opportunities for improvement. Feel free to:
|
58
|
+
|
59
|
+
* File a github issue with any problems or suggestions <3
|
60
|
+
* Fork and send a pull request with failing tests illustrating the bug or currently unsupported use case <3 <3
|
61
|
+
* Fork and send a pull request with bugfixes or new features with passing tests! <3 <3 <3
|
62
|
+
|
63
|
+
Standing on the shoulders of giants
|
64
|
+
-----------------------------------
|
65
|
+
|
66
|
+
Many thanks to the wonderful libraries that make this gem possible:
|
67
|
+
|
68
|
+
* [capybara](https://github.com/jnicklas/capybara)
|
69
|
+
* [selenium-webdriver](http://seleniumhq.org/docs/01_introducing_selenium.html#selenium-2-aka-selenium-webdriver)
|
70
|
+
* [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
|
+
|
72
|
+
Contributors
|
73
|
+
------------
|
74
|
+
* Carol Nichols ([twitter](http://twitter.com/carols10cents), [website](http://carol-nichols.com))
|
75
|
+
* Andrew Cox ([twitter](https://twitter.com/coxandrew), [website](http://andrewcox.org/))
|
76
|
+
* You???
|
77
|
+
|
78
|
+
License
|
79
|
+
-------
|
80
|
+
|
81
|
+
(The MIT License)
|
82
|
+
|
83
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
84
|
+
a copy of this software and associated documentation files (the
|
85
|
+
'Software'), to deal in the Software without restriction, including
|
86
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
87
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
88
|
+
permit persons to whom the Software is furnished to do so, subject to
|
89
|
+
the following conditions:
|
90
|
+
|
91
|
+
The above copyright notice and this permission notice shall be
|
92
|
+
included in all copies or substantial portions of the Software.
|
93
|
+
|
94
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
95
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
96
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
97
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
98
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
99
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
100
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
desc "Run tests"
|
6
|
+
task :test do
|
7
|
+
test_task = Rake::TestTask.new("tests") do |t|
|
8
|
+
t.test_files = Dir.glob(File.join("spec", "**", "*_spec.rb"))
|
9
|
+
end
|
10
|
+
task("tests").execute
|
11
|
+
end
|
data/compatriot.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/compatriot/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Carol Nichols"]
|
6
|
+
gem.email = ["carol.nichols@gmail.com"]
|
7
|
+
gem.description = %q{Finds likely UI browser cross-compatibility issues.}
|
8
|
+
gem.summary = %q{Runs a command in multiple browsers using selenium then compares the screenshots and presents those likely to have cross-browser incompatibilities.}
|
9
|
+
gem.homepage = "https://github.com/clnclarinet/compatriot"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "compatriot"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Compatriot::VERSION
|
17
|
+
|
18
|
+
gem.add_runtime_dependency "capybara", "~> 1.1.2"
|
19
|
+
gem.add_runtime_dependency "rake", "~> 0.9.2.2"
|
20
|
+
gem.add_runtime_dependency "chunky_png", "~> 1.2.5"
|
21
|
+
|
22
|
+
gem.add_development_dependency "sinatra", "~> 1.3.1"
|
23
|
+
end
|
data/lib/compatriot.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "compatriot/version"
|
2
|
+
require "compatriot/runner"
|
3
|
+
require "compatriot/browser"
|
4
|
+
require "compatriot/results_presenter"
|
5
|
+
require "compatriot/results"
|
6
|
+
require "compatriot/image_differ"
|
7
|
+
|
8
|
+
module Compatriot
|
9
|
+
class << self
|
10
|
+
attr_accessor :app
|
11
|
+
|
12
|
+
def run(paths)
|
13
|
+
Compatriot::Runner.start(app, paths)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require 'capybara/dsl'
|
3
|
+
|
4
|
+
module Compatriot
|
5
|
+
class Browser
|
6
|
+
include Capybara::DSL
|
7
|
+
|
8
|
+
def initialize(browser_name, results_directory)
|
9
|
+
@results_directory = results_directory
|
10
|
+
@browser_name = browser_name
|
11
|
+
@browser_selenium_id = translate_to_selenium(@browser_name)
|
12
|
+
@file_id = 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize_capybara(app)
|
16
|
+
driver = "selenium_#{@browser_name}".to_sym
|
17
|
+
Capybara.register_driver driver do |a|
|
18
|
+
Capybara::Selenium::Driver.new(a, :browser => @browser_selenium_id)
|
19
|
+
end
|
20
|
+
Capybara.default_driver = driver
|
21
|
+
Capybara.app = app
|
22
|
+
end
|
23
|
+
|
24
|
+
def take_screenshots(paths)
|
25
|
+
results = {}
|
26
|
+
paths.each do |path|
|
27
|
+
visit path
|
28
|
+
results[path] = screenshot
|
29
|
+
end
|
30
|
+
# Reset the selenium session to avoid timeout errors
|
31
|
+
Capybara.send(:session_pool).delete_if { |key, value| key =~ /selenium/i }
|
32
|
+
results
|
33
|
+
end
|
34
|
+
|
35
|
+
def screenshot
|
36
|
+
file_base_name = "#{@file_id}.png"
|
37
|
+
@file_id += 1
|
38
|
+
filepath = File.join(screenshot_path, file_base_name)
|
39
|
+
Capybara.page.driver.browser.save_screenshot(filepath)
|
40
|
+
file_base_name
|
41
|
+
end
|
42
|
+
|
43
|
+
def screenshot_path
|
44
|
+
return @screenshot_path if @screenshot_path
|
45
|
+
@screenshot_path = "#{@results_directory}/#{@browser_name}"
|
46
|
+
FileUtils.mkdir_p(@screenshot_path)
|
47
|
+
@screenshot_path
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def translate_to_selenium(browser_name)
|
53
|
+
browser_name.to_sym
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'chunky_png'
|
2
|
+
include ChunkyPNG::Color
|
3
|
+
|
4
|
+
module Compatriot
|
5
|
+
class ImageDiffer
|
6
|
+
|
7
|
+
def self.diff(results)
|
8
|
+
images = results.map{|r| ChunkyPNG::Image.from_file(r) }
|
9
|
+
f = self.color_difference(images, results.first)
|
10
|
+
File.join(
|
11
|
+
File.basename(File.dirname(f)),
|
12
|
+
File.basename(f)
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.same_pixels_exactly(images, name)
|
17
|
+
output = ChunkyPNG::Image.new(images.first.width, images.last.width, WHITE)
|
18
|
+
diff = []
|
19
|
+
|
20
|
+
images.first.height.times do |y|
|
21
|
+
images.first.row(y).each_with_index do |pixel, x|
|
22
|
+
output[x,y] = pixel
|
23
|
+
diff << [x,y] unless pixel == images.last[x,y]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
pixels_total = images.first.pixels.length
|
28
|
+
pixels_changed = diff.length
|
29
|
+
pixels_changed_percentage = (diff.length.to_f / images.first.pixels.length) * 100
|
30
|
+
|
31
|
+
x, y = diff.map{ |xy| xy[0] }, diff.map{ |xy| xy[1] }
|
32
|
+
|
33
|
+
output.rect(x.min, y.min, x.max, y.max, ChunkyPNG::Color.rgb(0,255,0))
|
34
|
+
filename = "#{name}-same_exactly.png"
|
35
|
+
output.save(filename)
|
36
|
+
filename
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.color_difference(images, name)
|
40
|
+
output = ChunkyPNG::Image.new(images.first.width, images.last.width, WHITE)
|
41
|
+
diff = []
|
42
|
+
|
43
|
+
images.first.height.times do |y|
|
44
|
+
images.first.row(y).each_with_index do |pixel, x|
|
45
|
+
unless pixel == images.last[x,y]
|
46
|
+
score = Math.sqrt(
|
47
|
+
(r(images.last[x,y]) - r(pixel)) ** 2 +
|
48
|
+
(g(images.last[x,y]) - g(pixel)) ** 2 +
|
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
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
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
|
+
filename = "#{name}-color_difference.png"
|
63
|
+
output.save(filename)
|
64
|
+
filename
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,43 @@
|
|
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
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Compatriot
|
4
|
+
class ResultsPresenter
|
5
|
+
def initialize(results_directory)
|
6
|
+
@results_directory = results_directory
|
7
|
+
end
|
8
|
+
|
9
|
+
def make_index_page(results)
|
10
|
+
index_page_file = File.join(@results_directory, "index.html")
|
11
|
+
|
12
|
+
b = binding
|
13
|
+
browsers = results.browsers
|
14
|
+
paths = results.paths
|
15
|
+
|
16
|
+
html = ERB.new <<-EOF
|
17
|
+
<html>
|
18
|
+
<head>
|
19
|
+
<title>Cross browser test results</title>
|
20
|
+
<style type='text/css'>
|
21
|
+
table, tr, th, td {
|
22
|
+
border: 1px solid #aaa;
|
23
|
+
}
|
24
|
+
img {
|
25
|
+
width: 300px;
|
26
|
+
}
|
27
|
+
</style>
|
28
|
+
</head>
|
29
|
+
<body>
|
30
|
+
<h1>Results</h1>
|
31
|
+
<table>
|
32
|
+
<tr>
|
33
|
+
<th>Path</th>
|
34
|
+
<% browsers.each do |browser| %>
|
35
|
+
<th><%= browser %></th>
|
36
|
+
<% end %>
|
37
|
+
<th>Diff</th>
|
38
|
+
</tr>
|
39
|
+
<% paths.each do |path| %>
|
40
|
+
<tr>
|
41
|
+
<td>
|
42
|
+
<%= path %>
|
43
|
+
</td>
|
44
|
+
<% browsers.each do |browser| %>
|
45
|
+
<td>
|
46
|
+
<img src="<%= browser %>/<%= results.screenshot_for(browser, path) %>" />
|
47
|
+
</td>
|
48
|
+
<% end %>
|
49
|
+
<td>
|
50
|
+
<img src="<%= results.diff_for(path) %>" />
|
51
|
+
</td>
|
52
|
+
</tr>
|
53
|
+
<% end %>
|
54
|
+
</table>
|
55
|
+
</body>
|
56
|
+
</html>
|
57
|
+
EOF
|
58
|
+
|
59
|
+
File.open(index_page_file, 'w') {|f| f.write(html.result(b)) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
module Compatriot
|
4
|
+
class Runner
|
5
|
+
def self.start(app, paths, clock = DateTime)
|
6
|
+
runner = new(app, paths, clock)
|
7
|
+
runner.take_screenshots
|
8
|
+
runner.compute_diffs
|
9
|
+
runner.make_index_page
|
10
|
+
runner
|
11
|
+
end
|
12
|
+
|
13
|
+
BROWSERS = ["firefox", "chrome"]
|
14
|
+
|
15
|
+
attr_reader :app, :results
|
16
|
+
|
17
|
+
def initialize(app, paths, clock)
|
18
|
+
@app = app
|
19
|
+
@paths = paths
|
20
|
+
@clock = clock
|
21
|
+
@results = Compatriot::Results.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def take_screenshots
|
25
|
+
BROWSERS.each do |b|
|
26
|
+
@results.take_screenshots(
|
27
|
+
:browser => b,
|
28
|
+
:app => @app,
|
29
|
+
:paths => @paths,
|
30
|
+
:results_directory => results_directory
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def compute_diffs
|
36
|
+
@results.compute_diffs
|
37
|
+
end
|
38
|
+
|
39
|
+
def make_index_page
|
40
|
+
presenter = Compatriot::ResultsPresenter.new(results_directory)
|
41
|
+
presenter.make_index_page(@results)
|
42
|
+
end
|
43
|
+
|
44
|
+
def results_directory
|
45
|
+
return @results_directory if @results_directory
|
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
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,53 @@
|
|
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
|
@@ -0,0 +1,55 @@
|
|
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
|
Binary file
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'rack'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
class TestApp < Sinatra::Base
|
6
|
+
set :root, File.dirname(__FILE__)
|
7
|
+
set :static, true
|
8
|
+
|
9
|
+
get '/' do
|
10
|
+
"<h1>Hello world!</h1"
|
11
|
+
end
|
12
|
+
|
13
|
+
get "/chrome-css-bug" do
|
14
|
+
"<table><tr style='background: url(/images/smileyface.jpg) no-repeat;'><td style='padding:30px;'>One</td><td style='padding:30px;'>Two</td></tr></table>"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
if __FILE__ == $0
|
20
|
+
Rack::Handler::WEBrick.run TestApp, :Port => 8070
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: compatriot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Carol Nichols
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-02 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: capybara
|
16
|
+
requirement: &2153232360 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.1.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2153232360
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &2153231860 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.9.2.2
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2153231860
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: chunky_png
|
38
|
+
requirement: &2153231400 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.2.5
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2153231400
|
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
|
58
|
+
description: Finds likely UI browser cross-compatibility issues.
|
59
|
+
email:
|
60
|
+
- carol.nichols@gmail.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- .rvmrc
|
67
|
+
- Gemfile
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- compatriot.gemspec
|
71
|
+
- examples/compatriot_tests.rb
|
72
|
+
- lib/compatriot.rb
|
73
|
+
- lib/compatriot/browser.rb
|
74
|
+
- lib/compatriot/image_differ.rb
|
75
|
+
- lib/compatriot/results.rb
|
76
|
+
- lib/compatriot/results_presenter.rb
|
77
|
+
- lib/compatriot/runner.rb
|
78
|
+
- lib/compatriot/version.rb
|
79
|
+
- spec/basic_behavior_spec.rb
|
80
|
+
- spec/list_of_app_paths_spec.rb
|
81
|
+
- spec/sample_app/public/images/smileyface.jpg
|
82
|
+
- spec/sample_app/test_app.rb
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
homepage: https://github.com/clnclarinet/compatriot
|
85
|
+
licenses: []
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.8.8
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Runs a command in multiple browsers using selenium then compares the screenshots
|
108
|
+
and presents those likely to have cross-browser incompatibilities.
|
109
|
+
test_files:
|
110
|
+
- spec/basic_behavior_spec.rb
|
111
|
+
- spec/list_of_app_paths_spec.rb
|
112
|
+
- spec/sample_app/public/images/smileyface.jpg
|
113
|
+
- spec/sample_app/test_app.rb
|
114
|
+
- spec/spec_helper.rb
|