Clicking list of elements on Cypress using a for loop without using each

Geo*_*nos 4 html dom cypress

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)