simulacrum 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +9 -0
  3. data/.env.example +2 -0
  4. data/.gitignore +10 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +14 -0
  7. data/.travis.yml +28 -0
  8. data/Gemfile +2 -0
  9. data/README.md +78 -19
  10. data/Rakefile +20 -0
  11. data/examples/README.md +3 -0
  12. data/examples/basic/Gemfile +4 -0
  13. data/examples/basic/README.md +13 -0
  14. data/examples/basic/config.ru +3 -0
  15. data/examples/basic/example_app.rb +7 -0
  16. data/examples/basic/public/button.html +13 -0
  17. data/examples/basic/public/index.html +22 -0
  18. data/examples/basic/public/panel.html +13 -0
  19. data/examples/basic/public/stylesheets/button.css +15 -0
  20. data/examples/basic/public/stylesheets/main.css +3 -0
  21. data/examples/basic/public/stylesheets/normalize.css +425 -0
  22. data/examples/basic/script/start +4 -0
  23. data/examples/basic/spec/simulacrum_helper.rb +9 -0
  24. data/examples/basic/spec/ui/button_spec.rb +10 -0
  25. data/exe/simulacrum +5 -0
  26. data/features/command_line/help.feature +8 -0
  27. data/features/exit_codes/failing.feature +24 -0
  28. data/features/exit_codes/passing.feature +24 -0
  29. data/features/exit_codes/pending.feature +22 -0
  30. data/features/output/candidate.feature +32 -0
  31. data/features/output/diff.feature +5 -0
  32. data/features/step_definitions/dummy_steps.rb +15 -0
  33. data/features/step_definitions/file_steps.rb +19 -0
  34. data/features/support/env.rb +15 -0
  35. data/fixtures/a1.png +0 -0
  36. data/fixtures/app/fixture_app.rb +12 -0
  37. data/fixtures/app/public/images/a1.png +0 -0
  38. data/fixtures/app/public/ui_component.html +10 -0
  39. data/fixtures/app/spec/component_spec.rb +9 -0
  40. data/fixtures/app/spec/simulacrum_helper.rb +37 -0
  41. data/fixtures/app/spec/ui/references/ui_component/test_driver/candidate.png +0 -0
  42. data/fixtures/diff.png +0 -0
  43. data/lib/simulacrum.rb +74 -15
  44. data/lib/simulacrum/cli.rb +38 -0
  45. data/lib/simulacrum/cli/parser.rb +152 -0
  46. data/lib/simulacrum/comparator.rb +15 -15
  47. data/lib/simulacrum/component.rb +22 -11
  48. data/lib/simulacrum/configuration.rb +20 -13
  49. data/lib/simulacrum/diff.rb +2 -0
  50. data/lib/simulacrum/diff/rmagick.rb +8 -6
  51. data/lib/simulacrum/driver.rb +45 -0
  52. data/lib/simulacrum/matchers.rb +6 -16
  53. data/lib/simulacrum/methods.rb +1 -0
  54. data/lib/simulacrum/renderer.rb +23 -8
  55. data/lib/simulacrum/runner.rb +44 -0
  56. data/lib/simulacrum/version.rb +2 -1
  57. data/rubocop-todo.yml +29 -0
  58. data/script/bootstrap +3 -0
  59. data/script/quality +7 -0
  60. data/script/spec +10 -0
  61. data/simulacrum.gemspec +52 -0
  62. data/spec/lib/simulacrum/cli/parser_spec.rb +113 -0
  63. data/spec/lib/simulacrum/cli_spec.rb +18 -0
  64. data/spec/lib/simulacrum/comparator_spec.rb +75 -0
  65. data/spec/lib/simulacrum/component_spec.rb +208 -0
  66. data/spec/lib/simulacrum/driver/local_spec.rb +11 -0
  67. data/spec/lib/simulacrum/version_spec.rb +12 -0
  68. data/spec/lib/simulacrum_spec.rb +53 -0
  69. data/spec/spec_helper.rb +13 -8
  70. data/spec/use_codeclimate.rb +3 -0
  71. data/spec/use_simplecov.rb +5 -12
  72. metadata +217 -32
  73. data/lib/simulacrum/diff/pdiff.rb +0 -47
  74. data/spec/fixtures/a.png +0 -0
  75. data/spec/fixtures/a2.png +0 -0
  76. data/spec/use_coveralls.rb +0 -2
@@ -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
- pending Simulacrum::Matchers.pending_message(component)
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 threshold of #{component.acceptable_delta}%.
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! New candidate created:
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
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  require 'ostruct'
2
3
  require_relative './component'
3
4
 
@@ -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.native.location
28
- size = element.native.size
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
- begin
36
- page.driver.browser.manage.window.resize_to(1024, 768)
37
- rescue Selenium::WebDriver::Error::UnknownError => e
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, Simulacrum.configuration.candidate_filename)
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
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  # Package information
2
3
  module Simulacrum
3
4
  PACKAGE = 'simulacrum'
4
- VERSION = '0.1.1'
5
+ VERSION = '0.3.0'
5
6
  end
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
@@ -0,0 +1,3 @@
1
+ #!/bin/sh -xe
2
+
3
+ bundle --quiet --binstubs
data/script/quality ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env sh -xe
2
+
3
+ function run_command {
4
+ $1 || exit 1
5
+ }
6
+
7
+ run_command "rake spec:quality"
data/script/spec ADDED
@@ -0,0 +1,10 @@
1
+ #!/bin/sh -xe
2
+
3
+ $(dirname $0)/bootstrap
4
+
5
+ rm -rf coverage/
6
+ rm -rf tmp/
7
+ bin/rubocop --display-cop-names
8
+ bin/rspec --require spec_helper
9
+ bin/cucumber --require features/support/env.rb --strict features
10
+ bin/rake quality
@@ -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