Authenticating Your App for Cypress Tests
Tags: angular
Published: 2022-05-17 17:27:22

---
title: Authenticating Your App for Cypress Tests
published: true
date: 2022-04-20 00:00:00 UTC
tags: angular
canonical_url: https://www.prestonlamb.com/blog/authenticating-your-app-for-cypress-tests
---

## [tldr;](#tldr)

Many Angular apps require the user to be authenticated before using the app. This can make writing and running Cypress end to end tests difficult. If your login flow requires you to leave the app to authenticate, the problem becomes even more difficult. This article will cover a solution to this problem and allow you to write your Cypress tests. The exact specifics of doing this will vary for each app, but each case should follow closely to the steps provided here:

- Create a login Cypress command
- Optionally, you can obtain a token from your auth server, and save it to localStorage (or sessionStorage, depending on your app)
- Run the login command in the `beforeEach` hook

The app I implemented this in was using the `angular-oauth2-oidc` package for authentication, and our auth server is Identity Server 4.

## [Cypress Login Command](#cypress-login-command)

The first step is to create a Cypress command that we can run to make the app believe the user is logged in. You can either use a fake token if you don't plan on calling the API or you can get a real token from the auth server. Below is an example of getting the token from the auth server and storing it in `localStorage`. You may have to save the token in another location for your app.

```ts
// authentication.commands.ts

declare namespace Cypress {
    interface Chainable {
        /**
         * Custom command to setup login
         * @example cy.login()
         */
        login(): Chainable<Element>;
    }
}

Cypress.Commands.add('login', () => {
    const options = {
        method: 'POST',
        url: Cypress.env('authServerTokenUrl'),
        body: {
            client_id: Cypress.env('client_id'),
            client_secret: Cypress.env('client_secret'),
            grant_type: Cypress.env('grant_type'),
            username: Cypress.env('username'),
            password: Cypress.env('password'),
        },
        form: true,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
    };

    cy.request(options).then((response) => {
        window.localStorage.setItem(
            'id_token_claims_obj',
            JSON.stringify({ /* userInfo object */ }),
        );
        window.localStorage.setItem('access_token', response.body.access_token);
        window.localStorage.setItem('id_token', response.body.access_token);
        window.localStorage.setItem('expires_at', '' + new Date().getTime() + 10 * 60 * 1000);
    });
});
```

Let's walk through the function together. The top portion of the code block, starting with `declare namespace Cypress`, declares the login method as part of the Cypress object. If you don't declare the login method like this calling `cy.login()` will result in an error. The next portion of the code block, starting with `Cypress.Commands.add`, adds the new `login` command to be used in your Cypress tests. An `options` object is created for making a call to the auth server to get a token. The request is made, and because it's a promise we can chain a `.then` method on to the request and get the returned data inside the callback. In my case, the token comes back on the `response.body` object as the `access_token`. Our app uses `localStorage` for storing the token so I store the token on `window.localStorage`. I also set the `expires_at` value and the user's info object. You may want to chain a `.catch` method on to the request where you can catch errors and log what happens. This will help you debug if the request fails.

All these values may have different keys in your application, but you'll likely need to set each value. I figured out which values were needed by logging in to the app and opening `localStorage`. I was able to see which values the auth package set in `localStorage` and then by process of elimination found the ones that were required for the user to be authenticated when the app loads.

If you want to use a fake token, you can remove everything from the `login` command except where the `localStorage` values are set. This method will work just fine if you don't plan on calling the application's API for any data.

## [Use the `login` Command](#use-login-command)

The next step is to actually use the `login` command you created. There are a couple of ways to do this, but the best way is to call the command in the `beforeEach` hook of your spec file.

```ts
// home.spec.ts

describe('Home', () => {
    beforeEach(() => {
        cy.login();

        // other test setup
    })

    // e2e tests
})
```

After I first created the `login` command I tried calling it in a `before` hook instead of `beforeEach`. That worked for the first test in the spec file, but `localStorage` is cleared by Cypress after each test. Because of that the app was no longer authenticated after the first test. Thus, make sure to call the login command in the beforeEach hook.

## [Cypress Environment Variables](#cypress-environment-variables)

Cypress has [multiple ways that you can provide environment variables](https://docs.cypress.io/guides/guides/environment-variables) to use in your tests. There are a lot of ways to provide the environment variables you need to request the token, but I used the `cypress.env.json` file when running the tests on my local machine and provided the values on the command line when running the tests in our CI/CD pipeline. The `cypress.env.json` file is not committed to our git repo so that the secrets are not accidentally made public.

## [Conclusion](#conclusion)

It took me a few hours to figure out the specifics of how to make the app believe the user was logged in, but this method works perfectly. We can mock all the data from the API and use a fake token, or we can use a real token and call our actual API