Cypress test for element existence conditionally with retry
Asked Answered
Z

3

7

I read the caveats in the docs on Cypress conditional testing, but nevertheless need to apply it to a particular test for certain reasons.

I have a function to do it, but there are certain selectors that do not work due to lack of retry in this function.

How can I implement retry in conditional testing and avoid flaky tests?

Is it even possible, or does one thing cancel out the other?

export function elementExists(selector: string): boolean {
  try {
    return Cypress.$(selector).length > 0;
  } catch (error) {
    return false;
}
Zinc answered 23/7, 2022 at 2:47 Comment(2)
You can look into this thread for your query - #49980811Subtractive
That's not what the question is about.Zinc
A
8

The "standard" way to test existence of an element is pretty simple, but it does not return true/false. It fails the test if element is not found.

cy.get(selector).should('exist')

Internally the .should() retries the element until command timeout is finished - then fails the test.

If you make your function recursive, you can do the same but instead of failing, return true/false.

function elementExists(selector, attempt = 0) {

  const interval = 100;  // 100ms between tries
  if (attempt * interval > Cypress.config('defaultCommandTimeout')) {
    cy.log(selector, 'not found')
    return cy.wrap(false, {log:false})      
  }

  return cy.get('body', {log:false}).then(($body) => {
    const element = $body.find(selector)
    if (element.length) {
      cy.log(selector, 'found')
      return cy.wrap(true, {log:false}) 
    } else {
      cy.wait(interval, {log:false})  
      return elementExists(selector, ++attempt)
    }
  })
}

elementExists(selector).then(exists => {
  if (exists) {
    ...
  }
})
Amagasaki answered 23/7, 2022 at 3:38 Comment(0)
S
5

It's even easier now with the cypress-if package.

But retry is implemented asynchronously, so you will have to return a Chainable.

export function elementExists(selector: string): Chainable<boolean> {
  return cy.get(selector)
    .if('exist')
    .then(true)
    .else()
    .then(false)
}

elementExists('span#123').then((result: boolean) => 
  if (result) {
    ...
  }
})

The above uses the full API and is very readable, but this should also work for you

export function elementExists(selector: string): Chainable<JQuery<HTMLElement>|undefined> {
  return cy.get(selector).if()
}

elementExists('span#123').then((result: JQuery<HTMLElement>|undefined) => 
  if(result.length) {
    ...
  }
})
Smithereens answered 23/10, 2022 at 19:28 Comment(0)
P
0

Based on @Fody answer, I have created a custom cy command that checks for an element given 3 parameters , selector , number of retries and the retry interval time. leaving it here in case someone needs something similar.


Cypress.Commands.add('waitForElementToExist', (selector, numRetries, retryInterval) => {
  const interval = retryInterval || 100; // Default interval of 100ms between retries
  
  const elementExists = (selector, attempt) => {
    cy.log('Attempt', attempt, 'of', numRetries, 'to find', selector);
    if (attempt >= numRetries) {
      cy.log(selector, 'not found');
      return cy.wrap(false, { log: false });
    }

    return cy.get('body').then(($body) => {
      const element = $body.find(selector);
      if (element.length) {
        cy.log(selector, 'found');
        return cy.wrap(true, { log: false });
      } else {
        cy.wait(interval);
        elementExists(selector, attempt + 1);
      }
    });
  };

  return elementExists(selector, 0);
});
Pellerin answered 8/11, 2023 at 4:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.