How to click on the second link with the same text using Capybara in Rails 3? - ruby-on-rails

In a view file I have:
= link_to 'View', post
= link_to 'View', comment
In a spec file (I'm using Capybara):
click_on 'View'
It clicks on the first link, but I want it to click on the second one. How can I do it?

You could try to find all entries and deal with an array:
page.all('a')[1].click
Would help to have a class or use within to scope your search ;)

There's probably a few ways but I usually scope something like this.
within(".comment") do
click_on("View")
end
There's quite possibly/probably alternatives as well. I usually do my acceptance testing from cucumber, so my steps typically look like
When I follow "View" within the comment element
Where I have a step that translates within the comment element to a scoped call to the step itself (which I think is built into the latest capybara web_steps)

The worst thing about "the second" link is that it can become the third or the first or even the twenty fifth someday. So, scoping with a within block is the best way. Example:
within(".comment") do
click_on("View")
end
But if it is difficult to specify the link with a within scope (which sometimes it really is), I guess the way to click the second link with a certain text is:
find(:xpath, "(//a[text()='View'])[2]").click
In later versions of capybara (2.0.2, for example) both click_on 'View' and click_link 'View' will raise an ambiguous match error:
Failure/Error: click_on 'View'
Capybara::Ambiguous:
Ambiguous match, found 2 elements matching link or button "View"
So this won't do even if you want to click the first link (or if any link would be ok, which is my case).
As far as I understand this is made to force people write more specific tests where particular links are clicked.
It definitely could be tricky to debug the code if you accidentally placed two or more links with identical text and try to see what is happening. It's good to rely on something that is unlikely to change and specifying a link with a within block is a nice way to do this.

There are many ways for solving this type of problems.
Do it like this
if(page.find("a")[:href] == "comment")
click_on("View")
or
page.find("a:eq(2)").click
Remember javascript indexing starts with 0 while In Capybara, indexing starts with 1. So use a:eq(2) here for second href.

For capybara 2 solution:
within(".comment") do
click_on("View")
end
would not help if you have a few .comment. So simple use: page.first(:link, "View").click

This works for me if you have several rows of identical classes and you want to find the second row. Like a previous author mentioned, capybara indexing starts at 1.
within all(".trip-row")[2] do
assert page.has_content?("content")
end

If you use capybara-ui you could define the widget, or reusable DOM reference, for each widget.
# define your widget. in this case,
# we're defining it in a role
widget :view_post, ['.post', text: 'View']
widget :view_comment, ['.comment', text: 'View']
# then click that widget in the test
role.click :view_post
role.click :view_comment

Related

Capybara: simple way to find elements without particular text

