Page object, command asynchrones dans Nightwatch

Après l’article de la semaine dernière, je continue mon exploration Nightwatch avec les Pages Object, et la réalisation de command.

Reprenons là où nous en étions avec le projet nightwatch-boilerplate que vous pouvez retrouver déjà complété avec les sujets de cet article sur la branche page-object-assertion-command.
Commençons par ajouter à la racine du fichier de configuration les chemins vers les dossiers (qu’on aura créé au préalable) pour que Nightwatch puisse retrouver chaque partie :

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

Et, enchainons directement sur les Page object

Page Object

Pour rappel, le pattern Page Object définie une abstraction permettant d’associer à une partie de la page un objet qui va piloter cette dernière. Cela permet entre autre chose de ne pas répéter les sélecteurs CSS et rendre les tests beaucoup plus lisibles en exposant des méthodes métier (Ex: browser.page.google().search()) au lieu de méthodes techniques (Ex : […]browser.click(‘button[name=btnG]’)[…]).
En pratique, nous allons créer un fichier nw/pages/google.js décrivant le page object pour la page de recherche Google :

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;
        }
    }]
};

La première partie ‘element‘, crée des alias associés à des sélecteurs CSS, la deuxième partie ‘command‘ permet de créer les méthodes métier utilisées après dans nos tests. Vous remarquerez l’utilisation des alias (Ex: ‘@searchInput’) dans les méthodes afin de ne pas répéter les sélecteurs et donc limiter les risques d’erreurs.

Tips : Je préfère pour ma part retourner ‘this.api’ dans les commands, car ‘this.api’ correspond au paramètre ‘browser’ dans les tests. Et donc en le retournant on peut chaîner les appels de différent page object.

Le test nw/tests/researchOnGoogle.test.js ressemble désormais à ça :

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();
    }
};

C’est plus claire, non ? Grâce à notre fichier google.js dans le dossier des page object, on a accès à une méthode google permettant d’utiliser le page object du même nom et d’effectuer des opérations sur la page de façon plus lisible qu’avant (cf: ancienne version)

Command

Dans cette partie, je vais vous expliquer comment construire des commands plus complexes en utilisant le prototype. Ce format de command permet de gérer entre autre chose les asynchonismes. Si votre besoin est relativement simple et synchrone, restez sur le format simple visible sur la doc.
A l’image d’un page object, pour créer une command vous devez créer un fichier dans le répertoire prévu à cet effet. Créons donc le fichier nw/commands/longCommand.js avec ce contenue :

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;

Une command est donc dans Nightwatch un prototype héritant du EventEmitter node et qui contient une méthode ‘command‘.
Pour signaler à Nightwatch que cette commande est terminée, on doit émettre l’événement ‘complete‘, et pour qu’elle soit chainable on retourne ‘this‘. Une fois intégré dans le test on se retrouve avec cette version finale :

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

Avec cet article, couplé au précédent sur le même sujet, vous êtes désormais prêt pour rédiger des tests d’acceptance efficaces, lisibles et faciles à maintenir tout en minimisant le temps d’execution.
Mathieu Breton CTO chez 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″]