4

I've come across different types of syntax for Protractor's Page Objects and I was wondering, what's their background and which way is suggested.

This is the official PageObject syntax from Protractor's tutorial. I like it the most, because it's clear and readable:

use strict;

var AngularHomepage = function() {
  var nameInput = element(by.model('yourName'));
  var greeting = element(by.binding('yourName'));

  this.get = function() {
    browser.get('http://www.angularjs.org');
  };

  this.setName = function(name) {
    nameInput.sendKeys(name);
  };

  this.getGreeting = function() {
    return greeting.getText();
  };
};
module.exports = AngularHomepage;

However, I've also found this kind:

'use strict';

var AngularPage = function () {
  browser.get('http://www.angularjs.org');
};

    AngularPage.prototype  = Object.create({}, {
      todoText:  {   get: function ()     { return element(by.model('todoText'));             }},
      addButton: {   get: function ()     { return element(by.css('[value="add"]'));          }},
      yourName:  {   get: function ()     { return element(by.model('yourName'));             }},
      greeting:  {   get: function ()     { return element(by.binding('yourName')).getText(); }},
      todoList:  {   get: function ()     { return element.all(by.repeater('todo in todos')); }},
      typeName:  { value: function (keys) { return this.yourName.sendKeys(keys);              }} ,
      todoAt:    { value: function (idx)  { return this.todoList.get(idx).getText();          }},
      addTodo:   { value: function (todo) {
        this.todoText.sendKeys(todo);
        this.addButton.click();
      }}
    });

    module.exports = AngularPage;

What are the pros/cons of those two approaches (apart from readability)? Is the second one up-to-date? I've seen that WebdriverIO uses that format.

I've also heard from one guy on Gitter that the first entry is inefficient. Can someone explain to me why?

anks
  • 303
  • 2
  • 12

2 Answers2

6

Page Object Model framework becomes popular mainly because of:

  1. Less code duplicate
  2. Easy to maintain for long
  3. High readability

So, generally we develop test framework(pom) for our convenience based on testing scope and needs by following suitable framework(pom) patterns. There are NO such rules which says that, strictly we should follow any framework.

NOTE: Framework is, to make our task easy, result oriented and effective

In your case, 1st one looks good and easy. And it does not leads to confusion or conflict while in maintenance phase of it.

Example: 1st case-> element locator's declaration happens at top of each page. It would be easy to change in case any element locator changed in future.

Whereas in 2nd case, locators declared in block level(scatter across the page). It would be a time taking process to identify and change the locators if required in future.

So, Choose which one you feel comfortable based on above points.

Optimworks
  • 2,537
  • 17
  • 20
3

I prefer to use ES6 class syntax (http://es6-features.org/#ClassDefinition). Here, i prepared some simple example how i work with page objects using ES6 classes and some helpful tricks.

var Page = require('../Page')
var Fragment = require('../Fragment')

class LoginPage extends Page {
    constructor() {
        super('/login');
        this.emailField = $('input.email');
        this.passwordField = $('input.password');
        this.submitButton = $('button.login');

        this.restorePasswordButton = $('button.restore');
    }

    login(username, password) {
        this.email.sendKeys(username);
        this.passwordField.sendKeys(password);
        this.submit.click();
    }

    restorePassword(email) {
        this.restorePasswordButton.click();
        new RestorePasswordModalWindow().submitEmail(email);
    }
}

class RestorePasswordModalWindow extends Fragment {
    constructor() {
        //Passing element that will be used as this.fragment;
        super($('div.modal'));
    }

    submitEmail(email) {
        //This how you can use methods from super class, just example - it is not perfect.
        this.waitUntilAppear(2000, 'Popup should appear before manipulating');
        //I love to use fragments, because they provides small and reusable parts of page.
        this.fragment.$('input.email').sendKeys(email);
        this.fragment.$('button.submit')click();
        this.waitUntilDisappear(2000, 'Popup should disappear before manipulating');
    }
}
module.exports = LoginPage;

// Page.js
class Page {
    constructor(url){
        //this will be part of page to add to base URL.
        this.url = url;
    }

    open() {
        //getting baseURL from params object in config.
        browser.get(browser.params.baseURL + this.url);
        return this; // this will allow chaining methods.
    }
}
module.exports = Page;

// Fragment.js
class Fragment {
    constructor(fragment) {
        this.fragment = fragment;
    }

    //Example of some general methods for all fragments. Notice that default method parameters will work only in node.js 6.x
    waitUntilAppear(timeout=5000, message) {
        browser.wait(this.EC.visibilityOf(this.fragment), timeout, message);
    }

    waitUntilDisappear(timeout=5000, message) {
        browser.wait(this.EC.invisibilityOf(this.fragment), timeout, message);
    }
}
module.exports = Fragment;

// Then in your test:
let loginPage = new LoginPage().open(); //chaining in action - getting LoginPage instance in return.
loginPage.restorePassword('batman@gmail.com'); // all logic is hidden in Fragment object
loginPage.login('superman@gmail.com')
Xotabu4
  • 3,063
  • 17
  • 29
  • After I posted this, my friend also advised me to use ES6 and I agree that it looks very nice and more familiar to me (I've written page objects in Ruby before). One question though - you don't use Protractor's syntax for elements (like `element(by.css('someclass')`) - is this because $('') is shorter or for other reasons? – anks Aug 05 '16 at 17:54
  • Yes, i use element(by.css('')) a lot, and $/$$ is nice global shortcut for it. No any functional difference. – Xotabu4 Aug 06 '16 at 08:10
  • this one doesn't work for me. I tried to use javascript class and at the end use module.exports = ClassName; In my test.js I use var Ahomepage = require('./AngularHomePagePOM.js'); Ahomepage = new POM_HomePage(); but always get error undefined [17:53:41] E/launcher - Error: ReferenceError: POM_HomePage is not defined – Francis Saul Mar 24 '20 at 09:56