simulacrum 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|