Multiple selectors using have_selector - capybara

I wish to convert my find's to using have_selector. Presently I have this working as:
expect(find('div[some-attr=true]'))['some-data-attr']).to eq('Hello World')
I wish to convert this to use have_selector and I tried:
expect(page).to have_selector('div[some-attr=true][some-data-attr]')
What am I doing wrong here?

This should be
expect(page).to have_css('div[some-attr=true][some-data-attr="Hello World"]')
This will only work if 'some-data-attr' isn't actually a property that has been modified since the page load. The selectors match on attribute values, but since your original method calls #[] on a returned element it could have been accessing a property with a changed value. Without knowing what those attribute names actually are and how they've been used it's impossible to say for sure. If it is actually a property and you need to use it a lot in your app you could write a custom filter on the :css selector
If Capybara.default_selector == :css then have_selector and have_css mean the same thing, but if you're using a css selector then you're better off just using have_css

For me worked like this:
expect(first("div.flex-cal-day-event-bar.flex-cal-day-event-bar--day-span-1[data-behaviour='event-listing']")).to have_content(text)

Related

Assert if any of two strings are present

I have a webpage with two different strings depending on time of the day - "Good day" or "Good night".
Is there any way to do some sort of assert_content(my_string) that check if one of the two given strings is present?
It doesn't matter which one is present, just that one is.
If using MiniTest then you can use assert_match:
assert_match /Good day|Good night/, string
If using RSpec then you can use match:
expect(string).to match /Good day|Good night/
While Sebastian's answer will work as long as the page is loaded when you go to check, it loses Capybaras waiting/retrying behavior and can lead to flaky tests if used to check content while a page is changing (immediately after clicking a button, etc).
When using minitest a better solution would be to continue using the Capybara provided assert_content matcher but pass it a regex
assert_content(/Good day|Good night/)
If using RSpec you can do the same
expect(page).to have_content(/Good day|Good night/)
or you can write it in a potentially more readable manner
expect(page).to have_content('Good day').or(have_content('Good night'))

RSpec Capybara Element at X no longer present in the DOM

I have a RSpec test with this:
within all('tr')[1] do
expect(page).to have_content 'Title'
expect(page).to have_content 'Sub Title'
end
And it's failing at expect(page).to have_content 'Title' with the following error message:
Element at 54 no longer present in the DOM
I have not been able to find the exact meaning of what this error message means and this test is flakey, sometimes it passes, sometimes not.
Unlike other capybara finders, all doesn't really wait for elements to appear in the DOM. So if your table is not fully loaded, it simply goes straight to the expectation within that block and could potentially fail. That could easily explain why sometimes it fails and other times it succeeds.
I suggest using another expectation before this block and ensure that the table is fully loaded first. I don't know what your DOM looks like, but you can try something like:
expect(page).to have_css('tr td', :count => 15)
So at this point, you've waited for 15 rows to show up in the DOM prior to moving onto your next steps. Hope that helps.
As the other answer details, #all by default doesn't wait for elements to appear so it's possible you're not actually getting the elements you think you are. Rather than the solution in the other answer, it is possible to make #all wait for elements to appear by specifying one of the :count, :minimum, :maximum, or :between options.
within all('tr', minimum: 10)[1] do
...
end
for instance. This is all assuming that the action before the #within in your test isn't triggering an ajax action that is replacing existing rows in a table with other rows. If row replacement is happening then you may be running into one of the biggest downsides of using #all -- when using #all the returned elements cannot automatically be re-queried if they leave the page and are replaced, since their entire query can't be stored with them (no index into the results). In that case you're better off changing the code to
within find(:xpath, './/tr[2]') do
...
end
This way the element you're searching within can be reloaded automatically if needed

Rspec with Capybara have_css matcher is not working

