webDriver cssSelector based on inner child attribute - java

here is some sample code and what I am trying to do:
<dl id="parentId">
<dt>
<a someattr="whatIwant"/>
</dt>
<dt>
<a someattr="whatIwantNextTime"/>
</dt>
</dl
I am trying to select the a element with someattr="whatIwant". this is my current cssselector:
"dl[id='parentId']>a[someattr='whatIwant']"
as well as:
"dl[id='parentId']>dt>a[someattr='whatIwant']"
Unfortunately, neither of these work; I appear to be following the w3schools template correctly, however I get element cannot be found exceptions when I try to run this, or illegal string exception if I add the '>dt>'. Does anyone have any insight on how to do this?
Thanks.

EDIT: You are using the direct descendant selector strategy (>) this only works if the element is a direct child. If you want a descenant of, use ()
dl#parentId a[someattr='whatIwant']
Also, assuming that your whatIwant is unique, then this selector will work perfectly..
Even further, you can do:
dl#parentId > td:nth-child(X) > a
where X is 1, 2, 3 (the index of the <dt /> that you want.
Also make sure that you are using the By.cssSelector strategy and nothing else if you are specifying CSS selectors.

So apparently I didn't do enough research, as I just figured this out 5 minutes after posting. The descendent selector ' ' works perfectly, which is:
"dl[id='parentId'] a[someattr='whatIwant']"

Related

I was able to run my program till morning. But suddenly getting this error " Driver info: driver.version: unknown [duplicate]

I am getting the following error:
"Compound class names not permitted"
While trying to access web element where-in the element's class name have spaces in between. The page source for the web element is as below.
driver.findElement(By.className("alert alert-success"));
<div class="alert alert-success" alert-dismissable"="" id="58417" style="display: none;">
<button type="button" class="close hide-panel close-icon-58417" data-dismiss="alert" aria-hidden="true" style="display: inline-block;">×</button><span id="caret-58417" class="notification-caret caret-58417"></span>
<div class="hide-panel close-icon-58417" id="58417" style="display: block;">
<span class="glyphicon glyphicon-ok-sign"></span><strong>Success</strong> KeyLinks Updated Successfully
<div class="notification-panel-body panel-body-58417">REST Invocation Success</div>
</div>
</div>
I tried to find the element by CSS path as below. But the element is not searchable with this.
driver.findElement(By.cssSelector(".alert alert-success"));
This was the workaround given in the Link but still no success. your help will be much appreciated.
You can access the element if it has multiple classes using "By":
from selenium.webdriver.common.by import By
driver.findElement(By.cssSelector(".alert.alert-success"));
You can use
driver.findElement(By.className("alert-success"));
or
driver.findElement(By.className("alert"));
As right now selenium doesn't support multiple class name.If your class name includes a space, WebDriver will see it as a "compound selector".
You can use cssSelector or id for selecting the webelement.
If usage of class name is must you can use the following ways:
1) css selectors:
driver.findElement(By.cssSelector(".alert.alert-success");
2) using xpath
driver.findElement(By.xpath("//div[#class='alert alert-success']"))
Try avoiding xpath and use css selectors instead.
The issue is because of the way find by Class name works.
in your code class name is class="alert alert-success"
If the class name has space you'll get the above error. You can simply get rid of the issue by using Id, CSS, Xpath, regular expression or any other element finder method.
Do you need to use Class Name or can you use another method? Let me know if you need to use class name.
It can also be done using class name like:
driver.find_element_by_class_name("alert")
or
driver.find_element_by_class_name("alert-success")
you can select anyone class name from two or more class names separated by spaces it'll work just fine.
Most of the time class name attribute of any element has groups of classes names.
like class = 'alert alert-success another class name'
if you are using css selector ,then just remove spaces between the class names and create you clas name like below :
driver.findElement(By.cssSelector(".alert.alert-success.another.class.name")
. => represent class
=> represent ID
for CSS selector.

Selenium Find By Xpath returning the wrong element

I am trying to select a channel from a series of channles that are displayed in a HTML table. I'm using the following Selenium method to select the link
WebElement channel = driver.findElement(By.xpath("//span[contains(text(),Sales)]"));
channel.click();
However it's selecting the first channel in the list (Account Management) instead. I would expect that it would either select the correct channel or throw an error, rather than select the wrong one. The following is the full xpath of the channel I want:
/html/body/div[2]/div[2]/form/div/table/tbody[2]/tr/td/ul/li[2]/a/span
The list of channels is defined like this in the HTML code:
<form action="nextpage.do" method="post" name="selectChannelForm">
<div class="de">
<h2>Select channel</h2>
<table id="selectChannelForm">
<tbody id=""></tbody>
<tbody id="">
<tr rowtype="container">
<td class="desecond" colspan="3">
<ul>
<li>
<a id="selected_a" href="nextpage.do?selectedChannel=123">
<span>Account Management</span></a>
</li>
<li>
<a id="selected_a" href="nextpage.do?selectedChannel=456">
<span>Sales</span></a>
</li>
<li>
<a id="selected_a" href="nextpage.do?selectedChannel=789">
<span>Complaints</span></a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<input type="hidden" value="selectChannelForm" name="formid">
</div>
First - your mistake!
You forgot the quotation marks around "Sales", just change your code a bit and it will work:
WebElement channel = driver.findElement(By.xpath("//span[contains(text(),'Sales')]"));
channel.click();
Second - xpath bug?
You're right that it is weird, that you are not getting an error message but instead the first element that is a span.
This might acutally be a bug in xpath. The contains functions realizes that your second argument is no a string, but instead of returning false, it returns true.
It actually hits all three of the span items. You only get the first as a result because you used the findElement function.
Try this and you will see the quirk:
System.out.println(driver.findElements(By.xpath("//span[contains(text(),Sales)]")).size());
Result will be:
3
Third - might be "as designed"
Having a look at the w3c definition you will find the following line:
If the value of $arg2 is the zero-length string, then the function
returns true.
Then on the xpath-site of microsoft you will find another interesting hint to the puzzle:
If an argument is not of type string, it is first converted to a
string and then evaluated.
Putting all this information together, I guess, xpath interprets your non-string/non-variable second parameter as an empty string and therefore returns true for all span elements since you were searching for //span.
UPDATE
From #MichaelKay in the comments we learn, that my "guess" was pretty close:
In XPath and XQuery, a bare name like "hello" means child::hello, and
if you're not using schema-awareness, then the system will just look
for children called hello, and if there aren't any, it will return an
empty node-set.
Conclusion: The behaviour the OP sees is as designed, even though it seems pretty non-intuitive.
The xpath that you are using is missing quotes "" around Sales text. text() function takes an argument that is a string and a string can be formed using quotes. Update your xpath in the following way -
driver.findElement(By.xpath("//span[contains(text(),'Sales')]")).click();
Or if you want to assign it to a WebElement, then do put in your quotes -
WebElement channel = driver.findElement(By.xpath("//span[contains(text(),'Sales')]"));
channel.click();
If at all you want to write nested double quotes or nested single quotes then use an escape character \ to write it. Here's how -
WebElement channel = driver.findElement(By.xpath("//span[contains(text(),\"Sales\")]"));
channel.click();
Hope this helps.
The xpath need to be modified as "//span[contains(text(),'Sales')]" .
As we can see below in the method definitions, contains method will return true only If second parameter is also a text.
Source: https://en.wikipedia.org/wiki/XPath
contains(s1, s2)
returns true if s1 contains s2.
text()
finds a node of type text

Selenium div attributes keep changing, how can I find this element?

I am trying to find an element with Selenium and Java, the problem is that the element's id, class, and name always increment so I am not able to find it with selenium. Below is what I am currently trying:
WebElement field = driver.findElement(By.xpath("//input[contains(#linkText, 'Broadcast copy')]"));
In my html file these are the attributes that keeps changing:
id="files[%2Fopt%240%2Frules%2F%2F000102%2.xml][%2Fcluster%2Fname]"
name="files[%2Fopt%240%2Frules%2F%2F000102%2.xml][%2Fcluster%2Fname]"
value="copy (Cluster 102)"
Entire html
<tbody>
<tr class='rowOdd'>
<td><b>Name</b></td>
<td> <input type='text' data-validation='required validate-name-unique validate-name-not-empty' size='65' id='files[%2Fopt%240%2Frules%2F%2F000102%2Fcluster.xml][%2Fcluster%2Fname]' name='files[%2Fopt%240%2Frules%2F%2F000102%2Fcluster.xml][%2Fcluster%2Fname]' value='copy (Cluster 102)' /> </td>
These always increment and I have no access to the html file to change anything. So my question is how can I find this input element? Thanks in advance.
UPDATE
I get the error:
Unable to locate element:{"method":"id", "selector":"files[.*][.*]"}
I believe the xpath you are using is incorrect. Use
//input[contains(text(), 'Broadcast copy')]
instead of
//input[contains(#linkText, 'Broadcast copy')]
According to the html you have provide the following should work as well
//body[contains(.,'Name')]//input
Try this..
In case "copy (Cluster" text in value attribute is not changing, then you can try below xpath:-
//body[contains(.,'Name')]//input[contains(#value,'copy (Cluster')]
Since the attributes of id, class, and css were constantly changing, 'data-validation' was one that stayed the same all the time. So the code below worked for me.
driver.findElement(By.xpath("//input[#data-validation='required validate-name-unique validate-name-not-empty']"));

Java selenium xpath - getting all elements under a specific element

this is a simplified HTML structure i'm searching through:
<div class="main">
...other stuff...
<td class="child">44</td>
<td class="child">59</td>
<td class="child">11</td>
</div>
<div class="main">
...other stuff...
<td class="child">5</td>
<td class="child">14</td>
<td class="child">98</td>
</div>
...this kind of structure repeats with similar numbers a few more times but with identical class names
I need to extract all the numbers under the first found main class so I've made a query to search for the first main, and all td's with the specific class under it. Can somebody give me a hint what I'm doing wrong since this query gives me all the numbers from all td's with class "child" in all "main" div's:
List<WebElement> koefi = driver.findElements(By.xpath("//div[#class='main'][1]//td[#class='child']"));
What am I doing wrong or is my logic right but I'm missing some other parts of html which I haven't pasted here since the structure is too cumbersome..?
Thank You!!
p.s.:
I tried this also but again, I get contents of all td's with "child" class, and not only the first "main"..
List<WebElement> koefi = driver.findElements(By.xpath("//*[1][#class='main']//td[#class='child']"));
UPDATE:
I managed to solve my problem by first getting the first occurence of the "main" div which is by default found by the .findElement function:
WebElement element = driver.findElement(By.xpath("//*[1][#id='main']"));
And then extracting with .findElements function the "child" classes:
List<WebElement> kk = element.findElements(By.className("child"));
I am still unable to figure out why doesn't the .findElements with my xpath work, or it works too well, it extracts every "main" class and not only the first one. And the original HTML is too big to paste here, so I don't want to bother you guys!!
A much cleaner solution would be to first grab all the divs with class main, like so:
List<WebElement> allDivs = driver.findElements(By.className("main"));
Then, as you specified, find all the tds with class child, like so:
List<WebElement> tds = allDivs[0].findElements(By.className("child"));
After that, it is just a matter of iterating over all the "tds" and read out your values.
You say in a comment that
the "main"'s are not direct siblings
so I suspect you are falling foul of a common error related to the definition of // in XPath. The path
//div[#class='main'][1]
does not select the first "main" div in the document. The reason for this is that // is a shorthand for /descendant-or-self::node()/ (including the leading and trailing slashes), so what this path actually means is
/descendant-or-self::node()/child::div[#class='main'][1]
When you see it fully expanded you realise that the [1] relates to the child:: step and not the search for descendants, i.e. you'll get all the div elements in the document that have the class "main" and are the first div-with-class-main under their respective parent elements. If your actual HTML is
<div>
<div class="main">...</div>
</div>
<div>
<div class="main">...</div>
</div>
then that XPath would select both of them (they're both the first under their parents). If you do just want the first one in the document then you should use the descendant:: axis
/descendant::div[#class='main'][1]
which will give you the first matching descendant only.

Using variables in #{select} tag attributes

I have a page in my app with a dynamically-generated form, in which I need a number of <select> elements. Since I don't know in advance how many there will be, I need to put an ID number in the name attribute of each <select>. I'm trying to use the built-in #{select} tag (documentation here) like so:
#{ select 'select_' + ${IDnum}}
...options, etc...
#{/select}
When I do that I get a MissingMethodException:
No signature of method: Template_1009.$() is applicable for argument types:
(Template_1009$_run_closure1_closure2_closure3) values:
[Template_1009$_run_closure1_closure2_closure3#ad2388] Possible solutions:
_(java.lang.String), is(java.lang.Object), run(), run(), any(), get(java.lang.String).
When I instead do:
#{ select 'select_${IDnum}'}
the page renders correctly, but the select element renders like this in view-source:
<select name="select_${IDnum}" size="1" >
So, how do I get the value of ${IDnum} into the name attribute? I can do this with normal HTML <select> tags, but I'll need to write some Javascript to emulate Play's value:${x} functionality that I really don't want to bother with.
Thanks!
Try this :
#{select 'select_'+IDNum}

Categories