Querying

Examples of querying for DOM elements in Cypress, for a full reference of commands, go to docs.cypress.ioopen in new window and read Selecting Elements: Best Practices Guideopen in new window. All Cypress querying commands automatically retry until the elements are found, see the retry-ability examples. Cypress supports both CSS and jQuery selectors.

// checks the page until it finds an element with class "title"
cy.get('.title')
// all querying commands have a built-in existence assertion
// the above command is equivalent to:
cy.get('.title').should('exist')

cy.get()open in new window

To query for the button, use the cy.get() command.

<div id="querying-example">
  <div class="well">
    <button id="query-btn" class="query-btn btn btn-primary">
      Button
    </button>
  </div>
</div>
// selects the button using ID
cy.get('#query-btn').should('contain', 'Button')
// selects the button using class
cy.get('.query-btn').should('contain', 'Button')

// Use CSS selectors just like jQuery
cy.get('#querying-example .well>button:first').should(
  'contain',
  'Button',
)

Number of items

You can attach an assertion to confirm the number of elements.

<section>
  <h4>Example</h4>
  <h5>Querying elements</h5>
  <p>This page has heading elements</p>
  <h6>cy.get</h6>
</section>
// find all H4 + H5 + H6 elements
// and confirm the minimum number of elements
// using the "greater" assertion
cy.get('h4,h5,h6').should('have.length.gt', 1)

Tip: use the .should('have.length', N) assertion to confirm the exact number of elements. For example, see Assertions page.

jQuery selectors

Cypress querying commands use jQuery selectorsopen in new window that go beyond the standard CSS selectors. Here are a couple of examples.

:checkbox selector

The jQuery :checkbox selectoropen in new window is equivalent to [type=checkbox] attribute CSS selector. The jQuery docs advise to use at least the element type like input:checkbox to avoid the default *:checkbox wildcard.

<div id="checkbox-example">
  <input type="checkbox" id="typeA" checked />
</div>
cy.get('#checkbox-example input:checkbox')
  .should('be.checked')
  .and('have.id', 'typeA')
  .then(($checkbox) => {
    // the jQuery :checkbox selector returns the same elements
    // as the [type=checkbox] attribute selector
    cy.get('#checkbox-example input[type=checkbox]').then(
      ($el) => {
        // let's confirm it
        expect($checkbox[0], 'same DOM element').to.equal($el[0])
      },
    )
  })

Disabled elements

<div id="some-buttons">
  <button id="one">One</button>
  <button id="two" disabled>Two</button>
  <button id="three" disabled>Three</button>
</div>
// get all disabled buttons
cy.get('#some-buttons button:disabled').should('have.length', 2)
// use combination of jQuery :not and :disabled selectors
cy.get('#some-buttons button:not(:disabled)')
  .should('have.length', 1)
  .and('have.text', 'One')

Checked elements

<div id="checks">
  <div>One <input type="checkbox" id="one" /></div>
  <div>Two <input type="checkbox" id="two" checked /></div>
  <div>Three <input type="checkbox" id="three" /></div>
</div>
// get all checked boxes
cy.get('#checks input:checked')
  .should('have.length', 1)
  .and('have.id', 'two')
// get all unchecked boxes through jQuery :not and :checked selectors
cy.get('#checks input:not(:checked)').should('have.length', 2)

:has selector

We can find elements that contain some other element using the :has selector. Let's find all paragraphs with bold text inside.

<div id="main-bold">
  <p>First paragraph</p>
  <p>Second <b>paragraph</b></p>
  <p>Third paragraph</p>
  <p>Fourth <b>paragraph</b></p>
</div>
cy.get('#main-bold p:has(b)').should('have.length', 2)

For another use of :has selector, see the recipes Find and Click The Accordion With A Button and List item with text tag.

:has with :not selector

We can also combine :has with :not selectors to find all paragraphs without <b> elements.

<div id="main-bold">
  <p>First paragraph</p>
  <p>Second <b>paragraph</b></p>
  <p>Third paragraph</p>
  <p>Fourth <b>paragraph</b></p>
</div>
cy.get('#main-bold p:not(:has(b))')
  .should('have.length', 2)
  .each(($p) => {
    // should be the first or the first paragraphs
    expect($p.text()).to.match(/^(First|Third)/)
  })

Find text with :contains selector

cy.get uses jQuery selectorsopen in new window, thus you can immediately use them to find elements by text (or without given text). Use :contains(text) to find multiple elements with the given text string and use :not(:contains(text)) to get elements without the text.

