Capybara synchronize with has_no_css?

2019-01-27 07:10发布

问题:

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.

回答1:

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


回答2:

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)


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



标签: ruby capybara