green_onion 0.0.2 → 0.0.3
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.
- data/.travis.yml +1 -1
- data/README.md +48 -5
- data/Rakefile +22 -0
- data/green_onion.gemspec +2 -0
- data/lib/green_onion/compare.rb +16 -8
- data/lib/green_onion/configuration.rb +1 -5
- data/lib/green_onion/version.rb +1 -1
- data/lib/green_onion.rb +39 -6
- data/spec/sample_app/sample_app.rb +17 -0
- data/spec/unit/green_onion_spec.rb +44 -6
- data/spec/unit/screenshot_spec.rb +15 -12
- metadata +29 -5
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -11,25 +11,68 @@ GreenOnion is a testing library for the UI only. It alerts you when the appearan
|
|
11
11
|
|
12
12
|
Add this line to your application's Gemfile:
|
13
13
|
|
14
|
-
gem 'green_onion'
|
14
|
+
`gem 'green_onion'`
|
15
15
|
|
16
16
|
And then execute:
|
17
17
|
|
18
|
-
|
18
|
+
`bundle`
|
19
19
|
|
20
20
|
Or install it yourself as:
|
21
21
|
|
22
|
-
|
22
|
+
`gem install green_onion`
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
|
26
|
-
###
|
26
|
+
### RSpec
|
27
27
|
|
28
|
+
For RSpec, `require 'green_onion'` in your spec_helper.rb file. Place this block in the file also:
|
29
|
+
|
30
|
+
`GreenOnion.configure do |c|
|
31
|
+
c.skins_dir = 'spec/skins'
|
32
|
+
c.threshold = 20
|
33
|
+
end`
|
34
|
+
|
35
|
+
* `skins_dir` is the directory that GreenOnion will store all skins. The namespace for skins is {URI name}.png (original), {URI name}_fresh.png (testing), and {URI name}_diff.png
|
36
|
+
* `threshold` is the percentage of acceptable change that the screenshots can take. This number can always be overwritten for an instance.
|
37
|
+
|
38
|
+
Then use one of the three methods below in a test...
|
39
|
+
|
40
|
+
### Percentage of change
|
41
|
+
|
42
|
+
`GreenOnion.skin_percentage(url, threshold {optional})`
|
28
43
|
The primary feature of GreenOnion is seeing how much (if at all) a view has changed from one instance to the next, and being alerted when a view has surpassed into an unacceptable threshold.
|
29
44
|
|
45
|
+
* `url` is the screen you want tested. Must include http://, example - 'http://yourSite.com'
|
46
|
+
* `threshold` can be overwritten here, or if not given in the configure block – it will default to a threshold of 100%
|
47
|
+
|
30
48
|
### Viewing screenshot diffs
|
31
49
|
|
32
|
-
|
50
|
+
`GreenOnion.skin_visual(url)`
|
51
|
+
Once you are aware of a issue in the UI, you can also rip open your spec/skins directory and manually see what the differences are from one screenshot to the next.
|
52
|
+
|
53
|
+
* `url` is the screen you want tested. Must include http://, example - 'http://yourSite.com'
|
54
|
+
|
55
|
+
### Both viewing screenshot diffs and percentage of change
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
## Contributing
|
60
|
+
|
61
|
+
### Testing
|
62
|
+
|
63
|
+
The best way to run the specs is with...
|
64
|
+
|
65
|
+
`bundle exec rake spec`
|
66
|
+
|
67
|
+
...this way a Sinatra WEBrick server will run concurrently with the test suite, and exit on completion. You can see the Sinatra app in spec/sample_app.
|
68
|
+
|
69
|
+
## Roadmap
|
70
|
+
|
71
|
+
* Screenshots can either be viewed as a visual diff, or overlayed newest over oldest and viewed as an onion-skin with sliding transparency.
|
72
|
+
* Allow for headless screenshooting
|
73
|
+
* More robust tests, especially around the visual diffs themselves
|
74
|
+
* More documentation
|
75
|
+
* More configuration/customizable settings
|
33
76
|
|
34
77
|
## THANK YOU
|
35
78
|
|
data/Rakefile
CHANGED
@@ -1,2 +1,24 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
require "bundler/gem_tasks"
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
desc "Running server..."
|
6
|
+
task :server do
|
7
|
+
require File.dirname(__FILE__) + '/spec/sample_app/sample_app.rb'
|
8
|
+
Rack::Handler::WEBrick.run SampleApp, :Port => 8070
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Running specs..."
|
12
|
+
task :specs do
|
13
|
+
system "rspec spec"
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Running specs on test server"
|
17
|
+
task :spec do
|
18
|
+
# have the server run on its own thread so that it doesn't block the spec task
|
19
|
+
server = Thread.new do
|
20
|
+
task("server").execute
|
21
|
+
end
|
22
|
+
task("specs").execute
|
23
|
+
server.kill
|
24
|
+
end
|
data/green_onion.gemspec
CHANGED
@@ -13,9 +13,11 @@ Gem::Specification.new do |gem|
|
|
13
13
|
gem.add_development_dependency "fileutils"
|
14
14
|
gem.add_development_dependency "pry"
|
15
15
|
gem.add_development_dependency "debugger"
|
16
|
+
gem.add_development_dependency "sinatra"
|
16
17
|
|
17
18
|
gem.add_dependency "capybara"
|
18
19
|
gem.add_dependency "oily_png"
|
20
|
+
gem.add_dependency "rainbow"
|
19
21
|
|
20
22
|
gem.files = `git ls-files`.split($\)
|
21
23
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
data/lib/green_onion/compare.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "oily_png"
|
2
|
+
require "rainbow"
|
2
3
|
|
3
4
|
module GreenOnion
|
4
5
|
class Compare
|
@@ -25,17 +26,24 @@ module GreenOnion
|
|
25
26
|
|
26
27
|
def percentage_diff(org, fresh)
|
27
28
|
diff_images(org, fresh)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
puts "pixels (total): #{@total_px}"
|
33
|
-
puts "pixels changed: #{@changed_px}"
|
34
|
-
puts "pixels changed (%): #{@percentage_changed}%"
|
29
|
+
@total_px = @images.first.pixels.length
|
30
|
+
@changed_px = @diff.length
|
31
|
+
@percentage_changed = ( (@diff.length.to_f / @images.first.pixels.length) * 100 ).round(2)
|
35
32
|
end
|
36
33
|
|
37
34
|
def visual_diff(org, fresh)
|
38
35
|
diff_images(org, fresh)
|
36
|
+
diff_iterating(org, fresh)
|
37
|
+
end
|
38
|
+
|
39
|
+
def percentage_and_visual_diff(org, fresh)
|
40
|
+
diff_images(org, fresh)
|
41
|
+
@total_px = @images.first.pixels.length
|
42
|
+
@changed_px = @diff.length
|
43
|
+
@percentage_changed = ( (@diff.length.to_f / @images.first.pixels.length) * 100 ).round(2)
|
44
|
+
end
|
45
|
+
|
46
|
+
def diff_iterating(org, fresh)
|
39
47
|
x, y = @diff.map{ |xy| xy[0] }, @diff.map{ |xy| xy[1] }
|
40
48
|
|
41
49
|
@diffed_image = org.insert(-5, '_diff')
|
@@ -43,7 +51,7 @@ module GreenOnion
|
|
43
51
|
begin
|
44
52
|
@images.last.rect(x.min, y.min, x.max, y.max, ChunkyPNG::Color.rgb(0,255,0))
|
45
53
|
rescue NoMethodError
|
46
|
-
puts "#{org} and #{fresh} skins are the same."
|
54
|
+
puts "#{org} and #{fresh} skins are the same.".color(:yellow)
|
47
55
|
end
|
48
56
|
|
49
57
|
@images.last.save(@diffed_image)
|
data/lib/green_onion/version.rb
CHANGED
data/lib/green_onion.rb
CHANGED
@@ -2,11 +2,14 @@ require "green_onion/version"
|
|
2
2
|
require "green_onion/screenshot"
|
3
3
|
require "green_onion/compare"
|
4
4
|
require "green_onion/configuration"
|
5
|
+
require "rainbow"
|
6
|
+
|
5
7
|
module GreenOnion
|
6
8
|
class << self
|
7
9
|
|
8
10
|
attr_reader :compare, :screenshot
|
9
11
|
|
12
|
+
# Pass configure block to set Configuration object
|
10
13
|
def configure
|
11
14
|
yield configuration
|
12
15
|
end
|
@@ -15,26 +18,56 @@ module GreenOnion
|
|
15
18
|
@configuration ||= GreenOnion::Configuration.new
|
16
19
|
end
|
17
20
|
|
21
|
+
# Bring the Screenshot and Compare classes together to create a skin
|
18
22
|
def skin(url)
|
19
23
|
@screenshot = Screenshot.new(
|
20
|
-
:dir =>
|
24
|
+
:dir => @configuration.skins_dir
|
21
25
|
)
|
22
26
|
@compare = GreenOnion::Compare.new
|
23
27
|
|
24
28
|
@screenshot.test_screenshot(url)
|
25
29
|
end
|
26
30
|
|
27
|
-
|
28
|
-
|
31
|
+
# Finds the percentage of change between skins
|
32
|
+
# Threshold can be set in configuration, or as an argument itself, and can be specific to an instance
|
33
|
+
def skin_percentage(url, threshold=@configuration.threshold)
|
34
|
+
threshold ||= 100
|
35
|
+
skin(url)
|
29
36
|
if(@screenshot.paths_hash.length > 1)
|
30
|
-
|
37
|
+
puts "\n" + url.color(:cyan)
|
38
|
+
@compare.percentage_diff(@screenshot.paths_hash[:original], @screenshot.paths_hash[:fresh])
|
39
|
+
threshold_alert(@compare.percentage_changed, threshold)
|
31
40
|
end
|
32
41
|
end
|
33
42
|
|
43
|
+
# Creates a diffed screenshot between skins
|
34
44
|
def skin_visual(url)
|
35
|
-
|
45
|
+
skin(url)
|
46
|
+
if(@screenshot.paths_hash.length > 1)
|
47
|
+
puts "\n" + url.color(:cyan)
|
48
|
+
@compare.visual_diff(@screenshot.paths_hash[:original], @screenshot.paths_hash[:fresh])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Creates a diffed screenshot between skins AND prints percentage changed
|
53
|
+
def skin_visual_and_percentage(url, threshold=@configuration.threshold)
|
54
|
+
skin(url)
|
36
55
|
if(@screenshot.paths_hash.length > 1)
|
37
|
-
|
56
|
+
puts "\n" + url.color(:cyan)
|
57
|
+
@compare.percentage_diff(@screenshot.paths_hash[:original], @screenshot.paths_hash[:fresh])
|
58
|
+
@compare.visual_diff(@screenshot.paths_hash[:original], @screenshot.paths_hash[:fresh])
|
59
|
+
threshold_alert(@compare.percentage_changed, threshold)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# This is used in skin_percentage to better alert if a set of skins are ok or not
|
64
|
+
def threshold_alert(actual, threshold)
|
65
|
+
if actual > threshold
|
66
|
+
puts "#{actual - threshold}% above threshold set @ #{threshold}%".color(:red)
|
67
|
+
puts "pixels changed (%): #{@compare.percentage_changed}%"
|
68
|
+
puts "pixels changed/total: #{@compare.changed_px}/#{@compare.total_px}"
|
69
|
+
else
|
70
|
+
puts "pixels changed/total: #{@compare.changed_px}/#{@compare.total_px}"
|
38
71
|
end
|
39
72
|
end
|
40
73
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'sinatra'
|
3
|
+
|
4
|
+
class SampleApp < Sinatra::Base
|
5
|
+
set :root, File.dirname(__FILE__)
|
6
|
+
set :static, true
|
7
|
+
set :logging, false
|
8
|
+
|
9
|
+
get '/' do
|
10
|
+
"<div style='height:200px; width:200px; background-color:rgb(#{rand(266)}, #{rand(266)}, #{rand(266)})'><!-- Big blue box --></div>"
|
11
|
+
end
|
12
|
+
|
13
|
+
get "/fake_uri" do
|
14
|
+
"<h2>foo</h2>"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -6,10 +6,13 @@ describe GreenOnion do
|
|
6
6
|
|
7
7
|
before(:each) do
|
8
8
|
@tmp_path = './spec/tmp'
|
9
|
+
@url = 'http://localhost:8070'
|
10
|
+
@url_w_uri = @url + '/fake_uri'
|
9
11
|
FileUtils.mkdir(@tmp_path)
|
10
12
|
|
11
13
|
GreenOnion.configure do |c|
|
12
14
|
c.skins_dir = @tmp_path
|
15
|
+
c.threshold = 1
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
@@ -23,7 +26,7 @@ describe GreenOnion do
|
|
23
26
|
|
24
27
|
it "should get the correct paths_hash" do
|
25
28
|
2.times do
|
26
|
-
GreenOnion.skin(
|
29
|
+
GreenOnion.skin(@url)
|
27
30
|
end
|
28
31
|
( (GreenOnion.screenshot.paths_hash[:original] == "#{@tmp_path}/root.png") &&
|
29
32
|
(GreenOnion.screenshot.paths_hash[:fresh] == "#{@tmp_path}/root_fresh.png") ).should be_true
|
@@ -31,18 +34,53 @@ describe GreenOnion do
|
|
31
34
|
|
32
35
|
it "should measure the percentage of diff between skins" do
|
33
36
|
2.times do
|
34
|
-
GreenOnion.skin_percentage(
|
37
|
+
GreenOnion.skin_percentage(@url)
|
35
38
|
end
|
36
|
-
GreenOnion.compare.percentage_changed.should be
|
39
|
+
GreenOnion.compare.percentage_changed.should be > 0
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should measure the percentage of diff between skins (even if there is no diff)" do
|
43
|
+
2.times do
|
44
|
+
GreenOnion.skin_percentage(@url_w_uri)
|
45
|
+
end
|
46
|
+
GreenOnion.compare.percentage_changed.should be == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should alert when diff percentage threshold is surpassed" do
|
50
|
+
$stdout.should_receive(:puts).exactly(4).times
|
51
|
+
2.times do
|
52
|
+
GreenOnion.skin_percentage(@url)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should print just URL and changed/total when diff percentage threshold has not been surpassed" do
|
57
|
+
$stdout.should_receive(:puts).exactly(2).times
|
58
|
+
2.times do
|
59
|
+
GreenOnion.skin_percentage(@url, 6)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should create visual diff between skins" do
|
64
|
+
2.times do
|
65
|
+
GreenOnion.skin_visual(@url)
|
66
|
+
end
|
67
|
+
GreenOnion.compare.diffed_image.should eq("#{@tmp_path}/root_diff.png")
|
37
68
|
end
|
38
69
|
|
39
|
-
it "should create visual diff between skins"
|
40
70
|
|
41
71
|
it "should create visual diff between skins (even when there is no change)" do
|
42
72
|
2.times do
|
43
|
-
GreenOnion.skin_visual(
|
73
|
+
GreenOnion.skin_visual(@url_w_uri)
|
74
|
+
end
|
75
|
+
GreenOnion.compare.diffed_image.should eq("#{@tmp_path}/fake_uri_diff.png")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should measure the percentage of diff between skins AND create visual diff" do
|
79
|
+
2.times do
|
80
|
+
GreenOnion.skin_visual_and_percentage(@url)
|
44
81
|
end
|
45
|
-
GreenOnion.compare.diffed_image.should eq(
|
82
|
+
( (GreenOnion.compare.diffed_image.should eq("#{@tmp_path}/root_diff.png")) &&
|
83
|
+
(GreenOnion.compare.percentage_changed.should be > 0) ).should be_true
|
46
84
|
end
|
47
85
|
|
48
86
|
end
|
@@ -2,6 +2,11 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe GreenOnion::Screenshot do
|
4
4
|
|
5
|
+
before(:all) do
|
6
|
+
@url = 'http://localhost:8070'
|
7
|
+
@url_w_uri = @url + '/fake_uri'
|
8
|
+
end
|
9
|
+
|
5
10
|
describe 'Snap single screenshot' do
|
6
11
|
|
7
12
|
before(:each) do
|
@@ -11,8 +16,7 @@ describe GreenOnion::Screenshot do
|
|
11
16
|
@screenshot = GreenOnion::Screenshot.new(
|
12
17
|
:dir => @tmp_path
|
13
18
|
)
|
14
|
-
@
|
15
|
-
@file = "#{@tmp_path}/maps.png"
|
19
|
+
@file = "#{@tmp_path}/fake_uri.png"
|
16
20
|
end
|
17
21
|
|
18
22
|
after(:each) do
|
@@ -20,24 +24,24 @@ describe GreenOnion::Screenshot do
|
|
20
24
|
end
|
21
25
|
|
22
26
|
it 'should build the path from the URI' do
|
23
|
-
@screenshot.url_to_path(@
|
27
|
+
@screenshot.url_to_path(@url_w_uri).should eq(@file)
|
24
28
|
end
|
25
29
|
|
26
30
|
it 'should build the path from root' do
|
27
|
-
@screenshot.url_to_path('http://
|
31
|
+
@screenshot.url_to_path('http://localhost:8070').should eq("#{@tmp_path}/root.png")
|
28
32
|
end
|
29
33
|
|
30
34
|
it 'should build the path from root (even with trailing slash)' do
|
31
|
-
@screenshot.url_to_path('http://
|
35
|
+
@screenshot.url_to_path('http://localhost:8070/').should eq("#{@tmp_path}/root.png")
|
32
36
|
end
|
33
37
|
|
34
38
|
it 'should snap and save screenshot' do
|
35
|
-
@screenshot.snap_screenshot(@
|
39
|
+
@screenshot.snap_screenshot(@url_w_uri, @file)
|
36
40
|
File.exist?(@file).should be_true
|
37
41
|
end
|
38
42
|
|
39
43
|
it "should destroy a singular screenshot" do
|
40
|
-
@screenshot.destroy(@
|
44
|
+
@screenshot.destroy(@url_w_uri)
|
41
45
|
File.exist?(@file).should be_false
|
42
46
|
end
|
43
47
|
end
|
@@ -51,11 +55,10 @@ describe GreenOnion::Screenshot do
|
|
51
55
|
@screenshot = GreenOnion::Screenshot.new(
|
52
56
|
:dir => @tmp_path
|
53
57
|
)
|
54
|
-
@
|
55
|
-
@
|
56
|
-
@file2 = "#{@tmp_path}/maps_fresh.png"
|
58
|
+
@file1 = "#{@tmp_path}/fake_uri.png"
|
59
|
+
@file2 = "#{@tmp_path}/fake_uri_fresh.png"
|
57
60
|
2.times do
|
58
|
-
@screenshot.test_screenshot(@
|
61
|
+
@screenshot.test_screenshot(@url_w_uri)
|
59
62
|
end
|
60
63
|
end
|
61
64
|
|
@@ -74,7 +77,7 @@ describe GreenOnion::Screenshot do
|
|
74
77
|
end
|
75
78
|
|
76
79
|
it "should destroy a set of screenshots" do
|
77
|
-
@screenshot.destroy(@
|
80
|
+
@screenshot.destroy(@url_w_uri)
|
78
81
|
( File.exist?(@file1) && File.exist?(@file2) ).should be_false
|
79
82
|
end
|
80
83
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: green_onion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Ted O'Meara
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-07-
|
13
|
+
date: 2012-07-27 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake
|
@@ -68,7 +68,7 @@ dependencies:
|
|
68
68
|
type: :development
|
69
69
|
version_requirements: *id005
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
71
|
+
name: sinatra
|
72
72
|
prerelease: false
|
73
73
|
requirement: &id006 !ruby/object:Gem::Requirement
|
74
74
|
none: false
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
- - ">="
|
77
77
|
- !ruby/object:Gem::Version
|
78
78
|
version: "0"
|
79
|
-
type: :
|
79
|
+
type: :development
|
80
80
|
version_requirements: *id006
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
|
-
name:
|
82
|
+
name: capybara
|
83
83
|
prerelease: false
|
84
84
|
requirement: &id007 !ruby/object:Gem::Requirement
|
85
85
|
none: false
|
@@ -89,6 +89,28 @@ dependencies:
|
|
89
89
|
version: "0"
|
90
90
|
type: :runtime
|
91
91
|
version_requirements: *id007
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: oily_png
|
94
|
+
prerelease: false
|
95
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
101
|
+
type: :runtime
|
102
|
+
version_requirements: *id008
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: rainbow
|
105
|
+
prerelease: false
|
106
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: "0"
|
112
|
+
type: :runtime
|
113
|
+
version_requirements: *id009
|
92
114
|
description: UI testing/screenshot diffing tool
|
93
115
|
email:
|
94
116
|
- ted@intridea.com
|
@@ -113,6 +135,7 @@ files:
|
|
113
135
|
- lib/green_onion/configuration.rb
|
114
136
|
- lib/green_onion/screenshot.rb
|
115
137
|
- lib/green_onion/version.rb
|
138
|
+
- spec/sample_app/sample_app.rb
|
116
139
|
- spec/skins/spec_shot.png
|
117
140
|
- spec/skins/spec_shot_fresh.png
|
118
141
|
- spec/spec_helper.rb
|
@@ -147,6 +170,7 @@ signing_key:
|
|
147
170
|
specification_version: 3
|
148
171
|
summary: Regressions in the view making you cry? Have more confidence with GreenOnion.
|
149
172
|
test_files:
|
173
|
+
- spec/sample_app/sample_app.rb
|
150
174
|
- spec/skins/spec_shot.png
|
151
175
|
- spec/skins/spec_shot_fresh.png
|
152
176
|
- spec/spec_helper.rb
|