task
cy.task() runs code in Node.js from your test, through
the task event you register in
setupNodeEvents. It handles the
work a browser can't do on its own:
- Seed or query a database.
- Read, clean up, and inspect files on disk.
- Run a CLI or another external command.
- Wait for something that happens outside the browser, such as an email.
- Keep state between spec files.
Your test runs in the browser and your task handler runs in Node.js, so the argument and the return value are both serialized as they cross between the two:
Don't start a web server with cy.task(). Start it before Cypress instead, as
described in Best Practices.
Syntax​
cy.task(event)
cy.task(event, arg)
cy.task(event, arg, options)
Usage​
Correct Usage
cy.task('log', 'This will be output to the terminal')
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
log(message) {
console.log(message)
return null
},
})
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
log(message) {
console.log(message)
return null
},
})
},
},
})
The task plugin event handler can return a value or a promise. The command
fails if the handler returns undefined or the promise resolves with
undefined. This catches typos and events that no handler covers.
If you don't need to return a value, return null to signal that the event was
handled.
Incorrect Usage
// Errors, the event name is required
cy.task()
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
// Errors, the handler returns undefined
log(message) {
console.log(message)
},
})
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
// Errors, the handler returns undefined
log(message) {
console.log(message)
},
})
},
},
})
Arguments​
event (String)
An event name to be handled via the task event in the
setupNodeEvents function.
arg (Object)
An argument to send along with the event. This can be any value that can be
serialized by
JSON.stringify(),
and the handler receives it the way JSON.stringify() would serialize it:
- Functions, symbols, and
undefinedare dropped from object properties and becomenullinside arrays. - Regular expressions,
Map, andSetbecome empty objects ({}). Dateobjects become ISO date strings. Rebuild one in the handler withnew Date(value).- An argument with circular references can't be serialized, so the command fails.
When you omit arg, the handler receives undefined, so a default parameter
value in the handler applies.
To pass multiple values, put them in an object and destructure it in the handler:
cy.task('hello', { greeting: 'Hello', name: 'World' })
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
// destructure the individual properties
hello({ greeting, name }) {
console.log('%s, %s', greeting, name)
return null
},
})
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
// destructure the individual properties
hello({ greeting, name }) {
console.log('%s, %s', greeting, name)
return null
},
})
},
},
})
options (Object)
Pass in an options object to change the default behavior of cy.task().
| Option | Default | Description |
|---|---|---|
log | true | Displays the command in the Command log |
timeout | taskTimeout | Time to wait for cy.task() to resolve before timing out |
Yields ​
cy.task() yields the value returned or resolved by the task event in
setupNodeEvents.
Examples​
Read and clean up files on disk with cy.task()​
Read a file that might not exist with cy.task()​
cy.readFile() assumes the file exists. To read a file that might not exist, use a task.
cy.task('readFileMaybe', 'my-file.txt').then((textOrNull) => { ... })
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const fs = require('fs')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
readFileMaybe(filename) {
if (fs.existsSync(filename)) {
return fs.readFileSync(filename, 'utf8')
}
return null
},
})
},
},
})
import { defineConfig } from 'cypress'
import fs from 'fs'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
readFileMaybe(filename) {
if (fs.existsSync(filename)) {
return fs.readFileSync(filename, 'utf8')
}
return null
},
})
},
},
})
Count the files in a folder with cy.task()​
cy.task('countFiles', 'cypress/downloads').then((count) => { ... })
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const fs = require('fs')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
countFiles(folderName) {
return new Promise((resolve, reject) => {
fs.readdir(folderName, (err, files) => {
if (err) {
return reject(err)
}
resolve(files.length)
})
})
},
})
},
},
})
import { defineConfig } from 'cypress'
import fs from 'fs'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
countFiles(folderName) {
return new Promise((resolve, reject) => {
fs.readdir(folderName, (err, files) => {
if (err) {
return reject(err)
}
resolve(files.length)
})
})
},
})
},
},
})
Clean up and verify downloaded files with cy.task()​
Tasks are a good fit for file system work, such as emptying the downloads folder
between tests and then finding the file the test just downloaded.
config.downloadsFolder is the resolved absolute path, so the task keeps
working if the project overrides
downloadsFolder.
cy.task('clearDownloads')
cy.task('latestDownload').then((filepath) => {
expect(filepath).to.be.a('string')
cy.readFile(filepath)
})
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const fs = require('fs')
const path = require('path')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
const downloads = config.downloadsFolder
on('task', {
clearDownloads() {
fs.rmSync(downloads, { recursive: true, force: true })
fs.mkdirSync(downloads, { recursive: true })
return null
},
latestDownload() {
const files = fs
.readdirSync(downloads)
.map((name) => ({
name,
time: fs.statSync(path.join(downloads, name)).mtimeMs,
}))
.sort((a, b) => b.time - a.time)
return files.length ? path.join(downloads, files[0].name) : null
},
})
},
},
})
import { defineConfig } from 'cypress'
import fs from 'fs'
import path from 'path'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
const downloads = config.downloadsFolder
on('task', {
clearDownloads() {
fs.rmSync(downloads, { recursive: true, force: true })
fs.mkdirSync(downloads, { recursive: true })
return null
},
latestDownload() {
const files = fs
.readdirSync(downloads)
.map((name) => ({
name,
time: fs.statSync(path.join(downloads, name)).mtimeMs,
}))
.sort((a, b) => b.time - a.time)
return files.length ? path.join(downloads, files[0].name) : null
},
})
},
},
})
Cypress already trashes the downloads folder before cypress run when
trashAssetsBeforeRuns
is enabled, which it is by default. A clearDownloads task is for clearing the
folder between tests within a run.
Manage test data and state with cy.task()​
Seed a database before each test with cy.task()​
describe('e2e', () => {
beforeEach(() => {
cy.task('defaults:db')
cy.visit('/')
})
it('displays article values', () => {
cy.get('.article-list').should('have.length', 10)
})
})
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
// the module in your app that seeds the database,
// with a path relative to the Cypress config file
const db = require('./server/db')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
async 'defaults:db'() {
await db.seed('defaults')
// seed() may resolve with undefined, so signal success explicitly
return null
},
})
},
},
})
import { defineConfig } from 'cypress'
// the module in your app that seeds the database,
// with a path relative to the Cypress config file
import db from './server/db'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
async 'defaults:db'() {
await db.seed('defaults')
// seed() may resolve with undefined, so signal success explicitly
return null
},
})
},
},
})
Query a MySQL database from a Cypress test​
Pass the query and the name of the database to run it against in one object, then destructure it in the handler. This example uses the mysql2 client.
const dbName = 'stagingA'
const query = 'SELECT * FROM users'
cy.task('queryDatabase', { dbName, query })
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const mysql = require('mysql2/promise')
// the connection details for each database could
// come from environment variables
const connections = {
stagingA: {
host: 'staging.my.co',
user: 'test',
password: '***',
database: 'users',
},
stagingB: {
host: 'staging-b.my.co',
user: 'test',
password: '***',
database: 'users',
},
}
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
// destructure the argument into the individual fields
async queryDatabase({ dbName, query }) {
const connectionInfo = connections[dbName]
if (!connectionInfo) {
throw new Error(`No database connection named ${dbName}`)
}
const connection = await mysql.createConnection(connectionInfo)
try {
const [rows] = await connection.query(query)
return rows
} finally {
await connection.end()
}
},
})
},
},
})
import { defineConfig } from 'cypress'
import mysql from 'mysql2/promise'
// the connection details for each database could
// come from environment variables
const connections: Record<string, mysql.ConnectionOptions> = {
stagingA: {
host: 'staging.my.co',
user: 'test',
password: '***',
database: 'users',
},
stagingB: {
host: 'staging-b.my.co',
user: 'test',
password: '***',
database: 'users',
},
}
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
// destructure the argument into the individual fields
async queryDatabase({ dbName, query }) {
const connectionInfo = connections[dbName]
if (!connectionInfo) {
throw new Error(`No database connection named ${dbName}`)
}
const connection = await mysql.createConnection(connectionInfo)
try {
const [rows] = await connection.query(query)
return rows
} finally {
await connection.end()
}
},
})
},
},
})
Share state between spec files with cy.task()​
The config process keeps running for the whole run, so a value stored in a variable there is still available to later spec files.
cy.task('setValue', { key: 'orgId', value: 42 })
cy.task('getValue', 'orgId').should('eq', 42)
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const store = {}
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
setValue({ key, value }) {
store[key] = value
return null
},
getValue(key) {
// return null rather than undefined when the key is missing
return store[key] ?? null
},
})
},
},
})
import { defineConfig } from 'cypress'
const store: Record<string, unknown> = {}
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
setValue({ key, value }) {
store[key] = value
return null
},
getValue(key) {
// return null rather than undefined when the key is missing
return store[key] ?? null
},
})
},
},
})
Keep these behaviors in mind:
- The store starts empty each time the config process starts, such as at the
beginning of every
cypress run. - Machines running specs in parallel each have their own config process, so they don't share values.
- A spec that reads a value another spec wrote only passes when the specs run
in that order. Use a store for values you can recreate when they're missing,
and
cy.session()for caching logins.
Keep a variable across origins with cy.task()​
When a test visits a different origin, Cypress reloads the spec to switch to the new origin, which resets the variables in your spec file. A task keeps the value in Node.js, where the reload doesn't reach it.
describe('Href visit', () => {
it('captures href', () => {
cy.visit('https://example.cypress.io')
cy.get('a')
.invoke('attr', 'href')
.then((href) => {
// href is not same-origin as current url
// like https://www.cypress-dx.com
cy.task('setHref', href)
})
})
it('visit href', () => {
cy.task('getHref').then((href) => {
// visit non same-origin url https://www.cypress-dx.com
cy.visit(href)
})
})
})
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
let href
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
setHref: (val) => {
return (href = val)
},
getHref: () => {
return href
},
})
},
},
})
import { defineConfig } from 'cypress'
let href: string
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
setHref: (val) => {
return (href = val)
},
getHref: () => {
return href
},
})
},
},
})
Run processes outside the browser with cy.task()​
Run an external command or CLI with cy.task()​
When setup depends on a tool that isn't a Node module, such as a Docker
container, a database CLI, or a cloud provider's command line tool, spawn it
from the task with
child_process.execFileSync().
Passing the arguments as an array avoids the shell entirely, so quoting and
PATH resolution behave the same way on every platform.
cy.task('resetDatabase')
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const { execFileSync } = require('child_process')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
resetDatabase() {
execFileSync('docker', ['exec', 'db', 'reset-script'], {
stdio: 'pipe',
})
return null
},
})
},
},
})
import { defineConfig } from 'cypress'
import { execFileSync } from 'child_process'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
resetDatabase() {
execFileSync('docker', ['exec', 'db', 'reset-script'], {
stdio: 'pipe',
})
return null
},
})
},
},
})
A task can also return the command's output to the test:
cy.task('listBucket', 's3://my-test-bucket').should('have.length', 3)
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const { execFileSync } = require('child_process')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
listBucket(bucket) {
const output = execFileSync('aws', ['s3', 'ls', bucket], {
encoding: 'utf8',
})
return output.split('\n').filter(Boolean)
},
})
},
},
})
import { defineConfig } from 'cypress'
import { execFileSync } from 'child_process'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
listBucket(bucket) {
const output = execFileSync('aws', ['s3', 'ls', bucket], {
encoding: 'utf8',
})
return output.split('\n').filter(Boolean)
},
})
},
},
})
Wait for an email or background job with cy.task()​
cy.task() runs the handler once and doesn't retry it. To wait for something
that happens outside the browser, such as an email arriving or a background job
finishing, poll inside the handler and resolve once the result is ready.
cy.task(
'waitForEmail',
{ timeout: 40000 }
)
.its('subject')
.should('eq', 'Welcome to the app')
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
// findEmail comes from your email testing service's client
const { findEmail } = require('./mail-client')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
async waitForEmail({ to, timeout }) {
const deadline = Date.now() + timeout
while (Date.now() < deadline) {
const email = await findEmail(to)
if (email) {
return email
}
await new Promise((resolve) => setTimeout(resolve, 1000))
}
throw new Error(`No email to ${to} arrived within ${timeout}ms`)
},
})
},
},
})
import { defineConfig } from 'cypress'
// findEmail comes from your email testing service's client
import { findEmail } from './mail-client'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
async waitForEmail({ to, timeout }) {
const deadline = Date.now() + timeout
while (Date.now() < deadline) {
const email = await findEmail(to)
if (email) {
return email
}
await new Promise((resolve) => setTimeout(resolve, 1000))
}
throw new Error(`No email to ${to} arrived within ${timeout}ms`)
},
})
},
},
})
Give the cy.task() timeout option a few seconds more than the handler's own
deadline. The handler then gives up first, and the test fails with its message
instead of a generic timeout.
Write cy.task() handlers in setupNodeEvents​
Return a promise from an async cy.task() handler​
cy.task('pause', 1000)
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
pause(ms) {
return new Promise((resolve) => {
// tasks should not resolve with undefined
setTimeout(() => resolve(null), ms)
})
},
})
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
pause(ms) {
return new Promise((resolve) => {
// tasks should not resolve with undefined
setTimeout(() => resolve(null), ms)
})
},
})
},
},
})
Type the value cy.task() yields in TypeScript​
In TypeScript, cy.task() yields unknown unless you pass a type parameter
describing what the task returns. Cypress doesn't check the type at runtime, so
keep it in step with the handler.
type User = { id: number; name: string }
cy.task<User[]>('getUsers').then((users) => {
// users is typed as User[]
expect(users[0].name).to.eq('Ada')
})
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
getUsers() {
return [
{ id: 1, name: 'Ada' },
{ id: 2, name: 'Grace' },
]
},
})
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
getUsers() {
return [
{ id: 1, name: 'Ada' },
{ id: 2, name: 'Grace' },
]
},
})
},
},
})
Run Node.js code before each Cypress test​
There is no setupNodeEvents event for each test. To run Node.js code before
every test, call cy.task() from a global beforeEach in your
support file.
beforeEach(function () {
cy.task('logTestStart', this.currentTest.fullTitle())
})
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
logTestStart(title) {
console.log(`Starting: ${title}`)
return null
},
})
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
logTestStart(title) {
console.log(`Starting: ${title}`)
return null
},
})
},
},
})
Notes​
cy.task() doesn't support long-running processes​
cy.task() doesn't support tasks that keep running, such as:
- Starting a server.
- Watching for file changes.
- Any process that needs to be interrupted to stop.
A task must end within the timeout, or Cypress fails the current test.
Change the cy.task() timeout for a command or suite​
cy.task() waits up to taskTimeout,
60 seconds by default. Keep tasks short where you can: Cypress doesn't run any
other commands until cy.task() finishes, so a long task slows down every test
that calls it.
To give one command more time, pass the timeout option:
// fails if seeding the database takes longer than 20 seconds
cy.task('seedDatabase', null, { timeout: 20000 })
To change it for a suite or test, set taskTimeout in the
test configuration. The
value applies while that suite or test runs, then returns to the previous
value.
describe('has data available from database', { taskTimeout: 90000 }, () => {
before(() => {
cy.task('seedDatabase')
})
// tests
after(() => {
cy.task('resetDatabase')
})
})
To change it for the rest of the spec file, set it with Cypress.config():
Cypress.config('taskTimeout', 30000)
Cypress.config('taskTimeout') // => 30000
Errors thrown in a cy.task() handler fail the test​
When a handler throws or returns a rejected promise, cy.task() fails the test
with the error's message.
Return null when "not found" is an answer the test can act on, and throw when
the test can't continue:
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
// a missing user is an expected answer, so return null
findUser(email) {
return users.find((user) => user.email === email) ?? null
},
// a test that deletes a missing user has bad setup, so throw
deleteUser(email) {
const index = users.findIndex((user) => user.email === email)
if (index === -1) {
throw new Error(`No user with email ${email}`)
}
users.splice(index, 1)
return null
},
})
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', {
// a missing user is an expected answer, so return null
findUser(email) {
return users.find((user) => user.email === email) ?? null
},
// a test that deletes a missing user has bad setup, so throw
deleteUser(email) {
const index = users.findIndex((user) => user.email === email)
if (index === -1) {
throw new Error(`No user with email ${email}`)
}
users.splice(index, 1)
return null
},
})
},
},
})
CypressError: `cy.task('deleteUser')` failed with the following error:
> No user with email [email protected]
https://on.cypress.io/api/task
In the Cypress App, the error points to the cy.task() line in your spec.
Expand Stack trace to find the From Node.js Internals section, which shows
where your handler threw.

