RSpec Capybara Element at X no longer present in the DOM - ruby-on-rails

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

Related

Feature spec pass locally but fail on Circle CI

I have feature specs for my ActiveAdmin view. It works on my local machine. But when ran in CircleCi it fails with
undefined method `text' for nil:NilClass
spec
it 'uses the update_at date when prepaid_invoice' do
travel_to(5.days.ago) { create_prepayment }
travel_to(3.days.ago) do
visit '/admin/payments'
expect(page).not_to have_css('.col.col-created > div')
amount = all('.col-amount').last
expect(amount.text).to eq('$1,000.00') # failing here
all behaves different depending on whether you're on a current release of Capybara or the older 2.x version. In current versions all will wait for up to Capybara.default_max_wait_time seconds for any matching elements to appear and if none do it will return an empty array (actually Array like Result object, but close enough). In 2.x all (by default) did no waiting for matching elements, it would just return the empty array if no elements matched. Either way - you're not finding any matching elements, and then calling last on an empty array - giving you nil.
There are a couple of ways around this. You could tell all that you expect at least one matching element ( which will then force waiting for matching elements Capybara 2.x )
amount = all('.col-amount', minimum: 1).last
or depending on exactly what you're checking you could just combine it into one
expect(page).to have_css('.col-amount', exact_text:'$1,000.00')
which gets away from calling methods on Capybara elements and using the generic RSpec matchers on them (which is something you don't want to do, for test stability reasons, unless you have no other options). Those two solution don't test exactly the same thing, but may test enough for what you want. Depending on exactly how your HTML is structured there may be more efficient solutions too.
If you're already using a recent version of Capybara then your error would indicate that what you expect to be on the page isn't (maybe you're on an error page, etc) or you don't have Capybara.default_max_wait_time set high enough for the system you're testing on.

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: 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

Is there a better alternative to using sleep in Capybara?

In my test I have a step where I fill out a field and press enter. This field then returns a result set on the next page.
Here is my helper method:
def search(term)
fill_in('query-text', :with => term)
click_button('search-button')
end
After that I have a step that simply says:
page.should have_content(tutor)
However, the issue is that even though the page after my page loads with the results, the step after it passes even if it should be false. I have set a debugger in the step and when I manually check it, the assertion fails as I expect it too. My assumption is that the the next step is checking before the page even reloads. I placed a sleep at the end of my search method to make it look like:
def search(term)
fill_in('query-text', :with => term)
click_button('search-button')
sleep 5
end
But I feel like using sleep is a hacky way to resolve this issue. I am using Capybara 2 so the use of wait_until is removed. Is there a better way to handle this issue rather than relying on sleep?
Do you have tutor text in your HTML page hidden? has_content? returns true for any text present in html, including hidden text which is not visible. So I would rather replace it with expect(page).to have_text(tutor), which checks only for visible text on a page. .text is also pretty slow method, so these extra split seconds may be handy for you.
Another approach is to restore wait_until method in your spec helpers, since it's really handy in most cases:
def wait_until(timeout = DEFAULT_WAIT_TIME)
Timeout.timeout(timeout) do
sleep(0.1) until value = yield
value
end
end
In any case it will be better than waiting for a fixed timeout each time.
This test passes as tutor is already present on the page.
So you should check not for tutor but for something else, e.g. for element with text tutor that is present only after page reload.
yes, you're right wait_until is removed, new method is #synchronize, but I don't know for now how to use it:)
look into
http://www.elabs.se/blog/53-why-wait_until-was-removed-from-capybara
https://github.com/jnicklas/capybara/blob/master/lib/capybara/node/base.rb#L44

How can I alleviate timing/AJAX woes using Capybara/Capybara Webkit/RSpec click_button and page.select?

For the sake of simplicity, I've left out most of my test and only included the offending code. It is:
click_button('Search')
page.select 'Preferred', :from => 'ticket_service_type'
When I run this, I receive the following:
Failure/Error: page.select 'Preferred', :from => 'ticket_service_type'
Capybara::ElementNotFound:
cannot select option, no select box with id, name, or label 'ticket_service_type' found`
The AJAX request this button click event triggers doesn't have anything to do with the select tag, so reversing the order in the test causes the test to pass. I know that Capybara's default wait time is 2 seconds and so I changed it to 10 with:
Capybara.default_wait_time = 10
This does not cause the test to pass. How can I get these two methods to play nice with one another and work in the order in which a user would operate the web page?
(Had I posted the code from my spec, I bet this would have been solved quickly.)
From The Cucumber Book (emphasis mine):
Luckily, Capybara knows how to deal with this situation in a simple way. If we add an explicit call to find, passing a CSS selector for a DOM element on the page that doesn’t yet exist, Capybara will wait a little (50ms) and try again until the element appears. If it doesn’t appear after a while (two seconds by default, though this is configurable), it will raise an exception, causing the step definition to fail.
So have your AJAX write something to the DOM then find() it in your step definition. It's not ideal. In my case I'm introducing a (hidden) DOM element just to facilitate testing but I haven't found another way.
Be sure to add :js => true to integration tests which depend upon JavaScript.

Resources