Cypress - Reload page until an element is located

1k Views Asked by At

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);
3

There are 3 best solutions below

0
Léo Coco On BEST ANSWER

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);
});
0
Léo Coco On

Here is the working solution using cypress-wait-until

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

Cypress rules apply inside cy.waitUntil() as well as outside so .wait(2500) would be bad practice.

It would be better to change your non-retying Cypress.$(selector).length into a proper Cypress retrying command. That way you get 4 seconds (default) retry but only wait as long as needed.

Particulary since cy.waitUntil() repeats n times, you are hard-waiting (wasting) a lot of seconds.

cy.waitUntil(() => { cy.reload(); cy.get(selector) }, options)  
// NB retry period for `cy.get()` is > 2.5 seconds