Prior to RSpec 3.7 feature specs were the way to go for full-stack testing of application interactions involving javascript in a real/headless browser environment. Recently released RSpec 3.7 added support for system specs based on Rails system tests. ActionDispatch::SystemTestCase was introduced in Rails 5.1 and provides preconfigured Capybara wrapper for real browser testing. Having preconfigured Capybara with commonly used features baked right in the framework means much less manual configuration hassle which often was tricky to set up properly. The advantages of using system specs over feature specs are the following:

  1. Database changes are automatically rolled back after a test and there is no need to manually configure and use database_cleaner strategies.
  2. More convenient way to switch a browser driver for each spec individually with driven_by.
  3. Automatic browser screenshots on failure displayed right in the terminal. Again, that’s preconfigured, no need to add supporting gem like capybara-screenshot.

Given the above, it’s also worth emphasizing that the RSpec team now recommends system specs over feature specs for Rails 5.1 and above.

Setting up RSpec system specs with headless Chrome

Let’s start by creating a new rails project. We’re going to exclude the default Rails Minitest integration with --skip-test as we’re going to use RSpec.

rails --version
Rails 5.2.0.beta2
rails new rspec-system-specs --skip-test --skip-active-storage

Except for adding rspec-rails to Gemfile, the most challenging part of the setup may be to figure out which gems are required for headless Chrome browser testing. Here is list the of required gems:

group :development, :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '~> 2.16.1'
  gem 'selenium-webdriver', '~> 3.8.0'
  # Easy installation and use of chromedriver to run system tests with Chrome
  gem 'chromedriver-helper', '~> 1.1.0'
  gem 'rspec-rails', '~> 3.7.2'

Make sure that chromedriver is installed:

brew install chromedriver
chromedriver --version
ChromeDriver 2.34.522932 (4140ab217e1ca1bec0c4b4d1b148f3361eb3a03e)

Generate spec/test_helper.rb and spec/rails_helper.rb:

rails g rspec:install

Writing system spec

Given we have a simple home#index action, configured as root in routes.rb, that renders app/views/home/index.html.erb:

<h1 id="title">Hello World</h1>

we can implement the first system spec spec/system/home_spec.rb as follows:

require 'rails_helper'

describe 'Homepage' do
  before do
    driven_by :selenium_chrome_headless

  it 'shows greeting' do
    visit root_url
    expect(page).to have_content 'Hello World'

We’re setting :selenium_chrome_headless driver as we want to use headless Chrome. Other registered drivers provided by Capybara are: :rack_test, :selenium and :selenium_chrome. See the driven_by documentation for more advanced configuration options, e.g. browser resolution.

Let’s see if it works:

$ rspec
Puma starting in single mode...
* Version 3.11.0 (ruby 2.5.0-p0), codename: Love Song
* Min threads: 0, max threads: 4
* Environment: test
* Listening on tcp://
Use Ctrl-C to stop

Finished in 1.41 seconds (files took 2.04 seconds to load)
1 example, 0 failures

Obviously we don’t want to set the driver in each spec. We can configure a global default in spec_helper.rb. For example:

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :selenium_chrome_headless

and override the default driver in individual spec files when needed.

Browser screenshots on failure

It’s worth mentioning that in case of spec failure, system spec automatically takes the browser screenshot and displays it right in the terminal output which is a very handy feature.

spec failure

That’s the feature baked in Rails system tests. There is no need to configure it manually as it was in case of feature specs, for example with supporting capybara-screenshot gem.

Javascript testing

So far we’ve only tested server-side rendered content. Let’s add some client-side javascript to prove that indeed system spec is using javascript capable browser (headless Chrome in our case):

$ ->
  $('#title').text('Hello Universe')

After modifying the spec assertion to expect(page).to have_content 'Hello Universe', we still get the passing spec testing client-side javascript changes:

$ rspec
Finished in 1.99 seconds (files took 2.44 seconds to load)
1 example, 0 failures

Automatic database rollback

As mentioned before, database changes in system specs are automatically rolled back. Let’s see if it’s the case by adding some database output in our page under test:

<h1 id="title">Hello World</h1>
  Planet count: <%= Planet.count %>

Modify the spec to seed a record and add new spec example asserting rendered record count:

describe 'Homepage' do
  before do
    driven_by :selenium_chrome_headless
    puts 'creating a planet'
    Planet.create!(name: 'Mars')

  it 'shows greeting' do
    visit root_url
    expect(page).to have_content 'Hello Universe'

  it 'shows planet count' do
    visit root_url
    expect(page).to have_content 'Planet count: 1'

and the rspec result is:

$ rspec --format d
creating a planet
  shows greeting
# automatic database rollback after spec example
creating a planet
  shows planet count

Finished in 1.52 seconds (files took 1.03 seconds to load)
2 examples, 0 failures

That’s great! We no longer need to configure database_cleaner to clean database state after spec example run as it was previously required in case of feature specs.

The source code of the demonstrated example can be found on GitHub.