What's the correct Protractor's syntax for

2019-03-16 19:29发布

问题:

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?

回答1:

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.



回答2:

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