Using Promises with the Page Object Model

What Promises are, how to manage them, and how to incorporate them into your Page Object Model test automation

Hal Deranek
Slalom Build
15 min readMay 3, 2018

--

EDITOR’S NOTE: This article has been superseded by a newer post with an updated approach to this topic: Redefining “Using Promises with the Page Object Model”. While the information below still works, it should now be considered obsolete, and we recommend reading the new article instead!

Before we begin…

When will this be of help

The information contained in this article will be useful for two sets of readers:

  • Those wanting to know more about the Page Object Model
  • Those who will be writing test automation for websites using an asynchronous framework, such as Angular and React

When can this be avoided

  • When writing test automation against very simple websites that are not dynamic, such as a small HTML site

What you will need

  • A basic understanding of the Page Object Model. Here are some examples:
  1. http://www.seleniumhq.org/docs/06_test_design_considerations.jsp#page-object-design-pattern
  2. https://martinfowler.com/bliki/PageObject.html
  • A basic understanding of test automation frameworks like Selenium-Webdriver
  • If you wish to follow my examples, you’ll need a basic understanding of Typescript (knowledge of JavaScript should suffice) and Protractor (used for testing Angular websites).

A note on footnotes

Medium offers this publishing platform for free (thanks, Medium!), however it lacks some elements I’d like to use… like footnotes. Within the article, you’ll see something like “… this is a sentence[15].” The number inside the italicized brackets is the footnote. You’ll find the footnotes’ info at the bottom of the article.

Explaining the Page Object Model

“A page object is an object-oriented class that serves as an interface to a page of your [application under test]. The tests then use the methods of this page object class whenever they need to interact with the UI of that page. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently all changes to support that new UI are located in one place.” — Seleniumhq.org

(Do yourself a favor — click on the link above and read the whole section titled “Page Object Design Pattern”)

Whenever looking to create automated testing for a website, there are different methods towards the approach. Perhaps the most obvious is to take a page and define everything in one file/class. As an example, take a look at this page:

http://www.way2automation.com/angularjs-protractor/webtables/

How many different visible elements do you think are on this page? Counting the search input box, each user name, each Edit button, etc., there are 84 elements visible on the page. If this page never changed, it’s conceivable that it would make sense to include them all in one large class, but this is a dynamic website. You can add, remove, or edit records. There must be a better way!

There is! The Page Object Model was conceived for situations such as these. Rather than seeing the page as singular elements, you can look at it more as various sections and patterns which you can exploit. The objects themselves are contained within the sections/patterns that you create.

As an example, let’s take a look at the website again, but this time, let’s break it down by sections…

The five sections of this page

Looking at the page, there are effectively five sections:

  • Search
  • Add User
  • Table Headers
  • Table Records
  • Pagination
Sections and patterns (dark blue outlines)

This is a great start, however the Table Records section still has a vast amount of elements. Also, number of records is likely to fluctuate at any time. Looking at the records themselves, there’s a distinctive pattern that emerges: the rows. If you create a class to represent this pattern, you can simply grab each row’s container and let the pattern handle the rest.

Code Examples

Using the section examples above, we can start coding our classes. Let’s start with the Dashboard Page. We have five sections, all of which will need their own classes. Here’s what the DashboardPage class would look like:

All five sections are included in the code above. One thing to note is that each returned class instance has an element passed in. This is the container. You use the container to ensure you’re only looking for elements within the correct section.

Here’s the class for the first section seen on the page — SearchBarSection. Since there’s only one element currently in the section — an inputBox — it’s not terribly complicated. Note how we search for the ‘input’ tag after the container. This ensures we only look for an ‘input’ tag within the passed-in container.

To see something more complicated, let’s think about the records table. Before we work on the class of the table itself, let’s think about the pattern of the record rows:

Much like the SearchBarSection, this class defines each element with a basic pattern, including using the container. Now that we’ve got the row pattern we can start thinking about finding all the records:

In the code above, we find all of the user rows by this:

await container.element.all(by.css(‘tr’))

This will find all ‘tr’ tags within the container. The next line will push a new instance of a UserRecord (as defined in the previous example) into an array which will then be returned once all elements have been found.

Benefits of the Page Object Model

Looking at the examples above, you can see that the Page Object Model simplifies how we approach working with a web page. Rather than working with 84 objects, we are working with five that have their own patterns and objects.

More importantly, it’s easier maintain. For instance, let’s say we want to add a middle name to the user records. Rather than having to update the code in several places, we only have to add an extra property to the UserTableRecord class.

Promises

What is a Promise?

