How to make Capybara check for visibility after some JS has run?
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.
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.
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.
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)
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.