如何使用Cypress检查可能不存在的元素

55

我正在编写一个Cypress测试,用于登录网站。有用户名密码字段以及一个提交按钮。大多数登录都很简单,但有时首先会出现一个必须被关闭的警告对话框。

我尝试了这个:

cy.get('#login-username').type('username');
cy.get('#login-password').type(`password{enter}`);

// Check for a possible warning dialog and dismiss it
if (cy.get('.warning')) {
  cy.get('#warn-dialog-submit').click();
}

在没有警告出现的情况下,这段代码可以正常运行:

CypressError: 超时重试:期望找到元素:'.warning',但未找到。

随后我尝试了以下代码,但由于警告未能及时出现,所以Cypress.$找不到任何内容:

cy.get('#login-username').type('username');
cy.get('#login-password').type(`password{enter}`);

// Check for a possible warning dialog and dismiss it
if (Cypress.$('.warning').length > 0) {
  cy.get('#warn-dialog-submit').click();
}

如何正确检查元素是否存在?我需要像cy.get()那样的东西,如果找不到元素则不会抱怨。


在Cypress中尝试对DOM元素进行条件测试存在各种缺点,并且有各种解决方法。所有这些都在Cypress文档的条件测试中详细说明。由于有多种方法可以完成您要尝试的操作,建议您阅读整个文档,决定哪种方法最适合您的应用程序和测试需求。 - Jennifer Shehane
这篇文章比另一篇更适合作为规范的重复目标,而不是反过来。@user16695029 你为什么会把这篇文章关闭成那篇文章的副本,而不是反过来呢? - TylerH
4
我认为Fody的回答关于轮询元素的方法是非常好的,比这里的任何答案都要好。(它具有条件+重试+早期超时) - user16695029
不确定应该遵循什么协议,我应该将那个答案复制到这里吗? - user16695029
7个回答

39
使用元素轮询来检查而不会导致测试失败。
在最长等待时间内,要么对话框永远不会出现,要么此代码将其关闭。
cy.get('#login-username').type('username');
cy.get('#login-password').type(`password{enter}`);

const ifElementExists = (selector, attempt = 0) => {
  if (attempt === 100) return null           // no appearance, return null
  if (Cypress.$(selector).length === 0) {
    cy.wait(100, {log:false})                // wait in small chunks
    getDialog(selector, ++attempt)           // try again
  }
  return cy.get(selector, {log:false})       // done, exit with the element
}

ifElementExists('.warning').then($el => {
  if ($el?.length) {
    $el.find('#warn-dialog-submit').click()
  }
})

35
    export function clickIfExist(element) {
        cy.get('body').then((body) => {
            if (body.find(element).length > 0) {
                cy.get(element).click();
            }
        });
    }

6
有没有一种方法可以为查找元素设置超时时间? - dewey
2
@dewey find 方法是 JQuery 的方法,而不是 Cypress 的方法,因此它没有任何内置的超时。这里有一个关于使用 JQuery 或纯 Javascript 等待元素存在的问题:https://dev59.com/xW035IYBdhLWcg3wQtun。我猜你可以像这样做:`if (body.find(element).length == 0) {cy.wait(timeout);} if (body.find(element).length > 0) {cy.get(element).click();}` - M. Justin

4
export const getEl = name => cy.get(`[data-cy="${name}"]`)

export const checkIfElementPresent = (visibleEl, text) => {
   cy.document().then((doc) => {
     if(doc.querySelectorAll(`[data-cy=${visibleEl}]`).length){
      getEl(visibleEl).should('have.text', text)

      return ;
   }
getEl(visibleEl).should('not.exist')})}

3
Cypress.$() 用更少的代码行数实现了与 doc.querySelectorAll() 相同的功能。 - Sitzfleisch

1

hasClass() 或者对于 CSS 选择器 has() 是 jQuery 内置方法之一,用于检查是否存在指定类名的元素。您可以返回一个布尔值以执行断言控制。

Cypress.Commands.add('isExistElement', selector => {
  cy.get('body').then(($el) => {
    if ($el.has(selector)) {
      return true
    } else {
      return false
    }
  })
});

