Getting Started With End-to-End Testing in Angular Using Protractor

Protractor is a popular end-to-end test framework that lets you test your Angular application on a real browser simulating the browser interactions just the way that a real user would interact with it. End-to-end tests are designed to ensure that the application behaves as expected from a user's perspective. Moreover, the tests are not concerned about the actual code implementation.

Protractor runs on top of the popular Selenium WebDriver, which is an API for browser automation and testing. In addition to the features provided by Selenium WebDriver, Protractor offers locators and methods for capturing the UI components of the Angular application.

In this tutorial, you will learn about:

setting up, configuring and running Protractor

writing basic tests for Protractor

page objects and why you should use them

guidelines to be considered while writing tests

writing E2E tests for an application from start to finish

Doesn't that sound exciting? However, first things first.

Do I Need to Use Protractor?

If you've been using Angular-CLI, you might know that by default, it comes shipped with two frameworks for testing. They are:

unit tests using Jasmine and Karma

end-to-end tests using Protractor

The apparent difference between the two is that the former is used to test the logic of the components and services, while the latter is used to ensure that the high-level functionality (which involves the UI elements) of the application works as expected.

If you are new to testing in Angular, I'd recommend reading the Testing Components in Angular Using Jasmine series to get a better idea of where to draw the line.

In the former's case, you can leverage the power of Angular testing utilities and Jasmine to write not just unit tests for components and services, but basic UI tests also. However, if you need to test the front-end functionality of your application from start to end, Protractor is the way to go. Protractor's API combined with design patterns such as page objects make it easier to write tests that are more readable. Here's an example to get things rolling.

The default project template created by Protractor depends on two files to run the tests: the spec files that reside inside the e2e directory and the configuration file (protractor.conf.js). Let's see how configurable protractor.conf.js is:

If you are ok with running the test on Chrome web browser, you can leave this as is and skip the rest of this section.

Setting Up Protractor With Selenium Standalone Server

The directConnect: true lets Protractor connect directly to the browser drivers. However, at the moment of writing this tutorial, Chrome is the only supported browser. If you need multi-browser support or run a browser other than Chrome, you will have to set up Selenium standalone server. The steps are as follows.

Install Protractor globally using npm:

npm install -g protractor

This installs the command-line tool for webdriver-manager along with that of protractor. Now update the webdriver-manager to use the latest binaries, and then start the Selenium standalone server.

webdriver-manager update
webdriver-manager start

Finally, set the directConnect: false and add the seleniumAddress property as follows:

The config file on GitHub provides more information about the configuration options available on Protractor. I will be using the default options for this tutorial.

Running the Tests

ng e2e is the only command you need to start running the tests if you are using Angular-CLI. If the tests appear to be slow, it's because Angular has to compile the code every time you run ng e2e. If you want to speed it up a bit, here's what you should do. Serve the application using ng serve.

Then fire up a new console tab and run:

ng e2e -s false

The tests should load faster now.

Our Goal

We will be writing E2E tests for a basic Pastebin application. Clone the project from the GitHub repo.

Both the versions, the starter version (the one without the tests) and the final version (the one with the tests), are available on separate branches. Clone the starter branch for now. Optionally, serve the project and go through the code to get acquainted with the application at hand.

Let's describe our Pastebin application briefly. The application will initially load a list of pastes (retrieved from a mock server) into a table. Each row in the table will have a View Paste button which, when clicked on, opens up a bootstrap modal window. The modal window displays the paste data with options to edit and delete the paste. Towards the end of the table, there is a Create Paste button which can be used to add new pastes.

The sample application.

The rest of the tutorial is dedicated to writing Protractor tests in Angular.

Protractor Basics

The spec file, ending with .e2e-spec.ts, will host the actual tests for our application. We will be placing all the test specs inside the e2e directory since that's the place we've configured Protractor to look for the specs.

There are two things you need to consider while writing Protractor tests:

Jasmine Syntax

Protractor API

Jasmine Syntax

Create a new file called test.e2e-spec.ts with the following code to get started.

This depicts how our tests will be organized inside the spec file using Jasmine's syntax. describe(), beforeEach() and it() are global Jasmine functions.

Jasmine has a great syntax for writing tests, and it works just as well with Protractor. If you are new to Jasmine, I would recommend going through Jasmine's GitHub page first.

The describe block is used to divide the tests into logical test suites. Each describe block (or test suite) can have multiple it blocks (or test specs). The actual tests are defined inside the test specs.

