Tips on Selenium PageObjects Design

About PageObjects

The "PageObjects" are introduced mainly to avoid duplicated codes in test and simplify the maintenance works as well. PageObject just models the WebUI under test and represent the state transition among UI(page) activities.

PageObjects and Page Factory Pattern are terms from Selenium documents. The concepts of PageObject and (w/ or w/o) Page Factory are also derived from general OOD. It could be regarded as a much simplified ORM or a simple DAO if we regard test cases as data, test scenario pattern as DB tables. So the methodology can apply to other tests besides web.

  • PageObject is a pattern of design
  • Selenium Java package provides a basic support on lazy init. However, it is still a wheel to build for specific test area.

A rough mapping could describe the concept easilier:

Traditional App Dev terms Web Automation Test Concepts
DB drivers like mysql-connector Web Drivers (take JSON call and get results from browser process)
DB Abstract as JDBC Selenium 2(it bridge the API to JSON calls)
ORM as peewee or SQLAlchemy to ensure DB agnostic PageObject provides agnostic on selectors and page details

Based on project experiences, PageObjects offers below three functions to automation:

Traditional App Dev terms Selenium PageObject Sections
DB Table Structure as create, alter operations Web Elements Encapsulation as lazy init, multiple selectors, and, retries
Select/View A snapshot of state Pages define the states of sceanrio
Transaction Procedure Functional methods provides interaction on pages, some of them triggers state transition, either return a new object of page or change the internal states

It can be utilized to design test suppport classes for database, CLI (especially tree view cli), interactive shell and desktop applications. The key is to design abstract objects to represent the internal relationships between states and adjunct methods to fetch/operate the POI(points of interests, I borrowed this concept from MapSys design).

Therefore, the PageObjects here formed a layer to separate test logics and supporting objects. When people are drafting tests, we don't need to switch to OOD part to spend time "thinking" about how to implement it and finally find out we forget the original motivation or target to do. This makes a concentration.

PageObjects Pattern or Bot Style Test?

it's not always a pattern that teams feel comfortable following.

Alternatively there is Bot Style in test and test automation. Following PageObjects style, one turns to draw a state transition map on paper or in mind, no matter it is automation or manual test. For Bot Stylte, the test is designed against action steps (user scenarios). It is not to see which style is "better" in a quick glance. Here I am trying to provide some tips on finding the correct technique within the project characters and constrains. Usualy no matter in manual or automation, Bot Style is the first technique to utilize at beginning to get more domain knowledge while doing sanity test. Bot Style focus on the encapsulation of interactive actions so it is consistent to the test description on test plan or user manual.

Moving on, some of the tests concepts could be abstracted with statemachine to form a pattern which is easy to extend and more robust to maintain. For example, an organization is maintaining a number of business stories composited with given common pages. To reduce time-to-market and accept new test cases without duplicated codes, PageObject might be considered an approperiate choice to study.

Let's say: If all the interactions are functional, stateless, elementary and reenterable, there is no much difference between Bot Style and PageObject Pattern.

An U(topia)aaS (named after A(I)aaS, B(ackend)aaS, F(unc)aaS) with which every services are interacting with others and human beings interfaces with read-only primative types of sequence or callable. Sequneces can be a list, string (another form of list), tuple, dict, json, or any other linear things while callable is a lambda, class with "__call__". Read-only types as list shall be copied before returning a modified version back.

Bot Style, as indicated by name, is to simulate the actions as what directly seen by users. A lot of organizations drafted Bot Style tests pretty well and maintain them in a high quality. It is not a must to go PageObject or state based test design but a selection on concept of abstract method to reflect the test purpose.

  • A simple way of Bot Style test case is just translate every single test step to a method and assemble the steps into a class of one test case. It is useful under stressing constrains and lower down the training cost. The test review is also more smoothy to participate since the most significant checkpoints during review are domain knowledge then.

  • Some test are not easy to abstract as PageObjects, e.g. front-end test.

  • Sometimes, the ROI is not in balance to refactor steps into reusable PageObjects, e.g. the business scenarios are stable. If we are testing an desktop application and the GUI elements, logs and operation sequences are quite stable which are not changing among versions, Bot Style is good to create and maintain.

