task

Execute code in Node via the task plugin event.

Syntax

cy.task(event)
cy.task(event, arg)
cy.task(event, arg, options)

Usage

Correct Usage

// in test
cy.task('log', 'This will be output to the terminal')
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
        },
      })
    }
  }
})
// cypress/plugins/index.js

module.exports = (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 will fail if undefined is returned or if the promise is resolved with undefined. This helps catch typos or cases where the task event is not handled.

If you do not need to return a value, explicitly return null to signal that the given event has been handled.

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(). Unserializable types such as functions, regular expressions, or symbols will be omitted to null.

If you need to pass multiple arguments, use an object

// in test
cy.task('hello', { greeting: 'Hello', name: 'World' })
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    setupNodeEvents(on, config) {
      on('task', {
        // deconstruct 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', {
        // deconstruct the individual properties
        hello({ greeting, name }) {
          console.log('%s, %s', greeting, name)
      
          return null
        },
      })
    }
  }
})
// cypress/plugins/index.js

module.exports = (on, config) => {
  on('task', {
    // deconstruct 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().

OptionDefaultDescription
logtrueDisplays the command in the Command log
timeouttaskTimeoutTime 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

cy.task() provides an escape hatch for running arbitrary Node code, so you can take actions necessary for your tests outside of the scope of Cypress. This is great for:

  • Seeding your test database.
  • Storing state in Node that you want persisted between spec files.
  • Performing parallel tasks, like making multiple http requests outside of Cypress.
  • Running an external process.

Read a file that might not exist

Command cy.readFile() assumes the file exists. If you need to read a file that might not exist, use cy.task.

// in test
cy.task('readFileMaybe', 'my-file.txt').then((textOrNull) => { ... })
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'

const fs = require('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
        },
      })
    }
  }
})
// cypress/plugins/index.js

const fs = require('fs')

module.exports = (on, config) => {
  on('task', {
    readFileMaybe(filename) {
      if (fs.existsSync(filename)) {
        return fs.readFileSync(filename, 'utf8')
      }
  
      return null
    },
  })
}

Return number of files in the folder

// in test
cy.task('countFiles', 'cypress/downloads').then((count) => { ... })
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'

const fs = require('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)
            })
          })
        },
      })
    }
  }
})
// cypress/plugins/index.js

const fs = require('fs')

module.exports = (on, config) => {
  on('task', {
    countFiles(folderName) {
      return new Promise((resolve, reject) => {
        fs.readdir(folderName, (err, files) => {
          if (err) {
            return reject(err)
          }
  
          resolve(files.length)
        })
      })
    },
  })
}

Seed a database

// in test
describe('e2e', () => {
  beforeEach(() => {
    cy.task('defaults:db')
    cy.visit('/')
  })

  it('displays article values', () => {
    cy.get('.article-list').should('have.length', 10)
  })
})
const { defineConfig } = require('cypress')

// we require some code in our app that
// is responsible for seeding our database
const db = require('../../server/src/db')

module.exports = defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    setupNodeEvents(on, config) {
      on('task', {
        'defaults:db': () => {
          return db.seed('defaults')
        },
      })
    }
  }
})
import { defineConfig } from 'cypress'

// we require some code in our app that
// is responsible for seeding our database
const db = require('../../server/src/db')

export default defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    setupNodeEvents(on, config) {
      on('task', {
        'defaults:db': () => {
          return db.seed('defaults')
        },
      })
    }
  }
})
// cypress/plugins/index.js

// we require some code in our app that
// is responsible for seeding our database
const db = require('../../server/src/db')

module.exports = (on, config) => {
  on('task', {
    'defaults:db': () => {
      return db.seed('defaults')
    },
  })
}

Return a Promise from an asynchronous task

// in test
cy.task('pause', 1000)
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)
          })
        },
      })
    }
  }
})
// cypress/plugins/index.js

module.exports = (on, config) => {
  on('task', {
    pause(ms) {
      return new Promise((resolve) => {
        // tasks should not resolve with undefined
        setTimeout(() => resolve(null), ms)
      })
    },
  })
}

Save a variable across non same-origin URL visits

