David Boydell
David Boydell

Reputation: 1185

Cypress hangs in loop when running custom Chai assertion

I have been trying to create my own custom chai assertion (based on the Cypress recipe template: https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/extending-cypress__chai-assertions/cypress/support/index.js).

What I have found with the code below is that when it is run I end up with a constant loop of WRAP, if I swap this.obj with element it then results in a constant stream of GET. I do not seem to ever progress further than getRect(first).then((actual)

If anyone could help me out I'd be very grateful.

cypress/integration/test.js

describe('testing custom chai', () => {
  it('uses a custom chai helper', () => {
    cy.visit('https://www.bbc.co.uk/news');
    cy.get('#orb-modules > header').should('be.leftAligned', '#orb-header');
  });
});

cypress/support/index.js

function getRect(selector) {
  if (selector === '&document') {
    return cy.document().then(doc => doc.documentElement.getBoundingClientRect());
  } if (typeof selector === 'string') {
    return cy.get(selector).then($elem => $elem[0].getBoundingClientRect());
  }
  return cy.wrap(selector).then(elem => Cypress.$(elem)[0].getBoundingClientRect());
}

function getRects(first, second) {
  return getRect(first).then((actual) => {
    getRect(second).then(expected => [actual, expected]);
  });
}

const aligned = (_chai, utils) => {
  function leftAligned(element) {
    getRects(element,this.obj).then((rects) => {
      this.assert(
        rects[0].left === rects[1].left,
        'expected #{this} to be equal',
        'expected #{this} to not be equal',
        this._obj,
      );
    });
  }
  _chai.Assertion.addMethod('leftAligned', leftAligned);
};

chai.use(aligned);

Upvotes: 2

Views: 1051

Answers (1)

Richard Matsen
Richard Matsen

Reputation: 23463

The basic problem is that the async commands cy.get(), cy.wrap(), cy.document() can't be used in the custom assertion. My best guess is that the auto-retry mechanism is going bananas and giving you the constant loop.

Instead, you can use Cypress.$() which is the synchronous version (essentially jquery exposed on the Cypress object).

The following seems to work ok. (I renamed getRects() param to subject, as sometimes it's a selector and sometimes it's the object passed in to .should()).

Note also this._obj instead of this.obj.

function getRect(subject) {
  if (subject === '&document') {
    return Cypress.$(document).context.documentElement.getBoundingClientRect();
  }
  if (typeof subject === 'string') {  // the selector passed in to assertion
    return Cypress.$(subject)[0].getBoundingClientRect();
  }
  if (typeof subject === 'object') {  // the element from cy.get() i.e this._obj 
    return subject[0].getBoundingClientRect();
  }
  return null;  // something unkown
}

function getRects(first, second) {
  const actual = getRect(first) 
  const expected = getRect(second)
  return [actual, expected];
}

const aligned = (_chai, utils) => {
  function leftAligned(element) {
    const rects = getRects(element, this._obj)
    this.assert(
      rects[0].left === rects[1].left,
      'expected #{this} to be equal',
      'expected #{this} to not be equal',
      this._obj,
    );
  }
  _chai.Assertion.addMethod('leftAligned', leftAligned);
};
chai.use(aligned);

I was unable to test your BBC page directly, as there's a cross-origin problem occurring

Refused to display 'https://www.bbc.com/news' in a frame because it set 'X-Frame-Options' to 'sameorigin'

but it does work with a mockup page

cypress/app/bbc-sim.html

<div id="orb-modules">
  <header>
    <h1>Brexit: Boris Johnson's second attempt to trigger election fails</h1>
  </header>
</div>

and testing like so

it('uses a custom chai helper', () => {
  cy.visit('app/bbc-sim.html')
  cy.get('#orb-modules > header').should('be.leftAligned', '#orb-modules');
});

Upvotes: 5

Related Questions