Read more

Capybara: Waiting for pending AJAX requests after a test

Henning Koch
April 09, 2015Software engineer at makandra GmbH

When ending a Selenium test Capybara resets the browser state by closing the tab, clearing cookies, localStorage, etc.

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

It may be a good idea to wait for all in-flight AJAX requests to finish before ending a scenario:

  • You may have client-side JavaScript that freaks out when the tab closure kills their pending requests. If that JavaScript opens an error alert or spams errors to the console, your test may fail after the last step.
  • With unlucky timing the server may receive an AJAX request as the browser tab closes, causing a connection abort when trying to send the response. Since Capybara fails a test if there is a server error this may produce a flaky test.

Waiting for pending AJAX requests

The following module exposes a method conclude_browser_requests. It prevents the browser from sending further AJAX requests and waits until all in-flight requests have finished:

module ConcludeBrowserRequests
  def conclude_browser_requests
    return unless page.driver.is_a?(Capybara::Selenium::Driver)

    page.execute_script(<<-JS)
      window.XMLHttpRequest.prototype.send = function() {
        // Don't send a request. The readyState will remain at XMLHttpRequest.UNSENT.
      }

      window.fetch = function() {
        // To prevent callbacks, return a promise that never settles.
        return new Promise(function() {})
      }
    JS

    Capybara::Lockstep.synchronize
  end
end

Note that this method uses capybara-lockstep Show archive.org snapshot in the last line.

Include this module in your test suite and call conclude_browser_requests at the end of each test. For example in a Cucumber suite:

World(ConcludeBrowserRequests)

After do
  conclude_browser_requests
end

Legacy solution for jQuery frontends

The module above is compatible with all JavaScript frameworks (or VanillaJS). If you cannot use capybara-lockstep Show archive.org snapshot and you're working on an application with a pure jQuery frontend, you may also use the module below stead.

Note that this will only stop and wait for requests made by jQuery's $.ajax() or $.get() functions.

module ConcludeBrowserRequestsWithJquery
  def conclude_browser_requests_with_jquery
    return unless page.driver.is_a?(Capybara::Selenium::Driver)

    # disable future ajax requests
    page.execute_script <<-JAVASCRIPT
      if ( (typeof jQuery) !== 'undefined' ) {
        jQuery.ajaxPrefilter(function(options, originalOptions, jqXHR) {
          jqXHR.abort();
        });
      }
    JAVASCRIPT

    # wait for active ajax requests to complete
    patiently do
      page.execute_script("return (typeof jQuery) === 'undefined' || jQuery.active === 0").should == true
    end
  end
end

The code above depends on Spreewald Show archive.org snapshot for patiently.

Henning Koch
April 09, 2015Software engineer at makandra GmbH
Posted by Henning Koch to makandra dev (2015-04-09 15:35)