How to test CSV file download in Capybara and RSpec? How to test CSV file download in Capybara and RSpec? selenium selenium

How to test CSV file download in Capybara and RSpec?


Adapted from CollectiveIdea and another source.

Works on OSX. Firefox 34.0.5

Spec:

  describe 'Download CSV' do    let( :submission_email ){ 'foo@example.com' }    let( :email_csv ){ "id,email,created_at\n1,#{ submission_email }," }    specify do      visit '/emails'      expect( page ).to have_content 'Email Submissions'      click_on 'Download CSV'      expect( DownloadHelpers::download_content ).to include email_csv    end  end

Spec helper:

require 'shared/download_helper'Capybara.register_driver :selenium do |app|  profile = Selenium::WebDriver::Firefox::Profile.new  profile['browser.download.dir'] = DownloadHelpers::PATH.to_s  profile['browser.download.folderList'] = 2  # Suppress "open with" dialog  profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv'  Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile)endconfig.before( :each ) do    DownloadHelpers::clear_downloadsend

shared/download_helper.rb:

module DownloadHelpers  TIMEOUT = 1  PATH    = Rails.root.join("tmp/downloads")  extend self  def downloads    Dir[PATH.join("*")]  end  def download    downloads.first  end  def download_content    wait_for_download    File.read(download)  end  def wait_for_download    Timeout.timeout(TIMEOUT) do      sleep 0.1 until downloaded?    end  end  def downloaded?    !downloading? && downloads.any?  end  def downloading?    downloads.grep(/\.part$/).any?  end  def clear_downloads    FileUtils.rm_f(downloads)  endend


I found another way to do this, if you're using the rack_test driver (no javascript/no browser):

DOWNLOAD_CACHE_PATH = Rails.root.join("tmp/downloaded_file").to_ssetup do  File.delete(DOWNLOAD_CACHE_PATH)endtest "download file" do  visit download_file_path  # simulate file download  File.write(DOWNLOAD_CACHE_PATH, page.body)  csv = CSV.open(DOWNLOAD_CACHE_PATH)  # assert something on the csv dataend


I have tried to implement something similar and spend lots of hours. Finally I have some solution, maybe it fit for you as well.

Gemfile:

#source 'https://rubygems.org'gem 'rails',                   '4.2.2'gem 'bcrypt',                  '3.1.7'gem 'bootstrap-sass',          '3.2.0.0'gem 'faker',                   '1.4.2'gem 'carrierwave',             '0.10.0'gem 'mini_magick',             '3.8.0'gem 'fog',                     '1.36.0'gem 'will_paginate',           '3.0.7'gem 'bootstrap-will_paginate', '0.0.10'gem 'sass-rails',              '5.0.2'gem 'uglifier',                '2.5.3'gem 'coffee-rails',            '4.1.0'gem 'jquery-rails',            '4.0.3'gem 'turbolinks',              '2.3.0'gem 'jbuilder',                '2.2.3'gem 'sdoc',                    '0.4.0', group: :docgem 'rename'gem 'sprockets',                             '3.6.3'gem 'responders',           '~> 2.0' gem 'inherited_resources'group :development, :test do  gem 'sqlite3',     '1.3.9'  gem 'byebug',      '3.4.0'  gem 'web-console', '2.0.0.beta3'  gem 'spring',      '1.1.3'endgroup :test do  gem 'minitest-reporters', '1.0.5'  gem 'mini_backtrace',     '0.1.3'  gem 'guard-minitest',     '2.3.1'    gem 'capybara',           '2.8.1'    gem 'rspec',              '3.5.0'    gem 'rspec-rails',     '~> 3.4'    gem 'cucumber-rails', :require => false    gem 'shoulda-matchers', '~> 3.0', require: false    gem 'database_cleaner'    gem 'factory_girl_rails', '~> 4.5.0'end

spec/rails_helper.rb

ENV['RAILS_ENV'] ||= 'test'require File.expand_path('../../config/environment', __FILE__)abort("The Rails environment is running in production mode!") if Rails.env.production?require 'spec_helper'require 'rspec/rails'require 'shoulda/matchers'Shoulda::Matchers.configure do |config|  config.integrate do |with|    with.test_framework :rspec    with.library :rails  endendconfig.use_transactional_fixtures = falseActiveRecord::Migration.maintain_test_schema!RSpec.configure do |config|  config.fixture_path = "#{::Rails.root}/spec/fixtures"  config.use_transactional_fixtures = true  config.infer_spec_type_from_file_location!  config.filter_rails_from_backtrace!end

spec/spec_helper.rb

ENV["RAILS_ENV"] ||= 'test'require File.expand_path("../../config/environment", __FILE__)require 'rspec/rails'require 'capybara/rspec'require 'capybara/rails'require 'download_helper'Capybara.register_driver :selenium do |app|  profile = Selenium::WebDriver::Firefox::Profile.new  profile['browser.download.dir'] = DownloadHelpers::PATH.to_s  profile['browser.download.folderList'] = 2  profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv'  Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile)endRSpec.configure do |config|  config.expect_with :rspec do |expectations|    expectations.include_chain_clauses_in_custom_matcher_descriptions = true  end  config.mock_with :rspec do |mocks|    mocks.verify_partial_doubles = true  end  config.shared_context_metadata_behavior = :apply_to_host_groups    config.include Capybara::DSL=begin  config.filter_run_when_matching :focus  config.example_status_persistence_file_path = "spec/examples.txt"  config.disable_monkey_patching!  if config.files_to_run.one?    config.default_formatter = 'doc'  end  config.profile_examples = 10  config.order = :random  Kernel.srand config.seed=endend

test/test_helper.rb

ENV['RAILS_ENV'] ||= 'test'require File.expand_path('../../config/environment', __FILE__)require 'rails/test_help'require 'capybara/rails'class ActiveSupport::TestCase  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.  fixtures :all    include ApplicationHelper    def is_logged_in?        !session[:user_id].nil?    end    # Logs in a test user.    def log_in_as(user, options = {})        password = options[:password] || 'password'        remember_me = options[:remember_me] || '1'        if integration_test?            post login_path, session: { email:user.email, password: password, remember_me: remember_me }        else            session[:user_id] = user.id        end    end    private        # Returns true inside an integration test.        def integration_test?            defined?(post_via_redirect)        endendclass ActionDispatch::IntegrationTest  # Make the Capybara DSL available in all integration tests  include Capybara::DSL  # Reset sessions and driver between tests  # Use super wherever this method is redefined in your individual test classes  def teardown    Capybara.reset_sessions!    Capybara.use_default_driver  endend

spec/download_helper.rb

module DownloadHelpers  TIMEOUT = 1  PATH    = Rails.root.join("tmp/downloads")  extend self  def downloads    Dir[PATH.join("*")]  end  def download    downloads.first  end  def download_content    wait_for_download    File.read(download)  end  def wait_for_download    Timeout.timeout(TIMEOUT) do      sleep 0.1 until downloaded?    end  end  def downloaded?    !downloading? && downloads.any?  end  def downloading?    downloads.grep(/\.part$/).any?  end  def clear_downloads    FileUtils.rm_f(downloads)  endend

spec/mpodels/spec.rb

  describe 'Download file' do    specify do      visit '/createfile'      click_on 'create file'            page.response_headers['Content-Type'].should == "text/csv"            header = page.response_headers['Content-Disposition']            header.should match /^attachment/            header.should match /filename=\"temp.csv\"$/       end    end