Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protractor Page Objects Inheritance

Given i'm building my angularjs protractor e2e tesing suite leveraging the page objects pattern.

And i separate page object code in different files as much as reasonable.

  1. What would be a good approach to enable page objects inheritance? javascript classic inheritance? Object.create() based inheritance? Other?

  2. Should i keep expectations within the page object? Or favor Martin Fowler optinion by moving them to an assertion library? in which case how exactly would that look like in this javascript-nodejs technology stack?

I've prepared a live jsfiddle playground here so you can try your improvements on.

Or simply paste code within the answer, i'll paste the jsfiddle content below for clarity:

loginPage.js

"use strict";

// A Page Object is a Singleton, so no need to constructors or classic js inheritance,
// please tell me if I'm wrong or what's the utility of creating a (new LoginPage())
// every time a spec need to use this login page.
var loginPage = {
    // Page Object Elements
    userElm: $('.user.loginPage'),

    // Page Object Assertions
    // Martin Fowler [doesn't favor](http://martinfowler.com/bliki/PageObject.html)
    // assertions in page objects, I'm open to suggestions on how to move 
    // the assertions away from the Page Object and see how an assertion library 
    // could like like in protractor.
    assertInputsDisplayed: function() {
        return ('Assertion: this.userElm: '+this.userElm);
    },

    // Page Object Actions
    get: function () {
        return ('navigating to LoginPage with userElm: '+this.userElm);
    }
};

module.exports.loginPage = loginPage;

loginDialog.js

"use strict";

var loginPage = require('./loginPage.js').loginPage;
var helpers = require('./helpers.js');

// Inherit properties from another Page Object
var loginDialog = helpers.extend({}, Object.create(loginPage), {
    // Page Object Elements
    userElm: $('.user.loginDialog'),

    // Page Object Actions
    get: function () {
        return ('navigating to LoginDialog with userElm: '+this.userElm);
    },

    logout: function () {
        return ('logging out of Dialog. user was: '+this.userElm);
    }
});

module.exports.loginDialog = loginDialog;

helpers.js

"use strict";

// some helper to avoid adding an external dependency for now
var extend = function(target) {
    var sources = [].slice.call(arguments, 1);
    sources.forEach(function (source) {
        for (var prop in source) {
            target[prop] = source[prop];
        }
    });
    return target;
};

usage.js

"use strict";

// Mock $() for easy unit testing this on nodejs REPL
global.$ = function(args) { return ('$BUILT '+args); };

var loginPage   = require('./loginPage.js').loginPage;
var loginDialog = require('./loginDialog.js').loginDialog;

console.log(loginPage.userElm);    //=> '$BUILT .user.loginPage'
console.log(loginDialog.userElm);  //=> '$BUILT .user.loginDialog'
console.log(loginPage.get());      //=> 'navigating to LoginPage with userElm: $BUILT .user.loginPage'
console.log(loginDialog.get());    //=> 'navigating to LoginPage with userElm: $BUILT .user.loginDialog'
console.log(loginPage.assertInputsDisplayed());   //=> 'LoginPage assertion: this.userElm: $BUILT .user.loginPage'
console.log(loginDialog.assertInputsDisplayed()); //=> 'LoginPage assertion: this.userElm: $BUILT .user.loginDialog'

//loginPage.logout();   //=> TypeError: Object #<Object> has no method 'logout'
console.log(loginDialog.logout()); //=> 'logging out of Dialog. user was: $BUILT .user.loginDialog'
like image 996
Leo Gallucci Avatar asked Jan 30 '14 20:01

Leo Gallucci


2 Answers

Here is link to a tutorial I set up for training some of my co-workers on create good Protractor test suites.

It's all live, with a demo site that you can visit, explore, etc.

https://github.com/Droogans/ProtractorPageObjects

This will set you up with installation, outlines, organizational techniques, and more.

Feel free to leave an issue if you have any trouble.

like image 89
yurisich Avatar answered Oct 03 '22 09:10

yurisich


My opinions and how we structure our test...

  • One general page model that holds some methods we expect most pages to use like go() or we have methods for interacting with some common custom elements.

  • Many page-specific models that inherit from this general page. Most of the methods on these models are related to getting various elements on the page or interacting with that page's ui. There are no assertion methods on these models.

  • UI models for interacting with certain more complicated widgets. Similar to page models but these aren't tied to a page. They are tied to a UI/widget. There are no assertion methods on these models.

  • For common & reusable assertions, we have assertion models that utilize various page models' and UI models' interaction methods. They are organized by page or UI. For assertions that are uncommon and not really reusable, we just put them into the spec itself.

  • Specs are usually organized by page though we do have specs for certain UI/widgets.

like image 37
wlingke Avatar answered Oct 03 '22 11:10

wlingke