diff --git a/content/api/commands/session.md b/content/api/commands/session.md index 38980e31e1..f1090762af 100644 --- a/content/api/commands/session.md +++ b/content/api/commands/session.md @@ -27,6 +27,15 @@ cy.session(id, setup, options) ** Correct Usage** ```javascript +// Caching session when logging in via page visit +cy.session(name, () => { + cy.visit('/login') + cy.get('[data-test=name]').type(name) + cy.get('[data-test=password]').type('s3cr3t') + cy.get('form').contains('Log In').click() + cy.url().should('contain', '/login-successful') +}) + // Caching session when logging in via API cy.session(username, () => { cy.request({ @@ -37,15 +46,6 @@ cy.session(username, () => { window.localStorage.setItem('authToken', body.token) }) }) - -// Caching session when logging in via page visit -cy.session(name, () => { - cy.visit('/login') - cy.get('[data-test=name]').type(name) - cy.get('[data-test=password]').type('s3cr3t') - cy.get('form').contains('Log In').click() - cy.url().should('contain', '/login-successful') -}) ``` ** Incorrect Usage** @@ -98,9 +98,10 @@ serialize into an identifier, so exercise care with the data you specify. This function is called whenever a session for the given `id` hasn't yet been cached, or if it's no longer valid (see the `validate` option). After `setup` -runs, Cypress will preserve all cookies, `sessionStorage`, and `localStorage`, -so that subsequent calls to `cy.session()` with the same `id` will bypass -`setup` and just restore the cached session data. +and `validate` runs for the first time, Cypress will preserve all cookies, +`sessionStorage`, and `localStorage`, so that subsequent calls to `cy.session()` +with the same `id` will bypass setup and just restore and validate the cached +session data. The page is cleared before `setup` when `testIsolation='on'` and is not cleared when `testIsolation='off'`. @@ -131,13 +132,11 @@ command with a call to `cy.session()`. ```javascript Cypress.Commands.add('login', (username, password) => { - cy.request({ - method: 'POST', - url: '/login', - body: { username, password }, - }).then(({ body }) => { - window.localStorage.setItem('authToken', body.token) - }) + cy.visit('/login') + cy.get('[data-test=name]').type(username) + cy.get('[data-test=password]').type(password) + cy.get('form').contains('Log In').click() + cy.url().should('contain', '/login-successful') }) ``` @@ -146,13 +145,11 @@ Cypress.Commands.add('login', (username, password) => { ```javascript Cypress.Commands.add('login', (username, password) => { cy.session([username, password], () => { - cy.request({ - method: 'POST', - url: '/login', - body: { username, password }, - }).then(({ body }) => { - window.localStorage.setItem('authToken', body.token) - }) + cy.visit('/login') + cy.get('[data-test=name]').type(username) + cy.get('[data-test=password]').type(password) + cy.get('form').contains('Log In').click() + cy.url().should('contain', '/login-successful') }) }) ``` @@ -164,13 +161,11 @@ Cypress.Commands.add('login', (username, password) => { cy.session( [username, password], () => { - cy.request({ - method: 'POST', - url: '/login', - body: { username, password }, - }).then(({ body }) => { - window.localStorage.setItem('authToken', body.token) - }) + cy.visit('/login') + cy.get('[data-test=name]').type(username) + cy.get('[data-test=password]').type(password) + cy.get('form').contains('Log In').click() + cy.url().should('contain', '/login-successful') }, { validate() { @@ -227,6 +222,8 @@ const login = (name, password) => { }, { validate() { + // Protected URLs should return a 40x http code if user is unauthorized, + // and by default this will cause cy.visit() to fail cy.visit('/account-details') }, } @@ -234,78 +231,6 @@ const login = (name, password) => { } ``` -### Asserting the session inside setup - -Because `cy.session()` caches session data immediately after the `setup` -function completes, it's a best practice to assert that the login process has -completed at the end of session setup, to ensure that `setup` doesn't return -before the session data is available to be cached. - -Asserting sessions in this way can help simplify your login custom command, and -reduce the need to -[conditionally cache sessions](#Conditionally-caching-a-session). - -```javascript -cy.session('user', () => { - cy.visit('/login') - cy.get('[data-test=name]').type(name) - cy.get('[data-test=password]').type('p4ssw0rd123') - cy.get('#login').click() - // Wait for the post-login redirect to ensure that the - // session actually exists to be cached - cy.url().should('contain', '/login-successful') -}) -``` - -### Conditionally caching a session - -Specs usually contain two types of tests where logins are necessary: - -1. Testing functionality that only exists for logged-in users -2. Testing the act of logging in - -For the first, caching sessions can be incredibly useful for reducing the amount -of time it takes to run tests. However, for the second, it may be necessary to -_not_ cache the session, so that other things can be asserted about the login -process. - -In this case, it can be helpful to create a custom login command that will -conditionally cache the session. However, wherever possible, it's better to -[assert the session inside setup](#Asserting-the-session-inside-setup). - -```javascript -Cypress.Commands.add('login', (name, { cacheSession = true } = {}) => { - const login = () => { - cy.visit('/login') - cy.get('[data-test=name]').type(name) - cy.get('[data-test=password]').type('p4ssw0rd123') - cy.get('#login').click() - } - if (cacheSession) { - cy.session(name, login) - } else { - login() - } -}) - -// Testing the login flow itself -describe('login', () => { - it('should redirect to the correct page after logging in', () => { - cy.login('user', { cacheSession: false }) - cy.url().should('contain', '/login-successful') - }) -}) - -// Testing something that simply requires being logged in -describe('account details', () => { - it('should have the correct document title', () => { - cy.login('user') - cy.visit('/account') - cy.title().should('eq', 'User Account Details') - }) -}) -``` - ### Switching sessions inside tests Because `cy.session()` clears the page and all session data before running @@ -472,9 +397,10 @@ const loginByApi = (name, password) => { ### Where to call `cy.visit()` -If you call [`cy.visit()`](/api/commands/visit) immediately after `cy.session()` -in your login function or custom command, it will effectively behave the same as -a login function without any session caching. +Intuitively it seems that you should call [`cy.visit()`](/api/commands/visit) +immediately after `cy.session()` in your login function or custom command, so it +behaves (from the point of view of the subsequent test) exactly the same as a +login function without `cy.session()`. ```javascript const login = (name) => { @@ -501,10 +427,11 @@ it('should test something else on the /home page', () => { }) ``` -However, any time you want to test something on a different page, you will need -to call `cy.visit()` at the beginning of that test, which will then be -effectively calling `cy.visit()` twice in a row, which will result in slightly -slower tests. +However, if you want to test something on a different page, you will need to +call `cy.visit()` at the beginning of that test, which means you will be calling +`cy.visit()` a **second** time in your test. Since `cy.visit()` waits for the +visited page to become active before continuing, this could add up to an +unacceptable waste of time. ```javascript // ...continued... @@ -515,10 +442,10 @@ it('should test something on the /other page', () => { }) ``` -Tests will often be faster if you call `cy.visit()` only when necessary. This -works especially well when +Tests will obviously be faster if you call `cy.visit()` only when necessary. +This can be easily realised by [organizing tests into suites](/guides/core-concepts/writing-and-organizing-tests#Test-Structure) -and calling `cy.visit()` after logging in inside a +and calling `cy.visit()` **after** logging in, inside a [`beforeEach`](/guides/core-concepts/writing-and-organizing-tests#Hooks) hook. ```javascript