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

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.

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.

Selenium check element presence if using FindBy annotation

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.

How to execute specific method implicitly before interacting with element in Selenium?

The application I'm automating is using jQuery and ajax calls. Hence before interacting with elements, I wait for document to be ready and all ajax calls to complete. Only then I interact with element like sendKeys(), click() etc
For this, I need to add that method (say waitForDocumentReadyAndAjaxTocomplete()) explicitly everywhere wherever element is being interacted.
Is there anyway I can call that method waitForDocumentReadyAndAjaxTocomplete() implicitly before interacting with an element?
Yes, there is a way. You can create an utility class which creates your identifier using the locator you want to use and in the method where you create the identifier you can call the waitForDocumentReadyAndAjaxTocomplete method.
Basically the utility class will handle the call and you can actually have a try catch or a polling mechanism to optimize the call to the method as suits your requirement.
This way you donot have to call the waitForDocumentReadyAndAjaxTocomplete method everytime. It will be called in your utility class.
You can use the power of PageFactory.initElements(..) where you can:
Design your bindings in Page Object pattern
Intitialize elements of your page classes using your custom ElementLocatorFactory / ElementLocator. Where you can can add waiting of your dom events before finding elements.
You can check how AjaxElementLocatorFactory is implemented. There should not be issues with following this approach sine all key methods are public so can be simply overriden.
However I guess you will have to supply #CacheLookup annotation to all your fields annotated with #FindBy

Pass value to xpath at runtime

Please read the whole Q before disliking or commenting something. I have searched on internet before posting it here. I'm having the below project structure.
pages(package)
> Homepage.java
test(package)
> Flipkart.java
Inside Homepage.java i have declared all the WebElements using POM Page Factory methods and created respective method to click on Electronics link.
#FindBy(xpath = '//input[#title='Electronics']')
private WebElement lnkElectronics;
Inside Fipkart.java I'm calling the Electronics click method.
My doubt over here is the declared WebElement is specifically for Electronics.
Is there a way i can create a WebElement with type like mentioned below and pass value for %s dynamically from main method?
#FindBy(xpath = '//input[#title='%s']')
private WebElement lnkElectronics;
Answer referenced from Page Object Model in Selenium
You cannot create a FindBy with Variable, FindBy accepts only constants.
In case if you want to achieve that variability then you should write or find the element using normal findElement method
As per the Test Design Consideration following Page Object Design Pattern :
A Page Object is an object-oriented class that serves as an interface to a page of the Application Under Test. Your #Tests uses the methods of this Page Object class whenever they need to interact with the User Interface of that page. The benefit is that if the UI changes for the page your #Tests themselves don’t needs to be changed. Only the code within the Page Object needs to be changed.
Advantages :
Clean separation between test code and page specific code such as locators, methods and layout.
A single repository for the operations offered by the page rather than having these services scattered throughout the tests.
Based on these Page Factory features you won't be able to create any generic WebElement for which you can pass value dynamically from main() or #Test annotated method e.g.
#FindBy(xpath = '//input[#title='%s']')
private WebElement lnkElectronics;
You can find almost a similar discussion in Where should I define modal specific code in Selenium Page Object Model pattern
WorkAround : on page class you can define a method, and can pass the text on the fly from the calling class to click on specific tab
if you want to click any common text on the page. You can create a method as given below and can pass the text on the fly to click on that specific tab on that page
public void clickTab(String tabText){
String tabxpath = "//div[contains(text(), '" + tabText + "')]";
driver.findElement(By.xpath(tabxpath)).click();
}

#FindBy doesn't care if the element isn't there when I call initElements

For instance, let's say I have class FanPage, with this annotation
#FindBy(how = How.ID, using = "ctl00__lvph_Add")
private WebElement _AddFanButton;
and then in my test code I say
fanPage = homePage.GoToFanPage()
which does
return PageFactory.initElements(driver, CC_VendorStatisticsMetadata.class);
Now if my annotation is incorrect (let's say it should be ctl00_lvph_AddFan), I would expect my call to initElements to fail. However, it doesn't and it simply returns a FanPage object to me. It only fails if I try to use _AddFanButton.
How do I get PageFactory to look for my annotations from the start?
You don't. The PageFactory does lazy initialization, and that's how it's designed.
Consider a Page Object where certain of your elements don't exist on the page until some action is taken. Since Page Objects are intended to encapsulate business logic and not just the elements on the page, this is a perfectly logical scenario. In that case, your initElements() call would fail on page object initialization every single time, and not give you the chance to call the business logic method that would cause the element to appear.
It's possible that the PageFactory will not work for you if this is a requirement for your test framework. In that case, you'd do well to construct your own implementation.