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('https://angularjs.org/'); //overrides baseURL }); it('should show a search field', function() { browser.sleep(5000); //used just to give you enough time to scroll to todo section addField.isDisplayed(); }); it('should let you add a new task ', function() { addField.sendKeys('New Task'); addButton.click(); 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('https://angularjs.org/'); //overrides baseURL browser.waitForAngular(); }, addItem: function(item) { var todo = this.toDo; todo.addField.isDisplayed(); todo.addField.sendKeys(item); todo.addButton.click(); } };
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() { toDoPage.go(); }); 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.
Mallory Mooney
Related Posts
-
Protractor and Elementor
Intro Locators and elements are the foundation of any automated test (youdontsay). You spend a…
-
Testing AngularJS Apps with Protractor
Do the words “automated” and “tests” make you cringe a little? Creating automated tests for…