在 Cypress 的 for 循环中等待异步方法
Wait for async method in for loop in Cypress
编辑:为什么这不是重复:因为 赛普拉斯,只是阅读而不是将所有内容标记为重复。
编辑 2:此外,请参阅答案以更好地理解通常的异步 for
循环问题与此问题之间的区别。
我正在编写赛普拉斯测试,我想创建一个赛普拉斯命令,用用户列表填充我的数据库。我希望创建循环在移动到下一个用户之前等待创建每个用户(因为我希望按特定顺序完成)。
现在,我的循环看起来像这样:
Cypress.Commands.add("populateDb", (users) => {
var createdItems = []
for (const user of users) {
cy.createUser(user, 'passe').then(response => {
createdUsers.unshift(response.body.user)
})
}
return createdItems
})
当然,这个循环不会等待每个用户都被创建后再进入下一个(我想要 'sequential treatment',NOT 'parallel and then wait for all promise to resolve' )
我在这里阅读了关于异步 for-loop 的答案:
- How do I return the response from an asynchronous call?
但我似乎找不到我想要的东西,主要是因为 cypress 不允许我将我的函数声明为异步函数,如下所示:
Cypress.Commands.add("populateDb", async (users) => {
//Some code
})
如果我不声明它 async
我将无法使用 await
。
难道没有一些 get()
方法之王同步等待 Promise 解决吗?
事实证明,赛普拉斯对等待异步方法解析的操作如此严格是有原因的:它总是自动按顺序运行所有异步命令,因为它们被调用,而不是并行运行,所以即使 createUser
是异步的,这也会以正确的顺序执行:
Cypress.Commands.add("populateDb", (users) => {
for (const user in users) {
cy.createUser(user).then(response => {
console.log(response)
})
}
})
如果你想获得返回的值(在我的例子中,我需要用户 ID 稍后删除它们),你可以将它们存储在文件根级别的 var
中并添加一个 cypress 命令 returns 那个 var.
var createdItems = []
Cypress.Commands.add("getCreatedItems", () => {
return createdItems
})
Cypress.Commands.add("populateDb", (users) => {
for (const user in users) {
cy.createUser(user).then(response => {
createdItems.unshift(response) // I unshift() because I need to delete them in FILO order later
})
}
})
然后在 cypress 测试文件中,您可以按照需要它们执行的顺序调用它们:
cy.populateDb(users)
cy.getCreatedItems().then(response => {
//response is my createdItems Array, and cy.getCreatedItems() will run only when cy.populateDb() is resolved
})
您也可以这样做:
Cypress.Commands.add("populateDb", (users) => {
for (const user in users) {
cy.createUser(user).then(response => {
createdItems.unshift(response) // I unshift() because I need to delete them in FILO order later
})
}
return createdItems;
})
cy.populateDb(users).then(response => {
// ... runs after populate ...
})
但另一个答案也对。每个 cy.xxxxx
命令实际上被添加到命令队列并且 运行 一个接一个地添加。如果在队列执行期间调用了新的 cy.xxxxx2
命令,它们将被添加到队列的前面。
这里用一个简单的例子来解释cypress命令队列和同步代码的执行顺序:
const a = 1; // a == 1
cy.cmd1().then(() => {
cy.cmd2().then(() => {
a += 1; // a == 5
});
a += 1; // a == 3
cy.cmd3()
a += 1; // a == 4
});
cy.cmd4().then(() => {
a += 1; // a == 6
});
a += 1; // a == 2
// at this point cypress command queue starts running the queue that is cmd1, cmd4
// 1. cmd1 runs and adds cmd2 and cmd3 to front of command queue also adds +2 to a
// 2. cmd2 runs and adds +1 to a
// 3. cmd3 runs
// 4. cmd4 runs and adds +1 to a
// ... all done ...
所以从这个例子中你可以看到在你的情况下你的循环将被串行执行,因为每个 cy.createUser
被添加到赛普拉斯命令队列然后顺序执行。
结合使用 wrap
和 each
cypress 命令,我能够实现一个等待每次迭代的循环,并且 return 无需全局变量即可获得完整结果。
我使用 wrap
命令的唯一原因是 cypress 命令 each
要求它与之前的 cypress 命令链接在一起。 each
命令将评估每次迭代并最终调用 then
,您可以在其中 return 完整的结果数组。我正在使用它来上传多个文件和 return 密钥列表,但您可以根据自己的需要修改它。
Cypress.Commands.add("uploadMultipleFiles", (files) => {
var uploadedS3Keys = []
cy.wrap(files).each((file, index, list) => {
cy.uploadFileToOrdersApi(file)
.then(s3Key => uploadedS3Keys.push(s3Key))
}).then(() => uploadedS3Keys)
})
编辑:为什么这不是重复:因为 赛普拉斯,只是阅读而不是将所有内容标记为重复。
编辑 2:此外,请参阅答案以更好地理解通常的异步 for
循环问题与此问题之间的区别。
我正在编写赛普拉斯测试,我想创建一个赛普拉斯命令,用用户列表填充我的数据库。我希望创建循环在移动到下一个用户之前等待创建每个用户(因为我希望按特定顺序完成)。
现在,我的循环看起来像这样:
Cypress.Commands.add("populateDb", (users) => {
var createdItems = []
for (const user of users) {
cy.createUser(user, 'passe').then(response => {
createdUsers.unshift(response.body.user)
})
}
return createdItems
})
当然,这个循环不会等待每个用户都被创建后再进入下一个(我想要 'sequential treatment',NOT 'parallel and then wait for all promise to resolve' )
我在这里阅读了关于异步 for-loop 的答案:
- How do I return the response from an asynchronous call?
但我似乎找不到我想要的东西,主要是因为 cypress 不允许我将我的函数声明为异步函数,如下所示:
Cypress.Commands.add("populateDb", async (users) => {
//Some code
})
如果我不声明它 async
我将无法使用 await
。
难道没有一些 get()
方法之王同步等待 Promise 解决吗?
事实证明,赛普拉斯对等待异步方法解析的操作如此严格是有原因的:它总是自动按顺序运行所有异步命令,因为它们被调用,而不是并行运行,所以即使 createUser
是异步的,这也会以正确的顺序执行:
Cypress.Commands.add("populateDb", (users) => {
for (const user in users) {
cy.createUser(user).then(response => {
console.log(response)
})
}
})
如果你想获得返回的值(在我的例子中,我需要用户 ID 稍后删除它们),你可以将它们存储在文件根级别的 var
中并添加一个 cypress 命令 returns 那个 var.
var createdItems = []
Cypress.Commands.add("getCreatedItems", () => {
return createdItems
})
Cypress.Commands.add("populateDb", (users) => {
for (const user in users) {
cy.createUser(user).then(response => {
createdItems.unshift(response) // I unshift() because I need to delete them in FILO order later
})
}
})
然后在 cypress 测试文件中,您可以按照需要它们执行的顺序调用它们:
cy.populateDb(users)
cy.getCreatedItems().then(response => {
//response is my createdItems Array, and cy.getCreatedItems() will run only when cy.populateDb() is resolved
})
您也可以这样做:
Cypress.Commands.add("populateDb", (users) => {
for (const user in users) {
cy.createUser(user).then(response => {
createdItems.unshift(response) // I unshift() because I need to delete them in FILO order later
})
}
return createdItems;
})
cy.populateDb(users).then(response => {
// ... runs after populate ...
})
但另一个答案也对。每个 cy.xxxxx
命令实际上被添加到命令队列并且 运行 一个接一个地添加。如果在队列执行期间调用了新的 cy.xxxxx2
命令,它们将被添加到队列的前面。
这里用一个简单的例子来解释cypress命令队列和同步代码的执行顺序:
const a = 1; // a == 1
cy.cmd1().then(() => {
cy.cmd2().then(() => {
a += 1; // a == 5
});
a += 1; // a == 3
cy.cmd3()
a += 1; // a == 4
});
cy.cmd4().then(() => {
a += 1; // a == 6
});
a += 1; // a == 2
// at this point cypress command queue starts running the queue that is cmd1, cmd4
// 1. cmd1 runs and adds cmd2 and cmd3 to front of command queue also adds +2 to a
// 2. cmd2 runs and adds +1 to a
// 3. cmd3 runs
// 4. cmd4 runs and adds +1 to a
// ... all done ...
所以从这个例子中你可以看到在你的情况下你的循环将被串行执行,因为每个 cy.createUser
被添加到赛普拉斯命令队列然后顺序执行。
结合使用 wrap
和 each
cypress 命令,我能够实现一个等待每次迭代的循环,并且 return 无需全局变量即可获得完整结果。
我使用 wrap
命令的唯一原因是 cypress 命令 each
要求它与之前的 cypress 命令链接在一起。 each
命令将评估每次迭代并最终调用 then
,您可以在其中 return 完整的结果数组。我正在使用它来上传多个文件和 return 密钥列表,但您可以根据自己的需要修改它。
Cypress.Commands.add("uploadMultipleFiles", (files) => {
var uploadedS3Keys = []
cy.wrap(files).each((file, index, list) => {
cy.uploadFileToOrdersApi(file)
.then(s3Key => uploadedS3Keys.push(s3Key))
}).then(() => uploadedS3Keys)
})