Get the By locator of an already found WebElement
Asked Answered
S

8

30

Is there an elegant way to get the By locator of a Selenium WebElement, that I already found/identified?

To be clear about the question: I want the "By locator" as used to find the element. I am in this case not interested in a specific attribute or a specific locator like the css-locator.

I know that I could parse the result of a WebElement's toString() method:

WebElement element = driver.findElement(By.id("myPreciousElement"));
System.out.println(element.toString());

Output would be for example:

[[FirefoxDriver: firefox on WINDOWS (....)] -> id: myPreciousElement]

if you found your element by xpath:

WebElement element = driver.findElement(By.xpath("//div[@someId = 'someValue']"));
System.out.println(element.toString());

Then your output will be:

[[FirefoxDriver: firefox on WINDOWS (....)] -> xpath: //div[@someId = 'someValue']]

So I currently wrote my own method that parses this output and gives me the "recreated" By locator.


BUT is there a more elegant way already implemented in Selenium to get the By locator used to find the element?

I couldn't find one so far.

If you are sure, there is none out of the box, can you think of any reason why the API creators might not provide this functionality?



*Despite the fact that this has nothing to do with the question, if someone wonders why you would ever need this functionality, just 2 examples:

  • if you use PageFactory you most likely will not have the locators as as member variables in your Page class, but you might need them later on when working with the page's elements.
  • you're working with APIs of people who just use the Page Object Pattern without PageFactory and thus expect you to hand over locators instead of the element itself.*
Speechless answered 28/7, 2015 at 12:55 Comment(3)
possible duplicate of How to get a CSS selector using Selenium WebDriver?Vow
don't think this is a duplicate with your proposed question, because I'm specifically interested in the By locator. As you answered in that other question, this would make more sense then retrieving the "css" locator, which might not exist, but there is always a By locator to retrieve an elementSpeechless
Did you ever figure this out? I am using the PageFactory implementation and have basically no way to verify that an element is displayed or not.Mroz
V
5

tldr; Not by default, no. You cannot extract a By from a previously found WebElement. It is possible, however, through a custom solution.

It's possible to implement a custom solution, but Selenium does not offer this out-of-the-box.

Consider the following, on "why"..

By by = By.id("someId");
WebElement e = driver.findElement(by);

you already have the By object, so you wouldn't need to call something like e.getBy()

Vow answered 28/7, 2015 at 13:38 Comment(14)
that being said - it's not possible as it stands. you can implement a custom solution, but Selenium does not support this out-of-the-boxVow
any idea why this might is not provided by the API? Do you think this is intentional for some reason?Speechless
well i'm actually a maintainer of the project, and it's not implemented because there's typically no reason to need to find the by of an element. easier just to say WebElement theElement = d.findElement(b) and you already have b saved somewhere. I could implement this, but it'd take a while. we're welcoming of pull requests :) github.com/seleniumhq/seleniumVow
Thanks a lot! would you mind, putting the essentials from your comment into your answer, I'll tag it as the right answer then. Cheers!Speechless
Regarding your example -> that's why I put the last part into my question, Selenium allows an architecture with its PageFactory that would force me to have the locator in 2 locations, one as a member variable and the other as the annotation for the elements.Speechless
But for PageObject and PageFactory pattern, The By is defined in annotation, the element itself cannot know BySaltpeter
I also think this API would be useful, I have instances in my test suites where I use driver.findElements() with some generic selector and then within a foreach loop sometimes it would be handy if I could get the specific selector for one of the elements in the list.Metropolitan
Hi, almost 2 years now since this question was asked. May I know if an implementation for this has been done? Thanks!Prowel
We also need this API too. It should return a list of By(s) locators. When a element becomes stale, just refresh it if all locators are known.Amative
Considering PageFactory pattern, Many people user @FindBy annotation so we don't always have its By locator.Perry
Thanks for your contribution, @frzsombor ! Feel free to create an answer for this question if this answer is no longer accurate circa Dec 2020Vow
I meant that I think the "Answer is No." part (with the bold "No") could be removed, as this is the accepted answer, and someone in TLDR mode (like I was) will think it is not possible, however it is, with a custom function. But also I see the fact that OP asks "Is there an elegant way [...]" and well, in this case your answer is correct. :)Impugn
I've added a tldr. Thanks for the recommendation!Vow
You need a reason for elegantly pulling a locator string? I've got one: staleElementReferenceExceptionDuplicate
W
5

No, there's not. I have implemented a possible solution as a proxy:

public class RefreshableWebElement implements WebElement {

    public RefreshableWebElement(Driver driver, By by) {
        this.driver = driver;
        this.by = by;
    }

    // ...

    public WebElement getElement() {
        return driver.findElement(by);
    }

    public void click() {
        getElement().click();
    }

    // other methods here
}
Watersoak answered 28/7, 2015 at 21:18 Comment(4)
nice idea with the refreshable element, will use it in the future, can you think of any reason why Selenium creators might not have provided this functionality intentionally?Speechless
I think they omitted this because WebElements can be located by complex sequences. For example, WebElement header = driver.findElement(By.id("table-id")).findElement(By.tagName("table")).findElements(By.xpath(".//th")).get(0);. How would you save references to all of those By locators?Onega
It is a path of locators.Amative
Wouldn't work for elements found using @FindBy annotationPerry
H
4

There is no elegant way provided by Selenium. And this is horrible

1) PageObject and PageFactory implies that we have WebElements in Page classes, but we don't have locators of those elements.

2) If I find element as descendant of current element using webElement.findElement(By), then I don't have the locator of this descendant even if I stored parent's locator in the variable.

