54

I am writing a Cypress test to log in to a website. There are username and password fields and a Submit button. Mostly logins are straightforward, but sometimes a warning dialog appears first that has to be dismissed.

I tried this:

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

Which works fine, except that the test fails if the warning doesn't appear:

CypressError: Timed out retrying: Expected to find element: '.warning', but never found it.

Then I tried this, which fails because the warning doesn't appear fast enough, so Cypress.$ doesn't find anything:

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

What is the correct way to check for the existence of an element? I need something like cy.get() that doesn't complain if the element can't be found.

Mads Brugger
  • 242
  • 9
Charles Anderson
  • 19,321
  • 13
  • 57
  • 73
  • There are various downsides to attempting to do conditional testing on DOM elements and also various workarounds in Cypress. All of this is explained in depth in the Cypress documentation in [Conditional Testing](https://on.cypress.io/conditional-testing). Since there are multiple ways to do what you are trying to do, I suggest you read through the entire document and decide what is best for your application and your testing needs. – Jennifer Shehane Dec 13 '17 at 14:51
  • This is a _much_ better canonical dupe target for the other one than vice versa. cc @user16695029 Why on earth would you close this as a duplicate of that and not the other way around? – TylerH May 31 '23 at 16:37
  • 4
    I was thinking [Fody's answer](https://stackoverflow.com/a/72960548/16695029) about polling for the element is *excellent*, much better than any answer here. (It has conditional + retry + early timeout) – user16695029 May 31 '23 at 21:14

6 Answers6

35
    export function clickIfExist(element) {
        cy.get('body').then((body) => {
            if (body.find(element).length > 0) {
                cy.get(element).click();
            }
        });
    }
Dev01
  • 13,292
  • 19
  • 70
  • 124
Ran Adler
  • 3,587
  • 30
  • 27
  • 5
    Is there a way to set a timeout for finding the element? – dewey Feb 25 '22 at 12:23
  • 2
    @dewey The `find` method is a JQuery method, not a Cypress one, so it doesn't have any built-in timeouts. Here is a question regarding waiting until an element exists using JQuery or plain Javascript: https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists. I guess you could do something like this: `if (body.find(element).length == 0) {cy.wait(timeout);} if (body.find(element).length > 0) {cy.get(element).click();}` – M. Justin May 20 '22 at 19:17
27

Use element polling and to check without failing the test.

Within a maximum wait time, either the dialog never arrives or this code dismisses it.

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()
  }
})
Fody
  • 23,754
  • 3
  • 20
  • 37
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')})}
M. Justin
  • 14,487
  • 7
  • 91
  • 130
1

The hasClass() or for CSS selector has() is an inbuilt method in jQuery which checks whether the elements with the specified class name exists or not. You can then return a boolean to perform assertion control.

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

Then, it can be made into a special cypress method with TypeScript file (index.d.ts) file and can be in the form of a chainable.

declare namespace Cypress {
    interface Chainable {
        isExistElement(cssSelector: string): Cypress.Chainable<boolean>
    }
}

As in the example below:

shouldSeeCreateTicketTab() {  
  cy.isExistElement(homePageSelector.createTicketTab).should("be.true");
}
ogulcan
  • 11
  • 2
0

I have done it with pure 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;
               }
           });

I'm trying to check if element exists on body

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

With a pure js querySelector

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

Then I fire my cy.get

cy.get('.pager-last a').then(jqWrapper => {
M. Justin
  • 14,487
  • 7
  • 91
  • 130
Patryk Janik
  • 2,476
  • 2
  • 13
  • 23
-1

Note that you might need to wait enough time until the element is added to the DOM, and should do this asynchronously:

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);
        });
    })
}
Lorraine R.
  • 1,545
  • 1
  • 14
  • 39
  • What if it does not appear - how to use Promise.reject() in that case? – Giacomo Jul 15 '23 at 20:38
  • The question was how to detect if elements exists, without failing the test and without exception. So, you should not reject. If the element does not exist, `clickIfElementExists` resolves with a value of false `resolve(false)`, and false is returned to the awaiter, where you can check `if (!await clickIfElementExists('element'))` – Lorraine R. Jul 16 '23 at 05:01
  • I clarified the example a bit @Giacomo – Lorraine R. Jul 16 '23 at 05:06
  • 2
    While that may technically work, you can get rid of the function, the promise, the async iify and just use `cy.wait(5000).then(() => { const exists = Cypress.$(elementSelector)...` – R.Winkles Jul 17 '23 at 23:20
  • 4
    But **don't do it** - Cypress was invented to get rid of hard wait times in tests. You will set 5 seconds, then find it occasionally fails so you bump to 10s, then you have to do the same in the next test and before long you are adding several minutes to the suite run time. – R.Winkles Jul 17 '23 at 23:23
  • My solution is based on other solutions that suggest to loop for a similar amount of time while constantly checking if the element was added to DOM, and then only exit the loop if the element was added, or fail of it wasn't after a timeout. This is broken in the same was as I see it, as long as Cypress inventors don't provide a way to elegantly check if an element exists or not without asserting and failing if its not. – Lorraine R. Jul 19 '23 at 03:24