Rspec with Capybara have_css matcher is not working - ruby-on-rails

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')

Related

Multiple selectors using have_selector

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)

What's the best way to reuse your step definition in ruby siteprism

im having some issue trying to create a reusable step definition using siteprism
let say feature file is
Given that im on the site
Then i should see a "stack over" text
And i should see a "ask" text
And i should see a "question" text
then on my step definition will be
I want to have arg1 to be dynamic and this logic will check if its true
Then (/^i should see a "(.*?)" text$/) do |arg1|
#common_page = CommonLib.new
#ref = arg1.gsub(/\s+/,'')
expect(#common_page.*#ref*.text).to eq (arg1)
end
Then on my page def will be
class CommonLib < siteprism::page
element :stackover, "#text_header"
element :ask, "#text_ask"
element :question, "#text_question"
the issue im having is this expect(#common_page.#ref.text).to eq (arg1)
the mapping is wrong #ref need to use the data it got like 'stackover', 'ask' and 'question' and map in the CommonLib page def
Calling #text and using the eq matcher is generally a bad idea since it bypasses Capybaras builtin retry behavior and can cause flaky tests on dynamically changing pages. Instead you should use have_text or the :text option passed to the finder
expect(#common_page.send(#ref)).to have_text(arg1)
or
expect(#common_page.send(#ref, text: arg1)).to be
Also, is there a reason you've made #common_page and #ref instance variables, they seem like they should just be regular variables that go out of scope at the end of the test.

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

Capybara: How To Assert A Given Number of Elements Exist

I've upgraded my whole stack from a Rails 3.0 based project to 3.1. I have specs passing but my features are now being a bit picky. The issue I'm currently having is this step:
Then /^I should see (\d+) menu item(?:s)? within "([^"]*)"$/ do |count, selector|
page.find(:css, selector, :count => count.to_i)
end
And in the feature itself, I might put:
Then I should see 5 menu items within "tr#menu_item_row"
The message I get is:
Then I should see 5 menu items within "tr#menu_item_row" # features/step_definitions/admin_menu_steps.rb:1
Ambiguous match, found 5 elements matching css "tr#menu_item_row" (Capybara::Ambiguous)
./features/step_definitions/admin_menu_steps.rb:2:in `/^I should see (\d+) menu item(?:s)? within "([^"]*)"$/'
features/admin_menu.feature:30:in `Then I should see 5 menu items within "tr#menu_item_row"'
As far as I can tell, the 5 elements match the 5 that were actually found. Did I write this code wrong or has something major changed? Thanks!
If you want to check 5 elements you shouldn't use #find as by default since Capybara 2.0 this method always throws an exception if it finds more or less than one element. This was an intentional and (I believe) a good change.
To assert that 5 elements are present an appropriate method is a rspec matcher:
expect(page).to have_css(selector, count: count.to_i)
I don't recommend to set match to prefer_exact as recommended by #fontno as in most of situations you want Capybara to throw an exception if find finds more than one element.
Yes, this is a change between versions 1.x and 2.x. You can look at all the changes in the capybara upgrade guide and this blog post.
The find method now raises an ambiguous match error if more than one element is found. If you only have a few examples you could do something like this
Then /^I should see (\d+) menu item(?:s)? within "([^"]*)"$/ do |count, selector|
page.find(:css, selector, :count => count.to_i, match: prefer_exact)
end
or if you have many examples like this you could change the capybara configuration for backwards compatability, something like this
Capybara.configure do |config|
config.match = :prefer_exact
config.ignore_hidden_elements = false
end
You may have to modify this to get it working but this is the general idea. See the links I mention above, its all in there. Hope this sets you in the right direction

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.

Resources