Since upgrading to Capybara 2.4, I've been running into this issue. Previously, this block worked fine:
page.document.synchronize do
page.should have_no_css('#ajax_indicator', :visible => true)
end
It's meant to force waiting until the ajax indicator disappears before proceeding with the next step.
Since the above returns a RSpec::Expectations::ExpectationNotMetError
, the synchronize doesn't rerun the block and instead just throws the error. Not sure why this was working in the version I was using before (I believe 2.1).
The synchronize
block only reruns blocks that return something like:
Capybara::ElementNotFound
Capybara::ExpectationNotMet
And whatever a certain driver adds to that list.
See Justin's response for a more comprehensive explanation and examples not using synchronize
, or look at my response for the direct solution.
The have_no_css
matcher already waits for the element to disappear. The problem seems to be using it within a synchronize
block. The synchronize
method only re-runs for certain exceptions, which does not include RSpec::Expectations::ExpectationNotMetError
.
Removing the synchronize
seems to do what you want - ie forces a wait until the element disappears. In other words, just do:
page.should have_no_css('#ajax_indicator', :visible => true)
Working Example
Here is a page, say "wait.htm", that I think reproduces your problem. It has a link that when clicked, waits 6 seconds and then hides the indicator element.
<html>
<head>
<title>wait test</title>
<script type="text/javascript" charset="utf-8">
function setTimeoutDisplay(id, display, timeout) {
setTimeout(function() {
document.getElementById(id).style.display = display;
}, timeout);
}
</script>
</head>
<body>
<div id="ajax_indicator" style="display:block;">indicator</div>
<a id="hide_foo" href="#" onclick="setTimeoutDisplay('ajax_indicator', 'none', 6000);">hide indicator</a>
</body>
</html>
The following spec shows that by using the page.should have_no_css
without manually calling synchronize
, Capybara is already forcing a wait. When waiting only 2 seconds, the spec fails since the element does not disappear. When waiting 10 seconds, the spec passes since the element has time to disappear.
require 'capybara/rspec'
Capybara.run_server = false
Capybara.current_driver = :selenium
Capybara.app_host = 'file:///C:/test/wait.htm'
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
end
end
RSpec.describe "#have_no_css", :js => true, :type => :feature do
it 'raise exception when element does not disappear in time' do
Capybara.default_wait_time = 2
visit('')
click_link('hide indicator')
page.should have_no_css('#ajax_indicator', :visible => true)
end
it 'passes when element disappears in time' do
Capybara.default_wait_time = 10
visit('')
click_link('hide indicator')
page.should have_no_css('#ajax_indicator', :visible => true)
end
end
Since version of Capybara 2.0 you can customize inline wait time parameter to pass it into the #have_no_css
method:
page.should have_no_css('#ajax_indicator', visible: true, wait: 3)
The solution I've settled for is the following:
page.document.synchronize do
page.assert_no_selector('#ajax_indicator', :visible => true)
end
The assert_no_selector
method properly throws a Capybara::ExpectationNotMet
error and appears to work in the same way as has_no_css
, so I'm satisfied with this solution.
I still have no idea why the RSpec error is being thrown for some methods but not others.