How to make Capybara check for visibility after some JS has run?

RubyRspecCapybaraIntegration Testing

Ruby Problem Overview


After loading a page I have code that runs and hides and shows various items based on data returned by an xhr.

My integration test looks something like this:

it "should not show the blah" do
    page.find('#blah').visible?.should be_true
end 

When I manually go to the page in the context this test runs, #blah is not visible as I expect. I suspect that Capybara is looking at the initial state of the page (invisible in this case), evaluating the state of the DOM and failing the test before the JS runs.

Yes, I set the :js => true on the containing describe block :)

Any ideas would be greatly appreciated! I'm hoping I don't have to put an intentional delay in here, that feels flaky and will slow things down.

Ruby Solutions


Solution 1 - Ruby

I think that the find statement here is the one with the implicit wait, so Capybara will wait until the element is on the page, but won't wait for it to become visible.

Here, you would want Capybara to wait for the visible element to appear, which should be achievable by specifying the visible option:

expect(page).to have_selector('#blah', visible: true)

I haven't tried it, but the ignore_hidden_elements configuration option might be useful here as well, if you wanted find to always wait for visible elements.

Solution 2 - Ruby

This is another way to do it that works perfectly fine for me:

find(:css, "#some_element").should be_visible

Especially for more complex finds, such as

find(:css, "#comment_stream_list li[data-id='#{@id3}']").should_not be_visible

which would assert that an element has been hidden.

Solution 3 - Ruby

If you want to check that an element is on the page but is not visible, visible: false won't work as you might expect. Had me stumped for a bit.

Here's how to do it:

# assert element is present, regardless of visibility
page.should have_css('#some_element', :visible => false)
# assert visible element is not present
page.should have_no_css('#some_element', :visible => true)

Solution 4 - Ruby

Using:

 Ruby:     ruby 1.9.3dev (2011-09-23 revision 33323) [i686-linux]
 Rails:    3.2.9
 Capybara: 2.0.3

I have a Rails application in which there is a link which when clicked should submit an AJAX post request and return a JS response.

Link code:

 link_to("Send Notification", notification_path(user_id: user_id), remote: true, method: :post)

The JS response (.js.haml file) should toggle the following hidden div on the page the link exists:

 #notification_status(style='display:none')

js.haml file contents:

:plain
  var notificationStatusContainer = $('#notification_status');
  notificationStatusContainer.val("#{@notification_status_msg}");
  notificationStatusContainer.show();

I was testing my scenario of sending notification and displaying the notification status message to the user using Cucumber (cucumber-rails gem with built in Capybara support)

I was trying to test that the element having id: notification_status was visible on successful response in my step definition.For this I tried following statements:

page.find('#notification_status').should be_visible
page.should have_selector('#notification_status', visible: true)
page.should have_css('#notification_status', visible: true)
page.find('#notification_status', visible: true)
page.find(:css, 'div#notification_status', visible: true)

Neither of above worked for me and failed my step.Out of the above listed 5 snippets the last 4 failed with following error:

'expected to find css "#notification_status" but there were no matches. Also found "", which matched the selector but not all filters. (Capybara::ExpectationNotMet)'

which was strange because following statement was passing correctly:

page.has_selector?('#notification_status')

And in fact I inspected the page source using

  print page.html

which showed up

<div style='' id='notification_status'></div>

which was expected.

Finally I found this link https://stackoverflow.com/questions/5153550/capybara-assert-attributes-of-an-element which showed up how to inspect an element's attribute in raw manner.