When visiting non same-origin URL, Cypress will change the hosted URL to the new URL, wiping the state of any local variables. We want to save a variable across visiting non same-origin URLs.

We can save the variable and retrieve the saved variable outside of the test using cy.task() as shown below.

// in test
describe('Href visit', () => {
  it('captures href', () => {
    cy.visit('https://www.mywebapp.com')
    cy.get('a')
      .invoke('attr', 'href')
      .then((href) => {
        // href is not same-origin as current url
        // like https://www.anotherwebapp.com
        cy.task('setHref', href)
      })
  })

  it('visit href', () => {
    cy.task('getHref').then((href) => {
      // visit non same-origin url https://www.anotherwebapp.com
      cy.visit(href)
    })
  })
})
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

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
        },
      })
    }
  }
})
// cypress/plugins/index.js

let href

module.exports = (on, config) => {
  on('task', {
    setHref: (val) => {
      return (href = val)
    },
    getHref: () => {
      return href
    },
  })
}

Command options

Change the timeout

You can increase the time allowed to execute the task, although we do not recommend executing tasks that take a long time to exit.

Cypress will not continue running any other commands until cy.task() has finished, so a long-running command will drastically slow down your test runs.

// will fail if seeding the database takes longer than 20 seconds to finish
cy.task('seedDatabase', null, { timeout: 20000 })

Notes

Tasks must end

Tasks that do not end are not supported

cy.task() does not support tasks that do not end, such as:

  • Starting a server.
  • A task that watches for file changes.
  • Any process that needs to be manually interrupted to stop.

A task must end within the taskTimeout or Cypress will fail the current test.

Tasks are merged automatically

Sometimes you might be using plugins that export their tasks for registration. Cypress automatically merges on('task') objects for you. For example if you are using cypress-skip-and-only-ui plugin and want to install your own task to read a file that might not exist:

const { defineConfig } = require('cypress')

const skipAndOnlyTask = require('cypress-skip-and-only-ui/task')
const fs = require('fs')
const myTask = {
  readFileMaybe(filename) {
    if (fs.existsSync(filename)) {
      return fs.readFileSync(filename, 'utf8')
    }

    return null
  },
}

module.exports = defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    setupNodeEvents(on, config) {
      // register plugin's task
      on('task', skipAndOnlyTask)
      // and register my own task
      on('task', myTask)
    }
  }
})
import { defineConfig } from 'cypress'

const skipAndOnlyTask = require('cypress-skip-and-only-ui/task')
const fs = require('fs')
const myTask = {
  readFileMaybe(filename) {
    if (fs.existsSync(filename)) {
      return fs.readFileSync(filename, 'utf8')
    }

    return null
  },
}

export default defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    setupNodeEvents(on, config) {
      // register plugin's task
      on('task', skipAndOnlyTask)
      // and register my own task
      on('task', myTask)
    }
  }
})
// cypress/plugins/index.js

const skipAndOnlyTask = require('cypress-skip-and-only-ui/task')
const fs = require('fs')
const myTask = {
  readFileMaybe(filename) {
    if (fs.existsSync(filename)) {
      return fs.readFileSync(filename, 'utf8')
    }

    return null
  },
}

module.exports = (on, config) => {
  // register plugin's task
  on('task', skipAndOnlyTask)
  // and register my own task
  on('task', myTask)
}

See #2284 for implementation.

Reset timeout via Cypress.config()

You can change the timeout of cy.task() for the remainder of the tests by setting the new values for taskTimeout within Cypress.config().

Cypress.config('taskTimeout', 30000)
Cypress.config('taskTimeout') // => 30000

Set timeout in the test configuration

You can configure the cy.task() timeout within a suite or test by passing the new configuration value within the test configuration.

This will set the timeout throughout the duration of the tests, then return it to the default taskTimeout when complete.

describe('has data available from database', { taskTimeout: 90000 }, () => {
  before(() => {
    cy.task('seedDatabase')
  })

  // tests

  after(() => {
    cy.task('resetDatabase')
  })
})

Allows a single argument only

The syntax cy.task(name, arg, options) only has place for a single argument to be passed from the test code to the plugins code. In the situations where you would like to pass multiple arguments, place them into an object to be destructured inside the task code. For example, if you would like to execute a database query and pass the database profile name you could do:

// in test
const dbName = 'stagingA'
const query = 'SELECT * FROM users'

cy.task('queryDatabase', { dbName, query })
const { defineConfig } = require('cypress')

const mysql = require('mysql')
// the connection strings for different databases could
// come from the Cypress configuration or 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',
  },
}

// querying the database from Node
function queryDB(connectionInfo, query) {
  const connection = mysql.createConnection(connectionInfo)

  connection.connect()

  return new Promise((resolve, reject) => {
    connection.query(query, (error, results) => {
      if (error) {
        return reject(error)
      }

      connection.end()

      return resolve(results)
    })
  })
}

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
        queryDatabase({ dbName, query }) {
          const connectionInfo = connections[dbName]
      
          if (!connectionInfo) {
            throw new Error(`Do not have DB connection under name ${dbName}`)
          }
      
          return queryDB(connectionInfo, query)
        },
      })
    }
  }
})
import { defineConfig } from 'cypress'

const mysql = require('mysql')
// the connection strings for different databases could
// come from the Cypress configuration or 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',
  },
}

// querying the database from Node
function queryDB(connectionInfo, query) {
  const connection = mysql.createConnection(connectionInfo)

  connection.connect()

  return new Promise((resolve, reject) => {
    connection.query(query, (error, results) => {
      if (error) {
        return reject(error)
      }

      connection.end()

      return resolve(results)
    })
  })
}

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
        queryDatabase({ dbName, query }) {
          const connectionInfo = connections[dbName]
      
          if (!connectionInfo) {
            throw new Error(`Do not have DB connection under name ${dbName}`)
          }
      
          return queryDB(connectionInfo, query)
        },
      })
    }
  }
})
// cypress/plugins/index.js

const mysql = require('mysql')
// the connection strings for different databases could
// come from the Cypress configuration or 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',
  },
}

// querying the database from Node
function queryDB(connectionInfo, query) {
  const connection = mysql.createConnection(connectionInfo)

  connection.connect()

  return new Promise((resolve, reject) => {
    connection.query(query, (error, results) => {
      if (error) {
        return reject(error)
      }

      connection.end()

      return resolve(results)
    })
  })
}

module.exports = (on, config) => {
  on('task', {
    // destructure the argument into the individual fields
    queryDatabase({ dbName, query }) {
      const connectionInfo = connections[dbName]
  
      if (!connectionInfo) {
        throw new Error(`Do not have DB connection under name ${dbName}`)
      }
  
      return queryDB(connectionInfo, query)
    },
  })
}

Argument should be serializable

The argument arg sent via cy.task(name, arg) should be serializable; it cannot have circular dependencies (issue #5539). If there are any special fields like Date, you are responsible for their conversion (issue #4980):

// in test
cy.task('date', new Date()).then((s) => {
  // the yielded result is a string
  // we need to convert it to Date object
  const result = new Date(s)
})
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    setupNodeEvents(on, config) {
      on('task', {
        date(s) {
          // s is a string, so convert it to Date
          const d = new Date(s)
      
          // do something with the date
          // and return it back
          return d
        },
      })
    }
  }
})
import { defineConfig } from 'cypress'

export default defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    setupNodeEvents(on, config) {
      on('task', {
        date(s) {
          // s is a string, so convert it to Date
          const d = new Date(s)
      
          // do something with the date
          // and return it back
          return d
        },
      })
    }
  }
})
// cypress/plugins/index.js

module.exports = (on, config) => {
  on('task', {
    date(s) {
      // s is a string, so convert it to Date
      const d = new Date(s)
  
      // do something with the date
      // and return it back
      return d
    },
  })
}

Rules

Requirements

  • cy.task() requires being chained off of cy .
  • cy.task() requires the task to eventually end.

Assertions

  • cy.task() will only run assertions you have chained once, and will not retry .

Timeouts

  • cy.task() can time out waiting for the task to end.

Command Log

This example uses the Return number of files in the folder task defined above.

cy.task('countFiles', 'cypress/integration')

The command above will display in the Command Log as:

Command Log task

When clicking on the task command within the command log, the console outputs the following:

Console Log task

History

VersionChanges
3.0.0cy.task() command added

See also