<table id="text-example">
  <tbody>
    <tr>
      <td>Same</td>
      <td>Same</td>
      <td>Different</td>
      <td>Same</td>
    </tr>
  </tbody>
</table>
cy.get('table#text-example').within(() => {
  // selects all table cells with text "Same"
  cy.get('td:contains("Same")').should('have.length', 3)
  // if the text does not have white spaces, no need to quote it
  cy.get('td:contains(Same)').should('have.length', 3)
  // you can find elements NOT having the given text
  cy.get('td:not(:contains(Same))')
    .should('have.length', 1)
    .and('have.text', 'Different')
})

jQuery :contains vs cy.contains

Cypress command cy.contains returns the first element with the matching text or regular expression. The command cy.get(':contains("text...")') returns multiple elements with the text.

<div id="uploads">
  <button id="up1">Select File</button>
  <button id="up2">Select File</button>
  <button id="up3">Select File</button>
  <button id="up4">Select File</button>
</div>

With cy.contains we get the first matching button

cy.contains('#uploads button', 'Select File')
  .should('have.length', 1)
  .and('have.id', 'up1')

With cy.get + jQuery :contains(text) we can get all the buttons.

cy.get('#uploads button:contains("Select File")')
  .should('have.length', 4)
  .last()
  .should('have.id', 'up4')

When you yield a list of elements, it is simple to get a particular one by its zero-based index with cy.eqopen in new window command.

cy.get('#uploads button:contains("Select File")')
  .should('have.length', 4)
  .eq(1)
  // finds the 2nd button
  .should('have.id', 'up2')

Note: cy.contains supports regular expressions, while jQuery :contains(text) does not.

Combine :has and :contains selectors

📺 Watch this example in the video A Cypress Example With Disabled Button And Has Text jQuery Selectorsopen in new window.

<div>
  <label>My button</label>
  <button disabled>Click</button>
</div>

Let's get the button by finding the DIV element that has a LABEL element inside with the text "My button".

// the DIV has LABEL with the text "My button"
// and then get the child "BUTTON" element
cy.get('div:has( label:contains("My button") ) button')
  // and confirm we found the right button element
  .should('have.text', 'Click')
  .and('be.disabled')

Multiple :has clauses

Let's pretend we want to find all DIV elements having a LABEL and BUTTON elements inside.

📺 Watch this example explained in the video Multiple :has Selector Clausesopen in new window.

<div>Does not have children</div>
<div>Has a label <label>Champ</label></div>
<div>
  <label>My button</label>
  <button disabled>Click</button>
</div>
<div>
  <label>My other button</label>
  <button disabled>Press</button>
</div>
<div>
  <button>Finish</button>
</div>
// find all DIV elements with a LABEL element inside
cy.get('div:has(label)').should('have.length', 3)
// find all DIV elements with a LABEL and a BUTTON inside
cy.get('div:has(label):has(button)').should('have.length', 2)
// find all DIV elements with a LABEL or a BUTTON inside
cy.get('div:has(label, button)')
  .should('have.length', 4)
  // confirm the last element in the found list
  .last()
  .contains('button', 'Finish')

:first and :last

<div id="labels">
  <div class="label">34</div>
  <div class="label">loading...</div>
</div>

Let's check if the first label has the text "34"

cy.contains('#labels .label', '34')

Equivalent cy.get command can use the :first jQuery selector

cy.get('#labels .label:first').should('have.text', '34')

You can get the second or other elements by index (zero-based) using the jQuery :eq selector. For example, to confirm part of the second element's text:

cy.get('#labels .label:eq(1)').should('include.text', 'load')

:last

<div id="student-names">
  <div class="label">Joe</div>
  <div class="label">Anna</div>
</div>

We can check the last element

cy.get('#student-names .label:last').should('have.text', 'Anna')

Wildcard selector

You can grab all elements using the wildcard * selector

<ul id="the-names">
  <li class="label">Joe</li>
  <li class="label">Anna</li>
</ul>

Let's confirm all HTML elements by checking their nodeName properties

cy.get('*')
  .should('have.length', 3)
  // cy.map comes from the cypress-map plugin
  // https://github.com/bahmutov/cypress-map
  .map('nodeName')
  .should('deep.equal', ['UL', 'LI', 'LI'])

Escaping special characters

If the element's selector has special characters like . or : escape the using \\ character

