simulacrum 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c310ede91a08d7f1f3dc4a8117b2c819cbae6106
4
+ data.tar.gz: 0baa8198be24a7d440e3c18bc1f2cc3dcf7a155c
5
+ SHA512:
6
+ metadata.gz: 73c02e295c5a64c54b5698b9ab635b13949e539f0acbaa246be388b7179c90faf369ea90812e2e7ef0372c66efca1f12a513beba83b7c316efcd72a4d2ffab75
7
+ data.tar.gz: 43d26d53bbb8562d4bdf036a45206fd09830dc892661244d848f73537e40ba9e1566201d7d01781fdf4a780ca9956755ea4c5d3611e5444aae8e7b284741ad69
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ ## Simulacrum
2
+
3
+ An opinionated UI component regression testing tool built to be tightly integrated with RSpec, Selenium and tools you already use.
4
+
5
+ ***
6
+
7
+ ### Installing
8
+ `gem 'simulacrum'`
9
+
10
+ ### Configuring
11
+
12
+ ```ruby
13
+ RSpec.configure do |config|
14
+ config.include Simulacrum
15
+ end
16
+ ```
17
+
18
+ Simulacrum can also be configured once included;
19
+
20
+ ```ruby
21
+ Simulacrum.configure do |config|
22
+ config.images_path = 'somewhere/example/spec/ui_specs'
23
+ config.acceptable_delta = 2 # allow a maximum of 2% difference
24
+ end
25
+ ```
26
+
27
+ ### Opinions
28
+
29
+ ### Usage
30
+
31
+ Simulacrum provides a small DSL for configuring and managing UI tests from within Rspec. Basically it boils down to these three methods;
32
+
33
+ - `component`
34
+ - `configure_browser`
35
+ - `look_the_same`
36
+
37
+ #### Inspiration
38
+
39
+ - Huxley
40
+ - Green Onion
@@ -0,0 +1,7 @@
1
+ require 'capybara'
2
+ require 'capybara/dsl'
3
+
4
+ module Simulacrum
5
+ class Browser
6
+ end
7
+ end
@@ -0,0 +1,59 @@
1
+ require_relative 'diff/rmagick'
2
+
3
+ module Simulacrum
4
+ # The Comparator class is responsible for comparing and handling
5
+ # processing of screenshots and candidates
6
+ class Comparator
7
+ include RSpec::Core::Pending
8
+
9
+ attr_accessor :component, :candidate, :diff
10
+
11
+ def initialize(component)
12
+ @component = component
13
+ end
14
+
15
+ def test
16
+ @candidate = @component.capture_candidate
17
+
18
+ # If the component has a reference then we should diff the candidate
19
+ # image against the reference
20
+ if @component.reference?
21
+ # If there is a diff between the candidate and the reference then we
22
+ # should save both the candidate and diff images and fail the test
23
+ perform_diff ? pass : fail
24
+
25
+ # Otherwise we should just write the captured candidate to disk, and mark
26
+ # the spec as being pending until the user works out if the candidate is
27
+ # OK by renaming candidate.png to reference.png
28
+ else
29
+ skip
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def pass
36
+ @component.remove_candidate
37
+ true
38
+ end
39
+
40
+ def fail
41
+ @diff.save(@component.diff_path)
42
+ false
43
+ end
44
+
45
+ def skip
46
+ nil
47
+ end
48
+
49
+ def perform_diff()
50
+ @diff = Simulacrum::RmagicDiff.new(@component.reference_path,
51
+ @component.candidate_path)
52
+ diff_delta_percent_is_acceptable
53
+ end
54
+
55
+ def diff_delta_percent_is_acceptable
56
+ (@diff.delta * 1000) < @component.acceptable_delta
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,66 @@
1
+ require 'capybara'
2
+
3
+ module Simulacrum
4
+ class Component
5
+ include Capybara::DSL
6
+ Capybara.default_driver = :selenium
7
+
8
+ attr_reader :name
9
+
10
+ def initialize(name, options)
11
+ @name = name
12
+ @options = options
13
+ end
14
+
15
+ # Load up the component url and capture an image, returns a File object
16
+ def capture_candidate
17
+ ensure_example_path
18
+ visit(@options.url)
19
+ within(capture_selector) do
20
+ page.save_screenshot(candidate_path)
21
+ end
22
+ end
23
+
24
+ def remove_candidate
25
+ FileUtils.rm(candidate_path) if candidate?
26
+ end
27
+
28
+ def root_path
29
+ File.join(Simulacrum.configuration.images_path, name.to_s)
30
+ end
31
+
32
+ def reference_path
33
+ File.join(root_path, Simulacrum.configuration.reference_filename)
34
+ end
35
+
36
+ def candidate_path
37
+ File.join(root_path, Simulacrum.configuration.candidate_filename)
38
+ end
39
+
40
+ def diff_path
41
+ File.join(root_path, Simulacrum.configuration.diff_filename)
42
+ end
43
+
44
+ def reference?
45
+ File.exists?(reference_path)
46
+ end
47
+
48
+ def candidate?
49
+ File.exists?(candidate_path)
50
+ end
51
+
52
+ def acceptable_delta
53
+ @options.acceptable_delta || Simulacrum.configuration.acceptable_delta
54
+ end
55
+
56
+ private
57
+
58
+ def ensure_example_path
59
+ FileUtils.mkdir_p(root_path)
60
+ end
61
+
62
+ def capture_selector
63
+ @options.capture_selector || Simulacrum.configuration.capture_selector
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,40 @@
1
+ require 'ostruct'
2
+
3
+ module Simulacrum
4
+ class Configuration
5
+ attr_reader :images_path, :reference_filename, :candidate_filename,
6
+ :diff_filename, :capture_selector, :acceptable_delta
7
+
8
+ def initialize
9
+ @config = OpenStruct.new
10
+ end
11
+
12
+ def configure(config)
13
+ @config = OpenStruct.new(@config.to_h.merge!(config))
14
+ end
15
+
16
+ def images_path
17
+ @config.images_path
18
+ end
19
+
20
+ def reference_filename
21
+ @config.reference_filename || 'reference.png'
22
+ end
23
+
24
+ def candidate_filename
25
+ @config.candidate_filename || 'candidate.png'
26
+ end
27
+
28
+ def diff_filename
29
+ @config.diff_filename || 'diff.png'
30
+ end
31
+
32
+ def capture_selector
33
+ @config.capture_selector || 'html'
34
+ end
35
+
36
+ def acceptable_delta
37
+ @config.acceptable_delta || 0.0
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ require 'chunky_png'
2
+ require_relative '../diff'
3
+
4
+ module Simulacrum
5
+ class Pdiff < Simulacrum::Diff
6
+ include ChunkyPNG::Color
7
+
8
+ def compare
9
+ a_image = ChunkyPNG::Image.from_file(@a_path)
10
+ b_image = ChunkyPNG::Image.from_file(@b_path)
11
+ end
12
+ end
13
+ end
14
+
15
+ # require 'chunky_png'
16
+ # include ChunkyPNG::Color
17
+
18
+
19
+ # images = [
20
+ # ChunkyPNG::Image.from_file('1.png'),
21
+ # ChunkyPNG::Image.from_file('2.png')
22
+ # ]
23
+
24
+ # output = ChunkyPNG::Image.new(images.first.width, images.last.width, WHITE)
25
+
26
+ # diff = []
27
+
28
+ # images.first.height.times do |y|
29
+ # images.first.row(y).each_with_index do |pixel, x|
30
+ # unless pixel == images.last[x,y]
31
+ # score = Math.sqrt(
32
+ # (r(images.last[x,y]) - r(pixel)) ** 2 +
33
+ # (g(images.last[x,y]) - g(pixel)) ** 2 +
34
+ # (b(images.last[x,y]) - b(pixel)) ** 2
35
+ # ) / Math.sqrt(MAX ** 2 * 3)
36
+
37
+ # output[x,y] = grayscale(MAX - (score * MAX).round)
38
+ # diff << score
39
+ # end
40
+ # end
41
+ # end
42
+
43
+ # puts "pixels (total): #{images.first.pixels.length}"
44
+ # puts "pixels changed: #{diff.length}"
45
+ # puts "image changed (%): #{(diff.inject {|sum, value| sum + value} / images.first.pixels.length) * 100}%"
46
+
47
+ # output.save('diff.png')
@@ -0,0 +1,18 @@
1
+ require 'rmagick'
2
+ require_relative '../diff'
3
+
4
+ module Simulacrum
5
+ class RmagicDiff < Simulacrum::Diff
6
+ private
7
+
8
+ def compare
9
+ a_image = Magick::Image.read(@a_path)
10
+ b_image = Magick::Image.read(@b_path)
11
+ @image, @delta = compare_images(a_image, b_image)
12
+ end
13
+
14
+ def compare_images(a_image, b_image)
15
+ a_image[0].compare_channel(b_image[0], Magick::MeanSquaredErrorMetric)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Simulacrum
2
+ class Diff
3
+ attr_accessor :a_path, :b_path, :delta, :image
4
+
5
+ def initialize(a_path, b_path)
6
+ @a_path = a_path
7
+ @b_path = b_path
8
+
9
+ compare
10
+ end
11
+
12
+ def save(path)
13
+ @image.write(path)
14
+ end
15
+
16
+ def percent_change
17
+ (@delta * 1000).round(2)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'comparator'
2
+
3
+ module Simulacrum
4
+ module Matchers
5
+ extend RSpec::Matchers::DSL
6
+
7
+ matcher :look_the_same do
8
+ match do |component|
9
+ comparator = Simulacrum::Comparator.new(component)
10
+ case comparator.test
11
+ when true
12
+ true
13
+ when false
14
+ fail <<-eos
15
+ The pixel change percentage exceeded the maximum threshold of #{component.acceptable_delta}%.
16
+
17
+ There was a #{comparator.diff.percent_change}% pixel difference found between \
18
+ the reference and the candidate.
19
+
20
+ Reference: #{component.reference_path}
21
+ Candidate: #{component.candidate_path}
22
+ Diff: #{component.diff_path}
23
+
24
+ Please review the diff and resolve manually.
25
+ eos
26
+ when nil
27
+ pending <<-eos
28
+ No reference image found! New candidate created:
29
+
30
+ #{component.candidate_path}
31
+
32
+ Please inspect this candidate image and if it looks OK then;
33
+
34
+ - mark it as a reference image by renaming it to 'reference.png'
35
+ - commit 'reference.png' file to your SCM of choice
36
+ - rerun this spec making sure it passes using the new reference image
37
+ eos
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ require 'ostruct'
2
+ require_relative 'browser'
3
+ require_relative 'component'
4
+
5
+ module Simulacrum
6
+ # Rspec utility methods for defining components, browser environments and
7
+ module Methods
8
+ def component(name, &block)
9
+ options = OpenStruct.new
10
+ yield options
11
+ component = Simulacrum::Component.new(name, options)
12
+ Simulacrum.components[name] = component
13
+
14
+ subject do
15
+ component
16
+ end
17
+ end
18
+
19
+ def configure_browser(name, &block)
20
+ options = OpenStruct.new
21
+ yield options
22
+ @browser = Simulacrum::Browser.new(name, options)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ # Package information
2
+ module Simulacrum
3
+ PACKAGE = 'simulacrum'
4
+ VERSION = '0.0.2'
5
+ end
data/lib/simulacrum.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'ostruct'
2
+ require_relative 'Simulacrum/methods'
3
+ require_relative 'Simulacrum/matchers'
4
+ require_relative 'Simulacrum/configuration'
5
+
6
+ # Gem module
7
+ module Simulacrum
8
+ @components = {}
9
+ @configuration = Simulacrum::Configuration.new
10
+
11
+ def self.components
12
+ @components
13
+ end
14
+
15
+ def self.configuration
16
+ @configuration
17
+ end
18
+
19
+ def self.configure(&block)
20
+ options = OpenStruct.new
21
+ yield options
22
+ @configuration.configure(options.to_h)
23
+ end
24
+
25
+ def self.included(receiver, &block)
26
+ receiver.extend Simulacrum::Methods
27
+ receiver.send :include, Simulacrum::Matchers
28
+ end
29
+ end
Binary file
Binary file
@@ -0,0 +1,12 @@
1
+ if ENV['COVERAGE']
2
+ require_relative 'use_coveralls' if ENV['TRAVIS']
3
+ require_relative 'use_simplecov' unless ENV['TRAVIS']
4
+ end
5
+
6
+ require 'bundler/setup'
7
+ require 'Simulacrum'
8
+ require 'rspec/autorun'
9
+
10
+ RSpec.configure do |config|
11
+ config.order = "random"
12
+ end
@@ -0,0 +1,2 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
@@ -0,0 +1,14 @@
1
+ puts "[Simplecov] enabled"
2
+ require 'simplecov'
3
+
4
+ class SimpleCov::Formatter::QualityFormatter
5
+ def format(result)
6
+ SimpleCov::Formatter::HTMLFormatter.new.format(result)
7
+ File.open("coverage/covered_percent", "w") do |f|
8
+ f.puts result.source_files.covered_percent.to_f
9
+ end
10
+ end
11
+ end
12
+
13
+ SimpleCov.formatter = SimpleCov::Formatter::QualityFormatter
14
+ SimpleCov.start
metadata ADDED
@@ -0,0 +1,221 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simulacrum
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Justin Morris
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capybara
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
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: rmagick
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: coveralls
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: pry
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: rake
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: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
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: rspec-nc
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: shoulda-matchers
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: An opinionated UI component regression testing tool built to be tightly
168
+ integrated with RSpec, Selenium and tools you already use.
169
+ email:
170
+ - desk@pixelbloom.com
171
+ executables: []
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - lib/simulacrum/browser.rb
176
+ - lib/simulacrum/comparator.rb
177
+ - lib/simulacrum/component.rb
178
+ - lib/simulacrum/configuration.rb
179
+ - lib/simulacrum/diff/pdiff.rb
180
+ - lib/simulacrum/diff/rmagick.rb
181
+ - lib/simulacrum/diff.rb
182
+ - lib/simulacrum/matchers.rb
183
+ - lib/simulacrum/methods.rb
184
+ - lib/simulacrum/version.rb
185
+ - lib/simulacrum.rb
186
+ - README.md
187
+ - spec/fixtures/a.png
188
+ - spec/fixtures/a2.png
189
+ - spec/spec_helper.rb
190
+ - spec/use_coveralls.rb
191
+ - spec/use_simplecov.rb
192
+ homepage: https://github.com/plasticine/simulacrum
193
+ licenses:
194
+ - MIT
195
+ metadata: {}
196
+ post_install_message:
197
+ rdoc_options: []
198
+ require_paths:
199
+ - lib
200
+ required_ruby_version: !ruby/object:Gem::Requirement
201
+ requirements:
202
+ - - '>='
203
+ - !ruby/object:Gem::Version
204
+ version: '0'
205
+ required_rubygems_version: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - '>='
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ requirements: []
211
+ rubyforge_project:
212
+ rubygems_version: 2.1.11
213
+ signing_key:
214
+ specification_version: 4
215
+ summary: A gem for visually testing and inspecting user interface components.
216
+ test_files:
217
+ - spec/fixtures/a.png
218
+ - spec/fixtures/a2.png
219
+ - spec/spec_helper.rb
220
+ - spec/use_coveralls.rb
221
+ - spec/use_simplecov.rb