How to check for an element that may not exist using Cypress
Asked Answered
E

8

58

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.

Eurus answered 12/12, 2017 at 13:11 Comment(3)
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. 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.Lara
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?Shenitashenk
I was thinking Fody's answer about polling for the element is excellent, much better than any answer here. (It has conditional + retry + early timeout)Abort
T
52

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
    ifElementExists(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()
  }
})
Tropology answered 1/6, 2023 at 1:51 Comment(0)
I
34
    export function clickIfExist(element) {
        cy.get('body').then((body) => {
            if (body.find(element).length > 0) {
                cy.get(element).click();
            }
        });
    }
Irremediable answered 6/1, 2020 at 8:12 Comment(2)
Is there a way to set a timeout for finding the element?Improvisatory
@Improvisatory 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: #5525571. 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();}Sadfaced
E
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')})}
Elemi answered 26/10, 2020 at 8:3 Comment(1)
Cypress.$() does the same thing as doc.querySelectorAll() in fewer lines.Dealfish
R
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");
}
Rail answered 23/5, 2020 at 11:57 Comment(1)
Wouldn't this will throw error if the element is not found?Berglund
L
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 => {
Leff answered 20/3, 2020 at 12:0 Comment(0)
M
0

As all we know cypress use promise to handle the test. So we can use a promiseFail to return if there's no specific element. I use a little modification of @Fody answer here.

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")
  }
});
Meristic answered 24/11, 2023 at 13:3 Comment(1)
Why would you add a promise when it's not needed?Myxomatosis
J
0

I would suggest https://github.com/bahmutov/cypress-if which is also recommended by Cypress.

cy.get('.warning').if().then(() => cy.get('#warn-dialog-submit').click());
Jedda answered 27/3 at 19:55 Comment(0)
K
-2

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);
        });
    })
}
Kashmir answered 15/7, 2023 at 20:7 Comment(6)
What if it does not appear - how to use Promise.reject() in that case?Pronucleus
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'))Kashmir
I clarified the example a bit @PronucleusKashmir
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)...Ashton
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.Ashton
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.Kashmir

© 2022 - 2024 — McMap. All rights reserved.