Headless Capybara Feature Specs with Chrome

Derek Prior

I’ve been a happy user of Capybara-WebKit for many years now, but its dependence on Qt can make it frustrating to install on macOS, particularly following macOS or Xcode updates. One such recent issue lead me to experiment with running my tests in Chrome via ChromeDriver and Selenium.

I found the installation of ChromeDriver and Capybara-Selenium to be significantly faster than the installation of Qt and Capybara-WebKit, and my teammates reported no difficulties with those steps either.

While tests executing with Capybara-WebKit are headless, I initially did not have this option when configuring ChromeDriver. In this initial configuration, I was reminded that it is occasionally useful to watch a test execute without having to resort to save_and_open_screenshot. I was also pleasantly surprised to find that ChromeDriver never seems to steal focus from my active Chrome session or any other applications, which is a refreshing change from my memories of executing tests in Firefox via Selenium.

Last week saw the stable channel release of Chrome 59, which supports headless operation on macOS. Headless operation on Linux was already possible as of Chrome 57 and will be coming to Windows soon as well. I updated my Capybara configuration to run ChromeDriver with headless support and am now a mostly satisfied ChromeDriver convert.

ChromeDriver Installation

Executing your feature specs in Chrome requires that you have Chrome and ChromeDriver installed. Web developers and designers are likely to have Chrome installed already, so that leaves us needing to install ChromeDriver.

ChromeDriver is installed via Homebrew with brew install chromedriver and is similarly available in your package manager of choice on Linux. If you already have ChromeDriver installed, be sure to install an up-to-date version for headless support. I’m currently using ChromeDriver version 2.30.

If you’re not comfortable making this a prerequisite to running your application’s tests, you can also install ChromeDriver by adding chromedriver-helper to your Gemfile. On install this will download a platform-appropriate binary for ChromeDriver and add it to your gem path. If you opt for this approach, be sure to read the documentation on updating ChromeDriver and the open issue on Windows support.

Capybara Configuration

Add capybara-selenium to the test group of your Gemfile and optionally remove capybara-webkit while you’re at it. All that’s left for us to do now is to configure our drivers. In your rails_helper.rb or some file required by that file, add the following:

require "selenium/webdriver"

Capybara.register_driver :chrome do |app|
  Capybara::Selenium::Driver.new(app, browser: :chrome)
end

Capybara.register_driver :headless_chrome do |app|
  capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
    chromeOptions: { args: %w(headless disable-gpu) }
  )

  Capybara::Selenium::Driver.new app,
    browser: :chrome,
    desired_capabilities: capabilities
end

Capybara.javascript_driver = :headless_chrome

This configures chrome and headless_chrome drivers and sets Capybara to use headless_chrome for JavaScript tests by default. If you’d like to watch the tests execute while debugging, you can change the driver to chrome.

You may notice that the headless_chrome driver also passes the disable-gpu option. The documentation for the headless Chrome indicates this is only temporarily necessary but does not specify why. It’s not clear if this is necessary now that the feature is stable, but it doesn’t seem to hurt performance.

If you use the headless_chrome driver on an older version of Chrome or on Windows, you will get a NetReadTimeout error. I had hoped ChromeDriver would ignore the option on older browsers, but this is unfortunately not the case. You may need to make Capybara.javascript_driver configurable via an environment variable until such time as Chrome 59 is ubiquitous across your team.

On CI

The project I tried this on uses CircleCI which required no changes to its configuration. Chrome and ChromeDriver are already available in that environment.

If your project uses Travis, you will need to enable the Chrome addon. If you’re trying out Heroku CI, you can use the Chrome buildpack.

Beyond Installation, How Does ChromeDriver Stack Up?

Capybara-WebKit runs our tests on a fork of the WebKit browser engine via Qt. This engine is generally “close enough” but is not functionally equivalent to Safari (built on WebKit), Chrome (built on Blink, another fork of WebKit), or any other browser your users are likely to be using. This has occasionally caused issues in tests. By executing our tests directly in Chrome we are testing with the exact browser many of our users will be using. We’re also just steps away from executing those same tests in Firefox or even in Safari via SafariDriver.

I don’t have any hard science to offer on the matter of performance. I can anecdotally report that Capybara-Webkit seems significantly faster. I used an application with a dozen JavaScript-dependent specs to compare performance and found that Capybara-Webkit runs rspec --tag js in about 16 seconds, while the same command takes about 22 seconds using ChromeDriver. It’s not clear to me if the performance difference is dominated by startup cost or if it will scale linearly with the number of specs.

Capybara-Webkit also offers the block_unknown_urls configuration setting which prevents loading potentially slow external assets in your tests, such as external web fonts or analytics packages. I’ve yet to find a similar configuration in ChromeDriver. ChromeDriver allows specifying a proxy which could be used to accomplish this, but it would mean needing to run that proxy process as well.

Finally, I’ve noticed that save_and_open_screenshot produces an empty, gray screenshot when running in headless mode, while it works as expected on Capybara-Webkit. When the need for visual inspection of a test arises, I switch to the chrome Capybara driver, but this may be a significant detriment to your workflow if you rely on automated screenshots after test failures, for example.

What Does This Mean for Capybara-WebKit?

We’re not sure. We’re still in the experimentation phase of our use of ChromeDriver. At this time, our new projects still default to using Capybara-WebKit though this may change as more projects try out headless ChromeDriver.

If you’re a Capybara-Webkit user and give headless Chrome a try, we’d love to hear your experiences. How did the performance of your test suite differ? Did you find Chrome via Selenium and ChromeDriver to be lacking any features you count in Capybara-WebKit? Have you found a way to take screenshots? Tweet us and let us know!