<div id="user:1234" class="admin.user">Joe</div>
cy.get('#user\\:1234').should('have.text', 'Joe')
cy.get('.admin\\.user')
  // no need to escape the non-selector text
  .should('have.id', 'user:1234')

See also the recipe Escape Selector.

Find elements without a given class

In the HTML below all links have the class "help", but some links have the class "external". We want to find all links having the class "help", but without the class "external".

<a href="article1.html" class="help external">Article 1</a>
<a href="article2.html" class="help">Article 2</a>
<a href="article3.html" class="help external">Article 3</a>
<a href="index.html" class="help">index</a>
cy.get('a.help:not(.external)')
  .should('have.length', 2)
  // confirm the two found elements
  .then(($el) => Cypress._.map($el, 'innerText'))
  .should('deep.equal', ['Article 2', 'index'])

Find elements without an attribute

Let's find all anchor links without HREF attribute

Some of the anchor links below are missing an href attribute. Let's find them

<div id="links">
  <a href="article1.html">Article 1</a>
  <a href="article2.html">Article 2</a>
  <a>Article 3</a>
  <a>Article 4</a>
  <a href="index.html">index</a>
</div>
// get all "A" elements
// without the attribute "href"
cy.get('#links a:not([href])').should('have.length', 2)

Find elements with an attribute having a different value

Let's find all anchor links without HREF attribute

Let's find all anchor links that have the attribute data-level other than primary or do not have this attribute

<div id="help-links">
  <a href="article1.html" data-level="primary">Article 1</a>
  <a href="article2.html" data-level="secondary">Article 2</a>
  <a data-level="primary">Article 3</a>
  <a>Article 4</a>
  <a href="index.html" data-level="index">index</a>
</div>
// get all "A" elements where the attribute "data-level"
// either does not exist or has a value other than "primary"
cy.get('#help-links a:not([data-level=primary])').should(
  'have.length',
  3,
)

Find elements without two given classes

Imagine in a calendar date picker we have previous month, this month, and the next month. We want to select the current month's first day. For simplicity, I will only include three elements.

<div class="datepicker">
  <!-- 5th of the last month -->
  <div class="date lastMonth">5</div>
  <div class="date" data-note="current">5</div>
  <div class="date">15</div>
  <div class="date">25</div>
  <div class="date nextMonth">5</div>
</div>
<style>
  .lastMonth:after {
    content: ' last month';
  }
  .nextMonth:after {
    content: ' next month';
  }
</style>

We only want to select the "5" day of the current month, and do not include the other days with the digit "5" like "15" and "25".

cy.get('.date:not(.lastMonth, .nextMonth)')
  // filter by text to find exactly "5"
  .contains(/^5$/)
  // confirm the got the correct element
  .should('have.attr', 'data-note', 'current')

Using attribute selector

You can grab all elements that have an attribute present. For example, to find all <LI> rows with the attribute "line" present:

<ul id="row-attributes">
  <li>No line</li>
  <li>No line</li>
  <!-- this attribute "line" has no value at all -->
  <li line>line</li>
  <li line="up">line</li>
  <li line="down">line</li>
  <li>No line</li>
</ul>
cy.get('#row-attributes li[line]').should('have.length', 3)

You can grab elements with a given attribute. For example, let's make sure there is only a single <a> element pointing at "index.html":

<div id="specific-href">
  <a href="article1.html">Article 1</a>
  <a href="article2.html">Article 2</a>
  <a href="article3.html">Article 3</a>
  <a href="index.html">index</a>
</div>
cy.get('#specific-href a[href="index.html"]')
  .should('have.length', 1)
  .and('have.text', 'index')

If you want to combine multiple attributes, see the recipe # Get By Attributes.

Attribute from a variable

<div id="attribute-value">
  <div data-product-id="123-04-5678">Chair</div>
</div>
const productId = '123-04-5678'
cy.get('#attribute-value')
  // use the "productId" variable value
  // to form the full selector string
  // via JavaScript template string
  .find(`[data-product-id="${productId}"]`)
  .should('have.text', 'Chair')

Alternative: concatenate the variable into a string using JavaScript + operator

cy.get(
  '#attribute-value [data-product-id="' + productId + '"]',
).should('have.text', 'Chair')

Get input elements with the given value

See the recipe Get input elements with the given value.

Escape the attribute

Sometimes an attribute can have a special character like . or : in it. Please escape the attribute using the \\ character.