Page Factory from a Technical Point of View

Above section compares Bot Style and PageObject mainly from process and organization view. This section describes the detail technical benefits with Page Factory to implement PageObject.

Page Factory offers proxies to access the WebElements on page. Therefore, this solution has below advantages:

  • When PageFactory is initialized with PageFactory.initElements(), the proxies are configured against the elements representing real page elements but they are not searched at the time being. The developers won't see NoSuchElementException.

  • Each access to the element via the proxies will trigger the search and bind again, so developers won't see StaleElementException.

    • To avoid the searching every time, annotation @CacheLookup on element. It will just do the search once and keep the reference.
    • Direct call to initElements() won't be impacted by this annotation.

Further more, if the PageObject actually reflects multiple but similar states, developers can combine the elememts definition together and keep the lazy initialization. Once the conditions are satisfied use an arch (method call) toward ame state and just start to access the elements right away.

General PageObject Structure

Here is the general PageObject code template including 4 major parts for general purpose. If there is an arc from current page to transition to another page, even it is the same PageObject type, the actioner (method) shall return the new page object. The caller from test code, e.g. JUnit case, shall detect exceptions.

The PageBase in the following sample provides an abstract of common properties and behaviors as:

  • URL check against an RegExp.
  • Page title check.
  • Cookie validation;
  • Common test supporting:
    • Screenshot;
    • Color contrast ratio;
    • Timing and waiting methods.
  • Common Data generator or loading supports.
  • Special XML, Path, or File Format validator.

As mentioned above, it is a tradeoff by the test/SDET whether to implement the abstract hierarchy.

This note is about placing a hook to take screenshot on failure. Based on the practice study, it is convenient to add such a hook when @RunWith(Cucumber.class) as the runner. However, if it is the default runner, which could be planted by a customized @Rule extends TestWatcher. This way, the hook needs have an access to the screenshot method if it is carried by PageBase Object. It could be a registration slot with static properties for example.

Here we refer to a simplified Page Object Model design in which most fundamental functionalities are just grouped to one layer of PageBase. However, for experienced readers and in best industry practice, it shall be considered whether to implement Page Object with multiple layers. For example, there shall be a bottom layer, e.g. IDriver, to encapsule WebDriver concerned operations as clear-and-input, send-key or javascript based input, default FluentWait or Explicit Wait, move-to-and-click/Keys.Enter, etc. These are all frequently seen common actions existing in most of testing projects. Above this layer, there are business related structures as extracting work-flow state from CSS selectors, Web Component Linkage based on work-flow assumptions, until a high abstract on business logics which are well mapped to encapsulated in Page Object details, transparent to test case designers.

A new post is under drafting to summarize experiences and tips on hierarchy and reusable structure of practical page object. In facts, it will be determined by cost benefit balance on how abstract the page object shall move toward.

package xxx.xxx.pageObjects
//import ...

