simulacrum 0.0.2

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