<div id="escape-attribute" attr.aria-label="Attribute example">
  Example
</div>
cy.get('[attr\\.aria-label="Attribute example"]')
  .should('have.id', 'escape-attribute')
  // ignore the newline characters by using the assertion "include.text"
  // rather than the assertion "have.text"
  .and('include.text', 'Example')

Attribute prefix

Let's get the element with ID starting with "local-example" prefix. The id is just an attribute, so we can use id^=value "id starts with the value" CSS selector:

<ul>
  <li id="local-example-123">first</li>
  <li id="remote-example-456">second</li>
</ul>
cy.get('[id^=local-example]').should('have.text', 'first')

Attribute suffix

Let's get the element with ID ending with "example-AF9" string. Again, we treat id field as a plain attribute and use the CSS selector id$=value "id ends with the value".

<ul>
  <li id="this-example-ZFX">first</li>
  <li id="that-example-AF9">second</li>
</ul>
cy.get('[id$=example-AF9]').should('have.text', 'second')

Attribute contains text

Let's find an anchor element with HREF attribute that includes the text "help"

<a href="/some/link.html">Link 1</a>
<a href="/another/link">Link 2</a>
<a href="/link/to/help/article.html">Link 3</a>
// quotes around the text without spaces are optional
cy.get('a[href*="help"]')
  .should('have.length', 1)
  .and('have.text', 'Link 3')

Having attribute disabled

Let's find all buttons with the attribute "disabled" present. While we are at it, let's find all the elements without such attribute.

<div id="few-buttons">
  <button>First</button>
  <button disabled>Second</button>
  <button disabled>Third</button>
  <button>Fourth</button>
</div>
// finds both button that have the attribute "disabled"
cy.get('#few-buttons button[disabled]')
  .should('have.length', 2)
  .first()
  .should('have.text', 'Second')
// finds the two buttons without the attribute "disabled"
cy.get('#few-buttons button:not([disabled])')
  .should('have.length', 2)
  .last()
  .should('have.text', 'Fourth')

Combining attribute selectors

Let's get the element with ID that starts with "my-" prefix and ending with "-yours" suffix

<ul id="combine-attributes">
  <li id="my-first-123">first</li>
  <li id="my-second-yours">second</li>
</ul>
cy.get('#combine-attributes').within(() => {
  cy.get('[id^=my-][id$=-yours]').should('have.text', 'second')
})

Using data attribute

To find elements by data attribute, query using the attribute selector.

<div data-test-id="test-example" class="example">
  Div with <code>data-test-id</code>
</div>
cy.get('[data-test-id="test-example"]').should(
  'have.class',
  'example',
)

cy.get() yields a jQuery object, you can get its attribute by invoking the .attr() method.

// find the element, confirm its attribute
cy.get('[data-test-id="test-example"]')
  .invoke('attr', 'data-test-id')
  .should('equal', 'test-example')

// or you can get an element's CSS property
cy.get('[data-test-id="test-example"]')
  .invoke('css', 'position')
  .should('equal', 'static')

Alternatively, chain assertions directly to the cy.get() call. See assertions documentationopen in new window.

cy.get('[data-test-id="test-example"]')
  .should('have.attr', 'data-test-id', 'test-example')
  .and('have.css', 'position', 'static')

Using partial data attribute

<div id="partial-data">
  <div data-test-id="fruit one">Apples</div>
  <div data-test-id="fruit two">Oranges</div>
  <div data-test-id="fruit one">Grapes</div>
  <div data-test-id="not a fruit">Potato</div>
</div>

Let's find all items with "data-test-id" that includes the text "fruit" anywhere in the string.

cy.get('#partial-data [data-test-id*=fruit]').should(
  'have.length',
  4,
)

Let's find all items that start with "fruit" in that attribute.

cy.get('#partial-data [data-test-id^=fruit]').should(
  'have.length',
  3,
)

Let's find the items that end the data attribute with the string "a fruit"

cy.get('#partial-data [data-test-id$="a fruit"]')
  .should('have.length', 1)
  .and('have.text', 'Potato')

Let's find the elements with text "one" anywhere in their text

cy.get('#partial-data [data-test-id*=one]')
  .should('have.length', 2)
  .first()
  .should('have.text', 'Apples')

AND selector

Let's find all P and LI elements. You can combine multiple selectors using comma operator.

<div id="and-selector-example">
  <ul>
    <li id="first">first</li>
    <li id="second">second</li>
  </ul>
  <p>Another line</p>
