Read more

Cucumber: Wait for any requests to finish before moving on to the next scenario

Arne Hartherz
October 26, 2012Software engineer at makandra GmbH

The approach outlined in this card is very old.
https://github.com/makandra/capybara-lockstep provides a much more sophisticated and stable solution.

Background

Generally, Selenium tests use the browser to interact with the page. If it's unavailable, a timeout error is thrown.

Illustration UI/UX Design

UI/UX Design by makandra brand

We make sure that your target audience has the best possible experience with your digital product. You get:

  • Design tailored to your audience
  • Proven processes customized to your needs
  • An expert team of experienced designers
Read more Show archive.org snapshot

Now, consider a scenario like this:

@javascript
Scenario: Receive an e-mail after clicking the fancy link
  When I follow "fancy link"
  Then I should have an e-mail with the subject "Hello"

When the last step in the scenario passes, you are done. Right? Wrong.

Why it's not enough

What if clicking our "fancy link" above sends the e-mail that we expect, but it also does stuff on the server that takes some time, eventually redirecting the user to a new page, showing other things?

It may not be relevant for the scenario above, but the application will still be busy:

  • Errors happening on the target page are not "visible" to the test
  • If the browser is still waiting for further responses from the server (like after following redirects), it is locked -- and the next test using it will fail for no reason, since it's not that test's fault.
  • And worst: After steps will be running.

How is that last item the worst? Consider these steps:

Before('@javascript') do
  # Disable code snippets that embed code to third party services, like Google Analytics, when
  # in a Selenium feature. For Rack::Test features we want to show them so we can test them.
  ThirdPartyServices.disable!
end

After do
  ThirdPartyServices.reset!
end

Cucumber cares only for clicking the link, which succeeds instantly.

So, the After steps are being called, re-enabling the third party services in our case. \
Any subsequent requests would embed JavaScript code into the page that makes our Selenium browser talk to the interwebs. This is most definitely not what you want for Selenium features. (It just should not happen. And if those services are down, features will be failing.)

Fixing it

The solution is rather simple: At the end of each scenario, wait for the browser to see a page again. \
You can explicitly wait for requests to finish by using jQuery's $.active.
Kind of a fallback is using Capybara's has_content? to wait for the page to be built, since you are not always waiting for JavaScript calls.

After('@javascript, @selenium') do
  wait_until { page.evaluate_script('$.active') == 0 } if Capybara.current_driver == :selenium
  page.has_content? ''
end
Posted by Arne Hartherz to makandra dev (2012-10-26 18:11)