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.
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