Skip to content

Commit 432d0dd

Browse files
Update Logging In section of Testing Your App page (#4885)
Co-authored-by: Emily Rohrbough <[email protected]> Closes #4498
1 parent 0dcf3a0 commit 432d0dd

File tree

1 file changed

+75
-85
lines changed

1 file changed

+75
-85
lines changed

content/guides/end-to-end-testing/testing-your-app.md

Lines changed: 75 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -410,111 +410,101 @@ You'll likely also want to test your login UI for:
410410

411411
Each of these likely requires a full blown e2e test.
412412

413-
Now, once you have your login completely tested - you may be tempted to think:
413+
#### Reusing the login code
414+
415+
At this point there's nothing stopping you copying and pasting the login code
416+
above into every one of your tests that needs an authenticated user. Or you
417+
could even put all your tests in one big spec file and put the login code in a
418+
`beforeEach` block. But neither of those approaches is particularly
419+
maintainable, and they're certainly not very elegant. A much better solution is
420+
to write a custom `cy.login()` [command](/api/cypress-api/custom-commands).
421+
422+
Custom commands allow you to easily encapsulate and reuse Cypress test logic.
423+
They let you add your own functionality to your test suite and then use it with
424+
the same
425+
[chainable and asynchronous API](guides/core-concepts/introduction-to-cypress#The-Cypress-Command-Queue)
426+
as the built-in Cypress commands. Lets make the above login example a custom
427+
command and add it to `cypress/support/commands.js` so it can be leveraged in
428+
any spec file:
414429

415-
> "...okay great! Let's repeat this login process before every single test!"
416-
417-
<Alert type="danger">
418-
419-
<strong class="alert-header">No! Please don\'t.</strong>
420-
421-
Do not use **your UI** to login before each test.
422-
423-
</Alert>
424-
425-
Let's investigate and tease apart why.
426-
427-
#### Bypassing your UI
428-
429-
When you're writing tests for a very **specific feature**, you _should_ use your
430-
UI to test it.
431-
432-
But when you're testing _another area of the system_ that relies on a state from
433-
a previous feature: **do not use your UI to set up this state**.
430+
```js
431+
// In cypress/support/commands.js
434432

435-
Here's a more robust example:
433+
Cypress.Commands.add('login', (username, password) => {
434+
cy.visit('/login')
436435

437-
Imagine you're testing the functionality of a **Shopping Cart**. To test this,
438-
you need the ability to add products to that cart. Well where do the products
439-
come from? Should you use your UI to login to the admin area, and then create
440-
all of the products including their descriptions, categories, and images? Once
441-
that's done should you then visit each product and add each one to the shopping
442-
cart?
436+
cy.get('input[name=username]').type(username)
443437

444-
No. You shouldn't do that.
438+
// {enter} causes the form to submit
439+
cy.get('input[name=password]').type(`${password}{enter}`, { log: false })
445440

446-
<Alert type="warning">
441+
// we should be redirected to /dashboard
442+
cy.url().should('include', '/dashboard')
447443

448-
<strong class="alert-header">Anti-Pattern</strong>
444+
// our auth cookie should be present
445+
cy.getCookie('your-session-cookie').should('exist')
449446

450-
Don't use your UI to build up state! It's enormously slow, cumbersome, and
451-
unnecessary.
447+
// UI should reflect this user being logged in
448+
cy.get('h1').should('contain', username)
449+
})
452450

453-
Read about [best practices](/guides/references/best-practices) here.
451+
// In your spec file
454452

455-
</Alert>
453+
it('does something on a secured page', function () {
454+
const { username, password } = this.currentUser
455+
cy.login(username, password)
456456

457-
Using your UI to **log in** is the _exact same scenario_ as what we described
458-
previously. Logging in is a prerequisite of state that comes before all of your
459-
other tests.
457+
// ...rest of test
458+
})
459+
```
460460

461-
Because Cypress isn't Selenium, we can actually take a huge shortcut here and
462-
skip needing to use our UI by using [`cy.request()`](/api/commands/request).
461+
#### Improving performance
463462

464-
Because [`cy.request()`](/api/commands/request) automatically gets and sets
465-
cookies under the hood, we can actually use it to build up state without using
466-
your browser's UI, yet still have it perform exactly as if it came from the
467-
browser!
463+
You're probably wondering what happened to our advice about logging in "only
464+
once". The custom command above will work just fine for testing your secured
465+
pages, but if you have more than a handful of tests, logging in before every
466+
test is going to increase the overall run time of your suite.
468467

469-
Let's revisit the example from above but assume we're testing some other part of
470-
the system.
468+
Luckily, Cypress provides the [`cy.session()`](/api/commands/session) command, a
469+
powerful performance tool that lets you cache the browser context associated
470+
with your user and reuse it for multiple tests without going through multiple
471+
login flows! Let's modify the custom `cy.login()` command from our previous
472+
example to use `cy.session()`:
471473

472474
```js
473-
describe('The Dashboard Page', () => {
474-
beforeEach(() => {
475-
// reset and seed the database prior to every test
476-
cy.exec('npm run db:reset && npm run db:seed')
477-
478-
// seed a user in the DB that we can control from our tests
479-
// assuming it generates a random password for us
480-
cy.request('POST', '/test/seed/user', { username: 'jane.lane' })
481-
.its('body')
482-
.as('currentUser')
483-
})
484-
485-
it('logs in programmatically without using the UI', function () {
486-
// destructuring assignment of the this.currentUser object
487-
const { username, password } = this.currentUser
488-
489-
// programmatically log us in without needing the UI
490-
cy.request('POST', '/login', {
491-
username,
492-
password,
493-
})
494-
495-
// now that we're logged in, we can visit
496-
// any kind of restricted route!
497-
cy.visit('/dashboard')
498-
499-
// our auth cookie should be present
500-
cy.getCookie('your-session-cookie').should('exist')
501-
502-
// UI should reflect this user being logged in
503-
cy.get('h1').should('contain', 'jane.lane')
504-
})
475+
Cypress.Commands.add('login', (username, password) => {
476+
cy.session(
477+
username,
478+
() => {
479+
cy.visit('/login')
480+
cy.get('input[name=username]').type(username)
481+
cy.get('input[name=password]').type(`${password}{enter}`, { log: false })
482+
cy.url().should('include', '/dashboard')
483+
cy.get('h1').should('contain', username)
484+
},
485+
{
486+
validate: () => {
487+
cy.getCookie('your-session-cookie').should('exist')
488+
},
489+
}
490+
)
505491
})
506492
```
507493

508-
Do you see the difference? We were able to login without needing to actually use
509-
our UI. This saves an enormous amount of time visiting the login page, filling
510-
out the username, password, and waiting for the server to redirect us _before
511-
every test_.
494+
<Alert type="info">
495+
496+
<strong class="alert-header">Third-Party Login</strong>
512497

513-
Because we previously tested the login system end-to-end without using any
514-
shortcuts, we already have 100% confidence it's working correctly.
498+
If your app implements login via a third-party authentication provider such as
499+
[Auth0](https://auth0.com/) or [Okta](https://www.okta.com/), you can use the
500+
[`cy.origin()`](/api/commands/origin) command to include their login pages as
501+
part of your authentication tests.
502+
503+
</Alert>
515504

516-
Use the methodology above when working with any area of your system that
517-
requires the state to be set up elsewhere. Just remember - don't use your UI!
505+
There's a lot going on here that's out of the scope for this introduction.
506+
Please check out the [`cy.session()`](/api/commands/session) documentation for a
507+
more in-depth explanation.
518508

519509
<Alert type="info">
520510

0 commit comments

Comments
 (0)