Selenium: Revisiting The Page Objects
By: Jatin Sethi
Tags: Selenium, Pagefactory, Page Object Model, Defaultelementlocator

Let’s list these components as they shall appear in the hierarchy of components where Home Page is (Level 0).

Main Navigation (Level 1: Home Page contains Main Navigation) Footer (Level 1: Home Page contains Footer) Sidebar (Level 1: Home Page contains Sidebar) Feed (Level 1: Home Page contains Feed) Promo (Level 2: Feed (4) contains Promo) Feed Item (Level 2: Feed (4) contains Feed Item) [Repeats Itself] Sidebar Item (Level 2: Sidebar (4) contains Sidebar Item) [Repeats Itself]

Given the above components, we need to create 7 unique objects to represent the home page. We are creating unique components so that they can be plugged-in any page without duplicating the code. For example, almost all pages are expected to contain the Main Navigation.

Section 2: Let’s Model This Page Using Page Factories

We shall use the inside-out approach to model these objects and use 2 kinds of factories:

Page Factory [Standard factory used to define the home page. We try and find elements in the DOM i.e. driver.findElement(…) or driver.findElements(…) ]

[Standard factory used to define the home page. We try and find elements in the DOM i.e. or ] Default Element Locator Factory (Used along with the Page Factory to define the components. We try and find elements in a given WebElement i.e. element.findElement(…) or element.findElements(…)]

Let’s model the Home Page with a complex component Feed using pseudo-code and see the use of these factories. As stated we shall start inside-out.

Model Level 2 Components | Promo & FeedItem

These are simple components because they fall last in the hierarchy. Here we shall learn to use the Default Element Locator Factory along with Page Factory.

Component 1/2: Promo

PROMO COMPONENT

public class PromoComponent {



@FindBy(...)

WebElement promoAnchor; public PromoComponent(WebElement promo_container) { DefaultElementLocatorFactory container = new

DefaultElementLocatorFactory( promo_container );

PageFactory.initElements(container, this);

} public String getPromoAttribute(String attribute) {

return promoAnchor.getAttribute(attribute); //Getters On The Promo }

Understanding the code:

PROMO CONTAINER DIV CONTAINS 1 ANCHOR TAG

promo_container is the div container element (as highlighted in the image above) that contains a single anchor child (element). This WebElement is passed to the Component constructor. We then create an instance of DefaultElementLocatorFactory and pass the promo_container as a parameter to it. We initialize the PageFactory with the instance of DefaultElementLocatorFactory. This will make sure that Selenium searches for the anchor element in the promo_container.

It may seem useless to create a component at this moment but in case more child elements are added to it in the future, we shall be in a much better place to incorporate the changes. Especially when this component may be used in multiple places. This component is specifically chosen for this example because it can be easily converted to something with 4 child elements — a heading, a description, an action button, and a background image.

Component 2/2: FeedItem

FEED ITEM FROM THE LIST OF FEEDS

public class FeedItemComponent {



@FindBy(...)

private WebElement feedItem_description; //Other elements of component FeedItem// public PromoComponent(WebElement feedItem_container) {

DefaultElementLocatorFactory container = new

DefaultElementLocatorFactory( feedItem_container );

PageFactory.initElements(container, this);

} public String getFeedDescription() {

...

return feedItem_description;

} public String getFeedAuthor() {

...

return feedItem_author;

} //More Getters On The FeedItem Content// }

Model Level 1 Component | Feed

This one is a complex component that will contain the Level 2 components. In this section, we shall learn how to make a call to the Level 2 components.

Component 1/1 | Feed

public class FeedComponent { @FindBy(...)

WebElement promo_container; @FindBy(...)

List<WebElement> feedItem_containers; public FeedComponent(WebElement feedComponent_container) {

//Same Thing - This is also a component//

DefaultElementLocatorFactory container ... ;

PageFactory.initElements(container, this);

}





public FeedItemComponent getFeedItem(String description) {

for(List<WebElement> item : feedItem_containers) {

FeedItemComponent feed = new FeedItemComponent(item);

if (feed.getFeedDescription().equals(description)) {

return feed;

}

}

return null;

}



public PromoComponent getPromo() {

return new PromoComponent(promo_container);

} //More Need Based Getters }

Understanding The Code:

This component (Feed Component)has no representation of its own but is presented by a collection of components at Level 2 i.e. one Promo Component and a list of Feed Item Components. This is what our @FindBy is focused on. Next, we initialize the Feed Component in the same way we initialized the simple components using the Default Element Locator Factory. This component performs no role in particular but is only responsible for delivering the intended sub-component which we may want to work with. For example, we can get the Promo Component or search for the Feed Item Component from a list of Feed Item Components using the function getFeedItem(). This function tries to search for the right feed item by scanning their text description and returns feed item.

Model The Page Object | Home Page At Level 0

Lastly, we shall model the Home Page. Treat this page object as a complex object with the only exception that we shall not use the Default Element Locator Factory. This is because it is Level 0 and we want to search for the simple or complex elements in the DOM. So we pass the instance of driver to the page factory.

public class HomePage { @FindBy(...)

WebElement feedComponent_container; public HomePage(WebDriver driver) {

PageFactory.initElements(driver, this);

} //Get Feed Component Container

public FeedContainer getFeedContainer() {

return new FeedComponent(feedComponent_container);

}

Section 3: Usage