Capybara: matches_xxx? fails when used with elements located using sibling selectors

Created on 23 Jul 2018  路  18Comments  路  Source: teamcapybara/capybara

Capybara version 3.3.1. Right now, matches_css? expects the CSS in the first argument to match fully the given element - that is, if I have a div.a.b.c the whole CSS should be passed in order for matches_css? to return true. Passing div.a for example or div or any other combination returns false. Having this ability would be nice.

Actually, I see no other way of doing exactly that apart from page.all(css, wait: 0).include?(element) which, however, is extremely slow when css is e.g. div because a million elements are returned from the browser.

Bug

All 18 comments

matches_css? shouldn't be requiring any "full match" - in fact there is no such thing as a "fully matching CSS selector" since you could always add more specificity through nth-child etc. Can you provide a small self contained example that shows it not working?

@boris-petrov Here's a gist showing it working - https://gist.github.com/twalpole/a71ec27599f0f7810c553a0df043b377 - so you can make any necessary changes to that to show your issue.

Well, having the following code:

p element
p css
p options
p element.matches_css?(css, options.merge(wait: 0))

It outputs:

#<Capybara::Node::Element tag="div" path="/HTML/BODY/DIV[1]/DIV[2]/DIV/DIV/DIV/DIV[1]/DIV/DIV/DIV[10]/DIV[2]">
"div"
{}
false

And this happens in every case I try. This seems wrong to me and it doesn't match what your example suggests. I'm on JRuby 9.2.0.0 which might be the problem. Or perhaps Capybara is doing something strange. Or am I missing something? I'll try to create a simple reproduction in the next few days.

@boris-petrov How have you found element?

element = find('.some-label', text: /^#{label}$/).find(:xpath, './following-sibling::*[1]')

Is it visible?

Should be, yes. Let me check.

If it is visible then I have no clue why you're getting false, and without a reproducible example there's not a lot I can do.

Yep, p element.visible? prints a true. I'm guessing it's a JRuby issue but let me try creating a reproduction and I'll test it on both mri and JRuby and open an issue on their side if it really is their problem. I'll post here also.

Ok, closing this for now - I will reopen if/when a reproducible example is provided.

@twalpole - here is a reproduction (it's a Capybara bug, not a JRuby one):

require "capybara/dsl"
require 'selenium/webdriver'

html = DATA.read
app = proc { |env| [200, { "Content-Type" => "text/html" }, [html] ] }

sess = Capybara::Session.new(:selenium_chrome, app)
sess.visit('/')
following_sibling = sess.find('.a', text: /^text$/).find(:xpath, './following-sibling::*[1]')
puts following_sibling.matches_css?('div')

__END__
<!DOCTYPE html>
<html>
<head>
<title>title</title>
</head>
<body>
  <div class="a">text</div>
  <div class="b">
  </div>
</body>
</html>

This prints false. Another problem - if you join the two lines of <div class="b"> together like so:

<div class="b"></div>

There is an exception:

Traceback (most recent call last):
    4: from a.rb:9:in `<main>'
    3: from /home/boris/.gem/ruby/2.5.0/gems/capybara-3.4.1/lib/capybara/node/finders.rb:33:in `find'
    2: from /home/boris/.gem/ruby/2.5.0/gems/capybara-3.4.1/lib/capybara/node/finders.rb:285:in `synced_resolve'
    1: from /home/boris/.gem/ruby/2.5.0/gems/capybara-3.4.1/lib/capybara/node/base.rb:83:in `synchronize'
/home/boris/.gem/ruby/2.5.0/gems/capybara-3.4.1/lib/capybara/node/finders.rb:294:in `block in synced_resolve': Unable to find visible xpath "./following-sibling::*[1]" within #<Capybara::Node::Element tag="div" path="/HTML/BODY/DIV[1]"> (Capybara::ElementNotFound)

Which is also a bug I guess.

Your locator is looking inside the div.a.

Inside the div.a there are no div items defined.

You want the following locator

div.a + div or div.a + .b

@luke-hill - printing following_sibling prints the correct element:

#<Capybara::Node::Element tag="div" path="/HTML/BODY/DIV[2]">

In the documentation it is not written that find finds only child elements. In any case, the following also does not work:

following_sibling = sess.find('.a', text: /^text$/).sibling('div')
p following_sibling
puts following_sibling.matches_css?('div')

This also prints the same element and also false.

Thirdly, the exception that I pasted above still happens in some cases and is another bug.

AHA -- this happens because matches_xxx? uses the current elements query_scope (the element that was the current scope when it was found) as the scope for finding all elements that match the passed CSS and then checking if the current element is one of them. This works fine when the current elements query_scope is an ancestor (which it would be most of the time), but doesn't work when a sibling is selected since its query_scope is not an ancestor of the current element being checked. Easiest way to fix this would be to use the elements parent as the scope for searching in a matches query, need to think about whether that has any downsides.

As for the 'div' difference, if that's a bug it would be a bug in selenium or chromedriver/chrome - I would expect <div class="b"></div> to report the error shown since the element takes up no space on the page and is therefore not visible and wouldn't, by default, be located by find. With any whitespace contents of the <div> I believe it should actually behave the same way and raise the error since whitespace is stripped and Chrome still reports the element as having 0px height but the element is reported as visible. I'm guessing this may be a bug in selenium but haven't looked in depth far enough to say that for sure - but I can say it's not a Capybara issue.

@boris-petrov Could you please try the matches_query_scope branch and see if it solves your issue?

@twalpole - yes, this branch seems to fix the issue on my side.

P.S. Thanks for the quick response and support!

@boris-petrov Thanks for reporting this, and for producing the reproducible example. Having an example of the failure makes debugging and fixing things so much easier. Will probably release a fix as 3.4.2 later today, assuming tests pass, etc.

Fixed via 84a69f3d8ea284c3999b1921b9ab4a3ffbb06cc7 - 3.4.2 coming in a bit

Was this page helpful?
0 / 5 - 0 ratings