Protractor Page Objects Inheritance
Asked Answered
P

2

19

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'
Purkey answered 30/1, 2014 at 20:9 Comment(2)
What's with these people that so easily down votes a question?? This is a perfectly formed question and the poster set everything up to help assist. +1 from me.Titfer
Leo, found a lot of interesting in this topic. Thanks for posting. I also, would be happy to see your insight here - what do you think about the idea I've suggested? Thanks.Yokoyama
T
8

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.

Teufert answered 30/1, 2014 at 21:52 Comment(1)
wow that's quite a tutorial you wrote! thanks!! however i don't like the idea of adding astrolabe to the stack, i gut feel this can be done without itPurkey
B
5

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.

Boll answered 30/1, 2014 at 20:34 Comment(3)
hey @Boll thanks for sharing your criteria, could you provide some code? that would be awesome ;)Purkey
@elgalu, I don't think I really have an pieces of code that I could share. However, I think the protractor examples are really good so I would recommending building off of those examples. github.com/angular/protractor/tree/master/specBoll
those examples are for a very basic angular site, but thanks anywayPurkey

© 2022 - 2024 — McMap. All rights reserved.