「一覧画面やフォーム、リリースのたびに手で動かして確認するの、地味に面倒じゃありませんか?」——モデルの単体テストは書いているけど、「ユーザーが実際にブラウザで操作したときにちゃんと動くか」までは手作業、というチームは多いはずです。
そこで使うのが RSpec のシステムスペック(system spec)です。ログインして、フォームに入力して、ボタンを押して、画面に結果が出る——この一連の操作をそのまま自動テストにできます。この記事では、rspec-rails と Capybara のセットアップから、最初の1本を書いて緑にするまで、そして「なぜか落ちる」時の対処や CI での動かし方までを、実務でテストを書いてきた立場で整理します。読み終わる頃には、あなたのアプリに system spec を1本追加できるようになります。
まず結論:system spec で「手動確認」を自動化できる
細かい設定は後で説明します。先にゴールのイメージだけ渡しておきます。system spec を1本書くと、次のような「人がブラウザでやっていた確認」が rspec コマンド一発で緑になります。

手動確認との一番の違いは、失敗したときに自動でスクリーンショットが保存されること。Rails 5.1 以降の「システムテスト」がベースなので、テストがコケた瞬間の画面をあとから見返せます。「CI では落ちるけど手元だと再現しない」の調査がぐっと楽になります。
| こんなとき | system spec が効く |
|---|---|
| ログイン→投稿→一覧表示、のような画面をまたぐ操作を担保したい | ◎ |
| JavaScript(Turbo・Stimulus・モーダル等)が絡む挙動を確認したい | ◎ |
| ボタンの色やピクセル単位の見た目を検証したい | △(見た目専用ツールの領域) |
| 1つのメソッドの戻り値だけを確認したい | ×(モデルスペックの担当) |
なぜ「feature spec」ではなく「system spec」なのか
RSpec でブラウザテストを調べると、spec/features に置く feature spec と、spec/system に置く system spec の2つが出てきて混乱しますよね。結論から言うと、これから書くなら system spec 一択です。
feature spec は Rails がシステムテストを標準搭載する前からある、いわば「先祖」です。RSpec 公式も Rails 5.1 以降のアプリには system spec を推奨しています。system spec には feature spec に無い、次のうれしい機能がプリセットされています。
| 項目 | system spec | feature spec(旧) |
|---|---|---|
| 失敗時のスクショ | 自動(追加gem不要) | 手動設定(capybara-screenshot が必要) |
| DBの後片付け | 自動でロールバック | database_cleaner を自前設定しがち |
| ドライバ切り替え | driven_by でテストごとに簡単 | やや手間 |
| ベース | Rails標準のシステムテスト | Capybara単体 |
ひとことで言えば、「system spec は Rails 標準のシステムテストを RSpec で書けるようにしたもの」。面倒な後片付けを Rails 側が肩代わりしてくれるので、こちらはテストの中身に集中できます。
セットアップ(gem を入れて初期化する)
まずは必要な gem を Gemfile に追加します。Rails 8 系の標準的な構成です。
group :development, :test do
gem "rspec-rails", "~> 8.0"
end
group :test do
gem "capybara", "~> 3.40"
gem "selenium-webdriver" # ブラウザを動かすドライバ
endここで1つ、以前のノウハウが古くなっているポイントがあります。かつては webdrivers gem で ChromeDriver を管理するのが定番でしたが、selenium-webdriver 4.11 以降は「Selenium Manager」が正しいドライバを自動でダウンロードするようになりました。Rails も新規アプリの Gemfile テンプレートから webdrivers を外しています。今から始めるなら webdrivers は不要です。
gem を入れたら、RSpec を初期化します。
bundle install
bin/rails generate rspec:installこれで spec/rails_helper.rb などが生成され、spec/system/ に置いたファイルが system spec として認識されるようになります。
ドライバを設定する(JSの有無で使い分ける)
system spec には「どのブラウザでテストを動かすか(=ドライバ)」の設定が要ります。ポイントは、JavaScript が要らないテストは速い rack_test、JS が絡むテストだけ本物のブラウザ(ヘッドレスChrome)を使うという使い分けです。
spec/support/capybara.rb を作り、次のように書きます(rails_helper.rb 内で spec/support を読み込む設定を有効にしておきます)。
RSpec.configure do |config|
# 通常はブラウザを立ち上げない rack_test(速い)
config.before(:each, type: :system) do
driven_by :rack_test
end
# js: true を付けたテストだけヘッドレスChromeで動かす
config.before(:each, type: :system, js: true) do
driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
end
end注意点として、RSpec の system spec は Rails の ApplicationSystemTestCase を読みません。デフォルトのままだと selenium が使われるので、上のように driven_by を自分で指定しておくのが安心です。
最初の1本を書いてみる(STEP1 → STEP2)
いよいよテスト本体です。いきなり複雑な例だと手が止まるので、まずは「ページが表示されること」だけの最小構成から始めます。
STEP1:ページが開けることを確認する
# spec/system/posts_spec.rb
require "rails_helper"
RSpec.describe "投稿機能", type: :system do
it "投稿一覧ページが表示される" do
visit posts_path
expect(page).to have_content("投稿一覧")
end
endvisit でページを開き、expect(page).to have_content(...) で「画面にこの文字があるはず」を検証する。これが system spec の基本形です。まずはこれが bundle exec rspec spec/system で緑になることを確認しましょう。
STEP2:ログインして投稿を作成する(実務の形)
次は本命。「ログイン → フォーム入力 → 送信 → 一覧に表示される」という、人が手でやっていた確認を丸ごと自動化します。テストデータの用意には FactoryBot を使う前提です。
# spec/system/posts_spec.rb
require "rails_helper"
RSpec.describe "投稿機能", type: :system do
let(:user) { create(:user) }
it "ログインして投稿を作成すると一覧に表示される", js: true do
# ① ログイン(自作のログイン画面を操作)
visit new_session_path
fill_in "メールアドレス", with: user.email
fill_in "パスワード", with: "password"
click_on "ログイン"
# ② 投稿を作成
visit new_post_path
fill_in "タイトル", with: "はじめての投稿"
fill_in "本文", with: "system spec のテストです"
click_on "投稿する"
# ③ 結果を検証
expect(page).to have_content("投稿を作成しました")
expect(page).to have_content("はじめての投稿")
end
endやっていることは 「①データを用意 → ②visit・fill_in・click_on で操作 → ③have_content で検証」の3段構成だけ。この型さえ覚えれば、大半の画面テストは書けます。

