Downloading file to specific folder using Capybara and Poltergeist driver

rupesh picture rupesh · Apr 1, 2013 · Viewed 13.2k times · Source

I am writing my acceptance tests using Capybara and Poltergeist driver.I need to validate the content of the CSV file downloaded.

  1. I tried various ways of rendering the content on the page itself instead of downloading it.
  2. Also tried changing the mime types, but it is not working.

Finally I want to settle down with the option of downloading the file in a specific folder and then read the CSV file using core ruby libraries.

In order to achieve this,when poltergeist driver clicks on download link then I want it to handle the pop-up and download the file directly in the given folder.

In Selenium's chrome and firefox drivers, I have option of configuring profiles to handle pop ups and configure download directory.

Is there any such option using poltergeist? Any information will be helpful.

Answer

wojtha picture wojtha · Apr 25, 2013

It is not possible with Poltergeist, you can just check the headers.

 step 'I should get zipped file' do
    page.response_headers['Content-Disposition'].should include("filename=\"file.zip\"")
 end

But is is possible with Chrome driver and also with recent versions of Firefox and Selenium Webdriver. Unfortunately it runs via Selenium - i.e. not headless... See this article: http://collectiveidea.com/blog/archives/2012/01/27/testing-file-downloads-with-capybara-and-chromedriver/

My approach - slightly different as I'm working with Spinach and Rubyzip:

Add the following to your Gemfile

group :test do
  gem 'chromedriver-helper'  # for Chrome <= 28
  gem 'chromedriver2-helper' # for Chrome >= 29
  gem 'selenium-webdriver'
end

features/support/capybara.rb - I'm using Poltergeist for scenarios with @javascript tag and Chrome for scenarios with @download tag.

require 'spinach/capybara'
require 'capybara/poltergeist'
require 'selenium/webdriver'

# ChromeDriver 1.x, for Chrome <= 28 
Capybara.register_driver :chrome do |app|
  profile = Selenium::WebDriver::Chrome::Profile.new
  profile['download.default_directory'] = DownloadHelper::PATH.to_s
  args = ["--window-size=1024,768"]
  Capybara::Selenium::Driver.new(app, browser: :chrome, profile: profile, args: args)
end

# ChromeDriver 2.x, for Chrome >= 29 
Capybara.register_driver :chrome do |app|
  prefs = {
    download: {
      prompt_for_download: false,
      default_directory: DownloadHelper::PATH.to_s
    }
  }
  args = ['--window-size=1024,768']
  Capybara::Selenium::Driver.new(app, browser: :chrome, prefs: prefs, args: args)
end

# Tested with Firefox 27 and Selenium Webdriver 2.39
Capybara.register_driver :firefox do |app|
  profile = Selenium::WebDriver::Firefox::Profile.new
  profile['browser.download.dir'] = DownloadHelper::PATH.to_s
  profile['browser.download.folderList'] = 2 # 2 - save to user defined location
  profile['browser.helperApps.neverAsk.saveToDisk'] = 'application/zip'
  Capybara::Selenium::Driver.new(app, browser: :firefox, profile: profile)
end

Capybara.javascript_driver = :poltergeist # :webkit :selenium :poltergeist :chrome

Spinach.hooks.on_tag("javascript") do
  Capybara.current_driver = Capybara.javascript_driver
  Capybara.default_wait_time = 5
end

Spinach.hooks.on_tag("download") do
  Capybara.current_driver = :chrome # or :firefox
  Capybara.default_wait_time = 50
end

features/support/downloads.rb

module DownloadHelper
  TIMEOUT = 10
  PATH    = Rails.root.join("tmp/downloads")

  extend self

  def downloads
    Dir[PATH.join("*")]
  end

  def download_path
    wait_for_download
    downloads.first
  end

  def download_content
    wait_for_download
    File.read(download_path)
  end

  def wait_for_download
    Timeout.timeout(TIMEOUT) do
      sleep 0.1 until downloaded?
    end
  end

  def downloaded?
    downloads.any? && !downloading?
  end

  def downloading?
    downloads.grep(/\.crdownload$/).any?
  end

  def clear_downloads
    FileUtils.rm_f(downloads)
  end

end

Spinach.hooks.before_scenario do |scenario|
  DownloadHelper.clear_downloads
end

Spinach.hooks.after_scenario do
  DownloadHelper.clear_downloads
end

features/file_download.feature

Feature: File download
   As a user
   I want to be able to download my files

Background:
  Given I am logged in as a user
  And I have uploaded files in the system

@download
Scenario: Successfull download
  When I click on the download button
  Then I should get zipped files

features/steps/file_download.rb - Note that you can't use page.response_headers as it is not supported by the Selenium/ChromeDriver. But you can check the filename of the downloaded file using the File.basename().

class Spinach::Features::FileDownload < Spinach::FeatureSteps
  include SharedAuthentication

  step 'I click on the download button' do
    click_link "Download"
  end

  step 'I should get zipped files' do
    File.basename(DownloadHelper.download_path).should == 'file.zip'
    Zip::ZipFile.open(DownloadHelper.download_path) do |zipfile|
      zipfile.find_entry('myfile.txt').should_not be_nil
      zipfile.find_entry('myphoto.jpg').should_not be_nil
    end
  end

end