From 1a7431f92add5436ab6912b8d9dc324edc4a33f6 Mon Sep 17 00:00:00 2001 From: DEBRIS APRON Date: Wed, 9 Nov 2022 16:42:33 -0800 Subject: [PATCH 1/2] Update cy.session API docs for v12 --- content/api/commands/session.md | 170 +++++++++++--------------------- 1 file changed, 55 insertions(+), 115 deletions(-) diff --git a/content/api/commands/session.md b/content/api/commands/session.md index c95e115712..88017d58f0 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'`. @@ -110,10 +111,10 @@ before `setup` runs, regardless of the testIsolation configuration. ** options** **_(Object)_** -| Option | Default | Description | -| ------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `validate` | `undefined` | Validates the newly-created or restored session.

Function to run immediately after the session is created and `setup` function runs or after a session is restored and the page is cleared. If this returns `false`, throws an exception, contains any failing Cypress command, or returns a Promise which rejects or resolves to `false`, the session is considered invalid.

- If validation fails immediately after `setup`, the test will fail.
- If validation fails after restoring a session, `setup` will re-run. | -| `cacheAcrossSpecs` | `false` | When enabled, the newly created session is considered "global" and can be restored in any spec during the test execution in the same Cypress run on the same machine. Use this option for a session that will be used multiple times, across many specs. | +| Option | Default | Description | +| ------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `validate` | `undefined` | Validates the newly-created or restored session.

Function that runs immediately after the `setup` function is run **and** after a cached session is restored. If this returns `false`, throws an exception, contains any failing Cypress command, or returns a Promise which rejects or resolves to `false`, the session is considered invalid.

- If validation fails immediately after setup, the test will fail.
- If validation succeeds immediatedly after setup, the session will not be saved until `validate` is finished.
- If validation fails after restoring a session, `setup` will re-run. | +| `cacheAcrossSpecs` | `false` | When enabled, the newly created session is considered "global" and can be restored in any spec during the test execution in the same Cypress run on the same machine. Use this option for a session that will be used multiple times, across many specs. | ### Yields [](/guides/core-concepts/introduction-to-cypress#Subject-Management) @@ -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 call 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 @@ -340,13 +265,27 @@ it('should transfer money between users', () => { ### Validating the session -If the `validate` function return `false`, throws an exception, returns a -Promise that resolves to `false` or rejects, or contains any failing Cypress -command, the session will be considered invalid, and `setup` will be re-run. +The `validate` function has a slightly different function depending on whether +it is being run immediately after `setup`, or after restoring the saved session. + +After `setup`, the session is not saved until `validate` has returned or +resolved. This allows you to use `validate` to ensure that the login flow is +fully complete by, for example, repeatedly checking that the user's session +token has been set. This must be done with a +[retry-able command](/guides/core-concepts/retry-ability), or your assertion may +fail the test simply because the page is running a little slow. Once your +assertion passes, the session is cached. + +When restoring a saved session, if the `validate` function return `false`, +throws an exception, returns a Promise that resolves to `false` or rejects, or +contains any failing Cypress command, the session will be considered invalid, +and `setup` will be re-run. So your `validate` function serves a dual purpose, +it ensures that the initial setup has worked correctly **and** it checks that +the session remains valid before re-use. -The page is not cleared after the `validate` function is executed. If you use -`cy.visit()` in your validation, your test will continue on the visited page -once `cy.session()` succeeds. +Note that the page is not cleared after the `validate` function is executed. If +you use `cy.visit()` in your validation, your test will continue on the visited +page once `cy.session()` succeeds. Here are a few `validate` examples: @@ -368,6 +307,7 @@ function validate() { } // Or just return false if the session is invalid +// TODO: How do they check this repeatedly? Maybe we need a `cy.retry` method? function validate() { if (!MyApp.isSessionValid()) { return false From 350881a0276336135a431409a756b467c0ee42ba Mon Sep 17 00:00:00 2001 From: DEBRIS APRON Date: Tue, 29 Nov 2022 16:12:23 -0800 Subject: [PATCH 2/2] Address review feedback --- content/api/commands/session.md | 66 +++++++++++++++++---------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/content/api/commands/session.md b/content/api/commands/session.md index 88017d58f0..56878e5213 100644 --- a/content/api/commands/session.md +++ b/content/api/commands/session.md @@ -111,10 +111,10 @@ before `setup` runs, regardless of the testIsolation configuration. ** options** **_(Object)_** -| Option | Default | Description | -| ------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `validate` | `undefined` | Validates the newly-created or restored session.

