为什么 Cypress 在运行 get 命令后说我的元素已分离?

Gwy*_*ynn 54 javascript automation browser-automation cypress

目标: 我想使用 cypress 的辅助功能选择器单击页面上的特定元素

代码

cy.findAllByRole('rowheader').eq(2).click();
Run Code Online (Sandbox Code Playgroud)

错误

Timed out retrying: cy.click() failed because this element is detached from the DOM.

<th scope="row" data-automation-id="taskItem" aria-invalid="false" tabindex="-1" class="css-5xw9jq">...</th>

Cypress requires elements be attached in the DOM to interact with them.

The previous command that ran was:

  > cy.eq()

This DOM element likely became detached somewhere between the previous and current command.
Run Code Online (Sandbox Code Playgroud)

问题: 我可以在 DOM 中看到该元素仍然存在 - 没有逻辑将该元素与 DOM 分离,并且 eq 方法当然不会这样做。此外,findAllByRow 方法显然正在工作,因为它找到了我想要单击的正确元素。怎么会说五行分离呢?对于这种情况有解决方法吗?

Ε Г*_*И О 33

这可能是个坏建议,但你能尝试以下方法吗?

cy.findAllByRole('rowheader').eq(2).click({force: true})
Run Code Online (Sandbox Code Playgroud)

  • `{force: true}` 是唯一对我有用的东西。 (14认同)

Fod*_*ody 27

没有可重现的示例,这是推测性的,但请尝试使用Cypress.dom.isDetached在单击之前添加防护

cy.findAllByRole('rowheader').eq(2)
  .should($el => {
    expect(Cypress.dom.isDetached($el)).to.eq(false)
  })   
  .click()
Run Code Online (Sandbox Code Playgroud)

如果期望失败,则先前的行导致分离并且cy.findAllByRole没有正确地重新查询元素。

如果是这样,你也许可以用普通的cy.get().


我也喜欢 @AntonyFuentesArtavia 使用别名的想法,因为别名机制保存原始查询,并在发现其主题分离时专门重新查询 DOM。

在这里查看我的答案

来自赛普拉斯源

const resolveAlias = () => {
  // if this is a DOM element
  if ($dom.isElement(subject)) {
    let replayFrom = false

    const replay = () => {
      cy.replayCommandsFrom(command)

      // its important to return undefined
      // here else we trick cypress into thinking
      // we have a promise violation
      return undefined
    }

    // if we're missing any element
    // within our subject then filter out
    // anything not currently in the DOM
    if ($dom.isDetached(subject)) {
      subject = subject.filter((index, el) => $dom.isAttached(el))

      // if we have nothing left
      // just go replay the commands
      if (!subject.length) {
        return replay()
      }
    }
Run Code Online (Sandbox Code Playgroud)


Ant*_*via 13

不要尝试强制单击/操作,而是使用 Cypress 别名并充分利用内置的断言重试能力。

例子:

cy.get('some locator').first().find('another locator').eq(1).as('element');
cy.get('@element').should(***some assertion to be retried until met***);
Run Code Online (Sandbox Code Playgroud)

尽管我正在遍历很多元素(这可能会导致问题,因为其中一个父元素可能会分离),但最终当我在其上放置别名时,我添加了指向最终元素的直接链接在链末端产生的元素。然后,当我引用该别名时,Cypress 将重新查询最终元素,并根据添加在其顶部的任何断言根据需要重试。这确实对我防止这个独立问题有很大帮助。


小智 12

我正在运行同样的问题,运行时出现相同的错误:

cy.get('<elementId>').should('be.visible').click();
Run Code Online (Sandbox Code Playgroud)

我可以看到,当测试运行时,它找到了该元素(并突出显示了它),断言得到了验证,然后以某种方式无法.click()找到该元素,即使它是链接的。

我发现在此行之前添加静态等待几秒钟可以解决问题,但我不确定为什么我需要这样做,并且不想使用静态等待。

没有正在运行的异步任务,因此无法进行动态等待。


Ala*_*Das 11

您的问题的答案写在您收到的错误消息中:

超时重试:cy.click() 失败,因为该元素已与 DOM 分离。

...

Cypress 需要将元素附加到 DOM 中才能与它们交互。

之前运行的命令是:

cy.eq()

该 DOM 元素可能在上一个命令和当前命令之间的某个位置分离。

收到此错误意味着您尝试与“死”DOM 元素进行交互 - 这意味着它已从 DOM 中分离或完全删除。因此,当 cypress 即将单击该元素时,该元素eq()要么已从 DOM 分离,要么已从 DOM 中删除。

在现代 JavaScript 框架中,DOM 元素会定期重新渲染 - 这意味着旧元素将被丢弃,并放置一个新元素。由于这种情况发生得如此之快,因此用户可能会觉得好像没有任何明显变化。但是,如果您正在执行测试命令,则您正在交互的元素可能已“死亡”。要处理这种情况,您必须:

  • 了解应用程序何时重新呈现
  • 重新查询新添加的 DOM 元素
  • 保护 Cypress 运行命令,直到满足特定条件

当我们说守卫时,这通常意味着:

  • 写一个断言
  • 等待 XHR

您可以从cypress 文档官方 cypress 博客中阅读更多内容。

解决方案:首先确保您的元素已加载且可见,然后执行click()

cy.findAllByRole('rowheader').eq(2).should('be.visible').click();
Run Code Online (Sandbox Code Playgroud)

  • 两个赛普拉斯命令之间怎么可能存在竞争条件?它们按顺序运行。 (8认同)
  • 守卫也不起作用,例如: `cy.get('button').should('be.visible').click()` 在 `.click()` 调用中失败。Cypress 确认该按钮可见,但点击失败。现在告诉我这不是产品中的错误。 (5认同)
  • 这是一个好问题,我会尽力回答这个问题。`should('be.visble')` 或任何 should 断言将重试,直到满足条件。IMO cypress 分离错误是一个时间游戏,断言可以为 DOM 赢得更多时间来稳定,以便下一个 cypress 命令成功工作。我已经看到多个断言在我的工作中工作得很好,就像 `cy.get('selector'.)should('be.visble').and('have.text', 'some text')` 一样。您还可以通过传递选项来添加超时。除了使用“cy.wait”之外的任何方法都是一个好方法。 (3认同)

小智 5

发生这种情况是因为 React 重新渲染了整个页面。尝试使用一个命令查找元素:

// do this
cy.get('[role="rowheader"]:nth-ckild(2)').click();

// instead of this
cy.findAllByRole('rowheader').eq(2).should('be.visible').click();
Run Code Online (Sandbox Code Playgroud)

使用单个命令修复了 Cypress 仅重试该命令之前的最后一个命令的事实should。因此,以前仅.eq(2)会重试。然而,您cy.findAllByRole('rowheader')也需要重试。

Cypress 提出了另一种交替命令和断言的解决方案。它对我不起作用,但你可以尝试一下:

cy.findAllByRole('rowheader').should('be.visible').eq(2).should('be.visible').click();
Run Code Online (Sandbox Code Playgroud)