简体   繁体   中英

How to test CSV file download in Capybara and RSpec?

The following is in the controller:

respond_to do |format|
  format.csv  { send_data as_csv, type:'text/csv' }
end

In spec:

click_link 'Download CSV'
page.driver.browser.switch_to.alert.accept

expect( page ).to have_content csv_data

But this doesn't work:

Failure/Error: page.driver.browser.switch_to.alert.accept
Selenium::WebDriver::Error::NoAlertPresentError: No alert is present

I see the Save File dialog box display, but apparently it is not an "alert" dialog.

How to click OK and get Capybara to see the data?

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)
end

config.before( :each ) do
    DownloadHelpers::clear_downloads
end

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)
  end
end

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_s

setup do
  File.delete(DOWNLOAD_CACHE_PATH)
end

test "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 data
end

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: :doc
gem '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'
end

group :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
  end
end

config.use_transactional_fixtures = false

ActiveRecord::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)
end


RSpec.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
=end
end

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)
        end

end

class 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
  end
end

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)
  end
end

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

根据浏览器的不同,您需要该浏览器的配置文件,我建议禁用要求在该配置文件中保存的属性。

@b-seven thanks a lot!

I made my own version of download_helper.rb maybe it will be useful for someone.

spec/support/helpers/download_helpers.rb

module DownloadHelpers
  DOWNLOAD_DIR = 'tmp/capybara_downloads'
  DOWNLOAD_PATH = ::Rails.root.join(DOWNLOAD_DIR)

  def clear_downloads
    FileUtils.rm_f(downloads)
  end

  def downloads
    Dir[DOWNLOAD_PATH.join('*')]
  end

  def download_file(filename)
    wait_for_download(filename)
    File.read(::Rails.root.join(DOWNLOAD_DIR, filename))
  end

  def wait_for_download(filename)
    Timeout.timeout(Capybara.default_max_wait_time,
                    Timeout::Error,
                    "File download timeout! File: #{filename}") do
      sleep 0.1 until File.exist?(::Rails.root.join(DOWNLOAD_DIR, filename))
  end
end

end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM