Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I find a certain element that comes right after another element with Capybara?

I am trying to use learn Capybara for a scraping task I have. I have heretofore only used it for testing. There are a million things I want to learn, but at the very basic part of it I want to know how to find a certain element that is a sibling and comes after another element that I am able to find?

Take a page like this:

<body>
  <h3>Name1</h3>
  <table>
    ...
  </table>
  <h3>Name2</h3>
  <table>
    ...
  </table>
  <h3>Name3</h3>
  <table>
    ...
  </table>
</body>

I want to return the <table> element that comes after the <h3> element having text Name2.

I know how to loop through elements with all, and I know how to use first instead of find, but I don't know how to "Find the first element X following specific element Y".

like image 630
AKWF Avatar asked May 13 '15 05:05

AKWF


3 Answers

CSS

In CSS you could use a sibling selector. These allow you to select sibling elements; or those at the same nesting level and with the same parent element. There are two types of sibling selectors:

  • '+' the adjacent sibling selector
  • '~' the general sibling selector (adjacent or non-adjacent siblings)

It's usually ideal to avoid matching by text whenever possible. (This makes your specs easier to write and also means that textual changes are less likely to break your specs.) In that ideal world your 'h3' elements might have IDs on them and we could just:

find('h3#name2+table')

However, in your example they don't have IDs so let's connect a couple of queries to scope to what we want.

find('h3', text: 'Name2').find('+table')

First we found the correct 'h3' element (using text matching) and then with that query as a basis we request the sibling 'table' element.

You may also note that if you used the general sibling selector '~' you would get an ambiguous element error; Capybara found all the 'table' elements rather than just the adjacent one.

XPath

Sometimes XPath is actually easier to use if you're really forced to do textual element selection. So you could instead:

find(:xpath, "//h3[contains(text(),'Name2')]/following-sibling::table")

More difficult to read but does the same thing. First find an 'h3' with text 'Name2' and then select it's sibling 'table' element.

like image 144
tgf Avatar answered Oct 04 '22 21:10

tgf


Capybara now has sibling and ancestor finders (~>=2.15.0)

[Updated 2018/09] As @trueunlessfalse commented out, the original answer using sibling finder will return amgibuous match error if there are multiple matches. So, please consider to use xpath in that case..

Following code using xpath will return what OP wanted

find('h3', text: 'Name2').first(:xpath, './following-sibling::table')


Following code will return what OP wanted => ambiguous match error

find('h3', text: 'Name2').sibling('table')

You can check the detail here. https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Finders:sibling

like image 23
QuestionDriven Avatar answered Oct 04 '22 22:10

QuestionDriven


Using

find('h3#name2+table')

didnt work for me. I needed to add :css then it worked

find(:css, 'h3#name2+table')
like image 29
chasethesunnn Avatar answered Oct 04 '22 23:10

chasethesunnn