Skip to main content
Cypress App

contains

Get the DOM element containing the text. DOM elements can contain more than the desired text and still match. Additionally, Cypress prefers some DOM elements over the deepest element found.

Syntax​

.contains(content)
.contains(content, options)
.contains(selector, content)
.contains(selector, content, options)

// ---or---

cy.contains(content)
cy.contains(content, options)
cy.contains(selector, content)
cy.contains(selector, content, options)

Usage​

Correct Usage

cy.contains('.nav a', 'About') // Yield anchor in .nav containing 'About'
cy.contains('Hello') // Yield first el on the page containing 'Hello'

Incorrect Usage

cy.title().contains('My App') // Errors, 'title' does not yield DOM element
cy.getCookies().contains('_key') // Errors, 'getCookies' does not yield DOM element

Arguments​

content (String, Number, RegExp)

Get the DOM element containing the content.

selector (String selector)

Specify a selector to filter DOM elements containing the text. Only elements matching the selector are candidates, so you can yield an element higher in the tree than the deepest one containing the text.

  • Cypress yields the deepest matching element.
  • The element preference order applies only when the selector matches a button, a, label, or input[type='submit'] element.
  • A comma-separated selector, such as 'a, button', searches for every listed selector.

options (Object)

Pass in an options object to change the default behavior of .contains().

OptionDefaultDescription
matchCasetrueCheck case sensitivity
logtrueDisplays the command in the Command log
timeoutdefaultCommandTimeoutTime to wait for .contains() to resolve before timing out
includeShadowDomincludeShadowDom config option valueWhether to traverse shadow DOM boundaries and include elements within the shadow DOM in the yielded results.

Yields ​

  • .contains() yields the new DOM element it found. It yields at most one element, so asserting a length greater than 1 throws an error.
  • .contains() is a query, and it is safe to chain further commands.

Examples​

Content​

Find the first element containing some text​

<ul>
<li>apples</li>
<li>oranges</li>
<li>bananas</li>
</ul>
// yields <li>apples</li>
cy.contains('apples')

Find the input[type='submit'] by value​

Get the form element and search in its descendants for the content "submit the form!"

<div id="main">
<form>
<div>
<label>name</label>
<input name="name" />
</div>
<div>
<label>age</label>
<input name="age" />
</div>
<input type="submit" value="submit the form!" />
</form>
</div>
// yields input[type='submit'] element then clicks it
cy.get('form').contains('submit the form!').click()

Number​

Find the first element containing a number​

Even though the <span> is the deepest element that contains a "4", Cypress automatically yields <button> elements over spans because of its preferred element order.

<button class="btn btn-primary" type="button">
Messages <span class="badge">4</span>
</button>
// yields <button>
cy.contains(4)

Regular Expression​

Find the first element with text matching the regular expression​

<ul>
<li>apples</li>
<li>oranges</li>
<li>bananas</li>
</ul>
// yields <li>bananas</li>
cy.contains(/^b\w+/)

Match the exact text instead of a substring​

A string matches any element whose text includes it, so 'Save' also matches "Save draft". Anchor a regular expression to match the whole text.

<div>
<button>Save draft</button>
<button>Save</button>
</div>
// yields <button>Save draft</button>
cy.contains('button', 'Save')

// yields <button>Save</button>
cy.contains('button', /^Save$/).click()

Cypress collapses whitespace to a single space but does not trim it, so markup with whitespace around the text, such as <button> Save </button>, has the text " Save ". Allow for it in the expression:

cy.contains('button', /^\s*Save\s*$/).click()

Selector​

Specify a selector to return a specific element​

Technically the <ul> and first <li> in the example below both contain "apples".

Normally Cypress would return the first <li> since that is the deepest element that contains "apples".

To override the element that is yielded, pass 'ul' as the selector.

<html>
<body>
<ul>
<li>apples</li>
<li>oranges</li>
<li>bananas</li>
</ul>
</body>
</html>
// yields <ul>...</ul>
cy.contains('ul', 'apples')

Keep the form as the subject​

Here's an example that uses the selector to ensure that the <form> remains the subject for future chaining.

<form>
<div>
<label>name</label>
<input name="name" />
</div>
<button type="submit">Proceed</button>
</form>
cy.get('form') // yields <form>...</form>
.contains('form', 'Proceed') // yields <form>...</form>
.submit() // yields <form>...</form>

Without the explicit selector the subject would change to be the <button>. Using the explicit selector ensures that chained commands will have the <form> as the subject.

Act on the table row containing some text​

