こんにちは、かつコーチです。
モデル単体のテストは書けても、ブラウザ操作を再現する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系ではcapybaraとselenium-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: trueとselenium_chrome_headlessを組み合わせる - Deviseの
Devise::Test::IntegrationHelpersで、ログイン処理をsign_inで簡略化できる - CI環境では
selenium_chromeではなくselenium_chrome_headlessを使う - 失敗時のスクリーンショット保存設定は、デバッグ効率を大きく左右する
次に読むべき記事
- RSpecでモデルのテストを書く
- Deviseで本格的な認証機能を実装する
タグ: Ruby on Rails, 上級者向け, テスト