cypresscypress-wait-until

Cypress - Reload page until an element is located


I try to use cypress-wait-until for a simple case. https://github.com/NoriSte/cypress-wait-until

  1. Visit a page
  2. Check if an element is here
  3. If not, reload the page until this element is found.
  4. Once found, assert the element exists

Working code (cypress-wait-until not used)

before(() => {
    cy.visit('http://localhost:8080/en/registration');
});

describe('Foo', () => {
  it('should check that registration button is displayed', () => {
    const selector = 'button[data-test=startRegistration-individual-button]';
    cy.get(selector).should('exist');
  });
});

Not working, timed out retrying

before(() => {
    cy.visit('http://localhost:8080/en/registration');
});

describe('Foo', () => {
  it('should check that registration button is displayed', () => {
    const options = { timeout: 8000, interval: 4000 };
    const selector = 'button[data-test=startRegistration-individual-button]';
    cy.waitUntil(() => cy.reload().then(() => Cypress.$(selector).length), options);
    cy.get(selector).should('exist');
  });
});

Not working, see error below

before(() => {
    cy.visit('http://localhost:8080/en/registration');
});

describe('Foo', () => {
  it('should check that registration button is displayed', () => {
    const options = { timeout: 8000, interval: 4000 };
    const selector = 'button[data-test=startRegistration-individual-button]';
    cy.waitUntil(() => {
        cy.reload();
        return Cypress.$(selector).length;
      }, options);

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

For the two versions not working as soon as I remove cy.reload(), it starts to work.

Question

What can I do to make it work with a reload?

EDIT

This command I wrote works correctly.

Cypress.Commands.add('refreshUntil', (selector: string, opts?: { retries: number; waitAfterRefresh: number }) => {
  const defaultOptions = {
    retries: 10,
    waitAfterRefresh: 2500,
  };

  const options = { ...defaultOptions, ...opts };

  function check(selector: string): any {
    if (Cypress.$(selector).length) { // Element is there
      return true;
    }
    if (options.retries === 0) {
      throw Error(`${selector} not found`);
    }
    options.retries -= 1;
    cy.log(`Element ${selector} not found. Remaining attempts: ${options.retries}`);
    cy.reload();
    // Wait a some time for the server to respond
    return cy.wait(options.waitAfterRefresh).then(() => check(selector));
  }

  check(parsedSelector);
});

I could see two potential difference with waitUntil from cypress-wait-until

  1. Cypress.$(selector).length would be new on each try
  2. There is a wait time after the reload before checking again if the element is there

EDIT 2

Here is the working solution using cypress-wait-until

cy.waitUntil(() => cy.reload().wait(2500).then(() => Cypress.$(selector).length), options);

Solution

  • I ended up writing my own method (inspired from cypress-wait-until ) without the need to have a hard wait time

    /**
     * Run a check, and then refresh wait until an element is displayed.
     * Retries for a specified amount of time.
     *
     * @function
     * @param {function} firstCheckFunction - The function to run before checking if the element is displayed.
     * @param {string|{ selector: string, type: string }} selector - The selector to search for. Can be a string or an object with selector and type properties.
     * @param {WaitUntilOptions} [opts={timeout: 5000, interval: 500}] - The options object, with timeout and interval properties.
     * @throws {Error} if the firstWaitFunction parameter is not a function.
     * @throws {Error} if the specified element is not found after all retries.
     * @example
     * cy.refreshUntilDisplayed('#element-id', () => {...});
     * cy.refreshUntilDisplayed({ selector: 'element-id', type: 'div' }, () => {...});
     */
    Cypress.Commands.add('waitFirstRefreshUntilDisplayed', (firstCheckFunction, selector: string | { selector: string, type: string }, opts = {}) => {
      if (!(firstCheckFunction instanceof Function)) {
        throw new Error(`\`firstCheckFunction\` parameter should be a function. Found: ${firstCheckFunction}`);
      }
    
      let parsedSelector = '';
    
      // Define the default options for the underlying `cy.wait` command
      const defaultOptions = {
        timeout: 5000,
        interval: 500,
      };
    
      const options = { ...defaultOptions, ...opts };
    
      // Calculate the number of retries to wait for the element to be displayed
      let retries = Math.floor(options.timeout / options.interval);
      const totalRetries = retries;
    
      if (typeof selector === 'string') {
        parsedSelector = selector;
      }
    
      if (typeof selector !== 'string' && selector.selector && selector.type) {
        parsedSelector = `${selector.type}[data-test=${selector.selector}]`;
      }
    
      // Define the check function that will be called recursively until the element is displayed
      function check(selector: string): boolean {
        if (Cypress.$(selector).length) { // Element exists
          return true;
        }
        if (retries < 1) {
          throw Error(`${selector} not found`);
        }
    
        if (totalRetries !== retries) { // we don't reload first time
          cy.log(`Element ${parsedSelector} not found. ${retries} left`);
          cy.reload();
        }
    
        // Waits for the firstCheckFunction to return true,
        // then pause for the time define in options.interval
        // and call recursively the check function
        cy.waitUntil(firstCheckFunction, options).then(() => { // We first for firstCheckFunction to be true
          cy.wait(options.interval).then(() => { // Then we loop until the selector is displayed
            retries -= 1;
            return check(selector);
          });
        });
        return false;
      }
    
      check(parsedSelector);
    });