Recently I needed to increase the information the NoSuchElementException contained when using Selenium’s @FindBy annotation. During this I stumbled across a way to get access to the By class created from the @FindBy.
For example say we have the following PageObject
public class testPageObject { @FindBy(css = "h1[name='test']") WebElement testElement1; @FindBy(xpath = "//*[class='test']") WebElement testElement2; @FindBy(id = "test") WebElement testElement3; public testPageObject() { //Cache the WebElements using a 5 second wait PageFactory.initElements(new AjaxElementLocatorFactory(driver, 5), this); } public void printBy() { By testElement1 = getBy("testElement1"); By testElement2 = getBy("testElement2"); By testElement3 = getBy("testElement3"); System.out.println(testElement1); System.out.println(testElement2); System.out.println(testElement3); } private By getBy(String fieldName) { try { return new Annotations(this.getClass().getDeclaredField(fieldName)).buildBy(); } catch (NoSuchFieldException e) { return null; } } }
The solution is to use Selenium’s built in Annotations class which takes a field and call buildBy() to return the By class for that field.
The output of the printBy() function:
By.selector: h1[name=’test’]
By.xpath: //*[class=’test’]
By.id: test
Hey, falkenfighter! Thanks, very useful. Is it possible to change xpath value in @FindBy? Say, I have the following annotation:
@FindBy(xpath=”//td[contains(text(),’$SystemName’)]”)
private WebElement RequiredSystemName;
In runtime, I want to replace $SystemName with the real value, say, “Firestone”. Is it doable? I mean, with your code in the article, I can get By.xpath: //td[contains(text(),’$SystemName’)]. But can I change xpath’value? Thanks.
It looks like I found the solution. I don’t change xpath value in @FindBy (still not sure if it is possible), but create the new one:
private WebElement prepareWebElementWithDynamicXpath (String xpathValue, String substitutionValue ) {
return driver.findElement(By.xpath(xpathValue.replace(“xxxxx”, substitutionValue)));
}
If you use the @Findby annotation Java requires all variables to be constant. So trying to place a dynamic variable into the annotation will not build. What I usually end up doing is target a higher ‘static’ element within the DOM and find that in the @Findby. Once you have the PageObject initialized with the static element find the dynamic element within the function. It’s not the best solution but provides a workaround.
// Find the static parent of the dynamic element we want to work with
@Findby(css = ".static-element")
WebElement myStaticElement;
// Once we have the static parent, find the dynamic element.
public void clickDynamicElement() {
String dynamicClass = ".dynamic-element";
myStaticElement.findElement(By.css(dynamicClass)).click();
}
Falkenfighter, thanks! So no silver bullet. Do I understand right that for any dynamic element you have to actually take care of two elements, both parent and child? Since I am a kind of newbie, may I ask why it is better than creating WebElement from scratch based on the value of a dynamic variable (you know it at runtime)? Thanks, Racoon.
The only reason I would create the parent element is that when the PageObject loads I’m guaranteed the initial elements are in the DOM. Sort of a fail fast design. But I’ve seen PageObjects built many ways and creating the WebElement from scratch is just as viable.
Thanks for sharing this!
this was really useful