/ ruby

Wait for AJAX with Capybara

Hiring developers? Let us at Evalcrew evaluate your candidates and hire only the best!

In a recent Rails project we had some issues with flaky feature tests where we used Capybara. Some tests would fail every second time or so. When I investigated the problem I found that it was a form that was updated with AJAX when a checkbox was checked. The updated form would have some different content, and the next Capybara action - clicking a button with an updated text would fail if Capybara tried to click the button before the AJAX request and DOM update was completed.

When I knew what was causing the tests to sporadically fail I thought that I would surely find someone else with this problem. Shortly I found the blog post Automatically Wait for AJAX with Capybara. It was a bit old, but I soon found at that it worked...to some degree. The tests with this click button would still fail from time to time.

If we take a closer look at the following method:

def wait_for_ajax
  Timeout.timeout(Capybara.default_max_wait_time) do
    loop until finished_all_ajax_requests?
  end
end

If there is an ongoing AJAX request, it will wait up to Capybara.default_max_wait_time for it to finish and then return. At first glance, it seems right. But what if this method is entered before the AJAX request has fired? Then it will just return without waiting.

To wait for the AJAX request to fire it should work to just reverse the loop above. That is, loop while finished_all_ajax_requests?. But what if the AJAX request is already finished? Then this loop would just go on forever. I decided to put a timeout of 0.2 seconds, just in case this would happen. I also wrapped the Timeout.timeout calls in a begin/rescue block.

My improved version of the method look like this:

def wait_for_ajax
  # Wait for first request if not already done
  begin
    Timeout.timeout(0.2) do
      loop while finished_all_ajax_requests?
    end
  rescue
  end

  # Wait for all AJAX requests to complete
  begin
    Timeout.timeout(Capybara.default_max_wait_time) do
      loop until finished_all_ajax_requests?
      sleep AJAX_COMPLETE_WAIT
    end
  rescue
  end
end

If the original solution did not work for you, then I hope that this will help you too getting a more robust test suite, as it did for me!