Implementation of Page Object Model using PageFactory is popular while creating automated test scripts using Selenium WebDriver – Java. But it sucks at many places as it limits usage in a way and behaves differently.
To use PageFcatory we need to declare fields with types as WebElement and List<WebElement> in Page Objects class using annotations like @FindBy , @FindBys and @FindAll. We need to initialize Page Object class using any overloaded initElements() method of PageFactory.
The good point about PageFactory is that declared web elements in Page Object class are lazily initialized. That means if we do not use a WebElement of a Page Object class, that web element will never be searched for. This is the reason that when we initialize a Page Object class, we do not get NoSuchElementException instantly for declared web elements which are not present at web page currently.
Another good point of PageFactory is that it searches for WebElement every time when a method is called on it which lowers chances of getting StaleElementReferenceException. However we can override this behavior using @CacheLookup.
Where does PageFactory suck?
Starting point of pain is Type of field which must be a WebElement or a List<WebElement>. It restricts the usage where you need to use By type. When an web element is found then it is stored in a variable of type WebElement. Because of limitations of PageFactory , every web elements need to have type as WebElemen or List<WebElement>. If PageFactory does not find element, it throws NoSuchElementException.
There is difference between Presence of an element and Display of an element. If an element is not present then it will not be displayed but if an element is present then it may or may not be displayed.
That is the reason you see all presence related methods accept By type not a WebElement type like visibility related methods.
In above image, you can see some visibility methods accepting By type but those methods actually checking presence followed by visibility.
You may need to work on already found element. For example – Checking enable status of element or if element is displayed. At these places PageFactory sucks.
Let’s see a scenario :-
I want to search something on Google. Enter search keyword and hit enter on Search button. I need to verify that Search button should not be visible on web page.
I will create two scripts – With and without PageFactory and to verify invisibility of search button, I will use invisibilityOf() of ExpectedConditions class.
package BasicSeleniumConcepts; import java.time.Duration; import java.util.Date; import org.openqa.selenium.By; import org.openqa.selenium.Keys; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.testng.annotations.Test; import io.github.bonigarcia.wdm.WebDriverManager; class GoogleHomePage { @FindBy(xpath = "(//input[@name='btnK'])[2]") WebElement ele; GoogleHomePage(WebDriver driver) { PageFactory.initElements(driver, this); } } public class DrawbacksOfPageFactory1 { @Test public void plainPattern() { WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); driver.get("https://www.google.com"); driver.findElement(By.name("q")).sendKeys("facebook"); WebElement ele = driver.findElement(By.xpath("(//input[@name='btnK'])[2]")); ele.sendKeys(Keys.ENTER); boolean isVisible = ExpectedConditions.invisibilityOf(ele).apply(driver).booleanValue(); if (isVisible) System.out.println("Element is invisible"); } @Test public void withPageFactory() { WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); GoogleHomePage homePage = new GoogleHomePage(driver); driver.get("https://www.google.com"); driver.findElement(By.name("q")).sendKeys("facebook"); homePage.ele.sendKeys(Keys.ENTER); boolean isVisible = ExpectedConditions.invisibilityOf(homePage.ele).apply(driver).booleanValue(); if (isVisible) System.out.println("Element is invisible"); } }
Output
Starting ChromeDriver 81.0.4044.138 (8c6c7ba89cc9453625af54f11fd83179e23450fa-refs/branch-heads/4044@{#999}) on port 40245 Only local connections are allowed. Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code. May 24, 2020 2:13:51 AM org.openqa.selenium.remote.ProtocolHandshake createSession Element is invisible Starting ChromeDriver 81.0.4044.138 (8c6c7ba89cc9453625af54f11fd83179e23450fa-refs/branch-heads/4044@{#999}) on port 2490 Only local connections are allowed. Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code. May 24, 2020 2:14:00 AM org.openqa.selenium.remote.ProtocolHandshake createSession org.openqa.selenium.NoSuchElementException: no such element: Unable to locate element: {"method":"xpath","selector":"(//input[@name='btnK'])[2]"}
Did you observe that test without PageFactory works perfectly fine whereas test with PageFactory gave NoSuchElementException. Reason behind is that PageFactory searched for a element every time whenever a method is called on it. So when invisibilityOf() method was called on Search element, it did not find that and throws exception instead of visibility status. If we use @CatchLookup annotation with element, we will get same result in both test methods.
In first test, when we check for invisibility of element then it gives true. What actually happened that after sending enter key on search button, element became stale and it throws StaleElementReferenceException which could be considered as invisibility of element. Same logic is followed by invisibilityOf() method. See actual implementation of method below :-
But in second test which is with PageFactory, it started searching for an element instead of checking invisibility status and throws NoSuchElementExcepion which we were not expecting.
PageFactory with WebDriverWait & FluentWait
If you use WebDriverWait in above examples then you see that test without PageFactory completed quickly but test with PageFactory will wait till timeout and throws TimeoutExeption at end. Reason behind is this that by default WebDriverWait will ignore NoSuchElementExcepion. In first test again StaleElementReferenceException will be throws which is not ignored by default.
If you use FluentWait in above examples then behavior will be same as without using explicit wait because no exception is ignored by default in FluentWait.
@Test public void plainPatternWithWebDriverWait() { WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); driver.get("https://www.google.com"); driver.findElement(By.name("q")).sendKeys("facebook"); WebElement ele = driver.findElement(By.xpath("(//input[@name='btnK'])[2]")); ele.sendKeys(Keys.ENTER); System.out.println("Start time : "+new Date()); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30)); boolean isVisible = wait.until(ExpectedConditions.invisibilityOf(ele)); if (isVisible) System.out.println("Element is invisible"); System.out.println("Start time : "+new Date()); } @Test public void withPageFactoryWithWebDriverWait() { WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); GoogleHomePage homePage = new GoogleHomePage(driver); driver.get("https://www.google.com"); driver.findElement(By.name("q")).sendKeys("facebook"); homePage.ele.sendKeys(Keys.ENTER); System.out.println("Start time : "+new Date()); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30)); boolean isVisible = wait.until(ExpectedConditions.invisibilityOf(homePage.ele)); if (isVisible) System.out.println("Element is invisible"); System.out.println("Start time : "+new Date()); }
Output
Start time : Sun May 24 02:38:48 IST 2020 Element is invisible Start time : Sun May 24 02:38:48 IST 2020 Start time : Sun May 24 02:38:57 IST 2020 org.openqa.selenium.TimeoutException: Expected condition failed: waiting for invisibility of Proxy element for: DefaultElementLocator 'By.xpath: (//input[@name='btnK'])[2]' (tried for 30 second(s) with 500 milliseconds interval) Caused by: org.openqa.selenium.NoSuchElementException: no such element: Unable to locate element: {"method":"xpath","selector":"(//input[@name='btnK'])[2]"}
Like above there may be multiple scenarios where behavior differs and it creates problem while writing assertions or creating common methods. Our automation framework should support both pattern. You may need to handle different scenarios for that.
One example of solving above problem is as below :-
We will create our own custom expected conditions and will handle those conditions which are ignored by default implementation.
MyExpectedConditions
package BasicSeleniumConcepts; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedCondition; public class MyExpectedCondiions { public static boolean invisibilityOf(WebElement element) { try { return !element.isDisplayed(); }catch(StaleElementReferenceException | NoSuchElementException e ) { System.out.println("Element is stale or not found whihc means element is invisible."); return true; } } public static ExpectedConditioninvisibilityOfPageFactoryElement(WebElement element) { return new ExpectedCondition () { @Override public Boolean apply(WebDriver webDriver) { return invisibilityOf(element); } @Override public String toString() { return "invisibility of " + element; } }; } }
Usage
package BasicSeleniumConcepts; import java.time.Duration; import java.util.Date; import org.openqa.selenium.By; import org.openqa.selenium.Keys; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; import org.openqa.selenium.support.ui.WebDriverWait; import org.testng.annotations.Test; import io.github.bonigarcia.wdm.WebDriverManager; class GoogleHomePageSolution { @FindBy(xpath = "(//input[@name='btnK'])[2]") WebElement ele; GoogleHomePageSolution(WebDriver driver) { PageFactory.initElements(driver, this); } } public class DrawbacksOfPageFactorySolution { @Test public void plainPattern() { WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); driver.get("https://www.google.com"); driver.findElement(By.name("q")).sendKeys("facebook"); WebElement ele = driver.findElement(By.xpath("(//input[@name='btnK'])[2]")); ele.sendKeys(Keys.ENTER); boolean isVisible = MyExpectedCondiions.invisibilityOf(ele); if (isVisible) System.out.println("Element is invisible"); } @Test public void withPageFactory() { WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); GoogleHomePageSolution homePage = new GoogleHomePageSolution(driver); driver.get("https://www.google.com"); driver.findElement(By.name("q")).sendKeys("facebook"); homePage.ele.sendKeys(Keys.ENTER); boolean isVisible = MyExpectedCondiions.invisibilityOf(homePage.ele); if (isVisible) System.out.println("Element is invisible"); } @Test public void plainPatternWithWebDriverWait() { WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); driver.get("https://www.google.com"); driver.findElement(By.name("q")).sendKeys("facebook"); WebElement ele = driver.findElement(By.xpath("(//input[@name='btnK'])[2]")); ele.sendKeys(Keys.ENTER); System.out.println("Start time : "+new Date()); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30)); boolean isVisible = wait.until(MyExpectedCondiions.invisibilityOfPageFactoryElement(ele)); if (isVisible) System.out.println("Element is invisible"); System.out.println("Start time : "+new Date()); } @Test public void withPageFactoryWithWebDriverWait() { WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); GoogleHomePageSolution homePage = new GoogleHomePageSolution(driver); driver.get("https://www.google.com"); driver.findElement(By.name("q")).sendKeys("facebook"); homePage.ele.sendKeys(Keys.ENTER); System.out.println("Start time : "+new Date()); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30)); boolean isVisible = wait.until(MyExpectedCondiions.invisibilityOfPageFactoryElement(homePage.ele)); if (isVisible) System.out.println("Element is invisible"); System.out.println("Start time : "+new Date()); } }
Output
Element is stale or not found whihc means element is invisible. Element is invisible Start time : Sun May 24 11:54:22 IST 2020 Element is stale or not found whihc means element is invisible. Element is invisible Start time : Sun May 24 11:54:23 IST 2020 Element is stale or not found whihc means element is invisible. Element is invisible Start time : Sun May 24 11:54:39 IST 2020 Element is stale or not found whihc means element is invisible. Element is invisible
So the PageFactory has mixed advantages and disadvantages which we need to keep in mind.
You can download/clone above sample project from here.
If you have any doubt, feel free to comment below.
If you like my posts, please like, comment, share and subscribe.
#ThanksForReading
#HappyLearning
Find all Selenium related post here, all API manual and automation related posts here and find frequently asked Java Programs here.
Many other topics you can navigate through menu.
Hi Amod,
So what is the solution for the By.. since im using pagefactory im unable to use explicit wait with expectedconditions(presenceofelement(by)) . Is there a way to use both the PageFactory and normal locating method(like By = by.xpath();)
or is there another way.