├── .rspec
├── .gitignore
├── lib
├── capybara-screenshot
│ ├── version.rb
│ ├── rspec
│ │ ├── textmate_link_reporter.rb
│ │ ├── base_reporter.rb
│ │ ├── html_embed_reporter.rb
│ │ ├── text_reporter.rb
│ │ └── html_link_reporter.rb
│ ├── helpers.rb
│ ├── minitest.rb
│ ├── capybara.rb
│ ├── spinach.rb
│ ├── cucumber.rb
│ ├── testunit.rb
│ ├── pruner.rb
│ ├── saver.rb
│ └── rspec.rb
└── capybara-screenshot.rb
├── Gemfile
├── gemfiles
├── latest.gemfile
├── rspec.3.0.gemfile
├── cucumber.1.2.gemfile
├── rspec.2.14.gemfile
├── rspec.2.99.gemfile
├── spinach.0.7.gemfile
├── cucumber.1.3.0.gemfile
└── spinach.0.8.0.gemfile
├── spec
├── support
│ ├── test_app.rb
│ ├── html_reporter_context.rb
│ └── common_setup.rb
├── cucumber
│ ├── support
│ │ └── env.rb
│ ├── step_definitions
│ │ └── step_definitions.rb
│ └── cucumber_spec.rb
├── unit
│ ├── rspec_reporters
│ │ ├── html_embed_reporter_spec.rb
│ │ ├── html_link_reporter_spec.rb
│ │ ├── textmate_link_reporter_spec.rb
│ │ └── text_reporter_spec.rb
│ ├── base_reporter_spec.rb
│ ├── capybara_spec.rb
│ ├── capybara-screenshot_rspec_spec.rb
│ ├── capybara-screenshot_spec.rb
│ ├── pruner_spec.rb
│ └── saver_spec.rb
├── spec_helper.rb
├── spinach
│ ├── support
│ │ └── spinach_failure.rb
│ └── spinach_spec.rb
├── feature
│ ├── testunit_spec.rb
│ └── minitest_spec.rb
└── rspec
│ └── rspec_spec.rb
├── .travis.yml
├── Appraisals
├── Rakefile
├── LICENSE
├── capybara-screenshot.gemspec
├── CHANGELOG.md
└── README.md
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format documentation
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | .bundle
3 | Gemfile.lock
4 | gemfiles/*.lock
5 | pkg/*
6 | .rvmrc
7 | bin
8 |
--------------------------------------------------------------------------------
/lib/capybara-screenshot/version.rb:
--------------------------------------------------------------------------------
1 | module Capybara
2 | module Screenshot
3 | VERSION = '1.0.7'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Specify your gem's dependencies in capybara-screenshot.gemspec
4 | gemspec
5 |
6 | gem 'rake'
7 | gem 'appraisal'
8 |
--------------------------------------------------------------------------------
/gemfiles/latest.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rake"
6 | gem "appraisal"
7 |
8 | gemspec :path => "../"
9 |
--------------------------------------------------------------------------------
/gemfiles/rspec.3.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rake"
6 | gem "appraisal"
7 | gem "rspec", "3.0"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/cucumber.1.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rake"
6 | gem "appraisal"
7 | gem "cucumber", "1.2"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/rspec.2.14.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rake"
6 | gem "appraisal"
7 | gem "rspec", "2.14"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/rspec.2.99.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rake"
6 | gem "appraisal"
7 | gem "rspec", "2.99"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/spinach.0.7.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rake"
6 | gem "appraisal"
7 | gem "spinach", "0.7"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/cucumber.1.3.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rake"
6 | gem "appraisal"
7 | gem "cucumber", "1.3.0"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/spinach.0.8.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rake"
6 | gem "appraisal"
7 | gem "spinach", "0.8.0"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/spec/support/test_app.rb:
--------------------------------------------------------------------------------
1 | require 'sinatra/base'
2 |
3 | Sinatra::Application.root = '.'
4 |
5 | class TestApp < Sinatra::Base
6 | get '/' do
7 | 'This is the root page'
8 | end
9 |
10 | get '/different_page' do
11 | 'This is a different page'
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | script: "rake travis:ci"
3 | rvm:
4 | - 1.9.3
5 | - 2.0.0
6 | - 2.1.2
7 | - 2.2.1
8 | - jruby-19mode
9 | - jruby-1.7.13
10 | gemfile:
11 | - gemfiles/cucumber.1.2.gemfile
12 | - gemfiles/cucumber.1.3.0.gemfile
13 | - gemfiles/latest.gemfile
14 | - gemfiles/rspec.2.14.gemfile
15 | - gemfiles/rspec.2.99.gemfile
16 | - gemfiles/rspec.3.0.gemfile
17 | - gemfiles/spinach.0.7.gemfile
18 | - gemfiles/spinach.0.8.0.gemfile
19 |
--------------------------------------------------------------------------------
/spec/cucumber/support/env.rb:
--------------------------------------------------------------------------------
1 | require 'capybara/cucumber'
2 | require 'capybara-screenshot'
3 | require 'capybara-screenshot/cucumber'
4 | require 'aruba/cucumber'
5 | require 'aruba/jruby'
6 |
7 | Capybara::Screenshot.register_filename_prefix_formatter(:cucumber) do |fault|
8 | 'my_screenshot'
9 | end
10 |
11 | Before do
12 | @aruba_timeout_seconds = 60
13 | end if RUBY_PLATFORM == 'java'
14 |
15 | After('@restore-capybara-default-session') do
16 | Capybara.session_name = nil
17 | end
18 |
--------------------------------------------------------------------------------
/spec/cucumber/step_definitions/step_definitions.rb:
--------------------------------------------------------------------------------
1 | When(/^I click on a missing link$/) do
2 | click_on "you'll never find me"
3 | end
4 |
5 | When(/^I click on a missing link on a different page in a different session$/) do
6 | using_session :different_session do
7 | visit '/different_page'
8 | click_on "you'll never find me"
9 | end
10 | end
11 |
12 | When(/^I visit "([^"]*)"$/) do |path|
13 | visit path
14 | end
15 |
16 | Then(/^I trigger an unhandled exception/) do
17 | raise "you can't handle me"
18 | end
19 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | appraise "rspec.2.14" do
2 | gem "rspec", "2.14"
3 | end
4 |
5 | appraise "rspec.2.99" do
6 | gem "rspec", "2.99"
7 | end
8 |
9 | appraise "rspec.3.0" do
10 | gem "rspec", "3.0"
11 | end
12 |
13 | appraise "cucumber.1.2" do
14 | gem "cucumber", "1.2"
15 | end
16 |
17 | appraise "cucumber.1.3.0" do
18 | gem "cucumber", "1.3.0"
19 | end
20 |
21 | appraise "spinach.0.7" do
22 | gem "spinach", "0.7"
23 | end
24 |
25 | appraise "spinach.0.8.0" do
26 | gem "spinach", "0.8.0"
27 | end
28 |
29 | appraise "latest" do
30 | # will get latest versions of all gems
31 | end
32 |
--------------------------------------------------------------------------------
/lib/capybara-screenshot/rspec/textmate_link_reporter.rb:
--------------------------------------------------------------------------------
1 | require 'capybara-screenshot/rspec/base_reporter'
2 | require 'capybara-screenshot/rspec/html_link_reporter'
3 | require 'shellwords'
4 |
5 | module Capybara
6 | module Screenshot
7 | module RSpec
8 | module TextMateLinkReporter
9 | extend BaseReporter
10 | include HtmlLinkReporter
11 | enhance_with_screenshot :extra_failure_content
12 |
13 | def attributes_for_screenshot_link(url)
14 | super.merge("onclick" => "TextMate.system('open #{Shellwords.escape(url)}'); return false;")
15 | end
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/capybara-screenshot/helpers.rb:
--------------------------------------------------------------------------------
1 | class CapybaraScreenshot
2 | module Helpers
3 | extend self
4 |
5 | COLORS =
6 | {
7 | "black" => 0,
8 | "red" => 1,
9 | "green" => 2,
10 | "yellow" => 3,
11 | "blue" => 4,
12 | "purple" => 5,
13 | "magenta" => 5,
14 | "cyan" => 6,
15 | "white" => 7
16 | }
17 |
18 | COLORS.each_pair do |color, value|
19 | define_method color do |text|
20 | "\033[0;#{30+value}m#{text}\033[0m"
21 | end
22 |
23 | define_method "bright_#{color}" do |text|
24 | "\033[1;#{30+value}m#{text}\033[0m"
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/capybara-screenshot/rspec/base_reporter.rb:
--------------------------------------------------------------------------------
1 | module Capybara
2 | module Screenshot
3 | module RSpec
4 | module BaseReporter
5 |
6 | # Automatically set up method aliases (very much like ActiveSupport's `alias_method_chain`)
7 | # when the module gets included.
8 | def enhance_with_screenshot(method)
9 | with_method, without_method = "#{method}_with_screenshot", "#{method}_without_screenshot"
10 | define_singleton_method :included do |mod|
11 | if mod.method_defined?(method) || mod.private_method_defined?(method)
12 | mod.send :alias_method, without_method, method
13 | mod.send :alias_method, method, with_method
14 | end
15 | end
16 | end
17 |
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/spec/unit/rspec_reporters/html_embed_reporter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Capybara::Screenshot::RSpec::HtmlEmbedReporter do
4 | include_context 'html reporter'
5 |
6 | context 'when an image was saved' do
7 | before do
8 | set_example double("example", metadata: {screenshot: {image: "path/to/image"}})
9 | end
10 |
11 | it 'embeds the image base64 encoded into the content' do
12 | expect(File).to receive(:binread).with("path/to/image").and_return("image data")
13 | encoded_image_data = Base64.encode64('image data')
14 | content_without_styles = @reporter.extra_failure_content(nil).gsub(/ ?style='.*?' ?/, "")
15 | expect(content_without_styles).to eql("original content")
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/unit/base_reporter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Capybara::Screenshot::RSpec::BaseReporter do
4 | describe '#enhance_with_screenshot' do
5 | it 'makes the original method available under an alias and replaces it with the enhanced method' do
6 | reporter_module = Module.new do
7 | extend Capybara::Screenshot::RSpec::BaseReporter
8 | enhance_with_screenshot :foo
9 | def foo_with_screenshot
10 | [foo_without_screenshot, :enhanced]
11 | end
12 | end
13 |
14 | klass = Class.new do
15 | def foo
16 | :original
17 | end
18 | end
19 |
20 | expect(klass.new.foo).to eql(:original)
21 | klass.send :include, reporter_module
22 | expect(klass.new.foo).to eql([:original, :enhanced])
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 |
4 | Bundler::GemHelper.install_tasks
5 |
6 | require 'rspec/core/rake_task'
7 |
8 | rspec_rake_task = RSpec::Core::RakeTask.new(:spec)
9 |
10 | task default: [:spec]
11 |
12 | def target_gem
13 | gem_file = ENV['BUNDLE_GEMFILE'] || ''
14 | targets = %w(cucumber spinach rspec)
15 |
16 | target = gem_file.match(/(#{targets.join('|')})/)
17 | if target && targets.include?(target[1])
18 | target[1].to_sym
19 | else
20 | []
21 | end
22 | end
23 |
24 | namespace :travis do
25 | task :ci => target_gem do
26 | Rake::Task['spec'].invoke
27 | end
28 |
29 | task :cucumber do
30 | rspec_rake_task.pattern = 'spec/cucumber'
31 | end
32 |
33 | task :spinach do
34 | rspec_rake_task.pattern = 'spec/spinach'
35 | end
36 |
37 | task :rspec do
38 | rspec_rake_task.pattern = 'spec/rspec'
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/capybara-screenshot/minitest.rb:
--------------------------------------------------------------------------------
1 | require "capybara-screenshot"
2 |
3 | module Capybara::Screenshot::MiniTestPlugin
4 | def before_setup
5 | super
6 | Capybara::Screenshot.final_session_name = nil
7 | end
8 |
9 | def after_teardown
10 | super
11 | if self.class.ancestors.map(&:to_s).include?('ActionDispatch::IntegrationTest')
12 | if Capybara::Screenshot.autosave_on_failure && !passed?
13 | Capybara.using_session(Capybara::Screenshot.final_session_name) do
14 | filename_prefix = Capybara::Screenshot.filename_prefix_for(:minitest, self)
15 |
16 | saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix)
17 | saver.save
18 | saver.output_screenshot_path
19 | end
20 | end
21 | end
22 | end
23 | end
24 |
25 | class MiniTest::Unit::TestCase
26 | include Capybara::Screenshot::MiniTestPlugin
27 | end
28 |
--------------------------------------------------------------------------------
/lib/capybara-screenshot/capybara.rb:
--------------------------------------------------------------------------------
1 | require "capybara-screenshot"
2 |
3 | module Capybara
4 | module DSL
5 | # Adds class methods to Capybara module and gets mixed into
6 | # the current scope during Cucumber and RSpec tests
7 |
8 | def screenshot_and_save_page
9 | Capybara::Screenshot.screenshot_and_save_page
10 | end
11 |
12 | def screenshot_and_open_image
13 | Capybara::Screenshot.screenshot_and_open_image
14 | end
15 |
16 | def using_session_with_screenshot(name)
17 | using_session_without_screenshot(name) do
18 | original_session_name = Capybara.session_name
19 | Capybara::Screenshot.final_session_name = name
20 | yield
21 | Capybara::Screenshot.final_session_name = original_session_name
22 | end
23 | end
24 |
25 | alias_method :using_session_without_screenshot, :using_session
26 | alias_method :using_session, :using_session_with_screenshot
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/spec/support/html_reporter_context.rb:
--------------------------------------------------------------------------------
1 | shared_context 'html reporter' do
2 | def set_example(example)
3 | @reporter.instance_variable_set :@failed_examples, [example]
4 | end
5 |
6 | before do
7 | # Mocking `RSpec::Core::Formatters::HtmlFormatter`, but only implementing the things that
8 | # are actually used in `HtmlLinkReporter#extra_failure_content_with_screenshot`.
9 | @reporter_class = Class.new do
10 | def extra_failure_content(exception)
11 | "original content"
12 | end
13 | end
14 |
15 | @reporter = @reporter_class.new
16 | @reporter.singleton_class.send :include, described_class
17 | end
18 |
19 | context 'when there is no screenshot' do
20 | before do
21 | set_example double("example", metadata: {})
22 | end
23 |
24 | it 'doesnt change the original content of the reporter' do
25 | expect(@reporter.extra_failure_content(nil)).to eql("original content")
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/capybara-screenshot/spinach.rb:
--------------------------------------------------------------------------------
1 | require "capybara-screenshot"
2 |
3 | Spinach.hooks.before_scenario do |scenario|
4 | Capybara::Screenshot.final_session_name = nil
5 | end
6 |
7 | module Capybara::Screenshot::Spinach
8 | def self.fail_with_screenshot(step_data, exception, location, step_definitions)
9 | if Capybara::Screenshot.autosave_on_failure
10 | Capybara.using_session(Capybara::Screenshot.final_session_name) do
11 | filename_prefix = Capybara::Screenshot.filename_prefix_for(:spinach, step_data)
12 | saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix)
13 | saver.save
14 | saver.output_screenshot_path
15 | end
16 | end
17 | end
18 | end
19 |
20 | Spinach.hooks.on_failed_step do |*args|
21 | Capybara::Screenshot::Spinach.fail_with_screenshot(*args)
22 | end
23 |
24 | Spinach.hooks.on_error_step do |*args|
25 | Capybara::Screenshot::Spinach.fail_with_screenshot(*args)
26 | end
27 |
--------------------------------------------------------------------------------
/lib/capybara-screenshot/rspec/html_embed_reporter.rb:
--------------------------------------------------------------------------------
1 | require 'capybara-screenshot/rspec/base_reporter'
2 | require 'base64'
3 |
4 | module Capybara
5 | module Screenshot
6 | module RSpec
7 | module HtmlEmbedReporter
8 | extend BaseReporter
9 | enhance_with_screenshot :extra_failure_content
10 |
11 | def extra_failure_content_with_screenshot(exception)
12 | result = extra_failure_content_without_screenshot(exception)
13 | example = @failed_examples.last
14 | # Ignores saved html file, only saved image will be embedded (if present)
15 | if (screenshot = example.metadata[:screenshot]) && screenshot[:image]
16 | image = File.binread(screenshot[:image])
17 | encoded_img = Base64.encode64(image)
18 | result += "
"
19 | end
20 | result
21 | end
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # This file was generated by the `rspec --init` command. Conventionally, all
2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3 | # Require this file using `require "spec_helper.rb"` to ensure that it is only
4 | # loaded once.
5 | #
6 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7 |
8 | $: << '../lib'
9 | require 'rspec'
10 | require 'capybara-screenshot'
11 | require 'capybara-screenshot/rspec'
12 | require 'timecop'
13 | require 'aruba/api'
14 | require 'aruba/jruby'
15 |
16 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
17 |
18 | RSpec.configure do |config|
19 | if RSpec::Core::Version::STRING.to_i == 2
20 | config.treat_symbols_as_metadata_keys_with_true_values = true
21 | end
22 | config.run_all_when_everything_filtered = true
23 | config.filter_run :focus
24 | config.before do
25 | @aruba_timeout_seconds = 60
26 | end if RUBY_PLATFORM == 'java'
27 | end
28 |
29 | Capybara.app = lambda { |env| [200, {}, ["OK"]] }
30 |
--------------------------------------------------------------------------------
/lib/capybara-screenshot/cucumber.rb:
--------------------------------------------------------------------------------
1 | require "capybara-screenshot"
2 |
3 | Before do |scenario|
4 | Capybara::Screenshot.final_session_name = nil
5 | end
6 |
7 | After do |scenario|
8 | if Capybara::Screenshot.autosave_on_failure && scenario.failed?
9 | Capybara.using_session(Capybara::Screenshot.final_session_name) do
10 | filename_prefix = Capybara::Screenshot.filename_prefix_for(:cucumber, scenario)
11 |
12 | saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix)
13 | saver.save
14 | saver.output_screenshot_path
15 |
16 | # Trying to embed the screenshot into our output."
17 | if File.exist?(saver.screenshot_path)
18 | require "base64"
19 | #encode the image into it's base64 representation
20 | image = open(saver.screenshot_path, 'rb') {|io|io.read}
21 | encoded_img = Base64.encode64(image)
22 | #this will embed the image in the HTML report, embed() is defined in cucumber
23 | embed(encoded_img, 'image/png;base64', "Screenshot of the error")
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Matthew O'Riordan, inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/spec/unit/rspec_reporters/html_link_reporter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Capybara::Screenshot::RSpec::HtmlLinkReporter do
4 | include_context 'html reporter'
5 |
6 | context 'when a html file was saved' do
7 | before do
8 | set_example double("example", metadata: {screenshot: {html: "path/to/a html file"}})
9 | end
10 |
11 | it 'appends a link to the html to the original content' do
12 | content_without_styles = @reporter.extra_failure_content(nil).gsub(/ ?style=".*?" ?/, "")
13 | expect(content_without_styles).to eql(%{original content
Saved files: HTML page
}) 14 | end 15 | end 16 | 17 | context 'when a html file and an image were saved' do 18 | before do 19 | set_example double("example", metadata: {screenshot: {html: "path/to/html", image: "path/to/an image"}}) 20 | end 21 | 22 | it 'appends links to both files to the original content' do 23 | content_without_styles = @reporter.extra_failure_content(nil).gsub(/ ?style=".*?" ?/, "") 24 | expect(content_without_styles).to eql(%{original contentSaved files: HTML pageScreenshot
}) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/capybara-screenshot/testunit.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit/testresult' 2 | 3 | module Capybara::Screenshot 4 | class << self 5 | attr_accessor :testunit_paths 6 | end 7 | 8 | self.testunit_paths = [%r{test/integration}] 9 | end 10 | 11 | Test::Unit::TestCase.class_eval do 12 | setup do 13 | Capybara::Screenshot.final_session_name = nil 14 | end 15 | end 16 | 17 | Test::Unit::TestResult.class_eval do 18 | private 19 | 20 | def notify_fault_with_screenshot(fault, *args) 21 | notify_fault_without_screenshot fault, *args 22 | is_integration_test = fault.location.any? do |location| 23 | Capybara::Screenshot.testunit_paths.any? { |path| location.match(path) } 24 | end 25 | if is_integration_test 26 | if Capybara::Screenshot.autosave_on_failure 27 | Capybara.using_session(Capybara::Screenshot.final_session_name) do 28 | filename_prefix = Capybara::Screenshot.filename_prefix_for(:testunit, fault) 29 | 30 | saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix) 31 | saver.save 32 | saver.output_screenshot_path 33 | end 34 | end 35 | end 36 | end 37 | alias notify_fault_without_screenshot notify_fault 38 | alias notify_fault notify_fault_with_screenshot 39 | end 40 | -------------------------------------------------------------------------------- /spec/spinach/support/spinach_failure.rb: -------------------------------------------------------------------------------- 1 | require 'capybara' 2 | require 'capybara/rspec' 3 | require 'capybara-screenshot' 4 | require 'capybara-screenshot/spinach' 5 | require 'spinach/capybara' 6 | require 'support/test_app' 7 | 8 | Spinach.config.failure_exceptions = [Capybara::ElementNotFound] 9 | 10 | class Spinach::Features::Failure < Spinach::FeatureSteps 11 | include ::Capybara::DSL 12 | 13 | before do 14 | ::Capybara::Screenshot.register_filename_prefix_formatter(:spinach) do |failed_step| 15 | raise 'expected failing step' if !@expected_failed_step.nil? && !failed_step.name.include?(@expected_failed_step) 16 | 'my_screenshot' 17 | end 18 | end 19 | 20 | step 'I visit "/"' do 21 | visit '/' 22 | end 23 | 24 | step 'I click on a missing link' do 25 | @expected_failed_step = 'I click on a missing link' 26 | click_on "you'll never find me" 27 | end 28 | 29 | step 'I trigger an unhandled exception' do 30 | @expected_failed_step = "I trigger an unhandled exception" 31 | raise "you can't handle me" 32 | end 33 | 34 | step 'I click on a missing link on a different page in a different session' do 35 | using_session :different_session do 36 | visit '/different_page' 37 | @expected_failed_step = 'I click on a missing link on a different page in a different session' 38 | click_on "you'll never find me" 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/capybara-screenshot/rspec/text_reporter.rb: -------------------------------------------------------------------------------- 1 | require 'capybara-screenshot/rspec/base_reporter' 2 | require 'capybara-screenshot/helpers' 3 | 4 | module Capybara 5 | module Screenshot 6 | module RSpec 7 | module TextReporter 8 | extend BaseReporter 9 | 10 | if ::RSpec::Core::Version::STRING.to_i <= 2 11 | enhance_with_screenshot :dump_failure_info 12 | else 13 | enhance_with_screenshot :example_failed 14 | end 15 | 16 | def dump_failure_info_with_screenshot(example) 17 | dump_failure_info_without_screenshot example 18 | output_screenshot_info(example) 19 | end 20 | 21 | def example_failed_with_screenshot(notification) 22 | example_failed_without_screenshot notification 23 | output_screenshot_info(notification.example) 24 | end 25 | 26 | private 27 | def output_screenshot_info(example) 28 | return unless (screenshot = example.metadata[:screenshot]) 29 | output.puts(long_padding + CapybaraScreenshot::Helpers.yellow("HTML screenshot: #{screenshot[:html]}")) if screenshot[:html] 30 | output.puts(long_padding + CapybaraScreenshot::Helpers.yellow("Image screenshot: #{screenshot[:image]}")) if screenshot[:image] 31 | end 32 | 33 | def long_padding 34 | " " 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/capybara-screenshot/rspec/html_link_reporter.rb: -------------------------------------------------------------------------------- 1 | require 'capybara-screenshot/rspec/base_reporter' 2 | require 'cgi' 3 | require 'uri' 4 | 5 | module Capybara 6 | module Screenshot 7 | module RSpec 8 | module HtmlLinkReporter 9 | extend BaseReporter 10 | enhance_with_screenshot :extra_failure_content 11 | 12 | def extra_failure_content_with_screenshot(exception) 13 | result = extra_failure_content_without_screenshot(exception) 14 | example = @failed_examples.last 15 | if (screenshot = example.metadata[:screenshot]) 16 | result << "Saved files: " 17 | result << link_to_screenshot("HTML page", screenshot[:html]) if screenshot[:html] 18 | result << link_to_screenshot("Screenshot", screenshot[:image]) if screenshot[:image] 19 | result << "
" 20 | end 21 | result 22 | end 23 | 24 | def link_to_screenshot(title, path) 25 | url = URI.escape("file://#{path}") 26 | title = CGI.escape_html(title) 27 | attributes = attributes_for_screenshot_link(url).map { |name, val| %{#{name}="#{CGI.escape_html(val)}"} }.join(" ") 28 | "#{title}" 29 | end 30 | 31 | def attributes_for_screenshot_link(url) 32 | {"href" => url, "style" => "margin-right: 10px; font-weight: bold"} 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/capybara-screenshot/pruner.rb: -------------------------------------------------------------------------------- 1 | module Capybara 2 | module Screenshot 3 | class Pruner 4 | attr_reader :strategy 5 | 6 | def initialize(strategy) 7 | @strategy = strategy 8 | 9 | @strategy_proc = case strategy 10 | when :keep_all 11 | -> { } 12 | when :keep_last_run 13 | -> { prune_with_last_run_strategy } 14 | when Hash 15 | raise ArgumentError, ":keep key is required" unless strategy[:keep] 16 | raise ArgumentError, ":keep value must be number greater than zero" unless strategy[:keep].to_i > 0 17 | -> { prune_with_numeric_strategy(strategy[:keep].to_i) } 18 | else 19 | fail "Invalid prune strategy #{strategy}. `:keep_all`or `{ keep: 10 }` are valid examples." 20 | end 21 | end 22 | 23 | def prune_old_screenshots 24 | strategy_proc.call 25 | end 26 | 27 | private 28 | attr_reader :strategy_proc 29 | 30 | def wildcard_path 31 | File.expand_path('*', Screenshot.capybara_root) 32 | end 33 | 34 | def prune_with_last_run_strategy 35 | FileUtils.rm_rf(Dir.glob(wildcard_path)) 36 | end 37 | 38 | def prune_with_numeric_strategy(count) 39 | files = Dir.glob(wildcard_path).sort_by do |file_name| 40 | File.mtime(File.expand_path(file_name, Screenshot.capybara_root)) 41 | end 42 | 43 | FileUtils.rm_rf(files[0...-count]) 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /capybara-screenshot.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "capybara-screenshot/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "capybara-screenshot" 7 | s.version = Capybara::Screenshot::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Matthew O'Riordan"] 10 | s.email = ["matthew.oriordan@gmail.com"] 11 | s.homepage = "http://github.com/mattheworiordan/capybara-screenshot" 12 | s.summary = %q{Automatically create snapshots when Cucumber steps fail with Capybara and Rails} 13 | s.description = %q{When a Cucumber step fails, it is useful to create a screenshot image and HTML file of the current page} 14 | s.license = 'MIT' 15 | 16 | s.rubyforge_project = "capybara-screenshot" 17 | 18 | if RUBY_VERSION < "1.9" 19 | s.add_dependency 'capybara', ['>= 1.0', '< 2'] 20 | else 21 | s.add_dependency 'capybara', ['>= 1.0', '< 3'] 22 | end 23 | s.add_dependency 'launchy' 24 | 25 | s.add_development_dependency 'rspec' 26 | s.add_development_dependency 'timecop' 27 | s.add_development_dependency 'cucumber' 28 | s.add_development_dependency 'aruba' 29 | s.add_development_dependency 'sinatra' 30 | s.add_development_dependency 'test-unit' 31 | s.add_development_dependency 'spinach' 32 | s.add_development_dependency 'minitest' 33 | 34 | s.files = `git ls-files`.split("\n") 35 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 36 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 37 | s.require_paths = ["lib"] 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/capybara_spec.rb: -------------------------------------------------------------------------------- 1 | require 'capybara-screenshot' 2 | require 'capybara/dsl' 3 | 4 | describe Capybara do 5 | it 'adds screen shot methods to the Capybara module' do 6 | expect(::Capybara).to respond_to(:screenshot_and_save_page) 7 | expect(::Capybara).to respond_to(:screenshot_and_open_image) 8 | end 9 | 10 | context 'request type example', :type => :request do 11 | it 'has access to screen shot instance methods' do 12 | expect(subject).to respond_to(:screenshot_and_save_page) 13 | expect(subject).to respond_to(:screenshot_and_open_image) 14 | end 15 | end 16 | 17 | describe 'using_session' do 18 | include Capybara::DSL 19 | 20 | it 'saves the name of the final session' do 21 | expect(Capybara::Screenshot).to receive(:final_session_name=).with(:different_session) 22 | expect { 23 | using_session :different_session do 24 | expect(0).to eq 1 25 | end 26 | }.to raise_exception ::RSpec::Expectations::ExpectationNotMetError 27 | end 28 | end 29 | end 30 | 31 | describe 'final_session_name' do 32 | subject { Capybara::Screenshot.clone } 33 | 34 | describe 'when the final session name has been set' do 35 | before do 36 | subject.final_session_name = 'my-failing-session' 37 | end 38 | 39 | it 'returns the name' do 40 | expect(subject.final_session_name).to eq 'my-failing-session' 41 | end 42 | end 43 | 44 | describe 'when the final session name has not been set' do 45 | it 'returns the current session name' do 46 | allow(Capybara).to receive(:session_name).and_return('my-current-session') 47 | expect(subject.final_session_name).to eq 'my-current-session' 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/support/common_setup.rb: -------------------------------------------------------------------------------- 1 | module CommonSetup 2 | def self.included(target) 3 | target.class_eval do 4 | include Aruba::Api 5 | end 6 | 7 | target.let(:gem_root) { File.expand_path('../..', File.dirname(__FILE__)) } 8 | 9 | target.let(:ensure_load_paths_valid) do 10 | <<-RUBY 11 | %w(lib spec).each do |include_folder| 12 | $LOAD_PATH.unshift(File.join('#{gem_root}', include_folder)) 13 | end 14 | RUBY 15 | end 16 | 17 | target.let(:screenshot_path) { 'tmp' } 18 | target.let(:screenshot_for_pruning_path) { "#{screenshot_path}/old_screenshot.html" } 19 | 20 | target.let(:setup_test_app) do 21 | <<-RUBY 22 | require 'support/test_app' 23 | Capybara.save_and_open_page_path = '#{screenshot_path}' 24 | Capybara.app = TestApp 25 | Capybara::Screenshot.append_timestamp = false 26 | #{@additional_setup_steps} 27 | RUBY 28 | end 29 | 30 | target.before do 31 | if ENV['BUNDLE_GEMFILE'] && ENV['BUNDLE_GEMFILE'].match(/^\.|^[^\/\.]/) 32 | ENV['BUNDLE_GEMFILE'] = File.join(gem_root, ENV['BUNDLE_GEMFILE']) 33 | end 34 | end 35 | 36 | def run_simple_with_retry(*args) 37 | run_simple(*args) 38 | rescue ChildProcess::TimeoutError => e 39 | puts "run_simple(#{args.join(', ')}) failed. Will retry once. `#{e.message}`" 40 | run_simple(*args) 41 | end 42 | 43 | def configure_prune_strategy(strategy) 44 | @additional_setup_steps = "Capybara::Screenshot.prune_strategy = :keep_last_run" 45 | end 46 | 47 | def create_screenshot_for_pruning 48 | write_file screenshot_for_pruning_path, '' 49 | end 50 | 51 | def assert_screenshot_pruned 52 | check_file_presence Array(screenshot_for_pruning_path), false 53 | end 54 | 55 | def assert_screenshot_not_pruned 56 | check_file_presence Array(screenshot_for_pruning_path), true 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/unit/capybara-screenshot_rspec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Screenshot::RSpec do 4 | describe '.after_failed_example' do 5 | context 'for a failed example in a feature that can be snapshotted' do 6 | before do 7 | allow(Capybara.page).to receive(:current_url).and_return("http://test.local") 8 | allow(Capybara::Screenshot::Saver).to receive(:new).and_return(mock_saver) 9 | end 10 | let(:example_group) { Module.new.send(:include, Capybara::DSL) } 11 | let(:example) { double("example", exception: Exception.new, example_group: example_group, metadata: {}) } 12 | let(:mock_saver) do 13 | Capybara::Screenshot::Saver.new(Capybara, Capybara.page).tap do |saver| 14 | allow(saver).to receive(:save) 15 | end 16 | end 17 | 18 | it 'instantiates a saver and calls `save` on it' do 19 | expect(mock_saver).to receive(:save) 20 | described_class.after_failed_example(example) 21 | end 22 | 23 | it 'extends the metadata with an empty hash for screenshot metadata' do 24 | described_class.after_failed_example(example) 25 | expect(example.metadata).to have_key(:screenshot) 26 | expect(example.metadata[:screenshot]).to eql({}) 27 | end 28 | 29 | context 'when a html file gets saved' do 30 | before { allow(mock_saver).to receive(:html_saved?).and_return(true) } 31 | 32 | it 'adds the html file path to the screenshot metadata' do 33 | described_class.after_failed_example(example) 34 | expect(example.metadata[:screenshot][:html]).to match("./screenshot") 35 | end 36 | end 37 | 38 | context 'when an image gets saved' do 39 | before { allow(mock_saver).to receive(:screenshot_saved?).and_return(true) } 40 | 41 | it 'adds the image path to the screenshot metadata' do 42 | described_class.after_failed_example(example) 43 | expect(example.metadata[:screenshot][:image]).to match("./screenshot") 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/unit/rspec_reporters/textmate_link_reporter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Screenshot::RSpec::TextMateLinkReporter do 4 | include_context 'html reporter' 5 | 6 | context 'when a html file was saved' do 7 | before do 8 | set_example double("example", metadata: {screenshot: {html: "path/to/a html file"}}) 9 | end 10 | 11 | it 'appends a link to the html to the original content' do 12 | content_without_styles = @reporter.extra_failure_content(nil).gsub(/ ?style=".*?"/, "") 13 | # Single quotes are handled differently by CGI.escape_html in Ruby 1.9 / Ruby 2, so to be 14 | # compatible with both versions we can't hard code the final escaped string. 15 | expected_onclick_handler = CGI.escape_html("TextMate.system('open file://path/to/a\\%20html\\%20file'); return false;") 16 | expect(content_without_styles).to eql(%{original content} + 17 | %{Saved files: HTML page
} 18 | ) 19 | end 20 | end 21 | 22 | context 'when a html file and an image were saved' do 23 | before do 24 | set_example double("example", metadata: {screenshot: {html: "path/to/html", image: "path/to/an image"}}) 25 | end 26 | 27 | it 'appends links to both files to the original content' do 28 | content_without_styles = @reporter.extra_failure_content(nil).gsub(/ ?style=".*?"/, "") 29 | # Single quotes are handled differently by CGI.escape_html in Ruby 1.9 / Ruby 2, so to be 30 | # compatible with both versions we can't hard code the final escaped string. 31 | expected_onclick_handler_1 = CGI.escape_html("TextMate.system('open file://path/to/html'); return false;") 32 | expected_onclick_handler_2 = CGI.escape_html("TextMate.system('open file://path/to/an\\%20image'); return false;") 33 | expect(content_without_styles).to eql(%{original content} + 34 | %{Saved files: HTML page} + 35 | %{Screenshot
} 36 | ) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/spinach/spinach_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Using Capybara::Screenshot with Spinach" do 4 | include CommonSetup 5 | 6 | before do 7 | clean_current_dir 8 | end 9 | 10 | def run_failing_case(failure_message, code) 11 | write_file('steps/failure.rb', <<-RUBY) 12 | #{ensure_load_paths_valid} 13 | require 'spinach/support/spinach_failure.rb' 14 | #{setup_test_app} 15 | RUBY 16 | 17 | write_file('spinach.feature', code) 18 | cmd = 'bundle exec spinach -f .' 19 | run_simple_with_retry cmd, false 20 | expect(output_from(cmd)).to match failure_message 21 | end 22 | 23 | it "saves a screenshot on failure" do 24 | run_failing_case(%q{Unable to find link or button "you'll never find me"}, <<-GHERKIN) 25 | Feature: Failure 26 | Scenario: Failure 27 | Given I visit "/" 28 | And I click on a missing link 29 | GHERKIN 30 | check_file_content('tmp/my_screenshot.html', 'This is the root page', true) 31 | end 32 | 33 | it "saves a screenshot on an error" do 34 | run_failing_case(%q{you can't handle me}, <<-GHERKIN) 35 | Feature: Failure 36 | Scenario: Failure 37 | Given I visit "/" 38 | And I trigger an unhandled exception 39 | GHERKIN 40 | check_file_content('tmp/my_screenshot.html', 'This is the root page', true) 41 | end 42 | 43 | it "saves a screenshot for the correct session for failures using_session" do 44 | run_failing_case(%q{Unable to find link or button "you'll never find me"}, <<-GHERKIN) 45 | Feature: Failure 46 | Scenario: Failure in different session 47 | Given I visit "/" 48 | And I click on a missing link on a different page in a different session 49 | GHERKIN 50 | check_file_content('tmp/my_screenshot.html', 'This is a different page', true) 51 | end 52 | 53 | it 'on failure it prunes previous screenshots when strategy is set' do 54 | create_screenshot_for_pruning 55 | configure_prune_strategy :last_run 56 | run_failing_case(%q{Unable to find link or button "you'll never find me"}, <<-GHERKIN) 57 | Feature: Failure 58 | Scenario: Failure 59 | Given I visit "/" 60 | And I click on a missing link 61 | GHERKIN 62 | assert_screenshot_pruned 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/feature/testunit_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Using Capybara::Screenshot with Test::Unit" do 4 | include CommonSetup 5 | 6 | before do 7 | clean_current_dir 8 | end 9 | 10 | def run_failing_case(code, integration_path = '.') 11 | write_file("#{integration_path}/test_failure.rb", <<-RUBY) 12 | #{ensure_load_paths_valid} 13 | require 'test/unit' 14 | require 'capybara' 15 | require 'capybara/rspec' 16 | require 'capybara-screenshot' 17 | require 'capybara-screenshot/testunit' 18 | 19 | #{setup_test_app} 20 | Capybara::Screenshot.register_filename_prefix_formatter(:testunit) do | fault | 21 | raise "expected fault" unless fault.exception.message.include? %q{Unable to find link or button "you'll never find me"} 22 | 'my_screenshot' 23 | end 24 | 25 | class TestFailure < Test::Unit::TestCase 26 | include Capybara::DSL 27 | 28 | def test_failure 29 | #{code} 30 | end 31 | end 32 | RUBY 33 | 34 | cmd = "bundle exec ruby #{integration_path}/test_failure.rb" 35 | run_simple_with_retry cmd, false 36 | expect(output_from(cmd)).to include %q{Unable to find link or button "you'll never find me"} 37 | end 38 | 39 | it "saves a screenshot on failure for any test in path 'test/integration'" do 40 | run_failing_case <<-RUBY, 'test/integration' 41 | visit '/' 42 | assert(page.body.include?('This is the root page')) 43 | click_on "you'll never find me" 44 | RUBY 45 | check_file_content 'tmp/my_screenshot.html', 'This is the root page', true 46 | end 47 | 48 | it "does not generate a screenshot for tests that are not in 'test/integration'" do 49 | run_failing_case <<-RUBY, 'test/something-else' 50 | visit '/' 51 | assert(page.body.include?('This is the root page')) 52 | click_on "you'll never find me" 53 | RUBY 54 | 55 | check_file_presence(%w{tmp/my_screenshot.html}, false) 56 | end 57 | 58 | it 'saves a screenshot for the correct session for failures using_session' do 59 | run_failing_case <<-RUBY, 'test/integration' 60 | visit '/' 61 | assert(page.body.include?('This is the root page')) 62 | using_session :different_session do 63 | visit '/different_page' 64 | assert(page.body.include?('This is a different page')) 65 | click_on "you'll never find me" 66 | end 67 | RUBY 68 | check_file_content 'tmp/my_screenshot.html', 'This is a different page', true 69 | end 70 | 71 | it 'prunes screenshots on failure' do 72 | create_screenshot_for_pruning 73 | configure_prune_strategy :last_run 74 | run_failing_case <<-RUBY, 'test/integration' 75 | visit '/' 76 | assert(page.body.include?('This is the root page')) 77 | click_on "you'll never find me" 78 | RUBY 79 | assert_screenshot_pruned 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/unit/capybara-screenshot_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Screenshot do 4 | describe '.register_driver' do 5 | before(:all) do 6 | @original_drivers = Capybara::Screenshot.registered_drivers.dup 7 | end 8 | 9 | after(:all) do 10 | Capybara::Screenshot.registered_drivers = @original_drivers 11 | end 12 | 13 | it 'stores driver with block' do 14 | block = lambda {} 15 | Capybara::Screenshot.register_driver :foo, &block 16 | 17 | expect(Capybara::Screenshot.registered_drivers[:foo]).to eql(block) 18 | end 19 | end 20 | 21 | describe '.register_filename_prefix_formatter' do 22 | before(:all) do 23 | @original_formatters = Capybara::Screenshot.filename_prefix_formatters.dup 24 | end 25 | 26 | after(:all) do 27 | Capybara::Screenshot.filename_prefix_formatters = @original_formatters 28 | end 29 | 30 | it 'stores test type with block' do 31 | block = lambda { |arg| } 32 | Capybara::Screenshot.register_filename_prefix_formatter :foo, &block 33 | 34 | expect(Capybara::Screenshot.filename_prefix_formatters[:foo]).to eql(block) 35 | end 36 | end 37 | 38 | describe '.filename_prefix_for' do 39 | it 'returns "screenshot" for undefined formatter' do 40 | expect(Capybara::Screenshot.filename_prefix_for(:foo, double('test'))).to eql('screenshot') 41 | end 42 | end 43 | 44 | describe '.append_screenshot_path' do 45 | it 'prints a deprecation message and delegates to RSpec.add_link_to_screenshot_for_failed_examples' do 46 | begin 47 | original_stderr = $stderr 48 | $stderr = StringIO.new 49 | expect { 50 | Capybara::Screenshot.append_screenshot_path = false 51 | }.to change { 52 | Capybara::Screenshot::RSpec.add_link_to_screenshot_for_failed_examples 53 | }.from(true).to(false) 54 | expect($stderr.string).to include("append_screenshot_path is deprecated") 55 | ensure 56 | $stderr = original_stderr 57 | end 58 | end 59 | end 60 | 61 | describe '#prune' do 62 | before do 63 | Capybara::Screenshot.reset_prune_history 64 | end 65 | 66 | it 'prunes once by default' do 67 | expect(Capybara::Screenshot::Pruner).to receive(:new).and_call_original.once 68 | 3.times { Capybara::Screenshot.prune } 69 | end 70 | 71 | it 'prunes every time if option force: true' do 72 | expect(Capybara::Screenshot::Pruner).to receive(:new).and_call_original.exactly(3).times 73 | 3.times { Capybara::Screenshot.prune(force: true) } 74 | end 75 | 76 | context 'prune strategy' do 77 | let(:prune_strategy) { { keep: 100 } } 78 | before do 79 | Capybara::Screenshot.prune_strategy = prune_strategy 80 | end 81 | 82 | it 'is passed to initializer' do 83 | expect(Capybara::Screenshot::Pruner).to receive(:new).with(prune_strategy).and_call_original 84 | Capybara::Screenshot.prune 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/capybara-screenshot/saver.rb: -------------------------------------------------------------------------------- 1 | require 'capybara-screenshot/helpers' 2 | 3 | module Capybara 4 | module Screenshot 5 | class Saver 6 | attr_reader :capybara, :page, :file_base_name 7 | def initialize(capybara, page, html_save=true, filename_prefix='screenshot') 8 | @capybara, @page, @html_save = capybara, page, html_save 9 | time_now = Time.now 10 | timestamp = "#{time_now.strftime('%Y-%m-%d-%H-%M-%S.')}#{'%03d' % (time_now.usec/1000).to_i}" 11 | 12 | filename = [filename_prefix] 13 | filename << timestamp if Capybara::Screenshot.append_timestamp 14 | filename << SecureRandom.hex if Capybara::Screenshot.append_random 15 | 16 | @file_base_name = filename.join('_') 17 | 18 | Capybara::Screenshot.prune 19 | end 20 | 21 | def save 22 | # if current_path empty then nothing to screen shot as browser has not loaded any URL 23 | return if capybara.current_path.to_s.empty? 24 | 25 | save_html if @html_save 26 | save_screenshot 27 | end 28 | 29 | def save_html 30 | path = html_path 31 | clear_save_and_open_page_path do 32 | if Capybara::VERSION.match(/^\d+/)[0] == '1' 33 | capybara.save_page(page.body, "#{path}") 34 | else 35 | capybara.save_page("#{path}") 36 | end 37 | end 38 | @html_saved = true 39 | end 40 | 41 | def save_screenshot 42 | path = screenshot_path 43 | clear_save_and_open_page_path do 44 | result = Capybara::Screenshot.registered_drivers.fetch(capybara.current_driver) { |driver_name| 45 | warn "capybara-screenshot could not detect a screenshot driver for '#{capybara.current_driver}'. Saving with default with unknown results." 46 | Capybara::Screenshot.registered_drivers[:default] 47 | }.call(page.driver, path) 48 | @screenshot_saved = result != :not_supported 49 | end 50 | end 51 | 52 | def html_path 53 | File.join(Capybara::Screenshot.capybara_root, "#{file_base_name}.html") 54 | end 55 | 56 | def screenshot_path 57 | File.join(Capybara::Screenshot.capybara_root, "#{file_base_name}.png") 58 | end 59 | 60 | def html_saved? 61 | @html_saved 62 | end 63 | 64 | def screenshot_saved? 65 | @screenshot_saved 66 | end 67 | 68 | # If Capybara.save_and_open_page_path is set then 69 | # the html_path or screenshot_path can be appended to this path in 70 | # some versions of Capybara instead of using it as an absolute path 71 | def clear_save_and_open_page_path 72 | old_path = Capybara.save_and_open_page_path 73 | Capybara.save_and_open_page_path = nil 74 | yield 75 | Capybara.save_and_open_page_path = old_path 76 | end 77 | 78 | def output_screenshot_path 79 | output "HTML screenshot: #{html_path}" if html_saved? 80 | output "Image screenshot: #{screenshot_path}" if screenshot_saved? 81 | end 82 | 83 | private 84 | def output(message) 85 | puts " #{CapybaraScreenshot::Helpers.yellow(message)}" 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/cucumber/cucumber_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Using Capybara::Screenshot with Cucumber" do 4 | include CommonSetup 5 | 6 | before do 7 | clean_current_dir 8 | end 9 | 10 | let(:cmd) { 'bundle exec cucumber' } 11 | 12 | def run_failing_case(failure_message, code) 13 | run_case code 14 | expect(output_from(cmd)).to match(failure_message) 15 | end 16 | 17 | def run_case(code, options = {}) 18 | write_file('features/support/env.rb', <<-RUBY) 19 | #{ensure_load_paths_valid} 20 | require 'cucumber/support/env.rb' 21 | #{setup_test_app} 22 | RUBY 23 | 24 | write_file('features/step_definitions/step_definitions.rb', <<-RUBY) 25 | %w(lib spec).each do |include_folder| 26 | $LOAD_PATH.unshift(File.join('#{gem_root}', include_folder)) 27 | end 28 | require 'cucumber/step_definitions/step_definitions.rb' 29 | RUBY 30 | 31 | write_file('features/cucumber.feature', code) 32 | 33 | run_simple_with_retry cmd, false 34 | 35 | expect(output_from(cmd)).to_not include('failed)') if options[:assert_all_passed] 36 | end 37 | 38 | it 'saves a screenshot on failure' do 39 | run_failing_case %q{Unable to find link or button "you'll never find me"}, <<-CUCUMBER 40 | Feature: Failure 41 | Scenario: Failure 42 | Given I visit "/" 43 | And I click on a missing link 44 | CUCUMBER 45 | check_file_content 'tmp/my_screenshot.html', 'This is the root page', true 46 | end 47 | 48 | it 'saves a screenshot on an error' do 49 | run_failing_case %q{you can't handle me}, <<-CUCUMBER 50 | Feature: Failure 51 | Scenario: Failure 52 | Given I visit "/" 53 | And I trigger an unhandled exception 54 | CUCUMBER 55 | check_file_content 'tmp/my_screenshot.html', 'This is the root page', true 56 | end 57 | 58 | it 'saves a screenshot for the correct session for failures using_session' do 59 | run_failing_case(%q{Unable to find link or button "you'll never find me"}, <<-CUCUMBER) 60 | Feature: Failure 61 | Scenario: Failure in different session 62 | Given I visit "/" 63 | And I click on a missing link on a different page in a different session 64 | CUCUMBER 65 | check_file_content 'tmp/my_screenshot.html', 'This is a different page', true 66 | end 67 | 68 | context 'pruning' do 69 | before do 70 | create_screenshot_for_pruning 71 | configure_prune_strategy :last_run 72 | end 73 | 74 | it 'on failure it prunes previous screenshots when strategy is set' do 75 | run_failing_case %q{Unable to find link or button "you'll never find me"}, <<-CUCUMBER 76 | Feature: Prune 77 | Scenario: Screenshots are pruned if strategy is set 78 | Given I visit "/" 79 | And I click on a missing link 80 | CUCUMBER 81 | assert_screenshot_pruned 82 | end 83 | 84 | it 'on success it never prunes' do 85 | run_case <<-CUCUMBER, assert_all_passed: true 86 | Feature: Prune 87 | Scenario: Screenshots are pruned if strategy is set 88 | Given I visit "/" 89 | CUCUMBER 90 | assert_screenshot_not_pruned 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /spec/unit/pruner_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Screenshot::Pruner do 4 | describe '#initialize' do 5 | let(:pruner) { Capybara::Screenshot::Pruner.new(strategy) } 6 | 7 | context 'accepts generic strategies:' do 8 | [:keep_all, :keep_last_run].each do |strategy_sym| 9 | let(:strategy) { strategy_sym } 10 | 11 | it ":#{strategy_sym}" do 12 | expect(pruner.strategy).to eq(strategy) 13 | end 14 | end 15 | end 16 | 17 | context 'keep:int' do 18 | let(:strategy) { { keep: 50 } } 19 | 20 | it 'is a suitable strategy' do 21 | expect(pruner.strategy).to eq(strategy) 22 | end 23 | end 24 | 25 | context 'invalid strategy' do 26 | context 'symbol' do 27 | let(:strategy) { :invalid_strategy } 28 | 29 | it 'raises an error' do 30 | expect { pruner }.to raise_error 31 | end 32 | end 33 | 34 | context 'keep:sym' do 35 | let(:strategy) { { keep: :symbol } } 36 | 37 | it 'raises an error' do 38 | expect { pruner }.to raise_error 39 | end 40 | end 41 | end 42 | end 43 | 44 | describe '#prune_old_screenshots' do 45 | let(:capybara_root) { Capybara::Screenshot.capybara_root } 46 | let(:remaining_files) { Dir.glob(File.expand_path('*', capybara_root)).sort } 47 | let(:files_created) { [] } 48 | let(:files_count) { 8 } 49 | let(:pruner) { Capybara::Screenshot::Pruner.new(strategy) } 50 | 51 | before do 52 | allow(Capybara::Screenshot).to receive(:capybara_root).and_return(Dir.mktmpdir.to_s) 53 | 54 | files_count.times do |i| 55 | files_created << FileUtils.touch("#{capybara_root}/#{i}").first.tap do |file_name| 56 | File.utime(Time.now, Time.now - files_count + i, file_name) 57 | end 58 | end 59 | 60 | pruner.prune_old_screenshots 61 | end 62 | 63 | after do 64 | FileUtils.rm_rf capybara_root 65 | end 66 | 67 | context 'with :keep_all strategy' do 68 | let(:strategy) { :keep_all } 69 | 70 | it 'should not remove screens' do 71 | expect(remaining_files).to eq(files_created) 72 | end 73 | end 74 | 75 | context 'with :keep_last_run strategy' do 76 | let(:strategy) { :keep_last_run } 77 | 78 | it 'should remove all screens' do 79 | expect(remaining_files).to be_empty 80 | end 81 | 82 | context 'when dir is missing' do 83 | before { FileUtils.rm_rf(Capybara::Screenshot.capybara_root) } 84 | 85 | it 'should not raise error' do 86 | expect { pruner.prune_old_screenshots }.to_not raise_error 87 | end 88 | end 89 | end 90 | 91 | context 'with :keep strategy' do 92 | let(:keep_count) { 3 } 93 | let(:strategy) { { keep: keep_count } } 94 | 95 | it 'should keep specified number of screens' do 96 | expect(remaining_files).to eq(files_created.last(keep_count)) 97 | end 98 | 99 | context 'when dir is missing' do 100 | before { FileUtils.rm_rf(Capybara::Screenshot.capybara_root) } 101 | 102 | it 'should not raise error when dir is missing' do 103 | expect { pruner.prune_old_screenshots }.to_not raise_error 104 | end 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /spec/unit/rspec_reporters/text_reporter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'capybara-screenshot/helpers' 3 | 4 | describe Capybara::Screenshot::RSpec::TextReporter do 5 | before do 6 | # Mocking `RSpec::Core::Formatters::ProgressFormatter`, but only implementing the methods that 7 | # are actually used in `TextReporter#dump_failure_info_with_screenshot`. 8 | @reporter_class = Class.new do 9 | attr_reader :output 10 | 11 | def initialize 12 | @output = StringIO.new 13 | end 14 | 15 | protected 16 | 17 | def long_padding 18 | " " 19 | end 20 | 21 | def failure_color(str) 22 | "colorized(#{str})" 23 | end 24 | 25 | private 26 | 27 | def dump_failure_info(example) 28 | output.puts "original failure info" 29 | end 30 | alias_method :example_failed, :dump_failure_info 31 | end 32 | 33 | @reporter = @reporter_class.new 34 | @reporter.singleton_class.send :include, described_class 35 | end 36 | 37 | let(:example_failed_method) do 38 | if ::RSpec::Core::Version::STRING.to_i <= 2 39 | :dump_failure_info 40 | else 41 | :example_failed 42 | end 43 | end 44 | 45 | def example_failed_method_argument_double(metadata = {}) 46 | example_group = Module.new.send(:include, Capybara::DSL) 47 | example = double("example", metadata: metadata, example_group: example_group) 48 | if ::RSpec::Core::Version::STRING.to_i <= 2 49 | example 50 | else 51 | double("notification").tap do |notification| 52 | allow(notification).to receive(:example).and_return(example) 53 | end 54 | end 55 | end 56 | 57 | context 'when there is no screenshot' do 58 | let(:example) { example_failed_method_argument_double } 59 | 60 | it 'doesnt change the original output of the reporter' do 61 | @reporter.send(example_failed_method, example) 62 | expect(@reporter.output.string).to eql("original failure info\n") 63 | end 64 | end 65 | 66 | context 'when a html file was saved' do 67 | let(:example) { example_failed_method_argument_double(screenshot: { html: "path/to/html" }) } 68 | 69 | it 'appends the html file path to the original output' do 70 | @reporter.send(example_failed_method, example) 71 | expect(@reporter.output.string).to eql("original failure info\n #{CapybaraScreenshot::Helpers.yellow("HTML screenshot: path/to/html")}\n") 72 | end 73 | end 74 | 75 | context 'when a html file and an image were saved' do 76 | let(:example) { example_failed_method_argument_double(screenshot: { html: "path/to/html", image: "path/to/image" }) } 77 | 78 | it 'appends the image path to the original output' do 79 | @reporter.send(example_failed_method, example) 80 | expect(@reporter.output.string).to eql("original failure info\n #{CapybaraScreenshot::Helpers.yellow("HTML screenshot: path/to/html")}\n #{CapybaraScreenshot::Helpers.yellow("Image screenshot: path/to/image")}\n") 81 | end 82 | end 83 | 84 | 85 | it 'works with older RSpec formatters where `#red` is used instead of `#failure_color`' do 86 | old_reporter_class = Class.new(@reporter_class) do 87 | undef_method :failure_color 88 | def red(str) 89 | "red(#{str})" 90 | end 91 | end 92 | old_reporter = old_reporter_class.new 93 | old_reporter.singleton_class.send :include, described_class 94 | example = example_failed_method_argument_double(screenshot: { html: "path/to/html" }) 95 | old_reporter.send(example_failed_method, example) 96 | expect(old_reporter.output.string).to eql("original failure info\n #{CapybaraScreenshot::Helpers.yellow("HTML screenshot: path/to/html")}\n") 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/feature/minitest_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Using Capybara::Screenshot with MiniTest" do 4 | include CommonSetup 5 | 6 | before do 7 | clean_current_dir 8 | end 9 | 10 | def run_failing_case(code) 11 | write_file('test_failure.rb', <<-RUBY) 12 | #{ensure_load_paths_valid} 13 | require 'minitest/autorun' 14 | require 'capybara' 15 | require 'capybara-screenshot' 16 | require 'capybara-screenshot/minitest' 17 | 18 | #{setup_test_app} 19 | Capybara::Screenshot.register_filename_prefix_formatter(:minitest) do |test_case| 20 | test_name = test_case.respond_to?(:name) ? test_case.name : test_case.__name__ 21 | raise "expected fault" unless test_name.include? 'test_failure' 22 | 'my_screenshot' 23 | end 24 | 25 | #{code} 26 | RUBY 27 | 28 | cmd = 'bundle exec ruby test_failure.rb' 29 | run_simple_with_retry cmd, false 30 | expect(output_from(cmd)).to include %q{Unable to find link or button "you'll never find me"} 31 | end 32 | 33 | it 'saves a screenshot on failure' do 34 | run_failing_case <<-RUBY 35 | module ActionDispatch 36 | class IntegrationTest < Minitest::Unit::TestCase; end 37 | end 38 | 39 | class TestFailure < ActionDispatch::IntegrationTest 40 | include Capybara::DSL 41 | 42 | def test_failure 43 | visit '/' 44 | assert(page.body.include?('This is the root page')) 45 | click_on "you'll never find me" 46 | end 47 | end 48 | RUBY 49 | check_file_content 'tmp/my_screenshot.html', 'This is the root page', true 50 | end 51 | 52 | it "does not save a screenshot for tests that don't inherit from ActionDispatch::IntegrationTest" do 53 | run_failing_case <<-RUBY 54 | class TestFailure < MiniTest::Unit::TestCase 55 | include Capybara::DSL 56 | 57 | def test_failure 58 | visit '/' 59 | assert(page.body.include?('This is the root page')) 60 | click_on "you'll never find me" 61 | end 62 | end 63 | RUBY 64 | check_file_presence(%w{tmp/my_screenshot.html}, false) 65 | end 66 | 67 | it 'saves a screenshot for the correct session for failures using_session' do 68 | run_failing_case <<-RUBY 69 | module ActionDispatch 70 | class IntegrationTest < Minitest::Unit::TestCase; end 71 | end 72 | 73 | class TestFailure < ActionDispatch::IntegrationTest 74 | include Capybara::DSL 75 | 76 | def test_failure 77 | visit '/' 78 | assert(page.body.include?('This is the root page')) 79 | using_session :different_session do 80 | visit '/different_page' 81 | assert(page.body.include?('This is a different page')) 82 | click_on "you'll never find me" 83 | end 84 | end 85 | end 86 | RUBY 87 | check_file_content 'tmp/my_screenshot.html', 'This is a different page', true 88 | end 89 | 90 | it 'prunes screenshots on failure' do 91 | create_screenshot_for_pruning 92 | configure_prune_strategy :last_run 93 | run_failing_case <<-RUBY 94 | module ActionDispatch 95 | class IntegrationTest < Minitest::Unit::TestCase; end 96 | end 97 | 98 | class TestFailure < ActionDispatch::IntegrationTest 99 | include Capybara::DSL 100 | 101 | def test_failure 102 | visit '/' 103 | assert(page.body.include?('This is the root page')) 104 | click_on "you'll never find me" 105 | end 106 | end 107 | RUBY 108 | assert_screenshot_pruned 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/capybara-screenshot/rspec.rb: -------------------------------------------------------------------------------- 1 | require "capybara-screenshot" 2 | 3 | require "capybara-screenshot/rspec/text_reporter" 4 | require "capybara-screenshot/rspec/html_link_reporter" 5 | require "capybara-screenshot/rspec/html_embed_reporter" 6 | require "capybara-screenshot/rspec/textmate_link_reporter" 7 | 8 | module Capybara 9 | module Screenshot 10 | module RSpec 11 | 12 | # Reporters extend RSpec formatters to display information about screenshots for failed 13 | # examples. 14 | # 15 | # Technically, a reporter is a module that gets injected into a RSpec formatter class. 16 | # It uses method aliasing to extend some (usually just one) of the formatter's methods. 17 | # 18 | # Implementing a custom reporter is as simple as creating a module and setting up the 19 | # appropriate aliases. Use `BaseReporter.enhance_with_screenshot` if you don't want 20 | # to set up the aliases manually: 21 | # 22 | # module MyReporter 23 | # extend Capybara::Screenshot::RSpec::BaseReporter 24 | # 25 | # # Will replace the formatter's original `dump_failure_info` method with 26 | # # `dump_failure_info_with_screenshot` from this module: 27 | # enhance_with_screenshot :dump_failure_info 28 | # 29 | # def dump_failure_info_with_screenshot(example) 30 | # dump_failure_info_without_screenshot(example) # call original implementation 31 | # ... # your additions here 32 | # end 33 | # end 34 | # 35 | # Finally customize `Capybara::Screenshot::RSpec::FORMATTERS` to make sure your reporter 36 | # gets injected into the appropriate formatter. 37 | 38 | REPORTERS = { 39 | "RSpec::Core::Formatters::ProgressFormatter" => Capybara::Screenshot::RSpec::TextReporter, 40 | "RSpec::Core::Formatters::DocumentationFormatter" => Capybara::Screenshot::RSpec::TextReporter, 41 | "RSpec::Core::Formatters::HtmlFormatter" => Capybara::Screenshot::RSpec::HtmlLinkReporter, 42 | "RSpec::Core::Formatters::TextMateFormatter" => Capybara::Screenshot::RSpec::TextMateLinkReporter, # RSpec 2 43 | "RSpec::Mate::Formatters::TextMateFormatter" => Capybara::Screenshot::RSpec::TextMateLinkReporter # RSpec 3 44 | } 45 | 46 | class << self 47 | attr_accessor :add_link_to_screenshot_for_failed_examples 48 | 49 | def after_failed_example(example) 50 | if example.example_group.include?(Capybara::DSL) # Capybara DSL method has been included for a feature we can snapshot 51 | Capybara.using_session(Capybara::Screenshot.final_session_name) do 52 | if Capybara.page.current_url != '' && Capybara::Screenshot.autosave_on_failure && example.exception 53 | filename_prefix = Capybara::Screenshot.filename_prefix_for(:rspec, example) 54 | 55 | saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix) 56 | saver.save 57 | 58 | example.metadata[:screenshot] = {} 59 | example.metadata[:screenshot][:html] = saver.html_path if saver.html_saved? 60 | example.metadata[:screenshot][:image] = saver.screenshot_path if saver.screenshot_saved? 61 | end 62 | end 63 | end 64 | end 65 | end 66 | 67 | self.add_link_to_screenshot_for_failed_examples = true 68 | end 69 | end 70 | end 71 | 72 | RSpec.configure do |config| 73 | config.before do 74 | Capybara::Screenshot.final_session_name = nil 75 | end 76 | 77 | config.after do |example_from_block_arg| 78 | # RSpec 3 no longer defines `example`, but passes the example as block argument instead 79 | example = config.respond_to?(:expose_current_running_example_as) ? example_from_block_arg : self.example 80 | 81 | Capybara::Screenshot::RSpec.after_failed_example(example) 82 | end 83 | 84 | config.before(:suite) do 85 | if Capybara::Screenshot::RSpec.add_link_to_screenshot_for_failed_examples 86 | RSpec.configuration.formatters.each do |formatter| 87 | next unless (reporter_module = Capybara::Screenshot::RSpec::REPORTERS[formatter.class.to_s]) 88 | formatter.singleton_class.send :include, reporter_module 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/capybara-screenshot.rb: -------------------------------------------------------------------------------- 1 | module Capybara 2 | module Screenshot 3 | class << self 4 | attr_accessor :autosave_on_failure 5 | attr_accessor :registered_drivers 6 | attr_accessor :filename_prefix_formatters 7 | attr_accessor :append_timestamp 8 | attr_accessor :append_random 9 | attr_accessor :webkit_options 10 | attr_writer :final_session_name 11 | attr_accessor :prune_strategy 12 | end 13 | 14 | self.autosave_on_failure = true 15 | self.registered_drivers = {} 16 | self.filename_prefix_formatters = {} 17 | self.append_timestamp = true 18 | self.append_random = false 19 | self.webkit_options = {} 20 | self.prune_strategy = :keep_all 21 | 22 | def self.append_screenshot_path=(value) 23 | $stderr.puts "WARNING: Capybara::Screenshot.append_screenshot_path is deprecated. " + 24 | "Please use Capybara::Screenshot::RSpec.add_link_to_screenshot_for_failed_examples instead." 25 | RSpec.add_link_to_screenshot_for_failed_examples = value 26 | end 27 | 28 | def self.screenshot_and_save_page 29 | saver = Saver.new(Capybara, Capybara.page) 30 | saver.save 31 | {:html => saver.html_path, :image => saver.screenshot_path} 32 | end 33 | 34 | def self.screenshot_and_open_image 35 | require "launchy" 36 | 37 | saver = Saver.new(Capybara, Capybara.page, false) 38 | saver.save 39 | Launchy.open saver.screenshot_path 40 | {:html => nil, :image => saver.screenshot_path} 41 | end 42 | 43 | class << self 44 | alias screen_shot_and_save_page screenshot_and_save_page 45 | alias screen_shot_and_open_image screenshot_and_open_image 46 | end 47 | 48 | def self.filename_prefix_for(test_type, test) 49 | filename_prefix_formatters.fetch(test_type) { |key| 50 | filename_prefix_formatters[:default] 51 | }.call(test) 52 | end 53 | 54 | def self.capybara_root 55 | return @capybara_root if defined?(@capybara_root) 56 | #If the path isn't set, default to the current directory 57 | capybara_tmp_path = Capybara.save_and_open_page_path || '.' 58 | 59 | @capybara = if defined?(::Rails) 60 | ::Rails.root.join capybara_tmp_path 61 | elsif defined?(Padrino) 62 | Padrino.root capybara_tmp_path 63 | elsif defined?(Sinatra) 64 | File.join(Sinatra::Application.root, capybara_tmp_path) 65 | else 66 | capybara_tmp_path 67 | end.to_s 68 | end 69 | 70 | def self.register_driver(driver, &block) 71 | self.registered_drivers[driver] = block 72 | end 73 | 74 | def self.register_filename_prefix_formatter(test_type, &block) 75 | self.filename_prefix_formatters[test_type] = block 76 | end 77 | 78 | def self.final_session_name 79 | @final_session_name || Capybara.session_name || :default 80 | end 81 | 82 | # Prune screenshots based on prune_strategy 83 | # Will run only once unless force:true 84 | def self.prune(options = {}) 85 | reset_prune_history if options[:force] 86 | Capybara::Screenshot::Pruner.new(Capybara::Screenshot.prune_strategy).prune_old_screenshots unless @pruned_previous_screenshots 87 | @pruned_previous_screenshots = true 88 | end 89 | 90 | # Reset prune history allowing further prunining on next failure 91 | def self.reset_prune_history 92 | @pruned_previous_screenshots = nil 93 | end 94 | end 95 | end 96 | 97 | # Register driver renderers. 98 | # The block should return `:not_supported` if a screenshot could not be saved. 99 | Capybara::Screenshot.class_eval do 100 | register_driver(:default) do |driver, path| 101 | driver.render(path) 102 | end 103 | 104 | register_driver(:rack_test) do |driver, path| 105 | :not_supported 106 | end 107 | 108 | register_driver(:mechanize) do |driver, path| 109 | :not_supported 110 | end 111 | 112 | register_driver(:selenium) do |driver, path| 113 | driver.browser.save_screenshot(path) 114 | end 115 | 116 | register_driver(:poltergeist) do |driver, path| 117 | driver.render(path, :full => true) 118 | end 119 | 120 | register_driver(:poltergeist_billy) do |driver, path| 121 | driver.render(path, :full => true) 122 | end 123 | 124 | webkit_block = proc do |driver, path| 125 | if driver.respond_to?(:save_screenshot) 126 | driver.save_screenshot(path, webkit_options) 127 | else 128 | driver.render(path) 129 | end 130 | end 131 | 132 | register_driver :webkit, &webkit_block 133 | register_driver :webkit_debug, &webkit_block 134 | 135 | register_driver(:terminus) do |driver, path| 136 | if driver.respond_to?(:save_screenshot) 137 | driver.save_screenshot(path) 138 | else 139 | :not_supported 140 | end 141 | end 142 | end 143 | 144 | # Register filename prefix formatters 145 | Capybara::Screenshot.class_eval do 146 | register_filename_prefix_formatter(:default) do |test| 147 | 'screenshot' 148 | end 149 | end 150 | 151 | require 'capybara/dsl' 152 | require 'capybara/util/save_and_open_page' if Capybara::VERSION.match(/^\d+/)[0] == '1' # no longer needed in Capybara version 2 153 | 154 | require 'capybara-screenshot/saver' 155 | require 'capybara-screenshot/capybara' 156 | require 'capybara-screenshot/pruner' 157 | -------------------------------------------------------------------------------- /spec/rspec/rspec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Screenshot::RSpec do 4 | describe "used with RSpec" do 5 | include CommonSetup 6 | 7 | before do 8 | clean_current_dir 9 | end 10 | 11 | def run_failing_case(code, error_message, format=nil) 12 | run_case code, format: format 13 | 14 | cmd = cmd_with_format(format) 15 | if error_message.kind_of?(Regexp) 16 | expect(output_from(cmd)).to match(error_message) 17 | else 18 | expect(output_from(cmd)).to include(error_message) 19 | end 20 | end 21 | 22 | def run_case(code, options = {}) 23 | write_file('spec/test_failure.rb', <<-RUBY) 24 | #{ensure_load_paths_valid} 25 | require 'rspec' 26 | require 'capybara' 27 | require 'capybara/rspec' 28 | require 'capybara-screenshot' 29 | require 'capybara-screenshot/rspec' 30 | 31 | #{setup_test_app} 32 | #{code} 33 | RUBY 34 | 35 | cmd = cmd_with_format(options[:format]) 36 | run_simple_with_retry cmd, false 37 | 38 | expect(output_from(cmd)).to include('0 failures') if options[:assert_all_passed] 39 | end 40 | 41 | def cmd_with_format(format) 42 | "bundle exec rspec #{"--format #{format} " if format}spec/test_failure.rb" 43 | end 44 | 45 | it 'saves a screenshot on failure' do 46 | run_failing_case <<-RUBY, %q{Unable to find link or button "you'll never find me"} 47 | feature 'screenshot with failure' do 48 | scenario 'click on a missing link' do 49 | visit '/' 50 | expect(page.body).to include('This is the root page') 51 | click_on "you'll never find me" 52 | end 53 | end 54 | RUBY 55 | check_file_content('tmp/screenshot.html', 'This is the root page', true) 56 | end 57 | 58 | formatters = { 59 | progress: 'HTML screenshot:', 60 | documentation: 'HTML screenshot:', 61 | html: %r{]*>HTML page} 62 | } 63 | 64 | # Textmate formatter is only included in RSpec 2 65 | if RSpec::Core::Version::STRING.to_i == 2 66 | formatters[:textmate] = %r{TextMate\.system\(.*open file://\./tmp/screenshot.html} 67 | end 68 | 69 | formatters.each do |formatter, error_message| 70 | it "uses the associated #{formatter} formatter" do 71 | run_failing_case <<-RUBY, error_message, formatter 72 | feature 'screenshot with failure' do 73 | scenario 'click on a missing link' do 74 | visit '/' 75 | click_on "you'll never find me" 76 | end 77 | end 78 | RUBY 79 | check_file_content('tmp/screenshot.html', 'This is the root page', true) 80 | end 81 | end 82 | 83 | it "does not save a screenshot for tests that don't use Capybara" do 84 | run_failing_case <<-RUBY, %q{expected: false} 85 | describe 'failing test' do 86 | it 'fails intentionally' do 87 | expect(true).to eql(false) 88 | end 89 | end 90 | RUBY 91 | check_file_presence(%w{tmp/screenshot.html}, false) 92 | end 93 | 94 | it 'saves a screenshot for the correct session for failures using_session' do 95 | run_failing_case <<-RUBY, %q{Unable to find link or button "you'll never find me"} 96 | feature 'screenshot with failure' do 97 | scenario 'click on a missing link' do 98 | visit '/' 99 | expect(page.body).to include('This is the root page') 100 | using_session :different_session do 101 | visit '/different_page' 102 | expect(page.body).to include('This is a different page') 103 | click_on "you'll never find me" 104 | end 105 | end 106 | end 107 | RUBY 108 | check_file_content('tmp/screenshot.html', 'This is a different page', true) 109 | end 110 | 111 | context 'pruning' do 112 | before do 113 | create_screenshot_for_pruning 114 | configure_prune_strategy :last_run 115 | end 116 | 117 | it 'on failure it prunes previous screenshots when strategy is set' do 118 | run_failing_case <<-RUBY, 'HTML screenshot:', :progress 119 | feature 'screenshot with failure' do 120 | scenario 'click on a missing link' do 121 | visit '/' 122 | click_on "you'll never find me" 123 | end 124 | end 125 | RUBY 126 | assert_screenshot_pruned 127 | end 128 | 129 | it 'on success it never prunes' do 130 | run_case <<-CUCUMBER, assert_all_passed: true 131 | feature 'screenshot without failure' do 132 | scenario 'click on a link' do 133 | visit '/' 134 | end 135 | end 136 | CUCUMBER 137 | assert_screenshot_not_pruned 138 | end 139 | end 140 | 141 | context 'no pruning by default' do 142 | before do 143 | create_screenshot_for_pruning 144 | end 145 | 146 | it 'on failure it leaves existing screenshots' do 147 | run_failing_case <<-RUBY, 'HTML screenshot:', :progress 148 | feature 'screenshot with failure' do 149 | scenario 'click on a missing link' do 150 | visit '/' 151 | click_on "you'll never find me" 152 | end 153 | end 154 | RUBY 155 | assert_screenshot_not_pruned 156 | end 157 | end 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 9 March 2015 - 1.0.6 -> 1.0.7 2 | ----------- 3 | 4 | * Fix capybara-webkit bug, see https://github.com/mattheworiordan/capybara-screenshot/issues/119 5 | * Fix Travis CI builds in Ruby < 2.1 and added Ruby 2.2 support 6 | 7 | 8 March 2015 - 1.0.5 -> 1.0.6 8 | ----------- 9 | 10 | * Removed dependency on the colored gem 11 | 12 | Thanks to [François Bernier](https://github.com/fbernier) 13 | 14 | 10 Feburary 2015 - 1.04 -> 1.0.5 15 | ----------- 16 | 17 | * Added support for appending a random string to the filename 18 | 19 | Thanks to [Brad Wedell](https://github.com/streetlogics) 20 | 21 | 5 January 2015 - 1.0.3 -> 1.0.4 22 | ----------- 23 | 24 | * Added support for Poltergeist Billy 25 | * Don't initialize a new Capybara::Session in after hook 26 | 27 | Thanks to [Neodude](https://github.com/neodude) and [Dominik Masur](https://github.com/dmasur) 28 | 29 | 1 October 2014 - 1.0.2 -> 1.0.3 30 | ----------- 31 | 32 | * Added ability to prune screenshots automatically, see https://github.com/mattheworiordan/capybara-screenshot/pull/100 33 | 34 | Thanks to [Anton Kolomiychuk](https://github.com/akolomiychuk) for his contribution. 35 | 36 | 27 September 2014 - 1.0.1 -> 1.0.2 37 | ----------- 38 | 39 | * Improved documentation to cover RSpec 3's new approach to using `rails_helper` in place of `spec_helper` for Rails tests 40 | * Updated documentation to use Ruby formatting in language blocks 41 | * Removed need to manually `require 'capybara-screenshot'` for RSpec 42 | 43 | 18 September 2014 - 1.0.0 -> 1.0.1 44 | ----------- 45 | 46 | * Hot fix for RSpec version issue that assumed RSpec base library was always available, now uses `RSpec::Core::VERSION` 47 | * Improve Travis CI performance and stability 48 | 49 | 18 September 2014 - 0.3.22 -> 1.0.0 50 | ----------- 51 | 52 | Because of the broad test coverage now across RSpec, Cucumber, Spinach, Minitest and TestUnit using [Aruba](https://github.com/cucumber/aruba), I feel that this gem is ready for its first major release. New features and refactoring can now reliably be done without the fear of regressions. 53 | 54 | The major changes in this 1.0 release are: 55 | 56 | * Acceptance test coverage for RSpec, Cucumber, Spinach, Minitest and TestUnit 57 | * Travis CI test coverage across a matrix of old and new versions of the aforementioned testing frameworks, see https://github.com/mattheworiordan/capybara-screenshot/blob/master/.travis.yml 58 | * Support for RSpec 3 using the custom formatters 59 | * Support for sessions using `using_session`, see https://github.com/mattheworiordan/capybara-screenshot/pull/91 for more info 60 | * Support for RSpec DocumentationFormatter 61 | * Considerable refactoring of the test suite 62 | 63 | Special thanks goes to [Andrew Brown](https://github.com/dontfidget) who has contributed a huge amount of the code that has helped enable this Gem to have its stable major version release. 64 | 65 | 22 July 2014 - 0.3.21 -> 0.3.22 66 | ----------- 67 | 68 | Replaced [colorize](https://rubygems.org/gems/colorize) gem with [colored](https://rubygems.org/gems/colored) due to license issue, see https://github.com/mattheworiordan/capybara-screenshot/issues/93. 69 | 70 | 22 July 2014 - 0.3.20 -> 0.3.21 71 | ----------- 72 | 73 | As a result of recent merges and insufficient test coverage, it seems that for test suites other than RSpec the HTML or Image screenshot path was no longer being outputted in the test results. This has now been fixed, and screenshot output format for RSpec and all other test suites has been standardised. 74 | 75 | 11 July 2014 - 0.3.19 -> 0.3.20 76 | ----------- 77 | 78 | * Added reporters to improve screenshot info in RSpec output 79 | * Added support for Webkit options such as width and height 80 | 81 | Thanks to https://github.com/multiplegeorges and https://github.com/noniq 82 | 83 | 2 April 2014 - 0.3.18 -> 0.3.19 84 | ----------- 85 | 86 | * Added support Spinach, thanks to https://github.com/suchitpuri 87 | 88 | 2 March 2014 - 0.3.16 -> 0.3.17 89 | ----------- 90 | 91 | * Added support for RSpec 3 and cleaned up the logging so there is less noise within the test results when a driver does not support a particular format. 92 | * Updated Travis to test against Ruby 2.0 and Ruby 2.1 93 | 94 | Thanks to https://github.com/noniq 95 | 96 | 7 January 2014 97 | ----------- 98 | 99 | Bug fix for Minitest 5, thanks to https://github.com/cschramm 100 | 101 | 102 | 12 September 2013 103 | ----------- 104 | 105 | Added support for Test Unit, fixed RSpec deprecation warnings and fixed a dependency issue. 106 | 107 | Thanks to: 108 | 109 | * https://github.com/budnik 110 | * https://github.com/jkraemer 111 | * https://github.com/mariovisic 112 | 113 | 114 | 23 July 2013 115 | ----------- 116 | 117 | https://github.com/stevenwilkin contributed code to display a warning for [Mechanize](http://mechanize.rubyforge.org/) users. 118 | 119 | 3 June 2013 120 | ----------- 121 | 122 | Dropped Ruby 1.8 support for this Gem because of conflicts with Nokogiri requiring a later version of Ruby. Instead, there is a new branch https://github.com/mattheworiordan/capybara-screenshot/tree/ruby-1.8-support which can be used if requiring backwards compatabiltiy. 123 | 124 | 18 Apr 2013 125 | ----------- 126 | 127 | Improved documentation, Ruby 1.8.7 support by not allowing Capybara 2.1 to be used, improved Sinatra support. 128 | RSpec screenshot fix to only screenshot when applicable: https://github.com/mattheworiordan/capybara-screenshot/issues/44 129 | 130 | 07 Jan 2013 131 | ----------- 132 | 133 | Support for Terminus, thanks to https://github.com/jamesotron 134 | 135 | 27 Dec 2012 136 | ----------- 137 | 138 | Previos version bump broke Ruby 1.8.7 support, so Travis CI build added to this Gem and Ruby 1.8.7 support along with JRuby support added. 139 | 140 | 30 Oct 2012 - Significant version bump 0.3 141 | ----------- 142 | 143 | After some consideration, and continued problems with load order of capybara-screenshot in relation to other required gems, the commits from @adzap in the pull request https://github.com/mattheworiordan/capybara-screenshot/pull/29 have been incorporated. Moving forwards, for every testing framework you use, you will be required to add an explicit require. 144 | 145 | 146 | 15 Feb 2012 147 | ----------- 148 | 149 | Merged pull request https://github.com/mattheworiordan/capybara-screenshot/pull/14 to limit when capybara-screenshot is fired for RSpec 150 | 151 | 30 Jan 2012 152 | ----------- 153 | 154 | Merged pull request from https://github.com/hlascelles to support Padrino 155 | 156 | 15 Jan 2012 157 | ----------- 158 | 159 | Removed unnecessary and annoying warning that a screen shot cannot be taken. This message was being shown when RSpec tests were run that did not even invoke Capybara 160 | 161 | 13 Jan 2012 162 | ----------- 163 | 164 | Updated documentation to reflect support for more frameworks, https://github.com/mattheworiordan/capybara-screenshot/issues/9 165 | 166 | 3 Jan 2012 167 | ---------- 168 | 169 | Removed Cucumber dependency https://github.com/mattheworiordan/capybara-screenshot/issues/7 170 | Allowed PNG save path to be configured using capybara.save_and_open_page_path 171 | 172 | 3 December 2011 173 | --------------- 174 | 175 | More robust handling of Minitest for users who have it installed as a dependency 176 | https://github.com/mattheworiordan/capybara-screenshot/issues/5 177 | 178 | 179 | 2 December 2011 180 | --------------- 181 | 182 | Fixed bug related to teardown hook not being available in Minitest for some reason (possibly version issues). 183 | https://github.com/mattheworiordan/capybara-screenshot/issues/5 184 | 185 | 24 November 2011 186 | ---------------- 187 | 188 | Added support for: 189 | 190 | * More platforms (Poltergeist) 191 | * Removed Rails dependencies (bug) 192 | * Added screenshot capability for Selenium 193 | * Added support for embed for HTML reports 194 | 195 | Thanks to [https://github.com/rb2k](https://github.com/rb2k) for 2 [great commits](https://github.com/mattheworiordan/capybara-screenshot/pull/4) 196 | 197 | 16 November 2011 198 | ---------------- 199 | 200 | Added support for Minitest using teardown hooks 201 | 202 | 203 | 16 November 2011 204 | ---------------- 205 | 206 | Added support for RSpec by adding a RSpec configuration after hook and checking if Capybara is being used. 207 | 208 | 15 November 2011 209 | ---------------- 210 | 211 | Ensured that tests run other than Cucumber won't fail. Prior to this Cucumber was required. 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | capybara-screenshot gem 2 | ======================= 3 | 4 | [](https://travis-ci.org/mattheworiordan/capybara-screenshot) 5 | [](https://codeclimate.com/github/mattheworiordan/capybara-screenshot) 6 | [](http://badge.fury.io/rb/capybara-screenshot) 7 | 8 | #### Capture a screen shot for every test failure automatically! 9 | 10 | `capybara-screenshot` used with [Capybara](https://github.com/jnicklas/capybara) and [Cucumber](http://cukes.info/), [Rspec](https://www.relishapp.com/rspec) or [Minitest](https://github.com/seattlerb/minitest), will capture a screen shot for each failure in your test suite. The HTML for the failed page, and a screenshot image (when using [capybara-webkit](https://github.com/thoughtbot/capybara-webkit), [Selenium](http://seleniumhq.org/) or [poltergeist](https://github.com/jonleighton/poltergeist)) is saved into `$APPLICATION_ROOT/tmp/capybara`. 11 | 12 | Having screenshots readily available for each test failure is incredibly helpful when trying to quickly diagnose a problem in your failing steps. You can view the source code, and have a screen shot of the page (when applicable), at the time of each failure. 13 | 14 | _Please note that Ruby 1.9+ is required to use this Gem. For Ruby 1.8 support, please see the [capybara-screenshot Ruby 1.8 branch](https://github.com/mattheworiordan/capybara-screenshot/tree/ruby-1.8-support)_ 15 | 16 | Installation 17 | ----- 18 | 19 | ### Step 1: install the gem 20 | 21 | Using Bundler, add the following to your Gemfile 22 | 23 | ```ruby 24 | gem 'capybara-screenshot', :group => :test 25 | ``` 26 | 27 | or install manually using Ruby Gems: 28 | 29 | ``` 30 | gem install capybara-screenshot 31 | ``` 32 | 33 | ### Step 2: load capybara-screenshot into your tests 34 | 35 | #### Cucumber 36 | 37 | In env.rb or a support file, please add: 38 | 39 | ```ruby 40 | require 'capybara-screenshot/cucumber' 41 | ``` 42 | 43 | #### RSpec 44 | 45 | In rails_helper.rb, spec_helper.rb, or a support file, after the require for 'capybara/rspec', please add: 46 | 47 | ```ruby 48 | # remember: you must require 'capybara/rspec' first 49 | require 'capybara-screenshot/rspec' 50 | ``` 51 | 52 | *Note: As of RSpec Rails 3.0, it is recommended that all your Rails environment code is loaded into `rails_helper.rb` instead of `spec_helper.rb`, and as such, the capybara-screenshot require should be located in `rails_helper.rb`. See the [RSpec Rails 3.0 upgrade notes](https://www.relishapp.com/rspec/rspec-rails/v/3-0/docs/upgrade) for more info.* 53 | 54 | #### Minitest 55 | 56 | Typically in 'test/test_helper.rb', please add: 57 | 58 | ```ruby 59 | require 'capybara-screenshot/minitest' 60 | ``` 61 | 62 | #### Test::Unit 63 | 64 | Typically in 'test/test_helper.rb', please add: 65 | 66 | ```ruby 67 | require 'capybara-screenshot/testunit' 68 | ``` 69 | 70 | By default, screenshots will be captured for `Test::Unit` tests in the path 'test/integration'. You can add additional paths as: 71 | 72 | ```ruby 73 | Capybara::Screenshot.testunit_paths << 'test/feature' 74 | ``` 75 | 76 | 77 | Manual screenshots 78 | ---- 79 | 80 | If you require more control, you can generate the screenshot on demand rather than on failure. This is useful 81 | if the failure occurs at a point where the screen shot is not as useful for debugging a rendering problem. This 82 | can be more useful if you disable the auto-generate on failure feature with the following config 83 | 84 | ```ruby 85 | Capybara::Screenshot.autosave_on_failure = false 86 | ``` 87 | 88 | Anywhere the Capybara DSL methods (visit, click etc.) are available so too are the screenshot methods. 89 | 90 | ```ruby 91 | screenshot_and_save_page 92 | ``` 93 | 94 | Or for screenshot only, which will automatically open the image. 95 | 96 | ```ruby 97 | screenshot_and_open_image 98 | ``` 99 | 100 | These are just calls on the main library methods. 101 | 102 | ```ruby 103 | Capybara::Screenshot.screenshot_and_save_page 104 | Capybara::Screenshot.screenshot_and_open_image 105 | ``` 106 | 107 | 108 | Driver configuration 109 | -------------------- 110 | 111 | The gem supports the default rendering method for Capybara to generate the screenshot, which is: 112 | 113 | ```ruby 114 | page.driver.render(path) 115 | ``` 116 | 117 | There are also some specific driver configurations for Selenium, Webkit, and Poltergeist. See [the definitions here](https://github.com/mattheworiordan/capybara-screenshot/blob/master/lib/capybara-screenshot.rb). The Rack::Test driver, Rails' default, does not allow 118 | rendering, so it has a driver definition as a noop. 119 | 120 | Capybara-webkit defaults to a screenshot size of 1000px by 10px. To specify a custom size, use the following option: 121 | 122 | ```ruby 123 | Capybara::Screenshot.webkit_options = { width: 1024, height: 768 } 124 | ``` 125 | 126 | If a driver is not found the default rendering will be used. If this doesn't work with your driver, then you can 127 | add another driver configuration like so 128 | 129 | ```ruby 130 | # The driver name should match the Capybara driver config name. 131 | Capybara::Screenshot.register_driver(:exotic_browser_driver) do |driver, path| 132 | driver.super_dooper_render(path) 133 | end 134 | ``` 135 | 136 | If your driver is based on existing browser driver, like Firefox, instead of `.super_dooper_render` do `driver.browser.save_screenshot path`. 137 | 138 | 139 | Custom screenshot filename 140 | -------------------------- 141 | 142 | If you want to control the screenshot filename for a specific test library, to inject the test name into it for example, 143 | you can override how the basename is generated for the file like so 144 | 145 | ```ruby 146 | Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example| 147 | "screenshot_#{example.description.gsub(' ', '-').gsub(/^.*\/spec\//,'')}" 148 | end 149 | ``` 150 | 151 | By default capybara-screenshot will append a timestamp to the basename. If you want to disable this behavior set the following option: 152 | 153 | ```ruby 154 | Capybara::Screenshot.append_timestamp = false 155 | ``` 156 | 157 | 158 | Custom screenshot directory 159 | -------------------------- 160 | By default screenshots are saved into `$APPLICATION_ROOT/tmp/capybara`. If you want to customize the location, override the file path as: 161 | 162 | ```ruby 163 | Capybara.save_and_open_page_path = "/file/path" 164 | ``` 165 | 166 | 167 | Pruning old screenshots automatically 168 | -------------------------- 169 | By default screenshots are saved indefinitely, if you want them to be automatically pruned on a new failure, then you can specify one of the following prune strategies as follows: 170 | 171 | ```ruby 172 | # Keep only the screenshots generated from the last failing test suite 173 | Capybara::Screenshot.prune_strategy = :keep_last_run 174 | 175 | # Keep up to the number of screenshots specified in the hash 176 | Capybara::Screenshot.prune_strategy = { keep: 20 } 177 | ``` 178 | 179 | 180 | Information about screenshots in RSpec output 181 | --------------------------------------------- 182 | 183 | By default, capybara-screenshot extend RSpec’s formatters to include a link to the screenshot and/or saved html page for each failed spec. If you want to disable this feature completely (eg. to avoid problems with CI tools), use: 184 | 185 | ```ruby 186 | Capybara::Screenshot::RSpec.add_link_to_screenshot_for_failed_examples = false 187 | ``` 188 | 189 | It’s also possible to directly embed the screenshot image in the output if you’re using RSpec’s HtmlFormatter: 190 | 191 | ```ruby 192 | Capybara::Screenshot::RSpec::REPORTERS["RSpec::Core::Formatters::HtmlFormatter"] = Capybara::Screenshot::RSpec::HtmlEmbedReporter 193 | ``` 194 | 195 | If you want to further customize the information added to RSpec’s output, just implement your own reporter class and customize `Capybara::Screenshot::RSpec::REPORTERS` accordingly. See [rspec.rb](lib/capybara-screenshot/rspec.rb) for more info. 196 | 197 | 198 | Common problems 199 | --------------- 200 | 201 | If you have recently upgraded from v0.2, or you find that screen shots are not automatically being generated, then it's most likely you have not included the necessary `require` statement for your testing framework described above. As of version 0.3, without the explicit require, Capybara-Screenshot will not automatically take screen shots. Please re-read the installation instructions above. 202 | 203 | Also make sure that you're not calling `Capybara.reset_sessions!` before the screenshot hook runs. For RSpec you want to make sure that you're using `append_after` instead of `after`, for instance: 204 | 205 | ```ruby 206 | config.append_after(:each) do 207 | Capybara.reset_sessions! 208 | end 209 | ``` 210 | 211 | [Raise an issue on the Capybara-Screenshot issue tracker](https://github.com/mattheworiordan/capybara-screenshot/issues) if you are still having problems. 212 | 213 | Repository & Contributing to this Gem 214 | ------------------------------------- 215 | 216 | #### Bugs 217 | 218 | Please raise an issue at [https://github.com/mattheworiordan/capybara-screenshot/issues](https://github.com/mattheworiordan/capybara-screenshot/issues) and ensure you provide sufficient detail to replicate the problem. 219 | 220 | #### Contributions 221 | 222 | Contributions are welcome. Please fork this gem, and submit a pull request. New features must include test coverage and must pass on all versions of the testing frameworks supported. Run `appraisal "bundle exec rspec && bundle exec cucumber"` locally to test your changes against all versions of testing framework gems supported. 223 | 224 | #### Rubygems 225 | 226 | The gem details on RubyGems.org can be found at [https://rubygems.org/gems/capybara-screenshot](https://rubygems.org/gems/capybara-screenshot) 227 | 228 | About 229 | ----- 230 | 231 | This gem was written by **Matthew O'Riordan**, with contributions from [many kind people](https://github.com/mattheworiordan/capybara-screenshot/network/members). 232 | 233 | - [http://mattheworiordan.com](http://mattheworiordan.com) 234 | - [@mattheworiordan](http://twitter.com/#!/mattheworiordan) 235 | - [Linked In](http://www.linkedin.com/in/lemon) 236 | 237 | License 238 | ------- 239 | 240 | Copyright © 2014 Matthew O'Riordan, inc. It is free software, and may be redistributed under the terms specified in the LICENSE file. 241 | -------------------------------------------------------------------------------- /spec/unit/saver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Screenshot::Saver do 4 | before(:all) do 5 | @original_drivers = Capybara::Screenshot.registered_drivers 6 | Capybara::Screenshot.registered_drivers[:default] = lambda {|driver, path| driver.render(path) } 7 | end 8 | 9 | after(:all) do 10 | Capybara::Screenshot.registered_drivers = @original_drivers 11 | end 12 | 13 | before do 14 | allow(Capybara::Screenshot).to receive(:capybara_root).and_return(capybara_root) 15 | Timecop.freeze(Time.local(2012, 6, 7, 8, 9, 10, 0)) 16 | end 17 | 18 | let(:capybara_root) { '/tmp' } 19 | let(:timestamp) { '2012-06-07-08-09-10.000' } 20 | let(:file_basename) { "screenshot_#{timestamp}" } 21 | let(:screenshot_path) { "#{capybara_root}/#{file_basename}.png" } 22 | 23 | let(:driver_mock) { double('Capybara driver').as_null_object } 24 | let(:page_mock) { double('Capybara session page', :body => 'body', :driver => driver_mock).as_null_object } 25 | let(:capybara_mock) { 26 | double(Capybara).as_null_object.tap do |m| 27 | allow(m).to receive(:current_driver).and_return(:default) 28 | allow(m).to receive(:current_path).and_return('/') 29 | end 30 | } 31 | 32 | let(:saver) { Capybara::Screenshot::Saver.new(capybara_mock, page_mock) } 33 | 34 | context 'html filename with Capybara Version 1' do 35 | before do 36 | stub_const("Capybara::VERSION", '1') 37 | end 38 | 39 | it 'has a default format of "screenshot_Y-M-D-H-M-S.ms.html"' do 40 | expect(capybara_mock).to receive(:save_page).with('body', File.join(capybara_root, "#{file_basename}.html")) 41 | 42 | saver.save 43 | end 44 | 45 | it 'uses name argument as prefix' do 46 | saver = Capybara::Screenshot::Saver.new(capybara_mock, page_mock, true, 'custom-prefix') 47 | 48 | expect(capybara_mock).to receive(:save_page).with('body', File.join(capybara_root, "custom-prefix_#{timestamp}.html")) 49 | 50 | saver.save 51 | end 52 | end 53 | 54 | context 'html filename with Capybara Version 2' do 55 | before do 56 | stub_const("Capybara::VERSION", '2') 57 | end 58 | 59 | it 'has a default format of "screenshot_Y-M-D-H-M-S.ms.html"' do 60 | expect(capybara_mock).to receive(:save_page).with(File.join(capybara_root, "#{file_basename}.html")) 61 | 62 | saver.save 63 | end 64 | 65 | it 'uses name argument as prefix' do 66 | saver = Capybara::Screenshot::Saver.new(capybara_mock, page_mock, true, 'custom-prefix') 67 | 68 | expect(capybara_mock).to receive(:save_page).with(File.join(capybara_root, "custom-prefix_#{timestamp}.html")) 69 | 70 | saver.save 71 | end 72 | end 73 | 74 | context 'screenshot image path' do 75 | it 'is in capybara root output' do 76 | expect(driver_mock).to receive(:render).with(/^#{capybara_root}\//) 77 | 78 | saver.save 79 | end 80 | 81 | it 'has a default filename format of "screenshot_Y-M-D-H-M-S.ms.png"' do 82 | expect(driver_mock).to receive(:render).with(/#{file_basename}\.png$/) 83 | 84 | saver.save 85 | end 86 | 87 | it "does not append timestamp if append_timestamp is false " do 88 | default_config = Capybara::Screenshot.append_timestamp 89 | Capybara::Screenshot.append_timestamp = false 90 | expect(driver_mock).to receive(:render).with(/screenshot.png$/) 91 | 92 | saver.save 93 | Capybara::Screenshot.append_timestamp = default_config 94 | end 95 | 96 | it 'uses filename prefix argument as basename prefix' do 97 | saver = Capybara::Screenshot::Saver.new(capybara_mock, page_mock, true, 'custom-prefix') 98 | expect(driver_mock).to receive(:render).with(/#{capybara_root}\/custom-prefix_#{timestamp}\.png$/) 99 | 100 | saver.save 101 | end 102 | end 103 | 104 | it 'does not save html if false passed as html argument' do 105 | saver = Capybara::Screenshot::Saver.new(capybara_mock, page_mock, false) 106 | expect(capybara_mock).to_not receive(:save_page) 107 | 108 | saver.save 109 | expect(saver).to_not be_html_saved 110 | end 111 | 112 | it 'does not save if current_path is empty' do 113 | allow(capybara_mock).to receive(:current_path).and_return(nil) 114 | expect(capybara_mock).to_not receive(:save_page) 115 | expect(driver_mock).to_not receive(:render) 116 | 117 | saver.save 118 | expect(saver).to_not be_screenshot_saved 119 | expect(saver).to_not be_html_saved 120 | end 121 | 122 | describe '#output_screenshot_path' do 123 | let(:saver) { Capybara::Screenshot::Saver.new(capybara_mock, page_mock) } 124 | 125 | before do 126 | allow(saver).to receive(:html_path) { 'page.html' } 127 | allow(saver).to receive(:screenshot_path) { 'screenshot.png' } 128 | end 129 | 130 | it 'outputs the path for the HTML screenshot' do 131 | allow(saver).to receive(:html_saved?).and_return(true) 132 | expect(saver).to receive(:output).with("HTML screenshot: page.html") 133 | saver.output_screenshot_path 134 | end 135 | 136 | it 'outputs the path for the Image screenshot' do 137 | allow(saver).to receive(:screenshot_saved?).and_return(true) 138 | expect(saver).to receive(:output).with("Image screenshot: screenshot.png") 139 | saver.output_screenshot_path 140 | end 141 | end 142 | 143 | describe "with selenium driver" do 144 | before do 145 | allow(capybara_mock).to receive(:current_driver).and_return(:selenium) 146 | end 147 | 148 | it 'saves via browser' do 149 | browser_mock = double('browser') 150 | expect(driver_mock).to receive(:browser).and_return(browser_mock) 151 | expect(browser_mock).to receive(:save_screenshot).with(screenshot_path) 152 | 153 | saver.save 154 | expect(saver).to be_screenshot_saved 155 | end 156 | end 157 | 158 | describe "with poltergeist driver" do 159 | before do 160 | allow(capybara_mock).to receive(:current_driver).and_return(:poltergeist) 161 | end 162 | 163 | it 'saves driver render with :full => true' do 164 | expect(driver_mock).to receive(:render).with(screenshot_path, {:full => true}) 165 | 166 | saver.save 167 | expect(saver).to be_screenshot_saved 168 | end 169 | end 170 | 171 | describe "with poltergeist_billy driver" do 172 | before do 173 | allow(capybara_mock).to receive(:current_driver).and_return(:poltergeist_billy) 174 | end 175 | 176 | it 'saves driver render with :full => true' do 177 | expect(driver_mock).to receive(:render).with(screenshot_path, {:full => true}) 178 | 179 | saver.save 180 | expect(saver).to be_screenshot_saved 181 | end 182 | end 183 | 184 | describe "with webkit driver" do 185 | before do 186 | allow(capybara_mock).to receive(:current_driver).and_return(:webkit) 187 | end 188 | 189 | context 'has render method' do 190 | before do 191 | allow(driver_mock).to receive(:respond_to?).with(:'save_screenshot').and_return(false) 192 | end 193 | 194 | it 'saves driver render' do 195 | expect(driver_mock).to receive(:render).with(screenshot_path) 196 | 197 | saver.save 198 | expect(saver).to be_screenshot_saved 199 | end 200 | end 201 | 202 | context 'has save_screenshot method' do 203 | let(:webkit_options){ {width: 800, height: 600} } 204 | 205 | before do 206 | allow(driver_mock).to receive(:respond_to?).with(:'save_screenshot').and_return(true) 207 | end 208 | 209 | it 'saves driver render' do 210 | expect(driver_mock).to receive(:save_screenshot).with(screenshot_path, {}) 211 | 212 | saver.save 213 | expect(saver).to be_screenshot_saved 214 | end 215 | 216 | it 'passes webkit_options to driver' do 217 | allow(Capybara::Screenshot).to receive(:webkit_options).and_return( webkit_options ) 218 | expect(driver_mock).to receive(:save_screenshot).with(screenshot_path, webkit_options) 219 | 220 | saver.save 221 | expect(saver).to be_screenshot_saved 222 | end 223 | end 224 | end 225 | 226 | describe "with webkit debug driver" do 227 | before do 228 | allow(capybara_mock).to receive(:current_driver).and_return(:webkit_debug) 229 | end 230 | 231 | context 'has render method' do 232 | before do 233 | allow(driver_mock).to receive(:respond_to?).with(:'save_screenshot').and_return(false) 234 | end 235 | 236 | it 'saves driver render' do 237 | expect(driver_mock).to receive(:render).with(screenshot_path) 238 | 239 | saver.save 240 | expect(saver).to be_screenshot_saved 241 | end 242 | end 243 | 244 | context 'has save_screenshot method' do 245 | let(:webkit_options){ {width: 800, height: 600} } 246 | 247 | before do 248 | allow(driver_mock).to receive(:respond_to?).with(:'save_screenshot').and_return(true) 249 | end 250 | 251 | it 'saves driver render' do 252 | expect(driver_mock).to receive(:save_screenshot).with(screenshot_path, {}) 253 | 254 | saver.save 255 | expect(saver).to be_screenshot_saved 256 | end 257 | 258 | it 'passes webkit_options to driver' do 259 | allow(Capybara::Screenshot).to receive(:webkit_options).and_return( webkit_options ) 260 | expect(driver_mock).to receive(:save_screenshot).with(screenshot_path, webkit_options) 261 | 262 | saver.save 263 | expect(saver).to be_screenshot_saved 264 | end 265 | end 266 | end 267 | 268 | describe "with unknown driver" do 269 | before do 270 | allow(capybara_mock).to receive(:current_driver).and_return(:unknown) 271 | allow(saver).to receive(:warn).and_return(nil) 272 | end 273 | 274 | it 'saves driver render' do 275 | expect(driver_mock).to receive(:render).with(screenshot_path) 276 | 277 | saver.save 278 | expect(saver).to be_screenshot_saved 279 | end 280 | 281 | it 'outputs warning about unknown results' do 282 | # Not pure mock testing 283 | expect(saver).to receive(:warn).with(/screenshot driver for 'unknown'.*unknown results/).and_return(nil) 284 | 285 | saver.save 286 | expect(saver).to be_screenshot_saved 287 | end 288 | 289 | describe "with rack_test driver" do 290 | before do 291 | allow(capybara_mock).to receive(:current_driver).and_return(:rack_test) 292 | end 293 | 294 | it 'indicates that a screenshot could not be saved' do 295 | saver.save 296 | expect(saver).to_not be_screenshot_saved 297 | end 298 | end 299 | 300 | describe "with mechanize driver" do 301 | before do 302 | allow(capybara_mock).to receive(:current_driver).and_return(:mechanize) 303 | end 304 | 305 | it 'indicates that a screenshot could not be saved' do 306 | saver.save 307 | expect(saver).to_not be_screenshot_saved 308 | end 309 | end 310 | end 311 | end 312 | --------------------------------------------------------------------------------