Cypress merges every on('task') registration​
Cypress merges every on('task') object you register, so you can split your
tasks into modules by area. Each module exports an object of handlers, like
this state.ts:
// cypress/tasks/state.ts
const store = {}
module.exports.stateTasks = {
setValue({ key, value }) {
store[key] = value
return null
},
getValue(key) {
return store[key] ?? null
},
}
// cypress/tasks/state.ts
const store: Record<string, unknown> = {}
export const stateTasks = {
setValue({ key, value }: { key: string; value: unknown }) {
store[key] = value
return null
},
getValue(key: string) {
return store[key] ?? null
},
}
Then register each module, here alongside a users.ts module built the same
way:
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const { stateTasks } = require('./cypress/tasks/state')
const { userTasks } = require('./cypress/tasks/users')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', stateTasks)
on('task', userTasks)
},
},
})
import { defineConfig } from 'cypress'
import { stateTasks } from './cypress/tasks/state'
import { userTasks } from './cypress/tasks/users'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
on('task', stateTasks)
on('task', userTasks)
},
},
})
Plugins that export their own tasks merge the same way, alongside yours.
If multiple task objects use the same key, the later registration overwrites that particular key, the same way merging multiple objects with duplicate keys overwrites the first one. Cypress prints a warning in the terminal that names each duplicated task.
Access secrets at runtime with cy.task()​
cy.task() reads secrets at runtime, which suits values from an external
secret manager or an API that isn't available at configuration time. For
secrets that are available at configuration time, use
cy.env() instead.
// executes at runtime
cy.task('getSecret', 'API_KEY', { log: false }).then((apiKey) => {
cy.request({
method: 'POST',
url: 'https://api.example.com/data',
headers: {
Authorization: `Bearer ${apiKey}`,
},
log: false,
})
})
Pass { log: false } to both commands. Otherwise the Command Log shows the
cy.task() call, its console output shows the secret it yields, and the
cy.request() console output shows the Authorization header. With
log: false, neither command appears in the Command Log.
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
// Task definition - executes when cy.task() is called in tests
on('task', {
getSecret(secretName) {
// This reads from process.env at runtime, not at configuration time
// Useful when secrets are fetched dynamically or only needed conditionally
const secret = process.env[secretName]
if (!secret) {
throw new Error(`Secret ${secretName} is not set`)
}
return secret
},
})
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
// setupNodeEvents can be defined in either
// the e2e or component configuration
e2e: {
setupNodeEvents(on, config) {
// Task definition - executes when cy.task() is called in tests
on('task', {
getSecret(secretName: string) {
// This reads from process.env at runtime, not at configuration time
// Useful when secrets are fetched dynamically or only needed conditionally
const secret = process.env[secretName]
if (!secret) {
throw new Error(`Secret ${secretName} is not set`)
}
return secret
},
})
},
},
})
Rules​
Requirements ​
cy.task()requires being chained off ofcy.cy.task()requires a non-empty string as its first argument.cy.task()requires thetaskevent to be registered in setupNodeEvents, with a handler for the given event name.cy.task()requires being invoked from the spec file or support file in end-to-end tests.cy.task()requires the task to eventually end.
Assertions ​
cy.task()runs the assertions you chain once and doesn't retry them.
Timeouts ​
cy.task()can time out waiting for the task to end.
Command Log​
This example uses the Count the files in a folder with cy.task() task defined above.
cy.task('countFiles', 'cypress/e2e')
The command above displays in the Command Log as:

When you click the task command in the Command Log, the console shows the
following:

History​
| Version | Changes |
|---|---|
| 5.6.0 | Added a type parameter for the value cy.task() yields |
| 5.3.0 | A task called without an argument receives undefined instead of null |
| 4.4.0 | The error for a task that resolves undefined names the resolution value it needs |
| 3.0.0 | cy.task() command added |