Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

ruby - Capybara synchronize with has_no_css?

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.


Follow-up: I've since revisited this problem so here's a few tips:

1) document.synchronize more often than not does nothing useful, as mentioned by others most of the finders have built-in wait. You can manually force it to not wait using wait: 0 when that makes sense.

Note that due to this, the following are not equivalent:

!assert_css('#ajax_indicator')
assert_no_css('#ajax_indicator')

The former will wait until the element exists, while the latter will wait until the element doesn't exist, even if they are otherwise logically equivalent.

2) Problems that lead to us initially inserting synchronize were due to a chicken and egg problem of sorts.

#ajax_indicator in this case will appear when there is active loading, then disappear after. We cannot distinguish from the indicator having not appeared yet, and having appeared then disappeared.

Although I have yet to 100% resolve this issue, what has improved our test reliability is looking for indicators to ensure page loading has reached a certain point, e.g.

do_thing_that_triggers_ajax
find('#thing-that-should-exist')
assert_no_selector('#ajax_indicator', visible: true)

This differents from asserting #ajax_indicator exists and then assert it doesn't exist, because in that case if the ajax happens too fast capybara may not catch it in action.

Depending on your scripts, you could possibly find more reliable indicators.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...