gillyb gillyb - 7 months ago 14
Javascript Question

How to correctly design page objects using theintern testing framework

I'm creating functional tests using theintern framework. I want to model my tests using "page objects" since I would like the code to be reusable.

In the original documentation, there is a very simplified sample that shows how to create a page object with one method called 'login'.

In this example, all the logic of this method is inside the method itself.

I would like to create a page object that represents a page a little more complex than a login page, and be able to reuse components inside the page for different actions.

Here's an example of what I want to do :

// in tests/support/pages/IndexPage.js
define(function (require) {
// the page object is created as a constructor
// so we can provide the remote Command object
// at runtime
function IndexPage(remote) {
this.remote = remote;
}

function enterUsername(username) {
return this.remote
.findById('login').click().type(username).end();
}
function enterPassword(pass) {
return this.remote
.findById('password').click().type(pass).end();
}

IndexPage.prototype = {
constructor: IndexPage,

// the login function accepts username and password
// and returns a promise that resolves to `true` on
// success or rejects with an error on failure
login: function (username, password) {
return this
.enterUsername(username)
.enterPassword(password)
.findById('loginButton')
.click()
.end()
// then, we verify the success of the action by
// looking for a login success marker on the page
.setFindTimeout(5000)
.findById('loginSuccess')
.then(function () {
// if it succeeds, resolve to `true`; otherwise
// allow the error from whichever previous
// operation failed to reject the final promise
return true;
});
},

// …additional page interaction tasks…
};

return IndexPage;
});


Notice how I created the
enterUsername
and
enterPassword
methods.

This is because I would like to reuse these methods in other tests of the same page object. The problem with this is that I can't chain these methods, it doesn't work.

The methods that can be chained all return the
Command
object, but when I chain my methods, they aren't defined on the
Command
method so the first method gets called (in my example this is
enterUsername
), but then the second fails, obviously since
enterPassword
isn't defined on the
Command
object.

I would like to know how I can model my Page Objects so I can reuse parts of the code within the page object, but still have a nice fluent syntax like this.

Thanks in advance :)

Answer

The simplest solution is to use your methods as then callback handlers, like:

function enterName(username) {
    return function () {
        return this.parent.findById('login').click().type(username);
    }
}

function enterPassword(password) {
    return function () {
        return this.parent.findById('password').click().type(pass).end();
    }
}

IndexPage.prototype = {
    constructor: IndexPage,

    login: function (username, password) {
        return this.remote
            .then(enterUsername(username))
            .then(enterPassword(password))
            .findById('loginButton')
            // ...
    }
}