//or public XxxxPage implements IPage
//if behaviors abstracted as Intf.
public XxxxPage extends PageBase{
////////////////////////////////
//***Part 1: WebDriver and remaining Constants ***
private WebDriver driver = null;
static String someRegExConst = "$my_url_regex";

////////////////////////////////
//***Part 2: WebElements ***
@FindBy(how = How.CSS, using = "input#class")
WebElement inputName;

@FindBy(how = How.CSS, using = "body > p:nth-child(2n+1)")
List<WebElement> pSomeParagraphList;

////////////////////////////////
//***Part 3: Constructors ***
public XxxxPage(WebDriver dr){
this.driver = dr;

//Transient state(s) handling if compliant to test purpose
//E.g. an authentication transient page handling
authenticate(driver, $Some_Class_Variables)

//Title and/or Url check, raise RuntimeException if fails;
//...(See notes below)

PageFactory.initElements(driver, this);

//Other checks after PageFactory init.
//Not recommended to add much tests here.
//If map, list operations are processed every time,
//the test duration will extend.
//However, stable tests do not need to play detail checks
//On every construction step.
//Tests shall be separate.
}

////////////////////////////////
//***Part 4: Web UI actions ***
//Actions including getters, setters, submission, clicker, mouseMover et.
public void setInputXxxYxx(String name) throws WrongPageException{
//Web Driver actions.
}

//Some Getter/Setters return an new instance of same or other PageObject
//The type here represnets the arc between Web UI(page)s
public ZzzPageObject submitRegisterForm(String name) throws WrongPageException{
//Setters/getters/methods calls and operations
return new ZzzPageObject(driver, someOtherParams)
}
}
  • Note-1: It is up to the test purpose whether to separate a specific state to handle transient state like authentication. In most cases, the constructor could verify the transient state via a callable design as well. However, if there is deeper abstract (for reuse or polymorphism afterwards) design, a separate PageObject or group of PageObjects shall be introduced.

  • Note-2: The potential problem is to throw exception in constructor method. One thing to take care is to make sure unmanaged resources are gracefully handled if exception thrown in subclass or base class. Another issue is related to security.Ref to Secure Coding Guideline for Java SE, Oracle, Guideline-7_3 in Ch-7: Object construction, parially constructed objects lead to security vulnerability. Besides, the @After and @AfterClass or @After in Cucumber shall be checked to avoid any side effects if there is state change while throwing execption in an incomplete procedure.

  • Note-3: In usual functional test, if the authentication is not the purpose to test against, authentication could be regarded as an arc from current state to same state. This arc of state-transition could be handled in one method called by the constructor. However, it depends on how complicated the authentication scenario is. If it branches to different situation with sophisticated processing steps, it shall be better to explicitly design a state of AuthenticationPageObject to maintain the logic and which can be a base for further security cases to grow from.

With above PageObject template, a simplified @Before hook could be designed following below template.

@Before
public void setUp(){
// Protective check.
if (driver != null){
//Quit driver (see notes below)
//DriverFactory.quitDriver(driver);
}

//Init driver, browser type, timeout, et. parameters
driver = DriverFactory.getDriver();
driver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS);
driver.manage().window().maximize();

// Checking ignore list.
String testName = name.getMethodName();
if (testListByPassBeforeHook.contains(testName)){
ColorPrint.println_blue("Ignore PageObj NewPost setup for test: " + testName);
return;
}else {
//From site entry, start from an object of New Post Object
//Here getNewPostPage will return a PageObject
//...representing the drafting post page
//...and it will implicitly walk through
//...a transient authentication page state
//As mentioned above, this transient state is implicit
//...which is verified within a method, not an object.
newPostPage = new PageObject(driver).getNewPostPage();
}
}

A simplified test method is convenient to expand given above PageObject and hook example.

public CommentPage submitAndVerifyNewPost(String title, String body, String expectedTitle, String expectedBody){
//A state transition happens here
CommentPage commentPage = newPostPage.publishNewPost(title, body);
Assert.assertEquals(expectedTitle, commentPage.getTitle());
Assert.assertEquals(expectedBody, commentPage.getBody());
return commentPage;
}

@Test
public void verifyPublishNewPost(){
//Here a fuzzer shall be considered
String title = "One time used title ";
String body = "One time used body ";
//...prepare expected values in cases of trim(), cap, et.

submitAndVerifyNewPost(title, body, expectedTitle, expectedBody);

//Check-2: Expect the new title when it turns back to Posts (view) page
PostsPage postsPage = new PostsPage(driver);
List<String> titles = postsPage.getPostTitles();
Assert.assertTrue("Recently added title should display in posts page", titles.contains(title));
}

Page Factory

Refer to Page Factory Github, Page Factory simplifies the work to instantiate the WebElement or List so PageObject could be lazily initialized and keep a clean focus of codes by reducing verbosity through dynamic proxy.

The sample of Page Factory Pattern could be found in "Part-2" of Page Object code example above. There are couple of tips for beginners.

  • As a strong typing language, here WebElement is the type as returned by lazing evaluation. For example, if a special operation with HTML select element to make one option selected, you can cast it to Select type.

  • If the return value is a list, use List as the sample code shows.

  • @FindBys vs @FindByAll: The annotation @FindBys({ @FindBy(contion1), @FindBy(condition2)}) will return an intersection of condition1 and condition2 while @FindByAll returns the union set of elements satisfy either condition.

