Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

End to End(E2E) Tests in Angular Application Using Protractor

5.00/5 (1 vote)
8 Jul 2018CPOL6 min read 15.4K  
How to create end to end tests for our Angular application

Introduction

In this article, we will learn how we can create end to end tests (e2e) for our Angular application. We will be using an existing end-to-end testing framework, which is nothing but, Protractor. Protractor runs your tests against your application running in a real browser, in the same way your user may perform the actions. I will be using an existing application which is available in my GitHub profile. The same is available in the source code section. Please do share your feedback with me. I hope you will like this article. Let's start coding.

Source Code

The source code can be found here. Please clone this repository before you start.

Importance of End to End Testing

Some of you might have already started writing end to end testing for your application, and you may be knowing how important that is. Here in this section, I will list down the key benefits of end to end testing.

  • End to end testing tests the complete flow or the action. For example, a complete login process can be treated as one end to end testing. Basically, it tests a specific functionality.
  • It doesn't improve the code quality as done in Unit testing. Unit testing is a different topic, and end to end testing and unit testing are completely different.
  • As I said earlier, end to end testing run tests on the browser, so it tests your application live, with some real data.
  • You can easily find out, if any functionalities are broken because of your recent changes or implementation.

Basic Overview

If you have already cloned the application, you should be able to see a folder e2e in your application code, open that folder. You can see three files as below:

  • tsconfig.e2e.json
  • app.po.ts
  • app.e2e-spec.ts

Here tsconfig.e2e.json is the configuration file, if you open that file, you can see that the file is been extended from tsconfig.json.

JavaScript
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/e2e",
    "baseUrl": "./",
    "module": "commonjs",
    "target": "es5",
    "types": [
      "jasmine",
      "jasminewd2",
      "node"
    ]
  }
}

app.po.ts is the page object, this is really helpful and important. Here is the place, where we will write the code to find out the elements in our page or view. So, in future, if you are changing the selectors of your element, your changes will impact only in this file, so that you don't need to change anything in your tests. Isn't that handy? By default, this file will have the code as below.

JavaScript
import { browser, by, element } from 'protractor';

export class AppPage {
  navigateTo() {
    return browser.get('/');
  }

  getParagraphText() {
    return element(by.css('app-root h1')).getText();
  }
}

As you can see, on the first line, we are importing browser, by, element from protractor.

  1. browser is for interacting with browser
  2. by is for finding the element by CSS or any other function
  3. element is for converting the selected element

Here in this line, element(by.css('app-root h1')).getText(), like you have already guessed, we are just finding an element with the selector 'app-root h1'.

app.e2e-spec.ts is the tests holder. We will be writing all of our tests here.

import { AppPage } from './app.po';

describe('my-angular5-app App', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display welcome message', () => {
    page.navigateTo();
    expect(page.getParagraphText()).toEqual('Welcome to ng5!');
  });
});

The first thing we do is, importing the page, in this case AppPage. This will be very easy for you if you have already written any unit test cases using jasmine. If you are not sure about how you can write the unit tests, I recommend you visit this link.

Once we import the page, we are declaring an object and initialize the same with AppPage instance in beforeEach function, so that the code can be executed before each tests runs.

And in our first test, we are just confirming that the title is 'Welcome to app' by getting the value from our page object by calling page.getParagraphText().

Creating Login Component

As you all know, a component will have two files by default:

  • login.component.html
  • login.component.ts

login.component.html

Let's write some HTML code for our component.

HTML
<div class="container" style="margin-top:100px;">
  <div class="row justify-content-center align-items-center">
    <div class="col-lg-4 col-sm-4 center-block ">
      <mat-card>
        <mat-card-header>
          <img mat-card-avatar src="../../../assets/images/App-login-manager-icon.png">
          <mat-card-title>Login here</mat-card-title>
          <mat-card-subtitle>Trust us for your data, and sign up</mat-card-subtitle>
        </mat-card-header>
        <mat-card-content>
          <div class="signup-fields">
            <form id="loginForm"[formGroup]="form" (ngSubmit)="login()">
              <div class="form-group">
                <input name="email" class="form-control" 
                 matInput type="email" placeholder="Email" formControlName="email" />
              </div>
              <div class="form-group">
                <input name="password" class="form-control" 
                 matInput type="password" placeholder="Password" 
                 formControlName="password" />
              </div>
              <div>
                <button id="btnSubmit" mat-raised-button type="submit" 
                 color="primary">Login</button>
              </div>
            </form>
          </div>
        </mat-card-content>
      </mat-card>
    </div>
  </div>
</div>

login.component.ts

JavaScript
import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  form;
  constructor(private fb: FormBuilder,
    private myRoute: Router,
    private auth: AuthService) {
    this.form = fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', Validators.required]
    });
  }

  ngOnInit() {
  }

  login() {
    if (this.form.valid) {
      this.auth.sendToken(this.form.value.email)
      this.myRoute.navigate(["home"]);
    }
  }
}