A Promise is an asynchronous operation. It’s called a “Promise” because we are promised a result at a future time[1]. All interactions with asynchronous websites return a Promise, but they aren’t necessarily ready as soon as you ask for them (as they would be in a synchronous website).

To put it simply, an asynchronous website allows different elements to load as they become available rather than in a specific order. A Promise is a request to those elements and will be available at some point but not always immediately. This is wonderful for website loading and usability. This is difficult for testing.

Why are Promises difficult?

In automation, you would expect a line of code to be executed in a linear fashion. Example:

You would expect to see this in your console:

Instead, it will output something like this[2]:

The reason is because isPresent() executes only when it’s ready. The testing framework will continue through the code, printing out what it has and moving on. Since it doesn’t yet have a value, it prints ManagedPromise::142 {[[PromiseStatus]]: “pending”} and continues.

Wrangling promises

There is a way to tame Promises, though. Here’s an effective practice:

Every method returns a Promise!

For every method you write that will interact with page objects, ensure it will return a Promise[3]. Once you buy into the mantra of “Everything is a Promise”, your coding will become easier. If you know that all methods return a Promise, then you will always be anticipating them and coding for them. It makes things less confusing.

Also, it should be noted that almost any asynchronous testing framework interaction with a website will return a Promise. Getting an element’s text, interacting with it, getting the browser’s URL value, etc. — all return Promises. If your methods also return Promises, they will mimic those default methods.

Async & Await

Async and Await are keywords used to help with asynchronous functionality and waiting for it to resolve[4]. Use Async and Await to force the testing framework to resolve a Promise at the moment of execution rather than immediately continuing on.

The async keyword should prepend each method you write. It casts that method into a Promise. You also need to use async to later use await on that method. We’ll see some examples of this soon.

The await keyword is used before a method call. Using it tells the testing framework to wait for a Promise to resolve before moving to the next line of code. It turns the asynchronous code into synchronous code. It should be used just before the method that returns a Promise, though, not at the beginning of the line (as I’ll show you).

We can use this knowledge in the example above to force the asynchronous testing framework to execute isPresent() before printing out the line to the console. Here’s how that would look:

When executed, this would be seen in the terminal:

Problems with Page Object Model automation & Promises

Constructors

One of the most basic concepts of POM and other object-oriented coding practices is the class object. A class is a way to encapsulate characteristics of an object that follows a pattern.

Going back to the Lego website example above, let’s create the class that contains the image and title properties: ListGridItem[5].

Unfortunately, this won’t work as written. A tenet of object-oriented coding is that a class expects there to be a constructor. The constructor is called any time you instantiate a class. The problem is that constructors cannot handle promises since constructors are meant to establish properties, not execute tasks[6]. So if I cannot use the constructor to set up my label and input characteristics, what can I do?

How to overcome the problems

Create initializers

The best way I have found to overcome this issue is to create an initializer method. You must call it each time you execute any method from that instance of the class[7]. One sticky point there — if the class instance has already been initialized, you might not want to initialize it every time you run one of the instance methods. At best it would affect performance, however it could also have other consequences. Therefore, you will also need to create a token property that will tell you if the initializer method has already been run.

How to implement this

Let’s take a look at how to properly implement this, given the example:

Let’s go over the code above…

Our token property. We declare that its type is a void Promise. Notice we do not set a value for it (nor for the other class instance properties), so its value is effectively null at this point.

It’s declared as private to ensure the property cannot be set outside the internal workings of the class instance.

Our constructor. It expects an object of type ElementFinder[8] to be passed in. This is the element that will contain the information necessary to set the other instance properties (image and title).

itemContainer is private as we don’t want it to be accessible outside the scope of this class instance.

Our initializer method. There are two things to note:

  1. async — Declares that this method is asynchronous. It must be declared to use the await keyword any time the initialize() method is called[9].
  2. initialize(): Promise<void> — This designates the method, named initialize(), will return a void Promise. This goes back to a previous point — Every method returns a Promise!

Where the initializer token property is first checked. If this.initializePromise equates to false — i.e., this class instance hasn’t been initialized (if it’s null) — then we want to execute the code within the brackets. If this.initializePromise equates to true, we’ll bypass so as not to reset the properties.