よく使う Capybara メソッド一覧
system spec の操作は Capybara のメソッドで書きます。まずはこの表の分だけ押さえれば十分です。
| やりたいこと | メソッド | 例 |
|---|---|---|
| ページを開く | visit | visit posts_path |
| テキスト入力 | fill_in | fill_in "タイトル", with: "○" |
| ボタン/リンク押下 | click_on | click_on "投稿する" |
| チェックボックス | check / uncheck | check "規約に同意" |
| セレクトボックス | select | select "Rails", from: "カテゴリ" |
| 文字があるか検証 | have_content | expect(page).to have_content("○") |
| 要素があるか検証 | have_selector | expect(page).to have_selector(".post", count: 3) |
テストが落ちる・効かないときのチェックリスト
system spec でつまずくポイントはだいたい決まっています。「なぜか落ちる」ときは、上から順に確認してみてください。
| 症状 | 原因と対処 |
|---|---|
| JSの動く画面(モーダル等)で要素が見つからない | js: true の付け忘れ。rack_test は JavaScript を実行しないので、Turbo や Stimulus が絡むテストには js: true を付ける |
| 非同期の描画待ちで落ちる(Ajax後の要素) | find や have_content は要素が現れるまで自動で待つ。この待機を使わず page.text.include?(...) のようにその瞬間の状態を直接見ると待たずに落ちる。判定は必ずマッチャ(have_content / have_no_content)で書く |
| ボタンを押した直後の検証が不安定 | スリープ(sleep 1)で待つのは悪手。Capybara の待機機構に任せ、have_content など「結果」を待つ表現で書く |
| データが次のテストに残る | system spec はデフォルトでトランザクションを自動ロールバックする。残る場合は自前の database_cleaner 設定が競合していないか確認 |
| ChromeDriver のバージョン不一致エラー | selenium-webdriver 4.11+ なら Selenium Manager が自動解決する。古い webdrivers gem が残っていたら外す |
Selenium と Cuprite、どっちを使う?
ヘッドレスChromeを動かすドライバには、定番の Selenium のほかに Cuprite という選択肢があります。Cuprite は CDP(Chrome DevTools Protocol)で直接 Chrome を操作するドライバで、Selenium より速く、ドライバの用意も不要です。
| 観点 | Selenium | Cuprite |
|---|---|---|
| 導入の手軽さ | ◎(Railsの実質標準) | ○(gem追加+数行の設定) |
| 速度 | △ | ◎(体感で速い) |
| デバッグ機能 | 標準的 | ◎(一時停止・ネットワーク待ち等) |
| 情報量 | ◎(枯れている) | ○(増加中) |
Cuprite を使うなら、設定はこれだけです。
# Gemfile(group :test に追加)
gem "cuprite"
# spec/support/capybara.rb
require "capybara/cuprite"
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(app, window_size: [1400, 1400], headless: true)
end
RSpec.configure do |config|
config.before(:each, type: :system, js: true) do
driven_by :cuprite
end
end迷ったら、まず Selenium で始めてOKです。テストが増えて実行時間が気になってきたら Cuprite に乗り換える、という順番が現実的です。最初から速度で悩まないことのほうが大事です。
CI(GitHub Actions)で system spec を回す
ローカルで緑になったら、CI でも自動実行して「壊れたら気づける」状態にしましょう。GitHub Actions の ubuntu-latest ランナーには Chrome がプリインストールされていて、Selenium Manager がドライバを解決するので、特別なインストールはほぼ不要です。
# .github/workflows/ci.yml(抜粋)
jobs:
system_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Prepare database
run: bin/rails db:test:prepare
- name: Run system specs
run: bundle exec rspec spec/system失敗時のスクリーンショットは tmp/screenshots/ に保存されます。CI では actions/upload-artifact でこのフォルダを保存しておくと、コケた画面をあとから確認できて調査が一気に楽になります。
どこまでテストを書くか(CTO視点の線引き)
最後に、実務でよく聞かれる「全部の画面に system spec を書くべき?」への答えです。結論は「No。壊れると痛い動線に絞る」です。system spec は本物のブラウザを動かすぶん実行が遅く、全網羅するとCIが重くなって誰もテストを回さなくなります。
おすすめは、①ログイン・決済・申込みなど「壊れると事業が止まる導線」 ②過去に本番でバグった画面の2つに集中投下すること。細かい分岐はモデルスペックやリクエストスペックに任せ、system spec は「一番大事な数本」を守る盾として使うのが、費用対効果の高い運用です。テストが落ちた原因の調べ方はエンジニアの「調べ方」の記事も参考にしてください。
まとめ
Rails のシステムテスト(system spec)は、「人がブラウザで手作業していた確認」をそのまま自動化する仕組みです。要点をまとめます。
- これから書くなら feature spec ではなく system spec(スクショ自動・DB後片付け自動)
- セットアップは
rspec-rails+capybara+selenium-webdriver。webdrivers gem はもう不要 - テストの型は 「①データ用意 → ②
visit・fill_in・click_on→ ③have_contentで検証」の3段 - JS が絡むテストにだけ
js: true。速度が気になったら Cuprite へ - 全網羅せず、壊れると痛い導線に絞る
まずは STEP1 の「ページが開けるか」の1本から。緑になる体験ができれば、あとは型に流し込むだけです。認証まわりのテスト対象を用意したい方はDeviseのカスタマイズ記事、一覧・フォームの実装はRailsブログ機能の実装記事、画像アップロードを含む画面はActiveStorageアバター機能の記事も合わせてどうぞ。
スポンサーリンク
