Skip to content

Cypress.io End to End Testing

The following tests are a recreation of the Angular End to End tests for the Tour of Heroes application. Those test were originally written by Dhomale and relied on Karma and Protractor for testing, but these tests have since been converted by Open Water Foundation to utilize Cypress.io.

To begin you will want to install cypress into your Tour of Heroes application. Navigate to your project folder and run the following command:

npm install -D Cypress

You will then want to open Cypress using the command bellow or any of the possible ways stated above:

npx cypres open

Once you have run cypress you will notice that the a 'Cypress' repository will be created in your project folder, and its contents will reflect the following:

├── cypress
|   ├── fixtures/ ........................ Fixtures files
|   ├── integration/ ...................... Test files are located in cypress/integration
|   ├── plugins/ ....................... Automatic plugins file
|   ├── support/ ........................... Automatic Support File





Cypress Folder Structure

Fixture Files

Fixture files will be automatically located in cypress/fixures but can be configured to another directory. "Fixtures are used as external pieces of static data that can be used by your tests" (Cypress.io).

Test Files

Fixture files will be automatically located in cypress/integration but can be configured to another directory. Test files can be written as:

  • .js
  • .jsx
  • .coffee
  • .cjsx
Plugin Files

"By default Cypress will automatically include the plugins file cypress/plugins/index.js before every single spec file it runs. We do this purely as a convenience mechanism so you don’t have to import this file in every single one of your spec files" (Cypress.io).

Support File

"By default Cypress will automatically include the support file cypress/support/index.js. This file runs before every single spec file . We do this purely as a convenience mechanism so you don’t have to import this file in every single one of your spec files.

The initial imported support file can be configured to another file.

The support file is a great place to put reusable behavior such as Custom Commands or global overrides that you want applied and available to all of your spec files" (Cypress.io).

You can define your behaviors in a beforeEach within any of the cypress/support files:

beforeEach(function () {
  cy.log('I run before every test in every spec file!!!!!!')
})

Writing tests

To begin writing our tests you will want to navigate to the integration folder located within the newly created Cypress folder. This is where your tests will go. Also notice that an examples folder can be located here as well. This example folder is there incase you need a reference for how to test your application. With examples ranging from:

  • actions
  • aliasing
  • assertions
  • connectors
  • cookies
  • cypress_api
  • files
  • navigation
  • And many more!

Be sure to look over these examples for further practice and familiarity

The standard naming convention for cypress is generally the same convention you have been following so far throughout this documentation. E2E testing files will need to end in spec.js in order to be rendered properly. In this example you will want create a folder named app.e2e.spec.js

To begin you will want a describe function to group the tests together. This is know as a test suite. The describe function take two parameters, a string and a callback function. Here we do the following:

describe('Tour of Heroes App Testing', () => {

});

Next we will follow the standards of testing and use it functions to perform the tests. The purpose of it function's are to test a behavior of your code. "it" refers to the object/component/class/whatever you are testing rather than a method, and should read like a sentence.

Test 1:

For our first test we will test that the Tour of heroes application "Should have the title 'Tour of Heroes'". This test will be placed within the test suite:

describe('Tour of Heroes App Testing', () => {

    it(`Should have the title 'Tour Of Heroes'`, () => {
        cy.visit('localhost:4200')
        cy.contains('Tour of Heroes');
    });

});

Notice that cypress navigates to the application site to begin testing:

cy.visit('localhost:4200')

If you are navigating to a localhost be sure that the site is running prior to running tests, otherwise the test will have no where to navigate to.

Additionally this test also uses cy.contains to locate the presence of the 'Tour of Heroes' title.

.contains()

.contains gets the DOM element containing the text. DOM elements can contain more than the desired text and still match. For more on cy.contains() refer to the Cypress.io documentation on commands like contains.

Test 2 and 3:

Should have the title 'Top Heroes', and Should have the title 'Hero Search'.

describe('Tour of Heroes App Testing', () => {

    it(`Should have the title 'Tour Of Heroes'`, () => {
        cy.visit('localhost:4200')
        cy.contains('Tour of Heroes');
    });

    it(`Should have title 'Top Heroes'`, () => {
        cy.contains('Top Heroes');
    });

     it(`Should have title 'Hero Search'`, () => {
        cy.contains('Hero Search');
    });


});

