【问题标题】:How to test CSV file download in Capybara and RSpec?如何在 Capybara 和 RSpec 中测试 CSV 文件下载?
【发布时间】:2015-06-01 06:59:20
【问题描述】:

控制器中有以下内容:

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

在规范中:

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

expect( page ).to have_content csv_data

但这不起作用:

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

我看到“保存文件”对话框显示,但显然它不是“警告”对话框。

如何点击确定让Capybara查看数据?

【问题讨论】:

    标签: ruby-on-rails ruby selenium rspec capybara


    【解决方案1】:

    改编自CollectiveIdea 和其他来源。

    适用于 OSX。火狐 34.0.5

    规格:

      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
    

    规范助手:

    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
    

    【讨论】:

    • 你为什么使用:let( :submission_email ){ 'foo@example.com' } let( :email_csv ){ "id,email,created_at\n1,#{ submit_email }," }
    • 我得到:无法加载这样的文件 -- shared/download_helper.rb (LoadError) 当 spec_helper 运行时需要 'shared/download_helper' 行。 download_helper.rb 在正确的位置。
    • @ChaosPredictor 将该文件放入 spec/shared/download_helper.rb
    • 由于现在不推荐将配置文件直接传递给驱动程序,因此这里有一种使用选项设置下载位置的方法:stackoverflow.com/questions/37391879/…
    【解决方案2】:

    如果您使用rack_test 驱动程序(无javascript/无浏览器),我找到了另一种方法:

    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
    

    【讨论】:

    • 我没有其他建议,但这是一个简单且有效的解决方案!
    • 听起来这个答案可能会通过使用TempFile来改进?
    【解决方案3】:

    我尝试实现类似的东西并花费大量时间。最后我有一些解决方案,也许它也适合你。

    宝石文件:

    #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
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        @b-seven 非常感谢!

        我制作了自己的 download_helper.rb 版本,也许它对某人有用。

        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
        

        结束

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-01-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-08-26
          • 2022-01-08
          • 1970-01-01
          相关资源
          最近更新 更多