Edited at

sleepやリトライに頼らない安定した feature spec を書こう

js: true なテストは失敗しがち。


  • 成功したり失敗したりする

  • ローカルだと成功するのにCIサービスでは失敗する

というような現象に悩まされます。

よくある対処としては


  • sleep を入れる

  • rspec_retry gem でリトライする

  • click_link できないから trigger('click') する

ですが、失敗頻度が下がっても完全に直るわけではなかったりします。

でも、よくよく調べてみるとテストコードの書き方で回避できることが多いです。私が出会ったいくつかの失敗パターンをまとめましたので、参考にしてみてください。

(※ poltergeist や selenium-webdriver (headless chrome) で起きた例です)


処理が終わってないのに次に進んでしまうパターン


Ajax が終わってないのに Ajax の結果をチェックしてしまう


失敗するコード

fill_in 'メッセージ', with: 'こんにちは'

click_link '投稿' # ajax
expect(Message.count).to eq 1


修正方法(その1)

モデルを直接テストせず、ブラウザの表示をテストしましょう。

ブラウザのDOMに対するテストであれば capybara がデフォルトで2秒はリトライしてくれるので、重い処理でない限りは成功します。

fill_in 'メッセージ', with: 'こんにちは'

click_link '投稿' # ajax
expect(page).to have_content 'こんにちは'


修正方法(その2)

どうしてもブラウザの表示をテストしづらいときは、 wait_for_ajax を使って待つ手もあります。(jQuery を使っている場合)

fill_in 'メッセージ', with: 'こんにちは'

click_link '投稿' # ajax
wait_for_ajax
expect(Message.count).to eq 1


メール送信処理が行われていないのにメールチェックしてしまう


失敗するコード

email_spec gem を使ってるコードです。

click_link '申請する'

# click_link によるサーバー側の処理が終了する前に open_last_email が呼ばれて失敗することがある
expect(open_last_email).to have_body_text '申請ありがとうございました。' # error! (open_last_email が nil)


修正方法

click_link '申請する'

# ページ遷移後のテキストのチェックをすれば、サーバー処理完了待ちになる
expect(page).to have_content '申請が完了しました'
expect(open_last_email).to have_body_text '申請ありがとうございました。'


deliver_later なメール送信処理が行われない

perform_enqueued_jobs を使うのはもちろんですが、 perform_enqueued_jobs のブロックの中でリクエストが完了する必要があります。


失敗するコード

capybara 2.18.0 + headless chrome で確認。

capybara 2.17.0 だと失敗しない。

confirm を使わない場合だと失敗しない。

# perform_enqueued_jobs で deliver_later なメールもすぐに送られるはずと思いきや...

perform_enqueued_jobs do
accept_confirm do
click_link '申請する' # deliver_later するアクション
end
end
# perform_enqueued_jobs を抜けた後で deliver_later が行われた場合、メール送信ジョブは即時実行されない
expect(page).to have_content '申請が完了しました'
expect(open_last_email).to have_body_text '申請ありがとうございました。' # error! (open_last_email が nil)


修正方法

perform_enqueued_jobs do

accept_confirm do
click_link '申請する' # deliver_later するアクション
end
# perform_enqueued_jobs の中でリクエスト完了を待てば、確実にメール送信ジョブが即時実行される
expect(page).to have_content '申請が完了しました'
end
expect(open_last_email).to have_body_text '申請ありがとうございました。'


クリックの後にページ遷移を待たずに visit してる


失敗するコード

fill_in 'メッセージ', with: 'こんにちは'

click_on '投稿する'
# ボタンをクリックした直後に別ページを開くと、リクエストがキャンセルされることがある
visit posts_path
expect(page).to have_content 'こんにちは' # error!


修正方法

fill_in 'メッセージ', with: 'こんにちは'

click_on '投稿する'
# ページ遷移を待てばリクエスト完了している
expect(page).to have_content '投稿されました'
visit posts_path
expect(page).to have_content 'こんにちは'


ページ遷移の前後に同じ名前の要素がある

リクエスト完了を待っているつもりが待ててない。

同じ文字列を含む別のリンクをクリックしてしまう。

TODO: 後でサンプルコードを書きたい


クリック失敗パターン


動く要素をクリックしようとしてミスしている

click_link は内部的には、


  • クリック対象要素の座標を調べる

  • 座標をクリックする

という手順で行われます。

スライドしながら現れるモーダルダイアログなどでは、要素の座標を調べてからクリックするまでの間に要素が移動してしまい、クリック失敗することがあります。

クリックできなくてもエラーにならずに先に進んでしまい、問題の原因とは違う場所でエラーが起きます。


修正方法

アニメーションの完了を待つのがよいです。

http://qiita.com/shunichi/items/003c32a454eae8e3fa8a


他の要素に隠されていてクリックできない

position: fixed なフッタなど別の要素がたまたまクリックしたい要素の上にあるとき、クリック失敗してしまいます。


修正方法


  • ブラウザのウインドウサイズを変更する

  • スクロールする


日本語フォントが入ってない

自分で作ったDockerイメージをCIで使ったところ、 poltergeist で Poltergeist.MouseEventFailed というエラーが発生しました。

スクリーンショットを取ったところ日本語が表示されていませんでした。

おそらく、日本語リンクの領域の面積がクリックできないほど小さくなってしまっていたのでしょう。


修正方法

CIで使っているDockerイメージにフォントをインストールしたら直りました。

http://qiita.com/6in/items/75230d5455465d5e911f

http://t-cyrill.hatenablog.jp/entry/2014/10/05/123803