Test 4

Tests 4 and 5 call separate functions that implement testing actions for us. Test 4 should select and route to a target hero's Hero details.

To begin we will create global variables, in this instance a specific Hero name and its corresponding Hero ID. We will also be creating the it function and call back function that will select the specified target Hero.

// global variables for specified Hero Name and ID
var targetHero =  'Magneta';
var targetHeroID = 15;

describe('Tour of Heroes App Testing', () => {

    it(`Should have the title 'Tour Of Heroes'`, () => {
        cy.visit('localhost:4200')
        cy.contains('Tour of Heroes');
    });

    it(`Should have title 'Top Heroes'`, () => {
        cy.contains('Top Heroes');
    });

     it(`Should have title 'Hero Search'`, () => {
        cy.contains('Hero Search');
    });

    // Next step is to create the dashboardSelectHero Function
    it (`Should select and route to (${targetHero}) Hero details`,      
    dashboardSelecTargetHero);

});


The above test will call the dashboardSelectHero function that handles the action of the test. This function will also still be located within the test suite. The function aims to find the targerHero, click that Hero, and ensure that the site routed properly to the Hero Detail page:

 function dashboardSelecTargetHero() {
        cy.contains(targetHero).click();
        // Ensuring Url routed properly
        cy.url().should('include', '/detail/15');
        cy.url().should('eq','http://localhost:4200/detail/15');

    }

.click()

Click a DOM element.

Syntax:

  • .click()
  • .click(options)
  • .click(position)
  • .click(position, options)
  • .click(x, y)
  • .click(x, y, options)

For more on cy.click() refer to the Cypress.io documentation on commands like .click().

.url()

Get the current URL of the page that is currently active

Syntax:

  • cy.url()
  • cy.url(option)

For more on cy.url() refer to the Cypress.io documentation on commands like .url().

.should()

Create an assertion. Assertions are automatically retried until they pass or time out.

Syntax:

  • .should(chainers)
  • .should(chainers, value)
  • .should(chainers, method, value)
  • .should(callbackFn)

For more on cy.should() refer to the Cypress.io documentation on commands like .should().

Test 5

Should update hero name (newHeroName) in Hero List

// global variables for specified Hero Name and ID
var targetHero =  'Magneta';
var targetHeroID = 15;

// Additional global variables for test 5
describe('Tour of Heroes App Testing', () => {

    it(`Should have the title 'Tour Of Heroes'`, () => {
        cy.visit('localhost:4200')
        cy.contains('Tour of Heroes');
    });

    it(`Should have title 'Top Heroes'`, () => {
        cy.contains('Top Heroes');
    });

     it(`Should have title 'Hero Search'`, () => {
        cy.contains('Hero Search');
    });


    it (`Should select and route to (${targetHero}) Hero details`,      
    dashboardSelecTargetHero);

    // Test 5: need to create 'updateHeroNameInDetailView' function
     it (`Should update hero name (${newHeroName}) in details view`, 
     updateHeroNameInDetailView);




});



