Rspecのフィーチャーテストが失敗したりしなかったり
ローカルの環境で単体でのテストの場合はパスするのですが、
一括でのテストの場合に、たまにエラーになるケースがあります。
環境は下記のような感じです。
- ruby 2.1.3
- rails 4.1.8
- rspec 3.1.0
- rspec-core-3.1.7
- capybara-2.4.4
- poltergeist-1.5.1
- phantomjs 1.9.8
config.order = 'random'
にしています。
randomでない場合に一括実行すると必ずエラーになるのでrandomにしています。
テストコードは、具体的には以下。その他にも同じようにランダムにエラーになる箇所があります。
scenario '顧客を新規追加する', js: true do
visit new_customer_path
tab = first('#new_customer') # Ambiguous match by rack_test
within tab do
fill_in 'customer_name', with: 'customer'
end
expect {
click_button I18n.t('helpers.submit.create')
}.to change(Customer, :count).by(1)
end
エラー内容は、他のケースでも以下のような感じです。
このようなケースに陥った方アドバイスをお願いします。
その他、色々と調べた結果、Ajax使用箇所では、以下のようなスニペットを用いたりしてみましたが・・
module WaitForAjax
def wait_for_ajax
Timeout.timeout(Capybara.default_wait_time) do
loop until finished_all_ajax_requests?
end
end
def finished_all_ajax_requests?
page.evaluate_script('jQuery.active').zero?
end
end
rspec_helperは以下の通りです。
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
require 'email_spec'
require 'shoulda-matchers'
require 'capybara/rails'
require 'capybara/rspec'
require 'capybara-screenshot'
require 'capybara-screenshot/rspec'
require 'capybara/poltergeist'
require 'capybara/webkit'
require 'devise'
require 'webpay/mock'
require 'pundit/rspec'
require 'simplecov'
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, inspector: true, js_errors: false, timeout: 60, debug: false)
end
Capybara.javascript_driver = :poltergeist
Capybara.default_wait_time = 5
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
ActiveRecord::Migration.maintain_test_schema!
DEFAULT_HOST = Settings.host
RSpec.configure do |config|
if ENV['CI'] == 'true'
config.filter_run_excluding webpay: true
else
config.filter_run_excluding slow: true, omit: true
config.filter_run_excluding service: true
end
config.before :suite do
FactoryGirl.reload
OmniAuth.config.test_mode = true
WebMock.disable_net_connect!(allow_localhost: true)
DatabaseCleaner.clean_with(:deletion)
load Rails.root.join('db', 'seeds.rb')
end
config.before :each do |example|
allow_any_instance_of(Account::Setting).to receive(:geocode).and_return([1,1])
if example.metadata[:js]
DatabaseCleaner.strategy = :deletion #
else
DatabaseCleaner.strategy = :transaction
end
DatabaseCleaner.start
end
config.after :each do |example|
page.driver.reset!
DatabaseCleaner.clean
if example.metadata[:js]
load Rails.root.join('db', 'seeds.rb')
end
end
config.after :suite do
end
config.infer_spec_type_from_file_location!
config.order = 'random'
## Capybara
config.include Capybara::DSL
Capybara.default_host = 'http://' + DEFAULT_HOST
# FactoryGirl
config.include FactoryGirl::Syntax::Methods
# EmailSpec
config.include(EmailSpec::Helpers)
config.include(EmailSpec::Matchers)
# Webpay
config.include WebPay::Mock::WebMockWrapper
# Devise etc..
config.include Devise::TestHelpers, type: :controller
config.extend ControllerMacros, type: :controller
config.include OmniauthMacros
config.include FeatureMacros, type: :feature
config.include WaitForAjax, type: :feature
end
==== 補足 =======
config.after(:each, js: :true) { wait_for_ajax }
をspec_helper.rbに追記。
かつ、config.order = 'random'
をコメントアウト。
spec spec/feature/
のみテスト。
scenario '顧客を新規追加する', js: true do
visit new_customer_path
wait_for_ajax
tab = first('#new_customer')
within tab do
fill_in 'customer_name', with: 'customer'
end
expect {
click_button I18n.t('helpers.submit.create')
wait_for_ajax
}.to change(Customer, :count).by(1)
end
上記のテストコードでエラーとなる。(61 examples, 1 failures)
しかし、再度実行すると、エラーとなります。(エラーになるかどうかは不定)。
========= 追記 ============
別件で以下のエラー対策にtest.rbを編集しました。
Circular dependency detected while autoloading constant
Circular dependency detected while autoloading constant を防止する
- config/environments/test.rb
- config.allow_concurrency = false
すると、エラーの様相が変わりました。
Capybaraのスクリーンショットなどが取得できるようになりました。
エラー内容もmouse event のクリックができないなどになりました。
- 上記の設定変更はAjax使用時の並行処理発生を防止する
- WaitforAjax絡みで、DBへの並行接続が問題になっているという情報があった(過去に見たのでリンク不明)
なので、今まで出ていたエラーの原因はDB絡みと仮定します。
しかし、現段階でもランダムにエラーは発生します。
エラーの中にmouse event clickに関するエラーも発生したので、
設定変更以降にエラーになる原因は、タイミングなのかなと思います。
試しにWaitForAjaxで、sleep
を入れて試してみました。
module WaitForAjax
def wait_for_ajax
Timeout.timeout(Capybara.default_wait_time) do
loop until finished_all_ajax_requests?
end
sleep 0.5
end
def finished_all_ajax_requests?
page.evaluate_script('jQuery.active').zero?
end
end
さらに、wait_for_ajaxを以下の箇所に追加してみました。
- visitの後
- clickなどの処理すべて
すると、エラーの発生頻度が低くなりました(数十回程度試した結果ですが。)
推測ですが、以下の要因が混在していたのだと思います。
- Ajaxによる並行接続でのDBエラーと
- Capybaraの画面操作のタイミング
今までは全体のテストの際に数回に1回程度エラーが出ていましたので、
少しの間様子見をし、また経過を追加します。
追加した箇所の例を記載しておきます。
以下のコードのfind_button(I18n.t('posts.update_state.finish')).click
の後の
wait_for_ajax
がなければ、
undefined method
perform_deliveries' for nil:NilClass`が発生します。
(タイミングによりますが)
scenario 'example', js: true do
post.accept!
visit post_path(token: post.token)
wait_for_ajax
ActionMailer::Base.deliveries.clear
find_button(I18n.t('posts.update_state.finish')).click
wait_for_ajax
within '#update-finished-modal-content' do
check('post_send_mail')
find_button(I18n.t('posts.modal.update_finished.submit')).click
end
find_button(I18n.t('helpers.submit.continue')).click
wait_for_ajax
expect(open_last_email_for(user.email)).to be_delivered_to user.email
end
=========== 追記 ===============
ローカル環境とCI上(外部サービス)で20回程度テストして、1回だけ以下のエラーが出ました。
- undefined method `perform_deliveries' for nil:NilClass
エラー発生の頻度は下がりましたが、根本的に解決とはいえないのですし、wait_for_ajaxを使わないとpoltergeistのJSテストが成り立たないというのも変な感じです。
poltergeistによるRspecのJSテストについて皆さんがそのような対応をしているとは思えないので、
引き続き回答をお待ちしています。
ちなみにCapybara-webkitに変更しても同じような箇所でエラーになります。
Capybara-webkitの場合、現在の設定でもエラーが出ます。
Webkitでのエラーの内容例
Capybara::Webkit::InvalidResponseError:
Unable to load URL: http://127.0.0.1:63380/users/reservations because of error loading http://127.0.0.1/mypage: Unknown error- undefined method `perform_deliveries' for nil:NilClass