Pass 'tr' as the selector to find the row for a record, then chain a second .contains() to find the control inside that row. Both are queries, so Cypress retries the whole chain until the button exists, and .click() ends the chain.

<table>
<tbody>
<tr>
<td>Jane</td>
<td><button>Edit</button></td>
</tr>
<tr>
<td>Jamal</td>
<td><button>Edit</button></td>
</tr>
</tbody>
</table>
// clicks the Edit button in Jane's row
cy.contains('tr', 'Jane').contains('button', 'Edit').click()

Match any of several element types​

A comma-separated selector searches for each one, which helps when the same control renders as a link on one page and a button on another.

<a href="/login">Sign in</a>
// yields the <a>, and would yield a <button> containing "Sign in" too
cy.contains('a, button', 'Sign in').click()

Case Sensitivity​

Here's an example using the matchCase option to ignore case sensitivity.

<div>Capital Sentence</div>
cy.get('div').contains('capital sentence') // fail
cy.get('div').contains('capital sentence', { matchCase: false }) // pass

matchCase: false also applies to a regular expression, which then matches as if it had the i flag. Passing a regular expression with the i flag together with matchCase: true throws an error, since the two conflict.

cy.contains(/capital sentence/, { matchCase: false }) // pass
cy.contains(/capital sentence/i) // pass
cy.contains(/capital sentence/i, { matchCase: true }) // error

Timeout​

Wait longer for content that loads slowly​

The timeout option applies to this .contains() and to the assertions chained after it, without changing defaultCommandTimeout for the rest of the test.

cy.contains('Report ready', { timeout: 15000 }).should('be.visible')

Shadow DOM​

Find content inside a shadow root​

By default, .contains() does not search inside shadow roots. Pass includeShadowDom: true to search them, or set the includeShadowDom configuration option to search them in every query.

<checkout-panel>
#shadow-root
<button>Checkout</button>
</checkout-panel>
cy.contains('button', 'Checkout', { includeShadowDom: true }).click()

To search one shadow root only, chain .contains() off of .shadow():

cy.get('checkout-panel').shadow().contains('button', 'Checkout').click()

Common cy.contains() workflows​

Wait for text to disappear​

Assert should('not.exist') to wait for a loading message, toast, or spinner label to go away. Confirm the message appears first: not.exist passes immediately when the message hasn't rendered yet, so the test would move on before the save even starts.

cy.contains('button', 'Save').click()
cy.contains('Saving').should('be.visible')
cy.contains('Saving').should('not.exist')

When the message can come and go too quickly to catch, assert on the end state instead:

cy.contains('button', 'Save').click()
cy.contains('Saved').should('be.visible')

Confirm the element is visible​

.contains() yields hidden elements, such as one styled with display: none. When the test depends on the user seeing the text, assert on visibility.

cy.contains('Welcome back').should('be.visible')

Act inside a dialog with .within()​

Inside a .within() callback, cy.contains() searches only the .within() subject, so a "Yes, Delete!" button elsewhere on the page never matches.

cy.contains('button', 'Delete User').click()

cy.get('[data-cy="confirm-dialog"]').within(() => {
cy.contains('button', 'Yes, Delete!').click()
})

cy.get('[data-cy="confirm-dialog"]').should('not.exist')

Notes​

Inverting cy.contains()​

There is no built-in negation for cy.contains(), so there is no way to ask it directly for the elements that do not contain a piece of text. Instead, select the full set of elements and remove the matching ones with .not() and the jQuery :contains selector. The match is a case-sensitive substring match.

<ul>
<li>Test: Name</li>
<li>Test: Name (1)</li>
<li>Test: Name (2)</li>
<li>Test: Name (3)</li>
</ul>
// yields only <li>Test: Name</li>, the item without a numeric suffix
cy.get('li').not(':contains("(")')

The same approach works with .filter() and the :not() selector when you would rather keep a positive filter:

// yields the rows that are not disabled
cy.get('tr').filter(':not(.disabled)')

Scopes​

.contains() acts differently whether it's starting a series of commands or being chained off an existing series.

When starting a series of commands​

This queries the <body> element for the content, so elements in the <head>, such as <title>, never match. Inside a .within() callback, it queries the .within() subject instead.

cy.contains('Log In')

When chained to an existing series of commands​

This queries inside of the #checkout-container element.

cy.get('#checkout-container').contains('Buy Now')

When no descendant of the subject contains the content, .contains() checks the subject itself, and yields it if it matches. When the subject holds several elements, .contains() searches all of them and yields the first match.

Be wary of chaining multiple contains​

Let's imagine a scenario where you click a button to delete a user and a dialog appears asking you to confirm this deletion.

