8

Is it possible to return a WebElement's xpath?

Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
KJW
  • 15,035
  • 47
  • 137
  • 243

8 Answers8

9

Not directly from WebDriver, but you can fake it if you really need to:

public String getElementXPath(WebDriver driver, WebElement element) {
    return (String)((JavascriptExecutor)driver).executeScript("gPt=function(c){if(c.id!==''){return'id(\"'+c.id+'\")'}if(c===document.body){return c.tagName}var a=0;var e=c.parentNode.childNodes;for(var b=0;b<e.length;b++){var d=e[b];if(d===c){return gPt(c.parentNode)+'/'+c.tagName+'['+(a+1)+']'}if(d.nodeType===1&&d.tagName===c.tagName){a++}}};return gPt(arguments[0]).toLowerCase();", element);
}

The Javascript is from this post, minified to fit on one line. It may not be perfect, but could give you an idea of where to go. Most drivers implement the JavascriptExecutor interface and have the capability of executing Javascript in the browser. executeScript can return any primitive JavaScript type, an HTML element, or non-nested list of any of the preceding.

Not all browsers support xpath the same way, so be careful if using these xpaths to select elements. Also, not all browsers have native xpath support (cough IE cough), so it was faked in that case.

Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
dflems
  • 214
  • 1
  • 5
5

If WebElement was found by By.xpath: on Java:

public static String GetWebElementXpath(WebElement El) throws AssertionError{
        if ((El instanceof WebElement)){
            Object o = El;
            String text = o.toString();
        /* text is smth like this
        [[FirefoxDriver: firefox on WINDOWS (9170d4a5-1554-4018-adac-f3f6385370c0)] -> xpath: //div[contains(@class,'forum-topic-preview')]//div[contains(@class,'small-human')]]
        */
            text = text.substring( text.indexOf("xpath: ")+7,text.length()-1);
            return text;
        }else   {   Assert.fail("Argument is not an WebElement, his actual class is:"+El.getClass());       }
        return "";
    }
Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
Ysee.mma
  • 61
  • 1
  • 2
4

Both of the above answers suffer from the same problem. By returning the completed XPath with the .toLowerCase() function called, any XPath containing an id with a capital letter will not work.

Example: //div[@id="deviceblock-1111"] will not work on tag <div id="deviceBlock-1111">

You could however just remove the .toLowerCase() call off the return but you'll end up with XPath's looking like this: //DIV[@id="deviceBlock-1111"]/DIV[2]/SELECT[1]/OPTION[5]

To solve this use the function below.

public String GetElementXPath(WebElement element, WebDriver driver)
{
    return (String) ((JavascriptExecutor) driver).executeScript(
    "getXPath=function(node)" +
    "{" +
        "if (node.id !== '')" +
        "{" +
            "return '//' + node.tagName.toLowerCase() + '[@id=\"' + node.id + '\"]'" +
        "}" +

        "if (node === document.body)" +
        "{" +
            "return node.tagName.toLowerCase()" +
        "}" +

        "var nodeCount = 0;" +
        "var childNodes = node.parentNode.childNodes;" +

        "for (var i=0; i<childNodes.length; i++)" +
        "{" +
            "var currentNode = childNodes[i];" +

            "if (currentNode === node)" +
            "{" +
                "return getXPath(node.parentNode) + 
                    '/' + node.tagName.toLowerCase() + 
                    '[' + (nodeCount+1) + ']'" +
            "}" +

            "if (currentNode.nodeType === 1 && " +
                "currentNode.tagName.toLowerCase() === node.tagName.toLowerCase())" +
            "{" +
                "nodeCount++" +
            "}" +
        "}" +
    "};" +

    "return getXPath(arguments[0]);", element);
}

This will return a correctly formatted, unique XPath from your WebElement.

//div[@id="deviceBlock-1111"]/div[2]/select[1]/option[5]

Falkenfighter
  • 568
  • 6
  • 16
2

I would comment directly on dflems' answer, but I do not have the reputation to do so.

Converting the entire xpath to lower case is fine unless the xpath contains an id value that is not all lower-case. Below is a modified version of dflems' Javascript, but in Python instead of Java:

