How do I select child elements of any depth using XPath? - xpath

Suppose I have this (simplified):
<form id="myform">
<!-- some input fields -->
<input type="submit" value="proceed"/>
</form>
Then I can select the submit button by XPath //form[#id='myform']/input[#type='submit']. Great.
However, my templates might change and I want to be flexible in the depth in which the submit button is located. It might be put in a table, like this:
<form id="myform">
<!-- some input fields -->
<table><tr><td>
<input type="submit" value="proceed"/>
</td></tr></table>
</form>
I know I can select elements which are grandchildren, but I can't select grand-grand-grand-...-childeren of any depth. E.g.:
//form[#id='myform']/*/input[#type='submit'] only selects grand-children, no further depths.
//form[#id='myform']/*/*/input[#type='submit'] only selects grand-grand-children, no further or less depths.
//form[#id='myform']/**/input[#type='submit'] is not valid.
So, how do I select this submit button reliably without using element IDs?

You're almost there. Simply use:
//form[#id='myform']//input[#type='submit']
The // shortcut can also be used inside an expression.

If you are using the XmlDocument and XmlNode.
Say:
XmlNode f = root.SelectSingleNode("//form[#id='myform']");
Use:
XmlNode s = f.SelectSingleNode(".//input[#type='submit']");
It depends on the tool that you use. But .// will select any child, any depth from a reference node.

//form/descendant::input[#type='submit']

Related

xPath: fetch element with an attribute containing the text of another element

Given I have the following HTML structure:
<button aria-labelledby="ref-1" id="foo" onclick="convey(event)">action 2</button>
<div class="anotherElement">foobar</div>
<div id="ref-1" hidden>target 2</div>
I would like to fetch button by its aria-labelledby attribute. I tried the following options:
//*[#aria-labelledby=string(/div[#id="ref-1"]/#id)]
//*[#aria-labelledby = string(.//*[normalize-space() = "target 2"]/#id)]
//*[#aria-labelledby = .//*[normalize-space() = "target 2"]/#id]
But wasn't able to fetch the element. Anyone has an idea what the right xPath could be?
Edit: simply put: how do I fetch the button element if my only information is "target 2", and if both elements can be randomly located?
//button[#aria-labelledby='ref-1']
or
//button[#aria-labelledby=(//*/#id)]
or
//button[#aria-labelledby=(//*[contains(.,'target 2')]/#id)]
or
//button[#aria-labelledby=(//*[contains(text(),'target 2')]/#id)]
?
Since button and div are the same level siblings here you can use preceding-sibling XPath expression like this:
//div[text()='target 2']//preceding-sibling::button
pay attention with with your actual XML this will match 2 button elements.
To make more precise math I think we will need to be based on more details, not only the target 2 text

How to get descendants with a specific tag name and text in protractor?

I have the following structure (it's just for sample). In protractor, I am getting the top element by id. However, the other elements do not have id's. I need to get the "label" element that contains the text '20'. Is there an easy way in protractor to select the element with a specific tag that contains a specific text from all the descendants of a parent element?
<pc-selector _... id="Number1">
<div ...></div>
<div ...>
<div ...>
<check-box _...>
<div _ngcontent-c25="" ...>
<label _ngcontent-c25="">
<input _ngcontent-c25="" type="checkbox">
<span _ngcontent-c25="" class="m-checkbox__marker"></span>
20 More text to follow</label>
</div>
</check-box>
</div>
</div>
</pc-selector>
I could't find anythitng, so I have tried with xpath, but protractor complains that my xpath is invalid:
parentElement = element(by.id('Number1'));
return parentElement.element(by.xpath(".//label[contains(text(),'20'))]"));
Any ideas?
You have an additional bracket in your [contains(text(),'20'))] which is likely causing you issue but there are multiple other ways this can be achieved using a single XPath or chaining other locators.
The process is that you must find the div with the correct id first and then locate the label that is a child of it.
//Xpath
element(by.xpath("//pc-selector[#id='Number1']//label[contains(text(),'20')]"));
//Chained CSS
element(by.id('Number1')).element(by.cssContainingText('label','20'));
You also may be interested to learn about xpath axes which can allow us to do very dynamic selection.
You can use the direct xpath to access the label.
element(by.xpath("//*[#id='Number1']//label"));

How can I wrap a div around existing elements?