3) If I use findElements function that returns List of elemetns, then I don't have locator for each specific element.

4) Having locator for element is useful at least because ExpectedConditions with locator as parameter are much better implemented than ExpectedConditions with WebElement as parameter.

For me Selenium is ill-conceived and poorly implemented library

Herwig answered 27/3, 2019 at 8:59 Comment(0)
D
4

Currently there is no specific method from selenium's end to do so. What you can do is write your custom method. You will get the clue of what selector type and path is used by just printing the webElement you have.

It looks something like this

[[ChromeDriver: chrome on XP (d85e7e220b2ec51b7faf42210816285e)] -> xpath: //input[@title='Search']]

Now, what you need to do is to extract the locator and its value. You can try something like this

private By getByFromElement(WebElement element) {
    By by = null;
    //[[ChromeDriver: chrome on XP (d85e7e220b2ec51b7faf42210816285e)] -> xpath: //input[@title='Search']]
    String[] pathVariables = (element.toString().split("->")[1].replaceFirst("(?s)(.*)\\]", "$1" + "")).split(":");

    String selector = pathVariables[0].trim();
    String value = pathVariables[1].trim();

    switch (selector) {
        case "id":
            by = By.id(value);
            break;
        case "className":
            by = By.className(value);
            break;
        case "tagName":
            by = By.tagName(value);
            break;
        case "xpath":
            by = By.xpath(value);
            break;
        case "cssSelector":
            by = By.cssSelector(value);
            break;
        case "linkText":
            by = By.linkText(value);
            break;
        case "name":
            by = By.name(value);
            break;
        case "partialLinkText":
            by = By.partialLinkText(value);
            break;
        default:
            throw new IllegalStateException("locator : " + selector + " not found!!!");
    }
    return by;
}
Drifter answered 26/5, 2019 at 16:55 Comment(0)
A
2

For me worked with commons-lang3

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.7</version>
</dependency>

For remote web element use method like:

protected String getLocator(WebElement element) {
        try {
            Object proxyOrigin = FieldUtils.readField(element, "h", true);
            Object locator = FieldUtils.readField(proxyOrigin, "locator", true);
            Object findBy = FieldUtils.readField(locator, "by", true);
            if (findBy != null) {
                return findBy.toString();
            }
        } catch (IllegalAccessException ignored) {
        }
        return "[unknown]";
    }
Aristotelianism answered 28/6, 2018 at 10:17 Comment(0)
F
0

I had written this utility function which returns a string combination of locator strategy + locator value.

private String getLocatorFromWebElement(WebElement element) {

    return element.toString().split("->")[1].replaceFirst("(?s)(.*)\\]", "$1" + "");
}
Fabron answered 14/3, 2019 at 11:34 Comment(0)
S
0

My solution when I ran into needing the By locator to use for an ExpectedConditions and I had my locators in the Page Object Factory was to use a String that had the locator in it and then build my By object and the element locator from that.

public class PageObject {
    private static final String XPATH_NAME = "...";

    public @iOSXCUITFindBy(xpath = XPATH_NAME)
    List<MobileElement> mobileElementName;

    public By getByXPath(){
        return new By.ByXPath(XPATH_NAME);
    }

    public PageObject() {
        PageFactory.initElements(driver, this);
    }
}
Shauna answered 29/12, 2020 at 19:49 Comment(0)
F
0

It is possible to obtain the XPath locator for a given WebElement:

public static String getFullXPath(WebDriver driver, WebElement element) {
    // Use JavaScript to obtain the full XPath
    JavascriptExecutor executor = (JavascriptExecutor) driver;
    String fullXPath = (String) executor.executeScript(
        "function absoluteXPath(element) {" +
        "var comp, comps = [];" +
        "var parent = null;" +
        "var xpath = '';" +
        "var getPos = function(element) {" +
        "var position = 1, curNode;" +
        "if (element.nodeType == Node.ATTRIBUTE_NODE) {" +
        "return null;" +
        "}" +
        "for (curNode = element.previousSibling; curNode; curNode = curNode.previousSibling) {" +
        "if (curNode.nodeName == element.nodeName) {" +
        "++position;" +
        "}" +
        "}" +
        "return position;" +
        "};" +
        "if (element instanceof Document) {" +
        "return '/';" +
        "}" +
        "for (; element && !(element instanceof Document); element = element.nodeType == Node.ATTRIBUTE_NODE ? element.ownerElement : element.parentNode) {" +
        "comp = comps[comps.length] = {};" +
        "switch (element.nodeType) {" +
        "case Node.TEXT_NODE:" +
        "comp.name = 'text()';" +
        "break;" +
        "case Node.ATTRIBUTE_NODE:" +
        "comp.name = '@' + element.nodeName;" +
        "break;" +
        "case Node.PROCESSING_INSTRUCTION_NODE:" +
        "comp.name = 'processing-instruction()';" +
        "break;" +
        "case Node.COMMENT_NODE:" +
        "comp.name = 'comment()';" +
        "break;" +
        "case Node.ELEMENT_NODE:" +
        "comp.name = element.nodeName;" +
        "break;" +
        "}" +
        "comp.position = getPos(element);" +
        "}" +
        "for (var i = comps.length - 1; i >= 0; i--) {" +
        "comp = comps[i];" +
        "xpath += '/' + comp.name.toLowerCase();" +
        "if (comp.position !== null) {" +
        "xpath += '[' + comp.position + ']';" +
        "}" +
        "}" +
        "return xpath;" +
        "} return absoluteXPath(arguments[0]);", element);

    return fullXPath;
}
Furie answered 25/10, 2023 at 20:17 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.