在Cypress中,如何与页面上新添加的元素交互?

huangapple go评论63阅读模式
英文:

In Cypress, how do I interact with a newly added element to the page?

问题

我想在Cypress中使用MutationObserver,因为它似乎是一个适当的机制,可以用来检测DOM发生的特定变化,然后与添加的元素进行交互。

假设我有一个界面,其中有3个按钮和一个div.new-elements-wrapper。单击每个按钮会在div.new-elements-wrapper中添加一个新的div元素,其data-type属性的值分别为button-abutton-bbutton-c,对应按钮A、B和C。这些元素可能会随机插入,而不仅仅是追加到div.new-elements-wrapper容器的末尾。

我想编写一个有效的测试:

  1. 点击按钮A。
  2. 等待新元素被添加到div.new-elements-wrapper区域。
  3. 当区域中出现新元素时,验证它是否具有data-type属性,以及其值是否为button-a

对于第2步,我不想继续,直到MutationObserver配置为专门监视div.new-elements-wrapper,并且在变异的nodesAddedList中有一个单一节点。然后,在第3步中,验证data-type属性是否为button-a

对于Cypress高手们,你们会如何做到这一点呢?

编辑:请注意,这些添加的组件上没有可唯一识别的信息。如果有两个元素,它们都是button-a,那么唯一知道哪一个被添加的方法就是通过MutationObserver通知。

英文:

I want to use MutationObserver with Cypress because it feels like an appropriate mechanism to use for detecting specific changes occurred to the DOM and then interact with element(s) that were added.

Assume that I have a UI that has 3 buttons and a div.new-elements-wrapper. Clicking each button results in a new div element into div.new-elements-wrapper with a data-type attribute of value button-a, button-b, and button-c for buttons A, B, and C, respectively. It's possible that these elements can be randomly inserted, not just appended to the end of the div.new-elements-wrapper container.

I want to write a test that effectively is:

  1. Click button A.
  2. Wait for new element to be added to div.new-elements-wrapper region.
  3. When new element appears in region, validate that it has a data-type attribute and that it's value is button-a.

Repeat for buttons B, and C.

For step 2, I don't want to continue on until a MutationObserver, configured to look specifically at div.new-elements-wrapper, has a single node in a mutation's nodesAddedList. Then, in step 3, validate the data-type attribute is button-a.

For the Cypress wizards out there, how would you do this?

EDIT: Note that there is no uniquely identifying information available on these components that are added. If there are two elements, both button-a, then the only way to know which was added would be to informed by a MutationObserver.

答案1

得分: 7

Cypress.Commands.add('watchForMutation', (selector, action, options = {}) => {
  const domTree = cy.$$(`${selector} *`)
  let diff;
  return cy.wrap(action(), options)
    .should(() => {
      const newDomTree = cy.$$(`${selector} *`)
      diff = newDomTree.not(domTree)
      expect(diff.length).to.be.gt(0)             // retry until true or timeout
    })
    .then(() => diff)
})

使用方式如下

```js
cy.watchForMutation('#container', () => {
  cy.get('button').click()
}, {timeout:6000})
.then(newElement => {
  ...
})

注意事项

  • selector * 对所有后代元素有效(类似于 subTree:true 选项)
  • should() 子句等待新元素在 options.timeout 时间范围内
  • 如果未传递,options.timeout 默认为标准命令超时时间 4000 毫秒
  • .then(() => diff) 返回新元素

<details>
<summary>英文:</summary>

The `MutationObserver` API would need a spy or a promise to work reliably in a Cypress test, since it invokes it&#39;s handler asynchronously to the test. 

This is how I would implement a mutation observer in Cypress, using `*` to observe all descendants of a DOM element and jQuery `.not()` to find the differences.

```js
Cypress.Commands.add(&#39;watchForMutation&#39;, (selector, action, options = {}) =&gt; {
  const domTree = cy.$$(`${selector} *`)
  let diff;
  return cy.wrap(action(), options)
    .should(() =&gt; {
      const newDomTree = cy.$$(`${selector} *`)
      diff = newDomTree.not(domTree)
      expect(diff.length).to.be.gt(0)             // retry until true or timeout
    })
    .then(() =&gt; diff)
})

