cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
46.81k stars 3.17k forks source link

should('have.text', ...) should ignore leading and trailing whitespace #3887

Open fr0 opened 5 years ago

fr0 commented 5 years ago

If your HTML happens to have whitespace in the tag, e.g., <div> foo </div>, that text will not impact how the user sees the page, unless it is a <pre> element. I don't think that the 'have.text' assertion should fail just because it found ' foo ' when it was expecting 'foo'.

If you're not willing to change this (since it is technically a breaking change), maybe there should be a have.text.trimmed?

Current behavior:

should('have.text', ...) fails if the HTML contains whitespace, even if that whitespace doesn't impact the rendered content.

Desired behavior:

should('have.text', ...) should ignore leading and trailing whitespace for elements that won't render it. Elements like pre (and elements with certain white-space css values) should still consider leading and trailing whitespace.

Versions

3.2.0

flotwig commented 5 years ago

There's a related feature request here for making cy.contains() strip newlines: #92

Lakitna commented 5 years ago

Recently I added a .text() command to https://github.com/Lakitna/cypress-commands .

.text() will trim whitespace and provides some whitespace options.

jennifer-shehane commented 5 years ago

.text() command proposal: https://github.com/cypress-io/cypress/issues/630

tomasbjerre commented 5 years ago

It is nice to have the text() trim the string but is it possible to chain assertions with text()?

What I did was to add a custom command like:

Cypress.Commands.add(
    'shouldHaveTrimmedText',
    {
        prevSubject: true,
    },
    (subject, equalTo) => {
        if (isNaN(equalTo)) {
            expect(subject.text()).to.eq(equalTo);
        } else {
            expect(parseInt(subject.text())).to.eq(equalTo);
        }
        return subject;
    },
);

And now I can do:

cy
 .get('.some-class')
 .shouldHaveTrimmedText('whatever');
x-yuri commented 5 years ago

One way to go about it is:

cy.get('...').should($el => expect($el.text().trim()).to.equal('...'));
Lakitna commented 5 years ago

It is nice to have the text() trim the string but is it possible to chain assertions with text()?

Yes, .text() as it exists in cypress-commands awaits all its upcoming assertions via the same API as the default Cypress commands. This way it has the same behaviour as commands you're familiar with like .get().

cy.get('...')
  .text()
  .should('equal', 'foo');
paurakhsharma commented 4 years ago

I also encountered the same issue so I use:

cy.get('.message').should('contain.text', message)
Mumcio commented 4 years ago

the same issue as above - cypres 4.x

loren138 commented 4 years ago

The problem with contains is it matches any substring.

Given: <div> Tag1, Tag2 </div>

cy.get('div').should('have.text', 'Tag1, Tag2'); should pass (trims whitespace) cy.get('div').should('have.text', 'Tag1'); should fail because of the extra text

Given that the cypress matchers are Chai, I'm not sure if we'd have to extend Chai or cypress to implement this.

This seems to work:

cy.get("div").invoke("text").then((text) => text.trim()).should("equal", "Tag1");

I'm not sure how to turn that into a custom command though.

x-yuri commented 4 years ago

@loren138 Have you seen these posts? https://github.com/cypress-io/cypress/issues/3887#issuecomment-482277997 https://github.com/cypress-io/cypress/issues/3887#issuecomment-533880136

loren138 commented 4 years ago

We try to keep the number of external packages as low as possible in our project so we can add our own custom commands but are not likely to use third party command packages.

This seems to work as a slight modification on https://github.com/cypress-io/cypress/issues/3887#issuecomment-522962482 but I don't think it's as flexible since it doesn't have the jQuery helper, but I can't figure out how to use invoke in the custom command.

Cypress.Commands.add(
    "shouldHaveTrimmedText",
    { prevSubject: true },
    (subject, equalTo) => {
        expect(subject.text().trim()).to.eq(equalTo);
        return subject;
    },
);
Lakitna commented 4 years ago

It shouldn't be difficult to copy .text() from cypress-commands. Why reinvent the wheel?

damsorian commented 3 years ago

any update?

h4de5 commented 3 years ago

please fix this!

marcospoliceno commented 2 years ago

Please FIX

todd-m-kemp commented 2 years ago

I've been using this as a workaround for the last while and it seems to be working well.

I took bits and pieces from comments above and overwrote .should and .and so that when the chainer have.text is used you compare the element's trimmed inner text instead. For all other chainers, the commands behave like the normally do.

Just put this into your commands.js file and adjust your have.text assertions as needed to remove the extra whitespace.

function overwriteShould(originalFn, subject, chainer, method, value) {
  if (chainer === 'have.text') {
    return originalFn(subject.prop('innerText').trim(), 'equal', method, value)
      .then(() => subject);
  }
  const args = Cypress._.reject(arguments, { name: 'originalFn' });
  return originalFn(...args);
}

Cypress.Commands.overwrite('should', overwriteShould);

Cypress.Commands.overwrite('and', overwriteShould);
yashrvy commented 2 years ago

