I have a list of options/buttons which I need to be sure they're all set to a specific value. Every wrapper can have several buttons but the first one is always what I want set before my tests are run.
Therefore I need loop these wrappers and target the first child / button of each of them.
Typically this would be a case for each() but Cypress errors after the first click - the DOM re-renders and Cypress can't find the remaining buttons.
Therefore I need an alternative solution. One of them would be a classic for loop. Here's the code:
<div>
<div class="ab-test-switch__experiment">
<div class="ab-test-switch__buttons"><button type="button" class="button ab-test-switch__button button--primary button--lg" data-variant="49_a">
49_a
</button><button type="button" class="button ab-test-switch__button button--secondary button--lg" data-variant="49_b">
49_b
</button>
</div>
</div>
<div class="ab-test-switch__experiment">
<div class="ab-test-switch__experiment__title">
<div class="v-popover">
<div class="trigger" style="display: inline-block;">
<p data-v-d7151c42="">(detail)</p>
</div>
</div>
</div>
<div class="ab-test-switch__buttons"><button data-v-7fea8896="" data-v-d7151c42="" type="button" class="button ab-test-switch__button button--secondary button--lg" data-variant="old_business_section" data-v-5c4d7450="">
old_business_section
</button><button data-v-7fea8896="" data-v-d7151c42="" type="button" class="button ab-test-switch__button button--primary button--lg" data-variant="new_business_section" data-v-5c4d7450="">
new_business_section
</button>
</div>
</div>
<div class="ab-test-switch__experiment">
<div class="ab-test-switch__experiment__title">
<h2 data-v-d7151c42="" data-v-5c4d7450="">53_mobile_banner</h2>
<div data-v-d7151c42="" class="v-popover" data-v-5c4d7450="">
<div class="trigger" style="display: inline-block;">
<p data-v-d7151c42="">(detail)</p>
</div>
</div>
</div>
<div class="ab-test-switch__buttons"><button type="button" class="button ab-test-switch__button button--secondary button--lg" data-variant="none" data-v-5c4d7450="">
none
</button><button type="button" class="button ab-test-switch__button button--primary button--lg" data-variant="mobile_banner" data-v-5c4d7450="">
mobile_banner
</button>
</div>
</div>
<div class="ab-test-switch__experiment">
<div class="ab-test-switch__experiment__title">
<div class="v-popover">
<div class="trigger" style="display: inline-block;">
<p>(detail)</p>
</div>
</div>
</div>
<div class="ab-test-switch__buttons"><button type="button" class="button ab-test-switch__button button--primary button--lg" data-v-5c4d7450="">
explore_a
</button><button type="button" class="button ab-test-switch__button button--secondary button--lg">
explore_b
</button>
</div>
</div>
</div>
Run Code Online (Sandbox Code Playgroud)
// this fails as the DOM changes after each click
cy.get('.ab-test-switch__buttons > :nth-child(1)').each(($el) => {
cy.wrap($el).click()
cy.wait(1000) // didn't help, there's no race condition here
})
Run Code Online (Sandbox Code Playgroud)
before(() => {
cy.visit('/company_profile_frontend/ab-test-switch')
// cy.get('.ab-test-switch__buttons > :nth-child(1)').click({ multiple: true, force: true }) (this didn't work either)
const numberOfAbTests = document.getElementsByClassName('ab-test-switch__buttons').length
for (let i = 1; i <= numberOfAbTests; i += 1) {
cy.get(`.ab-test-switch__buttons > :nth-child(${i})`).click().pause()
}
// cy.get('.ab-test-switch__buttons > :nth-child(1)').each(($el) => {
// cy.wrap($el).click().pause()
// // eslint-disable-next-line cypress/no-unnecessary-waiting
// cy.wait(1000)
// }) (another failed attempt)
})
Run Code Online (Sandbox Code Playgroud)
Any other way to make this work?
小智 5
只要numberOfAbTests测试开始时已知,for 循环就可以工作,而不是根据先前的命令计算或异步获取。
it('clicks buttons which become detached', () => {
const numberOfAbTests = 2;
...
for (let i = 1; i <= numberOfAbTests; i += 1) { // nth-child is 1-based not 0-based
cy.get(`.ab-test-switch__buttons > :nth-child(${i})`)
.click()
}
})
Run Code Online (Sandbox Code Playgroud)
相当于
it('clicks all the buttons', () => {
cy.get('.ab-test-switch__buttons > :nth-child(1)').click()
cy.get('.ab-test-switch__buttons > :nth-child(2)').click()
})
Run Code Online (Sandbox Code Playgroud)
因为赛普拉斯运行循环并对按钮单击命令进行排队,然后正如您所说,异步运行。
当numberOfAbTests不是静态已知时,您需要使用递归,如 @RosenMihaylov 所说,但他的实现遗漏了一个关键因素 - 您必须在按钮分离/替换的情况下重新查询按钮。
it('clicks all the buttons', () => {
cy.get('.ab-test-switch__buttons')
.then(buttons => {
const count = buttons.length; // button count not known before the test starts
clickButtonsInSuccession();
function clickButtonsInSuccession(i = 1) {
if (buttonIndex <= count) {
const buttonSelector = `.ab-test-switch__buttons > :nth-child(${i})`;
cy.get(buttonSelector) // re-query required here
.click()
clickButtonsInSuccession(i +1);
}
}
})
})
Run Code Online (Sandbox Code Playgroud)
这假设.ab-test-switch__buttons是按钮的容器,因此 DOM 的结构如下
<div class=".ab-test-switch__buttons">
<button>one</button>
<button>two</button>
</div>
Run Code Online (Sandbox Code Playgroud)
查看扩展后的代码
您需要在加载 DOM 后通过查询来获取测试计数,但是
const numberOfAbTests = document.getElementsByClassName('ab-test-switch__buttons').length;
Run Code Online (Sandbox Code Playgroud)
是同步代码,它在
任何命令(包括 )之前cy.visit()运行,因此返回 0。
考虑分两遍运行的测试,第一遍运行所有同步代码,然后运行命令。
例外情况是回调内的同步代码,例如.then(callbackFn)有效地将 推callbackFn入命令队列以在命令之间按顺序运行。
您可以使用命令来查询numberOfAbTests并将值传递到.then()
cy.get('.ab-test-switch__buttons')
.its('length')
.then(numberOfAbTests => {
for (let i = 1; i <= numberOfAbTests; i += 1) {
...
}
})
Run Code Online (Sandbox Code Playgroud)
或访问并计数before()然后在内部循环it(),
let numberOfAbTests;
before(() => {
// All commands here run before it() starts
cy.visit('../app/ab-test-switch.html').then(() => {
numberOfAbTests = Cypress.$('.ab-test-switch__buttons').length;
})
})
it('tests the button', () => {
for (let i = 1; i <= numberOfAbTests; i += 1) {
...
}
})
Run Code Online (Sandbox Code Playgroud)
或者忘记计算测试并直接使用.each()
cy.get('.ab-test-switch__buttons')
.each($abTest => {
cy.wrap($abTest).find('button')
.each($button => {
cy.wrap($button).click();
})
})
Run Code Online (Sandbox Code Playgroud)
选择器
选择器.ab-test-switch__buttons > :nth-child(${i})有问题,因为索引i引用了 abTest 按钮组,但您尝试使用它来单击单个按钮。
所以使用for循环,
cy.get('.ab-test-switch__buttons')
.each($abTest => {
cy.wrap($abTest).find('button')
.each($button => {
cy.wrap($button).click();
})
})
Run Code Online (Sandbox Code Playgroud)