// This doesn't work as intended
cy.contains('Delete User').click().contains('Yes, Delete!').click()

Because the second .contains() is chained off of a command that yielded the <button>, Cypress will look inside of the <button> for the new content.

In other words, Cypress looks inside of the <button> containing "Delete User" for the content "Yes, Delete!", which is not what you intended.

What you want to do is call cy again, which automatically creates a new chain scoped to the <body>.

cy.contains('Delete User').click()
cy.contains('Yes, Delete!').click()

Leading, trailing, duplicate whitespaces aren't ignored in <pre> tag​

Unlike other tags, <pre> doesn't ignore leading, trailing, or duplicate whitespaces as shown below:

<!--Code for test-->
<h2>Other tags</h2>
<p>Hello, World !</p>

<h2>Pre tag</h2>
<pre> Hello, World !</pre>

Rendered result:

The result of pre tag

To reflect this behavior, Cypress collapses each run of whitespace in an element's text to a single space before matching, except in a <pre> element, where it matches the text as written. The content you pass is not collapsed.

// test result for above code

cy.get('p').contains('Hello, World !') // pass
cy.get('p').contains(' Hello, World !') // fail

cy.get('pre').contains('Hello, World !') // fail
cy.get('pre').contains(' Hello, World !') // pass

Non-breaking space​

You can use a space character in cy.contains() to match text in the HTML that uses a non-breaking space entity &nbsp;.

<span>Hello&nbsp;world</span>
// finds the span element
cy.contains('Hello world')

Tip: read about assertions against a text with non-breaking space entities in How do I get an element's text contents?

Single Element​

Only the first matched element will be returned​

<ul id="header">
<li>Welcome, Jane Lane</li>
</ul>
<div id="main">
<span>These users have 10 connections with Jane Lane</span>
<ul>
<li>Jamal</li>
<li>Patricia</li>
</ul>
</div>

The below example will return the <li> in the #header since that is the first element that contains the text "Jane Lane".

// yields #header li
cy.contains('Jane Lane')

If you wanted to select the <span> instead, you could narrow the elements yielded before the .contains().

// yields <span>
cy.get('#main').contains('Jane Lane')

Default <input type="submit"> labels​

When the value attribute is omitted from an <input type="submit">, the default label is used and can be locale-dependent. See omitting the value attribute on MDN.

cy.contains() matches an <input type="submit"> by its value attribute.

If possible, set the value attribute:

<input type="submit" value="Submit" />

Otherwise, select the input another way and assert on the empty string:

cy.get('input[type="submit"]').should('have.value', '')

Preferences​

Element preference order​

.contains() defaults to preferring elements higher in the tree when they are:

  • input[type='submit']
  • button
  • a
  • label

When you pass a selector argument to .contains(), only elements matching that selector are candidates, so this preference order applies only if the selector matches one of these elements.

Favor of <button> over other deeper elements​

Even though the <span> is the deepest element that contains "Search", Cypress yields <button> elements over spans.

<form>
<button>
<i class="fa fa-search"></i>
<span>Search</span>
</button>
</form>
// yields <button>
cy.contains('Search').children('i').should('have.class', 'fa-search')

Favor of <a> over other deeper elements​

Even though the <span> is the deepest element that contains "Sign Out", Cypress yields anchor elements over spans.

<nav>
<a href="/users">
<span>Users</span>
</a>
<a href="/signout">
<span>Sign Out</span>
</a>
</nav>
// yields <a>
cy.get('nav').contains('Sign Out').should('have.attr', 'href', '/signout')

Favor of <label> over other deeper elements​

Even though the <span> is the deepest element that contains "Age", Cypress yields <label> elements over <span>.

<form>
<label>
<span>Name:</span>
<input name="name" />
</label>
<label>
<span>Age:</span>
<input name="age" />
</label>
</form>
// yields label
cy.contains('Age').find('input').type('29')

Rules​

Requirements ​

  • .contains() can be chained off of cy or off a command that yields DOM element(s).

Assertions ​

  • .contains() will automatically retry until the element(s) exist in the DOM.
  • .contains() will automatically retry until all chained assertions have passed.

Timeouts ​

  • .contains() can time out waiting for the element(s) to exist in the DOM.
  • .contains() can time out waiting for assertions you've added to pass.

Command Log​

Element contains text "New User"

cy.get('h1').contains('New User')

The commands above will display in the Command Log as:

Command Log contains

When clicking on the contains command within the command log, the console outputs the following:

console.log contains

History​

VersionChanges
5.2.0Added includeShadowDom option.
4.0.0Added support for option matchCase.

See also​