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