A good selector matches exactly the intended element(s) and no more.
I recommend always building out your selectors in a pry session. First build your selector using Capybara's all
method with .count
to make sure your selector does not return more elements than you are intending to target.
page.all('[build selector here]').count
Once you have your selector built this way, swap out the page.all('...').count
for the capybara method you need, e.g. find('...')
or assert_selector('...')
.
A good selector is the least specific necessary to match the intended element(s), while still being resilient to future changes.
Too specific:
find('nav > ul > li:nth-child(2) > a')
This will break if a future <div>
is wrapped around the <ul>
or if another list item is added making this the third child.
Not specific enough:
find('a', text: 'Tasks')
It's easily conceivable that in the future the page includes another link that includes the same text, making us act upon the wrong link.
Good:
find('a.activityMenu__link', text: 'Tasks')
It's safe to assume there will never be a second activity menu link with this same text.
Good:
find('nav.mainNavigation a.tasks-link')
It's safe to assume there will never be a second tasks link under the main navigation.
I recommend always including the HTML tag in all selectors, rather than just the class or id, to increase readability.
Bad:
find('#main #content-wrapper .item')
If I'm reading this code and referencing the HTML to find out what element(s) it is targeting, this selector doesn't give me any hints as to what tags I should be looking for.
Better:
find('nav#main div#content-wrapper ul.item')
This allows me to quickly scan the HTML for a <nav>
element, then a <div>
, and then a <ul>
.
Don't use selectors that are meant for styling
Bad:
find('a.psm-default-button', text: 'OK')
This is not resilient to minor UX changes. It will break if they change the color of the button, for example by changing the class to psm-primary-button
.