@@ -410,111 +410,101 @@ You'll likely also want to test your login UI for:
410
410
411
411
Each of these likely requires a full blown e2e test.
412
412
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:
414
429
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
434
432
435
- Here's a more robust example:
433
+ Cypress .Commands .add (' login' , (username , password ) => {
434
+ cy .visit (' /login' )
436
435
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)
443
437
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 })
445
440
446
- <Alert type =" warning " >
441
+ // we should be redirected to /dashboard
442
+ cy .url ().should (' include' , ' /dashboard' )
447
443
448
- <strong class =" alert-header " >Anti-Pattern</strong >
444
+ // our auth cookie should be present
445
+ cy .getCookie (' your-session-cookie' ).should (' exist' )
449
446
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
+ })
452
450
453
- Read about [ best practices ] ( /guides/references/best-practices ) here.
451
+ // In your spec file
454
452
455
- </Alert >
453
+ it (' does something on a secured page' , function () {
454
+ const { username , password } = this .currentUser
455
+ cy .login (username, password)
456
456
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
+ ```
460
460
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
463
462
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.
468
467
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() ` :
471
473
472
474
``` 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
+ )
505
491
})
506
492
```
507
493
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 >
512
497
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 >
515
504
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.
518
508
519
509
<Alert type =" info " >
520
510
0 commit comments