If you run the application, you can see that we are accepting the form only if we give relevant values to the fields, if not given, the form will be invalid. In our next step, we will write end to end test for this functionality. Sounds good?

Write End to End Tests for Login Component

To get started, let us create two files as below:

  1. login.po.ts
  2. login.e2e-spec.ts

Now let us define our page and some functions in login.po.ts.

Set up login.po.ts

Open the file and write some code as preceding.

JavaScript
import { browser, by, element } from 'protractor';

export class LoginPage {
    navigateTo(){
        return browser.get('/login');
    }
}

Now we will write the code to find the email and password text boxes.

JavaScript
getEmailTextbox() {
 return element(by.name('email'));
}

getPasswordTextbox() {
 return element(by.name('password'));
}

Set up login.e2e-spec.ts

It is time to set up our spec file before we start writing the tests.

JavaScript
import { LoginPage } from './login.po';

describe('Login tests', () => {
    let page: LoginPage;

    beforeEach(() => {
        page = new LoginPage();
        page.navigateTo();        
    });
});

We have imported our LoginPage and initialized the same. It is time to write the tests now.

JavaScript
it('Login form should be valid', () => {
 page.getEmailTextbox().sendKeys('info@sibeeshpassion.com');
 page.getPasswordTextbox().sendKeys('1234');

 let form = page.getForm().getAttribute('class');

 expect(form).toContain('ng-valid');
});

Here what we are doing is, set the values to our text boxes by using sendKeys function and then find the class attribute of our form, so that we can check whether it is valid or not. If the form is valid, the form will be having the class as ng-valid, if not, it will have ng-invalid class.

Run End to End Test

Running an end to end test is as easy as falling off a log. As we are using Angular CLI, all we have to do is run the command ng e2e This will be set in our package.json file.

JSON
"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  }

Let us run ng e2e now.

Shell
PS F:\My Projects\ng5> ng e2e

If everything goes well, your application should be opened in a browser and test the functionality. You will also get a message saying "Chrome is being controlled by automated test software".

Image 1

ng e2e browser output

And in your terminal console, you should see an output saying all of your tests are passed.

Image 2

Automated test output

Writing Few More Tests

Let us write few more tests to check some other functionalities.

Check Form Is Invalid

To check the form is invalid or not, we need to pass some invalid data to the form. The final test should be as follows:

null
it('Login form should be invalid', () => {
 page.getEmailTextbox().sendKeys('');
 page.getPasswordTextbox().sendKeys('');

 let form = page.getForm().getAttribute('class');

 expect(form).toContain('ng-invalid');
});

Check Whether the Value Is Been Saved to Local Storage

You might have already looked at what is the functionality we are doing when we click the Login button in our application. For now, we are just saving the email value to the local storage. Below is the function which gets called when we click the Login button.

JavaScript
login() {
    if (this.form.valid) {
      this.auth.sendToken(this.form.value.email)
      this.myRoute.navigate(["home"]);
    }
  }

And this is the sendToken method in our AuthService.

JavaScript
sendToken(token: string) {
    localStorage.setItem("LoggedInUser", token)
 }

Now, we are going to write automated test for this functionality. First, let us add a function which returns Submit button in login.po.ts.

JavaScript
getSubmitButton() {
 return element(by.css('#btnSubmit'));
}

Now, write the test as follows:

JavaScript
it('Should set email value to local storage', () => {
 page.getEmailTextbox().sendKeys('info@sibeeshpassion.com');
 page.getPasswordTextbox().sendKeys('1234');

 page.getSubmitButton().click();

 let valLocalStorage = browser.executeScript
                       ("return window.localStorage.getItem('LoggedInUser');");
 expect(valLocalStorage).toEqual('info@sibeeshpassion.com');
});

As you can see that, we are actually setting some valid data to the form and triggering the click event of our button, and the value of the email text box is being saved to the localStorage. In our test, we are checking the value of the localStorage by executing the script browser.executeScript("return window.localStorage.getItem('LoggedInUser');")We should get an output as below if everything is fine.

Image 3

locaStorage in Protractor

Conclusion

Thanks a lot for reading. I will come back with another post on the same topic very soon. Did I miss anything that you think is needed? Could you find this post useful? I hope you liked this article. Please share me your valuable suggestions and feedback.

Your Turn. What Do You Think?

A blog isn’t a blog without comments, but do try to stay on topic. If you have a question unrelated to this post, you’re better off posting it on C# Corner, Code Project, Stack Overflow, ASP.NET Forum instead of commenting here. Please post your question in the comments section below and I’ll definitely try to help if I can.

History

  • 18th July, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)