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.
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