def get_xpath_from_element(driver, element):
    return driver.execute_script("gPt=function(c){if(c.id!==''){return'id(\"'+c.id+'\")'}if(c===document.body){return c.tagName}var a=0;var e=c.parentNode.childNodes;for(var b=0;b<e.length;b++){var d=e[b];if(d===c){return gPt(c.parentNode)+'/'+c.tagName.toLowerCase()+'['+(a+1)+']'}if(d.nodeType===1&&d.tagName===c.tagName){a++}}};return gPt(arguments[0]);", element)

Community
  • 1
  • 1
  • Works flawlessly also on tbselenium and Tor Browser (Firefox Quantum 8.0.2 (based on Mozilla Firefox 60.2.1esr) (64-bit)) – Kernel Oct 21 '18 at 12:15
1
public String getElementXPath(WebDriver driver, WebElement element) {

    String javaScript = "function getElementXPath(elt){" +
                            "var path = \"\";" +
                            "for (; elt && elt.nodeType == 1; elt = elt.parentNode){" +
                                "idx = getElementIdx(elt);" +
                                "xname = elt.tagName;" +
                                "if (idx > 1){" +
                                    "xname += \"[\" + idx + \"]\";" +
                                "}" +
                                "path = \"/\" + xname + path;" +
                            "}" + 
                            "return path;" +
                        "}" +
                        "function getElementIdx(elt){" +
                            "var count = 1;" +
                            "for (var sib = elt.previousSibling; sib ; sib = sib.previousSibling){" +
                                "if(sib.nodeType == 1 && sib.tagName == elt.tagName){" +
                                    "count++;" +
                                "}" +
                            "}" +
                            "return count;" + 
                        "}" +
                        "return getElementXPath(arguments[0]).toLowerCase();";      

    return (String)((JavascriptExecutor)driver).executeScript(javaScript, element);     

}
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
Michael
  • 19
  • 1
  • 1
    the method signature above did not fit in the grayed area so make sure to watch out for that... – Michael Mar 26 '12 at 17:16
0

There is a way to get the elements XPath without the use of JavaScript.

  1. Define starting point of outer XPath, for example body tag.
  2. Check all possible inward tags with selenium for NoSuchElementException.
  3. Check getText for the lists of XPaths generated.
  4. win
Sufian
  • 6,405
  • 16
  • 66
  • 120
0
public static String getXPathFromElement(WebElement element) {
        String elementDescription = element.toString();
        return elementDescription.substring(elementDescription.lastIndexOf("-> ") + 3, elementDescription.lastIndexOf("]"));
}

Web element toString() looks like this:

'[[FirefoxDriver: firefox on WINDOWS (ceb69f9f-bef4-455d-b626-ab439f195be6)] -> id: pageBeanfundDescription]'

I just extract the id/xpath.

Ilana
  • 3
  • 4
0
/**
 * This method return By reference for the WebElement passed to it as a parameter.
 * @param element
 * @return
 */
public static By convertWebElementToByReference(WebElement element) 
{
    By byLocator = null;
    String elementDescription = element.toString();
    String elementTypeAndValue[] = (elementDescription.substring(elementDescription.lastIndexOf("-> ") + 3, elementDescription.lastIndexOf("]"))).split(":");        

    switch (elementTypeAndValue[0].trim()) 
    {
        case "id": byLocator = By.id(elementTypeAndValue[1].trim());
           break;

        case "xpath": byLocator = By.xpath(elementTypeAndValue[1].trim());
           break;

        case "link text": byLocator = By.linkText(elementTypeAndValue[1].trim());
           break;

        case "tag name": byLocator = By.tagName(elementTypeAndValue[1].trim());
           break;

        case "class name": byLocator = By.className(elementTypeAndValue[1].trim());
           break;

        case "partial link text": byLocator = By.partialLinkText(elementTypeAndValue[1].trim());
           break;

        case "name": byLocator = By.name(elementTypeAndValue[1].trim());
           break;

        case "css selector": byLocator = By.cssSelector(elementTypeAndValue[1].trim());
           break;

        default:
            throw new RuntimeException("Invalid locator type: " + elementTypeAndValue[0].trim());
    }

    return byLocator;
}