Synchronize Javascript in Capybara 2

In a project i’m working on i’m migrating our old integration specs with Capybara 1 to use Capybara 2. The last version of Capybara makes you to write better integration specs cause is more strict with the CSS selectors and content you use to find elements.

The big changes with Capybara 1 can be found here. The important change i’ll talk about is the missing wait_until method we had in Capybara 1. It doesn’t exist anymore in Capybara.

If we need to wait for an element to appear in the DOM cause of an AJAX request we can use matchers like have_css, have_selector or have_content. They’ll wait a time for the element to appear. This time by default is 2 seconds but you can change it just setting Capybara.default_wait_time.

Ok, this is the way to wait for the elements to appear in the DOM, but what we’d like it’s to wait for the result of evaluating some Javascript statement. This is a little more complex and it seems that Capybara doesn’t treat this case.

We want to evaluate $.active and check if the return value is 0. The matcher methods i’ve talked before use all of them the method synchronize of Capybara. We can take a look to this method:

def synchronize(seconds=Capybara.default_wait_time)
  start_time = Time.now

  if session.synchronized
    yield
  else
    session.synchronized = true
    begin
      yield
    rescue => e
      raise e unless driver.wait?
      raise e unless catch_error?(e)
      raise e if (Time.now - start_time) >= seconds
      sleep(0.05)
      raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if Time.now == start_time
      reload if Capybara.automatic_reload
      retry
    ensure
      session.synchronized = false
    end
  end
end

This method yields the block you pass and rescues any exception raised in the block. It will re-raises the exception unless some condition avoid it and then the block will be retried. The condition we are interested in is this one:

raise e unless catch_error?(e)

And catch_error? method is like this:

def catch_error?(error)
  (driver.invalid_element_errors + [Capybara::ElementNotFound]).any? do |type|
    error.is_a?(type)
  end
end

So synchronize doesn’t re-raises the exception if it’s one of the driver’s invalid_element_errors or Capybara::ElementNotFound. The invalid_element_errors is defined by the driver and depending on the kind of driver you are using (Poltergeist, Selenium, Webkit) you will need perhaps to monkeypatch the driver.

We can use Capybara::ElementNotFound or a class inheriting of it to control the exception raising in the block we want to synchronize. So a way to wait for a expected result in Javascript could have this implementation:

page.document.synchronize do
  page.evaluate_script('$.active') == 0 or raise Capybara::ElementNotFound
end

Discussion, links, and tweets

comments powered by Disqus

I'm a software developer at Wuaki.tv. Follow me on Twitter or on GitHub.