task
通过task
插件事件在Node中执行代码。
我们不建议使用cy.task()
启动web服务器。请阅读最佳实践。
语法
cy.task(event)
cy.task(event, arg)
cy.task(event, arg, options)
用法
正确用法
// 在测试中
cy.task('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) {
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
},
})
},
},
})
task
插件事件处理程序可以返回一个值或Promise。如果返回undefined
或Promise解析为undefined
,命令将失败。这 有助于捕获拼写错误或未处理任务事件的情况。
如果不需要返回值,请显式返回null
以表示给定事件已处理。
参数
event (String)
要通过setupNodeEvents函数中的task
事件处理的事件名称。
arg (Object)
随事件发送的参数。可以是任何能被JSON.stringify()序列化的值。不可序列化的类型(如函数、正则表达式或符号)将被省略为null
。
如果需要传递多个参数,请使用对象:
// 在测试中
cy.task('hello', { greeting: '你好', name: '世界' })
- 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', {
// 解构各个属性
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', {
// 解构各个属性
hello({ greeting, name }) {
console.log('%s, %s', greeting, name)
return null
},
})
},
},
})
options (Object)
传入选项对象以更改cy.task()
的默认行为。
选项 | 默认值 | 描述 |
---|---|---|
log | true | 在命令日志中显示命令 |
timeout | taskTimeout | 等待cy.task() 解析的时长,超时后将超时 |
生成结果
cy.task()
返回setupNodeEvents中task
事件返回或解析的值。
示例
cy.task()
为运行任意Node代码提供了逃生舱,因此您可以在Cypress范围之外执行测试所需的操作。这适用于:
- 为测试数据库播种。
- 在Node中存储状态,以便在规范文件之间持久化。
- 执行并行任务,如在Cypress之外发起多个HTTP请求。
- 运行外部进程。
读取可能不存在的文件
命令cy.readFile()假定文件存在。如果需要读取可能不存在的文件,请使用cy.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
},
})
},
},
})
返回文件夹中的文件数量
// 在测试中
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)
})
})
},
})
},
},
})
为数据库播种
// 在测试中
describe('e2e', () => {
beforeEach(() => {
cy.task('defaults:db')
cy.visit('/')
})
it('显示文章值', () => {
cy.get('.article-list').should('have.length', 10)
})
})
- cypress.config.js 文件
- cypress.config.ts 文件
const { defineConfig } = require('cypress')
// 我们应用中需要一些负责为数据库播种的代码
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'
// 我们应用中需要一些负责为数据库播种的代码
import db from '../../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')
},
})
},
},
})
从异步任务返回Promise
// 在测试中
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) => {
// 任务不应解析为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) => {
// 任务不应解析为undefined
setTimeout(() => resolve(null), ms)
})
},
})
},
},
})
跨非同源URL访问保存变量
访问非同源URL时,Cypress会将托管URL更改为新URL,清除任何局部变量的状态。我们希望跨访问非同源URL保存变量。
我们可以使用cy.task()
保存变量并在测试外部检索保存的变量,如下所示。
// 在测试中
describe('Href访问', () => {
it('捕获href', () => {
cy.visit('https://example.cypress.io')
cy.get('a')
.invoke('attr', 'href')
.then((href) => {
// href与当前URL非同源
// 如https://www.cypress-dx.com
cy.task('setHref', href)
})
})
it('访问href', () => {
cy.task('getHref').then((href) => {
// 访问非同源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
},
})
},
},
})
命令选项
更改超时时间
您可以增加执行任务的时间,尽管_我们不建议执行需要很长时间才能退出的任务_。
Cypress在cy.task()
完成之前_不会_继续运行任何其他命令,因此长时间运行的任务会大大减慢测试运行速度。
// 如果数据库播种超过20秒,将失败
cy.task('seedDatabase', null, { timeout: 20000 })
注意事项
任务必须结束
不支持不结束的任务
cy.task()
不支持不结束的任务,例如:
- 启动服务器。
- 监视文件更改的任务。
- 任何需要手动中断才能停止的进程。
任务必须在taskTimeout
内结束,否则Cypress将使当前测试失败。
任务自动合并
有时您可能使用导出其任务以供注册的插件。Cypress会自动为您合并on('task')
对象。例如,如果您使用cypress-skip-and-only-ui插件并希望安装自己的任务来读取可能不存在的文件:
- cypress.config.js 文件
- cypress.config.ts 文件
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) {
// 注册插件的任务
on('task', skipAndOnlyTask)
// 并注册我自己的任务
on('task', myTask)
},
},
})
import { defineConfig } from 'cypress'
import skipAndOnlyTask from 'cypress-skip-and-only-ui/task'
import fs from '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) {
// 注册插件的任务
on('task', skipAndOnlyTask)
// 并注册我自己的任务
on('task', myTask)
},
},
})
有关实现,请参见#2284。
如果多个任务对象使用相同的键,后注册的将覆盖该特定键,类似于合并具有重复键的多个对象时将覆盖第一个对象。
通过Cypress.config()
重置超时
您可以通过在Cypress.config()中为taskTimeout
设置新值来更改cy.task()
的剩余测试超时时间。
Cypress.config('taskTimeout', 30000)
Cypress.config('taskTimeout') // => 30000
在测试配置中设置超时
您可以在套件或测试中通过测试配置传递新的配置值来配置cy.task()
超时。
这将在测试期间设置超时,完成后将其返回到默认的taskTimeout
。
describe('数据库中有可用数据', { taskTimeout: 90000 }, () => {
before(() => {
cy.task('seedDatabase')
})
// 测试
after(() => {
cy.task('resetDatabase')
})
})
仅允许单个参数
语法cy.task(name, arg, options)
仅支持从测试代码向插件代码传递单个参数。在需要传递多个参数的情况下,将它们放入一个对象中,以便在任务代码中解构。例如,如果要执行数据库查询并传递数据库配置文件名称,可以这样做:
// 在测试中
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('mysql')
// 不同数据库的连接字符串可以来自Cypress配置或环境变量
const connections = {
stagingA: {
host: 'staging.my.co',
user: 'test',
password: '***',
database: 'users',
},
stagingB: {
host: 'staging-b.my.co',
user: 'test',
password: '***',
database: 'users',
},
}
// 从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', {
// 将参数解构为各个字段
queryDatabase({ dbName, query }) {
const connectionInfo = connections[dbName]
if (!connectionInfo) {
throw new Error(`没有名为${dbName}的数据库连接`)
}
return queryDB(connectionInfo, query)
},
})
},
},
})
import { defineConfig } from 'cypress'
import mysql from 'mysql'
// 不同数据库的连接字符串可以来自Cypress配置或环境变量
const connections = {
stagingA: {
host: 'staging.my.co',
user: 'test',
password: '***',
database: 'users',
},
stagingB: {
host: 'staging-b.my.co',
user: 'test',
password: '***',
database: 'users',
},
}
// 从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', {
// 将参数解构为各个字段
queryDatabase({ dbName, query }) {
const connectionInfo = connections[dbName]
if (!connectionInfo) {
throw new Error(`没有名为${dbName}的数据库连接`)
}
return queryDB(connectionInfo, query)
},
})
},
},
})
参数应可序列化
通过cy.task(name, arg)
发送的参数arg
应可序列化;不能有循环依赖(问题#5539)。如果有任何特殊字段如Date
,您需要负责它们的转换(问题#4980):
// 在测试中
cy.task('date', new Date()).then((s) => {
// 产生的结果是字符串
// 我们需要将其转换为Date对象
const result = new Date(s)
})
- 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', {
date(s) {
// s是字符串,因此将其转换为Date
const d = new Date(s)
// 对日期进行操作
// 并将其返回
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是字符串,因此将其转换为Date
const d = new Date(s)
// 对日期进行操作
// 并将其返回
return d
},
})
},
},
})
规则
要求
cy.task()
需要链接到cy
。cy.task()
要求任务最终结束。
断言
cy.task()
仅运行一次您链接的断言,不会重试。
超时设置
cy.task()
可能会因等待任务结束而超时。
命令日志
此示例使用上面定义的返回文件夹中的文件数量任务。
cy.task('countFiles', 'cypress/e2e')
上面的命令将在命令日志中显示为:

当点击命令日志中的task
命令时,控制台输出以下内容:

历史
版本 | 变更 |
---|---|
3.0.0 | 添加了cy.task() 命令 |