When it comes to Page Objects and their maintenance, there is a general topic worth exploring. Recently, I came across a Page Object Design Pattern technique that caught my attention and made perfect sense to me.
Instead of creating child page objects within the parent page objects, a better approach would be to embrace JavaScript's Prototypal Inheritance. This method offers numerous benefits, but let me first demonstrate how we can implement it:
Let's begin by defining 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 always export an instance of a page object rather than creating it in the test. Since we are working on end-to-end tests, we view the page as a stateless entity, similar to each stateless http request.
Now, let's move on to creating our child page objects ChildPage
, utilizing the 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 parent page's open method
} },
submit: { value: function() {
this.form.click();
} }
});
module.exports = ChildPage
Locators are defined within getter functions; these functions are evaluated when you access the property, not during object generation. This ensures that you request the element before performing any action on it.
The Object.create
method returns an instance of the page so we can immediately start using it.
// 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();
});
In the above code snippet, notice how we only require the child page object and utilize/override parent page objects in our specs. The following are the advantages of this design pattern:
I stumbled upon this design pattern in webdriverIO's developer guide, where most of the techniques I explained are derived from. Feel free to explore it further and share your thoughts!