There are several elements on the page, and I need an array of them, but without an element with particular text. What I do now is
tabs_quantity = page.all(:css, 'div.class1 ul.tabs li.tab.exist', visible: true)
tabs_quantity.each { |x|
if x.text != 'Main'
...blah-blah-blah...
I`ve seen only ":text => 'Text' everywhere but what I need is opposite to equality, so I've searched and tested but have not find if there is something simple like
tabs_quantity = page.all(:css, 'div.class1 ul.tabs li.tab.exist', visible: true, :text !=> //or "not equal", whatever// 'Main')
Thanks.
I agree with Raza. Definitely try to set a class on the elements you're looking for.
If that turns out to be impossible for whatever reason, here are a couple more options.
1) You could try regular expressions:
# match text that doesn't contain 'Main'
page.all('li.tab.exist', text: /^(?:(?!Main).)*$/)
That's not super easy to read. But since you've scoped your class down quite a bit, it might not be too slow.
2) Another alternative is xpath:
# find li with class of 'exist' and any text except 'Main'
page.all(:xpath, "//li[contains(#class, 'exist') and not(contains(.,'Main'))]" )
That's also a bit unweildy, especially if you want to go as in depth as your original css matcher. I'd definitely include a comment along with it.
Further info: using a css class is definitely faster and easier. Always avoid text matches whenever possible.
Just add a class to the group of elements you want to select and the find with:
page.find('.class_name')

Capybara: Finding a second checkbox which is identical to the first

I am writing tests for view files. I have a page with two checkboxes which allows the user to Select All items in two different lists. However, the checkboxes are part of a partial so are identical. I have managed to check the first one using:
first(:checkbox, "Select all").click
But I am unable to check the second. I have tried replacing first with last and with second but to no avail.
I think I may need to use the find selector but am struggling with that also. Any help much appreciated.
Thanks
UPDATE
It appears that this in fact another issue. I think that any checkbox that impacts on other checkboxes does not work as expected when "checked" by Capybara. So the current answers below work in the sense that they do check the checkbox, but the expected behaviour does not occur i.e. the checkboxes linked to them do not get "checked". I may be wrong though.
try this
# find the second checkbox
find('input[type="checkbox"]:nth-child(2)').click
You can use find all
all('input[type="checkbox"]', :text => 'Select all')[1].click
Whoops. Turns out it was a JS issue and not Capybara macthers! Thanks for the help though!

StaleElementReference Error Element not found in the cache

I'm using Capybara 2.1 with Ruby 1.9.3 using the selenium driver (with Minitest and Test Unit) in order to test a web app.
I am struggling with the StaleElementReferenceException problem. I have seen quite a number of discussions on the topic but I haven't been able to find a solution to the issue that I am facing.
So basically, I'm trying to find all pagination elements on my page using this code:
pagination_elements = page.all('.pagination a')
Then I'm doing some assertions on those elements like:
pagination_elements.first.must_have_content('1')
After those assertions, I'm continuing the test by clicking on the Next Page link to make sure that my future first pagination element will be the Previous Page.
To do that I'm retrieving paginations elements again :
new_pagination_elements = page.all('.pagination a')
And the Stale Error is occurring here, because I'm reaching elements that I've already reached. ( Here is the error )
You can see the link states here.
I really have no idea how to make this common test work properly.
Do you have any tips for a better way to reach my pagination elements?
I sometimes have some problem with AJAX intensive pages, in my case this workaround solves it:
begin
...
rescue Selenium::WebDriver::Error::StaleElementReferenceError
sleep 1
retry
end
I saw the main message in the gist is:
Element not found in the cache -
perhaps the page has changed since it was looked up
I have similar case before. There are two solutions:
Add page.reload before checking same stuff in new page, if you have set Capybara.automatic_reload = false in spec_helper
find a special element in new page which previous page doesn't have. This effect is equivalent to wait.
Another method is to use specific selector. For example, instead of
pagination_elements = page.all('.pagination a')
Use
pagination_elements = page.all('#post_123 .pagination a')
Append a unique id area to the selector and you should not meet such problem.
Interesting link about this error and how to fix it : http://stefan.haflidason.com/testing-with-rails-and-capybara-methods-that-wait-method-that-wont/
Apparently, in addition to race conditions, this error also appears due to misused within blocks. For example:
within '.edit_form' do
click '.edit_button'
# The error will appear here if the 'edit_button' is not a
# descendant of the 'edit_form'
end
HAve you tried to use WebDriver directly rather than via Capybara? This woudl potentially give you more control of when to and when to not cache objects.
e.g. (Apologies for the java syntax but should get the idea)
WebElement searchField = driver.findElement(By.CssSelector("input.foo"));
searchField.click();
searchField.sendKeys("foo foo");
System.out.println(searchField.getText());
//Do something elsewhere on the page which causes html to change (e.g. submit form)
.....
....
//This next line would throw stale object
System.out.println(searchField.getText());
//This line will not throw exception
searchField = driver.findElement(By.CssSelector("input.foo"));
System.out.println(searchField.getText());
Assigning "findElement" again to "searchField" means that we re-find the element. Knowing when to and when not re-assign is key went deciding how to cache your webelements.
I have not used Capybara, but I assume that it hides the caching strategy from you?

How do i determine what goes in the model vs controller?

This is my most confused aspect that I continually have to ask people about. They have given me answers like 'if it works with data it goes in the model'... but to me that is pretty much ALL of it.
Does anyone have a better way to explain this?
Perfect example from my current code:
I have a listing of posts that can be favorited or not favorited. On the front end, i differentiate the different ones by dynamically adding class="favorite" to the HTML depending on if its a favorite or not.
So basically ... <li class="item<%= is_favorite?(current_user.id) %>">
Part of me thinks this should go in the model because its going to be running a statement to find a record that matches :resource_id and :user_id...
but another part of me thinks its going to be in the controller because its directly outputting the word " favorite" which is used in html
My second mini question is, is it the-rails-way to put methods that the controller uses in the same controller? as long as they remain un-routed, etc. Or is that not the right spot?
You're thinking properly.
The right answer is that you should use presenters or decorators.
See this railscast for inspiration.

How to get parent node in Capybara?

I'm working with many jQuery plugins, that often create DOM elements without id or other identification properties, and the only way to get them in Capybara (for clicking for example) - is to get their neighbor (another child of its ancestor) first. But I didn't find anywhere, does Capybara support such things for example:
find('#some_button').parent.fill_in "Name:", :with => name
?
I really found jamuraa's answer helpful, but going for full xpath gave me a nightmare of a string in my case, so I happily made use of the ability to concatenate find's in Capybara, allowing me to mix css and xpath selection. Your example would then look like this:
find('#some_button').find(:xpath,".//..").fill_in "Name:", :with => name
Capybara 2.0 update: find(:xpath,".//..") will most likely result in an Ambiguous match error. In that case, use first(:xpath,".//..") instead.
I found the following that does work:
find(:xpath, '..')
Capybara has been updated to support this.
https://github.com/jnicklas/capybara/pull/505
There isn't a way to do this with capybara and CSS. I've used XPath in the past to accomplish this goal though, which does have a way to get the parent element and is supported by Capybara:
find(:xpath, '//*[#id="some_button"]/..').fill_in "Name:", :with => name
If you stumbled across this trying to figure out how to find any parent (as in ancestor) node (as hinted at in #vrish88's comment on #Pascal Lindelauf's answer):
find('#some_button').find(:xpath, 'ancestor::div[#id="some_div_id"]')
This answer pertains to how to manipulate a sibling element which is what I believe the original question is alluding to
Your question hypothesis works with a minor tweak. If the dynamically generated field looks like this and does not have an id:
<div>
<input></input>
<button>Test</button>
</div>
Your query would then be:
find('button', text: 'Test').find(:xpath, "..").find('input').set('whatever')
If the dynamically generated input does come attached with an id element (be careful with these though as in angular, they are wont to change based on adding and deleting elements) it would be something like this:
find('button', text: 'Test').find(:xpath, "..").fill_in('#input_1', with: 'whatever')
Hope that helps.
I'm using a different approach by finding the parent element first using the text within this parent element:
find("<parent element>", text: "text within your #some_button").fill_in "Name:", with: name
Maybe this is useful in a similiar situation.
As mentioned in comment by #Tyler Rick Capybara in these days have methods[
ancestor(selector) and sibling(selector)
I needed to find an ancestor with a css class, though it was indeterminate if it the target ancestor had one or more css classes present, so I didn't see a way to make a deterministic xpath query. I worked this up instead:
def find_ancestor_with_class(field, cssClass)
ancestor = field
loop do
ancestor = ancestor.find(:xpath, '..')
break if ancestor.nil?
break if ancestor.has_css? cssClass
end
ancestor
end
Warning: use this sparingly, it could cost you a lot of time in tests so make sure the ancestor is just a few hops away.
Here it is
http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Base:parent
There is a parent method present;)

Resources