gillyb
gillyb

Reputation: 8910

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 :)

Upvotes: 2

Views: 478

Answers (1)

jason0x43
jason0x43

Reputation: 3363

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')
            // ...
    }
}

Upvotes: 3

Related Questions