rspec-page-regression 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +105 -0
- data/Rakefile +8 -0
- data/lib/rspec/page-regression.rb +19 -0
- data/lib/rspec/page-regression/file_paths.rb +43 -0
- data/lib/rspec/page-regression/image_comparison.rb +76 -0
- data/lib/rspec/page-regression/matcher.rb +40 -0
- data/lib/rspec/page-regression/renderer.rb +11 -0
- data/lib/rspec/page-regression/version.rb +5 -0
- data/rspec-page-regression.gemspec +34 -0
- data/spec/fixtures/A.png +0 -0
- data/spec/fixtures/ABdiff.png +0 -0
- data/spec/fixtures/B.png +0 -0
- data/spec/fixtures/Small.png +0 -0
- data/spec/match_expectation_spec.rb +157 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/helpers.rb +24 -0
- data/spec/support/matchers.rb +15 -0
- metadata +273 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 ronen barzel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# rspec-page-regression
|
2
|
+
|
3
|
+
Rspec-page-regression is an [RSpec](https://github.com/rspec/rspec) plugin
|
4
|
+
that makes it easy to regression test your web application pages, to make
|
5
|
+
sure the pages continue to look the way you expect them to look.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Rspec-page-regression uses [PhantomJS](http://www.phantomjs.org/) to render web page snapshots, by virtue of the [Poltergeist](https://github.com/jnicklas/capybara) driver for [Capybara](https://github.com/jnicklas/capybara). Assuming you have those installed and ready to go...
|
10
|
+
|
11
|
+
In your Gemfile:
|
12
|
+
|
13
|
+
gem 'rspec-page-regression'
|
14
|
+
|
15
|
+
And in your spec_helper:
|
16
|
+
|
17
|
+
require 'rspec'
|
18
|
+
require 'rspec/page-regression'
|
19
|
+
|
20
|
+
Rspec-page-regression presupposes the convention that your spec files are under a directory named `spec` (checked in to your repo), with a sibling directory `tmp` (.gitignore'd)
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Rspec-page-regression provides a matcher that renders the page and compares
|
25
|
+
the resulting image against an expected image. To use it, you need to enable
|
26
|
+
Capybara and Poltergeist by specifying `:type => :feature` and `:js => true`:
|
27
|
+
|
28
|
+
describe "my page", :type => :feature, :js => true do
|
29
|
+
|
30
|
+
before(:each) do
|
31
|
+
visit my_page_path
|
32
|
+
end
|
33
|
+
|
34
|
+
it { page.should match_expectation }
|
35
|
+
|
36
|
+
context "popup help" do
|
37
|
+
before(:each) do
|
38
|
+
click_button "Help"
|
39
|
+
end
|
40
|
+
|
41
|
+
it { page.should match_expectation }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
The spec will pass if the test rendered image contains the exact same pixel values as the expectated image. Otherwise it will fail with an error message along the lines of:
|
46
|
+
|
47
|
+
Test image does not match expected image
|
48
|
+
$ open tmp/spec/expectation/my_page/popup_help/test.png spec/expectation/my_page/popup_help/expected.png tmp/spec/expectation/my_page/popup_help/difference.png
|
49
|
+
|
50
|
+
Notice that the second line gives a command you can copy and paste in order to visually compare the test and expected images.
|
51
|
+
|
52
|
+
It also shows a "difference image" in which each pixel contains the per-channel absolute difference between the test and expected images. That is, the difference images is black everywhere except has some funky colored pixels where the test and expected images differ. To help you locate those, it also has a red bounding box drawn around the region with differences.
|
53
|
+
|
54
|
+
### How do you create expectation images?
|
55
|
+
|
56
|
+
The easiest way to create an expected image is to run the test for the first time and let it fail. You'll then get a failure message like:
|
57
|
+
|
58
|
+
Missing expectation image spec/expectation/my_page/popup_help/expected.png
|
59
|
+
$ open tmp/spec/expectation/my_page/popup_help/test.png
|
60
|
+
To create it:
|
61
|
+
$ mkdir -p spec/expectation/my_page/popup_help && cp tmp/spec/expectation/my_page/popup_help/test.png spec/expectation/my_page/popup_help/expected.png
|
62
|
+
|
63
|
+
First view the image to make sure it really is what you expect. Then copy and past the last line to install it. (And then of course commit it into your repository.)
|
64
|
+
|
65
|
+
### How do you update expectation images?
|
66
|
+
|
67
|
+
If you've deliberatly changed something that affects the look of your web page, then your regression test will fail. The "test" image will contain the new look, and the "expected" image will contain the old.
|
68
|
+
|
69
|
+
Once you've visually checked the test image to make sure it's really what you want, then simply copy the test image over the old expected image. (And then of course commit it it into your repository.)
|
70
|
+
|
71
|
+
The failure message doesn't include a ready-to-copy-and-paste `$ cp` command, but you can copy and past the individual file paths from the "does not match" message. (The reason not to have a ready-to-copy-and-paste command is that in most cases the failure will be real, and it shouldn't be too easy to mindlessly copy and past to make it go away.)
|
72
|
+
|
73
|
+
### Where are the expectation images?
|
74
|
+
|
75
|
+
As per the above examples, the expectation images default to being stored under `spec/expectation`, with the remainder of the path constructed from the example group descriptions. (If the `it` also has a description it will be used as well.)
|
76
|
+
|
77
|
+
If that default scheme doesn't suit you, you can pass a path to where the expectation image should be found:
|
78
|
+
|
79
|
+
page.should match_expectation "/path/to/my/file.png"
|
80
|
+
|
81
|
+
Everything else will work normally, and the failure messages will refer to your path.
|
82
|
+
|
83
|
+
## Configuration
|
84
|
+
|
85
|
+
The default window size for the renders is 1024 x 768. You can specify another size as `[height, width]` in pixels:
|
86
|
+
|
87
|
+
# in spec_helper.rb:
|
88
|
+
RSpec::PageRecression.configure do |config|
|
89
|
+
config.page_size = [1280, 1024]
|
90
|
+
end
|
91
|
+
|
92
|
+
Note that this specifies the size of the browser window viewport; but rspec-page-regression requests a render of the full page, which might extend beyond the window. So the rendered file sizes may be larger than this configuration value.
|
93
|
+
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
Contributions are welcome! As usual, here's the drill:
|
98
|
+
|
99
|
+
1. Fork it
|
100
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
101
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
102
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
103
|
+
5. Create new Pull Request
|
104
|
+
|
105
|
+
Don't forget to include specs (`rake spec`)! Code coverage should be 100%
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "rspec/page-regression/file_paths"
|
2
|
+
require "rspec/page-regression/image_comparison"
|
3
|
+
require "rspec/page-regression/matcher"
|
4
|
+
require "rspec/page-regression/renderer"
|
5
|
+
require "rspec/page-regression/version"
|
6
|
+
|
7
|
+
module RSpec::PageRegression
|
8
|
+
def self.configure
|
9
|
+
yield self
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.page_size= (page_size)
|
13
|
+
@@page_size = page_size
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.page_size
|
17
|
+
@@page_size ||= [1024, 768]
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
|
3
|
+
module RSpec::PageRegression
|
4
|
+
class FilePaths
|
5
|
+
attr_reader :expected_image
|
6
|
+
attr_reader :test_image
|
7
|
+
attr_reader :difference_image
|
8
|
+
|
9
|
+
def initialize(example, expected_path = nil)
|
10
|
+
expected_path = Pathname.new(expected_path) if expected_path
|
11
|
+
|
12
|
+
descriptions = description_ancestry(example.metadata)
|
13
|
+
descriptions.pop if descriptions.last =~ %r{
|
14
|
+
^
|
15
|
+
(then_+)? (page_+)? (should_+)? match_expectation
|
16
|
+
(_#{Regexp.escape(expected_path.to_s)})?
|
17
|
+
$
|
18
|
+
}xi
|
19
|
+
canonical_path = descriptions.map{|s| s.parameterize('_')}.inject(Pathname.new(""), &:+)
|
20
|
+
|
21
|
+
app_root = Pathname.new(example.metadata[:file_path]).realpath.each_filename.take_while{|c| c != "spec"}.inject(Pathname.new("/"), &:+)
|
22
|
+
expected_root = app_root + "spec" + "expectation"
|
23
|
+
test_root = app_root + "tmp" + "spec" + "expectation"
|
24
|
+
cwd = Pathname.getwd
|
25
|
+
|
26
|
+
@expected_image = expected_path || (expected_root + canonical_path + "expected.png").relative_path_from(cwd)
|
27
|
+
@test_image = (test_root + canonical_path + "test.png").relative_path_from cwd
|
28
|
+
@difference_image = (test_root + canonical_path + "difference.png").relative_path_from cwd
|
29
|
+
end
|
30
|
+
|
31
|
+
def all
|
32
|
+
[test_image, expected_image, difference_image]
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def description_ancestry(metadata)
|
39
|
+
return [] if metadata.nil?
|
40
|
+
description_ancestry(metadata[:example_group]) << metadata[:description].parameterize("_")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "oily_png"
|
2
|
+
|
3
|
+
module RSpec::PageRegression
|
4
|
+
|
5
|
+
class ImageComparison
|
6
|
+
include ChunkyPNG::Color
|
7
|
+
|
8
|
+
attr_reader :result
|
9
|
+
|
10
|
+
def initialize(filepaths)
|
11
|
+
@filepaths = filepaths
|
12
|
+
@result = compare
|
13
|
+
end
|
14
|
+
|
15
|
+
def expected_size
|
16
|
+
[@iexpected.width , @iexpected.height]
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_size
|
20
|
+
[@itest.width , @itest.height]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def compare
|
26
|
+
return :missing_expected unless @filepaths.expected_image.exist?
|
27
|
+
return :missing_test unless @filepaths.test_image.exist?
|
28
|
+
|
29
|
+
@iexpected = ChunkyPNG::Image.from_file(@filepaths.expected_image)
|
30
|
+
@itest = ChunkyPNG::Image.from_file(@filepaths.test_image)
|
31
|
+
|
32
|
+
return :size_mismatch if test_size != expected_size
|
33
|
+
|
34
|
+
return :match if pixels_match?
|
35
|
+
|
36
|
+
create_difference_image
|
37
|
+
return :difference
|
38
|
+
end
|
39
|
+
|
40
|
+
def pixels_match?
|
41
|
+
@itest.height.times do |y|
|
42
|
+
return false if @itest.row(y) != @iexpected.row(y)
|
43
|
+
end
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_difference_image
|
48
|
+
idiff = ChunkyPNG::Image.from_file(@filepaths.expected_image)
|
49
|
+
xmin = @itest.width + 1
|
50
|
+
xmax = -1
|
51
|
+
ymin = @itest.height + 1
|
52
|
+
ymax = -1
|
53
|
+
@itest.height.times do |y|
|
54
|
+
@itest.row(y).each_with_index do |test_pixel, x|
|
55
|
+
idiff[x,y] = if test_pixel != (expected_pixel = idiff[x,y])
|
56
|
+
xmin = x if x < xmin
|
57
|
+
xmax = x if x > xmax
|
58
|
+
ymin = y if y < ymin
|
59
|
+
ymax = y if y > ymax
|
60
|
+
rgb(
|
61
|
+
(r(test_pixel) - r(expected_pixel)).abs,
|
62
|
+
(g(test_pixel) - g(expected_pixel)).abs,
|
63
|
+
(b(test_pixel) - b(expected_pixel)).abs
|
64
|
+
)
|
65
|
+
else
|
66
|
+
rgb(0,0,0)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
idiff.rect(xmin-1,ymin-1,xmax+1,ymax+1,rgb(255,0,0))
|
72
|
+
|
73
|
+
idiff.save @filepaths.difference_image
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'which_works'
|
2
|
+
|
3
|
+
module RSpec::PageRegression
|
4
|
+
|
5
|
+
RSpec::Matchers.define :match_expectation do |expectation_path|
|
6
|
+
|
7
|
+
match do |page|
|
8
|
+
@filepaths = FilePaths.new(example, expectation_path)
|
9
|
+
Renderer.render(page, @filepaths.test_image)
|
10
|
+
@comparison = ImageComparison.new(@filepaths)
|
11
|
+
@comparison.result == :match
|
12
|
+
end
|
13
|
+
|
14
|
+
failure_message_for_should do |page|
|
15
|
+
msg = case @comparison.result
|
16
|
+
when :missing_expected then "Missing expectation image #{@filepaths.expected_image}"
|
17
|
+
when :missing_test then "Missing test image #{@filepaths.test_image}"
|
18
|
+
when :size_mismatch then "Test image size #{@comparison.test_size.join('x')} does not match expectation #{@comparison.expected_size.join('x')}"
|
19
|
+
when :difference then "Test image does not match expected image"
|
20
|
+
end
|
21
|
+
|
22
|
+
msg += "\n $ #{viewer} #{@filepaths.all.select(&:exist?).join(' ')}"
|
23
|
+
|
24
|
+
case @comparison.result
|
25
|
+
when :missing_expected
|
26
|
+
msg += "\nCreate it via:\n $ mkdir -p #{@filepaths.expected_image.dirname} && cp #{@filepaths.test_image} #{@filepaths.expected_image}"
|
27
|
+
end
|
28
|
+
|
29
|
+
msg
|
30
|
+
end
|
31
|
+
|
32
|
+
failure_message_for_should_not do |page|
|
33
|
+
"Test image should not match expectation image"
|
34
|
+
end
|
35
|
+
|
36
|
+
def viewer
|
37
|
+
File.basename(Which.which("open", "feh", "display", :array => true).first || "viewer")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module RSpec::PageRegression
|
2
|
+
module Renderer
|
3
|
+
|
4
|
+
def self.render(page, test_image_path)
|
5
|
+
|
6
|
+
test_image_path.dirname.mkpath unless test_image_path.dirname.exist?
|
7
|
+
page.driver.resize *RSpec::PageRegression.page_size
|
8
|
+
page.driver.render test_image_path, :full => true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rspec/page-regression/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rspec-page-regression"
|
8
|
+
spec.version = RSpec::PageRegression::VERSION
|
9
|
+
spec.authors = ["ronen barzel"]
|
10
|
+
spec.email = ["ronen@barzel.org"]
|
11
|
+
spec.summary = %q{Web page rendering (html, css, and javascript) regression for RSpec}
|
12
|
+
spec.description = %q{Rspec-page-regression provides a mechanism for regression testing of web page renders in RSpec. It takes into account html, css, and javascript, by virtue of using phantomjs (via the poltergeist gem) to render snapshots. It provides an RSpec matcher that compares the test snapshot to an expected image, and facilitates management of the images.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activesupport"
|
22
|
+
spec.add_dependency "oily_png"
|
23
|
+
spec.add_dependency "poltergeist"
|
24
|
+
spec.add_dependency "rspec"
|
25
|
+
spec.add_dependency "which_works"
|
26
|
+
|
27
|
+
spec.add_development_dependency "bourne"
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
29
|
+
spec.add_development_dependency "mocha"
|
30
|
+
spec.add_development_dependency "rake"
|
31
|
+
spec.add_development_dependency "rspec-given"
|
32
|
+
spec.add_development_dependency "simplecov"
|
33
|
+
spec.add_development_dependency "simplecov-gem-adapter"
|
34
|
+
end
|
data/spec/fixtures/A.png
ADDED
Binary file
|
Binary file
|
data/spec/fixtures/B.png
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe "match_expectation" do
|
5
|
+
|
6
|
+
Given {
|
7
|
+
@opts = { :full => true }
|
8
|
+
@driver = mock("Driver")
|
9
|
+
@driver.stubs :resize
|
10
|
+
@driver.stubs :render
|
11
|
+
@page = mock("Page")
|
12
|
+
@page.stubs(:driver).returns @driver
|
13
|
+
@match_argument = nil
|
14
|
+
}
|
15
|
+
|
16
|
+
context "using should" do
|
17
|
+
|
18
|
+
When {
|
19
|
+
begin
|
20
|
+
@page.should match_expectation @match_argument
|
21
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
22
|
+
@error = e
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
context "framework" do
|
27
|
+
Then { @driver.should have_received(:resize).with(1024, 768) }
|
28
|
+
Then { @driver.should have_received(:render).with(test_path, @opts) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when files match" do
|
32
|
+
Given { use_test_image "A" }
|
33
|
+
Given { use_expected_image "A" }
|
34
|
+
|
35
|
+
Then { @error.should be_nil }
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
context "when files do not match" do
|
40
|
+
Given { use_test_image "A" }
|
41
|
+
Given { use_expected_image "B" }
|
42
|
+
|
43
|
+
Then { @error.should_not be_nil }
|
44
|
+
Then { @error.message.should include "Test image does not match expected image" }
|
45
|
+
Then { @error.message.should =~ viewer_pattern(test_path, expected_path, difference_path) }
|
46
|
+
|
47
|
+
Then { difference_path.read.should == fixture_image("ABdiff").read }
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when test image is missing" do
|
51
|
+
Given { use_expected_image "A" }
|
52
|
+
|
53
|
+
Then { @error.should_not be_nil }
|
54
|
+
Then { @error.message.should include "Missing test image #{test_path}" }
|
55
|
+
Then { @error.message.should =~ viewer_pattern(expected_path) }
|
56
|
+
end
|
57
|
+
|
58
|
+
context "when expected image is missing" do
|
59
|
+
Given { use_test_image "A" }
|
60
|
+
|
61
|
+
Then { @error.should_not be_nil }
|
62
|
+
Then { @error.message.should include "Missing expectation image #{expected_path}" }
|
63
|
+
Then { @error.message.should =~ viewer_pattern(test_path) }
|
64
|
+
Then { @error.message.should include "mkdir -p #{expected_path.dirname} && cp #{test_path} #{expected_path}" }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when sizes mismatch" do
|
68
|
+
Given { use_test_image "Small" }
|
69
|
+
Given { use_expected_image "A" }
|
70
|
+
|
71
|
+
Then { @error.should_not be_nil }
|
72
|
+
Then { @error.message.should include "Test image size 256x167 does not match expectation 512x334" }
|
73
|
+
Then { @error.message.should =~ viewer_pattern(test_path, expected_path) }
|
74
|
+
end
|
75
|
+
|
76
|
+
context "with match argument" do
|
77
|
+
Given { @match_argument = "/this/is/a/test.png" }
|
78
|
+
Then { @error.message.should include "Missing expectation image /this/is/a/test.png" }
|
79
|
+
end
|
80
|
+
|
81
|
+
context "with trivial example description" do
|
82
|
+
Given do
|
83
|
+
RSpec::Core::Example.any_instance.stubs :metadata => {
|
84
|
+
file_path: __FILE__,
|
85
|
+
description: "Then page.should match_expectation",
|
86
|
+
example_group: { description: "parent" }
|
87
|
+
}
|
88
|
+
end
|
89
|
+
Then { @driver.should have_received(:render).with(Pathname.new("tmp/spec/expectation/parent/test.png"), @opts) }
|
90
|
+
Then { @error.message.should include "Missing expectation image spec/expectation/parent/expected.png" }
|
91
|
+
end
|
92
|
+
|
93
|
+
context "with page size configuration" do
|
94
|
+
Given do
|
95
|
+
RSpec::PageRegression.configure do |config|
|
96
|
+
config.page_size = [123, 456]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
Then { @driver.should have_received(:resize).with(123, 456) }
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
context "using should_not" do
|
105
|
+
When {
|
106
|
+
begin
|
107
|
+
@page.should_not match_expectation
|
108
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
109
|
+
@error = e
|
110
|
+
end
|
111
|
+
}
|
112
|
+
|
113
|
+
context "when files don't match" do
|
114
|
+
Given { use_test_image "A" }
|
115
|
+
Given { use_expected_image "B" }
|
116
|
+
|
117
|
+
Then { @error.should be_nil }
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when files match" do
|
121
|
+
Given { use_test_image "A" }
|
122
|
+
Given { use_expected_image "A" }
|
123
|
+
|
124
|
+
Then { @error.should_not be_nil }
|
125
|
+
Then { @error.message.should == "Test image should not match expectation image" }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def fixture_image(name)
|
130
|
+
FixturesDir + "#{name}.png"
|
131
|
+
end
|
132
|
+
|
133
|
+
def use_fixture_image(name, path)
|
134
|
+
path.dirname.mkpath unless path.dirname.exist?
|
135
|
+
FileUtils.cp fixture_image(name), path
|
136
|
+
end
|
137
|
+
|
138
|
+
def use_test_image(name)
|
139
|
+
use_fixture_image(name, test_path)
|
140
|
+
end
|
141
|
+
|
142
|
+
def use_expected_image(name)
|
143
|
+
use_fixture_image(name, expected_path)
|
144
|
+
end
|
145
|
+
|
146
|
+
def viewer_pattern(*paths)
|
147
|
+
%r{
|
148
|
+
\b
|
149
|
+
(open|feh|display|viewer)
|
150
|
+
\s
|
151
|
+
#{paths.map{|path| Regexp.escape(path.to_s)}.join('\s')}
|
152
|
+
\s*$
|
153
|
+
}x
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'simplecov-gem-adapter'
|
3
|
+
SimpleCov.start "gem"
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
|
7
|
+
require 'rspec'
|
8
|
+
require 'rspec/given'
|
9
|
+
require 'rspec/page-regression'
|
10
|
+
require 'bourne'
|
11
|
+
|
12
|
+
SpecDir = Pathname.new(__FILE__).dirname
|
13
|
+
RootDir = SpecDir.dirname
|
14
|
+
TestDir = RootDir + "tmp/spec"
|
15
|
+
FixturesDir = SpecDir + "fixtures"
|
16
|
+
|
17
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
18
|
+
|
19
|
+
RSpec.configure do |config|
|
20
|
+
config.mock_with :mocha
|
21
|
+
config.include Helpers
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Helpers
|
2
|
+
|
3
|
+
def test_path
|
4
|
+
getpath(TestDir, "test")
|
5
|
+
end
|
6
|
+
|
7
|
+
def expected_path
|
8
|
+
getpath(SpecDir, "expected")
|
9
|
+
end
|
10
|
+
|
11
|
+
def difference_path
|
12
|
+
getpath(TestDir, "difference")
|
13
|
+
end
|
14
|
+
|
15
|
+
def getpath(root, base)
|
16
|
+
(root + "expectation" + group_path(example.metadata) + "#{base}.png").relative_path_from Pathname.getwd
|
17
|
+
end
|
18
|
+
|
19
|
+
def group_path(metadata)
|
20
|
+
return Pathname.new("") if metadata.nil?
|
21
|
+
group_path(metadata[:example_group]) + metadata[:description].parameterize("_")
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
def fail
|
4
|
+
raise_error(RSpec::Expectations::ExpectationNotMetError)
|
5
|
+
end
|
6
|
+
|
7
|
+
def fail_with(message)
|
8
|
+
raise_error(RSpec::Expectations::ExpectationNotMetError, message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def fail_matching(message)
|
12
|
+
raise_error(RSpec::Expectations::ExpectationNotMetError, /#{Regexp.escape(message)}/)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-page-regression
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- ronen barzel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: oily_png
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: poltergeist
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: which_works
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: bourne
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: bundler
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.3'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '1.3'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: mocha
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: rake
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: rspec-given
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: simplecov
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
type: :development
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: simplecov-gem-adapter
|
192
|
+
requirement: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :development
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
206
|
+
description: Rspec-page-regression provides a mechanism for regression testing of
|
207
|
+
web page renders in RSpec. It takes into account html, css, and javascript, by
|
208
|
+
virtue of using phantomjs (via the poltergeist gem) to render snapshots. It provides
|
209
|
+
an RSpec matcher that compares the test snapshot to an expected image, and facilitates
|
210
|
+
management of the images.
|
211
|
+
email:
|
212
|
+
- ronen@barzel.org
|
213
|
+
executables: []
|
214
|
+
extensions: []
|
215
|
+
extra_rdoc_files: []
|
216
|
+
files:
|
217
|
+
- .gitignore
|
218
|
+
- .rspec
|
219
|
+
- .travis.yml
|
220
|
+
- Gemfile
|
221
|
+
- LICENSE.txt
|
222
|
+
- README.md
|
223
|
+
- Rakefile
|
224
|
+
- lib/rspec/page-regression.rb
|
225
|
+
- lib/rspec/page-regression/file_paths.rb
|
226
|
+
- lib/rspec/page-regression/image_comparison.rb
|
227
|
+
- lib/rspec/page-regression/matcher.rb
|
228
|
+
- lib/rspec/page-regression/renderer.rb
|
229
|
+
- lib/rspec/page-regression/version.rb
|
230
|
+
- rspec-page-regression.gemspec
|
231
|
+
- spec/fixtures/A.png
|
232
|
+
- spec/fixtures/ABdiff.png
|
233
|
+
- spec/fixtures/B.png
|
234
|
+
- spec/fixtures/Small.png
|
235
|
+
- spec/match_expectation_spec.rb
|
236
|
+
- spec/spec_helper.rb
|
237
|
+
- spec/support/helpers.rb
|
238
|
+
- spec/support/matchers.rb
|
239
|
+
homepage: ''
|
240
|
+
licenses:
|
241
|
+
- MIT
|
242
|
+
post_install_message:
|
243
|
+
rdoc_options: []
|
244
|
+
require_paths:
|
245
|
+
- lib
|
246
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
247
|
+
none: false
|
248
|
+
requirements:
|
249
|
+
- - ! '>='
|
250
|
+
- !ruby/object:Gem::Version
|
251
|
+
version: '0'
|
252
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
253
|
+
none: false
|
254
|
+
requirements:
|
255
|
+
- - ! '>='
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: '0'
|
258
|
+
requirements: []
|
259
|
+
rubyforge_project:
|
260
|
+
rubygems_version: 1.8.25
|
261
|
+
signing_key:
|
262
|
+
specification_version: 3
|
263
|
+
summary: Web page rendering (html, css, and javascript) regression for RSpec
|
264
|
+
test_files:
|
265
|
+
- spec/fixtures/A.png
|
266
|
+
- spec/fixtures/ABdiff.png
|
267
|
+
- spec/fixtures/B.png
|
268
|
+
- spec/fixtures/Small.png
|
269
|
+
- spec/match_expectation_spec.rb
|
270
|
+
- spec/spec_helper.rb
|
271
|
+
- spec/support/helpers.rb
|
272
|
+
- spec/support/matchers.rb
|
273
|
+
has_rdoc:
|