Nandini Joshi
Nandini Joshi

Reputation: 219

Find an element by text and get xpath - selenium webdriver junit

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

Answers (14)

dlopezgonzalez
dlopezgonzalez

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

Gerrit-Jan
Gerrit-Jan

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

Yehuda Broderick
Yehuda Broderick

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

Adam Silenko
Adam Silenko

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

Atif Hussain
Atif Hussain

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

Yash
Yash

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

Scott Izu
Scott Izu

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

coding_idiot
coding_idiot

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

Scott Izu
Scott Izu

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

dred17
dred17

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

Dingredient
Dingredient

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

Abhishek Singh
Abhishek Singh

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

joostschouten
joostschouten

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

Robbie Wareham
Robbie Wareham

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

Related Questions