然后,它可以被制成一个带有TypeScript文件(index.d.ts)的特殊柏树方法,并且可以以可链接的形式出现。
declare namespace Cypress {
    interface Chainable {
        isExistElement(cssSelector: string): Cypress.Chainable<boolean>
    }
}

就像下面的例子一样:

shouldSeeCreateTicketTab() {  
  cy.isExistElement(homePageSelector.createTicketTab).should("be.true");
}

如果找不到元素,这会抛出错误吗? - Anand

0

我已经用纯JS完成了它。

cy.get('body').then((jqBodyWrapper) => {

               if (jqBodyWrapper[0].querySelector('.pager-last a')) {
                   cy.get('.pager-last a').then(jqWrapper => {
                       // hardcoded due to similarities found on page

                       const splitLink = jqWrapper[0].href.split("2C");
                       AMOUNT_OF_PAGES_TO_BE_RETRIEVED = Number(splitLink[splitLink.length - 1]) + 1;
                   })
               } else {
                   AMOUNT_OF_PAGES_TO_BE_RETRIEVED = 1;
               }
           });

我正在尝试检查元素是否存在于页面主体中

cy.get('body').then((jqBodyWrapper) => {

使用纯JS的querySelector

if (jqBodyWrapper[0].querySelector('.pager-last a')) {

然后我执行cy.get

cy.get('.pager-last a').then(jqWrapper => {

0
众所周知,Cypress使用Promise来处理测试。因此,如果没有特定的元素,我们可以使用promiseFail来返回。我在这里对@Fody的答案进行了一点修改。
const ifElementExists = (selector, attempt = 0) => {
  const promiseFail = new Promise((resolve) => {
    resolve([])
  })
  if (attempt === 10) return promiseFail;
  if (Cypress.$(selector).length === 0) {
    cy.wait(100, { log: false });
    return ifElementExists(selector, ++attempt);
  }
  return cy.get(selector, { log: false }); // done, exit with the element
};

ifElementExists(".todo-list").then($el => {
  if ($el?.length) {
    cy.get(".todo-list li").should("have.length", 2);
  } else {
    cy.log("NOT FOUND 404")
  }
});

为什么在不需要的情况下要添加一个承诺呢? - undefined

-1
请注意,您可能需要足够的时间等待元素添加到DOM中,并且应该以异步方式执行此操作。
describe(`App content suite`, () => {
    it('app logs in correctly', () => {         
        console.log('Checking if warning needs to be closed');

        const enoughTimeUntilWarningIsAddedToDom = 5000;
        cy.wait(enoughTimeUntilWarningIsAddedToDom);

        (async () => {
            const elementExists = await clickIfElementExists('.warning');

            if (elementExists) {
                console.log('element exists');
            } else {
                console.log('element does not exist');
            }
        })();
    }
}

function clickIfElementExists(elementSelector: string) {
    return new Promise(resolve => {
        cy.get('body').then((body) => {
            resolve(body.find(elementSelector).length > 0);
        });
    })
}

1
如果它没有出现,那么在这种情况下如何使用Promise.reject()? - Giacomo
问题是如何检测元素是否存在,而不会导致测试失败或异常。因此,你不应该拒绝。如果元素不存在,clickIfElementExists将以false的值解析resolve(false),并将false返回给等待者,在那里你可以检查if (!await clickIfElementExists('element')) - Lorraine R.
我稍微澄清了一个例子@Giacomo。 - Lorraine R.
3
虽然从技术上讲可能行得通,但你可以摒弃该函数、承诺和异步 IIFE,并只使用 cy.wait(5000).then(() => { const exists = Cypress.$(elementSelector)... - R.Winkles
4
但是不要这样做 - Cypress 的发明就是为了消除测试中的长时间等待。你设置了5秒,然后发现它偶尔失败,所以你将它增加到10秒,接着在下一个测试中也得做同样的事情,不久之后你会为套件的运行时间增加几分钟。 - R.Winkles
我的解决方案基于其他建议循环一段时间并不断检查元素是否已添加到DOM的解决方案,只有在元素被添加后或超时后未添加时才退出循环。就我看来,这种方法与我所见的一样存在问题,因为只要Cypress的发明者没有提供一种优雅地检查元素是否存在的方式而不进行断言和失败,它就是有缺陷的。 - Lorraine R.

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