Jamie's Blog

Lessons from a life of startups, coding, countryside, and kids

Rails Testing with Headless Chrome on Heroku CI

Heroku ci

I recently started setting up a Rails app on Heroku CI . It was mostly pretty painless but there were a few tricks to getting our Capybara specs running.

Heroku don’t fully support running browser tests yet; I mean, it works but it took a little finagling which hopefully you can avoid.

I’m going to assume you already have headless Chrome running your tests locally.

First version

All good tech posts should start with version numbers, especially when dealing with browser testing — a patch version can make or break it.

Our Rails 4 app was using these versions of capybara and selenium

  gem 'capybara', '~> 2.14', '>= 2.14.2' # 2.14.2
  gem 'selenium-webdriver', '~> 3.4'     #3.4.0

Now the complications start.

Firstly, Chrome is not installed on Heroku so we need to delve into the world of Heroku buildpacks. Likewise, Selenium needs chromedriver to talk to Chrome. That’s not installed by default either.

Add an app.json to tell Heroku to install two buildpacks in addition to to the standard Ruby buildpack:

{
  "environments": {
    "test": {
      "buildpacks": [
        { "url": "https://github.com/heroku/heroku-buildpack-ruby" },
        { "url": "https://github.com/heroku/heroku-buildpack-chromedriver" },
        { "url": "https://github.com/heroku/heroku-buildpack-google-chrome" }
      ],
      "addons":[
         "heroku-postgresql"
      ]
    }
  }
}

We’re only installing these buildpacks in our test environment, so only Heroku CI will install them (not review or production apps).

Now we need to tell Selenium where to find the chromedriver binary. The buildpack has installed a shim that we need to point to. In rails_helper.rb

  chrome_bin = ENV.fetch('GOOGLE_CHROME_SHIM', nil)
  chrome_opts = chrome_bin ? { "chromeOptions" => { "binary" => chrome_bin } } : {}
  Capybara.register_driver :selenium_chrome do |app|
    Capybara::Selenium::Driver.new(app, browser: :chrome, args: ['headless', 'disable-gpu', 'window-size=1280,1024'],
                                   desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(chrome_opts))
  end

  Capybara.javascript_driver = :selenium_chrome

And our specs should now run with headless Chrome when pushed to Heroku CI.

Hooray!

Latest version

Except… when we updated capybara and selenium

  gem 'capybara', '~> 2.15.1'
  gem 'selenium-webdriver', '~> 3.5.1

…everything broke again with unhelpful messages like

Selenium::WebDriver::Error::UnknownError:
unknown error: cannot find Chrome binary

It seems like the format for specifying the Selenium config has changed and after much trial and error on my part (you’re welcome!), this is what works:

  # Heroku build packs need to put the chromedriver binary in a non-standard location specified by GOOGLE_CHROME_SHIM
  chrome_bin = ENV.fetch('GOOGLE_CHROME_SHIM', nil)

  options = {}
  options[:args] = ['headless', 'disable-gpu', 'window-size=1280,1024']
  options[:binary] = chrome_bin if chrome_bin

  Capybara.register_driver :headless_chrome do |app|
    Capybara::Selenium::Driver.new(app,
       browser: :chrome,
       options: Selenium::WebDriver::Chrome::Options.new(options)
     )
  end

  Capybara.javascript_driver = :headless_chrome

Enjoy!