</div>
// find all P elements inside the element with id "and-selector-example"
// and all LI elements inside the element with id "and-selector-example"
cy.get(
  '#and-selector-example p, #and-selector-example li',
).should('have.length', 3)
// alternative: first find the element with id "and-selector-example"
// then find P and LI elements
cy.get('#and-selector-example').within(() => {
  cy.get('p, li').should('have.length', 3)
})

Table column

You can get the table column using CSS selectors

<style>
  table td {
    border: 3px solid black;
    padding: 3px 5px;
  }
  #sort-by-date {
    margin: 10px 0px;
  }
</style>
<table id="people">
  <thead>
    <tr>
      <td>Name</td>
      <td>Age</td>
      <td>Date (YYYY-MM-DD)</td>
    </tr>
  </thead>
  <tbody id="people-data">
    <tr>
      <td>Dave</td>
      <td>20</td>
      <td>2023-12-23</td>
    </tr>
    <tr>
      <td>Cary</td>
      <td>30</td>
      <td>2024-01-24</td>
    </tr>
    <tr>
      <td>Joe</td>
      <td>28</td>
      <td>2022-02-25</td>
    </tr>
    <tr>
      <td>Anna</td>
      <td>22</td>
      <td>2027-03-26</td>
    </tr>
  </tbody>
</table>
// let's get the first column
cy.get('table#people tbody td:nth-child(1)').should(($cells) => {
  expect($cells[0]).to.have.text('Dave')
  expect($cells[1]).to.have.text('Cary')
  expect($cells[2]).to.have.text('Joe')
  expect($cells[3]).to.have.text('Anna')
})
// let's get the second column
cy.get('table#people tbody td:nth-child(2)').should(($cells) => {
  expect($cells[0]).to.have.text('20')
  expect($cells[1]).to.have.text('30')
  expect($cells[2]).to.have.text('28')
  expect($cells[3]).to.have.text('22')
})

You can even map each element to a number before confirming the entire array.

// get the second column of cells
cy.get('table#people tbody td:nth-child(2)').should(($cells) => {
  const values = Cypress._.map($cells, 'innerText').map(Number)
  expect(values).to.deep.equal([20, 30, 28, 22])
})

Tip: you can extract multiple values and create assertions using cypress-should-reallyopen in new window or cypress-mapopen in new window helpers.

With the given computed style

See the recipe Computed style.

Index of the found element

Imagine we want to find the list item with the class "active", and then see what is the index of the element among its siblings.

You can watch this example in the video Get The Index Of An Element Using jQuery Methodopen in new window.

<ol id="carousel">
  <li>apples</li>
  <li>grapes</li>
  <li class="active">kiwi</li>
</ol>
li.active {
  font-weight: bold;
}
cy.get('#carousel li.active')
  // call jQuery index() method
  // to yield the index of the active LI item
  .invoke('index')
  .should('equal', 2)
  // if you want to work with the index,
  // yield it to the next command
  .then((index) => {
    cy.log(`index is **${index}**`)
  })

Let's find the LI element by text and confirm its index:

cy.contains('#carousel li', 'kiwi')
  .should('have.class', 'active')
  .invoke('index')
  .should('equal', 2)

Case-insensitive attribute selectors

See the Case-insensitive query recipe

cy.contains()open in new window

We can find elements by their content using cy.contains()

<div id="querying">
  <ul class="query-list">
    <li class="first">apples</li>
    <li class="second">oranges</li>
    <li class="third">bananas</li>
    <li class="fourth">more apples</li>
  </ul>
  <div class="query-button">
    <button class="btn btn-default">
      <span>Save Form</span>
    </button>
  </div>
</div>
// finds the first element with the given text
cy.get('.query-list')
  .contains('apples')
  .should('have.class', 'first')
// ignore text when matching
cy.get('.query-list')
  .contains('APPLE', { matchCase: false })
  .should('have.class', 'first')
  .and('have.text', 'apples')

cy.get('.query-list')
  .contains('bananas')
  .should('have.class', 'third')

// we can pass a regexp to `.contains()`
cy.get('.query-list')
  .contains(/^b\w+/)
  .should('have.class', 'third')

// passing a selector to contains will
// yield the selector containing the text
cy.get('div#querying')
  .contains('ul', 'oranges')
  .should('have.class', 'query-list')

cy.get('.query-button')
  .contains('Save Form')
  .should('have.class', 'btn')