Also I found in Capybara documentation for visible? method (http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Element#visible%3F-instance_method) following information:

 Not all drivers support CSS, so the result may be inaccurate.

Thus I came to the conclusion that when testing visibility of an element do not rely on results of Capybara's visible? method when using a CSS selector and using the solution suggested in link https://stackoverflow.com/questions/5153550/capybara-assert-attributes-of-an-element

I came up with following:

 module CustomMatchers
   def should_be_visible(css_selector)
    find(css_selector)['style'].should_not include('display:none', 'display: none')
   end
 end

 World(CustomMatchers)

Usage:

should_be_visible('#notification_status')

Solution 5 - Ruby

What visible means is not obvious

The failure may come from a misunderstanding of what is considered visible or not as it is non-obvious, not driver portable, and under-documented. Some tests:

HTML:

<div id="visible-empty"                                                                   ></div>
<div id="visible-empty-background"      style="width:10px; height:10px; background:black;"></div>
<div id="visible-empty-background-same" style="width:10px; height:10px; background:white;"></div>
<div id="visible-visibility-hidden"     style="visibility:hidden;"                        >a</div>
<div id="visible-display-none"          style="display:none;"                             >a</div>

The only thing Rack test considers as invisible is inline display: none (not internal CSS since it does not do selectors):

!all('#visible-empty',                 visible: true).empty? or raise
!all('#visible-empty-background',      visible: true).empty? or raise
!all('#visible-empty-background-same', visible: true).empty? or raise
!all('#visible-visibiility-hidden',    visible: true).empty? or raise
 all('#visible-display-none',          visible: true).empty? or raise

Poltergeist has a similar behavior, but it can deal with internal CSS and Js style.display manipulation:

Capybara.current_driver = :poltergeist
!all('#visible-empty',                 visible: true).empty? or raise
!all('#visible-empty-background',      visible: true).empty? or raise
!all('#visible-empty-background-same', visible: true).empty? or raise
!all('#visible-visibiility-hidden',    visible: true).empty? or raise
 all('#visible-display-none',          visible: true).empty? or raise

Selenium behaves quite differently: if considers an empty element invisible and visibility-hidden as well as display: none:

Capybara.current_driver = :selenium
 all('#visible-empty',                 visible: true).empty? or raise
!all('#visible-empty-background',      visible: true).empty? or raise
!all('#visible-empty-background-same', visible: true).empty? or raise
 all('#visible-visibiility-hidden',    visible: true).empty? or raise
 all('#visible-display-none',          visible: true).empty? or raise

Another common catch is the default value of visible:

  • it used to be false (sees both visible and invisible elements),
  • currently is true
  • is controlled by the Capybara.ignore_hidden_elements option.

Reference.

Full runnable test on my GitHub.

Solution 6 - Ruby

You might want to look at this post, which gives a sample method for waiting until all ajax requests are complete:

def wait_for_ajax(timeout = Capybara.default_wait_time)
  page.wait_until(timeout) do
    page.evaluate_script 'jQuery.active == 0'
  end
end

Solution 7 - Ruby

The accepted answer is a bit outdated now as 'should' is deprecated syntax. These days you'd be better off doing something along the lines of expect(page).not_to have_css('#blah', visible: :hidden)

Solution 8 - Ruby

The other answers on here are the best way to "wait" for the element. However I have found this didn't work for the site I am working on. Basically the element that needed clicking was visible before the function behind that was fully loaded. This is in fractions of a second but I found my test ran so quick on occasion that it clicked the button and nothing happened. I managed to work around it by doing this make-shift boolean expression:

if page.has_selector?('<css-that-appears-after-click>')
  puts ('<Some-message-you-want-printed-in-the-output>')
else
  find('<css-for-the-button-to-click-again>', :match == :first).trigger('click')
end

Basically it uses the capybara default wait time to look for something that should appear, if it isnt there it will retry your click.

Again I will say that the should have_selector method should be tried first but if it just wont work try this

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevin DavisView Question on Stackoverflow
Solution 1 - RubyJon MView Answer on Stackoverflow
Solution 2 - RubyBjörn GrossmannView Answer on Stackoverflow
Solution 3 - RubyZubinView Answer on Stackoverflow
Solution 4 - RubyJignesh GohelView Answer on Stackoverflow
Solution 5 - RubyCiro Santilli Путлер Капут 六四事View Answer on Stackoverflow
Solution 6 - RubysmallsenseView Answer on Stackoverflow
Solution 7 - RubyAlex FoxleighView Answer on Stackoverflow
Solution 8 - RubyDonoView Answer on Stackoverflow