Zack
Zack

Reputation: 2497

Ruby Selenium Capybara not timing out

I'm having issues with my specs not timing out. Some of my specs are getting to a certain point and just hanging. I'm sure there is something wrong with one of the spec resulting in it being broken, what I can't figure out is why they are just hanging indefinitely when I have a timeout defined...

# frozen-string-literal: true
require 'rspec'
require 'capybara/rspec'
require 'capybara/dsl'
require 'selenium-webdriver'
require 'site_prism'

Dir[File.dirname(__FILE__) + '/page_objects/*/*.rb'].each do |page_object|
  require page_object
end

def wait_for_ajax
  Timeout.timeout(Capybara.default_max_wait_time) do
    loop until page.evaluate_script('jQuery.active').zero? && page.has_no_css?(".k-loading-color")
  end
end

def whole_page
  Capybara.current_session
end

Capybara.register_driver :selenium do |app|
  Capybara::Selenium::Driver.new(app, browser: :chrome)
end

Capybara.default_driver = :selenium
Capybara.app_host = #REDACTED
Capybara.default_max_wait_time = 20

RSpec.configure do |config|
  config.before(:each) do
    config.include Capybara::DSL
  end

  config.after(:each) do
    Capybara.reset_sessions! 
  end
end

Upvotes: 1

Views: 286

Answers (1)

Thomas Walpole
Thomas Walpole

Reputation: 49870

You don't mention what commands it's hanging on, but I'm going to guess it's in your wait_for_ajax method. If that's the case it's because you're using Timeout.timeout which is the most dangerous to use method Ruby provides. The way it works is by starting a second thread which will then raise an exception in the original thread when the timeout occurs. The problem with that is the exception can occur anywhere in the original thread which means if the block inside the timeout call is doing anything non-trivial it can end up in a completely unrecoverable state (network comms, etc). Basically Timeout.timeout can only ever be safely used with a VERY detailed knowledge of every little thing occurring it its block, which means it effectively should never be used around any calls to a third party library. Instead you should just use a timer and sleep if you need timeout. Something like

def wait_for_ajax
  start = Time.now
  until page.evaluate_script('jQuery.active').zero? && page.has_no_css?(".k-loading-color", wait: false) do
    sleep 0.1
    raise <Some Error> if (Time.now - start) > Capybara.default_max_wait_time
  end
end

That being said you really shouldn't need wait_for_ajax with a useable UI and properly written tests.

Additionally, by including capybara/rspec you've already set up for reset_sessions to be called after every test and for Capybara::DSL to be included into the types of tests it should be included into - https://github.com/teamcapybara/capybara/blob/master/lib/capybara/rspec.rb#L9 - so by adding your own after block you're just ending up calling reset_sessions twice after every test which is just a waste of time.

Upvotes: 3

Related Questions