Xpath find a div without children with specific text - xpath

I need to retrieve a div without children with given text. I have this html
<h1>Rest Object</h1>
<div style="background-color: transparent;">
<div>Title: Rest object</div>
<div>ID: 2</div>
<div>Title: Rest object Copy</div>
<div>Full text: This is the full text. ID: 2</div>
<div>Value: 0.564</div>
<div>Timestamp: 2017-06-14 11:35:40</div>
</div>
I want to find <div>ID: 2</div>. How? I tried
xpath=(//div)
and it returns first div. I tried to use
xpath=(//div[not(div)])
and it returns
<div>Title: Rest object</div>.
UPDATE. Now I know I could you index.
xpath=(//div[not(div)][2])
<div>ID: 2</div>.
What if I don't know the index.
which returns

One way to get the needed div is to use starts-with() function:
//div[starts-with(.,'ID:')]
boolean starts-with(string, string) - Returns true if the first argument string starts with the second argument string; otherwise returns false
To restrict the search to div element which has no children you may use count(*) function:
//div[starts-with(.,'ID:')][count(*)=0]

Related

xPath - Why is this exact text selector not working with the data test id?

I have a block of code like so:
<ul class="open-menu">
<span>
<li data-testid="menu-item" class="menu-item option">
<svg>...</svg>
<div>
<strong>Text Here</strong>
<small>...</small>
</div>
</li>
<li data-testid="menu-item" class="menu-item option">
<svg>...</svg>
<div>
<strong>Text</strong>
<small>...</small>
</div>
</li>
</span>
</ul>
I'm trying to select a menu item based on exact text like so in the dev tools:
$x('.//*[contains(#data-testid, "menu-item") and normalize-space() = "Text"]');
But this doesn't seem to be selecting the element. However, when I do:
$x('.//*[contains(#data-testid, "menu-item")]');
I can see both of the menu items.
UPDATE:
It seems that this works:
$x('.//*[contains(#class, "menu-item") and normalize-space() = "Text"]');
Not sure why using a class in this context works and not a data-testid. How can I get my xpath selector to work with my data-testid?
Why is this exact text selector not working
The fact that both li elements are matched by the XPath expression
if omitting the condition normalize-space() = "Text" is a clue.
normalize-space() returns ... Text Here ... for the first li
in the posted XML and ... Text ... for the second (or some other
content in place of ... from div/svg or div/small) causing
normalize-space() = "Text" to fail.
In an update you say the same condition succeeds. This has nothing to
do with using #class instead of #data-testid; it must be triggered
by some content change.
How can I get my xpath selector to work with my data-testid?
By testing for an exact text match in the li's descendant strong
element,
.//*[#data-testid = "menu-item" and div/strong = "Text"]
which matches the second li. Making the test more robust is usually
in order, e.g.
.//*[contains(#data-testid,"menu-item") and normalize-space(div/strong) = "Text"]
Append /div/small or /descendant::small, for example, to the XPath
expression to extract just the small text.
data-testid="menu-item" is matching both the outer li elements while text content you are looking for is inside the inner strong element.
So, to locate the outer li element based on it's data-testid attribute value and it's inner strong element text value you can use XPath expression like this:
//*[contains(#data-testid, "menu-item") and .//normalize-space() = "Text"]
Or
.//*[contains(#data-testid, "menu-item") and .//*[normalize-space() = "Text"]]
I have tested, both expressions are working correctly

replace full string in xpath just get before

I am searching a solution to remove a string value obtained on a webpage with an XPath function.
I have this :
<div id="article_body" class="">
This my wonderful sentence, however here the string i dont want :
<br><br>
<div class="typo">Found a typo in the article? Click here.
</div>
</div>
So at the end I would have
This my wonderful sentence, however here the string i dont want :
I get the text with
//*[#id="article_body"]
Then I try to use replace:
//replace('*[#id="article_body"]','Found a typo in the article? ', )
But it doesn't work, so I think it's because I'm a newbie with XPath...
How can I do that please?
It appears that you are getting the computed string value of the selected div element.
The string-value of an element node is the concatenation of the string-values of all text node descendants of the element node in document order.
If you don't want to include the text() from the descendant nodes, and only want the text() that are immediate children of the div, then adjust your XPath:
//*[#id="article_body"]/text()
Otherwise, you could use substring-before():
substring-before(//*[#id="article_body"], 'Found a typo in the article?')

Xpath get node that only contains nodes of a certain type

Take this (id attributes only added so I can refer to them below)
<div id="one">
<figure>foo</figure>
<figure>bar</figure>
</div>
<div id="two">
<figure>foo</figure>
<div>bar</div>
</div>
<div id="three">
<div>bar</div>
</div>
How can I select all div elements whose children are all figure elements, i.e. selecting div one only in the given example?
I sort of need //div[count(not figure)>0].
This is one possible way :
//div[not(*[name() != 'figure']) and not(text()[normalize-space()])]
The left-side of and make sure the div doesn't have child element named other than 'figure', and the right-side make sure it doesn't have non-empty child text node.
or, the same approach but using count() :
//div[count(*[name() != 'figure']|text()[normalize-space()]) = 0]
I did it like this:
//div[figure][count(figure) = count(*)]
This finds divs that must contain at least one figure, and then it checks that the count of figure elements matches the count of all other elements; if this is true then it cannot contain anything else.

Can nightwatchjs perform an assertion based on a reference with accuracy?

In a case where a same element could change for a different id or name depending on many factors, I would be able to do an assertion on this element with accuracy.
Doest nighwatchjs permit to do an assertion based on a relative position like can do SAHI ? (Left of this element ..., Under a div, etc.)
I want to avoid Xpath solutions, it's based on the element type (div, id, name, etc.) and if I set it to all types:
//*[contains(text(),'hello world')]
I will get many occurrences and couldn't be able to know which one I'm trying to assert.
e.g : Running the same test on the same page, I would be able to find this "hello world" even if the div id changes or another element.
<div id="homebutton">
<p>
<a href=#>
<span name="hm">Home</span>
<a>
</p>
</div>
<div id=[0-9]>
<p>
<a href=#>
<span name="hw">hello world</span>
<a>
</p>
</div>
[...]
<div id=[0-9]>
<p>
<a href=#>
<span name="hw">hello world</span>
<a>
</p>
</div>
<div id="logoutbutton">
<p>
<a href=#>
<span name="lo">Logout</span>
<a>
</p>
</div>
Test example : Assert element containing string "hello world", not the one which is near the logout button but the one which is near the home button.
Expanding on my previous answer, you have two options, if the Hello World you want is *always the 2nd to last, appearing just before the Logout button then you want the 2nd to last of a type, you could use an xPath selector like this:
"//*[.='hello world'][last()-1]"
That's right in the Rosetta doc I shared with you, so you should know that by now
Another option is to get a collection of all matches. For that, I'd write a helper function like so:
module.exports = {
getCountOfElementsUseXpath : function (client, selector, value) {
// set an empty variable to store the count of elements
var elementCount;
// get a collection of all elements that match the passed selector
client.getEls(selector, function(collection) {
// set the variable to be that collection's length
elementCount = collection.length;
// log the count of elements to the terminal
console.log("There were " + elementCount + " question types")
return elementCount;
});
},
};
Then you can use that with some formula for how far your selector is from the last element.
The xpath selector "//div[contains(text(), 'hello world')]"
would match on both of the elements you've shown. If the element itself can change, you would use a wildcard: "//*[contains(text(), 'hello world')]"
For a match, on any element with that exact text:
"//*[.='hello world']"
A great source, a "Rosetta stone", for selector construction
To use an xpath selector with nightwatch:
"some test": function(client){
client
.useXpath().waitForElementPresent("//div[contains(text(), 'hello world')]", this.timeout)
}
The Xpath solution is okay but here is the solution I needed, more generic and giving many more options :
Using elements and manage to return an array of childrend elements
I choosed to return an array of objects with data matching my needs :
[{ id: webElementId, size: {width: 18, height: 35}, ...}, {id: webElementId, ...}, etc.]
With those informations, I can do many things:
Find an element with a specific text, attribute or cssproperty and
perform any action on it, like assertions or click on the right of it through a calculation of his size.
Mouse hover each elements matched (if you want to browse tabs with
submenus ul li / ol li)
More data is filled, more you can perform assertions.

Xpath get text of nested item not working but css does

I'm making a crawler with Scrapy and wondering why my xpath doesn't work when my CSS selector does? I want to get the number of commits from this html:
<li class="commits">
<a data-pjax="" href="/samthomson/flot/commits/master">
<span class="octicon octicon-history"></span>
<span class="num text-emphasized">
521
</span>
commits
</a>
</li
Xpath:
response.xpath('//li[#class="commits"]//a//span[#class="text-emphasized"]//text()').extract()
CSS:
response.css('li.commits a span.text-emphasized').css('::text').extract()
CSS returns the number (unescaped), but XPath returns nothing. Am I using the // for nested elements correctly?
You're not matching all values in the class attribute of the span tag, so use the contains function to check if only text-emphasized is present:
response.xpath('//li[#class="commits"]//a//span[contains(#class, "text-emphasized")]//text()')[0].strip()
Otherwise also include num:
response.xpath('//li[#class="commits"]//a//span[#class="num text-emphasized"]//text()')[0].strip()
Also, I use [0] to retrieve the first element returned by XPath and strip() to remove all whitespace, resulting in just the number.

Resources