【问题标题】:Clicking list of elements on Cypress using a for loop without using each使用 for 循环单击 Cypress 上的元素列表,而不使用每个
【发布时间】:2021-08-01 21:52:54
【问题描述】:

我有一个选项/按钮列表,我需要确保它们都设置为特定值。每个包装器都可以有多个按钮,但第一个总是我想要在运行测试之前设置的。

因此我需要循环这些包装器并定位它们每个的第一个子/按钮。

通常情况下,each() 会出现这种情况,但 Cypress 在第一次单击后会出错 - DOM 会重新渲染,并且 Cypress 找不到剩余的按钮。

因此,我需要一个替代解决方案。其中之一是经典的for 循环。代码如下:

<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>
  // 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
  })
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)
})

还有其他方法可以完成这项工作吗?

【问题讨论】:

    标签: html dom cypress


    【解决方案1】:

    只要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()
      }
    })
    

    等价于

    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()
    })
    

    因为赛普拉斯运行循环并将按钮单击命令排队,然后按您所说的异步运行。


    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);
            }
          }
        })
    })
    

    假设.ab-test-switch__buttons 是按钮的容器,所以 DOM 的结构类似于这样

    <div class=".ab-test-switch__buttons">
      <button>one</button>
      <button>two</button>
    </div>
    

    查看扩展代码

    您需要在加载后通过查询 DOM 来获取测试计数,但是

    const numberOfAbTests = document.getElementsByClassName('ab-test-switch__buttons').length;
    

    是同步代码,它在之前运行 任何命令,包括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) {
          ...
        }
      })
    

    或访问并计数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) {
        ...
      }
    })
    

    或者忘记计算测试并使用.each()

    cy.get('.ab-test-switch__buttons')
      .each($abTest => {
        cy.wrap($abTest).find('button')
          .each($button => {
            cy.wrap($button).click(); 
          })
      })
    

    选择器

    选择器.ab-test-switch__buttons &gt; :nth-child(${i}) 存在问题,因为索引i 指的是abTest 按钮组,但您正试图使用​​它来单击单个按钮。

    所以使用for循环,

    for (let i = 0; i < numberOfAbTests; i += 1) {      // NB :nth() is 0-based
                                                        // in contrast to :nth-child()
                                                        // which is 1-based
    
      cy.get(`.ab-test-switch__buttons:nth(${i}) > button`)
        .eq(0).click()                                       // click button A
    
      cy.get(`.ab-test-switch__buttons:nth(${i}) > button`)
        .eq(1).click()                                       // click button B
    }
    

    【讨论】:

    • 我有点困惑。我尝试了选项#1(编辑并发布了我的整个脚本),但它没有单击按钮。我假设我陷入了选项#2,这确实令人难以置信。没有递归就没有办法做到这一点!?
    • 首先,这是一个开创性的答案,如果可以的话,我会 +2 :)。 each() 不起作用(元素分离/Dom 已更改)。但是获取长度并使用 then() 到那里做 for 循环就像一个魅力。我用 ` for (let i = 0; i .ab-test-switch__buttons:nth(${i}) > button).eq(0).click() } ` ,确实我的选择器错了:(跨度>
    • PS:我希望你正在考虑成为一名活跃的赛普拉斯维护者
    【解决方案2】:

    你的 for 循环方法很好。由于页面在单击后重新呈现 - 将递归作为 cypress 命令的链会更好。例如:

    cy.get(".ab-test-switch__buttons > :nth-child(1)").then(
       (buttons) => {
           clickButtonsInSuccession();
           function clickButtonsInSuccession(buttonsClicked = 0) {
              if (buttonsClicked < buttons.length) {
                 cy.wrap(buttons[buttonsClicked]).click().pause();
                 // eslint-disable-next-line cypress/no-unnecessary-waiting
                 cy.wait(1000);
                 buttonsClicked++;
                 clickButtonsInSuccession(buttonsClicked);
              }
          }
       }
    );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-09
      • 2022-11-05
      相关资源
      最近更新 更多