在赛普拉斯的 for 循环中等待异步方法
Posted
技术标签:
【中文标题】在赛普拉斯的 for 循环中等待异步方法【英文标题】:Wait for async method in for loop in Cypress 【发布时间】:2019-03-15 12:50:33 【问题描述】:编辑:为什么这不是重复的:因为 Cypress,只需阅读而不是将所有内容标记为重复。
编辑 2:另外,请参阅答案以更好地理解通常的异步 for
循环问题与此问题之间的区别。
我正在编写 cypress 测试,我想创建一个 cypress 命令,用用户列表填充我的数据库。我希望创建循环等待每个用户被创建,然后再转到下一个用户(因为我希望按特定顺序完成)。
目前,我的循环如下所示:
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
)
当然,这个循环不会等到每个用户都被创建后才移动到下一个用户(我想要'顺序处理',NOT'并行然后等待所有承诺解决' )
我在这里阅读了关于异步 for-loop 的答案:
javascript ES6 promise for loop Using async/await with a forEach loop How do I return the response from an asynchronous call?但我似乎找不到我想要的,主要是因为赛普拉斯不允许我将我的函数声明为异步如下:
Cypress.Commands.add("populateDb", async (users) =>
//Some code
)
如果我不声明async
,我将无法使用await
。
难道没有一些 get()
方法之王可以同步等待 Promise 解决吗?
【问题讨论】:
【参考方案1】:使用wrap
和each
cypress 命令的组合,我能够实现等待每次迭代的循环,并且无需全局变量即可返回完整结果。
我使用wrap
命令的唯一原因是因为cypress 命令each
要求它与之前的cypress 命令链接。 each
命令将评估每次迭代并最终调用then
,您可以在其中返回完整的结果数组。我正在使用它来上传多个文件并返回密钥列表,但您可以根据自己的需要进行修改。
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】:事实证明,cypress 对等待异步方法解析的操作如此严格是有原因的:它总是自动按顺序运行所有异步命令,因为它们被调用,而不是并行运行,所以这即使createUser
是异步的,也会以正确的顺序执行:
Cypress.Commands.add("populateDb", (users) =>
for (const user in users)
cy.createUser(user).then(response =>
console.log(response)
)
)
如果您想获取返回值的值(在我的情况下,我需要用户 ID 稍后删除它们),您可以将它们存储在文件根级别的 var
中并添加一个 cypress 命令返回该变量。
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
)
【讨论】:
【参考方案3】:你也可以这样做:
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
都被添加到 cypress 命令队列中,然后按顺序执行。
【讨论】:
以上是关于在赛普拉斯的 for 循环中等待异步方法的主要内容,如果未能解决你的问题,请参考以下文章
如何使赛普拉斯等待具有多个结果的异步搜索完成而不会导致测试失败