Easily write scalable Node.js setup code for Cypress
cy.task()
allows Cypress users to run code in a Node.js process.
However, all Cypress tasks run in a global namespace and as your app and number of different test setups grow, relying on cy.task()
for test setups can become hard to maintain.
cypress-routines
enables you to organize your test setups neatly per spec-file. Routines run in Node.js, so you can easily access things like databases and file systems in your test setups.
Using cypress-routines to write and organize test setups that run in Node.js
# With yarn:
yarn add cypress-routines --dev
# With npm:
npm install cypress-routines --save-dev
In cypress/plugins/index.js
:
module.exports = async (on, config) => {
const db = await connectDb() // π Example
// After `on, config`, you can pass e.g. db π
require('cypress-routines/plugin')(on, config, db)
}
In cypress/support/index.js
:
require('cypress-routines/support')
In cypress.json
:
{
"ignoreTestFiles": ["*.routines.js"]
}
- Where do I put my routines?
- Writing routines
- Giving routines access to the database
- Calling routines
- Sharing routines across spec-files
- Global routines
- Sharing routine functions
Routines live next to their respective spec-file:
cypress/
integration/
login.spec.js
login.routines.js
signup.spec.js
signup.routines.js
You can also define global routines.
A routines-file is a simple node.js module that exports a factory-function that returns an object with functions ("routines") attached to it:
// cypress/integration/login.routines.js
function loginRoutines(db) {
return {
createUser(user) {
await db.collection('users').insertOne(user)
return user
}
}
}
module.exports = loginRoutines
The return-value of the routine will be accessible from the spec-file in the browser context, so it must be JSON-serializable.
The createUser
routine from login.routines.js
can be used from login.spec.js
like so:
cy.routine('createUser', { email: '...' }).then(() => {
// ...
})
In your Cypress plugin-file, pass the db
(or any other parameters you like) after on, config
to the function that's required as cypress-routines/plugin
.
// cypress/plugin/index.js
module.exports = async (on, config) => {
const db = await connectDb()
// All arguments after `on, config` are passed along
// to the routine-factories. In this case, we're passing
// `db` so that every routines-file can access the db
// if it needs to.
require('cypress-routines/plugin')(on, config, db, param2, param3 /* etc. */)
}
The factory-functions in your routines files now have access to those params.
// cypress/integration/login.routines.js
function loginRoutines(db, param2, param3 /* etc. */) {
return {
// ...
}
}
module.exports = loginRoutines
Routines are called with cy.routine(routineName: string, routineArg?: any)
. A routine can optionally take a single argument (must be JSON-serializable).
// cypress/integration/login.spec.js
it('logs in the user', function () {
const routineArg = {
email: '[email protected]',
hashedPassword: hashPassword('123456'),
}
cy.routine('createUser', routineArg).then(() => {
cy.visit('login')
// ...
})
})
a.spec.js
, can only call routines defined in a.routines.js
(not b.routines.js
).
cy.routine()
, like other Cypress commands, is asynchronous but cannot be used with async/await. Read here for more info on async commands.
Routines are scoped to their spec-files. For 95% of cases, this is what you want because it introduces clean separations between test-setups and makes it easy to find a routine that is used in a certain spec-file.
In some cases, you might want to reuse certain routines. There are two options for this:
- Global routines
- Sharing routine functions
Global routines can be defined in cypress/integration/global-routines.js
. The global routines-file looks like any other routines-file:
// cypress/global-routines.js
function globalRoutines(db) {
return {
async createDefaultUser() {
const defaultUser = {
email: '[email protected]',
hashedPassword: hashPassword('123456'),
}
await db.collection('users').insertOne(defaultUser)
return defaultUser
},
}
}
module.exports = globalRoutines
Global routines are called like regular routines, but with a leading '/'
:
// cypress/integration/login.spec.js
it('logs in the user', function () {
// π Leading '/'
cy.routine('/createDefaultUser').then((testUser) => {
cy.visit('login')
// ...
})
})
You can always require other routines-files from any routines-file. You can then re-use and re-export functions with normal JavaScript:
// cypress/integration/login.routines.js
// Either export an entire other routines-file:
module.exports = require('./homepage.routines.js')
// Or export single functions:
module.exports = (db) => {
const homepageRoutines = require('./homepage.routines.js')(db)
return {
createUser: homepageRoutines.createUser,
}
}