Skip to content

Instantly share code, notes, and snippets.

@brianjbayer
Last active March 23, 2024 15:33
Show Gist options
  • Save brianjbayer/2dea81a19b180301c26500479f9eeee4 to your computer and use it in GitHub Desktop.
Save brianjbayer/2dea81a19b180301c26500479f9eeee4 to your computer and use it in GitHub Desktop.
Use RSpec Rails System Specs for Web Server Level Functional Tests

Use RSpec Rails System Specs for Web Server Level Functional Tests

Center Street Drawbridge Cleveland, Ohio - Wendy Bayer

🕐 Updated March 2024


Why

Test all of your Rails app's functionality even at the highest feature level right in RSpec with the rest of your unit and integration tests.

Use RSpec Rails system specs to test your app's functionality at the web server (e.g. Puma) level.

Consider using RSpec Rails system specs and test doubles to reduce your End-to-End testing in a test environment.


What This Is

This is an overview and recipe for adding RSpec Rails system specs to your app's RSpec test suite. Here you will get...

  1. The background on system specs
  2. Their gem dependencies and how to add them
  3. An overview of configuring Capybara
  4. An overview of writing system specs and how to run them

Background

RSpec Rails system specs are the only level of RSpec Rails tests that launch the web server (Puma by default).

system specs...

What Are RSpec Rails System Specs

Rails (Minitest) introduced system tests in Rails 5.1 to build in the ability to functionally test your Rails application at the web browser level with Capybara.

system specs are RSpec Rails' wrapper for the Rails' (Minitest) system tests.


Configuring RSpec Rails System Specs

Defaults

By default, system tests are run with the Selenium driver, using the Chrome browser, and a screen size of 1400x1400.

RSpec does not use your ApplicationSystemTestCase helper. Instead it uses the default driven_by(:selenium) from Rails.

Configure Required Gems

At a minimum, your system specs require...

  • gem 'capybara'
  • gem 'selenium-webdriver'

By default, Rails (i.e. rails new myapp) should automatically include Minitest system test dependencies in the generated Gemfile.

Adding RSpec Rails System Specs Gems Manually

However, IF you skipped adding Minitest when you generated your app (e.g. rails new myapp -T) and added RSpec Rails manually to your project and Gemfile, THEN you will need to add the system spec dependencies manually as well.

Ensure that you have in your app's Gemfile...

RSpec-Rails

group :development, :test do
  ...
  gem 'rspec-rails'
  ...
end

Capybara and Selenium Webdriver

group :development, :test do
  ...
  # Adds support for RSpec system specs
  gem 'capybara'
  gem 'selenium-webdriver'
  ...
end

Install Added Gems

IF you manually added gems to the Gemfile, THEN you will need to install them with bundle install.

Configure Capybara

By default Rails Minitest uses and configures the ApplicationSystemTestCase helper in test/application_system_test_case.rb...

require 'test_helper'

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end

However, as mentioned, RSpec does not use your ApplicationSystemTestCase helper!

Configuring Capybara Manually for RSpec Rails

By default, RSpec Rails uses the Rails default driven_by(:selenium).

Your specific Rails app's Capybara configuration depends on your intended supported browsers and whether they are local or remote. However, I have included a sample Capybara configuration that I use to demonstrate its configuration. Since this is at the web server level, you must set the IP and Port of the host/webserver for your app.

Sample Local and Remote Browser (Container) Capybara Implementation

I like to have my development (and other) environment(s) as close to the intended production environment as possible to reduce the risk of surprises later. Running your Rails app in containers and containerized dev environments is a great way to do this. I also recommend using Selenium's Docker Containers to simplify browser dependencies.

👉 If you are using an ARM-based Mac (e.g. M2 chip), you will need to use the forked Docker Seleniarm Containers

This sample Capybara implementation supports both remote browsers (i.e. Selenium/Seleniarm Containers) and local/native browsers.

You implement this by...

  1. Creating a spec/support/capybara.rb configuration file
  2. Requiring the capybara configuration file in spec/rails_helper.rb
Create spec/support/capybara.rb Capybara Configuration File

This implementation uses environment variables to supply the running environment-specific values for...

  • PORT - the port for your Rails App (e.g. PORT="3000")
  • BROWSER - the type of Selenium-supported browser (e.g. BROWSER=chrome)
  • HEADLESS - if set (including set to empty), specifies running in headless mode provided that the specified browser supports it (e.g. HEADLESS=)
  • SELENIUM_REMOTE - the remote browser URL (e.g. SELENIUM_REMOTE='http://localhost:4444/wd/hub' )
  • CAPYBARA_WAIT - the time in seconds for Capybara to wait on page URLs or elements to load

