Selenium check element presence if using FindBy annotation - selenium

Trying to use the #FindBy annotation my feeling is there is no perfect way to check the presence of an element.
There are similar discussions here or here, but I see only workarounds and nothing like
#FindBy (id = "abc")
private WebElement TestElement;
if (TestElement.isPresent) {...};
I found solutions with #FindBys, but this way I have to implement an additional #FindBys for every element I would check the presence within DOM? Not really nice.
Every alternative solution with ExpectedConditions.presenceOf... or anything using FindElement needs a locator as parameter instead of a WebElement, correct?
Sure I can build workarounds using e.g. ExpectedConditions.elementToBeClickable(WebElement) within a try/catch and raise an org.junit.jupiter.api.Assertions.fail, but this is not a real DOM check.
To me it seems I'm more flexible with classic By definitions of Page Objects (e.g. private By searchInput = By.xpath("\\input[#class='input_search']");) instead of using #FindBy. Using #FindBy I always deal with the WebElement itself and there is no chance to check the presence before, right?
What are best pratice solutions to check the DOM presence of elements in context of page objects using #FindBy? Or should I stay with By to be safe.

This may not be the best solution but you can use findBy and return a list:
List<WebElement> TestElement = driver.findElements(By.id(...));
and check the size of the list elelement each time (in a while loop) to see if it is not empty.

Related

Selenium Page Factory - finding element with nested selectors

I'm trying to use Page Factory and #FindBy to initialize my WebElements. However, I'm running into trouble when I have an element that cannot be found with just the ID.
The following selector is a little more complex to find, so I'm using Selenium's ByChained:
val endreBoligModal: MutableList<WebElement> = driver.findElements(ByChained(By.className("hb-card"), By.className("hb-card-header"), By.className("hb-card-tittel"), By.tagName("span")))
The reason is that, for some reason, finding the element with a unique ID doesn't work. Selenium just cannot find it.
So, with regards to the Page Factory and the #FindBy way of creating elements - how do I do it?
The way I've used it so far is like this:
#FindBy(id = "login")
private WebElemement login
Or
#FindBy(css = "[id=login]")
By for the chained selector element, I cannot figure out how to do it with Page Factory.
I think there's something called #FindBys (with an s at the end). At least, there seems to be. But for the life of it I cannot find ANY documentation about it on the net, so I don't even know if it's relevant.
All help is appreciated.
You can just look at the sources of org.openqa.selenium.support.FindBys. There is a short but informative description saying:
Used to mark a field on a Page Object to indicate that lookup should use a series of #FindBy tags in a chain as described in ByChained It can be used on a types as well, but will not be processed by default.
Eg:
#FindBys({#FindBy(id = "foo"),
#FindBy(className = "bar")})
So the example seems pretty straightforward. Give it a try.

Require Logic to avoid "StaleElementReferenceException" after page refresh [duplicate]