The problem with contains is it matches any substring.

Given: <div> Tag1, Tag2 </div>

cy.get('div').should('have.text', 'Tag1, Tag2'); should pass (trims whitespace) cy.get('div').should('have.text', 'Tag1'); should fail because of the extra text

Given that the cypress matchers are Chai, I'm not sure if we'd have to extend Chai or cypress to implement this.

This seems to work:

cy.get("div").invoke("text").then((text) => text.trim()).should("equal", "Tag1");

I'm not sure how to turn that into a custom command though.

this worked for me

konstantinschuette commented 2 years ago

any update?

lvl99 commented 2 years ago

I've been using this as a workaround for the last while and it seems to be working well.

I took bits and pieces from comments above and overwrote .should and .and so that when the chainer have.text is used you compare the element's trimmed inner text instead. For all other chainers, the commands behave like the normally do.

Just put this into your commands.js file and adjust your have.text assertions as needed to remove the extra whitespace.

function overwriteShould(originalFn, subject, chainer, method, value) {
  if (chainer === 'have.text') {
    return originalFn(subject.prop('innerText').trim(), 'equal', method, value)
      .then(() => subject);
  }
  const args = Cypress._.reject(arguments, { name: 'originalFn' });
  return originalFn(...args);
}

Cypress.Commands.overwrite('should', overwriteShould);

Cypress.Commands.overwrite('and', overwriteShould);

@todd-m-kemp I used your method (thanks for that!), but I found that subject.prop("innerText") kept returning text in CAPITALS, so I opted for using the jQuery.text() method: subject.text().trim().

gutisalex commented 1 year ago

I've been using this as a workaround for the last while and it seems to be working well.

I took bits and pieces from comments above and overwrote .should and .and so that when the chainer have.text is used you compare the element's trimmed inner text instead. For all other chainers, the commands behave like the normally do.

Just put this into your commands.js file and adjust your have.text assertions as needed to remove the extra whitespace.

function overwriteShould(originalFn, subject, chainer, method, value) {
  if (chainer === 'have.text') {
    return originalFn(subject.prop('innerText').trim(), 'equal', method, value)
      .then(() => subject);
  }
  const args = Cypress._.reject(arguments, { name: 'originalFn' });
  return originalFn(...args);
}

Cypress.Commands.overwrite('should', overwriteShould);

Cypress.Commands.overwrite('and', overwriteShould);

this works for me but has some typescript issues which I ignore with @ts-ignore. Anyone know how to add the proper types to this?

And when will there be an official solution to this? I encountered that issue testing the headline of a site!

Having this in one line in my IDE

<h1 data-cy="headline">Welcome to mySite</h1>

and test it with the following cypress command

it('headline should display `Welcome to mySite`', () => {
    cy.mount(index)
    cy.get('[data-cy=headline]').should('have.text', 'Welcome to mySite')
  })

its all working so far...

But my editor tends to break that into 3 loc like with eslint and autosave enabled:

<h1 data-cy="headline">
  Welcome to mySite
</h1>

The test will than fail with the following message:

expected '<h1>' to have text 'Welcome to mySite', but the text was '\n Welcome to mySite\n  '
tragsdale7 commented 1 year ago

any updates?

gurudattgd04 commented 1 year ago

May be this will help someone, instead of overwriting should or creating new query, I tested below and its working

cy.visit("https://ecommerce-playground.lambdatest.io/"); cy.getSomeElement("#widget-navbar-217834 ul.horizontal") .find("li:contains(' Blog')") .invoke("text") .should("to.include", "Blog");

using to.include will ignore the spaces and compares the containing text

rrauenza commented 1 year ago

.should("to.include", "Blog"); using to.include will ignore the spaces and compares the containing text

Wouldn't this also match "aBlogger" ?

matdv commented 11 months ago

I ran into a problem where cypress is not waiting for the element to update when using this command: cy.wrap(subject).invoke("text").then((text) => text.trim()).should("equal", text)

So it is better to use the function parameter overload of should, so that cypress waits for the element to have the expected text. cy.wrap(subject).should(expect(element.text().trim()).to.equal(text))

Full command:

Cypress.Commands.add(
  'shouldHaveTrimmedText',
  {prevSubject: true},
  (subject: JQuery<HTMLElement>, text: string) => {
    cy.wrap(subject)
      .should(element => expect(element.text().trim()).to.equal(text));
  }
);
kussmaul commented 2 months ago

Another approach - I didn't want to override functions in all cases, so I defined a trimMatch function that I can use in other functions as needed.

const trimMatch = (exp : string) => (act : any) => expect(act.text().trim()).to.equal(exp);
// check disabled button 
const checkButtonDisabled = (id : string, label : string, icon : string) => {
  return cy.get(`button#${id}`).should('exist').and('be.visible').and('be.disabled').within(() => {
    cy.get('span'     ).should(trimMatch(label));
    cy.get('mat-icon' ).should('have.text', icon );
  });
}