Capybara Synchronize with Has_No_Css

Capybara synchronize with has_no_css?

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

Handle StaleElement exception

The use of all or first disables reloading of any elements returned (If you use find the element is reloadable since the query used to locate the element is fully known). This means that if the page changes at all during the time the last line of your code is running you'll end up with the StaleElement errors. This is possible in your code because has_no_css? can run before the overlay appears. One solution to this is to use has_css? with a short wait time, to detect the overlay before checking that it disappears. The has_xxx? methods just return true/false and don't raise errors so worst case has_css? misses the appearance/disappearance of the overlay completely and basically devolves into a sleep for the specified wait time.

visit('/table-page') # table with default values is displayed

# select all filters one by one. Wait for spinner to disappear after each selection
filters.each do |filter|
check(filter);
has_css?('.loading_overlay', wait: 1)
assert_no_selector('.loading-overlay', wait: 15)
end

# get table data as array of arrays. Added *minimum* so it waits for table
all('tbody tr', minimum: 1).map { |row| row.all('th,td').map(&:text) }

Check for temporary element with Capybara

The asynchronous nature of browser testing means that it's not always possible to reliably locate temporary elements with a default setup. Since you state you're ok with just making sure the element has disappeared, and you have a timeframe you can reliably guarantee the element would have appeared in you could do something like

#send request to server
something.click
#wait for temporary element to appear - don't error if it doesn't
page.has_css?('#foo', wait: 2)
#make sure temporary element disappears after server returns response
expect(page).to have_no_css('#foo')

That will wait up to 2 seconds (adjust as needed) for the element to appear. If it does appear it will continue as soon as the element is visible, if not it will return false after 2 seconds and continue on.

If it is actually required to detect the appearing and disappearing of the element, then any solution would depend on what exactly triggers the display and removal of the element. Assuming it's an indicator of a request happening which appears when the request starts and then is removed on request completion you would need to look at slowing down the request so the element remains visible longer. Two ways that could be accomplished would be

  1. A programmable proxy like puffing-billy
  2. If using the Selenium driver with Chrome you could use page.driver.browser.set_network_conditions although this would be a driver specific solution.

How to wait until an element disappears in Capybara?

After more investigation, it seems that the element was rightly not disappearing, because of another issue.

     Failure/Error: expect(@page.thing.results.map(&:text)).to include a_string_matching 'search_query'
expected ["Searching…"] to include (a string matching "search_query")
Diff:
@@ -1,2 +1,2 @@
-[(a string matching "search_query")]
+["Searching…"]

That "Searching..." display happens for a split second, and needs to be waited on before trying to click a result.

So now I would like to wait until I don't see the "Searching...", and then continuing with the next click.

I ended up making this while statement:

while search_complete = @page.thing.results.map(&:text).reduce.include?('Searching')
sleep 1
end

And this seems to wait until the "Searching..." disappears and the real results appear.

Why does it seem like the Capybara wait for page to load timer works for matchers but not finders?

You are correct assuming that the timeout does not apply when using first. But you can use the wait_until method, which will keep retrying until either the timeout expires, or the block returns something truthy, so:

page.wait_until() do
session.first(:xpath, xpath_route)
end

Record Deletion testing failure using Capybara with Selenium

First, best practices is to have all of your scenarios be independent so they can be run in any order. You have opted to ignore best practices here and made scenarios order dependent - hopefully you have a really good reason for doing that.

As for the sleep being necessary, that is (as your surmised) because browser actions occur asynchronously which means that accepting the confirm returns as soon as the confirm is accepted. It will then take a little time for the deletion request to be processed by your server and the Post.count will be checked before the deletion has actually occurred. To fix that you should be checking for whatever visual change indicates the deletion has occurred. In your case that looks to be the text "Post was successfully destroyed.", so move that inside the Post.count expectation in order to synchronize your test with the application.

expect{
within 'table' do
accept_confirm do
find_link(class: 'tester').click
end
end

expect(page).to have_content("Post was successfully destroyed.")
}.to change {Post.count}.by(-1)

Validating is there any different menu item with capybara

all('select#tbm2') is going to find all <select> elements with an id of tbm2, of which there are none in your HTML. Also .value gets the value property of an element but you have all div elements which don't have a value. If you wanted to use the approach you're trying for it would be something like

page.all('#tbm2 .tbi', count: 3).map(&:text).should == ['Regras GAR', 'Regras GAE', 'Log Processamentos GAR/GAE']

Another option would be to use a negative regex match, something like

page.find('#tbm2').should_not have_css('.tbi', text: /^(?!Regras GAR|Regras GAE|Log Processamentos GAR/GAE)/)

Another option would be to use the filter block that can be passed to most Capybara finder methods, like

page.should_not(have_css('#tbm2 .tbi') { |node| ['Regras GAR', 'Regras GAE', 'Log Processamentos GAR/GAE'].none?(node.text) })

Note: I'm using the should syntax because that's what you attempted in your question, but you really should be switching to RSpecs expect syntax nowadays



Related Topics



Leave a reply



Submit