I am trying to learn the PageFactory model. I understood the fact that when we do a initElements, the WebElements are located. Say for example, I click on a webelement and because of which there is a change in one of the other webelements in DOM. Now, obviously I would get a StaleElementReferenceException here. How would I resolve this issue?
Should I find that specific WebElement again knowing the fact that there can be a change in the WebElement's properties in the DOM? or is there an another way to handle this?
StaleElementReferenceException
StaleElementReferenceException extends WebDriverException and indicates that the previous reference of the element is now stale and the element reference is no longer present on the DOM of the page.
Common Reasons
The common reasons behind facing StaleElementReferenceException are as follows:
The element has been deleted entirely.
The element is no longer attached to the DOM.
The webpage on which the element was part of has been refreshed.
The (previous) element has been deleted by a JavaScript or AjaxCall and is replaced by a (new) element with the same ID or other attributes.
Solution : If an (old) element has been replaced with new identical one, the simple strategy would be to use findElement() or findElements to look out for the element again.
Answering your queries
When we do a initElements, the WebElements are located : When you call initElements() method, all the WebElements of that page will get initialized. For example,
LoginPageNew login_page = PageFactory.initElements(driver, LoginPageNew.class);
This line of code will initialize all the static WebElements defined within the scope of the LoginPageNew.class whenever and wherever it is invoked from your Automation Script.
I click on a webelement and because of which there is a change in one of the other webelements in DOM : This is pretty much possible.
As an example, in general invoking click() on a <input> tag wouldn't trigger any change of any of the WebElements on the HTML DOM.
Where as invoking click() on a <button> tag or <a> tag may call a JavaScript or a Ajax which inturn may delete an element or can replace the (previous) element by a (new) element with the same ID or other attributes.
Conclusion
So, if WebDriver throws a StaleElementReferenceException, that implies even though the element still exists, the reference is lost. We should discard the current reference we have and replace it by locating the WebElement once again when it gets attached to the DOM. That means you have to again reinitialize the class through initElements() method which inturn reinitializes all the WebElements defined in that page.
Solution
If a old element has been replaced with new identical one, the simple strategy would be to invoke WebDriverWait inconjunction with ExpectedConditions to look out for the element.
You can find relevant detailed discussions in:
How to add explicit wait in PageFactory in PageObjectModel?
References
Here are the references of this discussion:
Stale Element Reference Exception
Class StaleElementReferenceException
Selenium: How to tell if RemoteWebDriver.findElements(By) can throw StaleElementReferenceException at all?
This is a known problem with the PageFactory implementation.
If you are unlucky enough for the element to become stale in the instant between the element being found, and then the element being clicked upon, you will get this error. Unfortunately the PageFactory code does not try to find the element again if it has become stale and it throws an Exception.
I would classify this as a bug with PageFactory, it should auto re-find the element if it ever becomes stale (unless the #CacheLookup annotation is used).
The suggestion to recall initElements isn't going to fix anything, you only need to init the elements once because that binds a Java proxy class to the element in question. The page factory implementation is supposed to remove the possibility of StaleElementReferenceExceptions (hence why this is a bug)
Stale element exception is thrown in two cases
The element is no longer attached to the DOM.
The element has been deleted entirely.
When this happen you wrap your code in try catch block then you can loop and retry as many times as you need until it succeeds.
public void waitForElementPresent(final By by, int timeout){
WebDriverWait wait = (WebDriverWait)new WebDriverWait(driver,timeout)
.ignoring(StaleElementReferenceException.class);
wait.until(new ExpectedCondition<Boolean>(){
#Override
public Boolean apply(WebDriver webDriver) {
WebElement element = webDriver.findElement(by);
return element != null && element.isDisplayed();
}
});
}

In the Page Object Model, why do we use Webelements instead of Strings as class variables?

Simply put: when implementing the POM framework we have Pagefactory initialize all the elements in a pageobject. Why do we do this instead of storing the xpath/css selectors as strings and calling those as needed instead?
ex:
#FindBy(xpath = "//Button[text()='Add and Edit']")
#CacheLookup
private WebElement addAndEdit;
vs
private String addAndEdit;
This is not defined as part of the page object model, this is defined by PageFactory which is a helper class in initializing elements in a page object. The intent there is to set up a proxy so that the element's location strategy is defined by the #FindBy and you can reference the WebElement and it will go look it up for you when you use the reference.
If you are going to store locators, don't store a string... store instead a By locator. It has the extra information of the TYPE of locator, e.g. By.id, By.cssSelector, etc. I think this is much cleaner approach and will prevent you from having to somehow determine what type of locator that string variable is. This is the approach I use in all the Selenium projects that I have written and/or maintain.
According to the Selenium contributors, PageFactory should not be used. See this yt video of Simon Stewart, Lead Committer, stating don't use it (27:26) and so on and why. The link starts the section that leads into his comments on PageFactory.

StaleElementReference Exception in PageFactory

I am trying to learn the PageFactory model. I understood the fact that when we do a initElements, the WebElements are located. Say for example, I click on a webelement and because of which there is a change in one of the other webelements in DOM. Now, obviously I would get a StaleElementReferenceException here. How would I resolve this issue?
Should I find that specific WebElement again knowing the fact that there can be a change in the WebElement's properties in the DOM? or is there an another way to handle this?
StaleElementReferenceException
StaleElementReferenceException extends WebDriverException and indicates that the previous reference of the element is now stale and the element reference is no longer present on the DOM of the page.
Common Reasons
The common reasons behind facing StaleElementReferenceException are as follows:
The element has been deleted entirely.
The element is no longer attached to the DOM.
The webpage on which the element was part of has been refreshed.
The (previous) element has been deleted by a JavaScript or AjaxCall and is replaced by a (new) element with the same ID or other attributes.
Solution : If an (old) element has been replaced with new identical one, the simple strategy would be to use findElement() or findElements to look out for the element again.
Answering your queries
When we do a initElements, the WebElements are located : When you call initElements() method, all the WebElements of that page will get initialized. For example,
LoginPageNew login_page = PageFactory.initElements(driver, LoginPageNew.class);
This line of code will initialize all the static WebElements defined within the scope of the LoginPageNew.class whenever and wherever it is invoked from your Automation Script.
I click on a webelement and because of which there is a change in one of the other webelements in DOM : This is pretty much possible.
As an example, in general invoking click() on a <input> tag wouldn't trigger any change of any of the WebElements on the HTML DOM.
Where as invoking click() on a <button> tag or <a> tag may call a JavaScript or a Ajax which inturn may delete an element or can replace the (previous) element by a (new) element with the same ID or other attributes.
Conclusion
So, if WebDriver throws a StaleElementReferenceException, that implies even though the element still exists, the reference is lost. We should discard the current reference we have and replace it by locating the WebElement once again when it gets attached to the DOM. That means you have to again reinitialize the class through initElements() method which inturn reinitializes all the WebElements defined in that page.
Solution
If a old element has been replaced with new identical one, the simple strategy would be to invoke WebDriverWait inconjunction with ExpectedConditions to look out for the element.
You can find relevant detailed discussions in:
How to add explicit wait in PageFactory in PageObjectModel?
References
Here are the references of this discussion:
Stale Element Reference Exception
Class StaleElementReferenceException
Selenium: How to tell if RemoteWebDriver.findElements(By) can throw StaleElementReferenceException at all?
This is a known problem with the PageFactory implementation.
If you are unlucky enough for the element to become stale in the instant between the element being found, and then the element being clicked upon, you will get this error. Unfortunately the PageFactory code does not try to find the element again if it has become stale and it throws an Exception.
I would classify this as a bug with PageFactory, it should auto re-find the element if it ever becomes stale (unless the #CacheLookup annotation is used).
The suggestion to recall initElements isn't going to fix anything, you only need to init the elements once because that binds a Java proxy class to the element in question. The page factory implementation is supposed to remove the possibility of StaleElementReferenceExceptions (hence why this is a bug)
Stale element exception is thrown in two cases
The element is no longer attached to the DOM.
The element has been deleted entirely.
When this happen you wrap your code in try catch block then you can loop and retry as many times as you need until it succeeds.
public void waitForElementPresent(final By by, int timeout){
WebDriverWait wait = (WebDriverWait)new WebDriverWait(driver,timeout)
.ignoring(StaleElementReferenceException.class);
wait.until(new ExpectedCondition<Boolean>(){
#Override
public Boolean apply(WebDriver webDriver) {
WebElement element = webDriver.findElement(by);
return element != null && element.isDisplayed();
}
});
}

Using pagefactory can initialize webelement with visible element, if FindBy matches multiple webelement on the page

I have a page which has multiple button 'OK' for different sections. However, at any given point of time only one 'OK' button is visible and can be identified using advanced xpath mechanisms (But not always)
//button[#value='OK' and not(#style='')]
My Question here is that, can i extend the class PageFactory and override the initialize function by enhancing it by adding a mechanism which processes all the webelements returned by #FindBy decoraters. I will check which one is visible using .isDiplayed() and assign the visible one? This way, i need not have multiple lines of code across pages.
Currently more elements are matched for xpath //button[#value='OK'] and hence i am getting NoSuchElementException. Ideally there should be AmbiguousElementException, but we dont have any such...
Any help or pointers here please?