automatictester

my thoughts on test automation

Selenium PageFactory design pattern

According to some people, PageFactory design pattern helps to reduce boilerplate code from Page Objects. I don’t use PageFactory and never thought my Page Objects contain boilerplate code, however I decided to make some research: I’ve created a couple of classes using Page Object design pattern, and then rewritten them using PageFactory.

There is a single, fundamental difference between Page Objects and PageFactory: first pattern relies on By locators to identify WebElements, while second makes direct use of WebElements. This have a number of implications which will be discussed below.

ParentPage – Page Objects

package com.wordpress.automatictester.framework.classic.core;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class ParentPage {

    protected WebDriver driver;

    public ParentPage(WebDriver driver) {
        this.driver = driver;
    }

    public void click(By locator) {
        driver.findElement(locator).click();
    }

    public boolean isDisplayed(By locator) {
        return !driver.findElements(locator).isEmpty();
    }

}

Parent Page Object contains a number of wrapper methods for page interaction. They are defined in parent class and are available through inheritance in child all classes, enforcing code reuse. For isDisplayed() method, we use approach suggested in WebDriver Javadoc: “findElement should not be used to look for non-present elements, use findElements(By) and assert zero length response instead”.

ParentPage – PageFactory

package com.wordpress.automatictester.framework.pagefactory.core;

import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;

public class ParentPage {

    public void click(WebElement element) {
        element.click();
    }

    public boolean isDisplayed(WebElement element) {
        try {
            return element.isDisplayed();
        } catch (NoSuchElementException e) {
            return false;
        }
    }
}

With PageFactory, we don’t need to access driver to find element, as element is already defined (see classes below). For that reason we don’t also need explicit constructor and driver initialization. However, as we don’t have an access to the driver, we can’t execute findElements() method, and therefore can’t use preferred approach to verify if element is displayed. There is a workaround for that, we can use a list of WebElements (both in page object class and in parent page class):

public boolean isDisplayed(List elements) {
    return elements.size() > 0;
}

However, in that case we may need to keep two locators of same element (e.g. WebElement and list of WebElements) when we need to perform some actions on that element and check if it is displayed. Of course we can keep only the list and access its first element to perform any actions besides verification if it is displayed, but this is a workaround for workaround and has nothing to do with removal of boilerplate code, which was supposed to be primary benefit of using PageFactory pattern.

In total: some code wasn’t needed, while some other was necessary.

ParentTest – Page Objects

package com.wordpress.automatictester.framework.classic.core;

import com.wordpress.automatictester.framework.classic.pages.AboutPage;
import com.wordpress.automatictester.framework.classic.pages.MainPage;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

public class ParentTest {

    protected WebDriver driver;

    protected MainPage mainPage;
    protected AboutPage aboutPage;

    public void initializeTest() {
        driver = new FirefoxDriver();

        mainPage = new MainPage(driver);
        aboutPage = new AboutPage(driver);

        driver.navigate().to("http://www.seleniumhq.org/");
        driver.manage().window().maximize();
    }

    public void quit() {
        driver.quit();
    }
}

ParentTest – PageFactory

package com.wordpress.automatictester.framework.pagefactory.core;

import com.wordpress.automatictester.framework.pagefactory.pages.AboutPage;
import com.wordpress.automatictester.framework.pagefactory.pages.MainPage;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.PageFactory;

public class ParentTest {

    protected WebDriver driver;

    protected MainPage mainPage;
    protected AboutPage aboutPage;

    public void initializeTest() {
        driver = new FirefoxDriver();

        mainPage = PageFactory.initElements(driver, MainPage.class);
        aboutPage = PageFactory.initElements(driver, AboutPage.class);

        driver.navigate().to("http://www.seleniumhq.org/");
        driver.manage().window().maximize();
    }

    public void quit() {
        driver.quit();
    }
}

Almost no difference, except how we initialize page object instances.

MainPage – Page Objects

package com.wordpress.automatictester.framework.classic.pages;

import com.wordpress.automatictester.framework.classic.core.ParentPage;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class MainPage extends ParentPage {

    private static final By ABOUT_TAB = By.linkText("About");

    public MainPage(WebDriver driver) {
        super(driver);
    }

    public void clickAboutTab() {
        click(ABOUT_TAB);
    }
}

MainPage – PageFactory

package com.wordpress.automatictester.framework.pagefactory.pages;

import com.wordpress.automatictester.framework.pagefactory.core.ParentPage;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

public class MainPage extends ParentPage {

    @FindBy(linkText = "About")
    private static WebElement aboutTab;

    public void clickAboutTab() {
        click(aboutTab);
    }
}

If we follow PageFactory pattern, we don’t need to handle driver at all, which is good. On the other side, element locators are usually coded in two lines, because of annotations. It’s hard to say we achived boilerplate code reduction here.

AboutPage – Page Objects

package com.wordpress.automatictester.framework.classic.pages;

import com.wordpress.automatictester.framework.classic.core.ParentPage;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class AboutPage extends ParentPage {

    private static final By ROADMAP_LINK = By.linkText("Roadmap");
    private static final By HELP_LINK = By.linkText("Help");

    public AboutPage(WebDriver driver) {
        super(driver);
    }

    public boolean isRoadmapLinkDisplayed() {
        return isDisplayed(ROADMAP_LINK);
    }

    public boolean isHelpLinkDisplayed() {
        return isDisplayed(HELP_LINK);
    }
}

