Is there a DRYer XPath expression for union? - xpath

This works nicely for finding button-like HTML elements, (purposely simplified):
//button[text()='Buy']
| //input[#type='submit' and #value='Buy']
| //a/img[#title='Buy']
Now I need to constrain this to a context. For example, the Buy button that appears inside a labeled box:
//legend[text()='Flubber']
And this works, (.. gets us to the containing fieldset):
//legend[text()='Flubber']/..//button[text()='Buy']
| //legend[text()='Flubber']/..//input[#type='submit' and #value='Buy']
| //legend[text()='Flubber']/..//a/img[#title='Buy']
But is there any way to simplify this? Sadly, this sort of thing doesn't work:
//legend[text()='Flubber']/..//(
button[text()='Buy']
| input[#type='submit' and #value='Buy']
| a/img[#title='Buy'])
(Note that this is for XPath within the browser, so XSLT solutions will not help.)

Combine multiple conditions in a single predicate:
//legend[text()='Flubber']/..//*[self::button[text()='Buy'] or
self::input[#type='submit' and #value='Buy'] or
self::img[#title='Buy'][parent::a]]
In English:
Select all descendants of the parent (or the parent itself)
for any legend element having the
text "Flubber" that are any of 1) a button
element having the text "Buy" or 2) an
input element having an attribute
type whose value is "submit" and an
attribute named value whose value is
"Buy" or 3) an img having an
attribute named title with a value
of "Buy" and whose parent is an a
element.

From comments:
Adjusting slightly to obtain the A
rather than the IMG:
self::a[img[#title='Buy']]. (Now if
only 'Buy' could be reduced
Use this XPath 1.0 expression:
//legend[text() = 'Flubber']/..
//*[
self::button/text()
| self::input[#type = 'submit']/#value
| self::a/img/#title
= 'Buy'
]
EDIT: I didn't see the parent accessor. Other way in one direction only:
//*[legend[text() = 'Flubber']]
//*[
self::button/text()
| self::input[#type = 'submit']/#value
| self::a/img/#title
= 'Buy'
]

Related

Xpath uninion parameter from parent

here xpath one
/Document/Attributes/BlobContent/Property[#Name="FileName"]/parent::*/Reference/#Link
and xpath two
Document/Attributes/BlobContent/Property[#Name="FileName"]/parent::*/Property[#Name="FileName"]/#Value
both bring back the right result !
I would like to avoid the complete chaining [one | two] as that brought back only a list of alternating results.
tried with
/Document/Attributes/BlobContent/Property[#Name="FileName"]/parent::*/Reference/#Link | */Property[#Name="FileName"]/#Value
but that brings back only the later one.
So how would I correctly bring back two child node attributes from a found parent ?
For anyone interested I didn't find any XPATH solution. However that python code did work for me
import xml.etree.ElementTree as ET
tree = ET.parse(file_xml)
root = tree.getroot()
blobs = root.findall("*/Attributes[1]/BlobContent")
for blob in blobs:
try:
filename = blob.find('Property[#Name="FileName"]').attrib["Value"]
exportname = blob.find('Reference[#Type="RELATIVEFILE"]').attrib["Link"]
print(filename + "," + exportname)
except:
#no filename Property
pass

Make step definition dynamic to handle any incoming text

I have to run test1.feature file against two urls. In one of the url I have got a field named as EIN, but in second url the same field named as ABN
How can we make the step definition dynamic to handle any text coming in second string.
Url 1 : https://testsite.us.com/student
The Field is named as "EIN"
Url 2: https://testsite.au.com/student
The Field is named as "ABN"
test1.feature
And I type "11 234 234 444" in "ABN" field
step_definition
//Type text value in textbox input field
Then('I type {string} in {string} field', (textval, textboxname) => {
switch (textboxname) {
case "Email":
case "Work Phone":
case "ABN":
case "EIN":
case "ACN":
cy.get('#profile_container').parent().find('.fieldHeaderClass').contains(textboxname)
.next().find('input')
.clear({force:true})
.type(textval, { force: true });
break;
//final else condition
default:
cy.get('#profile_container').parent().find('.fieldHeaderClass').contains(textboxname)
.next()
.type(textval, { force: true });
}
});
First of all a great tip: Make use of page objects (POM design pattern). A page objects is an object (inside a third .js file besides your features and stepdifinitions) that you import in the stepdefinitions file that contains all of your selector code (cy.get(......)). You don't want selector code in your stepdefinitions. Makes it messy and more difficult to read.
Concerning your problem: If you want to repeat the same logic for (all kinds of) input values. Why not write your logic just ones (so without the long case statement) and use Scenario Outline to repeat your step for the different inputs? If your logic must be different every time, than don't even bother fixing this problem. Then you should simply write different steps for different logic..
Here is an example of scenario outline (inside a .feature):
Scenario Outline: Scenario Outline name
Given I type "<textval>" in "<textboxname>" field
Examples:
| textval | textboxname |
| some_value | ABN |
| some_other_value | EIN |
| some_other_value_2 | email |
| some_other_value_3 | ACN |
| some_other_value_4 | Work Phone |
Conclusion: Use Scenario Outline to repeat logic. If logic needs to be different for different inputs, then write another stepdefinition and don't try to combine and clip different logics into 1 step.

Is it possible in XPATH to find an element by attribute value, not by name?

For example I have an XML element:
<input id="optSmsCode" type="tel" name="otp" placeholder="SMS-code">
Suppose I know that somewhere there must be an attribute with otp value, but I don’t know in what attribute it can be, respectively, is it possible to have an XPath expression of type like this:
.//input[(contains(*, "otp")) or (contains(*, "ode"))]
Try it like this and see if it works:
one = '//input/#*[(contains(.,"otp") or contains(.,"ode"))]/..'
print(driver.find_elements_by_xpath(one))
Edit:
The contains() function has a required cardinality of first argument of either one or zero. In plain(ish) English, it means you can check only one element at a time to see if it contains the target string.
So, the expression above goes through each attribute of input separately (/#*), checks if the attribute value of that specific attribute contains within it the target string and - if target is found - goes up to the parent of that attribute (/..) which, in the case of an attribute, is the node itself (input).
This XPath expression selects all <input> elements that have some attribute, whose string value contains "otp" or "ode". Notice that there is no need to "go up to the parent ..."
//input[#*[contains(., 'otp') or contains(., 'ode')]]
If we know that "otp" or "ode" must be the whole value of the attribute (not just a substring of the value), then this expression is stricter and more efficient to evaluate:
//input[#*[. ='otp' or . = 'ode']]
In this latter case ("otp" or "ode" are the whole value of the attribute), if we have to compare against many values then an XPath expression of the above form will quickly become too long. There is a way to simplify such long expression and do just a single comparison:
//input[#*[contains('|s1|s2|s3|s4|s5|', concat('|', ., '|'))]]
The above expression selects all input elements in the document, that have at least one attribute whose value is one of the strings "s1", "s2", "s3", "s4" or "s5".

how to get attribute values using nokogiri

I have a webpage whose DOM structure I do not know...but i know the text which i need to find in that particular webpage..so in order to get its xpath what i do is :
doc = Nokogiri::HTML(webpage)
doc.traverse { |node|
if node.text?
if node.content == "my text"
path << node.path
end
end
}
puts path
now suppose i get an output like ::
html/body/div[4]/div[8]/div/div[38]/div/p/text()
so that later on when i access this webpage again i can do this ::
doc.xpath("#{path[0]}")
instead of traversing the whole DOM tree everytime i want the text
I want to do some further processing , for that i need to know which of the element nodes in the above xpath output have attributes associated with them and what are their attribute values. how would i achieve that? the output that i want is
#=> output desired
{ p => p_attr_value , div => div_attr_value , div[38] => div[38]_attr_value.....so on }
I am not facing the problem in searching the nodes where "my text" lies.. I wanted to have the full xpath of "my text" node..thts why i did the whole traversal...now after finding the full xpath i want the attributes associated with the each element node that I came across while getting to the "my text" node
constraints are ::I cant use any of the developer tools available in a web browser
PS :: I am newbie in ruby and nokogiri..
To select all attributes of an element that is selected using the XPath expression someExpr, you need to evaluate a new XPath expression:
someExpr/#*
where someExpr must be substituted with the real XPath expression used to select the particular element.
This selects all attributes of all (we assume that's just one) elements that are selected by the Xpath expression someExpr
For example, if the element we want is selected by:
/a/b/c
then all of its attributes are selected by:
/a/b/c/#*

resilient searching of elements via xpath?

from my previous question,
how does this xpath behave?
I found that
html//p//table//tr//td/a
could deal with any unexpected elements that show up between the above xpath.
For instance the xpath above could handle:
html/p/div/table/tr/td/a
html/p/table/tr/td/b/div/a
However, how can I formulate an xpath which be fully resilient to missing/unexpected elements ?
For instance, the xpath mentioned in the beginning cannot handle the following:
/html/table/tr/td/a (p is missing)
/html/div/span/table/tr/td/a (p is missing and position replaced with `div/span/`)
Does an xpath syntax exist to deal with above case ? If not, what would be an alternate approach ?
My gut tells me, it's not possible with xpath alone so I am utilizing the following algorithm using pseudocode.
Essentially, what it will do is split up the given xpath, and look for immediate child for each ancestor. If the expected child doesn't exist or is some other element, it will dig through all children of current ancestor, and attempt to discover the expected child.
function searchElement(){
elements[] = "/html/p/table/tr/td/a".split("/");
thisElement = "";
for (element in elements) {
if (firstItem){
thisElement = findElementByXpath(element);
}else{
try{
thisElement.findElementByXpath(element); //look for this element inside previous element (from previous iteration);
}catch(NotFoundException e){ //if element is not found, search all elements inside previous element, and look for it.
foundElement = false;
discoveredElement = thisElement.findElementByXpath("*");
while(foundElement != true){
if (discoveredElement.findEleemntByXpath(element) != null){
//successful, element found, overwrite.
thisElement = thisElement.findElementByXpath("*").findEleemntByXpath(element);
foundElement = true;
}else{
//not successful, keep digging.
discoveredElement = discoveredElement.findElementByXpath("*");
}
}
}
}
}
return thisElement;
}
Is this an optimal approach ? I am worried that searching for "*" and digging through each Element is rather inefficient.
I don't know what to tag this question besides "xpath"...feel free to edit.
Thank you.
If I understand you correctly, you want to select a elements with specific ordered optional ancestors.
Then your expression: /html//p//table//tr//td/a
It should be:
//a[(self::*|parent::td)[1]
[(self::*|ancestor::tr)[1]
[(self::*|ancestor::table)[1]
[(self::*|ancestor::p)[1]
[ancestor::html[not(parent::*)]]
]
]
]
]
But this is the same as:
/html//a |
/html//td/a |
/html//tr//a |
/html//tr//td/a |
/html//table//a |
/html//table//td/a |
/html//table//tr//a |
/html//table//tr//td/a |
/html//p//a |
/html//p//td/a |
/html//p//tr//a |
/html//p//tr//td/a |
/html//p//table//a |
/html//p//table//td/a |
/html//p//table//tr//a |
/html//p//table//tr//td/a |
and /html//a is so general that it would select any a
It's possible, but a really bad idea.
The // construct means "skip any number of elements." So you could use a path of //td to find a "td" element anywhere in the DOM.
Which means that you'll pick up the element at /html/body/im/not/what/you/want/td

Resources