//
you're reading...
WebDriver

Understanding PageFactory

I recently spent some time building some customisation around PageFactory. That was when I thought that maybe there is a need to explain PageFactory in detail apart from the user facing documentation that is available on the Selenium wiki page.

The concept of PageFactory within the Selenium libraries consists of the following components :

  • PageFactory – A utility class that is user facing and wires up all of the things together for the user.
  • ElementLocatorFactory – A factory that is capable of producing ElementLocators per invocation.
  • ElementLocator – This entity is capable of servicing calls to findElement() and findElements().
  • FieldDecorator – Given a field in a class, this entity decides on how the value of the field should look like.
  • AbstractAnnotations – This entity provides most of the heavy lifting implementation for parsing details that are provided via the annotations FindAll, FindBy and FindBys. But it does also provide the mechanism to customize how would the By be constructed [ remember By is the mother of all location strategies in the selenium world] and also defining how should caching be handled.

Here are some implementations that give life to the above components.

  • DefaultElementLocatorFactory – An implementation of ElementLocatorFactory which is capable of producing implementation of ElementLocator by sharing a reference to the WebDriver instance.
  • DefaultElementLocator – An implementation of ElementLocator which relies on the passed reference of ¬†WebDriver to honour calls to findElement() and findElements() for every Field. Here’s where things get interesting.
    • How to locate an element is a meta data that is added to a field in a page class using annotations.
    • Since now we are dependent on extracting out this meta information and since we know we are going to be getting a reference to a field, we are now into the world of reflection.
    • So we need an implementation that basically is capable of introspecting a Field and reading its annotations.
    • This is where Annotations entity comes in. So in a nutshell, DefaultElementLocator relies on WebDriver to actually query the DOM and relies on Annotations to provide the By to fulfill calls to findElement() and findElements().
  • Annotations – An extension of AbstractAnnotations, which queries the below annotations that may be added on a field in a page class to help construct the location strategy viz org.openqa.selenium.By.
    • org.openqa.selenium.support.FindBys
    • org.openqa.selenium.support.FindAll
    • org.openqa.selenium.support.FindBy
  • DefaultFieldDecorator – An implementation of FieldDecorator which checks if the field passed to is a WebElement (or) a List of WebElement. If not it returns back a null when asked to decorate. If an element is :
    • Of type WebElement, then it creates a proxy for it by passing in a custom invocation handler named LocatingElementHandler.
    • A List of WebElement, then it creates a proxy for it by passing in a custom invocation handler named LocatingElementListHandler.
  • LocatingElementHandler – An invocation handler which makes use of a WebDriver reference to find an actual WebElement and then delegates any calls to the method being proxied by passing the reference to the recently found WebElement.
  • LocatingElementListHandler – An invocation handler which makes use of a WebDriver reference to find a List of WebElements and then delegates any calls to the method being proxied the reference to the recently found list of WebElements.

Now that we have seen all the components of PageFactory, lets see what happens when you invoke initElements() to initialise the PageFactory system.

Lets say we have a page class as below :

public class GoogleSearchPage {
    // The element is now looked up using the name attribute
    @FindBy(how = How.NAME, using = "q")
    private WebElement searchBox;

    public WebElement getSearchBox() {
    	return searchBox;
    }
}

As you know, the most popular methods of initializing the PageFactory system is by making a call to one of the below static methods in PageFactory utility class.

Here are those variants :

  1. initElements(WebDriver driver, Class pageClassToProxy)
  2. initElements(WebDriver driver, Object page)
  3. initElements(ElementLocatorFactory factory, Object page)
  4. initElements(FieldDecorator decorator, Object page)

Here (3) and (4) are lesser known variants and not that commonly used.

and we are doing something like below :

GoogleSearchPage page = new GoogleSearchPage();
PageFactory.initElements(driver, page);

Irrespective fo which one you use [ (1) or (2)], here’s how the call order goes.

initElements(WebDriver driver, Class<T> pageClassToProxy) – uses reflection to create an instance for pageClassToProxy. It looks for a 1-arg constructor which takes in a WebDriver instance and uses it. If it doesn’t find that, it invokes the default zero arg constructor. That is why it is very important that you either have a zero arg default constructor for your Page Object class (or) explicitly add a constructor which accepts a WebDriver instance.
From here the call goes to initElements(WebDriver driver, Object page). This method creates an instance of DefaultElementLocatorFactory and then passes that to initElements(ElementLocatorFactory factory, Object page). Now the Factory reference is used to create an instance of DefaultFieldDecorator and the call now lands with initElements(FieldDecorator decorator, Object page).

