ローカルの環境で単体でのテストの場合はパスするのですが、
一括でのテストの場合に、たまにエラーになるケースがあります。

環境は下記のような感じです。

  • 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 methodperform_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