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/screenshotsfor 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:
bashbin/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:
rubyrequire '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:
rubytest '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:
rubyclass 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
- Running system tests with the regular test suite — System tests are slow because they launch a browser. Run them separately with
bin/rails test:systemand keep your fast unit tests in a separate CI step. - Forgetting to set screen size — Some elements may be hidden or repositioned at small viewport sizes. Always configure
screen_sizein yourApplicationSystemTestCaseto match a reasonable desktop resolution. - Using
:chromein CI — Headed Chrome requires a display server. Always use:headless_chromein CI environments to avoid failures.
Best Practices
- 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.
- Use
assert_domfor DOM assertions in Rails 8 — Rails 8 providesassert_domas a preferred assertion for checking DOM structure. It integrates cleanly with Capybara and reads naturally. - 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
ApplicationSystemTestCasebase class is generated automatically and configures Capybara. - Run system tests separately with
bin/rails test:systemsince they are slower than unit tests. - Screenshots are captured automatically on failure and can be triggered manually with
take_screenshot.
Code Examples
# 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