Back

Explore Courses Blog Tutorials Interview Questions
+1 vote
1 view
in DevOps and Agile by (19.7k points)

The Question:

What is the canonical way to define nested Page Objects in Protractor?

Use Case:

We have a complicated page that consists of multiple parts: a filter panel, a grid, a summary part, a control panel on the side. Putting all the element and method definitions into a single file and a single page object does not work and scale - it is becoming a mess which is difficult to maintain.

1 Answer

0 votes
by (62.9k points)

This is more of a general topic when it comes to Page Objects and how to maintain them. Sometime back I stumbled upon one of the Page Object Design Pattern techniques which I liked and made a lot of sense to me.

Rather than instantiating child page objects within the parent page objects, it would be ideal to follow javascript's Prototypal Inheritance concept. This has quite a number of benefits but first, let me show how we can achieve it:

First, we would create our parent page object ParentPage:

/ parentPage.js

var ParentPage = function () {

// defining common elements

this.someElement = element(by.id("someid"));

// defining common methods

ParentPage.prototype.open = function (path) {

browser.get('/' + path)

}

}

module.exports = new ParentPage();  //export instance of this parent page object

We will always export an instance of a page object and never create that instance in the test. Since we are writing end to end tests we always see the page as a stateless construct the same way as each http request is a stateless construct.

Now let's create our child page objects ChildPage, we would use Object.create method to inherit the prototype of our parent page:

//childPage.js

var ParentPage = require('./parentPage')

var ChildPage = Object.create(ParentPage, {

/**

 * define elements

 */

username: { get: function () { return element(by.css('#username')); } },

password: { get: function () { return element(by.css('#password')); } },

form:     { get: function () { return element(by.css('#login')); } },

/**

 * define or overwrite parent page methods

 */

open: { value: function() {

    ParentPage.open.call(this, 'login'); // overriding the parent page's open() method

} },

submit: { value: function() {

    this.form.click();

} }

});

module.exports = ChildPage

We are defining locators in getter functions. These functions get evaluated when you actually access the property and not when you generate the object. With that, you always request the element before you do an action on that.

The Object.create method returns an instance of that page thus we can begin using it right away.

/ childPage.spec.js

var ChildPage = require('../pageobjects/childPage');

describe('login form', function () {

it('test user login', function () {

    ChildPage.open();

    ChildPage.username.sendKeys('foo');

    ChildPage.password.sendKeys('bar');

    ChildPage.submit();

});

Notice above that we are solely requiring the child page object and utilizing/overriding parent page objects in our specs. Following are the benefits of this design pattern:

  • removes tight coupling between parent and child page objects

  • promotes inheritance between page objects

  • lazy loading of elements

  • encapsulation of methods and action

  • cleaner & much easier to understand the elements relationship instead of parentPage.childPage.someElement.click();

I found this design pattern in webdriverIO's developer guide, most of the above methods I explained are taken from that guide. Feel free to explore it and let me know your thoughts!

Browse Categories

...