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!