cypress-data-session
v2.8.6
Published
Cypress command for flexible test data setup
Downloads
77,232
Readme
cypress-data-session
Cypress command for flexible test data setup
Blog posts
📝 Read the blog posts
- Flexible Cypress Data Setup And Validation
- Faster User Object Creation
- Crawl Weather Using Cypress
- Minimize Mailosaur Use
- Cypress Tests For Apps That Use Central State
Look at the presentation slides Flexible Data Setup And Caching For Cypress.io Tests.
Training
🎓 This plugin is covered in my course Cypress Plugins:
- Lesson j1: Create and cache an item
- Lesson j2: Data session lifecycle
- Lesson j3: Dependent data sessions
- Lesson j4: Decide what to cache in the data session
- Lesson j5: Share the data session between specs
- Lesson j6: Instant login using a data session
- Lesson o4: Create data once using the support file hook
And in my course Testing The Swag Store
- Lesson 14: Instant login using cypress-data-session
- Bonus 33: Expire data session after N milliseconds
cy.dataSession vs cy.session
Some people have noticed similarities between cy.dataSession
command implemented by this plugin and the cy.session command included in Cypress itself. Here is my table of differences.
| Feature | cy.session | cy.dataSession | | ------------------------- | ------------------------- | -------------- | | Command is | official ✅ | community 🎁 | | Can cache | the browser session state | anything | | Stability | production | production | | Cache across specs | yes | yes | | Access to the cached data | no | yes | | Custom validation | no | yes | | Custom restore | no | yes | | Dependent caching | no | yes | | Time limit | no | yes | | Use count limit | no | yes | | Static utility methods | limited | all | | GUI integration | yes | no | | Should you use it? | maybe | yes | | Cypress version support | newer versions | all |
Videos
- Introduction to cypress-data-session package for creating two chat rooms in the chat application
- Use Data Alias Created Automatically By cypress-data-session
- Create User Using cypress-data-session Command
- Invalidate cy.session From cypress-data-session
- Share Data Across Specs Using cypress-data-session Plugin
- Use cy.dataSession To Create A User And Log In
- Quickly Create A User And Log in Using Dependent Data Sessions
- Introduction To cypress-data-session Plugin for setting up 3 todo items in a TodoMVC app
- Instant AWS Cognito Login Using cypress-data-session
Install
$ npm install -D cypress-data-session
# or using Yarn
$ yarn add -D cypress-data-session
Import this package from the spec or from the support file
// cypress/support/index.js
import 'cypress-data-session'
If you plan to use the shareAcrossSpecs option, you need to load this plugin from your plugin file
v10+
// cypress.config.js
import { defineConfig } from 'cypress'
const registerDataSession = require('cypress-data-session/src/plugin')
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
registerDataSession(on, config)
// IMPORTANT: return the config object
return config
},
},
})
v9
// cypress/plugin/index.js
module.exports = (on, config) => {
require('cypress-data-session/src/plugin')(on, config)
// IMPORTANT: return the config object
return config
}
Types
If using JavaScript, point the spec at this package using the ///
comment
// from your spec.js
/// <reference types="cypress-data-session" />
See src/index.d.ts
Use
You can call cy.dataSession
with simple 3 arguments or with an options object that allows you to pass more options.
three arguments
cy.dataSession(sessionName, setupFunction, validateValueFunction)
This example comes from spec.cy.js
beforeEach(() => {
// let's say you want to set up the value "A"
cy.dataSession(
'A', // data name
() => 'a', // data creation commands
(x) => x === 'a', // data validation function
)
})
it('has object A', () => {
expect(Cypress.getDataSession('A')).to.equal('a')
})
The value is automatically added as an alias, so you can use function () { ... }
syntax for the test callback and access the above value using this.A
property
it('exists under an alias', function () {
expect(this.A).to.equal('a')
})
Note: if the setup
function's text changes, the session is recomputed.
validate
The validate
predicate can use Cypress commands, but must ultimately yield a boolean value.
// validate a room id by fetching it from the database
// using cy.task and finding the room
const validate = (id) => {
return cy.task('getRooms', null, { log: false }).then((rooms) => {
// important: make sure to return a Boolean
// otherwise _.find returns undefined
// and Cypress cy.then callback returns the previous value
return Boolean(Cypress._.find(rooms, { _id: id }))
})
}
The validate
function is optional. If you skip it, the setup
will be run every time.
// without "validate" function, the "setup" runs every time
cy.dataSession(name, setup)
You can also pass a boolean value, which is useful during debugging.
validate: true // no need to recompute the value, it is still valid
validate: false // call setup again
See validate.cy.js spec.
onInvalidated
You can pass a function as the last argument to the cy.dataSession
to be called if the "validate" returns false. This function will be called before the "setup" function executes.
function onInvalidated() {
// clear user session for example
}
cy.dataSession(name, setupUser, validateUser, onInvalidated)
See an example in the spec invalidate.cy.js and video Invalidate cy.session From cypress-data-session.
Options object
You can pass multiple parameters using a single options object, see options.cy.js
cy.dataSession({
name: 'C',
setup: () => 'c',
validate: (x) => x === 'c',
})
recreate
Using the options object you can pass another function to be called after the validate
yields true. This function let's you to perform Cypress commands with the validated value to "finish" the recreation. For example, you could visit the page after setting the cookie from the data session to end on the page, just like the setup
does.
cy.dataSession({
name: 'logged in',
setup: () => {
// create user, visit the page, log in
// let's say we are on the page '/home
// save the cookie in the data session
cy.getCookie('connect.sid')
},
// assume the cookie is valid
validate: (c) => true,
recreate (cookie) => {
cy.setCookie(cookie)
cy.visit('/home')
}
})
shareAcrossSpecs
By default, the data session value is saved inside Cypress.env
object. This object is reset whenever the spec gets reloaded (think Cmd+R press or the full browser reload). The object is gone when the cypress run
finishes with a spec and opens another one. If you want the data value to persist across the browser reloads, or be shared across specs, use the shareAcrossSpecs: true
option.
cy.dataSession({
name: 'shared value',
setup: () => 'a',
validate: (x) => x === 'a',
shareAcrossSpecs: true,
})
The first spec that creates it, saves it in the plugin file process. Then other specs can re-use this value (after validation, of course).
preSetup
Sometimes you might need to run a few steps before the setup
commands. While you could have these commands inside the setup
function, it might make clear to anyone reading the code that these commands are preparing the data for the setup
function. For example, you could check if the user you are about to create exists already and needs to be deleted first.
cy.dataSession({
name: 'user',
preSetup() {
cy.task('findUser', 'Joe').then((user) => {
if (user) {
cy.task('deleteUser', user._id)
}
})
},
setup() {
// create the user "Joe"
},
validate(saved) {
// check if the user "Joe" exists
},
})
dependsOn
A data session can depend on another data session or even multiple data sessions. For example, the data session "logged in user" can depend on the "created user" data session. If the "parent" session changes, we need to recompute all sessions that depend on it.
cy.dataSession({
name: 'created user',
setup() {
// create a user
},
})
cy.dataSession({
name: 'logged in user',
dependsOn: 'created user',
setup() {
// take the user object from
// the data session "created user"
// and log in
},
})
If the "created user" gets invalidated and recomputed (its setup
method runs again), then the data session "logged in user" is invalidated automatically.
To list dependencies on multiple data sessions, pass an array of names
dependsOn: ['first', 'second', 'third']
init
Sometimes the data is generated, but sometimes we want to first see if we can load / initialize it the very first time (when there is nothing in the cache). This is where the init
callback is useful. For example, let's say we are creating a special test user using the method setup
and store its ID in the cache.
cy.dataSession({
name: 'test user',
setup() {
cy.task('createUser') // yields the user ID
},
validate: Cypress._.isString,
})
But what happens if there is a test user already created by the previous tests? The very first time we run the data session, we want to check if there is a test user, and if it exists, store its ID and NOT call the setup
function. Thus we add the init
function
cy.dataSession({
...
init () {
cy.task('findUser', 'test user') // yields the ID if found
}
})
The above example will find the test user (by calling the cy.task
) the very first time the command runs, since there is nothing in the data session yet. Then it validates the ID. If there is no ID, it runs the setup
function to create the test user. If there is a test user, and it returned the ID from the init
method, then it is cached (as if the setup
method created it), and the test continues.
fetch the current data session value
You can simply request the current data session, if any
cy.dataSession('sessionName').then(value => {
...
})
If the data session is not found in memory, it tries to fetch it from the plugins space (maybe it is shared across specs). If still not found, it throws an error.
showValue
By default, the cy.dataSession
only prints its messages. You can also print the actual data value into Cypress Command Log using the showValue: true
option
cy.dataSession({
name: 'my value',
setup() {
return 42
},
showValue: true,
}).should('equal', 42)
// The Command Log will have a message like
// "my value has data 42"
expires
You can automatically recompute the data session by calling its setup
method if more than N milliseconds have passed since the data session was set up.
cy.dataSession({
name: 'valid for 1 second,
setup() {
return 42
},
expires: 1000 // ms
})
limit
You can limit how many times a piece of cached data is used before automatically recomputing it. For example, to limit a cached item to be used 3 times only (including the the first one):
beforeEach(() => {
cy.dataSession({
name: 'limited',
setup() { ... },
limit: 3
})
})
it('works 1', () => {
// first use, calls setup() in the beforeEach hook
})
it('works 2', () => {
// second use, uses cached value the beforeEach hook
})
it('works 3', () => {
// third use, uses cached value in the beforeEach hook
})
it('works 4', () => {
// over the limit, has to recompute the data
// calls setup() in the beforeEach hook
})
recomputeOnRetry
Sometimes you want to enable test retries, but the data caching makes it hard to understand and run the full test. You can set the recomputeOnRetry: true
to automatically call the setup
again on tet retries.
Flow
Consider the cy.dataSession(options)
where the options
object might have the following method callbacks: validate
, init
, setup
, preSetup
, recreate
, and onInvalidated
. Your case might provide just some of these callback functions, but let's say you have provided all of them. Here is how they will be called.
- First, the code pulls cached data for the session name.
- if there is no cached value:
- it calls the
init
method, which might return a value - if there is a value, and it passes
validate
callback- it saves the value in the data session and finishes
- else it needs to generate the real value and save it
- it calls
preSetup
andsetup
methods and saves the value
- it calls
- it calls the
- else (there is a cached value):
- it calls
validate
with the cached value - if the
validate
returnstrue
, the code callsrecreate
method - else it has to recompute the value, so it calls
onInvalidated
,preSetup
, andsetup
methods
- it calls
Flowchart
flowchart TD
A[Start] --> B{Have\ncached\nvalue?}
B --> |No cached value| C[calls init]
C --> |init result| D{calls\nvalidate}
D --> |validated| J[recreate]
J --> E[Save the value]
E --> F[Finish]
D --> |Not validated| H[onInvalidated]
H --> G[preSetup & setup]
G --> E
B --> |With cached value| D
Examples
Global methods
A few global utility methods are added to the Cypress
object for accessing the data sessions. These methods are mostly utilities used internally.
Cypress.getDataSession(name)
Cypress.getDataSessionDetails(name)
Cypress.getSharedDataSessionDetails(name)
Cypress.clearDataSession(name)
Cypress.clearDataSessions()
Cypress.dataSessions(enable)
Cypress.setDataSession(name, value)
Cypress.formDataSessionKey(name)
Cypress.printSharedDataSessions
getDataSession
Returns the data for the session with the given name (if any).
getDataSessionDetails
Returns the session with data and internal details, useful for debugging. If the session is not in memory, but stored in the plugin process, use getSharedDataSessionDetails
.
getSharedDataSessionDetails
Fetches the session details from the plugin process.
dataSessions
Without any arguments, this static method lists all current data sessions.
clearDataSessions
Clears all current data sessions from memory.
clearDataSession
Clears the given data session from memory, from aliases, and from the plugin space (if shared). If running inside a test, automatically runs inside cy.then
to be part of the command chain. Can be used from the DevTools console
cy.dataSession('user', ...)
// rest of cy commands
// remove the data session "user"
Cypress.clearDataSession('user')
Migration
from v1 to v2
The default value for the validate
option changed from false
to true
, meaning any non-nil value is valid and does not require to be recomputed.
// v1
cy.dataSession(name, setup, true)
// v2
cy.dataSession(name, setup)
Same for the option form
// v1
cy.dataSession({
name,
setup,
validate: true,
})
// v2
cy.dataSession({
name,
setup,
})
If you previously set validate
to false, keep it the same
// v1, v2
cy.dataSession(name, setup, false)
Debugging
This plugin uses debug module to output verbose messages. Start Cypress with the environment variable DEBUG=cypress-data-session
to see the messages in the terminal. From a Linux terminal we can use:
$ DEBUG=cypress-data-session npx cypress open
Tip: read how to set the system environment variables on different operating systems here
To see the verbose log messages in the Cypress browser, open the DevTools and in the top window context set the local storage property
localStorage.debug = 'cypress-data-session'
Make sure the "verbose" logs are visible. Then you should see the debug logs while the plugin executes its code in the browser, just like this image shows:
See also
Custom command creation and publishing to NPM described in these blog posts:
Small print
Author: Gleb Bahmutov <[email protected]> © 2021
License: MIT - do anything with the code, but don't blame me if it does not work.
Support: if you find any problems with this module, email / tweet / open issue on Github
MIT License
Copyright (c) 2021 Gleb Bahmutov <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.