motion-screenspecs 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +111 -0
- data/lib/motion-screenspecs.rb +207 -0
- data/lib/spec/helpers.rb +76 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0e1ee4821ad5489d2e5e5b0b32e5d189733f0c30
|
4
|
+
data.tar.gz: 707ccb1260a5cf1d8607983cfa22bb0639b6451c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d8a7f61d656df6c054faadf2bfd8f8b965bf40b558c6370f0fc169b960893f303600743b9a8f205d920b2d4f6620960ac04b038c3e9518bbebe86991dfa19ab3
|
7
|
+
data.tar.gz: 0e17a6418a9631c08ebd252b80d4a4efff5241b8d93d997cb4c2c3739a3ed8f394b3fc33e1eeee3bd5b676a6f33297f6db5a4f549c390b930da18f9af138fa9b
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# motion-screenspecs
|
2
|
+
|
3
|
+
Test your RubyMotion app regressions using screenshot comparison (similar to [Huxley](https://github.com/facebook/huxley) and [Wraith](https://github.com/BBC-News/wraith)):
|
4
|
+
|
5
|
+
```
|
6
|
+
$ rake spec
|
7
|
+
|
8
|
+
AppScreenshots
|
9
|
+
- should take screenshots
|
10
|
+
|
11
|
+
AppScreenshots.menu
|
12
|
+
- should be <= 5.0% difference
|
13
|
+
|
14
|
+
AppScreenshots.timeline
|
15
|
+
- should be <= 5.0% difference [FAILED - was 10.75%]
|
16
|
+
|
17
|
+
Bacon::Error: was 10.75%
|
18
|
+
spec.rb:698:in `satisfy:': AppScreenshots.timeline - should be <= 5.0% difference
|
19
|
+
spec.rb:438:in `execute_block'
|
20
|
+
spec.rb:402:in `run_postponed_block:'
|
21
|
+
spec.rb:397:in `resume'
|
22
|
+
|
23
|
+
3 specifications (3 requirements), 1 failures, 0 errors
|
24
|
+
```
|
25
|
+
|
26
|
+
![exampke](readme.png)
|
27
|
+
|
28
|
+
(calculated diffs are truer than visual representation)
|
29
|
+
|
30
|
+
## Installation
|
31
|
+
|
32
|
+
Add this line to your application's Gemfile:
|
33
|
+
|
34
|
+
gem 'motion-screenspecs'
|
35
|
+
|
36
|
+
And then execute:
|
37
|
+
|
38
|
+
$ bundle
|
39
|
+
|
40
|
+
Or install it yourself as:
|
41
|
+
|
42
|
+
$ gem install motion-screenspecs
|
43
|
+
|
44
|
+
## Usage
|
45
|
+
|
46
|
+
motion-screenspecs works in unison with [motion-screenshots](https://github.com/usepropeller/motion-screenshots). Here are the steps you need to get everything working:
|
47
|
+
|
48
|
+
1. Create a subclass of `Motion::Screenshots::Base` (see the [motion-screenshots README](https://github.com/usepropeller/motion-screenshots/blob/master/README.md)).
|
49
|
+
|
50
|
+
2. Create the following directory structure:
|
51
|
+
|
52
|
+
```
|
53
|
+
spec/
|
54
|
+
screenshots/
|
55
|
+
[YourScreenshotSubclass]
|
56
|
+
expectations/
|
57
|
+
[title of your screenshot].png
|
58
|
+
[title of your other screenshot].png
|
59
|
+
[etc].png
|
60
|
+
```
|
61
|
+
|
62
|
+
The images in `expectations` are known values for your app. You can take these screenshots manually or using motion-screenshots' `rake screenshots`.
|
63
|
+
|
64
|
+
Failing image diffs will be saved in `spec/screenshots/[YourScreenshotSubclass]/failures`. All results from the latest test are saved to `spec/screenshots/[YourScreenshotSubclass]/results`.
|
65
|
+
|
66
|
+
3. Add a call to `tests_screenshots` in your specs:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
describe "Screenshots" do
|
70
|
+
tests_screenshots AppScreenshots
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
The [sample app](sample) is a complete example with a failing test.
|
75
|
+
|
76
|
+
Hats off to [Jeff Kreeftmeijer](http://jeffkreeftmeijer.com/2011/comparing-images-and-creating-image-diffs/) for the image diffing help!
|
77
|
+
|
78
|
+
## Configuration
|
79
|
+
|
80
|
+
There's a couple of configuration options you can use:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
Motion::Project::App.setup do |app|
|
84
|
+
# Set your tolerance % for image differences
|
85
|
+
Motion::Screenspecs.set_tolerance(5.0, app)
|
86
|
+
|
87
|
+
# Set how long the tests will wait for your screenshots to finish
|
88
|
+
Motion::Screenspecs.set_screenshot_timeout(120, app)
|
89
|
+
|
90
|
+
# Set how long the tests will wait for a given image to finish diffing
|
91
|
+
Motion::Screenspecs.set_diff_timeout(20, app)
|
92
|
+
|
93
|
+
# Set whether or not your failed diffs will open in Finder upon finishing tests
|
94
|
+
Motion::Screenspecs.open_failures_at_exit = true
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
`rake spec`
|
99
|
+
|
100
|
+
## Contributing
|
101
|
+
|
102
|
+
1. Fork it
|
103
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
104
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
105
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
106
|
+
5. Create new Pull Request
|
107
|
+
|
108
|
+
## Contact
|
109
|
+
|
110
|
+
[Clay Allsopp](http://clayallsopp.com/)
|
111
|
+
[@clayallsopp](https://twitter.com/clayallsopp)
|
@@ -0,0 +1,207 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'motion-screenshots'
|
6
|
+
require 'motion-env'
|
7
|
+
|
8
|
+
require 'webrick'
|
9
|
+
require 'oily_png'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'shellwords'
|
12
|
+
|
13
|
+
is_spec_mode = Rake.application.top_level_tasks.include?('spec')
|
14
|
+
|
15
|
+
if is_spec_mode
|
16
|
+
class Motion::Project::Config
|
17
|
+
|
18
|
+
def spec_core_files_with_screenshots
|
19
|
+
files = spec_core_files_without_screenshots
|
20
|
+
|
21
|
+
lib_helper = File.join(File.dirname(__FILE__), 'spec', 'helpers.rb')
|
22
|
+
files << lib_helper unless files.include?(lib_helper)
|
23
|
+
files
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method "spec_core_files_without_screenshots", "spec_core_files"
|
27
|
+
alias_method "spec_core_files", "spec_core_files_with_screenshots"
|
28
|
+
end
|
29
|
+
|
30
|
+
class Motion::Project::App
|
31
|
+
class << self
|
32
|
+
def build_with_screenspecs(platform, opts = {})
|
33
|
+
unless File.exist?('vendor/Pods/KSScreenshotManager')
|
34
|
+
Rake::Task["pod:install"].reenable
|
35
|
+
Rake::Task["pod:install"].invoke
|
36
|
+
end
|
37
|
+
build_without_screenspecs(platform, opts)
|
38
|
+
end
|
39
|
+
|
40
|
+
alias_method "build_without_screenspecs", "build"
|
41
|
+
alias_method "build", "build_with_screenspecs"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Motion
|
47
|
+
module Screenspecs
|
48
|
+
PORT = 9678
|
49
|
+
TOLERANCE_ENV = '_motion-screenspecs-tolerance'
|
50
|
+
SCREENSHOT_TIMEOUT_ENV = '_motion-screenspecs-screenshot-timeout'
|
51
|
+
DIFF_TIMEOUT_ENV = '_motion-screenspecs-diff-timeout'
|
52
|
+
|
53
|
+
def self.tolerance
|
54
|
+
@tolerance
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.set_tolerance(tolerance, config)
|
58
|
+
@tolerance = tolerance
|
59
|
+
config.env[TOLERANCE_ENV] = tolerance
|
60
|
+
tolerance
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.set_screenshot_timeout(timeout, config)
|
64
|
+
config.env[SCREENSHOT_TIMEOUT_ENV] = timeout
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.set_diff_timeout(timeout, config)
|
68
|
+
config.env[DIFF_TIMEOUT_ENV] = timeout
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.open_failures_at_exit?
|
72
|
+
!!@open_failures_at_exit
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.open_failures_at_exit=(open)
|
76
|
+
@open_failures_at_exit = open
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.failures
|
80
|
+
@failures ||= []
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.screenshots_root(screenshot_class)
|
84
|
+
"spec/screenshots/#{screenshot_class}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.start_server!
|
88
|
+
# Start a web server to bounce file paths to-and-from the
|
89
|
+
# RubyMotion-CRuby barrier
|
90
|
+
@web_server ||= begin
|
91
|
+
server = WEBrick::HTTPServer.new(:Port => Motion::Screenspecs::PORT, :Logger => WEBrick::Log.new("/dev/null"), :AccessLog => [])
|
92
|
+
server.mount '/', Motion::Screenspecs::Servlet
|
93
|
+
at_exit {
|
94
|
+
server.shutdown
|
95
|
+
}
|
96
|
+
Thread.start do
|
97
|
+
server.start
|
98
|
+
end
|
99
|
+
server
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
104
|
+
def do_GET (request, response)
|
105
|
+
if (title = request.query["title"]) &&
|
106
|
+
(screenshot_path = request.query["screenshot_path"]) &&
|
107
|
+
(screenshot_class = request.query['screenshot_class'])
|
108
|
+
screenshot_path.gsub!("file://", "")
|
109
|
+
screenshots_root = Motion::Screenspecs.screenshots_root(screenshot_class)
|
110
|
+
expectation_path = File.expand_path(File.join(screenshots_root, "expectations", "#{title}.png"))
|
111
|
+
result_path = File.expand_path(File.join(screenshots_root, "results", "#{title}.png"))
|
112
|
+
failure_path = File.expand_path(File.join(screenshots_root, "failures", "#{title}.png"))
|
113
|
+
FileUtils.mkdir_p(File.dirname(failure_path))
|
114
|
+
FileUtils.mkdir_p(File.dirname(result_path))
|
115
|
+
|
116
|
+
File.delete(result_path) if File.exists?(result_path)
|
117
|
+
temp_result_path = File.join(File.dirname(result_path), File.basename(screenshot_path))
|
118
|
+
FileUtils.cp(screenshot_path, File.dirname(result_path))
|
119
|
+
FileUtils.mv(temp_result_path, result_path)
|
120
|
+
|
121
|
+
File.delete(failure_path) if File.exists?(failure_path)
|
122
|
+
|
123
|
+
percentage = Motion::Screenspecs::ImageDiff.new.percentage(expectation_path, screenshot_path, failure_path)
|
124
|
+
success = percentage < Motion::Screenspecs.tolerance
|
125
|
+
response.status = success ? 200 : 400
|
126
|
+
response.content_type = "text/plain"
|
127
|
+
response.body = "%05.2f" % percentage
|
128
|
+
else
|
129
|
+
response.status = 404
|
130
|
+
response.body = "You did not provide the correct parameters"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class ImageDiff
|
136
|
+
include ChunkyPNG::Color
|
137
|
+
|
138
|
+
# via http://jeffkreeftmeijer.com/2011/comparing-images-and-creating-image-diffs/
|
139
|
+
def percentage(image_a, image_b, failure_path)
|
140
|
+
images = [
|
141
|
+
ChunkyPNG::Image.from_file(image_a),
|
142
|
+
ChunkyPNG::Image.from_file(image_b)
|
143
|
+
]
|
144
|
+
|
145
|
+
output = ChunkyPNG::Image.new(images.first.width, images.last.height, WHITE)
|
146
|
+
|
147
|
+
diff = []
|
148
|
+
|
149
|
+
images.first.height.times do |y|
|
150
|
+
images.first.row(y).each_with_index do |pixel, x|
|
151
|
+
unless pixel == images.last[x,y]
|
152
|
+
score = Math.sqrt(
|
153
|
+
(r(images.last[x,y]) - r(pixel)) ** 2 +
|
154
|
+
(g(images.last[x,y]) - g(pixel)) ** 2 +
|
155
|
+
(b(images.last[x,y]) - b(pixel)) ** 2
|
156
|
+
) / Math.sqrt(MAX ** 2 * 3)
|
157
|
+
|
158
|
+
output[x,y] = rgb(
|
159
|
+
r(pixel) + r(images.last[x,y]) - 2 * [r(pixel), r(images.last[x,y])].min,
|
160
|
+
g(pixel) + g(images.last[x,y]) - 2 * [g(pixel), g(images.last[x,y])].min,
|
161
|
+
b(pixel) + b(images.last[x,y]) - 2 * [b(pixel), b(images.last[x,y])].min
|
162
|
+
)
|
163
|
+
diff << score
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
summed = diff.inject {|sum, value| sum + value}
|
169
|
+
if summed
|
170
|
+
percent = (summed / images.first.pixels.length) * 100
|
171
|
+
else
|
172
|
+
percent = 0
|
173
|
+
end
|
174
|
+
if percent >= Motion::Screenspecs.tolerance
|
175
|
+
output.save(failure_path)
|
176
|
+
Motion::Screenspecs.failures << failure_path
|
177
|
+
end
|
178
|
+
percent
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
lib_dir_path = File.dirname(File.expand_path(__FILE__))
|
185
|
+
Motion::Project::App.setup do |app|
|
186
|
+
app.files.unshift(Dir.glob(File.join(lib_dir_path, "motion/**/*.rb")))
|
187
|
+
|
188
|
+
Motion::Screenspecs.set_tolerance(5.0, app)
|
189
|
+
Motion::Screenspecs.set_screenshot_timeout(120, app)
|
190
|
+
Motion::Screenspecs.set_diff_timeout(20, app)
|
191
|
+
Motion::Screenspecs.open_failures_at_exit = true
|
192
|
+
|
193
|
+
if is_spec_mode
|
194
|
+
app.pods do
|
195
|
+
pod 'KSScreenshotManager'
|
196
|
+
end
|
197
|
+
|
198
|
+
Motion::Screenspecs.start_server!
|
199
|
+
|
200
|
+
at_exit {
|
201
|
+
if Motion::Screenspecs.open_failures_at_exit?
|
202
|
+
folder_path = Dir.glob('spec/**/failures').first
|
203
|
+
`open #{folder_path.shellescape}` unless Motion::Screenspecs.failures.empty?
|
204
|
+
end
|
205
|
+
}
|
206
|
+
end
|
207
|
+
end
|
data/lib/spec/helpers.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module Bacon
|
2
|
+
class Context
|
3
|
+
def tests_screenshots(screenshot_class)
|
4
|
+
describe screenshot_class do
|
5
|
+
shared_screenshots = screenshot_class.send(:shared)
|
6
|
+
shared_screenshots.exitOnComplete = false
|
7
|
+
if shared_screenshots.respond_to?("isLoggingEnabled")
|
8
|
+
shared_screenshots.loggingEnabled = false
|
9
|
+
end
|
10
|
+
tolerance = ENV['_motion-screenspecs-tolerance']
|
11
|
+
screenshot_timeout = ENV['_motion-screenspecs-screenshot-timeout']
|
12
|
+
diff_timeout = ENV['_motion-screenspecs-diff-timeout']
|
13
|
+
|
14
|
+
it "should take screenshots" do
|
15
|
+
existing_after = shared_screenshots.screenshot_groups.last.instance_variable_get("@after_actions")
|
16
|
+
shared_screenshots.screenshot_groups.last.after do
|
17
|
+
existing_after.call if existing_after
|
18
|
+
|
19
|
+
# resume tests
|
20
|
+
resume
|
21
|
+
end
|
22
|
+
|
23
|
+
screenshot_class.start!
|
24
|
+
wait_max screenshot_timeout do
|
25
|
+
|
26
|
+
true.should == true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
shared_screenshots.screenshot_groups.each do |group|
|
31
|
+
title = group.instance_variable_get('@title')
|
32
|
+
before do
|
33
|
+
path_url = shared_screenshots.screenshotsURL
|
34
|
+
|
35
|
+
is_retina = UIScreen.mainScreen.scale != 1.0
|
36
|
+
density = is_retina ? "@2x" : ""
|
37
|
+
device_prefix = nil
|
38
|
+
|
39
|
+
if UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad
|
40
|
+
device_prefix = "ipad"
|
41
|
+
elsif UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone
|
42
|
+
device_prefix = "iphone#{CGRectGetHeight(UIScreen.mainScreen.bounds).to_i}"
|
43
|
+
end
|
44
|
+
|
45
|
+
device_prefix << density
|
46
|
+
|
47
|
+
file_name = "#{device_prefix}-#{NSLocale.currentLocale.localeIdentifier}-#{title}.png"
|
48
|
+
path = path_url.URLByAppendingPathComponent(file_name)
|
49
|
+
@screenshot_path = path.absoluteString
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#{screenshot_class}.#{title}" do
|
53
|
+
it "should be < #{tolerance}% difference" do
|
54
|
+
|
55
|
+
url = NSURL.URLWithString "http://localhost:9678?screenshot_class=#{screenshot_class}&title=#{title}&screenshot_path=#{@screenshot_path}"
|
56
|
+
request = NSURLRequest.requestWithURL(url)
|
57
|
+
NSURLConnection.sendAsynchronousRequest(request, queue:NSOperationQueue.mainQueue,
|
58
|
+
completionHandler:->(ns_url_response, data, error){
|
59
|
+
@response = ns_url_response
|
60
|
+
@data = data
|
61
|
+
resume
|
62
|
+
})
|
63
|
+
|
64
|
+
wait_max diff_timeout do
|
65
|
+
body = NSString.alloc.initWithData(@data, encoding:NSUTF8StringEncoding)
|
66
|
+
@response.statusCode.should.satisfy("was #{body}%") {|object|
|
67
|
+
object == 200
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: motion-screenspecs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Clay Allsopp
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: motion-screenshots
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: oily_png
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.1.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: motion-env
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.0.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
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
|
+
description: Test your RubyMotion apps using screenshot comparison.
|
70
|
+
email:
|
71
|
+
- clay.allsopp@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- README.md
|
77
|
+
- lib/motion-screenspecs.rb
|
78
|
+
- lib/spec/helpers.rb
|
79
|
+
homepage: https://github.com/usepropeller/motion-screenspecs
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 2.0.3
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: Test your RubyMotion apps using screenshot comparison.
|
103
|
+
test_files: []
|