@Findbys({
@FindBy(how = How.CSS, using = "div.A"),
@FindBy(how = How.CSS, using = "div.B")
})
public List<WebElement> BinAList;

Above example will return div elements which has className "B" and(from) className "A".

State Transition

Theoretically Selenium is a tool for UI testing or Web testing, which focus on user scenarios instead of functions. Functional test is still covered in API level tests. Here is a chart to describe where Selenium is in the Test Pyramid. Link

Selenium is an excellent tool to support the test on user journeys acrossing views or pages. Here states represent the different views which the end users visit. A user scenario typically consists a sequence of views or pages towards a complete, incomplete, successful or failed business target. The footprints include a sequence of interactions.

If the new view after a given action can still be interpreted by current PageObject to get an object level description about the set of interested points to test, we can either continuously to use the existing page object with a fresh on specific elements, or, create a new object with the same PageObject class. The construction phase will map element members to a set of interested points.

"POM" here means page object model.

Otherwise, the action method generally returns a new object which in new PageObject class type to represent a new HTML page view in browser.

Here the partial refresh could be considered as a kind of arc pointing to same state. Each constructor call of PageObject classes is regarded as an arc from the action method class to the new PageObject class.

For example, if an user adds a comment piece to current blog post page, the page refreshs and shows the commnet right away. It could be tested with either ways:

  • Refresh comment list corresponding List with a partial refreshment call. This is a state transition from the comment view state to the same state.

  • The comment adding action method could return a new instance of same PageObject class. This new instance holds the new comment WebElement thru the construction call.

In summary, a partial refreshment on certain WebElement(s) is regarded as a state transition to same state, while any call to a new instance of PageObject class, same class or another class, indicates a state transition from existing object class to the return value class.

More about state and state transition in testing could be found at State based Testing.

Smart Pages and Page Theme

The above PageBase and PageObjects are straight through samples for simple scenarios. For sophesticated E2E (end-to-end) acceptance testing, sometimes the applicaiton engine is more complicated that we can hardly predict the targeted page from current state.

Based on practices and technical discussion with buddies recently, two techniques could be introduced to support such "smart" acceptance test.

Smart Pages: Smart pages do not define which exact page the application engine will run to next step but just offer loose check points as excluding list and accepted list, or, nothing. The transitioned page will be recognized smartly by PageObject.

For example, if P1 can lead to P2 or P3 and both are accepted scenarios, PageObject will detect which page it is on after execution P1. It could be from title, URL, or, a CSS selector condition. When it detects current page is P2 and P2 is accepted, it will load the page definition from prepared data files to offer a map of wrappered elemenets with actions as input, text extraction, regular expression check, variable get/set, mouse events, et.

Page Theme: Themes are predefined states for current page. For example, if current Page is an interactive page, the PageObject will first check theme before searching on element map. Which could be a chained map with precedences.

Fuzzing Test (Data Factory)

Another approach to "Smart" Test is fuzzing test. The PageObject is created with a set of fuzzing rules instead of a data set loaded from persistence. (TBC)

Data Fuzzing

OWASP JBroFuzz is a popular Fuzzer lib to support fuzzing test. It offers various fuzzer DB definition and customized features to support data fuzzing.

Sequence Fuzzing

With JUnit, the execution sequence is able to customize with @Rule.

Further Topics

Classic PageObject spends too much efforts on disbaling the readability (encapslation) the action implementations to archeive the code reusing. However, most of the works could be simplified to a more friendly facade with Java features. selenide is worthy a try to make your PageObject design works done in a graceful and faster way.

References


[Updated Aug13, 2017]: Update with smart pages practice tips.

[Updated Mar30]: Add the concept mapping between DB App Dev and PageObject testing.

[Updated Feb07]: Reword state transition section and publish it.

[Updated Jan23]: labelled more points to complete this note.

[EOF]