Capybara Ambiguous match - ruby-on-rails

For some reason when I do this:
click_on("Save")
My Capybara detects both:
<button>Save</button>
<button>Save & Schedule</button>
And so it gives "ambiguous match: 2 elements found" error.
Is there a way to tell it to do exact match?

No, there is no way to do that. You have to add uniq ids for these buttons since capybara 2.0 rises an error if more than one element was found.

Related

How to write xpath if two text field having same class name in appium 1.7.for ios

Can anyone help me to write xpath in appium 1.7 for iOS..
If two class having same name
driver.findElementByClassName("TextField").sendKeys("abc");
driver.findElementByClassName("TextField").sendKeys("1234");
In any case use className is not reliable search strategy as its not a unique.
I usually suggest following rules:
have unique AccessibilityId for most of elements used in automation tests (talk with developers if they agree to fix this)
If not, build unique Xpath with relation to other elements that have AccessibilityId or more unique className
As a temporary solution you can do this:
List<WebElement> textfields = driver.findElementsByClassName("TextField");
textFields.get(0).sendKeys("abc");
textFields.get(1).sendKeys("1234");
since you didn't provide your page source, better print it out with driver.getPageSource() and think of good XPath that you put in:
List<WebElement> textfields = driver.findElementsByXpath(<your xpath>);
This below code working fine for me
driver.findElementByName("No account? Sign up").click();
driver.findElementByClassName("TextField").sendKeys("abc"); driver.findElementByClassName("SecureTextField").sendKeys("12345"); driver.findElementByClassName("SecureTextField").sendKeys(Keys.ENTER);
driver.findElementByXPath("(//XCUIElementTypeSecureTextField[2]").sendKeys("12345");
driver.findElementByXPath("(//XCUIElementTypeTextField)[2]").sendKeys(Keys.ENTER);
driver.findElementByXPath("(//XCUIElementTypeTextField)[2]").sendKeys("9876543210");

Understanding the meaning of the capybara syntax

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

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

Resources