├── .rspec
├── .rvmrc
├── lib
├── green_onion
│ ├── version.rb
│ ├── errors.rb
│ ├── drivers
│ │ ├── selenium.rb
│ │ ├── webkit.rb
│ │ └── poltergeist.rb
│ ├── browser.rb
│ ├── generators
│ │ └── skinner.erb
│ ├── configuration.rb
│ ├── cli.rb
│ ├── screenshot.rb
│ └── compare.rb
└── green_onion.rb
├── bin
└── green_onion
├── spec
├── skins
│ ├── spec_shot.png
│ ├── spec_shot_fresh.png
│ └── spec_shot_resize.png
├── sample_app
│ ├── public
│ │ ├── onion_face_0.jpg
│ │ └── onion_face_1.jpg
│ └── sample_app.rb
├── spec_helper.rb
└── unit
│ ├── drivers
│ ├── webkit_spec.rb
│ ├── selenium_spec.rb
│ └── poltergeist_spec.rb
│ ├── compare_spec.rb
│ ├── cli_spec.rb
│ ├── screenshot_spec.rb
│ └── green_onion_spec.rb
├── Gemfile
├── .travis.yml
├── .gitignore
├── LICENSE
├── green_onion.gemspec
├── Rakefile
└── README.md
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format documentation
--------------------------------------------------------------------------------
/.rvmrc:
--------------------------------------------------------------------------------
1 | rvm use 1.9.2@green_onion --create
2 |
--------------------------------------------------------------------------------
/lib/green_onion/version.rb:
--------------------------------------------------------------------------------
1 | module GreenOnion
2 | VERSION = "0.1.4"
3 | end
4 |
--------------------------------------------------------------------------------
/bin/green_onion:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "green_onion/cli"
3 |
4 | GreenOnion::CLI.start
--------------------------------------------------------------------------------
/spec/skins/spec_shot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobomo/green_onion/HEAD/spec/skins/spec_shot.png
--------------------------------------------------------------------------------
/spec/skins/spec_shot_fresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobomo/green_onion/HEAD/spec/skins/spec_shot_fresh.png
--------------------------------------------------------------------------------
/spec/skins/spec_shot_resize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobomo/green_onion/HEAD/spec/skins/spec_shot_resize.png
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in green_onion.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/spec/sample_app/public/onion_face_0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobomo/green_onion/HEAD/spec/sample_app/public/onion_face_0.jpg
--------------------------------------------------------------------------------
/spec/sample_app/public/onion_face_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobomo/green_onion/HEAD/spec/sample_app/public/onion_face_1.jpg
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 | require 'fileutils'
4 | require 'green_onion'
5 |
6 | RSpec.configure do |config|
7 | # some (optional) config here
8 | end
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 1.9.2
4 | before_install:
5 | - sudo apt-get install libqt4-dev libqtwebkit-dev
6 | before_script:
7 | - "export DISPLAY=:99.0"
8 | - "sh -e /etc/init.d/xvfb start"
9 | script: rake spec
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 |
--------------------------------------------------------------------------------
/lib/green_onion/errors.rb:
--------------------------------------------------------------------------------
1 | module GreenOnion
2 | class Errors
3 | #Base class for all errors
4 | class Error < StandardError; end
5 |
6 | class IllformattedURL < Error; end
7 |
8 | class ThresholdOutOfRange < Error; end
9 | end
10 | end
--------------------------------------------------------------------------------
/lib/green_onion/drivers/selenium.rb:
--------------------------------------------------------------------------------
1 | require 'capybara/dsl'
2 |
3 | module GreenOnion
4 | class Selenium
5 | include Capybara::DSL
6 |
7 | def initialize
8 | Capybara.default_driver = :selenium
9 | end
10 |
11 | def record(url, path, dimensions=nil)
12 | visit url
13 | page.driver.browser.save_screenshot(path)
14 | end
15 |
16 | end
17 | end
--------------------------------------------------------------------------------
/lib/green_onion/drivers/webkit.rb:
--------------------------------------------------------------------------------
1 | require 'capybara/dsl'
2 | require 'capybara-webkit'
3 |
4 | module GreenOnion
5 | class Webkit
6 | include Capybara::DSL
7 |
8 | def initialize
9 | Capybara.default_driver = :webkit
10 | end
11 |
12 | def record(url, path, dimensions)
13 | visit url
14 | page.driver.render(path, dimensions)
15 | end
16 |
17 | end
18 | end
--------------------------------------------------------------------------------
/lib/green_onion/drivers/poltergeist.rb:
--------------------------------------------------------------------------------
1 | require 'capybara/dsl'
2 | require 'capybara/poltergeist'
3 |
4 | module GreenOnion
5 | class Poltergeist
6 | include Capybara::DSL
7 |
8 | def initialize
9 | Capybara.default_driver = :poltergeist
10 | end
11 |
12 | def record(url, path, dimensions)
13 | visit url
14 | page.driver.resize(dimensions[:width], dimensions[:height])
15 | page.driver.render(path, :full => true)
16 | end
17 |
18 | end
19 | end
--------------------------------------------------------------------------------
/spec/sample_app/sample_app.rb:
--------------------------------------------------------------------------------
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 | "
"
11 | end
12 |
13 | get "/fake_uri" do
14 | "foo
"
15 | end
16 |
17 | get "/onion_face" do
18 | "
"
19 | end
20 |
21 | get "/another/uri/string" do
22 | "It was the best of times, it was the blorst of times.
"
23 | end
24 |
25 | end
--------------------------------------------------------------------------------
/lib/green_onion/browser.rb:
--------------------------------------------------------------------------------
1 | module GreenOnion
2 | class Browser
3 |
4 | attr_reader :driver, :dimensions
5 |
6 | def initialize(params={})
7 | @driver = params[:driver]
8 | @dimensions = params[:dimensions]
9 | load_driver
10 | end
11 |
12 | def load_driver
13 | begin
14 | require "green_onion/drivers/#{driver}"
15 | @driver_obj = GreenOnion.const_get(@driver.capitalize).new
16 | rescue LoadError => e
17 | raise e unless e.message.include?("green_onion/drivers")
18 | raise ArgumentError.new("#{@driver} is not supported by GreenOnion.")
19 | end
20 | end
21 |
22 | def snap_screenshot(url, path)
23 | @driver_obj.record(url, path, @dimensions)
24 | end
25 |
26 | end
27 | end
--------------------------------------------------------------------------------
/lib/green_onion/generators/skinner.erb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'rubygems'
4 | require 'green_onion'
5 |
6 | ENV["RAILS_ENV"] ||= 'test'
7 | require File.expand_path("../../config/environment", __FILE__)
8 |
9 | GreenOnion.configure do |c|
10 | # You can use and customize these configuration options
11 | # c.skins_dir = "#{::Rails.root}/spec/skins"
12 | # c.threshold = 100
13 | # c.dimensions = { :width => 1024, :height => 768 }
14 | # c.skin_name = {
15 | # :match => /[\/]/,
16 | # :replace => "_",
17 | # :prefix => nil,
18 | # :root => "root"
19 | # }
20 | end
21 |
22 | all_routes = Rails.application.routes.routes
23 | routes = all_routes.collect { |r| r.path.spec.to_s.gsub(/\/*(\(\.)*:(\w*)(\))*\/*/, "") }.delete_if(&:empty?)
24 |
25 | routes.each do |route|
26 | GreenOnion.skin_visual_and_percentage("<%= config[:url] %>" + route)
27 | end
--------------------------------------------------------------------------------
/spec/unit/drivers/webkit_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe 'Using Webkit' do
4 |
5 | before(:all) do
6 | @url = 'http://localhost:8070'
7 | @url_w_uri = @url + '/fake_uri'
8 | @tmp_path = './spec/tmp'
9 | @browser = GreenOnion::Browser.new(
10 | :dimensions => { :width => 1024, :height => 768 },
11 | :driver => "webkit"
12 | )
13 | end
14 |
15 | before(:each) do
16 | @screenshot = GreenOnion::Screenshot.new(
17 | :browser => @browser,
18 | :dir => @tmp_path,
19 | :skin_name => {
20 | :match => /[\/]/,
21 | :replace => "_",
22 | :prefix => nil,
23 | :root => "root"
24 | }
25 | )
26 | @file = "#{@tmp_path}/fake_uri.png"
27 | end
28 |
29 | after(:each) do
30 | FileUtils.rm_r(@tmp_path, :force => true)
31 | end
32 |
33 | it "should snap and save screenshot w/ Webkit" do
34 | @screenshot.test_screenshot(@url_w_uri)
35 | File.exist?(@file).should be_true
36 | end
37 | end
--------------------------------------------------------------------------------
/spec/unit/drivers/selenium_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe 'Using Selenium' do
4 |
5 | before(:all) do
6 | @url = 'http://localhost:8070'
7 | @url_w_uri = @url + '/fake_uri'
8 | @tmp_path = './spec/tmp'
9 | @browser = GreenOnion::Browser.new(
10 | :dimensions => { :width => 1024, :height => 768 },
11 | :driver => "selenium"
12 | )
13 | end
14 |
15 | before(:each) do
16 | @screenshot = GreenOnion::Screenshot.new(
17 | :browser => @browser,
18 | :dir => @tmp_path,
19 | :skin_name => {
20 | :match => /[\/]/,
21 | :replace => "_",
22 | :prefix => nil,
23 | :root => "root"
24 | }
25 | )
26 | @file = "#{@tmp_path}/fake_uri.png"
27 | end
28 |
29 | after(:each) do
30 | FileUtils.rm_r(@tmp_path, :force => true)
31 | end
32 |
33 | it "should snap and save screenshot w/ Selenium" do
34 | @screenshot.test_screenshot(@url_w_uri)
35 | File.exist?(@file).should be_true
36 | end
37 | end
--------------------------------------------------------------------------------
/spec/unit/drivers/poltergeist_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe 'Using Poltergeist' do
4 |
5 | before(:all) do
6 | @url = 'http://localhost:8070'
7 | @url_w_uri = @url + '/fake_uri'
8 | @tmp_path = './spec/tmp'
9 | @browser = GreenOnion::Browser.new(
10 | :dimensions => { :width => 1024, :height => 768 },
11 | :driver => "poltergeist"
12 | )
13 | end
14 |
15 | before(:each) do
16 | @screenshot = GreenOnion::Screenshot.new(
17 | :browser => @browser,
18 | :dir => @tmp_path,
19 | :skin_name => {
20 | :match => /[\/]/,
21 | :replace => "_",
22 | :prefix => nil,
23 | :root => "root"
24 | }
25 | )
26 | @file = "#{@tmp_path}/fake_uri.png"
27 | end
28 |
29 | after(:each) do
30 | FileUtils.rm_r(@tmp_path, :force => true)
31 | end
32 |
33 | it "should snap and save screenshot w/ Poltergeist" do
34 | @screenshot.test_screenshot(@url_w_uri)
35 | File.exist?(@file).should be_true
36 | end
37 | end
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Ted O'Meara
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/spec/unit/compare_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe GreenOnion::Compare do
4 |
5 | describe 'Comparing Screenshots' do
6 |
7 | before(:each) do
8 | @comparison = GreenOnion::Compare.new
9 | @spec_shot1 = './spec/skins/spec_shot.png'
10 | @spec_shot2 = './spec/skins/spec_shot_fresh.png'
11 | @spec_shot_resize = './spec/skins/spec_shot_resize.png'
12 | @diff_shot = './spec/skins/spec_shot_diff.png'
13 | end
14 |
15 | after(:all) do
16 | FileUtils.rm('./spec/skins/spec_shot_diff.png', :force => true)
17 | end
18 |
19 | it 'should get a percentage of difference between two shots' do
20 | @comparison.percentage_diff(@spec_shot1, @spec_shot2)
21 | @comparison.percentage_changed.should eq(66.0)
22 | end
23 |
24 | it 'should create a new file with a visual diff between two shots' do
25 | @comparison.visual_diff(@spec_shot1, @spec_shot2)
26 | File.exist?(@diff_shot).should be_true
27 | end
28 |
29 | it "should not throw error when dimensions are off" do
30 | expect { @comparison.visual_diff(@spec_shot1, @spec_shot_resize) }.to_not raise_error
31 | end
32 |
33 | end
34 | end
--------------------------------------------------------------------------------
/green_onion.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require File.expand_path('../lib/green_onion/version', __FILE__)
3 |
4 | Gem::Specification.new do |gem|
5 | gem.authors = ["Ted O'Meara"]
6 | gem.email = ["ted@intridea.com"]
7 | gem.description = %q{UI testing/screenshot diffing tool}
8 | gem.summary = %q{Regressions in the view making you cry? Have more confidence with GreenOnion.}
9 | gem.homepage = "http://intridea.github.com/green_onion"
10 |
11 | gem.add_development_dependency "rake"
12 | gem.add_development_dependency "rspec"
13 | gem.add_development_dependency "sinatra"
14 | gem.add_development_dependency "capybara-webkit"
15 | gem.add_development_dependency "poltergeist"
16 |
17 | gem.add_dependency "capybara", " ~> 1.1"
18 | gem.add_dependency "oily_png", "~> 1.0.2"
19 | gem.add_dependency "rainbow"
20 | gem.add_dependency "fileutils"
21 | gem.add_dependency "thor", ">= 0.14.6"
22 |
23 | gem.files = `git ls-files`.split($\)
24 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
25 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
26 | gem.name = "green_onion"
27 | gem.require_paths = ["lib"]
28 | gem.version = GreenOnion::VERSION
29 | end
30 |
--------------------------------------------------------------------------------
/lib/green_onion/configuration.rb:
--------------------------------------------------------------------------------
1 | module GreenOnion
2 | class Configuration
3 |
4 | attr_writer :threshold, :skins_dir, :driver
5 |
6 | def dimensions=(options)
7 | @dimensions = options
8 | end
9 |
10 | def dimensions
11 | @dimensions ||= { :height => 768, :width => 1024 }
12 | end
13 |
14 | def threshold
15 | @threshold ||= 100
16 | end
17 |
18 | def skins_dir
19 | @skins_dir ||= './spec/skins'
20 | end
21 |
22 | def driver
23 | @driver ||= :webkit
24 | end
25 |
26 | # Uses the driver and dimensions configuration vars to return a Browser object
27 | def browser
28 | @browser = Browser.new(
29 | :dimensions => dimensions,
30 | :driver => driver
31 | )
32 | end
33 |
34 | def skin_name=(options)
35 | @skin_name = skin_namespace_hash(options)
36 | end
37 |
38 | def skin_name
39 | @skin_name ||= skin_namespace_hash
40 | end
41 |
42 | # Serves as a template for skin_name getter/setter
43 | def skin_namespace_hash(options = {})
44 | {
45 | :match => options[:match] ? options[:match] : /[\/]/,
46 | :replace => options[:replace] ? options[:replace] : "_",
47 | :prefix => options[:prefix] ? options[:prefix] : nil,
48 | :root => options[:root] ? options[:root] : "root"
49 | }
50 | end
51 |
52 | end
53 | end
--------------------------------------------------------------------------------
/spec/unit/cli_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "bin/green_onion" do
4 |
5 | before(:all) do
6 | @tmp_path = './spec/tmp'
7 | @url = 'http://localhost:8070'
8 | @file1 = "#{@tmp_path}/root.png"
9 | @skinner_file = "#{@tmp_path}/skinner.rb"
10 | end
11 |
12 | describe "Skin Utility" do
13 |
14 | after(:each) do
15 | FileUtils.rm_r(@tmp_path, :force => true)
16 | end
17 |
18 | it "should run the skin task w/o any flags (need the --dir flag to keep spec directory clean)" do
19 | `bin/green_onion skin #{@url} -d=#{@tmp_path}`
20 | File.exist?(@file1).should be_true
21 | end
22 |
23 | it "should run the skin task w/ --method=p flag to run only percentage diff" do
24 | stdin, stdout, stderr = Open3.popen3("bin/green_onion skin #{@url} --dir=#{@tmp_path} --method=p --threshold=1 &&
25 | bin/green_onion skin #{@url} --dir=#{@tmp_path} --method=p --threshold=1")
26 | stderr.readlines.to_s.should include("above threshold set @")
27 | end
28 |
29 | end
30 |
31 | describe "Generator" do
32 |
33 | after(:each) do
34 | FileUtils.rm_r(@tmp_path, :force => true)
35 | end
36 |
37 | it "should build the skinner file" do
38 | `bin/green_onion generate --dir=#{@tmp_path}`
39 | File.exist?(@skinner_file).should be_true
40 | end
41 |
42 | it "should build the skinner file with the url included correctly" do
43 | `bin/green_onion generate --url=#{@url} --dir=#{@tmp_path}`
44 | skinner = IO.read(@skinner_file)
45 | skinner.should include("GreenOnion.skin_visual_and_percentage(\"http://localhost:8070\" + route)")
46 | end
47 |
48 | end
49 | end
--------------------------------------------------------------------------------
/lib/green_onion/cli.rb:
--------------------------------------------------------------------------------
1 | require "thor"
2 | require "green_onion"
3 |
4 | module GreenOnion
5 | class CLI < Thor
6 | include Thor::Actions
7 |
8 | source_root File.expand_path('../generators', __FILE__)
9 |
10 | class_option :dir, :aliases => "-d", :type => :string
11 |
12 | desc "skin ", "Creates skins from and compares them"
13 | method_option :method, :aliases => "-m", :type => :string
14 | method_option :threshold, :aliases => "-t", :type => :numeric
15 | method_option :browser, :aliases => "-b", :type => :string
16 | method_option :width, :aliases => "-w", :type => :numeric
17 | method_option :height, :aliases => "-h", :type => :numeric
18 | def skin(url)
19 | GreenOnion.configure do |c|
20 | c.skins_dir = options[:dir] if options[:dir]
21 | c.threshold = options[:threshold] if options[:threshold]
22 | c.dimensions = { :width => options[:width], :height => options[:height] } if options[:width] && options[:height]
23 | c.driver = options[:driver].to_sym if options[:driver]
24 | end
25 | case options[:method]
26 | when "v"
27 | GreenOnion.skin_visual(url)
28 | when "p"
29 | GreenOnion.skin_percentage(url)
30 | else
31 | GreenOnion.skin_visual_and_percentage(url)
32 | end
33 | end
34 |
35 | desc "generate", "Generates a 'skinner' file to test only Rails routes without params"
36 | method_option :url, :aliases => "-u", :type => :string
37 | def generate_skinner
38 | options[:dir] ? dir = options[:dir] : dir = "spec"
39 | options[:url] ? config = { :url => options[:url] } : config = { :url => "http://localhost:3000" }
40 | template('skinner.erb', "#{dir}/skinner.rb", config)
41 | end
42 | end
43 | end
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
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
25 |
26 | namespace :benchmarks do
27 | require 'benchmark'
28 | task("server").execute
29 | @tmp_path = './spec/tmp'
30 |
31 | desc "WebKit benchmark"
32 | task :webkit do
33 | require File.expand_path('../lib/green_onion', __FILE__)
34 | GreenOnion.configure do |c|
35 | c.skins_dir = @tmp_path
36 | c.driver = "webkit"
37 | end
38 | Benchmark.bm do |x|
39 | x.report("webkit:") { 2.times do; GreenOnion.skin_visual('http://localhost:8070'); end }
40 | end
41 | FileUtils.rm_r(@tmp_path, :force => true)
42 | end
43 |
44 | desc "PhantomJS benchmark"
45 | task :phantomjs do
46 | require File.expand_path('../lib/green_onion', __FILE__)
47 | GreenOnion.configure do |c|
48 | c.skins_dir = @tmp_path
49 | c.driver = "poltergeist"
50 | end
51 | Benchmark.bm do |x|
52 | x.report("poltergeist:") { 2.times do; GreenOnion.skin_visual('http://localhost:8070'); end }
53 | end
54 | FileUtils.rm_r(@tmp_path, :force => true)
55 | end
56 |
57 | desc "Selenium benchmark"
58 | task :selenium do
59 | require File.expand_path('../lib/green_onion', __FILE__)
60 | GreenOnion.configure do |c|
61 | c.skins_dir = @tmp_path
62 | c.driver = "selenium"
63 | end
64 | Benchmark.bm do |x|
65 | x.report("selenium:") { 2.times do; GreenOnion.skin_visual('http://localhost:8070'); end }
66 | end
67 | FileUtils.rm_r(@tmp_path, :force => true)
68 | end
69 | end
--------------------------------------------------------------------------------
/lib/green_onion/screenshot.rb:
--------------------------------------------------------------------------------
1 | require "fileutils"
2 |
3 | module GreenOnion
4 | class Screenshot
5 |
6 | attr_reader :paths_hash, :browser, :dir, :skin_name, :dimensions
7 |
8 | def initialize(params={})
9 | @dir = params[:dir]
10 | @skin_name = params[:skin_name]
11 | @browser = params[:browser]
12 | @paths_hash = {}
13 | end
14 |
15 | def test_screenshot(url)
16 | url_to_path(url)
17 | create_dir(@dir)
18 | @browser.snap_screenshot(url, @shot_path)
19 | end
20 |
21 | def url_to_path(url)
22 | get_path(url)
23 | if File.exist?(@paths_hash[:original])
24 | @paths_hash[:fresh] = @paths_hash[:original].dup.insert(-5, '_fresh')
25 | @shot_path = @paths_hash[:fresh]
26 | else
27 | @shot_path = @paths_hash[:original]
28 | end
29 | end
30 |
31 | def url_matcher(url)
32 | url_match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/).to_a.compact
33 | if url_match.length >= 5
34 | @filename = url_match[5]
35 | else
36 | raise Errors::IllformattedURL.new "Your URL is incorrectly formatted. Please make sure to use http://"
37 | end
38 | end
39 |
40 | def file_namer
41 | @filename.slice!(/^\//) # remove the beginning "/" if there is one
42 | @filename = @filename.gsub(@skin_name[:match], @skin_name[:replace]) # by default, all "/" in a URI string will be replaced with "_"
43 | @filename = @skin_name[:prefix] + @filename if @skin_name[:prefix] # add on a prefix defined in the configuration block
44 | @paths_hash[:original] = "#{@dir}/#{@filename}.png"
45 | end
46 |
47 | def create_dir(dir)
48 | unless Dir.exist?(dir)
49 | FileUtils.mkdir(dir)
50 | end
51 | end
52 |
53 | def get_path(url)
54 | url_matcher(url)
55 | if @filename.empty? || @filename == '/'
56 | @paths_hash[:original] = "#{@dir}/#{@skin_name[:root]}.png"
57 | else
58 | file_namer
59 | end
60 | end
61 |
62 | def destroy(url)
63 | get_path(url)
64 | destroy_files(@paths_hash[:original], @paths_hash[:fresh])
65 | end
66 |
67 | def destroy_files(org, fresh)
68 | if File.exist?(org)
69 | FileUtils.rm(org)
70 | if File.exist?(fresh)
71 | FileUtils.rm(fresh)
72 | end
73 | end
74 | end
75 |
76 | end
77 | end
--------------------------------------------------------------------------------
/lib/green_onion/compare.rb:
--------------------------------------------------------------------------------
1 | require "oily_png"
2 | require "rainbow"
3 |
4 | module GreenOnion
5 | class Compare
6 |
7 | attr_accessor :percentage_changed, :total_px, :changed_px
8 | attr_reader :diffed_image
9 |
10 | # Pulled from Jeff Kreeftmeijer's post here: http://jeffkreeftmeijer.com/2011/comparing-images-and-creating-image-diffs/
11 | # Thanks Jeff!
12 | def diff_images(org, fresh)
13 | @images = [
14 | ChunkyPNG::Image.from_file(org),
15 | ChunkyPNG::Image.from_file(fresh)
16 | ]
17 |
18 | @diff_index = []
19 | begin
20 | diff_iterator
21 | rescue ChunkyPNG::OutOfBounds
22 | warn "Skins are different sizes. Please delete #{org} and/or #{fresh}.".color(:yellow)
23 | end
24 | end
25 |
26 | # Run through all of the pixels on both org image, and fresh image. Change the pixel color accordingly.
27 | def diff_iterator
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 | @diff_index << [x,y]
32 | pixel_difference_filter(pixel, x, y)
33 | end
34 | end
35 | end
36 | end
37 |
38 | # Changes the pixel color to be the opposite RGB value
39 | def pixel_difference_filter(pixel, x, y)
40 | chans = []
41 | [:r, :b, :g].each do |chan|
42 | chans << channel_difference(chan, pixel, x, y)
43 | end
44 | @images.last[x,y] = ChunkyPNG::Color.rgb(chans[0], chans[1], chans[2])
45 | end
46 |
47 | # Interface to run the R, G, B methods on ChunkyPNG
48 | def channel_difference(chan, pixel, x, y)
49 | ChunkyPNG::Color.send(chan, pixel) + ChunkyPNG::Color.send(chan, @images.last[x,y]) - 2 * [ChunkyPNG::Color.send(chan, pixel), ChunkyPNG::Color.send(chan, @images.last[x,y])].min
50 | end
51 |
52 | # Returns the numeric results of the diff of 2 images
53 | def percentage_diff(org, fresh)
54 | diff_images(org, fresh)
55 | @total_px = @images.first.pixels.length
56 | @changed_px = @diff_index.length
57 | @percentage_changed = ( (@diff_index.length.to_f / @images.first.pixels.length) * 100 ).round(2)
58 | end
59 |
60 | # Returns the visual results of the diff of 2 images
61 | def visual_diff(org, fresh)
62 | diff_images(org, fresh)
63 | save_visual_diff(org, fresh)
64 | end
65 |
66 | # Saves the visual diff as a separate file
67 | def save_visual_diff(org, fresh)
68 | x, y = @diff_index.map{ |xy| xy[0] }, @diff_index.map{ |xy| xy[1] }
69 | @diffed_image = org.insert(-5, '_diff')
70 |
71 | begin
72 | @images.last.rect(x.min, y.min, x.max, y.max, ChunkyPNG::Color.rgb(0,255,0))
73 | rescue NoMethodError
74 | puts "Both skins are the same.".color(:yellow)
75 | end
76 |
77 | @images.last.save(@diffed_image)
78 | end
79 |
80 | end
81 | end
--------------------------------------------------------------------------------
/lib/green_onion.rb:
--------------------------------------------------------------------------------
1 | require "green_onion/version"
2 | require "green_onion/screenshot"
3 | require "green_onion/compare"
4 | require "green_onion/configuration"
5 | require "green_onion/errors"
6 | require "green_onion/browser"
7 | require "rainbow"
8 |
9 | module GreenOnion
10 | class << self
11 |
12 | attr_reader :compare, :screenshot
13 |
14 | # Pass configure block to set Configuration object
15 | def configure
16 | yield configuration
17 | end
18 |
19 | def configuration
20 | @configuration ||= Configuration.new
21 | end
22 |
23 | # Bring the Screenshot and Compare classes together to create a skin
24 | def skin(url)
25 | @screenshot = Screenshot.new(
26 | :dir => @configuration.skins_dir,
27 | :skin_name => @configuration.skin_name,
28 | :browser => @configuration.browser
29 | )
30 | @compare = Compare.new
31 |
32 | @screenshot.test_screenshot(url)
33 | end
34 |
35 | # Finds the percentage of change between skins
36 | # Threshold can be set in configuration, or as an argument itself, and can be specific to an instance
37 | def skin_percentage(url, threshold=@configuration.threshold)
38 | raise Errors::ThresholdOutOfRange.new "The threshold need to be a number between 1 and 100" if threshold > 100
39 | skin(url)
40 | skin_picker(url, { :percentage => true }, threshold)
41 | end
42 |
43 | # Creates a diffed screenshot between skins
44 | def skin_visual(url)
45 | skin(url)
46 | skin_picker(url, { :visual => true })
47 | end
48 |
49 | # Creates a diffed screenshot between skins AND prints percentage changed
50 | def skin_visual_and_percentage(url, threshold=@configuration.threshold)
51 | raise Errors::ThresholdOutOfRange.new "The threshold need to be a number between 1 and 100" if threshold > 100
52 | skin(url)
53 | skin_picker(url, { :percentage => true, :visual => true }, threshold)
54 | end
55 |
56 | def skin_picker(url, type, threshold=100)
57 | if(@screenshot.paths_hash.length > 1)
58 | puts "\n" + url.color(:cyan)
59 | if type[:percentage]
60 | @compare.percentage_diff(@screenshot.paths_hash[:original], @screenshot.paths_hash[:fresh])
61 | threshold_alert(@compare.percentage_changed, threshold)
62 | end
63 | if type[:visual]
64 | @compare.visual_diff(@screenshot.paths_hash[:original], @screenshot.paths_hash[:fresh])
65 | end
66 | else
67 | puts "\n#{url}".color(:cyan) + " has been saved to #{@screenshot.paths_hash[:original]}".color(:yellow)
68 | end
69 | end
70 |
71 | # This is used in skin_percentage to raise error if a set of skins are ok or not
72 | def threshold_alert(actual, threshold)
73 | if actual > threshold
74 | abort "#{actual - threshold}% above threshold set @ #{threshold}%".color(:red) +
75 | "\npixels changed (%): #{@compare.percentage_changed}%" +
76 | "\npixels changed/total: #{@compare.changed_px}/#{@compare.total_px}"
77 | else
78 | puts "pixels changed/total: #{@compare.changed_px}/#{@compare.total_px}"
79 | end
80 | end
81 |
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/spec/unit/screenshot_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe GreenOnion::Screenshot do
4 |
5 | before(:all) do
6 | @url = 'http://localhost:8070'
7 | @url_w_uri = @url + '/fake_uri'
8 | @tmp_path = './spec/tmp'
9 | @browser = GreenOnion::Browser.new(
10 | :dimensions => { :width => 1024, :height => 768 },
11 | :driver => "webkit"
12 | )
13 | end
14 |
15 | describe 'Snap single screenshot' do
16 |
17 | before(:each) do
18 | @screenshot = GreenOnion::Screenshot.new(
19 | :browser => @browser,
20 | :dir => @tmp_path,
21 | :skin_name => {
22 | :match => /[\/]/,
23 | :replace => "",
24 | :prefix => nil,
25 | :root => "root"
26 | }
27 | )
28 | @file = "#{@tmp_path}/fake_uri.png"
29 | end
30 |
31 | after(:each) do
32 | FileUtils.rm_r(@tmp_path, :force => true)
33 | end
34 |
35 | it 'should build the path from the URI' do
36 | @screenshot.url_to_path(@url_w_uri).should eq(@file)
37 | end
38 |
39 | it 'should build the path from root' do
40 | @screenshot.url_to_path('http://localhost:8070').should eq("#{@tmp_path}/root.png")
41 | end
42 |
43 | it 'should build the path from root (even with trailing slash)' do
44 | @screenshot.url_to_path('http://localhost:8070/').should eq("#{@tmp_path}/root.png")
45 | end
46 |
47 | it 'should snap and save screenshot' do
48 | @screenshot.browser.snap_screenshot(@url_w_uri, @file)
49 | File.exist?(@file).should be_true
50 | end
51 |
52 | it "should destroy a singular screenshot" do
53 | @screenshot.destroy(@url_w_uri)
54 | File.exist?(@file).should be_false
55 | end
56 | end
57 |
58 | describe 'Snap two screenshots' do
59 |
60 | before(:each) do
61 | @screenshot = GreenOnion::Screenshot.new(
62 | :browser => @browser,
63 | :dir => @tmp_path,
64 | :skin_name => {
65 | :match => /[\/]/,
66 | :replace => "",
67 | :prefix => nil,
68 | :root => "root"
69 | }
70 | )
71 | @file1 = "#{@tmp_path}/fake_uri.png"
72 | @file2 = "#{@tmp_path}/fake_uri_fresh.png"
73 | 2.times do
74 | @screenshot.test_screenshot(@url_w_uri)
75 | end
76 | end
77 |
78 | after(:each) do
79 | FileUtils.rm_r(@tmp_path, :force => true)
80 | end
81 |
82 | it "should create the paths_hash correctly" do
83 | ( (@screenshot.paths_hash[:original].should eq(@file1)) && (@screenshot.paths_hash[:fresh].should eq(@file2)) ).should be_true
84 | end
85 |
86 | it "should snap and save another screenshot if a screenshot already exists" do
87 | if File.exist?(@file1)
88 | File.exist?(@file2).should be_true
89 | end
90 | end
91 |
92 | it "should destroy a set of screenshots" do
93 | @screenshot.destroy(@url_w_uri)
94 | ( File.exist?(@file1) && File.exist?(@file2) ).should be_false
95 | end
96 | end
97 |
98 | describe "Custom filenaming" do
99 |
100 | after(:each) do
101 | FileUtils.rm_r(@tmp_path, :force => true)
102 | end
103 |
104 | it "should allow users to create a naming convention" do
105 | @screenshot = GreenOnion::Screenshot.new(
106 | :browser => @browser,
107 | :dir => @tmp_path,
108 | :skin_name => {
109 | :match => /[\/]/,
110 | :replace => "#",
111 | :prefix => nil,
112 | :root => "root"
113 | }
114 | )
115 | @screenshot.get_path("#{@url}/another/uri/string")
116 | @screenshot.paths_hash[:original].should eq("#{@tmp_path}/another#uri#string.png")
117 | end
118 |
119 | it "should allow filenames to have a timestamp" do
120 | this_month = Time.now.strftime("%m_%Y_")
121 | @screenshot = GreenOnion::Screenshot.new(
122 | :browser => @browser,
123 | :dir => @tmp_path,
124 | :skin_name => {
125 | :match => /[\/]/,
126 | :replace => "-",
127 | :prefix => this_month,
128 | :root => "root"
129 | }
130 | )
131 | @screenshot.get_path("#{@url}/another/uri/string")
132 | @screenshot.paths_hash[:original].should eq("#{@tmp_path}/#{this_month}another-uri-string.png")
133 | end
134 |
135 | it "should allow renaming for root skins" do
136 | @screenshot = GreenOnion::Screenshot.new(
137 | :browser => @browser,
138 | :dir => @tmp_path,
139 | :skin_name => {
140 | :match => /[\/]/,
141 | :replace => "-",
142 | :prefix => nil,
143 | :root => "first"
144 | }
145 | )
146 | @screenshot.get_path(@url)
147 | @screenshot.paths_hash[:original].should eq("#{@tmp_path}/first.png")
148 | end
149 | end
150 |
151 | end
--------------------------------------------------------------------------------
/spec/unit/green_onion_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe GreenOnion do
4 |
5 | before(:all) do
6 | @tmp_path = './spec/tmp'
7 | @url = 'http://localhost:8070'
8 | @url_w_uri = @url + '/fake_uri'
9 | end
10 |
11 | describe "Skins" do
12 | before(:each) do
13 | GreenOnion.configure do |c|
14 | c.skins_dir = @tmp_path
15 | end
16 | end
17 |
18 | after(:each) do
19 | FileUtils.rm_r(@tmp_path, :force => true)
20 | end
21 |
22 | it "should default to 1024x768 browser dimensions" do
23 | ( (GreenOnion.configuration.dimensions[:height] == 768) &&
24 | (GreenOnion.configuration.dimensions[:width] == 1024) ).should be_true
25 | end
26 |
27 | it "should set/get custom directory" do
28 | GreenOnion.configuration.skins_dir.should eq(@tmp_path)
29 | end
30 |
31 | it "should get the correct paths_hash" do
32 | 2.times do
33 | GreenOnion.skin(@url)
34 | end
35 | ( (GreenOnion.screenshot.paths_hash[:original] == "#{@tmp_path}/root.png") &&
36 | (GreenOnion.screenshot.paths_hash[:fresh] == "#{@tmp_path}/root_fresh.png") ).should be_true
37 | end
38 |
39 | it "should measure the percentage of diff between skins" do
40 | 2.times do
41 | GreenOnion.skin_percentage(@url)
42 | end
43 | GreenOnion.compare.percentage_changed.should be > 0
44 | end
45 |
46 | it "should measure the percentage of diff between skins (even if there is no diff)" do
47 | 2.times do
48 | GreenOnion.skin_percentage(@url_w_uri)
49 | end
50 | GreenOnion.compare.percentage_changed.should be == 0
51 | end
52 |
53 | it "should print just URL and changed/total when diff percentage threshold has not been surpassed" do
54 | $stdout.should_receive(:puts).exactly(3).times
55 | 2.times do
56 | GreenOnion.skin_percentage(@url, 6)
57 | end
58 | end
59 |
60 | it "should create visual diff between skins" do
61 | 2.times do
62 | GreenOnion.skin_visual(@url)
63 | end
64 | GreenOnion.compare.diffed_image.should eq("#{@tmp_path}/root_diff.png")
65 | end
66 |
67 | it "should create visual diff between skins (even when there is no change)" do
68 | 2.times do
69 | GreenOnion.skin_visual(@url_w_uri)
70 | end
71 | GreenOnion.compare.diffed_image.should eq("#{@tmp_path}/fake_uri_diff.png")
72 | end
73 |
74 | it "should measure the percentage of diff between skins AND create visual diff" do
75 | 2.times do
76 | GreenOnion.skin_visual_and_percentage(@url)
77 | end
78 | ( (GreenOnion.compare.diffed_image.should eq("#{@tmp_path}/root_diff.png")) &&
79 | (GreenOnion.compare.percentage_changed.should be > 0) ).should be_true
80 | end
81 | end
82 |
83 | describe "Skins with custom dimensions" do
84 | before(:each) do
85 | GreenOnion.configure do |c|
86 | c.skins_dir = @tmp_path
87 | c.dimensions = { :width => 1440, :height => 900 }
88 | end
89 | end
90 |
91 | after(:each) do
92 | FileUtils.rm_r(@tmp_path, :force => true)
93 | end
94 |
95 | it "should allow custom browser dimensions" do
96 | ( (GreenOnion.configuration.dimensions[:height] == 900) &&
97 | (GreenOnion.configuration.dimensions[:width] == 1440) ).should be_true
98 | end
99 | end
100 |
101 | describe "Skins with custom threshold" do
102 | before(:each) do
103 | GreenOnion.configure do |c|
104 | c.skins_dir = @tmp_path
105 | c.threshold = 1
106 | end
107 | end
108 |
109 | after(:each) do
110 | FileUtils.rm_r(@tmp_path, :force => true)
111 | end
112 |
113 | it "should alert when diff percentage threshold is surpassed" do
114 | GreenOnion.should_receive(:abort)
115 | 2.times do
116 | GreenOnion.skin_percentage(@url)
117 | end
118 | end
119 | end
120 |
121 | describe "Skins with custom file namespace" do
122 |
123 | after(:each) do
124 | FileUtils.rm_r(@tmp_path, :force => true)
125 | end
126 |
127 | it "should allow custom file namespacing" do
128 | GreenOnion.configure do |c|
129 | c.skins_dir = @tmp_path
130 | c.skin_name = {
131 | :match => /[\/a-z]/,
132 | :replace => "-",
133 | :prefix => "start",
134 | :root => "first"
135 | }
136 | end
137 | ( (GreenOnion.configuration.skin_name[:match] == /[\/a-z]/) &&
138 | (GreenOnion.configuration.skin_name[:replace] == "-") &&
139 | (GreenOnion.configuration.skin_name[:prefix] == "start") &&
140 | (GreenOnion.configuration.skin_name[:root] == "first") ).should be_true
141 | end
142 |
143 | it "should allow incomplete setting of skin_name hash" do
144 | GreenOnion.configure do |c|
145 | c.skins_dir = @tmp_path
146 | c.skin_name = {
147 | :replace => "o"
148 | }
149 | end
150 | ( (GreenOnion.configuration.skin_name[:match] == /[\/]/) &&
151 | (GreenOnion.configuration.skin_name[:replace] == "o") &&
152 | (GreenOnion.configuration.skin_name[:prefix] == nil) &&
153 | (GreenOnion.configuration.skin_name[:root] == "root") ).should be_true
154 | end
155 | end
156 |
157 | describe "Errors" do
158 | before(:each) do
159 | GreenOnion.configure do |c|
160 | c.skins_dir = @tmp_path
161 | end
162 | end
163 |
164 | after(:each) do
165 | FileUtils.rm_r(@tmp_path, :force => true)
166 | end
167 |
168 | it "should raise error for when ill-formatted URL is used" do
169 | expect { GreenOnion.skin_percentage("localhost") }.to raise_error(GreenOnion::Errors::IllformattedURL)
170 | end
171 |
172 | it "should raise error for when threshold is out of range for skin_percentage" do
173 | expect { GreenOnion.skin_percentage(@url, 101) }.to raise_error(GreenOnion::Errors::ThresholdOutOfRange)
174 | end
175 |
176 | it "should raise error for when threshold is out of range for skin_visual_and_percentage" do
177 | expect { GreenOnion.skin_visual_and_percentage(@url, 101) }.to raise_error(GreenOnion::Errors::ThresholdOutOfRange)
178 | end
179 |
180 | it "should raise error for when unknown driver is assigned" do
181 | GreenOnion.configure do |c|
182 | c.skins_dir = @tmp_path
183 | c.driver = :foo
184 | end
185 | expect { GreenOnion.skin_percentage(@url) }.to raise_error(ArgumentError)
186 | end
187 | end
188 |
189 |
190 | describe "Skins with custom driver" do
191 | before(:each) do
192 | GreenOnion.configure do |c|
193 | c.skins_dir = @tmp_path
194 | c.driver = "selenium"
195 | end
196 | end
197 |
198 | after(:each) do
199 | FileUtils.rm_r(@tmp_path, :force => true)
200 | end
201 |
202 | it "should allow custom browser driver" do
203 | GreenOnion.configuration.browser.driver.should eq("selenium")
204 | end
205 | end
206 | end
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [
](http://travis-ci.org/#!/intridea/green_onion) [](https://codeclimate.com/github/intridea/green_onion)
3 |
4 | # GreenOnion
5 |
6 | Regression issues in the view make you cry.
7 |
8 | GreenOnion is a testing library for the UI only. It alerts you when the appearance of a view has changed, let's you know the percentage of total change, and allows you to visualize the areas that have been changed. It fits right into your test suite, and is dependent on familiar tools like Capybara.
9 |
10 | ## Documentation
11 |
12 | [RDoc](http://rdoc.info/gems/green_onion/frames)
13 |
14 | ## Installation
15 |
16 | If you want to use [capybara-webkit](https://github.com/thoughtbot/capybara-webkit), you'll need to get Qt built in your testing environment. [Follow these steps](https://github.com/thoughtbot/capybara-webkit/wiki/Installing-Qt-and-compiling-capybara-webkit) and `gem install capybara-webkit` to get it up and running. Overwise, you can just use `'selenium'` (or another driver) in the configuration block.
17 |
18 | Add this line to your application's Gemfile:
19 |
20 | gem 'green_onion'
21 |
22 | And then execute:
23 |
24 | bundle
25 |
26 | Or install it yourself as:
27 |
28 | gem install green_onion
29 |
30 | ## Usage
31 |
32 | ### Command Line Interface
33 |
34 | #### Skinning
35 |
36 | To just run a comparison between skins in your shell, you can use the command below:
37 |
38 | green_onion skin [options]
39 |
40 | Options
41 | * `` - is the screen you want tested. Must include http://, example - 'http://yourSite.com'
42 | * `--dir=DIR` - 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. The default directory will be './spec/skins'
43 | * `--method=[p, v, vp]` - the method in which you'd like to compare the skins. `p` is for percentage, `v` is for visual. The default is visual and percentage.
44 | * `--threshold=[1-100]` is the percentage of acceptable change that the screenshots can take. This number can always be overwritten for an instance.
45 | * `--width=[number]` is the width of the browser window. The default width is 1024. (only when using capybara-webkit)
46 | * `--height=[number]` is the height of the browser window. The default height is 768. (only when using capybara-webkit)
47 | * `--browser=DRIVER` is the browser driver for Capybara. It is `webkit` by default, but you can also pass in `selenium` or `poltergeist` (for PhantomJS).
48 |
49 | #### Generating skinner file
50 |
51 | To generate a "skinner" file, which will test a Rails application with the routes without params included (this is an area that could be worked on a lot more :) ); use the command below:
52 |
53 | green_onion generate [options]
54 |
55 | * `--url=URL` - the domain that you will be testing your Rails app. The default is "http://localhost:3000".
56 | * `--dir=DIR` - the directory in which you would like to generate the skinner. The default is "spec/skinner.rb"
57 |
58 | ### Adding GreenOnion to integration tests with RSpec
59 |
60 | For adding GreenOnion to your integration tests in RSpec, add `require 'green_onion'` to your spec_helper.rb file. Place this block in the file also:
61 |
62 | GreenOnion.configure do |c|
63 | c.skins_dir = 'your/path/to/skins'
64 | c.skin_name = {
65 | :match => /[\/]/,
66 | :replace => '_',
67 | :prefix => nil,
68 | :root => 'root'
69 | }
70 | c.driver = 'webkit'
71 | c.dimensions = {
72 | :width => 1440,
73 | :height => 768
74 | }
75 | c.threshold = 20
76 | end
77 |
78 | * `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. The default directory will be './spec/skins'
79 | * `skin_name` is a hash that defines the skin namespace. The options include:
80 | * `:match` - a regex pattern that will replace characters from the URI. The default pattern will match to all "/" in a URI.
81 | * `:replace` - the string that replaces what is matched. These options are just abstractions of String.gsub in GreenOnion::Screenshot.
82 | * `:prefix` - a value that will be concatenated to the front of the filename. A good example would be if you wanted to add a timestamp: `:prefix => Time.now.strftime("%m_%Y_")`.
83 | * `:root` - the string that will be used to name the root of a domain.
84 | * `driver` is a string for the browser driver to use. The default is `'webkit'`. You could also pass in `'selenium'` or `'poltergeist'` (for PhantomJS) instead.
85 | * `dimensions` is a hash with the height and width of the browser window. The default dimensions are 1024x768.
86 | * `threshold` is the percentage of acceptable change that the screenshots can take. This number can always be overwritten for an instance.
87 |
88 | Then use one of the three methods below in a test...
89 |
90 | #### Percentage of change
91 |
92 | GreenOnion.skin_percentage(url, threshold [optional])
93 | 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.
94 |
95 | * `url` is the screen you want tested. Must include http://, example - 'http://yourSite.com'
96 | * `threshold` can be overwritten here, or if not given in the configure block – it will default to a threshold of 100%
97 |
98 | #### Viewing screenshot diffs
99 |
100 | GreenOnion.skin_visual(url)
101 | 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.
102 |
103 | * `url` is the screen you want tested. Must include http://, example - 'http://yourSite.com'
104 |
105 | #### Both viewing screenshot diffs and percentage of change
106 |
107 | GreenOnion.skin_visual_and_percentage(url, threshold {optional})
108 | This is just a combination of the two methods above.
109 |
110 | ## Contributing
111 |
112 | ### Testing
113 |
114 | The best way to run the specs is with...
115 |
116 | bundle exec rake spec
117 |
118 | ...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.
119 |
120 | ## Roadmap
121 |
122 | * Screenshots can either be viewed as a visual diff, or overlayed newest over oldest and viewed as an onion-skin with sliding transparency.
123 | * Allow for flexibility in picking browsers
124 | * Skinner generator needs love <3
125 | * Should allow for testing using fixtures/factories
126 | * More robust tests, especially around the visual diffs themselves
127 | * More documentation
128 | * More configuration/customizable settings
129 |
130 | ## THANK YOU
131 |
132 | Much of this work could not be completed without these people and projects
133 |
134 | ### [Jeff Kreeftmeijer](http://jeffkreeftmeijer.com)
135 | This is the post that got the wheels in motion: http://jeffkreeftmeijer.com/2011/comparing-images-and-creating-image-diffs/. Most of the GreenOnion::Compare class is based on this work alone. Great job Jeff!
136 |
137 | ### [Compatriot](https://github.com/carols10cents/compatriot)
138 | Carol Nichols saw the same post, and worked on an excellent gem for cross-browser testing. That gem greatly influenced design decisions with GreenOnion.
139 |
140 | ### [VCR](https://github.com/myronmarston/vcr)
141 | Many patterns and ideas also came from VCR, because of its flexibility in allowing users to pick what gems to work with.
142 |
143 | ### [Capybara](https://github.com/jnicklas/capybara), [ChunkyPNG](https://github.com/wvanbergen/chunky_png), [Thor](https://github.com/wycats/thor), and [OilyPNG](https://github.com/wvanbergen/oily_png)
144 | The land on which we sow our bulbs.
145 |
146 | ## Contributor
147 | [Ted O'Meara](http://www.intridea.com/about/team/ted-o-meara)
148 |
149 | ## License
150 | MIT License. See LICENSE for details.
151 |
152 | ## Copyright
153 | Copyright (c) 2012 Intridea, Inc.
--------------------------------------------------------------------------------