See also: Contains text in a list, Debug cy.get and cy.contains commands, cy.contains and regular expressions.

cy.contains with selector and text

You can give the element selector to match. The text can be anywhere in the element or its children.

<div id="contains-example">
  <div
    data-cy="parent"
    style="font-weight: heavy; text-decoration: underline"
  >
    <span>Some text</span>
  </div>
</div>
cy.get('#contains-example').within(() => {
  // finds the immediate element
  cy.contains('Some text').should(
    'have.prop',
    'nodeName',
    'SPAN',
  )
  // find the parent element with "Some text" somewhere inside
  cy.contains('[data-cy=parent]', 'Some text')
    .should('have.prop', 'nodeName', 'DIV') // we found the parent div
    .and('have.css', 'text-decoration')
    // the text-decoration style string includes color and line type
    // we are only interested in the presence of the "underline" keyword
    .should('include', 'underline')
})

cy.contains with regular expression

You can use a regular expression instead of text

<div id="user-name">Cypress User</div>
// ignore case
cy.contains(/cypress user/i)
// match text exactly
cy.contains(/^Cypress User$/)

Even if there are optional white space characters around the text, you can still use ^ and $ to require no other text in the element.

Note the whitespace around the word "Incredible"

<div class="nickname">  Incredible    </div>
// find the nickname "Incredible" that can have whitespace around it
// but cannot have any other characters
cy.contains('.nickname', /^\s*Incredible\s*$/)

cy.contains with regular expression OR

Let's confirm that the title text is one of the three possible titles

<div class="my-title">Cypress Examples Guide</div>
<style>
  .my-title {
    font-size: x-large;
  }
</style>
// we do not know the precise expected title
// we know it can be one of three possible titles
cy.contains(
  '.my-title',
  /^(Testing Examples|Cypress Examples Guide|Short Feature Tests)$/,
)

Perhaps a simpler test extracts the text from the element by invoking the jQuery text method and checks if it equals one of the allowed strings. With Cypress v12 queriesopen in new window, the entire chain cy.get().invoke().should() is retried.

// you can also get the text and confirm it
cy.get('.my-title')
  .invoke('text')
  .should('be.oneOf', [
    'Testing Examples',
    'Cypress Examples Guide',
    'Short Feature Tests',
  ])

We can also use the .should('match', regular expression) to check the text

// you can also get the text and confirm it
cy.get('.my-title')
  .invoke('text')
  .should('match', /example/i)

Tip: be careful with "OR" assertions, as they might relax the tests too much. For example, when adding a new item, the text might be "New item added". When editing an existing test the text might be "Item updated". It is tempting to match one of two variants:

.should('be.oneOf', ['New item added', 'Item updated'])

What if the application code accidentally shows "Item updated" when adding a new item? The test will still pass just fine. This is why I would argue that the test should know exactly what to expect. For example, the test code might have a variable "updated" to control which test it is:

function checkText(updated) {
  if (updated) {
    cy.contains('.my-title', 'Item updated')
  } else {
    cy.contains('.my-title', 'New item added')
  }
}

The test is much stricter and simpler to read.

cy.contains with regular expression AND

Let's find a paragraph with words "hello", "world", and "more" in it.

<section id="paragraphs">
  <p class="first">Some text here</p>
  <p class="second">
    Simon says "hello" and then he says "world" and that's it.
    Nothing more.
  </p>
  <p class="third">I need more examples.</p>
</section>
// look for a paragraph with the world "hello", "world", and "more"
// separated by some other characters.
cy.contains('#paragraphs p', /hello.+world.+more/)
  // confirm we have found the right paragraph
  .should('have.class', 'second')

cy.contains with duplicate white spaces

If the HTML element contains duplicate white spaces, using cy.contains becomes trickier. The example below has a double space between the : and b characters.

<div id="spaces">LEGO:  blocks</div>

If you inspect this element in the browser's console, you will see that the browser returns different strings for innerHTML and innerText properties - and the browser collapses multiple spaces into one.

> $0.innerText.length
> 12
> $0.innerHTML.length
> 13

If you are using the literal string for matching, the cy.contains will fail, since it uses the innerText property.

// FAILS, cannot find the element while having "  " double space
// cy.contains('#spaces', 'LEGO:  blocks')
// solution: find the element and assert its text yourself
cy.get('#spaces').should($el => {
  expect($el).to.have.html('LEGO:  blocks')
})
// you can also remove duplicate white spaces before calling cy.contains
// and for good measure trimp the text we are looking for
cy.contains('#spaces', 'LEGO:  blocks'.replace(/\s+/g, ' ').trim())

