Rails + Capybara: How to target last div of a certain class? - capybara

I have 3 divs with class variant_container. How would I select the last one so that I could do
within(last_variant_div) do
...
end
?

Depending on your preferences, here are two solutions.
Using XPath
XPath has a last() function to find the last matching node. You can use this in the within locator:
within(:xpath, '(//div[#class="variant_container"])[last()]') do
# do stuff
end
Using all
The within method can also be passed a node to search within. This means that you can locate the last node by find or all, which can sometimes give more flexibility.
For example, in this case, we can now use a CSS-selector to find the last div (by combining it with Capybara's all method). Note that CSS-selectors by themselves do not have the ability to find the last element of a certain class.
last_div = all('div.variant_container').last
within(last_div) do
# do stuff
end

Related

AppiumLibrary: Element Should Not Be Visible Keyword?

In AppiumLibrary, as of version 1.4.5, a very handy keyword called Element Should Be Visible has been introduced. But I am looking for the opposite version of that keyword too, which would be something like Element Should Not Be Visible. Since AppiumLibrary doesn't have that yet, is there any way we could achieve this?
There's always the possibility of expanding the library's python code, this shouldn't be very difficult. You basically just need to clone the Element Should Be Visible keyword definition and change the condition to do the opposite.
But, if that's not a possibility for you, perhaps you could use the keyword Run Keyword And Expect Error, in combination with the keyword you mentioned, Element Should Be Visible. Using this keyword on an element that isn't visible would throw an error, which in this particular case would be the desired outcome.
Still, that's a fishy workaround which will not help the readability of your test, and you should consider first looking into expanding the library itself.
Thanks to Verv for the guidelines. I tried both approaches that he suggested, and both seem to be super easy. For future reference, I'm explaining both methods here.
Method 1:
Under AppiumeLibrary/keywords directory, there's a file called _element.py, which defines the Element Should Be Visible keyword. I was able to clone it to create the new keyword I was looking for.
Below is the code snippet that defines Element Should Be Visible
def element_should_be_visible(self, locator, loglevel='INFO'):
"""Verifies that element identified with locator is visible.
Key attributes for arbitrary elements are `id` and `name`. See
`introduction` for details about locating elements.
New in AppiumLibrary 1.4.5
"""
if not self._element_find(locator, True, True).is_displayed():
self.log_source(loglevel)
raise AssertionError("Element '%s' should be visible "
"but did not" % locator)
Next to the above code snippet, you can add the following code snippet to define a new keyword Element Should Not Be Visible
def element_should_not_be_visible(self, locator, loglevel='INFO'):
"""Verifies that element identified with locator is visible.
Key attributes for arbitrary elements are `id` and `name`. See
`introduction` for details about locating elements.
New in AppiumLibrary 1.4.5
"""
if self._element_find(locator, True, True).is_displayed():
self.log_source(loglevel)
raise AssertionError("Element '%s' should not be visible "
"but did" % locator)
Method 2
If you don't want to expand the existing library, you could just use the combination of existing keywords as follows:
${isVisible}= Run Keyword And Return Status Element Should Be Visible 'someElementSelector'
Should Be Equal ${isVisible} ${FALSE}

How to switch to a frame from within another frame in Capybara/Poltergeist?

I'm new to Ruby on Rails and I was working on a catalog website which consists of two frames: the first one contains product families and the second one dynamically shows the list of machines within those families as you click them on the first frame. This happens since form target is that frame's name:
I have already inspected this answer but there is an important difference: I want to iteratively click through the product family links in the first frame by working "within_frame('families_frame')" and get the list of machines in the second frame. However "switch_to_frame" and similar solutions do not work and I get the following error:
switch_to_window is not supposed to be invoked from within's, within_frame's' or within_window's' block. (Capybara::ScopeError)
Because my iterator runs from "within_frame". How can I switch to the resulting frame, reach the content and then switch back to the working frame?
The HTML structure is:
<HTML>
<FRAME NAME="families_frame" SRC=".../productfamily.jsp">
<FRAME NAME="machines_frame" SRC=".../blank.jsp">
</HTML>
Thank you a lot.
With the html structure you've posted you need to iterate outside your within_frame so something like this
families = []
within_frame 'families_frame' do
families = page.all('the selector for the items to iterate on')
end
families.each do |family|
within_frame 'families_frame' do
family.click
end
within_frame 'machines_frame' do
# whatever you need to do in the machines frame
end
end
Note: this will only work if the families frame isn't being updated too -- if it is you'll need to keep an index position and query for it each time you go into the families_frame rather than being able to iterate through the result of the selector or you'll end up with stale element references

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

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

Resources