Capybara: Waiting for pending AJAX requests after a test

Posted About 9 years ago. Visible to the public. Repeats.

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

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
Last edit
Over 2 years ago
Henning Koch
Keywords
wait_for_ajax_requests, wait_for_pending_ajax_requests, cucumber, patiently, rspec, test, scenario
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra dev (2015-04-09 13:35)