英文:
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-a
、button-b
和button-c
,对应按钮A、B和C。这些元素可能会随机插入,而不仅仅是追加到div.new-elements-wrapper
容器的末尾。
我想编写一个有效的测试:
- 点击按钮A。
- 等待新元素被添加到
div.new-elements-wrapper
区域。 - 当区域中出现新元素时,验证它是否具有
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:
- Click button A.
- Wait for new element to be added to
div.new-elements-wrapper
region. - When new element appears in region, validate that it has a
data-type
attribute and that it's value isbutton-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'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('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)
})
Use like this
cy.watchForMutation('#container', () => {
cy.get('button').click()
}, {timeout:6000})
.then(newElement => {
...
})
Notes
- the
selector *
works for all descendants (likesubTree:true
option) - the
should()
clause waits for the new element withinoptions.timeout
time frame options.timeout
defaults to standard command timeout of 4000 ms if not passed- the
.then(() => 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
<div>
to<body>
- does the add asynchronously
- gives the new
<div>
a (predictable) attribute for selecting
<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>
Here's the way Cypress observes the mutations to the DOM
cy.contains('button', 'a').click()
cy.contains('[data-test-id="0"]', 'div 0') // retries up to 4 seconds
cy.contains('button', 'b').click()
cy.contains('[data-test-id="1"]', 'div 1') // retries up to 4 seconds
cy.contains('button', 'c').click()
cy.contains('[data-test-id="2"]', 'div 2') // retries up to 4 seconds
<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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论