I’ll break this down a little out of order…

  • this.initializePromise = new Promise<void>(async (resolve) => { — This sets this.initializePromise to be a new void Promise. Therefore, the next time the initialize() method is called, this.initializePromise will no longer be null and we should not get here again.
  • return — This returns everything. Returns are very important within methods that deal with promises as they tell the asynchronous testing framework to execute all code after the return before proceeding.
  • resolve — This is a handler function that is returned at the end of a Promise, signaling a fulfilled (successful) Promise.

This initializes the image and title class instance properties:

  • this.itemContainer — The parent element that was passed into the constructor.

Returns the resolve function declared a few lines above as a handler for Promise. This signals the new Promise was fulfilled.

And that’s basically it! Now, let’s see it with an example of some class methods…

Before talking about the new methods, let’s note some changes in lines 5 and 6. These two properties are now declared as private where they previously were defaulted to public. Why?

If we were to try to access these properties before they had been initialized, an error would be generated. By declaring them private, we ensure that the access to the properties must go through a class method where we can ensure they have been initialized before use.

Let’s break down one of the other three new methods…

Declares class instance method, isPresent(), and declares that it will return a Boolean Promise. We use the keyword async so that the await keyword can be used when calling the method.

Runs the initialize() method that is described above. If it’s already been run, almost no action happens. Using the await keyword tells Protractor to wait for this.initialize() to resolve before continuing to the next command.

Now that we are sure the class instance has been instantiated, we can be sure the properties are set and ready. This call will return a Boolean Promise (true: the image is present; false: the image is not present). Note: Using the await keyword is not necessary since return is being used.

All of the three methods are fairly similar, so you can extrapolate from here.

Putting it all together

Now that we’ve looked at how to write a class to handle promises, let’s see how to implement it. To reiterate, we’ve created a class that will receive an item container and parse from it the image and title. Here’s an example of what we’re getting — If we inspect one of these elements, we’ll see the following:

Looking at the example above, we can see some of the code involved with the items. The li element with the class “list-grid__item” is the pattern for the item containers. The image and title are within it (see the arrows). Here’s an example of a test (written in Jasmine) that uses this information:

Let’s break this down…

Imports the class we created from a file where it’s contained

Declares that there will be a variable named item and that it will be of type ListGridItem, however we don’t instantiate it yet.

Instantiates the item variable. Let’s talk about the different parts:

  • $(‘li.list-grid__item’) — Selenium shorthand for ‘find an element by CSS’, saying we want an element of type li that has the class “list-grid__item” amongst its classes.
  • Note: There are several elements on the Lego.com homepage that match this pattern. When this happens, the first one is used (which is fine for this example). It will generate a warning in the terminal.

done is used here much like resolve was in previous examples.

Runs a test to ensure item.isPresent() returns the Boolean value true. Note: Though expect should be able to handle Promises, I have found that adding an await here helps to ensure proper execution.

Runs a test to ensure item.getTitle() is not null.

These three lines ensure that clicking an item will take you to a different page by getting the current URL (Line 17), clicking the item (Line 18), and then verifying the new URL does not match the old one (Line 19).

Run that test!

If you bring the code down from Github and follow the directions in the Readme, you should see this as your input:

As usual for testers, the Jasmine testing framework is much better on calling out the faults rather than the successes, but what can you do (other than find some better reporting packages)?

Conclusion

With the information and examples above, you should be able to start handling Promises in the Page Object Model. Your test automation will be more reliable, easier to extend, and easier to maintain. It’s not the simplest solution, but if you follow the lessons and invest in doing things right, your code and automation will improve. And more importantly, you’ll impress your coworkers!

But we have only started down this path. Further articles will cover topics such as how to work with having user-defined classes inside of other classes and class abstractions. Once you have gone through those, you will truly be on your way to becoming to be a Promise and Page Object test automation master!

Footnotes

[1] https://toddmotto.com/promises-angular-q

[2] Note: Disregard the number seen after “ManagedPromise::” as it will change frequently

[3] Exception: Some helper methods don’t need to return a Promise. If it’s not interacting with the site, or if it’s not working with Promises in any way, then it doesn’t need to return a Promise. Examples would be helper methods that make calculations, manipulate text, etc. It’s up to you on how far you want to take the “EVERYTHING!” mantra.

[4] Resolve: A Promise can be said to be resolved when it has finished processing and the element is ready to be used

[5] I try to make the class describe the object. In this case, I got “ListGridItem” from the class of the HTML tag that encapsulates the elements we’re looking for. The class is named “list-grid__item”.

[6] https://stackoverflow.com/questions/24398699/is-it-bad-practice-to-have-a-constructor-function-return-a-promise

[7] Exception: private class instance methods. Since a private method can only be called within the scope of other public facing class instance methods, you can assume the call to the initialize method has already been made when the private method is called.

[8] ElementFinder: A class defined in Protractor and imported in the first line of the code

[9] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

--

--

Hal Deranek
Slalom Build

Hal Deranek has been in the testing game for a while, excelling at automation strategy and execution. He currently works for Slalom in Washington, DC.