I'm trying to parse an existing document and modify it by wrapping a div around some existing form elements.
HTML form looks a bit like this:
<form>
<label for="username">Username:</label>
<input name="username" type="text" />
<label for="password">Password:</label>
<input name="password" type="password" />
</form>
I can parse the document OK with Nokogiri and i'm aware of the wrap method but i'm struggling to grasp how to select both the label and input tags in one go and then wrap a div around these. So the result I am looking for is:
<form>
<div class="form-group">
<label for="username">Username:</label>
<input name="username" type="text" />
</div>
<div class="form-group">
<label for="password">Password:</label>
<input name="password" type="password" />
</div>
</form>
I have tried various XPaths / CSS selectors and can create a nodeset of just labels/inputs or all of the elements of the whole form. Is there any way to achieve this modification?
A single XPath expression can only return a single collection of nodes, so in order to achieve what you want you will need to make several queries, one for each label – input pair.
You can select an individual pair with something like this, assuming the markup is well behaved (i.e each input has a label before it):
//label[1] | //label[1]/following-sibling::input[1]
This will select the first label and the following input. However you want to select all such pairs. One way would be to first select all the label nodes, and then for each label select it and the following input.
labels = doc.xpath("//form/label")
labels.each do |l|
nodes = l.xpath(". | ./following-sibling::input[1]")
# nodes now contains a label-input pair...
end
I don’t think the wrap method will work to add a div element as an ancestor to each pair, as it will add the element to each member of the nodeset. You will probably have to move them manually, something like
labels = doc.xpath("//form/label")
labels.each do |l|
# Select this node and its neighbour.
nodes = l.xpath(". | ./following-sibling::input[1]")
# Create the new element, and add it before the label.
div = Nokogiri::XML::Node.new('div', l.document)
l.before(div)
# Move each of the pair onto this new element.
nodes.each do |n|
div.add_child(n)
end
end
Note that this method doesn’t move any text nodes, so you may find the whitespace of your document changes a bit.

Locate elements in a list tag with WebDriver

I am trying to loop through a list tag and collect all the elements and click on a particular item if it matches the given string.
The HTML code is:
<ul multiple="multiple" name="optionsTab" taborder="1" class="focus">
<li value="2" selected="selected">option1</li>
<li value="5" selected="selected">option2</li>
<input id="form-3-input-5" name="optionsTab" type="hidden" value= "2,5">
</ul>
Java code:
webdriver1.findElement(By.xpath("//ul/li[1]")).click();
to directly click on option1 gives me an elementNotFoundException.
What is the best way to locate the li tag and access the list.
The simplest case would be to find the list first Webelemebt list = driver.findElement(By.name("optionsTab")); and then find the elements within that list List<Webelement> elements = list.findElements(By.xpath("//li"));
Then you can iterate over each element within your collection to find the element that you want.
You can also use the below XPATH
//ul[#name='optionsTab']/li[1]
you could also use for instance the following CssSelector approach:
webdriver1.findElement(By.CssSelector("ul[name='optionsTab']>li[value='2']")).Click();
If you keep getting ElementNotFound exceptions are you sure this element has loaded yet? You could always try a wait for the element?

How to check of a Node is inside a form tag?

Using XPath, how do I determine if a node is within a form tag? I guess I am trying to locate the form tag of its ancestor/preceding (but I couldn't get it to work).
example 1:
<form id="doNotKnowIDofForm">
<div id="level1">
<span id="mySpan">someText</span>
</div>
</form>
example 2:
<form id="doNotKnowIDofForm">
This is a closed form.
</form>
<div id="level1">
<span id="mySpan">someText</span>
</div>
</form>
I can use xpath "//span[id='mySpan']" to locate the span node. But I would like to know if mySpan is inside a form (I do not know the id of the form). I have tried "//span[id='mySpan']/preceding::form/" and "//span[id='mySpan']/ancestor::form/"
Thanks in advance.
EDIT: I would like the XPath to select the myForm form tag in Example1 but NOT in Example2
I'm not 100% sure from your description whether you're looking to select the form element, or the span element. It seems more likely that you're going for the form, so I'll address that first.
Your XPath with the ancestor::form would have been ok if it didn't have the slash at the end, but it's more roundabout than it needs to be. I think this is a better way:
//form[.//span/#id = 'mySpan']
or this:
//form[descendant::span/#id = 'mySpan']
To produce an XPath that locates certain nodes only if they are within a form, you would put the ancestor::form inside the predicate:
//span[#id = 'mySpan' and ancestor::form]
or you can do this, which would again be more straightforward:
//form//span[#id = 'mySpan']
Your own attempt
//span[id='mySpan']/ancestor::form/
looks fine to me.
You can simply use,
"form//span[id='mySpan']"

Resources