使用capybara / selenium / firefox在rails功能spec上测试下载的执行已过期

时间:2016-06-21 19:26:10

标签: ruby-on-rails selenium rspec capybara

更新:刻录太多小时后,决定选择简单的方法并使用机架测试。这可以直接使用,至少可以验证内容类型是pdf。

scenario 'document can be downloaded' do
  visit my_documents_path

  click_on 'Download'
  expect(page.response_headers['Content-Type']).to eq "application/pdf"
end

我正在尝试编写一个功能规范来测试我下载的pdf的内容,并且我已按照此处的说明进行操作:https://stackoverflow.com/a/29544674/2464546

我正在运行Ruby 2.3,Rails 4.2.5.2,RSpec 3.4,Capybara 2.7。

在上面的SO链接的代码中,有一行可以抑制Firefox Save弹出窗口,并将其从pdf调整为csv

# Suppress "open with" dialog
profile['browser.helperApps.neverAsk.saveToDisk'] = 'application/pdf'

这似乎不起作用,因为对话框仍然会弹出,然后我的测试错误了。

我的功能规格:

scenario 'document can be downloaded', js: true do
  visit my_documents_path

  click_on 'Download'

  expect(DownloadHelpers::download_content).to have_content('Thingy')
end

每次运行此规范时,都会错误地说execution expired每次都有不同的错误。例如,最新的一个具有以下内容:

1) My documents home page document can be downloaded
 Failure/Error: Dir[PATH.join("*")]

 Timeout::Error:
   execution expired
 # ./spec/features/shared/download_helper.rb:8:in `downloads'
 # ./spec/features/shared/download_helper.rb:31:in `downloading?'
 # ./spec/features/shared/download_helper.rb:27:in `downloaded?'
 # ./spec/features/shared/download_helper.rb:22:in `block in wait_for_download'
 # ./spec/features/shared/download_helper.rb:21:in `wait_for_download'
 # ./spec/features/shared/download_helper.rb:16:in `download_content'
 # ./spec/features/my_documents/index_spec.rb:42:in `block (2 levels) in <top (required)>'

DownloadHelpers模块中,我已将sleep从0.1更改为1到3,偶尔我会收到以下错误,睡眠计数会更改w /无论我将它设置为:

 Failure/Error: sleep 3 until downloaded?

 Timeout::Error:
   execution expired
 # ./spec/features/shared/download_helper.rb:22:in `sleep'
 # ./spec/features/shared/download_helper.rb:22:in `block in wait_for_download'
 # ./spec/features/shared/download_helper.rb:21:in `wait_for_download'
 # ./spec/features/shared/download_helper.rb:16:in `download_content'
 # ./spec/features/my_documents/index_spec.rb:42:in `block (2 levels) in <top (required)>'

我也改变了TIMEOUT计数而没有改变上面的失败结果。最终,对话框仍会弹出,不会消失/看起来不会下载文件。

我的控制器位于“下载”按钮后面:

def download
  pdf = @document.pdf_file_name

  send_file pdf
end

创建的文档并不大,因为它只是一个名称和几行,所以我不怀疑它需要超过几秒钟来下载和读取文件。

为什么执行过期?如何让Capybara / Feature规范下载文件,以便我的期望通过?

使用棱角分明的document.haml视图我并不认为这很重要。

      %li.col-xs-6
        %li.col-xs-6= link_to 'Download', download_document_path(id: document.id), "ng-click" => "logAnalytics('#{document.document_template_id}', 'download')"

此外,我的功能规格顶部有require 'rails_helper',而我的rails_helper包含来自SO帖子的代码(完整,带相关位):

require 'features/shared/download_helper'

RSpec.configure do |config|

 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'] = 'application/pdf'
   Capybara::Selenium::Driver.new(app, browser: :firefox, profile: profile)
 end

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

4 个答案:

答案 0 :(得分:0)

我认为以下任何一项都有效:

page.driver.browser.switch_to.alert.accept
# or
page.driver.browser.switch_to.alert.dismiss
# or
page.driver.browser.switch_to.alert.text

答案 1 :(得分:0)

检查你的pdf下载返回的mime类型,可能性实际上并不是'application / pdf',并且最有可能被返回为'application / octet-stream'。您应该将返回类型修正为正确,但在短期内,您可以尝试更改为profile['browser.helperApps.neverAsk.saveToDisk'] = 'application/pdf,application/octet-stream'

答案 2 :(得分:0)

我试图实现类似的东西,花了很多时间。最后我有一些解决方案,也许它也适合你。

的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

规格/ 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_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_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

规格/ 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

规格/ 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

答案 3 :(得分:0)

我也遇到了'执行过期'错误。我按照上面的所有步骤,但错误仍然存​​在。事实证明,文件系统( PPG Max Product Sales 1: P1 130 2: P2 100 )中缺少下载目录。 创建该消息使消息消失并且测试通过。