简体   繁体   中英

Execution expired on rails feature spec testing a download using capybara/selenium/firefox

Update : After burning too many hours, decided to opt for the easy way out and use rack-test. This works straight out of the box and at least verifies that the content-type is a 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

I am trying to write a feature spec to test the contents of my downloaded pdf, and I have followed the directions found here: https://stackoverflow.com/a/29544674/2464546

I am running Ruby 2.3, Rails 4.2.5.2, RSpec 3.4, Capybara 2.7.

In the code from the SO link above, there's a line that supposedly suppresses the Firefox Save popup, with this, adjusted to pdf from csv :

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

This doesn't seem to work because the dialog box still pops up, and then my test errors out.

My feature spec:

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

  click_on 'Download'

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

Every time I run this spec, it errors out saying execution expired with a different error each time. For example the latest one had the following:

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

In the DownloadHelpers module, I've changed the sleep from 0.1 to 1 to 3, and occasionally I'll get the following error, with the sleep count changing w/ whatever I've set it to:

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

I have also changed the TIMEOUT count with no change in failure result from the above. Ultimately, the dialog box still pops up and does not go away/doesn't look like it downloads the file.

My controller behind the Download button:

def download
  pdf = @document.pdf_file_name

  send_file pdf
end

The created document is not large, as all it has is a name and a few lines, so I don't suspect it'd need more than a few seconds to download and read the file.

Why is the execution expiring? How do I get Capybara/Feature spec to download the file so my expectation passes?

The view document.haml using angular tho I don't think that matters.

      %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')"

Also, my feature spec has require 'rails_helper' at the top, and my rails_helper has the code from the SO post (in full, w/ relevant bits):

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

I think any of the following will work:

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

Check the mime type your pdf download is returning, odds are it's not actually 'application/pdf' and is most likely being returned as 'application/octet-stream'. You should fix the return type to be correct, but in the short term you can try changing to profile['browser.helperApps.neverAsk.saveToDisk'] = 'application/pdf,application/octet-stream'

I got the 'execution expired' error too. I followed all the steps above, but the error remained. It turned out to be the download directory was missing in the filesystem ( DownloadHelpers::PATH ). Creating that one made the message disappear and the test pass.

I was experiencing this same error. The solution for me was to increase the timeout. So where ever you are doing the sleeping until timeout, increase the timeout. Of course it depends on your business logic but 5 seconds works for me consistently whereas 2 seconds failed consistently.

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

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