17

I have the following typical cucumber steps in a Rails 3.1 project:

...
When I follow "Remove from cart"
Then I should see "Test Product removed from cart"

The difficulty is that "Remove from cart" button is an ajax :remote call, which returns "Test Product removed from cart" to the #cart_notice element via:

$('#cart_notice').append("<%= @product.name %> removed from cart");

The function works fine in the browser, but doesn't find the "Test Product removed from cart" text in cucumber. I'm guessing this is because Cucumber is searching for the text before the AJAX returns it?

So, in short...how do I ensure cucumber waits for the ajax to return a result before searching for the desired content?

PlankTon
  • 12,443
  • 16
  • 84
  • 153

4 Answers4

17

To add to what dexter said, you may want to write a step that executes JS in the browser which waits for ajax requests to finish. With jQuery, I use this step:

When /^I wait for the ajax request to finish$/ do
  start_time = Time.now
  page.evaluate_script('jQuery.isReady&&jQuery.active==0').class.should_not eql(String) until page.evaluate_script('jQuery.isReady&&jQuery.active==0') or (start_time + 5.seconds) < Time.now do
    sleep 1
  end
end

You can then include the step as needed, or after every javascript step:

AfterStep('@javascript') do
  begin
    When 'I wait for the ajax request to finish'
  rescue
  end
end

I was having issues with the automatic synchronization, and this cleared it up.

warren
  • 32,620
  • 21
  • 85
  • 124
jcnnghm
  • 7,426
  • 7
  • 32
  • 38
  • I believe you are missing a "do" at the end of the page.evaluate_script line. – John Naegle Mar 07 '12 at 17:28
  • Do we really need this `page.evaluate_script('jQuery.isReady&&jQuery.active==0').class.should_not eql(String)` assertion? I can't find when `page.evaluate_script('....').class` will be equal `String` – bronislav Jun 27 '13 at 12:38
  • It should be step('I wait for the ajax request to finish') instead of When 'I wait for the ajax request to finish' – Mehmet Davut Jun 21 '15 at 14:28
  • I don't understand how ppl can write a string that requires almost a 4K screen to be read without scrolling.... – Cyril Duchon-Doris Feb 23 '17 at 16:19
0

I suppose wait_until should do the job. It will command to capybara to check something until its true for some time.

Mark Huk
  • 2,379
  • 21
  • 28
0

Old question, but the Spreewald gem should help https://github.com/makandra/spreewald

You can use the patiently method from the Spreewald gem like so:

Then /^I should see "([^\"]*)" in the HTML$/ do |text|
  patiently do
    page.body.should include(text)
  end
end

The step will maintain a loop for a period of time until the desired text appears in the test dom, else the step will fail.

(taken from https://makandracards.com/makandra/12139-waiting-for-page-loads-and-ajax-requests-to-finish-with-capybara)

ethaning
  • 386
  • 4
  • 12
0

I guess you are using cucumber with capybara. In that case, capybara comes with a resynchronize feature. "Capybara can block and wait for Ajax requests to finish after you’ve interacted with the page." - from capybara documentation

You can enable it in features/support/env.rb

Capybara.register_driver :selenium do |app|
  Capybara::Driver::Selenium.new(app, :browser => browser.to_sym, :resynchronize => true) 
end

But, I have seen this causing timeout issues. So, if that isn't working for you, I would recommend introducing a manual wait step before asserting the results of the ajax request.

...
When I follow "Remove from cart"
And I wait for 5 seconds
Then I should see "Test Product removed from cart"

You can define the wait step in step_definitions/web_steps.rb as

When /^I wait for (\d+) seconds?$/ do |secs|
  sleep secs.to_i
end
dexter
  • 13,365
  • 5
  • 39
  • 56
  • 2
    you should never just wait a certain period of time --- this will make your tests quite unstable. Better integrate waiting with active polling and a timeout boundary – pagid Dec 21 '11 at 23:02
  • Agreed. Waiting for X seconds is never the answer. – EndangeredMassa Mar 09 '12 at 17:20
  • **Attention**: The driver is now named Capybara::Selenium::Driver instead of Capybara::Driver::Selenium. With the code above you might get the error "Capybara::Driver::Selenium has been renamed to Capybara::Selenium::Driver". – 0x4a6f4672 Jul 27 '12 at 12:36