Headless Browser Testing with Chrome and Rspec

If a user can have many different roles and permissions, you’ll want to test what they can view for each page. You can write request specs to test api responses, but this does not test your view logic.

For testing user flows, I like to simulate what the user will see by driving a chrome browser. In this example, I will be using rspec and ruby on rails.

First, let’s add these gems to your gemfile:

gem 'rspec-rails'
gem 'capybara'
gem 'capybara-email'
gem 'webdrivers'

Now create a new directory at spec/support. I like keep all of my test configuration and helper methods here. Add this line to your rails_helper file to load all of your test support files:

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

Now we will want to configure capybara to work with chrome. Create a new file in spec/support called chrome_setup.rb with this code:

Capybara.register_driver :selenium_chrome do |app|
  # Set chrome download dir and auto confirm all "are you sure you want to download" to test downloading docs and pdfs.
  chrome_prefs = {
    'download' => {
      'default_directory' => DownloadHelpers::PATH.to_s,
      'prompt_for_download' => false
    },
    'profile' => {
      'default_content_settings' => { 'multiple-automatic-downloads': 1 }, # for chrome version olde ~42
      'default_content_setting_values' => { 'automatic_downloads': 1 }, # for chrome newe 46
      'password_manager_enabled' => false
    },
    'safebrowsing' => {
      'enabled' => false,
      'disable_download_protection' => true
    }
  }

  # Set headless with docker friendly args.
  chrome_args = %w[window-size=1024,768 disable-gpu no-sandbox disable-translate no-default-browser-check disable-popup-blocking]
  # To run full browser instead of headless mode, run this command: HEADLESS=false rspec spec
  unless ENV.fetch('HEADLESS', 'true') == 'false'
    chrome_args += %w[headless]
  end

  # Initialize chromedriver.
  capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
    chromeOptions: {
      prefs: chrome_prefs,
      args: chrome_args
    }
  )
  driver = Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities)

  driver
end

This enables running chrome in headless mode. Headless mode just means driving chrome without rendering it on your screen. This will greatly increase performance and memory usage keeping your CI builds snappy.

If you also want to test file downloads, add a new file in spec/support called downloads.rb with this code:

module DownloadHelpers
  TIMEOUT = 10
  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.3 until downloaded?
    end
  end

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

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

  def clear_downloads
    FileUtils.rm_f(downloads)
  end
end

Now add this to chrome_setup.rb:

# Allow file downloads to work in chromedriver headless mode.
bridge = driver.browser.send(:bridge)
path = '/session/:session_id/chromium/send_command'
path[':session_id'] = bridge.session_id
bridge.http.call(:post, path, cmd: 'Page.setDownloadBehavior',
  params: {
    behavior: 'allow',
    downloadPath: DownloadHelpers::PATH.to_s
  }
)

You’re ready to write your first feature test. Here is a very basic example:

feature 'Viewing Project', js: true do
  scenario 'project owner can view project' do
    login_as project_owner
    visit project_path
    fill_in 'Name', with: 'Test Project'
    click_on 'Create Project'
    visit projects_path

    expect(page).to have_content('Test Project')
  end
end

Now run this command:

HEADLESS=false rspec spec

A chrome browser will launch and be driven by your test.