I am trying to create an xpath with using (and) and (or) both expression together but not getting success.
I want to grab prices of products but some prices comes as .//p/span[#class='currency-value'] while other comes as .//p/span/span[#class='currency-value'] so I want to use OR exprssion for this
And I don't want price where product is for Advertise so I am using .//span[not(contains(text(),'Ad'))]
I have tried below xpath but its not working.
.//p/span[#class='currency-value'] | .//p/span/span[#class='currency-value'] and .//span[not(.='Ad')]
Rather than saying "try XXX" I think it's useful if you understand what's wrong with your current attempts.
.//p/span[#class='currency-value'] | .//p/span/span[#class='currency-value'] and .//span[not(.='Ad')]
The "|" operator in XPath means "union" - it forms the union of two node-sets. So //x | //y selects the union of the nodes selected by //x and those selected by //y. So far so good. You can simplify the "union" part of your expression to
(.//p/span | .//p/span/span)[#class='currency-value']
if you want.
The "and" is more problematic. The operands of "and" have to be booleans, whereas in your expression both operands are node-sets. I suspect (though I can't be sure) that your intent is to exclude from the union node-set those nodes that satisfy the predicate .='Ad' but without seeing your source data it's not clear how the products and prices relate to each other. Perhaps you intended this:
(.//p/span | .//p/span/span)[#class='currency-value'][not(.='Ad')]
or perhaps this:
(.//p/span | .//p/span/span)[#class='currency-value'][not(..='Ad')]
Either way, if I'm right that your intent is to exclude some of the nodes that would otherwise be selected, then an additional predicate is the way to do it.
After looking at the page, this CSS selector will work
div.hide-lg:not([data-behat-search-results-ads-xl]):not(.prolist-row) p > span.currency-value, div.hide-lg:not([data-behat-search-results-ads-xl]):not(.prolist-row) p > span > span.currency-value
Try this one. Do not overcomplicate with //p/span
//span[#class='currency-value'][not(.='Ad')]
Try this one. Combines #Vitaliy's answer with yours:
.//p/span[#class='currency-value'][not(.='Ad')] | .//p/span/span[#class='currency-value'][not(.='Ad')]
Please find the use of AND and OR in xpath as below:
this is the Page html:
<div class="mtlist-tab">
<ul class="wwf-tab-h">
<li class="tab-head">
<li class="tab-head on">
<li class="tab-head">
</ul>
</div>
if you are using AND in xpath where the two elements have same class class name then it will return 2 matching elements.
//li[#class='tab-head' and #class='tab-head']
if you are using AND in xpath where the two elements have different class class name then it will return 0 matching elements.
//li[#class='tab-head' and #class='tab-head on']
if you are using OR in xpath where the two elements have different class class name then it will return 3 matching elements(according to the HTML).
//li[#class='tab-head' OR #class='tab-head on']
Related
<tr><strong>dynamic title</strong></tr>
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr><strong>static title</strong></tr>
<tr>4</tr>
<tr>5</tr>
<tr><strong>dynamic title</strong></tr>
<tr>6</tr>
<tr>7</tr>
<tr><strong>dynamic title</strong></tr>
<tr>8</tr>
<tr>9</tr>
Given the above scenario, I want to select the values 4 and 5 using the static title as a marker.
//tr[preceding-sibling::tr[strong][contains(.,"static title")]] or
//tr[strong[contains(.,"static title")]]/following-sibling::tr
This will select 6,7,8 and 9 too.
Is there a way to make it select the first preceding-sibling with strong and then check for the contains? Or maybe we can use count() somehow?
You can insert [1] after strong which means "the first strong preceding sibling must be static":
//tr[preceding-sibling::tr[strong][1][contains(.,"static title")]]
This looks little messy, but should work:
//tr[contains(strong,"static title")]/following-sibling::tr[strong][1]/preceding-sibling::tr[preceding-sibling::tr[contains(strong,"static title")]]
I am having trouble using selecting from this select element.
<select name="vehicle_attrs[position_count]" class="mb1"><option>Position / Quantity</option><option>Front</option><option>Rear</option></select>
I have tried
select('Front', :from=>'mb1')
select('Front', :from=>'vehicle_attrs[position_count]')
select('Front', :from=>'vehicle_attrs[1]')
All of them result in a can not find selection box error
I've never liked how restrictive Capybara's concept of a 'locator' is (i.e. must have a name/id/label), but if you dig into the source code, those helpful methods like select, click_on, and fill_in are just wrappers for find and some native method of Element, which takes arbitrary CSS, and works in almost all situations. In this case, you could use:
find('[name="vehicle_attrs[position_count]"]').find('option', text: 'Front').select_option
Since dropdowns often have multiple similar options, where one is a substring of the other, you might consider using an exact string match, like
find('[name="vehicle_attrs[position_count]"]').find('option', text: /\AFront\z/).select_option
From the docs for select - https://www.rubydoc.info/github/teamcapybara/capybara/Capybara/Node/Actions#select-instance_method - we can see that the from option takes "The id, Capybara.test_id atrtribute, name or label of the select box".
Neither 'mb1' or 'vehicle_attrs[1]' are any of those so they would be expected to fail.
'vehicle_attrs[position_count]' is the name so assuming the box is actually visible on the page (not replaced with a JS driven select widget, etc), that should work. If it doesn't, then edit your question and add the full exact error message you get when trying to use it. Of course if there is only one select box on the page with an option of 'Front' then you don't need to specify the from option at all and can just do
select 'Front'
Using findElements() and size() methods we can get the count.
But I want to extract the count using count() function in the xpath.
Will the count function return an integer value?
Suppose,
My xpath is (//input[#id='stack'])[3] and there are 3 matching nodes with //input[#name='hai']
Can I modify my xpath like below?
(//input[#id='stack'])[count(//input[#name=''hai])]
Yes, if
count(//input[#name='hai'])
evaluates to
3
then
(//input[#id='stack'])[count(//input[#name='hai'])]
will select the same nodes as
(//input[#id='stack'])[3]
would select.
However, your intent is quite unclear, especially given that
//input[#id='stack'] will select all of the input elements with
an id attribute value of 'stack'. Usually id attribute values
are unique across the document, so usually this would select only a
single element.
(//input[#id='stack'])[count(//input[#name='hai'])] assumes that there are at least as many input elements id'ed as 'stack' as there are input elements named 'hai' -- an odd assumption.
driver.findElements() returns a List of WebElements, and .size() returns the integer size of a list, so I think youd be better off doing the following:
int myCount = driver.findElements(By.xpath("<Your Xpath Here>")).size();
This will get you the count of elements on your page that match your xpath input
I am trying to find a way to return the index of a HTML child tag based on its xpath.
For instance, on the right rail of a page, I have three elements:
//*[#id="ctl00_ctl50_g_3B684B74_3A19_4750_AA2A_FB3D56462880"]/div[1]/h4
//*[#id="ctl00_ctl50_g_3B684B74_3A19_4750_AA2A_FB3D56462880"]/div[2]/h4
//*[#id="ctl00_ctl50_g_3B684B74_3A19_4750_AA2A_FB3D56462880"]/div[3]/h4
Assume that I've found the first element, and I want to return the number inside the tag div, which is 1. How can I do it?
I referred to this previous post (How to count HTML child tag in Selenium WebDriver using Java) but still cannot figure it out.
You can get the number using regex:
var regExp = /div\[([^)]+)\]/;
var matches = regExp.exec("//[#id=\"ctl00_ctl50_g_3B684B74_3A19_4750_AA2A_FB3D56462880\"]/div[2]/h4");
console.log(matches[1]); \\ returns 2
You can select preceeding sibling in xpath to get all the reports before your current one like this:
//h4[contains(text(),'hello1')]/preceding-sibling::h4
Now you only have to count how many you found plus the current and you have your index.
Another option would be to select all the reports at once and loop over them checking for their content. They always come in the same order they are in the dom.
for java it could look like this:
List<WebElement> reports = driver.findElements(By.xpath("//*[#id='ctl00_ctl50_g_3B684B74_3A19_4750_AA2A_FB3D56462880']/div/h4")
for(WebElement element : reports){
if(element.getText().contains("report1"){
return reports.indexOf(element) + 1;
}
}
Otherwise you will have to parse the xpath by yourself to extract the value (see LG3527118's answer for this).
I want to select the following html items (action,comedy) but except the last one (tags).
To select all my follow code is working:
//*[#id="video-tags"]//a[contains(#href,'tags')]
But to select except the last one (tags), it won't work with my follow code:
//*[#id="video-tags"]//a[contains(#href,'tags') not(position() > last() -1)]
The html
<ul id="video-tags">
<li>Uploader: </li>
<li class="profile_name">wilco</li>
<li><em>Tagged: </em></li>
<li>action, </li>
<li>comedy, </li>
<li>more <strong>tags</strong></li>
</ul>
Thanks in advance
Nick
Aside from the syntax error - you need an and, i.e. contains(#href,'tags') and not(position()...) - you're tripping up on a subtlety of how // is defined.
The XPath //a[position() < last()] will not give you every a except the last one, it will give you every a that is not the last a inside its respective parent element. Since each li contains at most one a, every a is the last a in its respective parent, so this test will match nothing at all.
You can achieve what you want by wrapping most of the expression in parentheses and putting the position check in a separate predicate
(//*[#id="video-tags"]//a[contains(#href,'tags')])[position() < last()]
The parentheses cause the final predicate to apply to the node set selected by the expression as a whole, rather than just to the a location step, i.e. it will first find all the a elements whose href contains "tags", then return all but the last of these selected elements in document order.
Technical explanation - the definition of // in XPath is that it is a shorthand for /descendant-or-self::node()/ (including the slashes), which is a location step that gives you this node and all its descendant nodes. So //a means /descendant-or-self::node()/child::a, and //a[something] means /descendant-or-self::node()/child::a[something] - the predicate applies to the child:: step, not the descendant-or-self:: one. If you want to apply a predicate to the descendant search then you should use the descendant:: axis explicitly - /descendant::a[something].
try this
(//ul[#id="video-tags"]//a[contains(#href,'tags')]/text())