Protractor and Page Objects

September 29, 2015

In a previous post, we looked at using the Protractor framework with AngularJS apps. While Protractor is a great testing tool out of the box, it can benefit from some best practices to make a testing suite and its code more manageable. One practice worth considering for organizing test code is the use of a Page Object design pattern.

A page object is a class that simply stores your page elements. Elements and methods are housed in the page object file. This pattern allows testers to write clean code, avoid duplication, and better maintain their suites.

Let’s take a look at our previous Protractor example:

describe('Protractor Test', function() {  
  var addField = element(by.css('[placeholder="add new todo here"]'));  
  var checkedBox = element(by.model('todo.done'));  
  var addButton = element(by.css('[value="add"]'));  
  it('should navigate to the AngularJS homepage', function() {  
    browser.get(''); //overrides baseURL  
  it('should show a search field', function() {  
    browser.sleep(5000); //used just to give you enough time to scroll to todo section  
  it('should let you add a new task ', function() {  
    addField.sendKeys('New Task');;  
    browser.sleep(5000); //used just to see the task list update  

As you can see, each element is defined within the spec. This pattern can be ok for a small suite, but it isn’t able to support larger, more complex automation. With this pattern, each spec has its’ own set of elements to maintain. If multiple specs use the same elements then code duplication will quickly become a problem. And, if the UI of your application changes (that rarely happens, right?), you will need to update your elements within each, individual test.

All of these complications can be fixed with the use of page objects. Here is what a page object file for the spec could look like:

'use strict';  
module.exports = {  
    toDo: {  
        addField: element(by.css('[placeholder="add new todo here"]')),  
        checkedBox: element(by.model('todo.done')),  
        addButton: element(by.css('[value="add"]'))  
    go: function() {  
        browser.get(''); //overrides baseURL  
    addItem: function(item) {  
        var todo = this.toDo;  
        todo.addField.sendKeys(item); ;  

The majority of our logic needed to run the tests is now housed in a file called toDoPage.js. As your application grows, you may have multiple page object files that correspond to individual views. For example, a login view and home view may have their own, dedicated page objects. Organization of your tests and files may differ based on preference, but it’s important to decide on that organization structure early on.

Now that the page object has been defined, here is the cleaned up test spec:

var toDoPage = require('../pages/toDoPage.js');  
describe('Protractor Test', function() {  
  it('should navigate to the AngularJS homepage', function() {  
  it('should let you add a new task ', function() {  
   toDoPage.addItem('New Task Item')  

This cleans up the spec significantly and enhances maintainability. With the Page Object design pattern, test workflows are in the specs while logic and UI elements are in the page objects. That is a good way to look at the separation between these two components.

This is a simple example, but it should give you enough information to begin adopting this design pattern. There are a lot of great resources on creating Page Objects if you want to learn more.

Mooney_Mallory square
Mallory Mooney is a QA Engineer at Modus Create. She has experience in developing automation suites for a broad range of frameworks like Angular, Ext JS, and .Net and is an advocate for open source automation tools. She believes in building the “right” tests for the job - not just an arbitrary number of them. Aside from her work, Mallory loves road trips, photography, breakfast tacos, Texas BBQ, and is a loyal Red Raider. Wreck ‘em Tech.

  • mike cataldo

    Great post! Question: I’ve seen examples where instead of
    module.exports = { your object };
    the last line of code would be module.exports = new toDoPage();
    What is the advantage of writing it the way you did? Any disadvantage(s)? When is important to instantiate?

    • Mallory

      Great question, Mike!

      Both ways would work for creating page objects. It depends on the complexity of your project and what you want to design for your testing framework. For such a simple example as what we have in this post, exporting the entire page object works fine. This would allow us to use multiple instances of our page objects whenever we choose. Personally, I just like having that kind of flexibility.

      The disadvantage of this may come as your test suite becomes more complex (like running things in parallel). In situations like that, using module.exports = new toDoPage(); will better suite your needs.

What We Do

We’ll work closely with your team to instill Lean practices for ideation, strategy, design and delivery — practices that are adaptable to every part of your business.

See what Modus can do for you.


We're Hiring!

Join our awesome team of dedicated engineers.