Capybara: Testing file downloads

Updated . Posted . Visible to the public. Repeats.

Download buttons can be difficult to test, especially with Selenium. Depending on browser, user settings and response headers, one of three things can happen:

  • The browser shows a "Save as..." dialog. Since it is a modal dialog, we can no longer communicate with the browser through Selenium.
  • The browser automatically downloads the file without prompting the user. For the test it looks like nothing has happened.
  • The browser shows a binary document in its own window, like a PDF. Capybara/Selenium freaks out because there is no HTML document on the other side.

Below you can find multiple methods how to test file downloads with Selenium and Capybara despite these issues. Both have different advantages and drawbacks.

Method 1: Inspecting the file without navigation

There are ways to download and inspect a file without navigating away from the current page.

See Capybara: Most okayest helper to download and inspect files.

Method 2: Use a request spec

Only test that the download link's [href] attribute points to a particular route.

Then test that route with a request spec. Or, if the uploaded file lives in public/, test that the linked file exists.

Method 3: Test without Selenium

If you can get away with it, run that one test with Rack::Test instead of Selenium.

With a Rack::Test driver Capybara allows access to the response headers via page.driver.response.headers and page.driver.response.body.

Method 4: Looking into the download folder of the test browser

Warning

The code examples for this method need to be updated.

Pros:

  • Resembles most closely what happens in the real world.
  • You can inspect the file with Ruby code in any way you want.

Cons:

  • Does not work well with CI setups (because the browser might not be on the same container as cucumber)
  • Solution is specific to Chrome.

Note: The step below will override a spreewald step Show archive.org snapshot and allows to use Regex for the filename. You might want to rename this step within you project in case you are using Spreewald Show archive.org snapshot .

features/step_definitions/spreewald_overrides_steps.rb

Then(/^I should get a download with filename "(.*?)"$/) do |filename|
  if Capybara.current_driver == :selenium
    patiently do
      expect(DownloadHelpers.last_download&.basename&.to_s).to match(filename)
    end
  else
    expect(response_headers['Content-Disposition']).to match(/filename=\"#{filename}\"/)
  end
end

features/support/download_helpers.rb

# This is an adapted approach from
# https://collectiveidea.com/blog/archives/2012/01/27/testing-file-downloads-with-capybara-and-chromedriver
module DownloadHelpers
  TIMEOUT = 10

  module_function

  def download_path
    download_path = Rails.root.join("tmp/test_downloads#{ENV['TEST_ENV_NUMBER']}")
    FileUtils.mkdir_p(download_path)

    download_path
  end

  def clear_downloads
    FileUtils.rm_r(download_path) if File.exist?(download_path)
  end

  def last_download
    downloads = Pathname.new(download_path)

    Timeout.timeout(TIMEOUT) do
      sleep 0.1 until downloads.glob('*.crdownload').blank?
    end

    downloads.children.sort_by(&File.method(:ctime)).last
  end

end

Before do
  DownloadHelpers.clear_downloads
end

After do
  DownloadHelpers.clear_downloads
end

features/support/selenium.rb

Capybara.register_driver :selenium do |app|
  options = Selenium::WebDriver::Chrome::Options.new

  options.add_argument('--mute-audio')
  options.add_argument('--disable-infobars')
  options.add_preference('credentials_enable_service', false)
  options.add_preference('profile.password_manager_enabled', false)
  options.add_preference(:download, default_directory: DownloadHelpers.download_path.to_s)

  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Before do
  Capybara.current_driver = :rack_test
end

Before('@javascript') do
  Capybara.current_driver = :selenium
end
Last edit
Daniel Straßner
Keywords
file, attachment, send_file, send_data, test, cucumber, integration, e2e, end-to-end
License
Source code in this card is licensed under the MIT License.
Posted by Emanuel to makandra dev (2019-05-21 09:57)