Use like this

cy.watchForMutation(&#39;#container&#39;, () =&gt; {
  cy.get(&#39;button&#39;).click()
}, {timeout:6000})
.then(newElement =&gt; {
  ...
})

Notes

  • the selector * works for all descendants (like subTree:true option)
  • the should() clause waits for the new element within options.timeout time frame
  • options.timeout defaults to standard command timeout of 4000 ms if not passed
  • the .then(() =&gt; diff) passes back the new element(s)

答案2

得分: 5

Cypress已经通过普通命令来完成这个操作。

以下是一个示例页面,执行了您所描述的操作:

  • 将按钮连接到一个函数,该函数向<body>添加了一个<div>
  • 异步执行添加操作
  • 为新的<div>提供了一个可预测的属性以供选择
<body>
  <button onclick="add()">a</button>
  <button onclick="add()">b</button>
  <button onclick="add()">c</button>
  <script>
    let id = 0
    function add() {
      setTimeout(() => {
        const div = document.createElement('div')
        div.innerText = `div ${id}`
        div.setAttribute('data-test-id', id++)
        const body = document.querySelector('body')
        body.appendChild(div)      
      }, 1000)
    }
  </script>
</body>

以下是Cypress观察DOM变化的方式:

cy.contains('button', 'a').click()
cy.contains('[data-test-id="0"]', 'div 0')   // 重试最多4秒

cy.contains('button', 'b').click()
cy.contains('[data-test-id="1"]', 'div 1')   // 重试最多4秒

cy.contains('button', 'c').click()
cy.contains('[data-test-id="2"]', 'div 2')   // 重试最多4秒    

请注意:MutationObserver不是必需的,而且会导致测试不稳定,因为它是一个在主要Cypress进程之外运行的事件处理程序。良好的测试依赖于运行器对发生的事情有控制,不同步发生的事情会使测试变得不可预测。

英文:

Cypress already does this with normal commands.

Here is a sample page that does what you describe

  • connects buttons to a function that adds a &lt;div&gt; to &lt;body&gt;
  • does the add asynchronously
  • gives the new &lt;div&gt; a (predictable) attribute for selecting
&lt;body&gt;
  &lt;button onclick=&quot;add()&quot;&gt;a&lt;/button&gt;
  &lt;button onclick=&quot;add()&quot;&gt;b&lt;/button&gt;
  &lt;button onclick=&quot;add()&quot;&gt;c&lt;/button&gt;
  &lt;script&gt;
    let id = 0
    function add() {
      setTimeout(() =&gt; {
        const div = document.createElement(&#39;div&#39;)
        div.innerText = `div ${id}`
        div.setAttribute(&#39;data-test-id&#39;, id++)
        const body = document.querySelector(&#39;body&#39;)
        body.appendChild(div)      
      }, 1000)
    }
  &lt;/script&gt;
&lt;/body&gt;

Here's the way Cypress observes the mutations to the DOM

cy.contains(&#39;button&#39;, &#39;a&#39;).click()
cy.contains(&#39;[data-test-id=&quot;0&quot;]&#39;, &#39;div 0&#39;)   // retries up to 4 seconds

cy.contains(&#39;button&#39;, &#39;b&#39;).click()
cy.contains(&#39;[data-test-id=&quot;1&quot;]&#39;, &#39;div 1&#39;)   // retries up to 4 seconds

cy.contains(&#39;button&#39;, &#39;c&#39;).click()
cy.contains(&#39;[data-test-id=&quot;2&quot;]&#39;, &#39;div 2&#39;)   // retries up to 4 seconds    

在Cypress中,如何与页面上新添加的元素交互?


<h3>Why not MutationObserver</h3>

MutationObserver isn't necessary and would give you flaky tests, since it's an event handler that acts outside the main Cypress process.

Good tests rely on the runner having control over what happens when, things that happen out of sync make the test unpredictable.

huangapple
  • 本文由 发表于 2023年6月19日 02:04:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76501922.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定