"Why should I structure my tests this way?" you may ask. A test suite can be used to logically describe a particular feature of your application. For instance, all the specs concerned with the Pastebin component should ideally be covered inside a describe block titled Pastebin Page. Although this may result in tests that are redundant, your tests will be more readable and maintainable.

A describe block can have a beforeEach() method which will be executed once, before each spec in that block. So, if you need the browser to navigate to a URL before each test, placing the code for navigation inside beforeEach() is the right thing to do.

Expect statements, which accept a value, are chained with some matcher functions. Both the real and the expected values are compared, and a boolean is returned which determines whether the test fails or not.

browser.get('/') and element(by.css('.pastebin')).getText() are part of the Protractor API. Let's get our hands dirty and jump right into what Protractor has to offer.

The prominent components exported by Protractor API are listed below.

browser(): You should call browser() for all the browser-level operations such as navigation, debugging, etc.

element(): This is used to look up an element in the DOM based on a search condition or a chain of conditions. It returns an ElementFinder object, and you can perform actions such as getText() or click() on them.

element.all(): This is used to look for an array of elements that match some chain of conditions. It returns an ElementArrayFinder object. All the actions that can be performed on ElementFinder can be performed on ElementArrayFinder also.

locators: Locators provide methods for finding an element in an Angular application.

Since we will be using locators very often, here are some of the commonly used locators.

by.css('selector-name'): This is by far the commonly used locator for finding an element based on the name of the CSS selector.

by.name('name-value'): Locates an element with a matching value for the name attribute.

by.buttonText('button-value'): Locates a button element or an array of button elements based on the inner text.

Note: The locators by.model, by.binding and by.repeater do not work with Angular 2+ applications at the time of writing this tutorial. Use the CSS-based locators instead.

The code above works, and you can verify that yourself. However, wouldn't you feel more comfortable writing tests without the Protractor-specific vocabulary in your spec file? Here's what I am talking about:

The specs appear more straightforward without the extra Protractor baggage. How did I do that? Let me introduce you to Page Objects.

Page Objects

Page Object is a design pattern which is popular in the test automation circles. A page object models a page or part of an application using an object-oriented class. All the objects (that are relevant to our tests) like text, headings, tables, buttons, and links can be captured in a page object. We can then import these page objects into the spec file and invoke their methods. This reduces code duplication and makes maintenance of code easier.

Create a directory named page-objects and add a new file inside it called pastebin.po.ts. All the objects concerned with the Pastebin component will be captured here. As previously mentioned, we divided the whole app into three different components, and each component will have a page object dedicated to it. The naming scheme .po.ts is purely conventional, and you can name it anything you want.

In short, element() returns an ElementFinder, and element().all returns an ElementArrayFinder. You can use the locators (by.css, by.tagName, etc.) to find the location of the element in the DOM and pass it to element() or element.all().

ElementFinder and ElementArrayFinder can then be chained with actions, such as isPresent(), getText(), click(), etc. These methods return a promise that gets resolved when that particular action has been completed.

The reason why we don't have a chain of then()s in our test is because Protractor takes care of it internally. The tests appear to be synchronous even though they are not; therefore, the end result is a linear coding experience. However, I recommend using async/await syntax to ensure that the code is future proof.

You can chain multiple ElementFinder objects, as shown below. This is particularly helpful if the DOM has multiple selectors of the same name and we need to capture the right one.

Exercises

There are a couple of things missing, though: the tests for the View Paste button and the modal window that pops up after clicking the button. I am going to leave this as an exercise for you. However, I will drop you a hint.

The structure of the page objects and the specs for the ViewPastePage are similar to that of the AddPastePage.

Blueprint for the ViewPaste component

Here are the scenarios that you need to test:

ViewPaste Page should have a button, and on click, it should bring up a modal window.

The modal window should display the paste data of the recently added paste.

The modal window should let you update values.

The delete button should work.

Try to stick to the guidelines wherever possible. If you're in doubt, switch to the final branch to see the final draft of the code.

Wrapping It Up

So there you have it. In this article, we've covered writing end-to-end tests for our Angular application using Protractor. We started off with a discussion about unit tests vs. e2e tests, and then we learned about setting up, configuring and running Protractor. The rest of the tutorial concentrated on writing actual tests for the demo Pastebin application.

Please let me know your thoughts and experiences about writing tests using Protractor or writing tests for Angular in general. I would love to hear them. Thanks for reading!