cezanne 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/cezanne.gemspec +30 -0
- data/lib/cezanne/comparison.rb +14 -0
- data/lib/cezanne/image.rb +27 -0
- data/lib/cezanne/local_files.rb +36 -0
- data/lib/cezanne/remote_files.rb +27 -0
- data/lib/cezanne/rspec.rb +43 -0
- data/lib/cezanne/version.rb +3 -0
- data/lib/cezanne.rb +56 -0
- data/spec/cezanne_filesystem_spec.rb +75 -0
- data/spec/cezanne_image_spec.rb +55 -0
- data/spec/cezanne_rspec_spec.rb +100 -0
- data/spec/cezanne_spec.rb +215 -0
- data/spec/images/different.html +9 -0
- data/spec/images/new.html +9 -0
- data/spec/images/page_name_2_browser.gif +0 -0
- data/spec/images/page_name_browser.gif +0 -0
- data/spec/images/similar.html +9 -0
- data/spec/spec_helper.rb +6 -0
- metadata +189 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3b042161b943e9c613765446eb96de02c932c1ed
|
4
|
+
data.tar.gz: ea2b8b6803ed4cb416da7dc9d031265f8ce368f4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f839e12234ddb4d54248264aa6d974191d129c705652eb01f98d860463e6a9e47d1f68e9bf77c981e7e41253aecd4546fceb3f86002d3fe6a290a8643a372b3f
|
7
|
+
data.tar.gz: 42f2832a17ce4a8cc10bed8ae5bedaca3c334ef953da157a8e30751e90597b26605a53cc2b9eb9ef3966290a0fef77533b01c2e8e7ef0d7e05fbbc4e2ee875e5
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Andrea Pigato
|
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,29 @@
|
|
1
|
+
# Cezanne
|
2
|
+
|
3
|
+
Visual regression testing tool
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'cezanne'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install cezanne
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
coming soon
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( http://github.com/<my-github-username>/cezanne/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/cezanne.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cezanne/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cezanne"
|
8
|
+
spec.version = Cezanne::VERSION
|
9
|
+
spec.authors = ["Sky Haiku"]
|
10
|
+
spec.email = ["skyhaikuteam@gmail.com"]
|
11
|
+
spec.summary = %q{Visual regression testing tool}
|
12
|
+
spec.description = %q{Let Cezanne help you make sure your views look alright}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
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_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "pry"
|
25
|
+
spec.add_development_dependency "capybara"
|
26
|
+
spec.add_development_dependency "selenium-webdriver"
|
27
|
+
|
28
|
+
spec.add_dependency "rmagick"
|
29
|
+
spec.add_dependency "dropscreen"
|
30
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
module Cezanne
|
4
|
+
|
5
|
+
SIMILARITY_THRESHOLD = 42
|
6
|
+
|
7
|
+
def spot_differences_between this, that
|
8
|
+
width = [this.width, that.width].min
|
9
|
+
height = [this.height, that.height].min
|
10
|
+
[this, that].each { |img| img.crop!(width, height) }
|
11
|
+
this.picture.compare_channel(that.picture, Magick::PeakSignalToNoiseRatioMetric)[1] < SIMILARITY_THRESHOLD
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
module Cezanne
|
4
|
+
|
5
|
+
class Image
|
6
|
+
attr_reader :path, :picture
|
7
|
+
|
8
|
+
def initialize path
|
9
|
+
@path = path
|
10
|
+
@picture = Magick::Image.read(@path).first
|
11
|
+
end
|
12
|
+
|
13
|
+
def crop! *coords
|
14
|
+
if coords.length == 2 then coords = [0, 0, *coords] end # treat them as widht and height
|
15
|
+
@picture.crop!(*coords)
|
16
|
+
end
|
17
|
+
|
18
|
+
def width
|
19
|
+
@picture.columns
|
20
|
+
end
|
21
|
+
|
22
|
+
def height
|
23
|
+
@picture.rows
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Cezanne
|
2
|
+
|
3
|
+
class LocalFiles
|
4
|
+
|
5
|
+
def initialize uid, base_path
|
6
|
+
|
7
|
+
@folders = {
|
8
|
+
ref: File.join(base_path, 'reference_screenshots'),
|
9
|
+
diff: File.join(base_path, uid, 'different_screenshots'),
|
10
|
+
new: File.join(base_path, uid, 'new_screenshots'),
|
11
|
+
tmp: File.join(base_path, uid, 'tmp_screenshots')
|
12
|
+
}
|
13
|
+
|
14
|
+
@folders.each { |key, path| setup_directory_for key }
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def path_for key
|
19
|
+
@folders[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def clean
|
23
|
+
path = File.dirname( path_for(:ref) )
|
24
|
+
FileUtils.rm_rf path
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def setup_directory_for key
|
30
|
+
FileUtils.rm_rf path_for(key)
|
31
|
+
FileUtils.mkdir_p path_for(key)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'dropscreen'
|
2
|
+
|
3
|
+
module Cezanne
|
4
|
+
|
5
|
+
class RemoteFiles
|
6
|
+
|
7
|
+
def initialize uid, base_path
|
8
|
+
@remote_folders = {
|
9
|
+
ref: File.join('reference_screenshots'),
|
10
|
+
diff: File.join(uid, 'different_screenshots'),
|
11
|
+
new: File.join(uid, 'new_screenshots')
|
12
|
+
}
|
13
|
+
|
14
|
+
@adapter = Dropscreen::Client.new( remote_base_dir: base_path, local_base_dir: './' )
|
15
|
+
end
|
16
|
+
|
17
|
+
def pull key, path
|
18
|
+
@adapter.pull @remote_folders[key], path
|
19
|
+
end
|
20
|
+
|
21
|
+
def push path, key
|
22
|
+
@adapter.push path, @remote_folders[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'cezanne'
|
2
|
+
require 'rspec/core'
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
|
6
|
+
config.add_setting :cezanne
|
7
|
+
|
8
|
+
config.before(:all, screenshots: true) do
|
9
|
+
if self.class.include?(Cezanne)
|
10
|
+
uid = config.cezanne[:uid]
|
11
|
+
project_name = config.cezanne[:project_name]
|
12
|
+
config.cezanne[:local_files] = Cezanne::LocalFiles.new(uid, 'artifacts')
|
13
|
+
config.cezanne[:remote_files] = Cezanne::RemoteFiles.new(uid, project_name)
|
14
|
+
begin
|
15
|
+
config.cezanne[:remote_files].pull(:ref, config.cezanne[:local_files].path_for(:ref))
|
16
|
+
rescue
|
17
|
+
puts "no reference screenshot exist for project #{project_name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
config.after(:all, screenshots: true) do
|
23
|
+
if self.class.include?(Cezanne)
|
24
|
+
[:new, :diff].each do |key|
|
25
|
+
config.cezanne[:remote_files].push(config.cezanne[:local_files].path_for(key), key)
|
26
|
+
end
|
27
|
+
config.cezanne[:local_files].clean
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
module Cezanne
|
34
|
+
|
35
|
+
def local_files
|
36
|
+
RSpec.configuration.cezanne[:local_files]
|
37
|
+
end
|
38
|
+
|
39
|
+
def remote_files
|
40
|
+
RSpec.configuration.cezanne[:remote_files]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/cezanne.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require "cezanne/version"
|
2
|
+
require "cezanne/local_files"
|
3
|
+
require "cezanne/remote_files"
|
4
|
+
require "cezanne/image"
|
5
|
+
require "cezanne/comparison"
|
6
|
+
|
7
|
+
module Cezanne
|
8
|
+
|
9
|
+
def check_visual_regression_for page_name
|
10
|
+
screenshot = take_screenshot page_name
|
11
|
+
reference_screenshot = get_reference_screenshot_for page_name
|
12
|
+
|
13
|
+
unless reference_screenshot
|
14
|
+
mark_as_new screenshot
|
15
|
+
raise "new screenshot for #{page_name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
if spot_differences_between screenshot, reference_screenshot
|
19
|
+
mark_for_review screenshot
|
20
|
+
raise "screenshot for #{page_name} didn't match"
|
21
|
+
end
|
22
|
+
|
23
|
+
return true
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def take_screenshot page_name
|
29
|
+
path = File.join( local_files.path_for(:tmp), file_name_for(page_name) )
|
30
|
+
page.driver.browser.save_screenshot(path)
|
31
|
+
image(path)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def get_reference_screenshot_for page_name
|
36
|
+
path = File.join( local_files.path_for(:ref), file_name_for(page_name) )
|
37
|
+
return false unless File.exists? path
|
38
|
+
image(path)
|
39
|
+
end
|
40
|
+
|
41
|
+
def file_name_for page_name
|
42
|
+
"#{page_name}_#{page.driver.browser.browser.to_s}.gif"
|
43
|
+
end
|
44
|
+
|
45
|
+
def mark_as_new screenshot
|
46
|
+
FileUtils.mv(screenshot.path, local_files.path_for(:new))
|
47
|
+
end
|
48
|
+
|
49
|
+
def mark_for_review screenshot
|
50
|
+
FileUtils.mv(screenshot.path, local_files.path_for(:diff))
|
51
|
+
end
|
52
|
+
|
53
|
+
def image path
|
54
|
+
Cezanne::Image.new(path)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cezanne::LocalFiles do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@local_files = Cezanne::LocalFiles.new 'test', 'spec/artifacts'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#initialize' do
|
10
|
+
|
11
|
+
it 'create local folders for the screenshots' do
|
12
|
+
expect(File.exists?('spec/artifacts/reference_screenshots')).to be true
|
13
|
+
expect(File.exists?('spec/artifacts/test/different_screenshots')).to be true
|
14
|
+
expect(File.exists?('spec/artifacts/test/new_screenshots')).to be true
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#path_for' do
|
20
|
+
|
21
|
+
it 'is a path helper' do
|
22
|
+
[:ref, :diff, :new, :tmp].each do |key|
|
23
|
+
expect(@local_files.path_for(key)).to eq(@local_files.instance_variable_get(:@folders)[key])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#clean' do
|
30
|
+
|
31
|
+
it 'remove all local folders' do
|
32
|
+
@local_files.clean
|
33
|
+
expect(File.exists?('spec/artifacts/local_files_test')).to be false
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
after(:each) do
|
39
|
+
FileUtils.rm_rf 'spec/artifacts'
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe Cezanne::RemoteFiles do
|
45
|
+
|
46
|
+
let(:dropscreen_client) { double('Dropscreen::Client') }
|
47
|
+
|
48
|
+
before(:each) do
|
49
|
+
allow(Dropscreen::Client).to receive('new').and_return(dropscreen_client)
|
50
|
+
@remote_files = Cezanne::RemoteFiles.new 'test_uid', 'cezanne'
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#pull' do
|
54
|
+
|
55
|
+
it 'is a proxy to Dropscreen::Client#pull' do
|
56
|
+
expect(dropscreen_client).to receive(:pull).at_least(1).times.with(kind_of(String), 'local_path')
|
57
|
+
[:ref, :diff, :new].each do |key|
|
58
|
+
@remote_files.pull key, 'local_path'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#push' do
|
65
|
+
|
66
|
+
it 'is a proxy to Dropscreen::Client#push' do
|
67
|
+
expect(dropscreen_client).to receive(:push).at_least(1).times.with('local_path', kind_of(String))
|
68
|
+
[:ref, :diff, :new].each do |key|
|
69
|
+
@remote_files.push 'local_path', key
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cezanne::Image do
|
4
|
+
|
5
|
+
let(:image) { Cezanne::Image.new('spec/images/page_name_browser.gif') }
|
6
|
+
|
7
|
+
describe '#initialize' do
|
8
|
+
|
9
|
+
it 'wraps Magick::Image' do
|
10
|
+
expect(image.picture).to be_instance_of(Magick::Image)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'return an error if there is no file at path' do
|
14
|
+
expect { Cezanne::Image.new('unexistent_file_path') }.to raise_error
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#width' do
|
19
|
+
|
20
|
+
it 'has a width' do
|
21
|
+
expect(image.width).to be 763
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#height' do
|
27
|
+
|
28
|
+
it 'has a height' do
|
29
|
+
expect(image.height).to be 599
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#crop!' do
|
35
|
+
|
36
|
+
it 'accepts 4 parameters x, y, width, height' do
|
37
|
+
x = 1
|
38
|
+
y = 2
|
39
|
+
width = 5
|
40
|
+
height = 10
|
41
|
+
image.crop!(x,y,width,height)
|
42
|
+
expect(image.width).to be 5
|
43
|
+
expect(image.height).to be 10
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'accepts width and height alone' do
|
47
|
+
width = 10
|
48
|
+
height = 5
|
49
|
+
image.crop!(width, height)
|
50
|
+
expect(image.width).to be 10
|
51
|
+
expect(image.height).to be 5
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'cezanne/rspec'
|
2
|
+
require 'capybara/rspec'
|
3
|
+
require 'selenium-webdriver'
|
4
|
+
require 'pry'
|
5
|
+
|
6
|
+
|
7
|
+
Capybara.app = Rack::Directory.new('spec/images')
|
8
|
+
Capybara.default_driver = :selenium
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.include Cezanne
|
12
|
+
config.cezanne = { uid: 'test', project_name: 'cezanne' }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'Cezanne RSpec integration', type: :feature, screenshots: true do
|
16
|
+
|
17
|
+
# wrap each test with before(:all) and after(:all)
|
18
|
+
|
19
|
+
after(:all) do |example|
|
20
|
+
RSpec.configuration.after(:all, screenshots: true).last.instance_variable_set(:@block, Proc.new {}) # disable the after(:all) hook
|
21
|
+
end
|
22
|
+
|
23
|
+
before(:each) do |example|
|
24
|
+
RSpec.configuration.before(:all, screenshots: true).first.run(example)
|
25
|
+
end
|
26
|
+
|
27
|
+
after(:each) do |example|
|
28
|
+
RSpec.configuration.after(:all, screenshots: true).last.run(example) unless example.metadata[:after_hook_test]
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
describe 'initialization' do
|
37
|
+
|
38
|
+
it 'create local folders' do
|
39
|
+
expect(File.exist?('artifacts/reference_screenshots')).to be true
|
40
|
+
expect(File.exist?('artifacts/test/tmp_screenshots')).to be true
|
41
|
+
expect(File.exist?('artifacts/test/different_screenshots')).to be true
|
42
|
+
expect(File.exist?('artifacts/test/new_screenshots')).to be true
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
it 'pull reference_screenshots' do
|
47
|
+
expect(File.exist?('artifacts/reference_screenshots/similar_firefox.gif')).to be true
|
48
|
+
expect(File.exist?('artifacts/reference_screenshots/different_firefox.gif')).to be true
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'take screenshots' do
|
54
|
+
|
55
|
+
context 'similar' do
|
56
|
+
|
57
|
+
it 'pass the test' do
|
58
|
+
visit '/similar.html'
|
59
|
+
expect { check_visual_regression_for 'similar' }.not_to raise_error
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'different' do
|
65
|
+
|
66
|
+
it 'fail the test' do
|
67
|
+
visit '/different.html'
|
68
|
+
expect { check_visual_regression_for 'different' }.to raise_error
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
context 'new' do
|
75
|
+
|
76
|
+
it 'fail the test' do
|
77
|
+
visit '/new.html'
|
78
|
+
expect { check_visual_regression_for 'new' }.to raise_error
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'finalization', after_hook_test: true do
|
86
|
+
|
87
|
+
it 'push new, diff screenshots to remote' do |example|
|
88
|
+
expect(RSpec.configuration.cezanne[:remote_files]).to receive('push').with(kind_of(String), :diff).and_call_original
|
89
|
+
expect(RSpec.configuration.cezanne[:remote_files]).to receive('push').with(kind_of(String), :new).and_call_original
|
90
|
+
RSpec.configuration.after(:all, screenshots: true).last.run(example)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'clean local folders' do |example|
|
94
|
+
RSpec.configuration.after(:all, screenshots: true).last.run(example)
|
95
|
+
expect(File.exists?('artifacts')).to be false
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cezanne do
|
4
|
+
|
5
|
+
let(:class_with_cezanne) { Class.include(Cezanne).new }
|
6
|
+
# Capybara mocks
|
7
|
+
let(:page) { double('page') }
|
8
|
+
let(:driver) { double('driver') }
|
9
|
+
let(:browser) { double('browser') }
|
10
|
+
# RMagick mocks
|
11
|
+
let(:picture) { double('Magick::Image') }
|
12
|
+
# Cezanne classes mocks
|
13
|
+
let(:local_files) { double('local_files') }
|
14
|
+
let(:image) { double('Cezanne::Image') }
|
15
|
+
|
16
|
+
before(:each) do
|
17
|
+
allow(browser).to receive('browser').and_return(:browser)
|
18
|
+
allow(browser).to receive('save_screenshot')
|
19
|
+
allow(driver).to receive('browser').and_return(browser)
|
20
|
+
allow(page).to receive('driver').and_return(driver)
|
21
|
+
allow(local_files).to receive('path_for').and_return('spec/images')
|
22
|
+
allow(class_with_cezanne).to receive('page').and_return(page)
|
23
|
+
allow(class_with_cezanne).to receive('local_files').and_return(local_files)
|
24
|
+
allow(class_with_cezanne).to receive('image').and_return(image)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#take_screenshot' do
|
28
|
+
|
29
|
+
it 'take a screenshot using the browser driver' do
|
30
|
+
expect(page.driver.browser).to receive('save_screenshot')
|
31
|
+
class_with_cezanne.send(:take_screenshot, 'page_name')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'return a Cezanne::Image' do
|
35
|
+
expect(class_with_cezanne.send(:take_screenshot, 'page_name')).to be image
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#get_reference_screenshot_for' do
|
41
|
+
|
42
|
+
it 'return the reference Cezanne::Image' do
|
43
|
+
expect(class_with_cezanne.send(:get_reference_screenshot_for, 'page_name')).to be image
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'return false if the reference image does not exist' do
|
47
|
+
expect(class_with_cezanne.send(:get_reference_screenshot_for, 'page_name_with_no_reference')).to be false
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#spot_differences_between' do
|
53
|
+
|
54
|
+
let(:this) { image }
|
55
|
+
let(:that) { this.clone }
|
56
|
+
|
57
|
+
before(:each) do
|
58
|
+
allow(image).to receive('picture').and_return(picture)
|
59
|
+
allow(image).to receive('crop!')
|
60
|
+
allow(picture).to receive('compare_channel').with(picture, Magick::PeakSignalToNoiseRatioMetric).and_return([nil, 1])
|
61
|
+
allow(this).to receive('width').and_return(1)
|
62
|
+
allow(this).to receive('height').and_return(2)
|
63
|
+
allow(that).to receive('width').and_return(2)
|
64
|
+
allow(that).to receive('height').and_return(1)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'crop images to min width and height' do
|
68
|
+
expect(this).to receive('crop!').with(1,1)
|
69
|
+
expect(that).to receive('crop!').with(1,1)
|
70
|
+
class_with_cezanne.send(:spot_differences_between, this, that)
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'similar images' do
|
74
|
+
|
75
|
+
it 'return false' do
|
76
|
+
expect(this.picture).to receive('compare_channel').and_return([nil, Cezanne::SIMILARITY_THRESHOLD + 1])
|
77
|
+
expect(class_with_cezanne.send(:spot_differences_between, this, that)).to be false
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'different images' do
|
83
|
+
|
84
|
+
it 'return true' do
|
85
|
+
expect(this.picture).to receive('compare_channel').and_return([nil, Cezanne::SIMILARITY_THRESHOLD - 1])
|
86
|
+
expect(class_with_cezanne.send(:spot_differences_between, this, that)).to be true
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#file_name_for' do
|
93
|
+
|
94
|
+
it 'contains page_name and browser' do
|
95
|
+
expect(class_with_cezanne.send(:file_name_for, 'fancy_page_name')).to eq('fancy_page_name_browser.gif')
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#mark_for_review' do
|
101
|
+
|
102
|
+
let(:screenshot) { image }
|
103
|
+
|
104
|
+
before(:each) do
|
105
|
+
allow(image).to receive('path').and_return('spec/images/page_name_browser.gif')
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'moves screenshot to the diff folder' do
|
109
|
+
expect(local_files).to receive('path_for').with(:diff).and_return('spec/images/different.gif')
|
110
|
+
class_with_cezanne.send(:mark_for_review, screenshot)
|
111
|
+
expect(File.exists?('spec/images/different.gif')).to be true
|
112
|
+
end
|
113
|
+
|
114
|
+
after(:each) do
|
115
|
+
FileUtils.mv('spec/images/different.gif', 'spec/images/page_name_browser.gif')
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
describe '#mark_as_new' do
|
122
|
+
|
123
|
+
let(:screenshot) { image }
|
124
|
+
|
125
|
+
before(:each) do
|
126
|
+
allow(image).to receive('path').and_return('spec/images/page_name_browser.gif')
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'moves screenshot to the new folder' do
|
130
|
+
expect(local_files).to receive('path_for').with(:new).and_return('spec/images/new.gif')
|
131
|
+
class_with_cezanne.send(:mark_as_new, screenshot)
|
132
|
+
expect(File.exists?('spec/images/new.gif')).to be true
|
133
|
+
end
|
134
|
+
|
135
|
+
after(:each) do
|
136
|
+
FileUtils.mv('spec/images/new.gif', 'spec/images/page_name_browser.gif')
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#image' do
|
142
|
+
|
143
|
+
before(:each) do
|
144
|
+
allow(class_with_cezanne).to receive('image').and_call_original
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'return a Cezanne::Image' do
|
148
|
+
path = 'spec/images/page_name_browser.gif'
|
149
|
+
expect(class_with_cezanne.send(:image, path)).to be_instance_of(Cezanne::Image)
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
describe '#check_visual_regression_for' do
|
155
|
+
|
156
|
+
before(:each) do
|
157
|
+
allow(class_with_cezanne).to receive('get_reference_screenshot_for').and_return(Cezanne::Image.new('spec/images/page_name_browser.gif'))
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'succesful match' do
|
161
|
+
|
162
|
+
it 'does not raise an error' do
|
163
|
+
allow(class_with_cezanne).to receive('take_screenshot').and_return(Cezanne::Image.new('spec/images/page_name_browser.gif'))
|
164
|
+
expect { class_with_cezanne.check_visual_regression_for 'page_name' }.not_to raise_error
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'failed match' do
|
170
|
+
|
171
|
+
let(:screenshot) { Cezanne::Image.new('spec/images/page_name_2_browser.gif') }
|
172
|
+
|
173
|
+
before(:each) do
|
174
|
+
allow(class_with_cezanne).to receive('take_screenshot').and_return(screenshot)
|
175
|
+
allow(class_with_cezanne).to receive('mark_for_review')
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'raise a <did not match> error' do
|
179
|
+
expect { class_with_cezanne.check_visual_regression_for('page_name') }.to raise_error(/didn't match/)
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'mark the screenshot for review' do
|
183
|
+
expect(class_with_cezanne).to receive('mark_for_review').with(screenshot)
|
184
|
+
begin
|
185
|
+
class_with_cezanne.check_visual_regression_for('page_name')
|
186
|
+
rescue
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context 'new screenshot' do
|
192
|
+
|
193
|
+
let(:screenshot) { Cezanne::Image.new('spec/images/page_name_browser.gif') }
|
194
|
+
|
195
|
+
before(:each) do
|
196
|
+
allow(class_with_cezanne).to receive('get_reference_screenshot_for').and_return(false)
|
197
|
+
allow(class_with_cezanne).to receive('take_screenshot').and_return(screenshot)
|
198
|
+
allow(class_with_cezanne).to receive('mark_as_new')
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'raise a <new> error' do
|
202
|
+
expect { class_with_cezanne.check_visual_regression_for('page_name') }.to raise_error(/new screenshot/)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'mark the screenshot as new' do
|
206
|
+
expect(class_with_cezanne).to receive('mark_as_new').with(screenshot)
|
207
|
+
begin
|
208
|
+
class_with_cezanne.check_visual_regression_for('page_name')
|
209
|
+
rescue
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
Binary file
|
Binary file
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cezanne
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sky Haiku
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: capybara
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: selenium-webdriver
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rmagick
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: dropscreen
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Let Cezanne help you make sure your views look alright
|
126
|
+
email:
|
127
|
+
- skyhaikuteam@gmail.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- Gemfile
|
134
|
+
- LICENSE.txt
|
135
|
+
- README.md
|
136
|
+
- Rakefile
|
137
|
+
- cezanne.gemspec
|
138
|
+
- lib/cezanne.rb
|
139
|
+
- lib/cezanne/comparison.rb
|
140
|
+
- lib/cezanne/image.rb
|
141
|
+
- lib/cezanne/local_files.rb
|
142
|
+
- lib/cezanne/remote_files.rb
|
143
|
+
- lib/cezanne/rspec.rb
|
144
|
+
- lib/cezanne/version.rb
|
145
|
+
- spec/cezanne_filesystem_spec.rb
|
146
|
+
- spec/cezanne_image_spec.rb
|
147
|
+
- spec/cezanne_rspec_spec.rb
|
148
|
+
- spec/cezanne_spec.rb
|
149
|
+
- spec/images/different.html
|
150
|
+
- spec/images/new.html
|
151
|
+
- spec/images/page_name_2_browser.gif
|
152
|
+
- spec/images/page_name_browser.gif
|
153
|
+
- spec/images/similar.html
|
154
|
+
- spec/spec_helper.rb
|
155
|
+
homepage: ''
|
156
|
+
licenses:
|
157
|
+
- MIT
|
158
|
+
metadata: {}
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
requirements: []
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 2.2.2
|
176
|
+
signing_key:
|
177
|
+
specification_version: 4
|
178
|
+
summary: Visual regression testing tool
|
179
|
+
test_files:
|
180
|
+
- spec/cezanne_filesystem_spec.rb
|
181
|
+
- spec/cezanne_image_spec.rb
|
182
|
+
- spec/cezanne_rspec_spec.rb
|
183
|
+
- spec/cezanne_spec.rb
|
184
|
+
- spec/images/different.html
|
185
|
+
- spec/images/new.html
|
186
|
+
- spec/images/page_name_2_browser.gif
|
187
|
+
- spec/images/page_name_browser.gif
|
188
|
+
- spec/images/similar.html
|
189
|
+
- spec/spec_helper.rb
|