Now the page class is introspected to retrieve its class name [ this happens in a loop until we get the parent class as Object. This is needed because we may have our PageObject class extend each other. So we would need to initialize the entire chain].

  • For every class, its public, private and protected fields are extracted out.
  • For every such field the decorator spurts out proxy object values, which is then injected into the field map of the page object.
    • If the field is a WebElement then a proxy which uses an instance of LocatingElementHandler is created and used.
    • If the field is a List of WebElements then a proxy which uses an instance of LocatingElementListHandler is created and used.

Going by our example page class, the GoogleSearchPage instance would contain a field named searchBox whose value would be a proxy implementation that contains an invocation handler which has a reference to an elementlocator and the elementlocator has a reference to the WebDriver.

So its like packing in all the details required to proxy an actual method call on WebElement and also a webdriver reference for querying the DOM, into the field of a page class that is annotated with the PageFactory specific annotations.

In a nut shell the call path is (1) —> (2) —> (3) —> (4)

The most important point to remember here is that : At this juncture we are only building the page object instance such that for every field we have injected a proxied object into it as field values. There are still no calls to the DOM being made.

Now lets see what happens when you access any one of the WebElement data members of your class and try doing some operation on it.

From our earlier object, when we invoke

page.getSearchBox().sendKeys("Hello-World");

it causes the proxied object that was persisted via initElements() call for the data member searchBox to be popped out.
Now the call to sendKeys() gets delegated to LocatingElementHandler object [ remember this is the InvocationHandler we passed when we created a proxy instance].

LocatingELementHandler now uses the DefaultElementLocator reference that is passed to it to query the DOM and search for the actual web element. It then passes the reference of the newly found WebElement to the method that is being proxied. So in our example, it passes the call to the underlying sendKeys() method on the WebElement passing “Hello-World” text to WebElement.

Hope that gets you started with understanding PageFactory.

Advertisements

Discussion

12 thoughts on “Understanding PageFactory

  1. Gr8 article sir as always. But issue i find in POM design pattern is that it works smooth when we have linear flow in our test case. But thats not always the case. Some times, a method in a POM can return different page, depending upon the context. For example, clicking a sign up button from Home Page would return back to Home and clicking a sign up button from checkout page would return back to CheckoutPage, Having multiple methods with different return type would deviate from DRY.

    Posted by Sunny Sachdeva | September 6, 2016, 10:21 am
    • Sunny that is why I like to use page object model as just a representation of an actual web page and not build the business intent into it ( i.e., it producing a new page by itself based on some action ). The test cases would chain the pages in the order it needs. I like to use PageFactory just for initialising the WebElement preferably from a json or a yaml file which contains the location strategy data. That way the code and the locators are decoupled. It also lets me build code generator for generating the page classes ( something that SeLion does but without page factory )

      Posted by Confusions Personified | September 6, 2016, 11:17 am
  2. I will look into more detail on how SeLion achieves it, Currently in my project, I use Generics where each respective test case would need to tell which Next page it expects.

    Posted by Sunny Sachdeva | September 6, 2016, 11:25 am
  3. if tests directly instantiate the next page, I would loose method chaining. I have seen jr guys sometimes either forgot to instantiate new page object or they do it multiple times in each @Test… whats your view on this.

    Posted by Sunny Sachdeva | September 6, 2016, 11:34 am
    • What sort of method chaining are you talking about ? If junior guys are forgetting to instantiate the next page they must be asked to not do so. It’s like saying my engineer forgets declaring local variable so I will have everything declared globally. It’s a coding practice thing. It’s not fair to have an automation framework try and fix these things. If your page instantiates the next page and if there can be variations in the flow very soon the next page instantiation mechanism becomes complex. Instead if it’s done by a test the test knows exactly what code path it is going to tread upon and so it can instantiate the right page and move forward.

      Posted by Confusions Personified | September 6, 2016, 11:42 am
  4. What’s the advantage of having something like public By searchTextBox = driver.findElementById(“id_search”); and calling this in the test versus Page factory model?

    Posted by prvn | September 6, 2016, 11:21 pm
    • Reducing verbosity of code. Being able to extend PageFactory concept to start reading locators from a json or yaml file etc. There are many benefits. By isolating elements and binding them to page classes changes to locators are confined to one place. So to use a page you basically instantiate the page and the initialization takes care of the rest of the things.

      Posted by Confusions Personified | September 6, 2016, 11:23 pm

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: