Railsのsystemテストは、ランダムに落ちたり落ちなかったりすることがあります。ここでは、ランダムに落ちるテストをどうにかするためのコツを2つ紹介します。
基本
systemテスト(ブラウザーを使ったE2Eテスト)では、次の3つのアプリケーション(プロセスまたはスレッド)がバラバラに動いていると考えてください。

テストがランダムに落ちる原因でよくあるのは、テストが進行するに従って、テストプログラムがほかを置いて先走ったり、ブラウザーが先走ったりすることです。
systemテストでこの3つの進み具合を揃え、ランダムに落ちないようにする基本は、何か動作を起こすたびに、画面のチェックを入れることです。
click_link '製品一覧'
expect(page).to have_css('h1', text: '製品一覧') # これを省略しない!
click_link '石けん'
expect(page).to have_css('h1', text: '石けん')
「製品一覧」などのテキストが出るまでCapybaraがうまいこと待機してくれるので、sleepを入れる必要はありません。たいていはこれでうまく行きますが、それでもランダムに落ちるケースでは、次の2つの原因を疑ってみてください。
1. Ajax読み込みを確認してから次へ行く(フラッシュ対策)
「製品を登録しました」のようなフラッシュのテキストをチェックしているときに、フラッシュが画面に出なくてテストがランダムに落ちる、というケースです。この場合、フォームの送信中にAjax通信が来て、フラッシュがAjaxに取られている可能性があります。
例えば、フォームを表示するときにAjaxで追加のデータを取っているようなときは、Ajaxの読み込みが完了したことを確認したうえで、送信ボタンをクリックします。次の例は、カテゴリー一覧をAjaxで取ってリストに入れているときの書き方です。
click_link '新規登録'
expect(page).to have_css('h1', text: '製品の登録')
expect(page).to have_css('#category option', count: 5) # Ajax読み込み待ち
fill_in '製品名', with: '石けん'
click_button '登録する'
expect(page).to have_text('製品を登録しました。')
2. DBにデータをすべて用意してからテストを始める
似たようなテストを並べて、データをちょっとだけ変えたいというときに、systemテストの進行中にDBを操作すると(クリック、クリック、DB更新、クリック……)、テストがランダムに落ちることがあります。ブラウザーが画面を取得したときにDB更新が間に合っていないせいです。ローカルでは起きなくてもCI環境で起こることがあります。
この場合は、テスト全体を始める前に必要なデータを作るようにします。次の「添付品あり」の例のように途中でデータを作っているテストがあるとします。
describe "製品の登録" do
before do
login
click_link '製品一覧'
expect(page).to have_css('h1', text: '製品一覧')
end
it "通常の場合" do
click_link '新規登録'
fill_in '製品名', with: 'スペシャル石けん'
click_button '登録する'
expect(page).to have_text('製品を更新しました。')
end
it "添付品あり" do
create(:attached_product, name: 'おまけ') # ここでレコード作らない!
click_link '新規登録'
fill_in '製品名', with: 'スペシャル石けん'
check 'おまけ'
click_button '登録する'
expect(page).to have_text('製品を登録しました。')
end
end
こういうテストが落ちるときは、多少コードが長くなっても、次のような修正を入れます(テストコードをきれいにしたかったら、この後で考えます)。
describe "製品の登録" do
before do
login
click_link '製品一覧'
expect(page).to have_css('h1', text: '製品一覧')
end
it "通常の場合" do
click_link '新規登録'
fill_in '製品名', with: '石けん'
click_button '登録する'
expect(page).to have_text('製品を更新しました。')
end
end
describe "添付品あり製品の登録" do
before do
create(:attached_product, name: 'おまけ') # 始めに移動
login
click_link '製品一覧'
expect(page).to have_css('h1', text: '製品一覧')
end
it "添付品あり" do
click_link '新規登録'
fill_in '製品名', with: '石けん'
check 'おまけ'
click_button '登録する'
expect(page).to have_text('製品を登録しました。')
end
end
なお、systemテスト以外では、テストの途中でデータをいじるのはアリだと思っています(美しさにこだわる人は嫌がるかもしれません)。
3. アニメーションで落ちる(研究中)
jQueryやBootstrap、そのほかCSSやJavaScriptのアニメーションで落ちることがあります。この対策についてはまたあとで。