Understanding the meaning of the capybara syntax - ruby-on-rails

I want to understand what the following Capybara syntax means -
find(:xpath, '//*[#id="application-lines"]/div[2]/ul/li[2]/a').click
I particularly don't understand the second attribute of the find method.
It would be great if someone could help me understand the syntax!

That is not something specific of Capybara, that is an XPath, is used to navigate the elements of an XML document.
In this case is looking for a node with id application-lines and inside that element retrieving the second div[2], with an ul element from which retrieves the second li and retrieves the a element inside it. All this ends up doing is a click on the a element found.
You can learn about XPaths here: XPath tutorial

Related

In Appium 1.0+, now that finding elements by tag is deprecated, how do I find an element by tag name when operating on the DOM of the webview?

Once I switch my context to the DOM of the webview, I want to be able to search those elements by tag, but I get the error that searching by tag is deprecated and to search by class instead. This won't work to find DOM elements by tag. Is there still a way to do it? Thanks!
As per Appium documentation for migrating to 1.0:
We've removed the following locator strategies:
-name
-tag name
... tag name has been replaced by class name. So to find an element by its
UI type, use the class name locator strategy for your client.
Why searching by tag name?
Although Selenium still supports this type of query, Appium decided not to do anymore. Actually when interacting with the device, searching by tag name is very inefficient.
Why would you want to do that? Think about it, if your page has a bit of content, you will end up having many p, div, span tags. Your search will return many elements and then you will have to go thorugh the list and locate the one you are interested in. If your page is very little, then you will probably end up with one tag of the type you are looking for, however why not applying a class and solve the problem?
Classes are not for CSS style
Remember that HTML attribute class was not introduced by W3C for applying CSS style. It is used to provide an element with more informationa bout its purpose in the DOM. When you apply a class to an element, you should do that basing on the role that element holds! Thus locating an element by class is sure better.
So forget searching by tag name. You should change your strategy and apply class names to your tags in your hybrid app. If you do not want to do so, then do not switch to the new version of Appium but this will keep you far from future innovations!
Migrating from a tagname based element location to a class name
orientd one is good practice. That's why you should change too.
maybe this can help
element.getAttribute("class")

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 to click on the second link with the same text using Capybara in Rails 3?

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

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

Hpricot CSS Class search

I am working on some code that scrapes a page for two css classes on a page. I am simply using the Hpricot search method for this as so:
webpage.search("body").search("div.first_class | div.second_class")
...for each item found i create an object and put it into an array, this works great except for one thing.
The search will go through the entire html page and add an object into an array every time it comes across '.first_class' and then it will go through the document again looking for '.second_class', resulting in the final array containing all of the searched items in the incorrect order in the array, i.e all of the '.first_class' objects, followed by all the '.second_class' objects.
Is there a way i can get this to search the document in one go and add an object into the array each time it comes across one of the specified classes, giving me an array of items that is in the order they are come across in on the page i am scraping?
Any help much appreciated. Thanks
See the section here on "Checking for a Few Attributes":
http://wiki.github.com/why/hpricot/hpricot-challenge
You should be able to stack the elements in the same way as you do attributes. This feature is apparently possible in Hpricot versions after 2006 Mar 17... An example with elements is:
doc.search("[#href][#type]")
Ok so it turned out i was mistaken and this didn't do anything different to what i previously had at all. However, i have come up with a solution, wether it is the most suitable or not i am not sure. It seems like a fairly straight forward for an annoying problem though.
I now perform the search for the two classes above as i mentioned above:
webpage.search("body").search("[#class~='first_class']|[#class~='second_class']")
However this still returned an array firstly containing all the divs with a class of 'first_class' followed by all divs with a class of 'second_class'. So to fix this and get an array of all the items as they appear in order on the page, i simply chain the 'add_class' method with my own custom class e.g. 'foo_bar'. This then allows me to perform another search on the page for all divs with just this one tag, thus returning an array of all the items i am after, in the order they appear on the page.
webpage.search("body").search("[#class~='first_class']|[#class~='second_class']").add_class("foo_bar")
webpage.search("body").search("[#class~='foo_bar']")
Thanks for the tip. I hadn't spotted that in the documentation and also found another page i hadnt seen either. I have fixed this with the following line:
webpage.search("body").search("[#class~='first_class']|[#class~='second_class']")
This now adds an object into the array each time it comes across one of the above classes in the document. Brilliant!

Resources