在 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 的答案:

但我似乎找不到我想要的东西,主要是因为 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 被添加到赛普拉斯命令队列然后顺序执行。

结合使用 wrapeach 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)
})