How to login programmatically with Cypress

walmyrlimaesilv

Walmyr

Posted on January 9, 2022

How to login programmatically with Cypress

Learn how to make your automated tests faster by authenticating via API

Automated graphical user interface tests must be independent of each other. In addition, such tests must relly as little as possible on the graphical user interface to reach the desired state for the test itself to take place.

It seems counter-intuitive, but that's precisely it.

From the graphical user interface, we should test it only once. More than that is waste.

However, in most web applications, the user must be authenticated to access certain functionality. So, how to authenticate such a user without going through the login page?

That's precisely what I will show you in this pinch of Cypress.

Note: It is worth remembering that this is just an alternative and that it may not be suitable for your use case. For more examples, I recommend reading the Testing Strategies section of the Cypress official documentation.

To ease the explanation, I will use a project I have recently contributed to β€” the BR Modelo Web.

Let's imagine the following test case.

// cypress/e2e/programmaticLogin.cy.js

it('successfully logs in programmatically', () => {
  cy.intercept('GET', `${Cypress.env('apiUrl')}/models?userId=*`)
    .as('getUserModels')

  cy.request('POST', `${Cypress.env('apiUrl')}/users/login`, {
    username: Cypress.env('userEmail'),
    password: Cypress.env('userPassword'),
  }).then(({ body }) => {
    cy.setCookie('sessionId', body.sessionId)
    cy.setCookie('userId', body.userId)
    cy.setCookie('userName', body.userName)
  })

  cy.visit('/#!/main')
  cy.wait('@getUserModels')
  cy.contains('h2', 'Models').should('be.visible')
}
Enter fullscreen mode Exit fullscreen mode

Now, let's understand what this code does.

First, inside the test body, that is, inside the it block, I use the cy.intercept command. With such a command, I can β€œlisten” πŸ‘‚ to network calls, such as a GET request to the application's API URL that fetches the models of the logged-in user. Then I give an alias to that intercept. The alias is getUserModels.

Then comes the part where the programmatic authentication happens.

In this part, I use the cy.request functionality to make a POST request to the login URL, passing the username and password properties in the request body, both coming from variables (using the Cypress.env() functionality). I do this not to expose sensitive data.

Then, I chain to the cy.request() command a .then(), which takes as argument an arrow function, which takes as an argument the response's body of the cy.request(), using JavaScript's object destructuring.

In the body of this arrow function, I use the cy.setCookie() functionality, as the name suggests, to set some cookies based on the body of the request-response. These are precisely the cookies set when the user logs in via the graphical user interface.

With cookies set, I visit the application's homepage.

Finally, I do some checks.

First, I wait for the intercept request created earlier to occur, with cy.wait(), passing it the alias created earlier ('@getUserModels').

And then, I check that a particular element is visible (an h2 with the text Models), which is only visible to authenticated users, proving that the login was successful.

Done! πŸŽ‰

Attention: When testing a login functionality, it is recommended that such testing takes place via the graphical user interface. However, for all other features that require the authenticated user, use login programmatically and save a few seconds on each test!

Bonus - Custom Command

Since more than one test suite will need to login programmatically, we can move that logic to a custom command, which can be reused as many times as needed.

Here's what the test code would look like.

// cypress/e2e/programmaticLogin.cy.js

it('successfully logs in via GUI', () => {
  cy.intercept('GET', `${Cypress.env('apiUrl')}/models?userId=*`)
    .as('getUserModels')
  cy.loginViaAPI()
  cy.wait('@getUserModels')
  cy.contains('h2', 'Models').should('be.visible')
})
Enter fullscreen mode Exit fullscreen mode

And the custom command.

// cypress/support/commands.js

Cypress.Commands.add('loginViaAPI', (
  email = Cypress.env('userEmail'),
  password = Cypress.env('userPassword')
) => {
  cy.request('POST', `${Cypress.env('apiUrl')}/users/login`, {
    username: email,
    password,
  }).then(({ body }) => {
    cy.setCookie('sessionId', body.sessionId)
    cy.setCookie('userId', body.userId)
    cy.setCookie('userName', body.userName)
    cy.visit('/#!/main')
  })
})
Enter fullscreen mode Exit fullscreen mode

In the test, now all the logic of cy.request and cy.setCookie is abstracted. I just call the cy.loginViaAPI() command, and it manages to do what needs to be done to authenticate the user.

In addition to having the previous logic of programmatic login, the custom command can now also receive an email and password as arguments. However, if no arguments are passed, such values already have defaults coming from variables.

Also, I decided to move the visit to the home page to the custom command.

See the test running and authenticating without going through the login page. It looks like magic! πŸͺ„

Image description

That's it!

I hope you enjoyed it.


Access the final version in this public repository on my GitHub profile.

Or the BR Modelo Web App project.

Take the opportunity to leave a star! ⭐


Did you like the content? Leave a comment.


Curious and want to learn more about Cypress Testing Automation? Check out my courses on Udemy.


πŸ‘‹ Until next time and happy testing!


This post was originally published in Portuguese at the Talking About Testing blog.

πŸ’– πŸ’ͺ πŸ™… 🚩
walmyrlimaesilv
Walmyr

Posted on January 9, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related