Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
fr0 opened this issue Apr 4, 2019 · 25 comments
Open

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

fr0 opened this issue Apr 4, 2019 · 25 comments
Labels
existing workaround pkg/driver This is due to an issue in the packages/driver directory type: breaking change Requires a new major release version type: enhancement Requested enhancement of existing feature

Comments

@fr0
Copy link

fr0 commented Apr 4, 2019

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
Copy link
Contributor

flotwig commented Apr 4, 2019

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

@Lakitna
Copy link
Contributor

Lakitna commented Apr 11, 2019

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

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

@cypress-bot cypress-bot bot added the stage: proposal 💡 No work has been done of this issue label Apr 24, 2019
@jennifer-shehane jennifer-shehane added type: enhancement Requested enhancement of existing feature pkg/driver This is due to an issue in the packages/driver directory labels Apr 24, 2019
@jennifer-shehane
Copy link
Member

.text() command proposal: #630

@tomasbjerre
Copy link
Contributor

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
Copy link

x-yuri commented Sep 8, 2019

One way to go about it is:

cy.get('...').should($el => expect($el.text().trim()).to.equal('...'));

@Lakitna
Copy link
Contributor

Lakitna commented Sep 22, 2019

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
Copy link

I also encountered the same issue so I use:

cy.get('.message').should('contain.text', message)

@Mumcio

This comment has been minimized.

@loren138
Copy link

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
Copy link

x-yuri commented Jul 10, 2020

@loren138 Have you seen these posts?
#3887 (comment)
#3887 (comment)

@loren138
Copy link

loren138 commented Jul 10, 2020

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 #3887 (comment) 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
Copy link
Contributor

Lakitna commented Jul 10, 2020

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

@jennifer-shehane jennifer-shehane added existing workaround type: breaking change Requires a new major release version labels Aug 19, 2020
@damsorian
Copy link

any update?

@h4de5

This comment was marked as off-topic.

@marcospoliceno

This comment was marked as off-topic.

@todd-m-kemp
Copy link

todd-m-kemp commented Feb 8, 2022

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
Copy link

yashrvy commented Apr 4, 2022

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

@cypress-bot cypress-bot bot added stage: icebox and removed stage: proposal 💡 No work has been done of this issue labels Apr 28, 2022
@konstantinschuette
Copy link

any update?

@lvl99
Copy link

lvl99 commented Aug 19, 2022

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
Copy link

gutisalex commented Nov 23, 2022

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
Copy link

any updates?

yndajas added a commit to ministryofjustice/hmpps-accredited-programmes-ui that referenced this issue May 10, 2023
This is mostly automatic formatting from djLint (some of which is
*weird*, but consistent and occasionally weird formatting is better than
no formatting)

I've also abstracted a chunky bit of HTML (mostly from an SVG path) into
a partial and tried to make the formatting a little more sensible within
constraints of djLint acceptability

One test is updated. djLint generally prefers using new lines for
the inner content of tags - the automatic formatter won't indent the
lines it moves down, but it accepts manually indented lines. However,
one of these two changes adds a new line character, which then breaks
tests that use `have.text` without whitespace[1]. One way around this is
to use `contain.text`[2], but then you're not asserting that there isn't
any other content. I've opted for trimming then asserting equality
(rather than containment)[3], but this might get messy in future.
Another option is adding a package that does the whitespace trimming
with a simple method[4]/[5], but unless it becomes a recurring theme, I
think it's best to avoid adding an extra dependency

[1]: cypress-io/cypress#3887
[2]: cypress-io/cypress#3887 (comment)
[3]: cypress-io/cypress#3887 (comment)
[4]: cypress-io/cypress#3887 (comment)
[5]: https://github.com/Lakitna/cypress-commands
@gurudattgd04
Copy link

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
Copy link
Contributor

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

Wouldn't this also match "aBlogger" ?

@matdv
Copy link

matdv commented Oct 26, 2023

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
Copy link

kussmaul commented Aug 5, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
existing workaround pkg/driver This is due to an issue in the packages/driver directory type: breaking change Requires a new major release version type: enhancement Requested enhancement of existing feature
Projects
None yet
Development

No branches or pull requests