乐趣区

关于cypress:cypress-的错误消息-the-element-has-become-detached-from-the-dom

这个谬误音讯的剖析和解决方案,能够参考 Cypress 的官网文档。

这个谬误音讯提醒咱们,咱们编写的 Cypress 代码正在同一个“死去”的 DOM 元素交互。

显然,在实在的应用场景下,一个用户也无奈同这种类型的 UI 元素交互。

看个理论例子:

<body>
  <div id="parent">
    <button>delete</button>
  </div>
</body>

这个 button 被点击之后,会将本人从 DOM 树中移除:

$('button').click(function () {
  // when the <button> is clicked
  // we remove the button from the DOM
  $(this).remove()})

下列这行测试代码会引起谬误:

cy.get('button').click().parent()

当 cypress 执行到下一个 parent 命令时,检测到施加该命令的 button 曾经从 DOM 树中移除了,因而会报错。

解决方案:

cy.get('button').click()
cy.get('#parent')

解决此类问题的指导方针:

Guard Cypress from running commands until a specific condition is met

两种实现 guard 的形式:

  1. Writing an assertion
  2. Waiting on an XHR

看另一个例子:

输出 clem,从后果列表里抉择 User clementine , 即所谓的 type head search 成果。

测试代码如下:

it('selects a value by typing and selecting', () => {
  // spy on the search XHR
  cy.server()
  cy.route('https://jsonplaceholder.cypress.io/users?term=clem&_type=query&q=clem').as('user_search')

  // first open the container, which makes the initial ajax call
  cy.get('#select2-user-container').click()

  // then type into the input element to trigger search, and wait for results
  cy.get('input[aria-controls="select2-user-results"]').type('clem{enter}')

  // select a value, again by retrying command
  // https://on.cypress.io/retry-ability
  cy.contains('.select2-results__option', 'Clementine Bauch').should('be.visible').click()
  // confirm Select2 widget renders the name
  cy.get('#select2-user-container').should('have.text', 'Clementine Bauch')
})

要点:

应用 cy.route 监控某个 XHR, 这里能够监控相对路径吗?

本地测试通过,在 CI 上运行时会遇到下列谬误:

如何剖析这个问题呢?能够应用 pause 操作,让 test runner 暂停。

// first open the container, which makes the initial ajax call
cy.get('#select2-user-container').click().pause()

当咱们点击了 select2 widget 时,会立刻触发一个 AJAX call. 而测试代码并不会期待 clem 搜寻申请的返回。它只是二心查找 “Clementine Bauch” 的 DOM 元素。

// first open the container, which makes the initial ajax call
cy.get('#select2-user-container').click()

// then type into the input element to trigger search,
// and wait for results
cy.get('input[aria-controls="select2-user-results"]').type('clem{enter}')

cy.contains('.select2-results__option',
   'Clementine Bauch').should('be.visible').click()

下面的测试在本地运行时大部分工夫可能会通过,但在 CI 上它可能会常常失败,因为网络调用速度较慢,浏览器 DOM 更新可能较慢。以下是测试和应用程序如何进入导致“detached element”谬误的竞争条件。

  1. 测试点击小部件
  2. Select2 小部件触发第一个搜寻 Ajax 调用。在 CI 上,此调用可能比预期慢。
  3. 测试代码输出“clem”进行搜寻,这会触发第二个 AJAX 调用。
  4. Select2 小部件接管对带有十个用户名的第一个搜寻 Ajax 调用的响应,其中一个是“Clementine Bauch”。这些名称被增加到 DOM

而后测试搜寻可见的抉择“Clementine Bauch”– 并在初始用户列表中找到它。

而后,测试运行器将要单击找到的元素。留神这里的竟态条件。当第二个搜寻 Ajax 调用“term=clem”从服务器返回时。Select2 小部件删除以后的选项列表,只显示两个找到的用户:“Clementine Bauch”和“Clementina DuBuque”。

而后测试代码执行 Clem 元素的点击。

Cypress 抛出谬误,因为它要单击的带有文本“Clementine Bauch”的 DOM 元素不再链接到 HTML 文档;它已被应用程序从文档中删除,而 Cypress 依然援用了该元素。

这就是问题的本源。

上面这段代码能够人为地让这个竟态条件总是触发:

cy.contains('.select2-results__option',
            'Clementine Bauch').should('be.visible')
  .pause()
  .then(($clem) => {
    // remove the element from the DOM using jQuery method
    $clem.remove()
    // pass the element to the click
    cy.wrap($clem)
  })
  .click()

既然理解了竟态条件触发的本源,修改起来就有方向了。

咱们心愿测试在持续之前始终期待应用程序实现其操作。

解决方案:

cy.get('#select2-user-container').click()

// flake solution: wait for the widget to load the initial set of users
cy.get('.select2-results__option').should('have.length.gt', 3)

// then type into the input element to trigger search
// also avoid typing "enter" as it "locks" the selection
cy.get('input[aria-controls="select2-user-results"]').type('clem')

咱们通过 cy.get(‘XXX’).should(”) 操作,确保在执行 clem 输出之前,初始的 user list 对应的 AJAX 肯定回复到服务器上了,否则 select2-options 的长度必然小于 3.

当测试在搜寻框中键入“clem”时,应用程序将触发 Ajax 调用,该调用返回用户的子集。因而,测试须要期待显示新汇合 – 否则它将从初始列表中找到“Clementine Bauch”并遇到 detached 谬误。咱们晓得只有两个用户匹配“clem”,因而咱们能够再次确认显示的用户数以期待应用程序。

/ then type into the input element to trigger search, and wait for results
cy.get('input[aria-controls="select2-user-results"]').type('clem')

// flake solution: wait for the search for "clem" to finish
cy.get('.select2-results__option').should('have.length', 2)

cy.contains('.select2-results__option', 'Clementine Bauch')
    .should('be.visible').click()

// confirm Select2 widget renders the name
cy.get('#select2-user-container')
  .should('have.text', 'Clementine Bauch')

如果自觉的在 click 调用里传入 force:true 的参数,可能会引入新的问题。


更多 Jerry 的原创文章,尽在:” 汪子熙 ”:

退出移动版