cy.contains and children elements

The cy.contains command can find the element even if the text is split across its children elements.

<ul id="sell-fruits">
  <li>
    <span class="name">Apples</span>
    <span class="price">$4.00</span>
  </li>
</ul>
cy.contains('#sell-fruits li', 'Apples $4.00')

Extract part of the text

Once you found an element with some text, you can extract a specific part using a regular expression with named groupsopen in new window, which are supported by the modern browsers.

<div id="my-name">My name is Gleb, what's yours?</div>
cy.contains('#my-name', 'My name is')
  // let's extract the name
  .invoke('text')
  // match a group using a regex
  .invoke('match', /name is (?<name>\w+),/)
  // grab just the specific group "name"
  .its('groups.name')
  .should('equal', 'Gleb')

Text with double quotes

Double quotes in the text you are looking for present no problem for the cy.contains command.

<div id="with-quotes">My name is "Cypress examples"</div>
cy.contains('div', 'name is "Cypress examples"')
  .should('exist')
  .scrollIntoView()
  .should('have.id', 'with-quotes')

Escape text

When using the cy.contains command, you need to escape the backslashes \ characters.

<div id="escape-text-example">
  <div id="message">[INFO]: this is \\a message\\</div>
</div>
// notice how we need to escape the JavaScript string
// because it needs to have double back slashes
const msg = '[INFO]: this is \\\\a message\\\\'
cy.get('#escape-text-example')
  .contains(msg)
  .should('have.id', 'message')
cy.get('#escape-text-example')
  // almost equivalent jQuery :contains(text) selector
  // but unfortunately it breaks on back slashes
  // https://api.jquery.com/contains-selector/
  // .find(`:contains("${msg}")`)
  // we need to escape each escaped backslash!
  .find(`:contains("[INFO]: this is \\\\\\\\a message\\\\\\\\")`)
  .should('have.id', 'message')

Imagine the HTML text has double white space. Then cy.contains struggles.

<div data-testid="product-name">
  Garmin Instinct 2 Solar, (GRAPHITE)  (010-02627-10)
</div>
const selector = '[data-testid=product-name]'
const text =
  'Garmin Instinct 2 Solar, (GRAPHITE)  (010-02627-10)'
cy.get(selector).should('contain.text', text)
// the entire text does not match
cy.contains(selector, text).should('not.exist')
// but pieces of the long text can be found correctly
cy.contains(selector, 'Garmin Instinct 2 Solar')
cy.contains(selector, '(GRAPHITE)')
cy.contains(selector, '(010-02627-10)')
// it is the double spaces that pose a problem
cy.contains(selector, ')  (').should('not.exist')
// if we remove the double space, it works
cy.contains(selector, ') (')

Suggestion: clean up the text before searching by replacing multiple spaces with a single " " character.

cy.contains(selector, text.replaceAll(/\s+/g, ' '))

.withinopen in new window

We can find elements within a specific DOM element .within()

<h6>Name input</h6>
<input
  type="text"
  id="inputName"
  class="form-control"
  placeholder="Name"
/>
<h6>Form</h6>
<form class="query-form">
  <input
    type="text"
    id="inputEmail"
    class="form-control"
    placeholder="Email"
  />
  <input
    type="text"
    id="inputPassword"
    class="form-control"
    placeholder="Password"
  />
</form>
// validate placeholder attributes
cy.get('.query-form').within(() => {
  cy.get('input:first').should(
    'have.attr',
    'placeholder',
    'Email',
  )
  cy.get('input:last').should(
    'have.attr',
    'placeholder',
    'Password',
  )
})

Yields the original element

The cy.within yields the same DOM element it received as the parent.

<div id="within-yields">
  The parent div
  <div class="some-child">Child element</div>
</div>
cy.get('#within-yields')
  .within(() => {
    // we are trying to return something
    // from the .within callback,
    // but it won't have any effect
    return cy
      .contains('Child element')
      .should('have.class', 'some-child')
  })
  .should('have.id', 'within-yields')

You can attempt to cy.wrapopen in new window a different value - still the original parent element is going to be yielded.

<div id="wrap-inside-within">
  The parent div
  <div class="some-child">Child element</div>
