Skip to content

Update cy.session API docs for v12 #4851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 5, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 42 additions & 115 deletions content/api/commands/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ cy.session(id, setup, options)
**<Icon name="check-circle" color="green"></Icon> 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({
Expand All @@ -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')
})
```

**<Icon name="exclamation-triangle" color="red"></Icon> Incorrect Usage**
Expand Down Expand Up @@ -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'`.
Expand Down Expand Up @@ -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')
})
```

Expand All @@ -146,13 +145,11 @@ Cypress.Commands.add('login', (username, password) => {
```javascript
Cypress.Commands.add('login', (username, password) => {
cy.session([username, password], () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any ID with username & password should be removed from the examples IMO. We dont' have a log:false option and we don't want to direct users to use sensitive information which they may not want video recorded/face-up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a legitimate concern but we currently do this absolutely everywhere - guides, API docs, recipes, RWA, Kitchen Sink etc etc. If we're going to ban this practice there needs to be a larger conversation about that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to ban it, but we should remove it from our examples to provide good guidance. It is something we need to be updating in our documentation and examples.

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')
})
})
```
Expand All @@ -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() {
Expand Down Expand Up @@ -227,85 +222,15 @@ 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')
},
}
)
}
```

### 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
Expand Down Expand Up @@ -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) => {
Expand All @@ -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...
Expand All @@ -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
Expand Down