The above test will call theupdateHeroNameInDetailView function which handles the actions of the test. This function will also still be located within the test suite. updateHeroNameInDetailView aims to find the update the name of the hero in the Hero Detail view. It does so by clearing the input, typing in a new name clicking the save button, and then ensuring that the site routed properly to the new updated Hero Detail page:

    function updateHeroNameInDetailView() {

        cy.get('.ng-untouched').clear().type(newHeroName);
        cy.get('app-hero-detail > :nth-child(1) > :nth-child(5)').click();

        cy.get('[ng-reflect-router-link="/detail/15"] > .module >                       
        h4').contains(newHeroName);
        // should navigate properly
        cy.url().should('include', '/dashboard');
        cy.url().should('eq','http://localhost:4200/dashboard');

    }



.type ()

Type into a DOM element.

Syntax:

  • .type(text)
  • .type(text, options)

For more on cy.type() refer to the Cypress.io documentation on commands like .type().

.get()

Get one or more DOM elements by selector or alias.

Syntax:

  • cy.get(selector)
  • cy.get(alias)
  • cy.get(selector, options)
  • cy.get(alias, options)

For more on cy.get() refer to the Cypress.io documentation on commands like .get().

Note:

The selectors within the get function, such as cy.get('[ng-reflect-router-link="/detail/15"] can be obtained through a tool referred to as the Selector Playground.

"The Selector Playground is an interactive feature that helps you:"

  • Determine a unique selector for an element.
  • See what elements match a given selector.
  • See what element matches a string of text.

For a quick overview and video on the Selector Playground please refer to this link.

Following Tests

The rest of the presented tests will utilize functions tools that have already been covered in the first few tests. Be sure to study the tests closely to understand the functionality of these tests. Most will read through as if in English, and a description of each test is presented in the it function. Upon saving your tests, Cypress will automatically run your test. You will see a green check mark if these tests pass.

Your final testing code should reflect the following code below. The full code for this Cypress end to end testing example can be found on github in the owf-learn-angular repository under tour-of-heroes-cypress-e2e-testing folder.

var targetHero =  'Magneta';
var targetHeroID = 15;
var nameSuffix = 'X'
var newHeroName = targetHero + nameSuffix;

describe('Tour of Heroes App', () => {

    beforeEach(() => {
    // This is done in the beforeEach function so that the code can be executed before each test runs
    // cy.visit('localhost:4200')
    });

    it(`Should have title 'Tour Of Heroes'`, () => {
        cy.visit('localhost:4200')
        cy.contains('Tour of Heroes');
    });

    it(`Should have title 'Top Heroes'`, () => {

        cy.contains('Top Heroes');
    });

    it(`Should have title 'Hero Search'`, () => {

        cy.contains('Hero Search');
    });

    it (`Should select and route to (${targetHero}) Hero details`, dashboardSelecTargetHero);

    it (`Should update hero name (${newHeroName}) in details view`, updateHeroNameInDetailView);

    it (`Should save and show (${newHeroName}) in Heroes List`, () => {

        cy.get('[routerlink="/heroes"]').click();
        cy.get(':nth-child(5) > a').contains(newHeroName);

    });

    it (`Should delete (${newHeroName}) in details view`, () => {
        cy.get(':nth-child(5) > .delete').click();
        // expect(page.allHeroes.count()).toEqual(9, 'number of heroes'); // og file

    });

    it (`Should add back ${targetHero}`, () => {
        cy.get('input').type(targetHero);
        cy.get('app-heroes > div > button').click();
        cy.contains('Magneta');
    });

    it (`Should add the hero name Alice`, () => {
        cy.get('input').type('Alice');
        cy.get('app-heroes > div > button').click();
        cy.contains('Alice');
    });

    it (`Should search for 'Ma'`, () => {
        cy.visit('localhost:4200');
        cy.get('#search-box').type('Ma');

        // Making sure options with 'Ma' are presented
        cy.get(':nth-child(1) > a').contains('Magneta');
        cy.get('.search-result > :nth-child(2) > a').contains('RubberMan');
        cy.get(':nth-child(3) > a').contains('Dynama');
        cy.get(':nth-child(4) > a').contains('Magma');

    });

    it (`Should continue search with 'g'`, () => {
        cy.get('#search-box').type('g');

        // Making sure options with 'Mag' are presented
        cy.get(':nth-child(1) > a').contains('Magneta');
        cy.get(':nth-child(4) > a').contains('Magma');


    })
    it (`Should continue search with 'n' and selects 'Magneta'`, () => {
        cy.get('#search-box').type('n');

        // Making sure options with 'Magn' are presented
        cy.get(':nth-child(1) > a').contains('Magneta').click();

    });

    it (`Should Navigate to Magneta details view properly`, () => {
         // Ensuring Url routed properly
         cy.url().should('include', '/detail/15');
         cy.url().should('eq','http://localhost:4200/detail/15')

    });


    function dashboardSelecTargetHero() {

        cy.contains('Magneta').click();
        // Ensuring Url routed properly
        cy.url().should('include', '/detail/15');
        cy.url().should('eq','http://localhost:4200/detail/15');

    }

    function updateHeroNameInDetailView() {

        cy.get('.ng-untouched').clear().type(newHeroName);
        cy.get('app-hero-detail > :nth-child(1) > :nth-child(5)').click();

        cy.get('[ng-reflect-router-link="/detail/15"] > .module > h4').contains(newHeroName);
        // should navigate properly
        cy.url().should('include', '/dashboard');
        cy.url().should('eq','http://localhost:4200/dashboard');

    }


  });