Получить по локатору уже найденного WebElement
Есть ли элегантный способ получить локатор By для Selenium WebElement, который я уже нашел / идентифицировал?
Чтобы прояснить вопрос: я хочу, чтобы "По локатору" использовался для поиска элемента. В этом случае меня не интересует конкретный атрибут или конкретный локатор, такой как css-locator.
Я знаю, что мог бы проанализировать результат метода toString() WebElement:
WebElement element = driver.findElement(By.id("myPreciousElement"));
System.out.println(element.toString());
Вывод будет, например:
[[FirefoxDriver: firefox в WINDOWS (....)] -> id: myPreciousElement]
если вы нашли свой элемент по xpath:
WebElement element = driver.findElement(By.xpath("//div[@someId = 'someValue']"));
System.out.println(element.toString());
Тогда ваш вывод будет:
[[FirefoxDriver: firefox в WINDOWS (....)] -> xpath: //div[@someId = 'someValue']]
Поэтому в настоящее время я написал свой собственный метод, который анализирует этот вывод и дает мне "воссозданный" локатор By.
НО есть ли в Selenium более элегантный способ использовать локатор By для поиска элемента?
Я не мог найти один до сих пор.Если вы уверены, что ничего такого нет, можете ли вы вспомнить причину, по которой создатели API могут не предоставлять такую функциональность?
* Несмотря на то, что это не имеет ничего общего с вопросом, если кто-то задается вопросом, зачем вам эта функциональность когда-либо понадобится, просто 2 примера:
- если вы используете PageFactory, вы, скорее всего, не будете использовать локаторы как переменные-члены в своем классе Page, но они могут понадобиться вам позже при работе с элементами страницы.
- вы работаете с API людей, которые просто используют шаблон объекта страницы без PageFactory и, таким образом, ожидают, что вы передадите локаторы вместо самого элемента.*
8 ответов
Ответ - нет. Вы не можете извлечь By
из ранее найденного WebElement по умолчанию.
При этом можно реализовать индивидуальное решение, но Selenium не предлагает этого "из коробки".
Подумайте о том, почему "почему"..
By by = By.id("someId");
WebElement e = driver.findElement(by);
у вас уже есть By
объект, поэтому вам не нужно вызывать что-то вроде e.getBy()
Я мог бы реализовать что-то подобное в будущем, но без обещаний. Опять же - это просто сахар.
Нет, нет Я реализовал возможное решение в качестве прокси:
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
}
В настоящее время нет никакого специального метода с конца селена, чтобы сделать это. Что вы можете сделать, это написать свой собственный метод. Вы получите представление о том, какой тип селектора и путь используется, просто распечатав имеющийся у вас веб-элемент.
Это выглядит примерно так
[[ChromeDriver: chrome в XP (d85e7e220b2ec51b7faf42210816285e)] -> xpath: //input[@title='Search']]
Теперь, что вам нужно сделать, это извлечь локатор и его значение. Вы можете попробовать что-то вроде этого
`private By getByFromElement(элемент WebElement) {
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;
}
`
Нет никакого элегантного способа, предоставленного Selenium. И это ужасно
1) PageObject
а также PageFactory
подразумевает, что у нас есть WebElements
в Page
классы, но у нас нет локаторов этих элементов.
2) Если я нахожу элемент как потомок текущего элемента, используя webElement.findElement(By)
, тогда у меня нет локатора этого потомка, даже если я сохранил родительский локатор в переменной.
3) Если я использую findElements
функция, которая возвращает List
Elemetns, то у меня нет локатора для каждого конкретного элемента.
4) Наличие локатора для элемента полезно хотя бы потому, что ExpectedConditions
с локатором в качестве параметра реализованы намного лучше, чем ExpectedConditions
с WebElement
в качестве параметра.
Для меня Selenium - непродуманная и плохо реализованная библиотека
Для меня работал с commons-lang3
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
Для удаленного веб-элемента используйте метод вроде:
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]";
}
Я написал эту служебную функцию, которая возвращает строковую комбинацию стратегии локатора + значение локатора.
private String getLocatorFromWebElement(WebElement element) {
return element.toString().split("->")[1].replaceFirst("(?s)(.*)\\]", "$1" + "");
}
Мое решение, когда я столкнулся с необходимостью использовать локатор By для ExpectedConditions, и у меня были мои локаторы в Фабрике объектов страницы, состояло в том, чтобы использовать строку, в которой был локатор, а затем построить мой объект By и локатор элементов из этого.
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);
}
}
Можно получить локатор XPath для данного 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;
}