simulacrum 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.cane +9 -0
- data/.env.example +2 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/.travis.yml +28 -0
- data/Gemfile +2 -0
- data/README.md +78 -19
- data/Rakefile +20 -0
- data/examples/README.md +3 -0
- data/examples/basic/Gemfile +4 -0
- data/examples/basic/README.md +13 -0
- data/examples/basic/config.ru +3 -0
- data/examples/basic/example_app.rb +7 -0
- data/examples/basic/public/button.html +13 -0
- data/examples/basic/public/index.html +22 -0
- data/examples/basic/public/panel.html +13 -0
- data/examples/basic/public/stylesheets/button.css +15 -0
- data/examples/basic/public/stylesheets/main.css +3 -0
- data/examples/basic/public/stylesheets/normalize.css +425 -0
- data/examples/basic/script/start +4 -0
- data/examples/basic/spec/simulacrum_helper.rb +9 -0
- data/examples/basic/spec/ui/button_spec.rb +10 -0
- data/exe/simulacrum +5 -0
- data/features/command_line/help.feature +8 -0
- data/features/exit_codes/failing.feature +24 -0
- data/features/exit_codes/passing.feature +24 -0
- data/features/exit_codes/pending.feature +22 -0
- data/features/output/candidate.feature +32 -0
- data/features/output/diff.feature +5 -0
- data/features/step_definitions/dummy_steps.rb +15 -0
- data/features/step_definitions/file_steps.rb +19 -0
- data/features/support/env.rb +15 -0
- data/fixtures/a1.png +0 -0
- data/fixtures/app/fixture_app.rb +12 -0
- data/fixtures/app/public/images/a1.png +0 -0
- data/fixtures/app/public/ui_component.html +10 -0
- data/fixtures/app/spec/component_spec.rb +9 -0
- data/fixtures/app/spec/simulacrum_helper.rb +37 -0
- data/fixtures/app/spec/ui/references/ui_component/test_driver/candidate.png +0 -0
- data/fixtures/diff.png +0 -0
- data/lib/simulacrum.rb +74 -15
- data/lib/simulacrum/cli.rb +38 -0
- data/lib/simulacrum/cli/parser.rb +152 -0
- data/lib/simulacrum/comparator.rb +15 -15
- data/lib/simulacrum/component.rb +22 -11
- data/lib/simulacrum/configuration.rb +20 -13
- data/lib/simulacrum/diff.rb +2 -0
- data/lib/simulacrum/diff/rmagick.rb +8 -6
- data/lib/simulacrum/driver.rb +45 -0
- data/lib/simulacrum/matchers.rb +6 -16
- data/lib/simulacrum/methods.rb +1 -0
- data/lib/simulacrum/renderer.rb +23 -8
- data/lib/simulacrum/runner.rb +44 -0
- data/lib/simulacrum/version.rb +2 -1
- data/rubocop-todo.yml +29 -0
- data/script/bootstrap +3 -0
- data/script/quality +7 -0
- data/script/spec +10 -0
- data/simulacrum.gemspec +52 -0
- data/spec/lib/simulacrum/cli/parser_spec.rb +113 -0
- data/spec/lib/simulacrum/cli_spec.rb +18 -0
- data/spec/lib/simulacrum/comparator_spec.rb +75 -0
- data/spec/lib/simulacrum/component_spec.rb +208 -0
- data/spec/lib/simulacrum/driver/local_spec.rb +11 -0
- data/spec/lib/simulacrum/version_spec.rb +12 -0
- data/spec/lib/simulacrum_spec.rb +53 -0
- data/spec/spec_helper.rb +13 -8
- data/spec/use_codeclimate.rb +3 -0
- data/spec/use_simplecov.rb +5 -12
- metadata +217 -32
- data/lib/simulacrum/diff/pdiff.rb +0 -47
- data/spec/fixtures/a.png +0 -0
- data/spec/fixtures/a2.png +0 -0
- data/spec/use_coveralls.rb +0 -2
data/lib/simulacrum/matchers.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
require 'rspec'
|
2
3
|
require_relative './comparator'
|
3
4
|
|
4
5
|
module Simulacrum
|
6
|
+
# Custom RSpec matchers
|
5
7
|
module Matchers
|
6
8
|
extend RSpec::Matchers::DSL
|
7
9
|
|
@@ -16,37 +18,25 @@ module Simulacrum
|
|
16
18
|
when false
|
17
19
|
fail Simulacrum::Matchers.fail_message(component, comparator)
|
18
20
|
when nil
|
19
|
-
|
21
|
+
skip Simulacrum::Matchers.pending_message(component)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
def self.fail_message(component, comparator)
|
25
27
|
<<-eos
|
26
|
-
The pixel change percentage exceeded the maximum
|
27
|
-
|
28
|
-
There was a #{comparator.diff.delta_percent}% pixel difference found between \
|
29
|
-
the reference and the candidate.
|
28
|
+
The pixel change percentage (#{comparator.diff.delta}%) exceeded the maximum \
|
29
|
+
threshold of #{component.delta_threshold}%.
|
30
30
|
|
31
31
|
Reference: #{component.reference_path}
|
32
32
|
Candidate: #{component.candidate_path}
|
33
33
|
Diff: #{component.diff_path}
|
34
|
-
|
35
|
-
Please review the diff and resolve manually.
|
36
34
|
eos
|
37
35
|
end
|
38
36
|
|
39
37
|
def self.pending_message(component)
|
40
38
|
<<-eos
|
41
|
-
No reference image found!
|
42
|
-
|
43
|
-
#{component.candidate_path}
|
44
|
-
|
45
|
-
Please inspect this candidate image and if it looks OK then;
|
46
|
-
|
47
|
-
- mark it as a reference image by renaming it to 'reference.png'
|
48
|
-
- commit 'reference.png' file to your SCM of choice
|
49
|
-
- rerun this spec making sure it passes using the new reference image
|
39
|
+
No reference image found! Candidate created: #{component.candidate_path}
|
50
40
|
eos
|
51
41
|
end
|
52
42
|
end
|
data/lib/simulacrum/methods.rb
CHANGED
data/lib/simulacrum/renderer.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
require 'capybara'
|
2
3
|
require 'tmpdir'
|
3
4
|
|
4
5
|
module Simulacrum
|
6
|
+
# The Renderer Class is responsible for driving Capybara and setting up
|
7
|
+
# the desired page, screenshotting, croping, etc.
|
5
8
|
class Renderer
|
6
9
|
include Capybara::DSL
|
7
10
|
|
@@ -23,27 +26,39 @@ module Simulacrum
|
|
23
26
|
end
|
24
27
|
|
25
28
|
def get_bounds_for_selector(selector)
|
26
|
-
element = page.find(selector)
|
27
|
-
location = element
|
28
|
-
size = element
|
29
|
+
element = page.find(selector.to_s)
|
30
|
+
location = element_location(element)
|
31
|
+
size = element_size(element)
|
29
32
|
[location.x, location.y, size.width, size.height]
|
30
33
|
end
|
31
34
|
|
32
35
|
private
|
33
36
|
|
37
|
+
def element_location(element)
|
38
|
+
element.native.location
|
39
|
+
end
|
40
|
+
|
41
|
+
def element_size(element)
|
42
|
+
element.native.size
|
43
|
+
end
|
44
|
+
|
34
45
|
def resize_window!
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
46
|
+
page.driver.browser.manage.window.resize_to(*window_size)
|
47
|
+
rescue Selenium::WebDriver::Error::UnknownError
|
48
|
+
return
|
39
49
|
end
|
40
50
|
|
41
51
|
def save_screenshot!
|
42
52
|
page.driver.save_screenshot(tmp_path)
|
43
53
|
end
|
44
54
|
|
55
|
+
def window_size
|
56
|
+
[1024, 768]
|
57
|
+
end
|
58
|
+
|
45
59
|
def tmp_path
|
46
|
-
@tmp_path ||= File.join(@tmp_dir,
|
60
|
+
@tmp_path ||= File.join(@tmp_dir,
|
61
|
+
Simulacrum.configuration.candidate_filename)
|
47
62
|
end
|
48
63
|
end
|
49
64
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rspec'
|
3
|
+
require 'simulacrum/driver'
|
4
|
+
require 'simulacrum/methods'
|
5
|
+
require 'simulacrum/matchers'
|
6
|
+
|
7
|
+
module Simulacrum
|
8
|
+
# Base Runner class for running RSpec in parallel.
|
9
|
+
class Runner
|
10
|
+
attr_reader :exit_code
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
configure_driver
|
14
|
+
configure_rspec
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
@exit_code = run_rspec
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def configure_driver
|
24
|
+
Simulacrum::Driver.use
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_rspec
|
28
|
+
RSpec::Core::Runner.run(test_files)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_files
|
32
|
+
Simulacrum.runner_options.files
|
33
|
+
end
|
34
|
+
|
35
|
+
def configure_rspec
|
36
|
+
RSpec.configuration.include Simulacrum::Matchers
|
37
|
+
RSpec.configuration.extend Simulacrum::Methods
|
38
|
+
RSpec.configuration.color = Simulacrum.runner_options.color
|
39
|
+
RSpec.configuration.tty = true
|
40
|
+
RSpec.configuration.pattern = '**/*_spec.rb'
|
41
|
+
RSpec.configuration.profile_examples = false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/simulacrum/version.rb
CHANGED
data/rubocop-todo.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
+
# on 2014-06-12 23:22:51 +1000 using RuboCop version 0.20.1.
|
3
|
+
# The point is for the user to remove these configuration records
|
4
|
+
# one by one as the offenses are removed from the code base.
|
5
|
+
# Note that changes in the inspected code, or installation of new
|
6
|
+
# versions of RuboCop, may require this file to be generated again.
|
7
|
+
|
8
|
+
# Offense count: 1
|
9
|
+
# Configuration parameters: CountComments.
|
10
|
+
ClassLength:
|
11
|
+
Max: 148
|
12
|
+
|
13
|
+
# Offense count: 1
|
14
|
+
# Cop supports --auto-correct.
|
15
|
+
ColonMethodCall:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
# Offense count: 1
|
19
|
+
HandleExceptions:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
# Offense count: 20
|
23
|
+
LineLength:
|
24
|
+
Max: 202
|
25
|
+
|
26
|
+
# Offense count: 5
|
27
|
+
# Configuration parameters: CountComments.
|
28
|
+
MethodLength:
|
29
|
+
Max: 22
|
data/script/bootstrap
ADDED
data/script/quality
ADDED
data/script/spec
ADDED
data/simulacrum.gemspec
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'simulacrum/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = Simulacrum::PACKAGE
|
7
|
+
gem.version = Simulacrum::VERSION
|
8
|
+
gem.platform = Gem::Platform::RUBY
|
9
|
+
gem.required_ruby_version = '>= 1.9.3'
|
10
|
+
|
11
|
+
gem.authors = ['Justin Morris']
|
12
|
+
gem.email = ['desk@pixelbloom.com']
|
13
|
+
gem.homepage = 'https://github.com/plasticine/simulacrum'
|
14
|
+
gem.licenses = ['MIT']
|
15
|
+
gem.summary =
|
16
|
+
%q{A gem for visually testing and inspecting user interface components.}
|
17
|
+
gem.description = %q{
|
18
|
+
An opinionated UI component regression testing tool built to be tightly
|
19
|
+
integrated with RSpec, Selenium and tools you already use.
|
20
|
+
}
|
21
|
+
|
22
|
+
gem.required_ruby_version = '>= 1.9.3'
|
23
|
+
|
24
|
+
gem.files = `git ls-files`.split("\n")
|
25
|
+
gem.test_files = Dir['spec/**/*']
|
26
|
+
gem.require_paths = ['lib']
|
27
|
+
gem.has_rdoc = false
|
28
|
+
gem.bindir = 'exe'
|
29
|
+
gem.executables = ['simulacrum']
|
30
|
+
|
31
|
+
gem.add_dependency 'capybara', ['~> 2.4.1']
|
32
|
+
gem.add_dependency 'rmagick', '~> 2.13.2'
|
33
|
+
gem.add_dependency 'rspec', ['>= 2.14.1']
|
34
|
+
gem.add_dependency 'selenium-webdriver', ['~> 2.42.0']
|
35
|
+
|
36
|
+
gem.add_development_dependency 'appraisal'
|
37
|
+
gem.add_development_dependency 'aruba'
|
38
|
+
gem.add_development_dependency 'cane'
|
39
|
+
gem.add_development_dependency 'codeclimate-test-reporter'
|
40
|
+
gem.add_development_dependency 'cucumber'
|
41
|
+
gem.add_development_dependency 'pry'
|
42
|
+
gem.add_development_dependency 'rake'
|
43
|
+
gem.add_development_dependency 'rspec', ['~> 3.0.0']
|
44
|
+
gem.add_development_dependency 'rubocop', ['~> 0.20.1']
|
45
|
+
gem.add_development_dependency 'sauce', ['~> 3.4.8']
|
46
|
+
gem.add_development_dependency 'sauce-connect', ['~> 3.4.1']
|
47
|
+
gem.add_development_dependency 'sauce-cucumber', ['~> 3.4.0']
|
48
|
+
gem.add_development_dependency 'shoulda-matchers'
|
49
|
+
gem.add_development_dependency 'simplecov'
|
50
|
+
gem.add_development_dependency 'sinatra'
|
51
|
+
gem.add_development_dependency 'dotenv'
|
52
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'stringio'
|
4
|
+
require 'simulacrum/cli/parser'
|
5
|
+
|
6
|
+
describe Simulacrum::CLI::Parser do
|
7
|
+
def run(args)
|
8
|
+
output = StringIO.new('')
|
9
|
+
result = Simulacrum::CLI::Parser.new(output).parse(args.split(/\s+/m))
|
10
|
+
|
11
|
+
parser = OpenStruct.new
|
12
|
+
parser.output = output.string
|
13
|
+
parser.result = result
|
14
|
+
parser
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:option) { '' }
|
18
|
+
subject { run(option) }
|
19
|
+
|
20
|
+
describe 'invalid options' do
|
21
|
+
let(:option) { '--velociraptors' }
|
22
|
+
|
23
|
+
it 'handles invalid options by showing help' do
|
24
|
+
expect(subject.output).to include('Usage:')
|
25
|
+
expect(subject.result).to eq(false)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '--version' do
|
30
|
+
let(:option) { '--version' }
|
31
|
+
|
32
|
+
it { expect(subject.result).to eq(true) }
|
33
|
+
it { expect(subject.output).to include(Simulacrum::VERSION) }
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '--color' do
|
37
|
+
it { expect(subject.result.color).to eq(false) }
|
38
|
+
|
39
|
+
context 'when the option is set' do
|
40
|
+
let(:option) { '--color' }
|
41
|
+
|
42
|
+
it { expect(subject.result.color).to eq(true) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '--verbose' do
|
47
|
+
it { expect(subject.result.verbose).to eq(false) }
|
48
|
+
|
49
|
+
context 'when the option is set' do
|
50
|
+
let(:option) { '--verbose' }
|
51
|
+
|
52
|
+
it { expect(subject.result.verbose).to eq(true) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '--runner' do
|
57
|
+
it { expect(subject.result.runner).to be_nil }
|
58
|
+
|
59
|
+
context 'when the option is set' do
|
60
|
+
let(:option) { '--runner browserstack' }
|
61
|
+
|
62
|
+
it { expect(subject.result.runner).to eq(:browserstack) }
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when the option is set to an invalid value' do
|
66
|
+
let(:option) { '--runner dropbear' }
|
67
|
+
|
68
|
+
it 'throws an InvalidArgument exception' do
|
69
|
+
expect { subject.result.runner }.to raise_error(OptionParser::InvalidArgument)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '--username' do
|
75
|
+
it { expect(subject.result.username).to be_nil }
|
76
|
+
|
77
|
+
context 'when the option is set' do
|
78
|
+
let(:option) { '--username justin' }
|
79
|
+
|
80
|
+
it { expect(subject.result.username).to eq('justin') }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '--apikey' do
|
85
|
+
it { expect(subject.result.apikey).to be_nil }
|
86
|
+
|
87
|
+
context 'when the option is set' do
|
88
|
+
let(:option) { '--apikey 1234abcd' }
|
89
|
+
|
90
|
+
it { expect(subject.result.apikey).to eq('1234abcd') }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '--max-processes' do
|
95
|
+
it { expect(subject.result.max_processes).to be_nil }
|
96
|
+
|
97
|
+
context 'when the option is set' do
|
98
|
+
let(:option) { '--max-processes 5' }
|
99
|
+
|
100
|
+
it { expect(subject.result.max_processes).to eq(5) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '--browser' do
|
105
|
+
it { expect(subject.result.browser).to be_nil }
|
106
|
+
|
107
|
+
context 'when the option is set' do
|
108
|
+
let(:option) { '--browser firefox' }
|
109
|
+
|
110
|
+
it { expect(subject.result.browser).to eq(:firefox) }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'simulacrum/cli'
|
4
|
+
|
5
|
+
describe Simulacrum::CLI do
|
6
|
+
describe '.execute!' do
|
7
|
+
let(:parser) { stub_const('Simulacrum::CLI::Parser', double) }
|
8
|
+
let(:simulacrum) { stub_const('Simulacrum', double) }
|
9
|
+
let(:exit_code) { 0 }
|
10
|
+
|
11
|
+
it 'runs simulacrum with the given args' do
|
12
|
+
expect(parser).to receive(:parse).with('--args').and_return(args: true)
|
13
|
+
expect(simulacrum).to receive(:run).with(args: true).and_return(exit_code)
|
14
|
+
|
15
|
+
expect(-> { described_class.execute!('--args') }).to raise_error SystemExit
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'simulacrum/comparator'
|
5
|
+
|
6
|
+
describe Simulacrum::Comparator do
|
7
|
+
let(:reference_path) { '/reference_path' }
|
8
|
+
let(:candidate_path) { '/candidate_path' }
|
9
|
+
let(:diff_path) { '/diff_path' }
|
10
|
+
let(:delta_threshold) { 0 }
|
11
|
+
let(:component) do
|
12
|
+
double(reference_path: reference_path,
|
13
|
+
candidate_path: candidate_path,
|
14
|
+
diff_path: diff_path,
|
15
|
+
delta_threshold: delta_threshold)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#initialize' do
|
19
|
+
subject { described_class.new(component) }
|
20
|
+
|
21
|
+
before do
|
22
|
+
expect(component).to receive(:render)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#test' do
|
27
|
+
subject { described_class.new(component).test }
|
28
|
+
|
29
|
+
before do
|
30
|
+
expect(component).to receive(:render)
|
31
|
+
expect(component).to receive(:reference?) { reference }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'a reference already exists' do
|
35
|
+
let(:reference) { true }
|
36
|
+
|
37
|
+
before do
|
38
|
+
expect(Simulacrum::RMagicDiff).to receive(:new).with(reference_path, candidate_path) { diff }
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'there is a difference between the candidate and the reference' do
|
42
|
+
let(:diff) { double(delta: 1) }
|
43
|
+
|
44
|
+
before do
|
45
|
+
expect(diff).to receive(:save).with(diff_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
it { is_expected.to eq false }
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'the reference and the candidate are identical' do
|
52
|
+
let(:diff) { double(delta: 0) }
|
53
|
+
|
54
|
+
before do
|
55
|
+
expect(component).to receive(:remove_candidate)
|
56
|
+
expect(component).to receive(:remove_diff)
|
57
|
+
end
|
58
|
+
|
59
|
+
it { is_expected.to eq true }
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'component delta threshold' do
|
63
|
+
it 'should use the component delta threshold to perform to determine if the test passes' do
|
64
|
+
pending
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'there is no reference' do
|
70
|
+
let(:reference) { false }
|
71
|
+
|
72
|
+
it { is_expected.to be_nil }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|