WebDriverJS & Mocha Part 4: More Page Objects & Config

This post continues on from my last post about writing page objects in WebDriverJS & Mocha.

At this stage our tests look pretty good…

test.describe('Ralph Says', function() {
    this.timeout(mochaTimeOut);

    test.it('shows a quote container', function() {
        var ralphSaysPage = new RalphSaysPage(driver);
        ralphSaysPage.visit();
        ralphSaysPage.quoteContainerPresent().then(function(present) {
            assert.equal(present, true, "Quote container not displayed");
        });
    });

    test.it('shows a non-empty quote', function() {
        var ralphSaysPage = new RalphSaysPage(driver);
        ralphSaysPage.visit();
        ralphSaysPage.quoteTextDisplayed().then(function(text) {
            assert.notEqual(text, '', 'Quote is empty');
        });
    });
});

…but I think they could be better.

We are initializing our page class, then calling visit straight away. I prefer a pattern of making the constructor able to do a visit, and also checking to make sure we’re at the right place, to avoid unexpected errors.

We can write how we want it to look first:

test.describe('Ralph Says', function() {
    this.timeout(mochaTimeOut);

    test.it('shows a quote container', function() {
        var ralphSaysPage = new RalphSaysPage(driver, true);
        ralphSaysPage.quoteContainerPresent().then(function(present) {
            assert.equal(present, true, "Quote container not displayed");
        });
    });

    test.it('shows a non-empty quote', function() {
        var ralphSaysPage = new RalphSaysPage(driver, true);
        ralphSaysPage.quoteTextDisplayed().then(function(text) {
            assert.notEqual(text, '', 'Quote is empty');
        });
    });
});

The constructor now takes a second parameter which indicates whether we want to visit the page as we create it.

So make our page model accept this by moving our visit code into the constructor:

var webdriver = require('selenium-webdriver');
var until = webdriver.until;

RalphSaysPage = function RalphSaysPage(driver, visit) {
    this.driver = driver;
    this.url = 'http://ralphsays.github.io';
    this.quoteSelector = webdriver.By.id('quote');
    if (visit === true) {
        this.driver.get(this.url);
    }
    this.driver.wait(until.elementLocated(this.quoteSelector), 2000);
};

RalphSaysPage.prototype.quoteContainerPresent = function() {
    var d = webdriver.promise.defer();
    this.driver.isElementPresent(this.quoteSelector).then(function(present) {
        d.fulfill(present);
	});
    return d.promise;
};

RalphSaysPage.prototype.quoteTextDisplayed = function() {
    var d = webdriver.promise.defer();
    this.driver.findElement(this.quoteSelector).getText().then(function(text) {
        d.fulfill(text);
    });
    return d.promise;
};

module.exports = RalphSaysPage;

You can also see that I have added an explicit wait to the page on line 11, it will wait up to 2000ms for that element to be present. By adding to our constructor we can remove the visit method.

In doing this, it has highlighted there is a bit of ‘config’ sneaking into our code. Three things stand out to me: our URL (which could change for different environments or locales, the explicit wait timeout, and also a mocha timeout in our mocha specs.

Luckily configuration management is very easy in JavaScript using the config library. We can install it from the command line (or add it to our package.json file).

npm install config

All we need to do is add a “config” directory to our project, and we’ll just add a single file called ‘default.json’ with our config values (you can add more like development.json, test.json etc. to configure different environments).

This is our default.json config file content:

{
  "url": "http://ralphsays.github.io",
  "explicitWaitMS": 2000,
  "mochaTimeoutMS": 30000
}

Now all we need to do is reference the config and the rest is magic.

var webdriver = require('selenium-webdriver');
var until = webdriver.until;
var config = require('config');

RalphSaysPage = function RalphSaysPage(driver, visit) {
    this.driver = driver;
    this.url = config.get('url');
    this.explicitWaitMS = config.get('explicitWaitMS');
    this.quoteSelector = webdriver.By.id('quote');
    if (visit === true) {
        this.driver.get(this.url);
    }
    this.driver.wait(until.elementLocated(this.quoteSelector), this.explicitWaitMS);
};

RalphSaysPage.prototype.quoteContainerPresent = function() {
    var d = webdriver.promise.defer();
    this.driver.isElementPresent(this.quoteSelector).then(function(present) {
        d.fulfill(present);
    });
    return d.promise;
};

RalphSaysPage.prototype.quoteTextDisplayed = function() {
    var d = webdriver.promise.defer();
    this.driver.findElement(this.quoteSelector).getText().then(function(text) {
        d.fulfill(text);
    });
    return d.promise;
};

module.exports = RalphSaysPage;

We can do the same in our test specs:

var assert = require('assert');
var webdriver = require('selenium-webdriver');
var test = require('selenium-webdriver/testing');
var config = require('config');
var RalphSaysPage = require('../lib/ralph-says-page.js');
var driver;

const mochaTimeoutMS = config.get('mochaTimeoutMS');

test.before(function() {
    this.timeout(mochaTimeoutMS);
    driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.chrome()).build();
});

test.describe('Ralph Says', function() {
    this.timeout(mochaTimeoutMS);

    test.it('shows a quote container', function() {
        var ralphSaysPage = new RalphSaysPage(driver, true);
        ralphSaysPage.quoteContainerPresent().then(function(present) {
            assert.equal(present, true, "Quote container not displayed");
        });
    });

    test.it('shows a non-empty quote', function() {
        var ralphSaysPage = new RalphSaysPage(driver, true);
        ralphSaysPage.quoteTextDisplayed().then(function(text) {
            assert.notEqual(text, '', 'Quote is empty');
        });
    });
});

test.afterEach(function() {
    driver.manage().deleteAllCookies();
});

test.after(function() {
    driver.quit();
});

There you have it. We made our page objects better to accept a visit and wait for an expected element, and got rid of the config from our tests so they’re more maintainable.

All the code is available on Github here, and running on CircleCI here.

Author: Alister Scott

Alister is an Excellence Wrangler for Automattic.

2 thoughts on “WebDriverJS & Mocha Part 4: More Page Objects & Config”

  1. Having been doing mocha/selenium frontend test for a while, I still find this series quite useful and very clear. Hope you could keep updating with your thoughts and findings. I am sure for a website like wordpress, there will be plenty of interesting techniques to be added to your tests.

    Looking at the current tests, have you tried out chai/chai-webdriver for the assertion part ?

    Like

Comments are closed.