Reputation: 219
I have a table with 9 rows and 6 columns in my webpage. I want to search for a text "MakeGoodDisabled-Programwise_09_44_38_461(n)" and get the xpath of the cell. I have used the following but it fails because it is not able to find the text on the page. Can you please help? I am using Selenium Webdriver Junit to code this.
List < WebElement > links = driver.findElements(By.tagName("td"));
Iterator < WebElement > itr = links.iterator();
while (itr.hasNext()) {
String test = itr.next().getText();
if (test.equals("MakeGoodDisabled-Programwise_09_44_38_461(n)")) {
String xpath = driver.findElement(By.name(test)).getAttribute("xpath");
System.out.println(xpath);
}
}
Upvotes: 16
Views: 122208
Reputation: 4297
Using node:
async get_xpath(element) {
try {
const script = `
function getXPath(el) {
if (el.tagName.toLowerCase() == 'html') return '/html';
if (el === document.body) return '/html/body';
var ix = 0;
var siblings = el.parentNode.childNodes;
for (var i = 0; i < siblings.length; i++) {
var sibling = siblings[i];
if (sibling === el) {
return getXPath(el.parentNode) + '/' + el.tagName.toLowerCase() + '[' + (ix + 1) + ']';
}
if (sibling.nodeType === 1 && sibling.tagName === el.tagName) {
ix++;
}
}
}
return getXPath(arguments[0]);
`;
const xpath = await this.driver.executeScript(script, element);
return xpath;
} catch (error) {
console.error(`error: ${error}`);
}
}
Upvotes: 0
Reputation: 69
Try this method to generate a full xpath (the required parameters are the full html and the unique matching text/id/class, full html can be extracted from Jsoup:
public static String GenerateXpath(String html, String searchString) {
String xpath = "";
// creating subhtml for the Xpath
int match_position = html.indexOf(searchString) + searchString.length();
String subhtml = html.substring(0, match_position);
//reverse loop, reading from behind the html... and generating the xpath..
String s1 = ">";
String s2 = "<";
int count = 0;
int max = subhtml.length() - subhtml.replace(s2, "").length();
int Xcount = 1;
int root_length = 0;
String prev_root = "";
HashMap<Integer, String> map = new HashMap<Integer, String>();
for (int i = subhtml.lastIndexOf(s2); i >= 0; i = subhtml.lastIndexOf(s2, i - 1)) {
count++;
if (count == max) {
break;
}
// printing between s2 and s1 '< >'
String characters = subhtml.substring(subhtml.lastIndexOf(s2, i - 1), i);
// Counting spaces between s1 and s2 '< >'
String spaces = subhtml.substring(subhtml.lastIndexOf(s1, i - 1), i + 1);
int space = spaces.length() - spaces.replace(" ", "").length();
//making a single xpath root
String dirty_root = (characters+" ").split(" ")[0];
String root = dirty_root.replaceAll("\\W+","");
//Counting the 'root'
if ((root_length == space) && !dirty_root.contains("/") && root.equals(prev_root)) {
Xcount++;
System.out.println(
"Xcount = " + Xcount + " (space length = " + space + " & root length = " + root_length + ")");
System.out.println(characters);
}
// filling the hashmap to later generate the xpath...
if (!dirty_root.contains("/")) {
if (root_length == space + 1) {
map.put(space,root);
if (Xcount > 1) {
map.replace((space + 1), map.get(space + 1) + "[" + Xcount + "]");
}
root_length = space;
prev_root = root;
Xcount = 1;
} else if (count == 1) {
root_length = space;
map.put(space,root);
prev_root = root;
Xcount = 1;
}
}
}
//putting all integer keys to list
List<Integer> iList = new ArrayList<Integer>();
//'for looping' all keys to the list
for (Map.Entry m : map.entrySet()) {
iList.add((Integer) m.getKey());
}
//sorting all numbers from low to high in list
Collections.sort(iList);
Collections.reverse(iList);
for (int temp : iList) {
System.out.println(temp + " " + map.get(temp));
xpath = map.get(temp) + "/" + xpath;
}
//adding the last 'tale'
xpath = "/" + xpath + (subhtml.substring(subhtml.lastIndexOf("<"), subhtml.length()) + " ").split(" ")[0].replaceAll("\\W+", "");
System.out.println(xpath);
return xpath;
}
Upvotes: 0
Reputation: 25
Needed this recently, took a crack at it myself. Here is my method:
public static String findAbsoluteXpathOfElement(WebElement element, String collectedXpath) {
String tagName = element.getTagName(); // tag of element at current depth
// break condition
if (tagName.equals("html"))
return "/html" + collectedXpath;
// meat of method
WebElement parent = element.findElement(By.xpath("./.."));
List<WebElement> children = parent.findElements(By.xpath("./" + tagName));
for (int i = 0; i < children.size(); i++) {
WebElement child = children.get(i);
if (child.equals(element)) {
String xpath = "/" + tagName + "[" + (i+1) + "]";
return findAbsoluteXpathOfElement(parent, xpath + collectedXpath); // move up one level
}
}
return "";
}
Upvotes: 0
Reputation: 3108
My java method to get absolute xpath:
public static String getXpath(WebElement element){
int n = element.findElements(By.xpath("./ancestor::*")).size();
String path = "";
WebElement current = element;
for(int i = n; i > 0; i--){
String tag = current.getTagName();
int lvl = current.findElements(By.xpath("./preceding-sibling::" + tag)).size() + 1;
path = String.format("/%s[%d]%s", tag, lvl, path);
current = current.findElement(By.xpath("./parent::*"));
}
return "/" + current.getTagName() + path;
}
and for relative xpath (this was first :) ):
public static String getXpath(WebElement self, WebElement ancestor){
int a = ancestor.findElements(By.xpath("./ancestor::*")).size();
int s = self.findElements(By.xpath("./ancestor::*")).size();
String path = "";
WebElement current = self;
for(int i = s - a; i > 0; i--){
String tag = current.getTagName();
int lvl = current.findElements(By.xpath("./preceding-sibling::" + tag)).size() + 1;
path = String.format("/%s[%d]%s", tag, lvl, path);
current = current.findElement(By.xpath("./parent::*"));
}
return path;
}
Upvotes: 2
Reputation: 898
I think their is not any special function available for getting the xpath of searched element. For this, First you need to find the element manually then get the xpath through getXpath functions.
Upvotes: 0
Reputation: 9578
JavaScript fnction's to generate XPath
XPath/locator is a way of addressing an element by navigating through the Tree structure.
Absolute XPath (/): /html/body/div[5]/div[4]
WebElement XPath:
function WebElement_XPath(element) {
if (element.tagName == 'HTML') return '/html';
if (element===document.body) return '/html/body';
// calculate position among siblings
var position = 0;
// Gets all siblings of that element.
var siblings = element.parentNode.childNodes;
for (var i = 0; i < siblings.length; i++) {
var sibling = siblings[i];
// Check Siblink with our element if match then recursively call for its parent element.
if (sibling === element) return WebElement_XPath(element.parentNode)+'/'+element.tagName+'['+(position+1)+']';
// if it is a siblink & element-node then only increments position.
var type = sibling.nodeType;
if (type === 1 && sibling.tagName === element.tagName) position++;
}
}
MouseEvent XPath:
var eventXPath = '';
function MouseEvent_XPath(e) {
event = e.target || e.srcElement ||e.originalTarget;
for ( var xpathEle = ''; event && event.nodeType == 1; event = event.parentNode ) {
if ( event.tagName == 'HTML' || event.tagName == 'html' ) {
eventXPath = '//html'+xpathEle; break;
}
if ( event.tagName == 'BODY' || event.tagName == 'body' ) {
eventXPath = '//html/body'+xpathEle; break;
}
// calculate position among siblings
var position = 0;
// Gets all siblings of that event.
var siblings = event.parentNode.children;
for (var i = 0; i < siblings.length; i++) {
var sibling = siblings[i];
// Check Sibling with our event if match then recursively call for its parent event.
if (sibling === event) xpathEle = "/"+event.tagName+'['+(position+1)+']'+xpathEle;
// if it is a sibling with same tagName then only increments position.
if (sibling.tagName === event.tagName) position++;
}
}
}
Relative xpath (//): //div[@id=’social-media’]/ul/li[3]/a
//Element.TagName[@id="idvalue" and @class="classValue" and text()="innerHTML data"] OR you can use .(dot), . is an alias for text(), [.="innerHTML data"] if you know the content you are looking for is somewhere inside the specified tag, you can use this
Example run in console:
var xpath = "//div[@id='answer-32201731' and @data-answerid='32201731']";
var element = document.evaluate(xpath, window.document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue;
element.style.setProperty( 'outline', "3px solid blue","important");
Upvotes: 1
Reputation: 2309
You can also use this to crawl up and generate the xpath:
Call the method below with
generateXPATH(element, "");
The output will be something like:
/html[1]/body[1]/div[5]/div[1]/div[2]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/form[1]/div[2]/div[1]/input[2]
METHOD
private String generateXPATH(WebElement childElement, String current) {
String childTag = childElement.getTagName();
if(childTag.equals("html")) {
return "/html[1]"+current;
}
WebElement parentElement = childElement.findElement(By.xpath(".."));
List<WebElement> childrenElements = parentElement.findElements(By.xpath("*"));
int count = 0;
for(int i=0;i<childrenElements.size(); i++) {
WebElement childrenElement = childrenElements.get(i);
String childrenElementTag = childrenElement.getTagName();
if(childTag.equals(childrenElementTag)) {
count++;
}
if(childElement.equals(childrenElement)) {
return generateXPATH(parentElement, "/" + childTag + "[" + count + "]"+current);
}
}
return null;
}
Upvotes: 40
Reputation: 13734
My intention is to find a text in a table and get the corresponding next column value in the same row. I thought that I will replace the column number found by fetching the xpath with the column number I want. is there a better way to do it
Of course there's a way. Here's one possible solution.
Get all the rows:
While (iterate over row)
While(Iterate over column)
if(column.Text=='YOUR_MATCH'){
int voila=column.Index
}
}
}
Now you can simply move to that particular index for the other rows; or you could use xpath like .../tr/td[voila]
to retrieve all cells for that particular column.
I've written an approach, please don't take to be real-working-code!
Upvotes: 1
Reputation: 2309
Before iterating, use this class. Then, when you findXPATHIDFromWebElement
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class XPATHDriverWrapper {
Map xpathIDToWebElementMap = new LinkedHashMap();
Map webElementToXPATHIDMap = new LinkedHashMap();
public XPATHDriverWrapper(WebDriver driver){
WebElement htmlElement = driver.findElement(By.xpath("/html"));
iterateThroughChildren(htmlElement, "/html");
}
private void iterateThroughChildren(WebElement parentElement, String parentXPATH) {
Map siblingCountMap = new LinkedHashMap();
List childrenElements = parentElement.findElements(By.xpath(parentXPATH+"/*"));
for(int i=0;i<childrenElements.size(); i++) {
WebElement childElement = childrenElements.get(i);
String childTag = childElement.getTagName();
String childXPATH = constructXPATH(parentXPATH, siblingCountMap, childTag);
xpathIDToWebElementMap.put(childXPATH, childElement);
webElementToXPATHIDMap.put(childElement, childXPATH);
iterateThroughChildren(childElement, childXPATH);
// System.out.println("childXPATH:"+childXPATH);
}
}
public WebElement findWebElementFromXPATHID(String xpathID) {
return xpathIDToWebElementMap.get(xpathID);
}
public String findXPATHIDFromWebElement(WebElement webElement) {
return webElementToXPATHIDMap.get(webElement);
}
private String constructXPATH(String parentXPATH,
Map siblingCountMap, String childTag) {
Integer count = siblingCountMap.get(childTag);
if(count == null) {
count = 1;
} else {
count = count + 1;
}
siblingCountMap.put(childTag, count);
String childXPATH = parentXPATH + "/" + childTag + "[" + count + "]";
return childXPATH;
}
}
Another wrapper class to generate ids from Document is posted at: http://scottizu.wordpress.com/2014/05/12/generating-unique-ids-for-webelements-via-xpath/
Upvotes: 0
Reputation: 159
Almost the same situation to me:
I have an element in the table, but I don't know in which column it's located (like some username across all the usernames). I need to find particular string with username and click "activate" button just for this user. (the username text locates in the 2nd column which is td[2] and the button locates in the 5th column which is td[5]). So I need to run all columns one by one to find the user and then click it's button:
for (int iter = 1;; iter++) {
try {
if (iter == 1) {
if ((username).equals(driver.findElement(By.xpath("/html/body/div[3]/div[4]/div/form/table/tbody/tr/td[2]/a")).getText())) {
driver.findElement(By.xpath("/html/body/div[3]/div[4]/div/form/table/tbody/tr/td[5]/a")).click();
break;
}
}
} catch (Error e) {}
try {
if (iter > 1) {
if ((username).equals(driver.findElement(By.xpath("/html/body/div[3]/div[4]/div/form/table/tbody/tr[" + iter + "]/td[2]/a")).getText())) {
driver.findElement(By.xpath("/html/body/div[3]/div[4]/div/form/table/tbody/tr[" + iter + "]/td[5]/a")).click();
break;
}
}
} catch (Error e) {}
}
}
Upvotes: 0
Reputation: 2199
You can generate xpaths with JavaScript:
function getPathTo(element) {
// only generate xpaths for elements which contain a particular text:
if (element.innerText == "MakeGoodDisabled-Programwise_09_44_38_461(n)") {
// use id to produce relative paths rather than absolute, whenever possible
if ((element.id !== '') && (element.id != 'undefined')) {
return 'id(\"' + element.id + '\")';
}
// stop looping when you get to the body tag
if (element === document.body) {
return element.tagName;
}
// calculate position among siblings
var ix = 0;
var siblings = element.parentNode.childNodes;
for (var i = 0; i < siblings.length; i++) {
var sibling = siblings[i];
if (sibling === element) {
return getPathTo(element.parentNode) + '/' + element.tagName + '[' + (ix + 1) + ']';
}
if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
ix++;
}
}
}
}
// put all matching xpaths in an array
var allXPaths = [];
// if you know the particular tag you are looking for, replace * below to optimize
var allTags = document.getElementsByTagName('*');
for (i = 0; i < allTags.length; i++) {
if ((getPathTo(allTags[i]).indexOf('/HEAD') == -1) && (getPathTo(allTags[i]).indexOf('undefined') == -1)) {
allXPaths.push(getPathTo(allTags[i]));
console.log(getPathTo(allTags[i]));
}
}
return allXPaths;
If you put that JavaScript in a String called getXPaths
then in Java, you can execute it like this:
ArrayList<String> xpaths = (ArrayList<String>) js.executeScript(getXPaths);
It returns an array rather than a String, because if your page happens to have fewer or more elements with matching tagname/innerText, You'll want to know. You can tell by the size of the array.
Upvotes: 1
Reputation: 9765
The question which you asked does not make any sense to me. I guess there might be a strong reason for you to 'want to do it' !
Your line of code
String xpath = driver.findElement(By.name(test)).getAttribute("xpath");
will not return anything because there is no attribute 'xpath' in html elements. Please get your basics clear on to what xpath means??
if i have an html element as shown below
<input name = "username" value = "Name" readonly ="readonly">
i can get the values of attribute by using
driver.findElement(By.name("username").getAttribute("value"); // returns 'Name'
This will give me value of 'value' attribute
or
driver.findElement(By.name("username").getAttribute("readonly"); // returns 'readonly'
same as above !
Upvotes: 6
Reputation: 3893
Assuming you have an attribute on your element with the name "xpath" you could do the following:
WebElement yourElement = driver.findElement(By.xpath("//td[contains(text(),'MakeGoodDisabled-Programwise_09_44_38_461(n)')]");
String xpathAttributeValue = targetButton.getAttribute("xpath");
Though by the sounds of it you are trying to achieve something else here you have not shared with us yet. Do you want selenium to create an xpath for you? It can't.
Upvotes: 0
Reputation: 3438
The XPath of an element is not a definitive value. An element can be found by many XPaths.
You cannot use Webdriver to extract an XPath and even if you could, it is unlikely to be the most efficient or sensible one, that can only be defined by the automator.
Upvotes: 8