In Cucumber, with Rspec and Capybara I have a test to check that a button has a class. Here it is
expect(#some_button).to have_css(".in-cart")
it fails, but
#some_button['class']
returns
'btn product in-cart'
so the button definitely has the 'in-cart' class.
As a temporary measure I've changed my test to be;-
expect(#some_button['class']).to match /in-cart/
Which is obviously insane. But why should 'have_css', or 'has_css?' return false for a DOM element that clearly has the expected class?
Also page.all('.in-cart') includes the button, so Capybara can definitely find it.
Incidentally I also tried 'button.in-cart', 'in-cart',expect (etc).to have_selector, expect(etc.has_selector?('.in-cart')).to be_truthy and all combinations.
have_css matcher is expected to be applied to the parent container instead of the actual element
# your view
<div id="container">
<div class="in_cart product"></div>
</div>
# your step definition
parent = page.find('div#container')
expect(parent).to have_css(".in-cart")
# => returns true, as there is nested div.in_cart
expect('div#container div').to have_css(".in-cart")
# => returns false, as there is no such selector inside of the latter div
As of matching attributes of the exact object, you've got to stick with simple quering by key
element = page.find('div#container div')
element['class'].should include('in-cart')
expect(element['class']).to match /in-cart/
Same logic applies to all of the RSpecMatchers.
In newer versions of Capybara/Rspec, by default, expect(page) will query the entire page looking for a match; however, sometimes we may not want that and instead will prefer to target a specific class/region of the page. For those cases, narrow down the context of page using within:
within('.container') do
expect(page).to have_css(".in-cart")
end
Assuming, the parent has a class container Capybara will only search within this element.
expect(page).to have_css('button.in-cart')

Capybara find all links on page and check url

I want to check that all links on a page contain a certain element. This is the current web_step I have but it is not working. Any ideas?
Then /^all links above footer should countain "([^"]+)"$/ do |parameter|
al = page.all('a')
al.each do |i|
i.include?(parameter).should be_true
end
end
You probably need to assert against a particular attribute of each a element - if you're checking that the 'src' attribute contains 'parameter' then:
i[:href].include?(parameter).should be_true
Or, to make better use of the rspec matchers (and get better failure messages):
i[:href].should include parameter
[:src] no longer appears to work. Use [:href] instead.

Cucumber, How to check if parent has a certain class?

I am using cucumber with rails, and I'm checking if the parent of an element has a certain class. I found this code, but it doesn't work. I'm using Capybara by the way. Thanks in advance!
Then the element "([^"]*)" with parent "([^"]*)" should have class "([^"]*)" do |element_id,parent,css_class|
response.should have_selector "#{parent} .#{css_class}" do |matches|
matches.should have_selector element_id
end
end
Technically, this isn't really the way you should be testing with cucumber. Cucumber is for testing behaviour, not checking the DOM - though of course it can be used in that way.
If you're really wanting to write something like this, you could make that much simpler:
Then /^the element "([^"]*)" with parent "([^"]*)" should have class "([^"]*)"$/ do |element_id,parent,css_class|
page.should have_css("#{parent}.#{css_class} #{element_id}")
end
This sucks on many levels, though. Not only are you checking the DOM directly from the step, which makes for very unreadable features, but you're mixing passing in element names, class names and element IDs - each with a slightly different style... In this step, element can be any type of selector whereas parent and css_class are much more fixed - they must always 'fit' into the selector string or it won't find anything.
I haven't really explained that very well, but in a nutshell, you should consider what you're actually trying to test and think whether it can be renamed to something more useful and re-usable. Would you ever be able to re-use that step without looking at it's implementation to figure out what goes where?
Also, with more expressive naming, the test instantly becomes more useful later down the line. For example, the step Then I should see the current list is active is much more readable and expressive than Then the element "li.active" with parent "ul" should have class "active-list". Be specific in the step definition implementation, not your features!
Have a read of this blog post - http://elabs.se/blog/15-you-re-cuking-it-wrong - it should give you a good idea of how to write better steps.

Resources