Skip to main content
Cypress应用

task

通过task插件事件在Node中执行代码。

caution
反模式

我们不建议使用cy.task()启动web服务器。请阅读最佳实践

语法

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

用法

正确用法

// 在测试中
cy.task('log', '这将在终端输出')
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
},
})
},
},
})

task插件事件处理程序可以返回一个值或Promise。如果返回undefined或Promise解析为undefined,命令将失败。这有助于捕获拼写错误或未处理任务事件的情况。

如果不需要返回值,请显式返回null以表示给定事件已处理。

参数

event (String)

要通过setupNodeEvents函数中的task事件处理的事件名称。

arg (Object)

随事件发送的参数。可以是任何能被JSON.stringify()序列化的值。不可序列化的类型(如函数、正则表达式或符号)将被省略为null

如果需要传递多个参数,请使用对象:

// 在测试中
cy.task('hello', { greeting: '你好', name: '世界' })
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
},
})
},
},
})

options (Object)

传入选项对象以更改cy.task()的默认行为。

选项默认值描述
logtrue命令日志中显示命令
timeouttaskTimeout等待cy.task()解析的时长,超时后将超时

生成结果 了解主题管理

cy.task()返回setupNodeEventstask事件返回或解析的值。

示例

cy.task()为运行任意Node代码提供了逃生舱,因此您可以在Cypress范围之外执行测试所需的操作。这适用于:

  • 为测试数据库播种。
  • 在Node中存储状态,以便在规范文件之间持久化。
  • 执行并行任务,如在Cypress之外发起多个HTTP请求。
  • 运行外部进程。

读取可能不存在的文件

命令cy.readFile()假定文件存在。如果需要读取可能不存在的文件,请使用cy.task

// 在测试中
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
},
})
},
},
})

返回文件夹中的文件数量

// 在测试中
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)
})
})
},
})
},
},
})

为数据库播种

// 在测试中
describe('e2e', () => {
beforeEach(() => {
cy.task('defaults:db')
cy.visit('/')
})

it('显示文章值', () => {
cy.get('.article-list').should('have.length', 10)
})
})
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')
},
})
},
},
})

从异步任务返回Promise

// 在测试中
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) => {
// 任务不应解析为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)
})
})
})
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
},
})
},
},
})

命令选项

更改超时时间

您可以增加执行任务的时间,尽管_我们不建议执行需要很长时间才能退出的任务_。

Cypress在cy.task()完成之前_不会_继续运行任何其他命令,因此长时间运行的任务会大大减慢测试运行速度。

// 如果数据库播种超过20秒,将失败
cy.task('seedDatabase', null, { timeout: 20000 })

注意事项

任务必须结束

不支持不结束的任务

cy.task()不支持不结束的任务,例如:

  • 启动服务器。
  • 监视文件更改的任务。
  • 任何需要手动中断才能停止的进程。

任务必须在taskTimeout内结束,否则Cypress将使当前测试失败。

任务自动合并

有时您可能使用导出其任务以供注册的插件。Cypress会自动为您合并on('task')对象。例如,如果您使用cypress-skip-and-only-ui插件并希望安装自己的任务来读取可能不存在的文件:

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)
},
},
})

有关实现,请参见#2284

caution
重复的任务键

如果多个任务对象使用相同的键,后注册的将覆盖该特定键,类似于合并具有重复键的多个对象时将覆盖第一个对象。

通过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 })
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)
},
})
},
},
})

参数应可序列化

通过cy.task(name, arg)发送的参数arg应可序列化;不能有循环依赖(问题#5539)。如果有任何特殊字段如Date,您需要负责它们的转换(问题#4980):

// 在测试中
cy.task('date', new Date()).then((s) => {
// 产生的结果是字符串
// 我们需要将其转换为Date对象
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是字符串,因此将其转换为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()命令

另请参阅