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, orinput[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().
| Option | Default | Description |
|---|---|---|
matchCase | true | Check case sensitivity |
log | true | Displays the command in the Command log |
timeout | defaultCommandTimeout | Time to wait for .contains() to resolve before timing out |
includeShadowDom | includeShadowDom config option value | Whether 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 alengthgreater than1throws 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:

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 .
<span>Hello 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']buttonalabel
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 ofcyor 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:

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

History​
| Version | Changes |
|---|---|
| 5.2.0 | Added includeShadowDom option. |
| 4.0.0 | Added support for option matchCase. |