Function that runs immediately after the `setup` function is run **and** after a cached session is restored. If this returns `false`, throws an exception, contains any failing Cypress command, or returns a Promise which rejects or resolves to `false`, the session is considered invalid.

- If validation fails immediately after setup, the test will fail.
- If validation succeeds immediatedly after setup, the session will not be saved until `validate` is finished.
- If validation fails after restoring a session, `setup` will re-run. | -| `cacheAcrossSpecs` | `false` | When enabled, the newly created session is considered "global" and can be restored in any spec during the test execution in the same Cypress run on the same machine. Use this option for a session that will be used multiple times, across many specs. | +| Option | Default | Description | +| ------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `validate` | `undefined` | Validates the newly-created or restored session.

Function that runs immediately after the `setup` function is run **and** after a cached session is restored. If this returns `false`, throws an exception, contains any failing Cypress command, or returns a Promise which rejects or resolves to `false`, the session is considered invalid.

- If validation fails immediately after setup, the test will fail.
- If validation succeeds immediately after setup, the session will not be saved until `validate` is finished.
- If validation fails after restoring a session, `setup` will re-run. | +| `cacheAcrossSpecs` | `false` | When enabled, the newly created session is considered "global" and can be restored in any spec during the test execution in the same Cypress run on the same machine. Use this option for a session that will be used multiple times, across many specs. | ### Yields [](/guides/core-concepts/introduction-to-cypress#Subject-Management) @@ -223,7 +223,7 @@ const login = (name, password) => { { validate() { // Protected URLs should return a 40x http code if user is unauthorized, - // and by default this will call cy.visit to fail + // and by default this will cause cy.visit() to fail cy.visit('/account-details') }, } @@ -265,8 +265,10 @@ it('should transfer money between users', () => { ### Validating the session -The `validate` function has a slightly different function depending on whether -it is being run immediately after `setup`, or after restoring the saved session. +The `validate` function is run in two different contexts, after the initial +session setup and before reusing the saved session. In both of these contexts it +should test only for the presence of a valid session, without depending on any +other state. After `setup`, the session is not saved until `validate` has returned or resolved. This allows you to use `validate` to ensure that the login flow is @@ -276,15 +278,18 @@ token has been set. This must be done with a fail the test simply because the page is running a little slow. Once your assertion passes, the session is cached. -When restoring a saved session, if the `validate` function return `false`, -throws an exception, returns a Promise that resolves to `false` or rejects, or -contains any failing Cypress command, the session will be considered invalid, -and `setup` will be re-run. So your `validate` function serves a dual purpose, -it ensures that the initial setup has worked correctly **and** it checks that -the session remains valid before re-use. - -Note that the page is not cleared after the `validate` function is executed. If -you use `cy.visit()` in your validation, your test will continue on the visited +When restoring a saved session, if the `validate` function throws an exception, +returns a Promise that resolves to `false` or rejects, or contains any failing +Cypress command, the session will be considered invalid, and `setup` will be +re-run. So your `validate` function serves a dual purpose, it ensures that the +initial setup has worked correctly **and** it checks that the session remains +valid before re-use. + +Note that what happens after the `validate` function is run depends on the value +of the `testIsolation` config option. If `testIsolation` is on (the default), +the page is cleared before continuing with the rest of the test. If +`testIsolation` is off, the page is **not** cleared. In the latter case, if you +use `cy.visit()` in your validation, your test will continue to execute on that page once `cy.session()` succeeds. Here are a few `validate` examples: @@ -306,12 +311,9 @@ function validate() { cy.url().should('match', /^/account/) } -// Or just return false if the session is invalid -// TODO: How do they check this repeatedly? Maybe we need a `cy.retry` method? +// Or just call a method that tells us if the user is logged in function validate() { - if (!MyApp.isSessionValid()) { - return false - } + cy.wrap(MyApp).invoke("isSessionValid").should("be.true") } ``` @@ -415,9 +417,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) => { @@ -444,10 +447,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... @@ -458,10 +462,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