Page object, asynchronous command in Nightwatch

After the last post, I continued my Nightwatch exploration with the Page Objects and the command production.

Get back where we left the boilerplate project nightwatch-boilerplate which can be found already fulfilled with the updates of this article on the branch page-object-assertion-command.
Start by adding in the configuration’s root file the paths to the folders (we have previously created) so that Nightwatch can find each part:

[...]
"page_objects_path": "nw/pages",
"custom_commands_path" : ["nw/commands"],
"custom_assertions_path" : ["nw/assertions"],
[...]

Then, carry on the Page Objects

Page Object

First of all, reminder: the page object pattern defines an abstraction which allow to attach to a part of a page, an object which will be responsible for its driving. This approach has many avantages. Among many things, this allows you to write your CSS selectors only once and then avoid code repetitions. Also, tests become a lot more readable by exposing some business methods (Ex: browser.page.google().search) instead of technical methods (Ex : […]browser.click(‘button[name=btnG]’)[…]).
In practice, we will create a file nw/pages/google.js describing the page object responsible of the Google research page :

module.exports = {
    elements: {
        searchInput: 'input[type=text]',
        searchBtn: 'button[name=btnG]'
    },
    commands: [{
        fillInSearchInput () {
            this
                .waitForElementVisible('body')
                .setValue('@searchInput', this.api.globals.searchTerm);
            return this.api;
        },
        submit () {
            this
                .waitForElementVisible('@searchBtn')
                .click('@searchBtn')
                .api.pause(1000);
            return this.api;
        }
    }]
};

The first part ‘element’, creates aliases bounded to CSS selectors, the second part ‘command’ allows to create the business methods used in our tests. Thanks to the usage of aliases (Ex: ‘@searchInput’) in the methods, we do not repeat the CSS selectors and avoid the error threat.

Tips : Personally, I usually use return ‘this.api’ in the commands because ‘this.api’ matches the  ‘browser’ parameter in the tests. By returning it, we can chain calls of different page object.

Now, the test nw/tests/researchOnGoogle.test.js looks like that :

module.exports = {
    'Search on google': (browser) => {
        browser
            .init()
            .page.google().fillInSearchInput()
            .page.google().submit()
            .assert.containsText('#main', browser.globals.movieName)
            .end();
    },
    after: (browser) => {
        browser.end();
    }
};

It is much readable, Is it not ? (cf: old version)
Thanks to our google.js file in the page objects directory, we can access to a method google which gives access to a page object with the same name.

Command

In this part, I will explain you how to build more complex commands with use of the prototype. This command format allows to handle, among other things, the asynchronism. If our need is relatively simple and synchrone, stay on the simple format shown in the doc.
Like a page object, to create a command you have to create a file in the folder dedicated of this usage. Let’s create this file nw/commands/longCommand.js with this content :

const util = require('util');
const events = require('events');
const LONG_TIME = 2000;
function Cmd() {
    events.EventEmitter.call(this);
}
util.inherits(Cmd, events.EventEmitter);
Cmd.prototype.command = function () {
    setTimeout(() => this.emit('complete'), LONG_TIME);
    return this;
};
module.exports = Cmd;

So, a command in Nightwatch is a prototype inheriting of EventEmitter and which contains a method ‘command‘.
To signal to Nightwatch that this command is over, we must dispatch the ‘complete‘ event, and then to be chainable we return ‘this‘. After integrate it in the test we obtain this final version :

module.exports = {
    'Search on google': (browser) => {
        browser
            .init()
            .longCommand()
            .page.google().fillInSearchInput()
            .page.google().submit()
            .assert.containsText('#main', browser.globals.movieName)
            .end();
    },
    after: (browser) => {
        browser.end();
    }
};

Conclusion

This article, with the previous one covering the same topic, you are now ready to write efficient, readable, and easy to maintain acceptable tests, and reduce the execution time in the same time.
Mathieu Breton CTO at JS-Republic
[separator type=”” size=”” icon=”star”] [actionbox color=”default” title=”” description=”JS-REPUBLIC est une société de services spécialisée dans le développement JavaScript. Nous sommes centre de formation agréé. Retrouvez toutes nos formations techniques sur notre site partenaire dédié au Training” btn_label=”Nos formations” btn_link=”http://training.ux-republic.com” btn_color=”primary” btn_size=”big” btn_icon=”star” btn_external=”1″]