Capybara ajax race conditions

2019-04-13 09:50发布

问题:

I am frequently running into issues in capybara with request tests failing because capybara is not waiting for ajax events to complete before moving on.

Google seems to indicate I should be using :resynchronize option for my tests to fix this. But it is not working.

To prove this is an issue, failing tests can be fixed by putting a sleep statement after the ajax call. This seems hacky any bad practice as the appropriote delay will vary depending on the speed of the machine running the tests. And picking a suitably large value will seriously slow down running a test suite with lots of ajax actions.

An example of a failing / passing test is below. The sleep before clicking on save makes the difference between passing / failing on page.should have_content('custom item'):

it "should be able create a new todo item", :js, :resynchronize, :focus do
  # Visit new action
  visit new_todo_list


  # Fill in the name
  fill_in "#name", "test list"

  # Click on add item to launch inline popup
  find('a.add-item').click
  within '#new_item' do
    fill_in 'todo_item_description', :with => 'custom item'
    # Run ajax action to add currrent item
    find('.btn').click
  end

  sleep(1)

  # Save
  find('a.save').click

  page.should have_content('test list')
  page.should have_content('custom item')

end

Is this a bug in capybara or am I doing something wrong?

Thanks for any help...

回答1:

I had issues with this a while back, and used this approach to figure out when the ajax requests are done:

wait_until do
  page.evaluate_script('$.active') == 0
end

Still pretty hacky, but slightly better than using sleep. I got it from here. I'm using it for Cucumber features but it should work in rspec request specs as well.

Update (6/19/2013)

wait_until was removed from Capybara in version 2.0, see: Why wait_until was removed from Capybara for details on why.

I've followed one of the suggestions and implemented it anyway, just for this one case (which I think is justified):

def wait_until
  require "timeout"
  Timeout.timeout(Capybara.default_wait_time) do
    sleep(0.1) until value = yield
    value
  end
end