</div>
cy.get('#wrap-inside-within')
  .within(() => {
    // returning cy.wrap(...) has no effect on the yielded value
    // it will still be the original parent DOM element
    return cy.wrap('a new value')
  })
  .should('have.id', 'wrap-inside-within')

Temporarily escape .within

You can temporarily escape the .within scope by using cy.rootopen in new window + cy.closestopen in new window commands.

<section id="escape-example">
  <h6>Name input</h6>
  <input
    type="text"
    id="inputName"
    class="form-control"
    placeholder="Name"
  />
  <h6>Form</h6>
  <form class="the-form">
    <input
      type="text"
      id="inputEmail"
      class="form-control"
      placeholder="Email"
    />
    <input
      type="text"
      id="inputPassword"
      class="form-control"
      placeholder="Password"
    />
  </form>
</section>
cy.get('.the-form').within(() => {
  // escape back find H6
  cy.root()
    .closest('#escape-example')
    .contains('h6', 'Name input')
  // escape and enter text into the input field
  cy.root()
    .closest('#escape-example')
    .find('input#inputName')
    .type('Batman')
})

Note: you need the cy.root() command first because cy.closest is a child command and cannot be used to start the new command chain.

Number of elements

Using .within followed by cy.get is convenient for finding multiple matching elements inside another element. For example, let's confirm that the given picture element has at least 2 source elements and 1 img child element.

<picture>
  <source srcset="logo-768.png 768w, logo-768-1.5x.png 1.5x" />
  <source srcset="logo-480.png, logo-480-2x.png 2x" />
  <img src="logo-320.png" alt="logo" />
</picture>
cy.get('picture').within(() => {
  // at least 2 source elements
  cy.get('source').should('have.length.gt', 1)
  // single img element
  cy.get('img').should('have.length', 1)
})

Within works with multiple elements

The command cy.within requires the parent subject to be a single element.

<ul id="fruits">
  <li id="item-apples"><a href="/apples">Apples</a></li>
  <li id="item-oranges"><a href="/oranges">Oranges</a></li>
</ul>
cy.get('#fruits li')
  .should('have.length', 2) // there are 2 LI items
  // 🚨 NOT GOING TO WORK
  // "Your subject contained 2 elements"
  .within(() => {
    // Nope, not going to get here
  })

cy.root()open in new window

We can find the root DOM element cy.root()

<ul class="query-ul">
  <li>One</li>
  <li>Two</li>
  <li>Buckle my shoe</li>
</ul>
// By default, root is the document
cy.root().should('match', 'html')

cy.get('.query-ul').within(() => {
  // In this within, the root is now the ul DOM element
  cy.root().should('have.class', 'query-ul')
})

Best Practices: Selecting elementsopen in new window

Prefer dedicated data-cy or data-test attributes to CSS class names and element IDs. See detailed discussion at Best Practices: Selecting elementsopen in new window

<div id="best-practices">
  <button
    id="main"
    class="btn btn-large"
    name="submission"
    role="button"
    data-cy="submit"
  >
    Submit
  </button>
</div>
cy.get('#best-practices').within(() => {
  // Worst - too generic, no context
  cy.get('button').click()

  // Bad. Coupled to styling. Highly subject to change.
  cy.get('.btn.btn-large').click()

  // Average. Coupled to the `name` attribute which has HTML semantics.
  cy.get('[name=submission]').click()

  // Better. But still coupled to styling or JS event listeners.
  cy.get('#main').click()

  // Slightly better. Uses an ID but also ensures the element
  // has an ARIA role attribute
  cy.get('#main[role=button]').click()

  // Much better. But still coupled to text content that may change.
  cy.contains('Submit').click()

  // Best. Insulated from all changes.
  cy.get('[data-cy=submit]').click()
})

cy.get vs .find

The cy.getopen in new window command always starts its search from the document element, or, if used inside .within, from the cy.rootopen in new window element. The .findopen in new window command starts the search from the current subject.

<div class="test-title">cy.get vs .find</div>
<section id="comparison">
  <div class="feature">Both are querying commands</div>
</section>
cy.get('#comparison')
  .get('div')
  // finds the DIV .test-title outside the #parent
  // and the DIV .feature inside
  .should('have.class', 'test-title')
  .and('have.class', 'feature')
cy.get('#comparison')
  .find('div')
  // the search is limited to the tree at #comparison element
  .should('have.length', 1)
  .and('have.class', 'feature')

Pseudo class selectors

See the Pseudo CSS selectors recipe.