cy.contains Variants

Separate the existence from the text check

<div class="AlertBox">Could not log in</div>
<div>
  <ul id="names">
    <li>John Smith</li>
    <li>Mary Jane</li>
  </ul>
</div>
// passes because the text matches
cy.contains('.AlertBox', 'Could not log in')
// 🚨 slight mistake: there is no dot
// at the end of the message
cy.contains('.AlertBox', 'Could not log in.')

Hmm, so the failure to find the AlertBox with the expected message is ambiguous:

  • does the alert box not exist?
  • does it exist, but has a different text?

This is why I prefer separating cy.contains(select, text) into cy.get(selector).should('have.text', text) combination

// 🚨 still fails
// but gives an accurate message
// the .AlertBox expected to have text "..."
// but the text was "..."
cy.get('.AlertBox').should('have.text', 'Could not log in.')

Since cy.contains(text) uses partial text, we can use the "include.text" assertion

cy.get('.AlertBox').should('include.text', 'Could not')

We have to be careful though, because the text match could use partial text from several found elements. For example:

cy.get('#names li').should('include.text', 'SmithMary')

But that is NOT what we probably wanted to do: we did not want to concatenate all LI texts into a single long string and do partial text match! So the final alternative is to use cypress-mapopen in new window queries to check strings

// exact string match
cy.get('#names li')
  .map('innerText')
  .should('not.include', 'SmithMary')
  .and('include', 'Mary Jane')

If we want to do partial text match, we could process the array of strings to get a partial text match using a regular expression

const text = 'Mary J'
cy.get('#names li')
  .map('innerText')
  // one of the strings is a partial match
  .invoke('some', (s) => s.includes(text))
  .should('be.true')