Cypress.io:contains()不等待元素

18
我们正在使用Cypress编写UI测试,通常使用起来非常简单。但我一遍又一遍地遇到一个烦人的等待问题。
场景非常简单。用户点击搜索按钮,然后选择带有特定文本的元素。以下是代码:
cy.get('#search-button').click();
cy.contains('Test item 1').click();
cy.get('#cheapest-offer-button').click();

第三次点击事件失败了,因为cy.contains('Test item 1')已经不再等待页面和元素被渲染。从测试步骤中我看到,它只是在页面中央单击,实际上什么也没做。所以所有随后的步骤当然会失败。

但是如果我在这些调用之间添加一个wait(),就像这样:

cy.get('#search-button').click();
cy.wait(2000);
cy.contains('Test item 1').click();
cy.get('#cheapest-offer-button').click();    
页面被正确渲染,测试项目1出现,被点击并且所有随后的步骤都成功了。
根据最佳实践wait()调用不应该是必需的,因此应该避免使用。我在这里做错了什么?

2
有时候很难想象正在发生什么事情,你能展示一下DOM吗?(或者描述一下元素以及你知道的在点击后发生的事情)。通常情况下,你可以用 cy.contains(selector, newContent) 替换 cy.wait(),其中 newContent 是一个文本片段,表示获取已完成。 - Richard Matsen
cy.contains('Test item 1') 尽管它还没有可见,但正在查找某些内容。如果cypress无法找到该元素,则会抛出异常并使测试失败。请查看左侧的命令输出以查看该调用返回了什么。您可能需要更改定位该元素的方式以使其更具体,或者添加 .should("be.visible") 可能会使其正常工作。发布命令日志和HTML的屏幕截图将有助于我们更轻松地解决问题。 - Brendan
我相信它也可以像 cy.get(selector).contains('期望文本') 这样使用。 - ecolell
5个回答

9

总结

contains增加更长的超时时间:

cy.get('#search-button').click();
cy.contains('Test item 1', { timeout: 4000 }).click();
cy.get('#cheapest-offer-button').click();  

解释

和许多Cypress命令一样,contains带有一个接受选项对象的第二个参数。你可以通过 timeout 键来传递命令应等待的毫秒数,例如:

.contains('Stuff', { timeout: 5000 }) // Timeout after 5 secs

这样一来,该命令的行为就好像在它之前添加了一个 wait,但如果命令成功,它不会像 wait 那样等待所有时间。

Cypress 官方文档关于超时的说明解释了这种技术:它是如何工作的,应该如何完成以及它如何影响链接的断言。

如果你无法点击该项的原因是可见性问题,则可以尝试 .click({ force: true }),不过这应该是最后的选择,因为它可能隐藏实际的错误。


3

看起来这是一个常见的问题https://github.com/cypress-io/cypress/issues/695

解决方法是强制cypress等待所有异步操作,就像基于Selenium webdriver的框架一样。它比cy.wait()更快 执行以下方法:

function waitForBrowser() { 
   cy.window().then(win => {
      return new Cypress.Promise(resolve => win['requestIdleCallback'](resolve));
   });
}

只需要按照以下方式使用:

cy.get('#search-button').click();
waitForBrowser();
cy.contains('Test item 1').click();
cy.get('#cheapest-offer-button').click();

如果你使用Angular,最好使用waitForAngular而不是waitForBrowser。
function waitForAngular() {
  return cy.window().then(win => {
      return new Cypress.Promise((resolve, reject) => {
        let testabilities = win['getAllAngularTestabilities']();
        if (!testabilities) {
            return reject(new Error('No testabilities. Check Angular API'));
        }
        let count = testabilities.length;
        testabilities.forEach(testability => testability.whenStable(() => {
            count--;
            if (count !== 0) return;
            resolve();
        }));
      });
  });
}

我确实尝试过使用这种方法,但是没有得到好的结果。很抱歉!! - PraNuta
可行 :) 只是很遗憾我们必须手动执行这个... 对我来说,在 Cypress 3.8.2 中它运行得很好,现在在 4.4.0 中我必须等待 Angular。 - Dormouse

1

除了以上发布的内容之外,另一个选择是使用类似于

cy.contains('Test item 1').should('exist').click()

或者cy.contains('Test item 1').should('be.visible').click()。

我不确定为什么这样会起作用,但它确实起作用,它不需要设置特定的超时时间。

另外,没有人建议使用其他方法。

cy.server()
cy.route()

并且

cy.wait('@routeAlias')

但这需要比原帖中提供的更多的信息。

0

您可能已经知道Cypress命令是异步的。因此,如果某个命令依赖于另一个操作,则最好将其编写为其依赖操作的子程序。

cy.get('#search-button').click().then(() => {
    cy.contains('Test item 1').should('be.visible').click();
    cy.get('#cheapest-offer-button').should('be.visible').click();
});

0

当我尝试获取具有特定值的 span 时,我遇到了相同的问题。我通过提供更具体的路径来访问该 span,而不是获取页面中的所有 span 元素,解决了这个问题。

cy.get('span').contains('Round ID') //not working
cy.get('.details span').contains('Round ID') //worked

因此,当获取您想要定位的元素类型时,可以尝试更具体一些。另一个选择是使用wait()函数...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接