Posted 7 months ago. Visible to the public.

Cucumber: Testing file downloads with Selenium

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

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

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 and allows to use Regex for the filename. You might want to rename this step within you project in case you are using Spreewald.

features/step_definitions/spreewald_overrides_steps.rb

Copy
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

Copy
# 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

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

Method 2: Inspecting the file using JavaScript

Pros:

  • Works with CI.
  • Browser-agnostic.

Cons:

  • You don't have access to the physical file in Ruby.
  • Works only with links, not with downloads triggered by JavaScript.

The idea is to request the file via the browser's fetch API, check that is downloads correctly, and return the file name (and possibly the file content) to cucumber.

Use the following step definition:

Copy
When('I download {string}') do |link| if Capybara.current_driver == :selenium href = find_link(link)['href'] result = page.evaluate_async_script(<<~JS, href) let [url, done] = arguments fetch(url).then((response) => { if (response.status >= 400) { done({ error: 'download failed' }) } else { window.lastResponse = response done({ content_disposition: response.headers.get('Content-Disposition') }) } }) JS expect(result['error']).to eq(nil), result['error'] @content_disposition = result['content_disposition'] else @content_disposition = response_headers['Content-Disposition'] end end Then('the download should have the filename {string}') do |filename| expect(@content_disposition).to match(/filename=\"#{filename}\"/) end Then('the downloaded file content should include the following lines:') do |table| if Capybara.current_driver == :selenium file_content = page.evaluate_async_script(<<~JS) let [done] = arguments lastResponse.text().then((text) => { done(text) }) JS single_line_file = file_content.gsub("\n", '').gsub(' ', '') table.raw.each do |row| expect(single_line_file).to include(*row), "expected \n file to include \n #{row} \n" end else raise 'not implemented for driver' end end

Use it like this:

Copy
When I download "download me" Then the download should have the filename "export.csv" And the downloaded file content should include the following lines: """ Name, E-mail Mr. Foo, mr.foo@example.com """

Once an application no longer requires constant development, it needs periodic maintenance for stable and secure operation. makandra offers monthly maintenance contracts that let you focus on your business while we make sure the lights stay on.

Owner of this card:

Avatar
Emanuel De
Last edit:
about 1 month ago
by Besprechungs-PC
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Emanuel De to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more