cypress-io/cypress-react-unit-test

cypress-react-selector cy.react should retry

bahmutov opened this issue · 8 comments

When using https://github.com/abhinaba-ghosh/cypress-react-selector noticed cy.react command does not retry finding the element.

Workaround - use cy.get or cy.contains to first confirm the component is present by finding the DOM, the fetch the component using cy.react

Test in cypress/component/advanced/react-book-example/src/components/ProductsList.spec.js

it('retries until component is found', () => {
  // or the command times out
  const products = [
    { id: 1, name: 'First item' },
    { id: 2, name: 'Second item' },
  ]
  const response = {
    json: cy.stub().resolves({
      products,
    }),
  }

  cy.stub(window, 'fetch')
    .withArgs('http://myapi.com/products')
    // simulate slow load by delaying the response
    .resolves(Cypress.Promise.resolve(response).delay(1000))
  mount(<ProductsList />)

  // use https://github.com/abhinaba-ghosh/cypress-react-selector
  // to find DOM elements by React component constructor name, props, or state
  cy.waitForReact(1000, '#cypress-root')
  // cy.react should requery the elements until
  // the assertions that follow it are satisfied,
  // or, if there are no assertions, that an element is found
  cy.react('AProduct').should('have.length', 2)
})

Screenshot - notice that 2 components appear after 1 second, but by then the test has already failed

Screen Shot 2020-08-11 at 11 17 55 PM

To recreate the problem:

  • clone the repo
  • install dependencies with npm i
  • start Cypress npx cypress open
  • open test cypress/component/advanced/react-book-example/src/components/ProductsList.spec.js
  • uncomment the test above

Hi @bahmutov , need some help to understand the cypress promises better. I am following the cypress-xpath retry logic. But, it is throwing error like:

export const react = (subject, component, props, state) => {
  cy.log(`Finding ${getIdentifierLogs(component, props, state)}`);

  const getNodes = () => {
    let elements;
    return cy.window({ log: false }).then((window) => {
     // some node calculations
      return nodes;
    });
  };

  const resolveValue = () => {
    return Cypress.Promise.try(getNodes).then((value) => {
      return cy.verifyUpcomingAssertions(value, options, {
        onRetry: resolveValue,
      });
    });
  };

  return resolveValue().then((value) => {
    return value;
  });
};

Screenshot 2020-08-12 at 10 07 52 PM

Got the solution:

 const resultPromise = resolveValue().then((value) => {
    return value;
  });

  return cy.wrap(resultPromise);

super

@bahmutov , but basically it is not retrying getNodes() at all. It is waiting for the timeout, but not retrying the window function. Can you please suggest?

cy.wrap is not a solution I think. So, I. removed it. Here is the code and respective error now. Handling promises is getting tricky here.

export const react = (subject, component, props, state, options = {}) => {
  if (subject === null) {
    throw new Error(`Previous component found null.`);
  }

  const getNodes = () => {
    return cy.window({ log: false }).then((window) => {
      let elements;
      if (!window.resq) {
        throw new Error(
          '[cypress-react-selector] not loaded yet. did you forget to run cy.waitForReact()?'
        );
      }

      if (subject) {
        elements = window.resq.resq$$(component, subject[0]);
      } else {
        elements = window.resq.resq$$(component);
      }

      if (props) {
        elements = elements.byProps(props);
      }
      if (state) {
        elements = elements.byState(state);
      }
      if (!elements.length) {
        console.log(
          `Component not found ${getIdentifierLogs(component, props, state)}`
        );
        return null;
      }
      let nodes = [];
      elements.forEach((elm) => {
        var node = elm.node,
          isFragment = elm.isFragment;
        if (isFragment) {
          nodes = nodes.concat(node);
        } else {
          nodes.push(node);
        }
      });
      return nodes;
    });
  };

  const resolveValue = () => {
    return Cypress.Promise.try(getNodes).then((value) => {
      return cy.verifyUpcomingAssertions(value, options, {
        onRetry: resolveValue,
      });
    });
  };

  return resolveValue().then((value) => {
    return value;
  });

};

Error output:

Screenshot 2020-08-14 at 3 07 43 AM

I think you should move cy.window and .then out of the retry - because you do want to get the window object ones, and then only retry the .resq.resq$$ part again and again

Thanks, it works!

  • Changes are available in V2.0.1
  • Breaking changes introduced.

Raised PR: #386

Screenshot 2020-08-16 at 3 58 04 AM

works