AboutPage – PageFactory

package com.wordpress.automatictester.framework.pagefactory.pages;

import com.wordpress.automatictester.framework.pagefactory.core.ParentPage;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

public class AboutPage extends ParentPage {

    @FindBy(linkText = "Roadmap")
    private static WebElement roadMapLink;

    @FindBy(linkText = "Help")
    private static WebElement helpLink;

    public boolean isRoadmapLinkDisplayed() {
        return isDisplayed(roadMapLink);
    }

    public boolean isHelpLinkDisplayed() {
        return isDisplayed(helpLink);
    }
}

Same here.

AboutTest – Page Objects

package com.wordpress.automatictester.framework.classic.tests;

import com.wordpress.automatictester.framework.classic.core.ParentTest;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

public class AboutTest extends ParentTest {

    @BeforeClass
    public void start() {
        super.initializeTest();
    }

    @Test
    public void verifyAboutPageLinks() {
        mainPage.clickAboutTab();
        assertThat(aboutPage.isRoadmapLinkDisplayed(), is(true));
        assertThat(aboutPage.isHelpLinkDisplayed(), is(false));
    }

    @AfterClass
    public void quit() {
        super.quit();
    }
}

AboutTest – PageFactory

package com.wordpress.automatictester.framework.pagefactory.tests;

import com.wordpress.automatictester.framework.pagefactory.core.ParentTest;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

public class AboutTest extends ParentTest {

    @BeforeClass
    public void start() {
        super.initializeTest();
    }

    @Test
    public void verifyAboutPageLinks() {
        mainPage.clickAboutTab();
        assertThat(aboutPage.isRoadmapLinkDisplayed(), is(true));
        assertThat(aboutPage.isHelpLinkDisplayed(), is(false));
    }

    @AfterClass
    public void quit() {
        super.quit();
    }
}

In case of test class, there is no difference between those two approaches.

Other PageFactory implications

There are also some other implications of not working with driver directly. With Page Objects, you can access sub-element of particular element:

driver.findElement(By.id("someId")).findElement(By.id("someOtherId"));

With PageFactory you can’t – or actually you can, but then you no longer make use of PageFactory, as you need to explicitly call By locator and follow Page Objects pattern:

someWebElement.findElement(By.id("someId"));

Conclusion

PageFactory design pattern was an attempt to resolve problem of boilerplate code of Page Objects. However, as shown in above examples, PageFactory can reduce number of lines of code in one place, but increase in another.

Weakness of PageFactory is that it relies on WebElements, while Page Objects design pattern relies on By locators. If you have By locator, you can always get a WebElement. If you have WebElement, you can’t get its By locator. In more advanced scenarios this may lead to storing both WebElements and By locators in your code.

Advertisements

3 responses to “Selenium PageFactory design pattern

  1. Nagarjuna December 31, 2014 at 7:18 am

    Hi automatic tester,
    i haven’t gone through the whole article yet, but code in “ParentPage – PageFactory” gave the solution for what i was looking for. i.e. passing element as argument. very happy. wish you a very happy new year 🙂

  2. fyrespray October 7, 2015 at 11:30 am

    his blog post is actually quite inaccurate, I suggest having a deeper look into the PageFactory classes. To fix a couple of misconceptions:

    You can execute the findElements method with the page factory, just do the following:

    @FindBy(how = How.CSS, using = “input[type=radio]”)
    private static List radioButtons;

    If you want to keep two locators for a single element you can do that as well:

    @FindAll({
    @FindBy(how = How.ID, using = “nav-entry”),
    @FindBy(how = How.ID, using = “nav-link”)
    })
    private WebElement navgiation;

    You can also initialise the page factory in your constructor so you don’t need a line of code to new up the object and then another one to intialise it.

    public MyPageFactory() throws Exception {
    PageFactory.initElements(driver, this);
    }

    The PageFactory classes aren’t perfect, but they do a lot for you. The fact that it turns your WebElements into proxies that find the element every time means you are less likely to suffer from StaleElementReferenceExceptions.

    Finally It is possible to get the locator from a WebElement, but it’s a real hack:

    https://github.com/Ardesco/Powder-Monkey/blob/master/src/main/java/com/lazerycode/selenium/tools/WebElementTools.java

    I personally use the PageFactory classes all the time.

    • automatictester November 14, 2015 at 3:27 pm

      Thanks for your comment and contribution to the ecosystem!

      I don’t think this post is inaccurate, I think we agree in most relevant cases. What is different is our personal preferences with regards to how test automation code should be designed.

      As you pointed out, with PageFactory you can’t get locator from WebElement without hacks. I came across a few solutions to do that, but I wouldn’t trust any of them. Machine-generated Selenium locators are usually of low quality, tend to break during minor UI refactorings and not necessary work well in all browsers. Most of all, locators are the core of Selenium automation. If you need hacks to get them, you most probably go down the wrong route.

      I agree I can use List of WebElements. That’s already mentioned in my blog post.

      I also agree I can use more than one locator for the same element – in a few different ways, depending on context. I’ve also mentioned this above, and gave an example.

      Just a final word on StaleElementReferenceException – I agree PageFactory should help here. However, according to my experiences it doesn’t, and actually makes things worse. I can’t see any obvious reason for that.

      As I’ve mentioned, this is all about personal preferences. My preference is to avoid PageFactory, unless there is a reason to do the opposite. It can help in one place, but force you to use workarounds elsewhere.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: