Replacing Selenium with Cuprite for Rails system tests
Tags: rails, testing, howto
Published: 2020-12-18 12:52:56

---
title: Replacing Selenium with Cuprite for Rails system tests
published: true
date: 2020-12-18 12:02:00 UTC
tags: rails, testing, howto
canonical_url: https://paraside.in/replacing-selenium-with-cuprite-for-rails-system-tests/
---

# Replacing Selenium with Cuprite for Rails system tests

In the [world of tests](https://guides.rubyonrails.org/testing.html), system tests are as close to real users as it gets: you control a browser and make it use your app as you expect humans to.

To do so, you need two things:

- **a browser** : you will see it (safari, chrome…) run on its own on your machine unless you set it to be "headless", a gory way to tell your machine that you actually don't want to see the insides of your tests
- **a driver** : the pilot that will take your test instructions and command the browser

By default, Rails ships with Selenium, but there's a new kid in town: [Cuprite](https://github.com/rubycdp/cuprite), a "_Headless Chrome driver for Capybara_", which is faster and has some nice tricks up its sleeves like options to pause and debug.

I discovered cuprite thanks to Evil Martians blog post: "\*\*[System of a test:\*\* Proper browser testing in Ruby on Rails](https://evilmartians.com/chronicles/system-of-a-test-setting-up-end-to-end-rails-testing)".

The following is just a quick-start version for those who don't care about docker or rspec, with some minor twists.

# Basic setup

To get started, you simply need to add the cuprite gem. Selenium will have to stay unless you're [on Rails 6.1](https://github.com/rails/rails/pull/39179).

```ruby
group :test do
  gem 'capybara'
  gem 'selenium-webdriver' # Only for rails <= 6.1
  gem 'cuprite'
end

```

And then you can edit `test/application_system_test_case.rb`

```ruby
require "test_helper"
require "capybara/cuprite" # <- Add this

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  # And replace selenium with cuprite
  driven_by :cuprite, using: :chromium, screen_size: [1400, 1400]
end
```

And with this you're good to go.

You'll notice that I'm not using `:chrome` but the open source browser it's based on: [Chromium](https://www.chromium.org/getting-involved/download-chromium). This is because I just uninstalled Chrome, because https://chromeisbad.com.

[https://twitter.com/sowenjub/status/1338958371844726784?s=20](https://twitter.com/sowenjub/status/1338958371844726784?s=20)

# A more elaborate setup

There are a couple more setup files I grabbed from [Evil Martians' setup](https://evilmartians.com/chronicles/system-of-a-test-setting-up-end-to-end-rails-testing), with a slightly different organisation since I'm not using rspec.

```
test/
  test_helpers/
    system/
      better_rails_system_tests.rb
      capybara_setup.rb
      cuprite_helpers.rb
      cuprite_setup.rb

```

And with this, `test/application_system_test_case.rb` will end up looking like this:

```ruby
require "test_helper"
require "test_helpers/system/better_rails_system_tests"
require "test_helpers/system/capybara_setup"
require "test_helpers/system/cuprite_helpers"
require "test_helpers/system/cuprite_setup"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :cuprite, using: :chromium, screen_size: [1400, 1400]

  include BetterRailsSystemTests
  include CupriteHelpers
end

```

And of course you need to create the files, taken from Evil Martians.

```ruby
# test_helpers/system/better_rails_system_tests

module BetterRailsSystemTests
  # Use our `Capybara.save_path` to store screenshots with other capybara artifacts
  # (Rails screenshots path is not configurable https://github.com/rails/rails/blob/49baf092439fc74fc3377b12e3334c3dd9d0752f/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb#L79)
  def absolute_image_path
    Rails.root.join("#{Capybara.save_path}/screenshots/#{image_name}.png")
  end

  # Make failure screenshots compatible with multi-session setup.
  # That's where we use Capybara.last_used_session introduced before.
  def take_screenshot
    return super unless Capybara.last_used_session

    Capybara.using_session(Capybara.last_used_session) { super }
  end
end



# test_helpers/system/capybara_setup

# Usually, especially when using Selenium, developers tend to increase the max wait time.
# With Cuprite, there is no need for that.
# We use a Capybara default value here explicitly.
Capybara.default_max_wait_time = 2

# Normalize whitespaces when using `has_text?` and similar matchers,
# i.e., ignore newlines, trailing spaces, etc.
# That makes tests less dependent on slightly UI changes.
Capybara.default_normalize_ws = true

# Where to store system tests artifacts (e.g. screenshots, downloaded files, etc.).
# It could be useful to be able to configure this path from the outside (e.g., on CI).
Capybara.save_path = ENV.fetch("CAPYBARA_ARTIFACTS", "./tmp/capybara")

# The Capybara.using_session allows you to manipulate a different browser session, and thus, multiple independent sessions within a single test scenario. That’s especially useful for testing real-time features, e.g., something with WebSocket.
# This patch tracks the name of the last session used. We’re going to use this information to support taking failure screenshots in multi-session tests.
Capybara.singleton_class.prepend(Module.new do
  attr_accessor :last_used_session

  def using_session(name, &block)
    self.last_used_session = name
    super
  ensure
    self.last_used_session = nil
  end
end)

```

For this one I created a dedicated file but you could put it at the end of `cuprite_setup` as well.

```ruby
# test_helpers/system/cuprite_helpers

module CupriteHelpers
  # Drop #pause anywhere in a test to stop the execution.
  # Useful when you want to checkout the contents of a web page in the middle of a test
  # running in a headful mode.
  def pause
    page.driver.pause
  end

  # Drop #debug anywhere in a test to open a Chrome inspector and pause the execution
  def debug(*args)
    page.driver.debug(*args)
  end
end



# test_helpers/system/cuprite_setup

# First, load Cuprite Capybara integration
require "capybara/cuprite"

# Then, we need to register our driver to be able to use it later
# with #driven_by method.
Capybara.register_driver(:cuprite) do |app|
  Capybara::Cuprite::Driver.new(
    app,
    **{
      window_size: [1200, 800],
      # See additional options for Dockerized environment in the respective section of this article
      browser_options: {},
      # Increase Chrome startup wait time (required for stable CI builds)
      process_timeout: 10,
      # Enable debugging capabilities
      inspector: true,
      # Allow running Chrome in a headful mode by setting HEADLESS env
      # var to a falsey value
      headless: !ENV["HEADLESS"].in?(%w[n 0 no false])
    }
  )
end

# Configure Capybara to use :cuprite driver by default
Capybara.default_driver = Capybara.javascript_driver = :cuprite 

```

And now we just have to write a simple test:

```ruby
require "application_system_test_case"

class HomeTest < ApplicationSystemTestCase
  test "open home screen" do
    visit root_url
    save_and_open_page # Will save the html page in /tmp/capybara and open it in your default browser
    take_screenshot # Will save a screenshot in /tmp/capybara/screenshots
    pause # To see the current view, requires HEADLESS=0 (or n, no, false)
    debug() # To see the current view with debug tools
    assert_selector "h1", text: "Hello, world"
  end
end

```

To launch it, run the following:

```bash
# If you want to see the browser or use `pause`
HEADLESS=0 rails test test/system/home_test.rb

# Otherwise this will do
rails test test/system/home_test.rb

```