Note that when running locally/natively, if BROWSER is not set, this example will default to the :selenium browser which is Chrome.

In your Rails app's project directory, create file spec/support/capybara.rb with the following Capybara configuration...

# frozen_string_literal: true

def create_capybara_browser(browser:, url:)
  return :selenium unless browser

  Capybara.register_driver :capybara_browser do |app|
    options = browser_options(browser)
    Capybara::Selenium::Driver.new(
      app,
      browser: url ? :remote : browser.to_sym,
      options: options,
      url:)
  end
  :capybara_browser
end

def browser_options(browser)
  browser = browser.to_s.gsub(/\W/, '').capitalize
  # e.g. Selenium::WebDriver::Chrome::Options.new
  options = Selenium::WebDriver.const_get(browser).const_get('Options').new
  options.add_argument('--headless') if headless_specified?
  options
end

def headless_specified?
  headless = ENV.include?('HEADLESS')
end

def configure_rspec_capybara(capybara_browser, app_host_port)
  # This is always running locally (even in container) so get local IP address
  app_host = IPSocket.getaddress(Socket.gethostname)
  RSpec.configure do |config|
    config.before(:each, type: :system) do
      driven_by capybara_browser

      Capybara.app_host = "http://#{app_host}:#{app_host_port}"
      Capybara.server_host = app_host
      Capybara.server_port = app_host_port
    end
  end
end

# -- Main Capybar Configuration --
url = ENV['SELENIUM_REMOTE']
browser = ENV['BROWSER']

capybara_browser = create_capybara_browser(browser:, url:)

configure_rspec_capybara(capybara_browser, ENV['PORT'])

# Increase the Capybara Wait Time
# NOTE: Docker Compose can convert environment variables values to string
Capybara.default_max_wait_time = ENV.fetch('CAPYBARA_WAIT', 10).to_i
Require Capybara Configuration File in spec/rails_helper.rb

You add the Capybara configuration you just created to the RSpec Rails framework by requiring it in your app's spec/rails_helper.rb file by adding the following line...

require 'support/capybara'

Writing RSpec Rails System Specs

Once you have your Capybara configuration implemented as you like, you can start writing and running your system specs.

Specifying RSpec Rails System Specs

By convention in RSpec Rails, you put your system specs under the spec/system directory.

You can also explicitly tag your system specs with :type => :system...

RSpec.describe 'Hello Rails', type: :system do
  ...
end

Generating RSpec Rails System Specs

You can use rails generate to generate your system specs...

bundle exec bin/rails generate rspec:system [test-name]

For example bundle exec bin/rails generate rspec:system hello_rails.

Sample RSpec Rails System Spec Test

Here is a very simple example of an RSpec Rails system spec expecting the phrase "Hello Rails" to be on the home page of the Rails app...

# Specifies user sees Hello Rails on home page

require 'rails_helper'

RSpec.describe 'Hello Rails', type: :system do
  context 'when user visits the Home Page...' do
    before(:each) do
      visit '/'
    end
    it { expect(page).to have_text('Hello, Rails!') }
  end
end

Running the RSpec Rails System Specs

Be sure to specify any environment variables.

Here's an example of running all the RSpec tests with the Sample Remote Browser (Container) Capybara Implementation...

SELENIUM_REMOTE='http://localhost:4444/wd/hub' \
BROWSER=chrome \
APP_HOST_PORT="3000" \
bundle exec rspec

Run With Other RSpec Tests (Default)

Run your Rails RSpec tests the way that you normally do to include your system specs, for example...

bundle exec rspec
bundle exec rake spec

Run Just RSpec Rails System Specs

You can also run just your RSpec Rails system specs using...

  • Rails rake task rake spec:system

    bundle exec rake spec:system
    
  • RSpec's abilities to specify tests to run your system specs...

    • To run only specs in the spec/system folder...

      bundle exec rspec spec/system
      
    • To run only type: :system tagged system specs...

      bundle exec rspec --tag type:system
      

Run All RSpec Specs Except for Rails System Specs

You can run all of your tests except for your RSpec Rails system specs using RSpec's exclude functions...

  • To exclude specs in the spec/system folder...

    bundle exec rspec --exclude-pattern "spec/system/*_spec.rb"
    
  • To exclude type: :system tagged system specs...

    bundle exec rspec --tag ~type:system
    

I Learned This From... (Sources)

I used some of the Capybara configuration from my personal projects which I've refined over the years.

When I learned and implemented this, I was specifically doing it in containers and in Kubernetes. These sources reflect that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment