Reputation: 6909
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.
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:
Upvotes: 30
Views: 45885
Reputation: 1
The solution to extract By from WebElement [method getByFromElement() ] suggested above, will fail when a xpath locator that has parent::a or ancestor::div in its xpath. Because the split is done on basis of '"'
This is because an extra ':' char in xpath, will result in wrong splits into array.
Thus instead the split(":"); ---- > split(":",2) will work instead.
Upvotes: 0
Reputation: 33
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;
}
Upvotes: 0
Reputation: 1
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);
}
}
Upvotes: 0
Reputation: 29032
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()
Upvotes: 5
Reputation: 51
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;
}
Upvotes: 5
Reputation: 460
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
Upvotes: 4
Reputation: 19
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" + "");
}
Upvotes: 0
Reputation: 33
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]";
}
Upvotes: 2
Reputation: 1698
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
}
Upvotes: 5