Protractor, a tool for e2e testing of Angular-based applications, can be a great tool in your development pipeline. However, it can also take a bit of searching to work out exactly what it’s doing under the hood and how it can affect your tests. Simple tests such as the one below are easy to follow and don’t really cause beginners any issues:
In the above example, we task Protractor with getting the browser title with a call to ‘getTitle()’, then simply add an expectation that it should match our anticipated response.
Under the covers, each of the lines above is actually an asynchronous operation, and when each line is executed, the action is added to a queue to be executed at some point. In fact, if you explore the documentation here, it specifies:
WebDriverJS (and thus, Protractor) APIs are entirely asynchronous. All functions return promises.
Behind the sciences, WebDriver, the underlying component driving your e2e tests, maintains this queue of promises, called the ‘Control Flow’ in order to keep everything executing in the correct order. Protractor actually modifies Jasmine so that each test spec waits until this ‘control flow’ queue is empty before exiting.
Jasmine expectations are also adapted to understand these promises. That’s why the last line in the example above works – the code actually adds an expectation task to the control flow, which will run after the other tasks:
Writing tests without knowing that Protractor works in this way can lead to some puzzling results; Imagine if you have a dynamic number of elements on the page and you wish to check to see if they have all been drawn in the browser; your first, naive implementation might look something like this:
In this example, we’re iterating over some elements, calling ‘isPresent()’ to check if they exist on the page, then testing that hopefully they all exist, and that no calls to ‘isPresent()’ returned false.
This test won’t work though – the call to ‘isPresent()’ returns a promise, not a true/false value! How can we fix it? Like this:
Here we form an array of the promises returned from each call to ‘isPresent()’, then wait for all of them to resolve before we check the values.