├── Rakefile ├── lib └── rspec │ ├── visual │ ├── version.rb │ ├── helpers │ │ └── screenshot_helper.rb │ ├── configuration.rb │ └── matchers │ │ └── look_like_matcher.rb │ └── visual.rb ├── Gemfile ├── .gitignore ├── LICENSE.txt ├── rspec-visual.gemspec └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /lib/rspec/visual/version.rb: -------------------------------------------------------------------------------- 1 | module Rspec 2 | module Visual 3 | VERSION = "0.0.2" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rspec-visual.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | -------------------------------------------------------------------------------- /lib/rspec/visual.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/visual/version' 2 | require 'rspec/visual/configuration' 3 | require 'rspec/visual/helpers/screenshot_helper' 4 | require 'rspec/visual/matchers/look_like_matcher' 5 | -------------------------------------------------------------------------------- /lib/rspec/visual/helpers/screenshot_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/visual/configuration' 2 | module ScreenshotHelper 3 | def take_screenshot(page, example) 4 | file_path = File.join(Rspec::Visual::Configuration.screenshot_folder, "#{example.description}.png") 5 | page.save_screenshot(file_path, full: true) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/rspec/visual/configuration.rb: -------------------------------------------------------------------------------- 1 | require 'singleton' 2 | require 'capybara/rspec' 3 | require 'capybara/poltergeist' 4 | 5 | module Rspec 6 | module Visual 7 | class Configuration 8 | include Singleton 9 | 10 | class << self 11 | attr_accessor :screenshot_folder, :stable_screenshot_folder 12 | 13 | def configure 14 | yield self 15 | end 16 | 17 | def configure_capybara 18 | Capybara.register_driver :poltergeist do |app| 19 | Capybara::Poltergeist::Driver.new(app, js_errors: false) 20 | end 21 | 22 | RSpec.configure do |config| 23 | config.before(:example, :visual => :true) do 24 | config.include ScreenshotHelper 25 | Capybara.current_driver = :poltergeist 26 | end 27 | 28 | config.after(:example, :visual => :true) do 29 | Capybara.use_default_driver 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Simon Bagreev 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. 23 | -------------------------------------------------------------------------------- /lib/rspec/visual/matchers/look_like_matcher.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'rspec/expectations' 3 | require 'fileutils' 4 | 5 | RSpec::Matchers.define :look_like do |expected| 6 | expected_image_file_path = File.join(Rspec::Visual::Configuration.stable_screenshot_folder, "#{expected}.png") 7 | actual_image_file_path = File.join(Rspec::Visual::Configuration.screenshot_folder, "#{expected}.png") 8 | 9 | match do |actual| 10 | unless File.exists?(expected_image_file_path) 11 | FileUtils.cp(actual_image_file_path, expected_image_file_path) and return true 12 | end 13 | 14 | diff_file_path = File.join(Rspec::Visual::Configuration.screenshot_folder, "#{expected}_diff.png") 15 | system "compare -metric PAE -subimage-search -dissimilarity-threshold 1 \ 16 | #{expected_image_file_path} #{actual_image_file_path} #{diff_file_path}" 17 | end 18 | 19 | failure_message do |actual| 20 | "expected #{actual_image_file_path} to look like #{expected_image_file_path}\n"\ 21 | "If you intended #{expected} to look diffrently, please copy over "\ 22 | "#{actual_image_file_path} into #{Rspec::Visual::Configuration.stable_screenshot_folder}" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /rspec-visual.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'rspec/visual/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "rspec-visual" 8 | spec.version = Rspec::Visual::VERSION 9 | spec.authors = ["Simon Bagreev"] 10 | spec.email = ["sbagreev@gmail.com"] 11 | spec.summary = %Q{ Visual testing with rspec via screenshot comparison. } 12 | spec.description = %Q{ Adds 'visual' specs and matchers that take screenshots 13 | of the app and compare them to find regressions in 14 | frontend. Must have imagemagick installed! Depends on 15 | rspec, capybara, poltergeist } 16 | spec.homepage = "" 17 | spec.license = "MIT" 18 | 19 | spec.files = `git ls-files -z`.split("\x0") 20 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 21 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 22 | spec.require_paths = ["lib"] 23 | 24 | spec.add_dependency "rspec", "~> 3.0" 25 | spec.add_dependency "capybara", "~> 2.4" 26 | spec.add_dependency "poltergeist", "~> 1.6" 27 | spec.add_development_dependency "bundler", "~> 1.7" 28 | spec.add_development_dependency "rake", "~> 10.0" 29 | end 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rspec::Visual 2 | 3 | [![Gem Version](https://badge.fury.io/rb/rspec-visual.svg)](https://badge.fury.io/rb/rspec-visual) 4 | 5 | 6 | RSpec plugin for writing "visual" tests: tests that look for changes in the 7 | applications's look by comparing screenshots on current branch to the latest 8 | 'stable' screenshots 9 | 10 | ## Installation and Usage 11 | 12 | ### Step 1 - Installation 13 | 14 | Add this line to your application's Gemfile: 15 | 16 | ```ruby 17 | gem 'rspec-visual' 18 | ``` 19 | 20 | And then execute: 21 | 22 | $ bundle 23 | 24 | Or install it yourself as: 25 | 26 | $ gem install rspec-visual 27 | 28 | ### Step 2 - Configuration 29 | 30 | Configure `rspec-visual` with the folder for _"stable"_ screenshots (they will 31 | be persisted in version control), folder for temporary screenshots, and run `capybara` setup: 32 | 33 | ```ruby 34 | # spec/support/rspec-visual.rb 35 | Rspec::Visual::Configuration.configure do |config| 36 | config.screenshot_folder = Rails.root.join('tmp') 37 | config.stable_screenshot_folder = Rails.root.join('docs/visual') 38 | config.configure_capybara 39 | end 40 | ``` 41 | 42 | ### Step 3 - Writing tests 43 | 44 | Write some visual tests (**make sure to tag them with `visual: true`**) 45 | using following steps: 46 | 47 | - in your test, visit the page that you want to check 48 | - take the screenshot using `take_screenshot` helper and passing it page and example 49 | - use `look_like` matcher for assertion 50 | 51 | ```ruby 52 | # spec/features/visual/home_page_spec.rb 53 | require 'rails_helper' 54 | 55 | describe 'home page', type: :feature, visual: true do 56 | it 'home_page' do |example| 57 | visit '/' 58 | take_screenshot(page, example) 59 | should look_like example.description 60 | end 61 | end 62 | ``` 63 | 64 | ### Step 4 - Checking the result 65 | 66 | #### First run 67 | 68 | No failures will occur. Screenshots will be saved in the "stable" folder that you 69 | defined in "Configure" step. 70 | 71 | #### Consequent runs 72 | 73 | Screenshots from all "visual" tests will be matched against ones in "stable" folder. 74 | 75 | In case there is a difference, like so: 76 | 77 | #### "Stable" screenshot 78 | ![Original](/../screenshots-do-not-delete/sample1.jpg?raw=true "Original") 79 | 80 | #### Screenshot with changes (banner missing) 81 | ![Actual](/../screenshots-do-not-delete/sample2.jpg?raw=true "Actual") 82 | 83 | #### Diff file (diff area is highlighted in red) 84 | ![Diff](/../screenshots-do-not-delete/sample_diff.jpg?raw=true "Diff") 85 | 86 | The test will fail and difference file will be generated in 87 | `Rspec::Visual::Configuration.screenshot_folder`: 88 | 89 | Also, failure message will say: 90 | 91 | ```shell 92 | Failures: 93 | 94 | 1) home page home_page 95 | Failure/Error: should look_like example.description 96 | expected /path/to/tmp/home_page.png to look like /path/to/docs/visual/home_page.png 97 | If you intended home_page to look diffrently, please copy over /path/to/tmp/home_page.png into /path/to/docs/visual 98 | # ./spec/features/visual/home_page_visual_spec.rb:10:in `block (2 levels) in ' 99 | ``` 100 | _**Please read the failure message carefully, and if you indeed wanted the page to change 101 | than manually copy now-correct screenshot to the "stable" folder in order for specs to pass**_ 102 | 103 | ## GOTCHAS 104 | 105 | 1. depends on `rspec, capybara, poltergeist, phantomJS` and _**imagemagick**_ 106 | 2. May conflict with `VCR`, to overcome use: 107 | ```ruby 108 | VCR.configure do |c| 109 | c.ignore_localhost = true 110 | end 111 | ``` 112 | 3. If you want to check the app that is NOT _localhost_, use Capybara config like so: 113 | 114 | ```ruby 115 | RSpec.configure do |config| 116 | default = Capybara.server_port 117 | config.before(:example, :visual => :true) do 118 | Capybara.run_server = false 119 | Capybara.server_port = 80 120 | Capybara.app_host = 'http://stage.myapp.com' 121 | end 122 | 123 | config.after(:example, :visual => :true) do 124 | Capybara.run_server = true 125 | Capybara.server_port = default 126 | end 127 | end 128 | ``` 129 | 130 | ## Contributing 131 | 132 | ### TODO 133 | 134 | - add ability to take screenshots in different browsers 135 | - add ability to take screenshots in different viewports 136 | - add ability to configure 'similarity' percentage 137 | - add ability to test certain areas of the page 138 | - add test coverage 139 | - expand beyond `poltergeist`, try `selenium-webkit` 140 | 141 | 1. Fork it ( https://github.com/[my-github-username]/rspec-visual/fork ) 142 | 2. Create your feature branch (`git checkout -b my-new-feature`) 143 | 3. Commit your changes (`git commit -am 'Add some feature'`) 144 | 4. Push to the branch (`git push origin my-new-feature`) 145 | 5. Create a new Pull Request 146 | 147 | ## License 148 | 149 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 150 | --------------------------------------------------------------------------------