16

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);
 }
}
zmag
  • 7,825
  • 12
  • 32
  • 42
Nandini Joshi
  • 219
  • 2
  • 4
  • 8

13 Answers13

39

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;
}
Scott Izu
  • 2,229
  • 25
  • 12
  • 6
    Wow! You just made my day! Thanks! – Payam Jun 15 '15 at 22:51
  • You are awesome! Thanks! – Simon Baars Mar 07 '17 at 13:36
  • 2
    This is very good solution. Thank you very much !! I am so happy that I want to kiss you in the mouth !! – Om Sao Jul 19 '17 at 13:24
  • you are passing child webelement and a blank string here in this function, right? generateXPATH(element, ""); It does not generate correct xpath for the child webelment for me. Any idea where I might be missing? – Shoaib Akhtar Jul 30 '18 at 10:51
  • 1
    I am passing webelement of those element whose absolute XPath I want to find, it does return absolute XPath, but I noticed for some it returns correct XPath value but for few it does not, for example try on google home page(https://google.co.in) for element whose relative xpath is google image i.e //input[@value='Google Search'], it does not return correct absolute xpath – Shoaib Akhtar Jul 30 '18 at 13:29
  • This still works fine. – deokyong song Mar 08 '22 at 06:33
7

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.

Robbie Wareham
  • 3,380
  • 1
  • 20
  • 38
  • OK. 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? – Nandini Joshi Aug 29 '13 at 12:27
  • You can do that in one XPath statement using sibling selectors http://stackoverflow.com/questions/3139402/how-to-select-following-sibling-xml-tag-using-xpath – Robbie Wareham Aug 29 '13 at 12:31
  • what would you use instead of xpaths then? – KJW Oct 18 '13 at 22:43
  • Sorry, I don't follow your question. – Robbie Wareham Nov 23 '13 at 09:22
6

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 !

Abhishek Singh
  • 10,243
  • 22
  • 74
  • 108
3

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;
}
Adam Silenko
  • 3,025
  • 1
  • 14
  • 30
1

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.

Dingredient
  • 2,191
  • 22
  • 47
1

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!

Alex
  • 8,093
  • 6
  • 49
  • 79
coding_idiot
  • 13,526
  • 10
  • 65
  • 116
1

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");
Yash
  • 9,250
  • 2
  • 69
  • 74
0

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.

joostschouten
  • 3,863
  • 1
  • 18
  • 31
0

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) {}
                } 
          }
dred17
  • 139
  • 2
  • 17
0

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/

Scott Izu
  • 2,229
  • 25
  • 12
0

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.

Atif Hussain
  • 880
  • 12
  • 19
0

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 "";
}
0

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;

}
Gerrit-Jan
  • 59
  • 1
  • 9