Introduction

System tests verify your application from the user's perspective by driving a real browser. Unlike controller or integration tests that work at the HTTP level, system tests click buttons, fill in forms, and assert what appears on screen — exactly as a human would. In Rails 8, system tests default to headless Chrome, making them fast, reliable, and CI-friendly out of the box.

Key Concepts

  • System Test: A test that runs in a real browser, exercising the full stack including JavaScript, CSS, and server-side rendering.
  • Capybara: The Ruby library that provides a DSL for browser interactions. Rails bundles Capybara and wires it into the test framework automatically.
  • Headless Chrome: A mode where Chrome runs without a visible window. Rails 8 defaults to driven_by :selenium, using: :headless_chrome, which is ideal for CI pipelines.
  • ApplicationSystemTestCase: The base class for all system tests, generated by Rails in test/application_system_test_case.rb.
  • Screenshot on Failure: Rails automatically captures a screenshot when a system test fails, saving it to tmp/screenshots for debugging.

Real World Context

Unit and integration tests are fast but blind — they cannot tell you if a button is hidden behind a modal, if JavaScript fails to load, or if a Turbo Frame renders incorrectly. System tests catch the bugs that only surface when all the layers work together. Every production Rails application benefits from a thin layer of system tests covering critical user journeys like sign-up, checkout, and data entry flows.

Deep Dive

When you generate a Rails 8 application, it creates the system test base class automatically:

ruby
# test/application_system_test_case.rb
require 'test_helper'

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

The driven_by method configures which browser driver to use. Rails 8 defaults to headless Chrome, which means you do not need to install or configure a separate driver — the selenium-webdriver gem handles everything.

To create your first system test, generate it with the Rails generator:

bash
bin/rails generate system_test articles

This creates test/system/articles_test.rb. Here is a basic system test that visits a page and verifies its content:

ruby
require 'application_system_test_case'

class ArticlesTest < ApplicationSystemTestCase
  test 'viewing the index' do
    visit articles_url
    assert_selector 'h1', text: 'Articles'
  end

  test 'creating an article' do
    visit articles_url
    click_on 'New Article'

    fill_in 'Title', with: 'My New Article'
    fill_in 'Body', with: 'Article content here'
    click_on 'Create Article'

    assert_text 'Article was successfully created'
  end
end

The first test visits the articles index and asserts that an h1 element with the text "Articles" exists. The second test navigates through the creation flow, fills in fields by their labels, submits the form, and asserts the success message appears.

You run system tests separately from other tests because they are slower:

bash
# Run all system tests
bin/rails test:system

# Run a specific system test file
bin/rails test test/system/articles_test.rb

# Run a single test by line number
bin/rails test test/system/articles_test.rb:5

When a test fails, Rails captures a screenshot automatically. You can also capture one manually at any point:

ruby
test 'debugging a visual issue' do
  visit articles_url
  take_screenshot  # Saved to tmp/screenshots/
  assert_selector 'h1', text: 'Articles'
end

For local development, you can switch to a headed browser to watch the test run visually:

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

Change :headless_chrome to :chrome and you will see the browser open and interact with your application in real time. Switch back to headless before pushing to CI.

Common Pitfalls

  1. Running system tests with the regular test suite — System tests are slow because they launch a browser. Run them separately with bin/rails test:system and keep your fast unit tests in a separate CI step.
  2. Forgetting to set screen size — Some elements may be hidden or repositioned at small viewport sizes. Always configure screen_size in your ApplicationSystemTestCase to match a reasonable desktop resolution.
  3. Using :chrome in CI — Headed Chrome requires a display server. Always use :headless_chrome in CI environments to avoid failures.

Best Practices

  1. Keep system tests focused on critical paths — Do not try to system-test every edge case. Cover sign-up, login, primary CRUD flows, and payment — the journeys where failure costs the most.
  2. Use assert_dom for DOM assertions in Rails 8 — Rails 8 provides assert_dom as a preferred assertion for checking DOM structure. It integrates cleanly with Capybara and reads naturally.
  3. Configure headless Chrome for CI, headed Chrome for debugging — Use an environment variable to toggle: driven_by :selenium, using: (ENV['HEADLESS'] != 'false' ? :headless_chrome : :chrome).

Summary

  • System tests drive a real browser to test your application from the user's perspective.
  • Rails 8 defaults to headless Chrome via driven_by :selenium, using: :headless_chrome.
  • The ApplicationSystemTestCase base class is generated automatically and configures Capybara.
  • Run system tests separately with bin/rails test:system since they are slower than unit tests.
  • Screenshots are captured automatically on failure and can be triggered manually with take_screenshot.

Code Examples

ruby
# test/application_system_test_case.rb
require 'test_helper'

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  # Rails 8 defaults to headless Chrome
  driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
end
✓ Completed