Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I define a custom assertion operator in Cypress?

In Cypress test, I often need to check if the text in an DOM element is equal some expected test. But since there might be some whitespaces around the text, I can't simply write:

cy.get('.cell')
  .should('have.text', 'Hello')

Instead, I have to write:

cy.get('.cell')
  .then($cell => $cell.text().trim())
  .should('eq', 'Hello')

I want to define a custom assertion operator like have.text.trimmed, allow me to use it like this:

cy.get('.cell')
  .should('have.text.trimmed', 'Hello');

But I can't find any document in official site about it. Would someone share some example?

like image 809
Freewind Avatar asked Apr 25 '19 06:04

Freewind


2 Answers

Finally, I find the way to do it. Although Cypress doesn't provide such a feature, but since Cypress uses Chai, we can just define Chai methods.

Note: it's not possible to define have.text.trimmed, since assertion method text is a Chai method. rather than a chainable method, there is no way to provide a trimmed after it.

But there are still two options:

  1. Define a Chai method textTrimmed. It allows us using .should('have.textTrimmed', 'sometext'), which is preferred since we can define a custom assertion message and no tricky hacking on jQuery instances.

  2. Define a Chai chainable method trimmed. It allows using .should('have.trimmed.text', 'sometext'), which seems to work, but the assertion is determined by the Chai method text, which may be confusion. It is not recommended.

have.textTrimmed

This is in TypeScript:

chai.Assertion.addMethod('textTrimmed', function (expectedString: string) {
  const $element = this._obj;

  new chai.Assertion($element).to.be.exist;

  const actual = $element.text().trim();
  const expected = expectedString.trim();
  this.assert(
    actual === expected
    , ' Expected #{this} to have text #{exp} after trimmed, but the text was #{act} after trimmed'
    , 'expected #{this} not to have text #{exp} after trimmed'
    , expected
    , actual
  );
});

Put the code in cypress/support/index.js file to make sure running it before tests.

You may want to see the complete demo here: https://github.com/freewind-demos/typescript-cypress-add-custom-assertion-method-textTrimmed-demo/blob/master/cypress/support/textTrimmed.ts

File have.trimmed.text

chai.use((chai, utils) => {

  chai.Assertion.addChainableMethod("trimmed", () => {
  }, function () {
    const obj = utils.flag(this, 'object')

    const oldText = obj.text.bind(obj);
    obj.text = () => {
      return oldText().trim()
    }
  });
})

As I said, it's not recommended because of the tricky hack and unclear assertion message.

You can also see the complete demo here: https://github.com/freewind-demos/typescript-cypress-custom-operator-have-trimmed-text-demo/blob/master/cypress/support/index.ts

like image 170
Freewind Avatar answered Sep 27 '22 23:09

Freewind


Currently it is not possible out of the box for Cypress. The feature request is Provide a "Cypress" way to access textContent (and/or innerText) - .text() command (#630).

But you can work around it by adding custom commands to support/commands.js and use those commands in your testscript. You will end up with this in commands.js:

Cypress.Commands.add('haveText', function (text) {
  cy.get('.cell')
  .then($cell => $cell.text().trim())
  .should('eq', text)
})

In the test script you will end up with:

cy.haveText('Hello')

Instead of using the trim() in command.js you could also use contains(), this does a partial match so whitespaces are no problem (note that 'apple pie' also meets the requirements if you look for 'apple', if that is not a problem you can use contains(). Commands.js would look like this:

Cypress.Commands.add('haveText', function (text) {
  cy.get('.cell')
  .should('contains', text)
})

But what probably meets your requirements even more is using contains() in combination with a regular expression. You don't need any scripts in commands.js, but just in the test script you can use this:

cy.contains(/^\s*Hello\s*$/))

The \s* is to match any whitespace character zero or more times. The ^ is to start matching at the beginning of the text The $ is to end matching at the end of the text.

The regular expression can't be used within a should(), sadly enough.

like image 40
Mr. J. Avatar answered Sep 27 '22 22:09

Mr. J.