【Ruby on Rails】System SpecでE2Eテストを書く

Ruby

こんにちは、かつコーチです。

モデル単体のテストは書けても、ブラウザ操作を再現するE2Eテストとなると、設定の複雑さで挫折した経験がある方も多いのではないでしょうか。

この記事では、CapybaraベースのSystem Specの設定と、実践的なブラウザ操作テストの書き方を解説します。

ユニットテストだけではカバーできない、画面遷移を含めた動作保証をSystem Specで実現しましょう。

System Spec(Capybara)の設定

必要なgemの追加

System SpecはCapybaraとSeleniumを組み合わせて動作します。

# Gemfile
group :test do
  gem "capybara"
  gem "selenium-webdriver"
end

Rails 8系ではcapybaraselenium-webdriverがデフォルトのGemfileに含まれていることが多いですが、コメントアウトされている場合は解除してください。

rails_helperでのドライバ設定

spec/rails_helper.rbに、System Spec用の設定を追加します。

# spec/rails_helper.rb
RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    driven_by :selenium_chrome_headless
  end
end

rack_testはJavaScriptを実行しないため高速ですが、Ajaxを伴う画面操作は再現できません。

JavaScriptの動作確認が必要なテストには、js: trueのタグを付けてselenium_chrome_headlessを使い分けます。

spec/systemディレクトリの作成

System Specはspec/system配下に配置するのが規約です。

mkdir -p spec/system

ジェネレータを使う場合は、次のコマンドでひな形付きのファイルを生成できます。

rails generate rspec:system Article

ブラウザ操作をシミュレートするテストの書き方

ログインからの一連の操作を再現する

投稿作成画面へのアクセスからフォーム送信までを、実際のユーザー操作に沿って記述します。

# spec/system/articles_spec.rb
require "rails_helper"

RSpec.describe "Articles", type: :system do
  let(:user) { create(:user) }

  before do
    driven_by :rack_test
    visit new_user_session_path
    fill_in "Email", with: user.email
    fill_in "Password", with: user.password
    click_button "Log in"
  end

  it "新しい記事を投稿できること" do
    visit new_article_path
    fill_in "Title", with: "System Specのテスト記事"
    fill_in "Body", with: "System Specで作成した本文です。"
    click_button "登録する"

    expect(page).to have_content "System Specのテスト記事"
    expect(page).to have_current_path(article_path(Article.last))
  end
end

fill_inはラベルまたはname属性でフォーム要素を特定します。

click_buttonでボタンを押下し、have_contentで画面遷移後の表示内容を検証しています。

Ajax操作を含むテスト

いいねボタンなど、Ajaxで非同期に画面が更新される機能はjs: trueを指定します。

# spec/system/likes_spec.rb
require "rails_helper"

RSpec.describe "Likes", type: :system, js: true do
  let(:user) { create(:user) }
  let(:article) { create(:article) }

  it "いいねボタンを押すと件数が増えること" do
    sign_in user
    visit article_path(article)

    expect {
      click_button "いいね"
    }.to change { page.find("#like-count").text.to_i }.by(1)
  end
end

changeマッチャーでボタン押下前後の表示テキストを比較することで、非同期更新の結果を検証できます。

Deviseを導入している場合、sign_inヘルパーをspec/rails_helper.rbで有効化しておくと、ログイン処理を毎回書かずに済みます。

# spec/rails_helper.rb
RSpec.configure do |config|
  config.include Devise::Test::IntegrationHelpers, type: :system
end

よくあるつまずきポイント・エラー対処

Chromeが見つからずテストが起動しない

私が初めてSystem Specのjs: trueテストを実行したとき、次のエラーで止まりました。

Selenium::WebDriver::Error::WebDriverError: unable to obtain driver for chrome

❌Before

# spec/rails_helper.rb
config.before(:each, type: :system, js: true) do
  driven_by :selenium_chrome
end

selenium_chromeはヘッドレスではないため、CI環境や画面のないサーバーではブラウザを起動できません。

✅After

# spec/rails_helper.rb
config.before(:each, type: :system, js: true) do
  driven_by :selenium_chrome_headless
end

selenium_chrome_headlessに変更したことで、画面を持たない環境でもテストが正常に動作するようになりました。

ローカルでデバッグ目的で画面表示を確認したい場合は、一時的にselenium_chromeへ切り替えて実行すると挙動を目視できます。

スクリーンショットが保存されずデバッグが難航する

テストが失敗した際、原因を特定するにはスクリーンショットが手がかりになります。

❌Before

デフォルト設定のままテストを実行すると、失敗時のスクリーンショットがtmp/screenshotsに散らばり、どのテストの失敗か分かりにくい状態でした。

✅After

# spec/rails_helper.rb
RSpec.configure do |config|
  config.after(:each, type: :system) do |example|
    if example.exception
      page.save_screenshot("tmp/screenshots/#{example.full_description.parameterize}.png")
    end
  end
end

失敗したテストの説明文をファイル名に含めることで、どのテストが失敗したのかを一目で判別できるようになりました。

CI上でアーティファクトとして保存する設定と組み合わせると、リモート実行時のデバッグ効率も大きく向上します。

まとめ

この記事のポイント

  • System Specはspec/system配下に配置し、driven_byでドライバを切り替える
  • JavaScriptを伴う操作にはjs: trueselenium_chrome_headlessを組み合わせる
  • DeviseのDevise::Test::IntegrationHelpersで、ログイン処理をsign_inで簡略化できる
  • CI環境ではselenium_chromeではなくselenium_chrome_headlessを使う
  • 失敗時のスクリーンショット保存設定は、デバッグ効率を大きく左右する

次に読むべき記事

  • RSpecでモデルのテストを書く
  • Deviseで本格的な認証機能を実装する

タグ: Ruby on Rails, 上級者向け, テスト

タイトルとURLをコピーしました