# Getting Text from List of Elements

Imagine we have HTML elements.

<div>
  <div class="matching">first</div>
  <div>second</div>
  <div class="matching">third</div>
  <div class="matching">fourth</div>
  <div>fifth</div>
</div>
first
second
third
fourth
fifth

We want to get the text values of elements with the class matching

cy.get('.matching')
  .should('have.length', 3)
  .then(($els) => {
    // we get a list of jQuery elements
    // let's convert the jQuery object into a plain array
    return (
      Cypress.$.makeArray($els)
        // and extract inner text from each
        .map((el) => el.innerText)
    )
  })
  .should('deep.equal', ['first', 'third', 'fourth'])

// let's use Lodash to get property "innerText"
// from every item in the array
cy.log('**using Lodash**')
cy.get('.matching')
  .should('have.length', 3)
  .then(($els) => {
    // jQuery => Array => get "innerText" from each
    return Cypress._.map(Cypress.$.makeArray($els), 'innerText')
  })
  .should('deep.equal', ['first', 'third', 'fourth'])

cy.log('**using Lodash to convert and map**')
cy.get('.matching')
  .should('have.length', 3)
  .then(($els) => {
    expect(Cypress.dom.isJquery($els), 'jQuery input').to.be.true
    // Lodash can iterate over jQuery object
    return Cypress._.map($els, 'innerText')
  })
  .should('be.an', 'array')
  .and('deep.equal', ['first', 'third', 'fourth'])

You can extract the logic to get the text from the list of elements into its own utility function.

const getTexts = ($el) => {
  return Cypress._.map($el, 'innerText')
}
cy.get('.matching')
  .should('have.length', 3)
  .then(getTexts)
  .should('deep.equal', ['first', 'third', 'fourth'])

So the final advice to extract text from the list of found elements is to use the Lodash _.map method.

cy.get('.matching').then(($els) =>
  Cypress._.map($els, 'innerText'),
)

Find this recipe in the video Get Text From A List Of Elements (opens new window).

# Array vs jQuery object

Note: we cannot use cy.get(...).then(Cypress.$.makeArray).then(els => ...) to convert from jQuery object first, because the result of the $.makeArray is an array of elements, and it gets immediately wrapped back into jQuery object after returning from .then

<div>
  <div class="matching">first</div>
  <div>second</div>
  <div class="matching">third</div>
  <div class="matching">fourth</div>
  <div>fifth</div>
</div>
first
second
third
fourth
fifth
cy.get('.matching')
  .then(($els) => {
    expect(Cypress.dom.isJquery($els), 'jQuery object').to.be
      .true
    const elements = Cypress.$.makeArray($els)
    expect(Cypress.dom.isJquery(elements), 'converted').to.be
      .false
    expect(elements, 'to array').to.be.an('array')
    // we are returning an array of DOM elements
    return elements
  })
  .then((x) => {
    // but get back a jQuery object again
    expect(Cypress.dom.isJquery(x), 'back to jQuery object').to
      .be.true
    expect(x, 'an not a array').to.not.be.an('array')
    expect(x.length, '3 elements').to.equal(3)
  })
<ul id="items">
  <li>Apples</li>
  <li>Oranges</li>
  <li>Pears</li>
  <li>Grapes</li>
</div>
  • Apples
  • Oranges
  • Pears
  • Grapes
cy.get('#items li').each(($li) => cy.log($li.text()))

# Collect the items then print

<ul id="items">
  <li>Apples</li>
  <li>Oranges</li>
  <li>Pears</li>
  <li>Grapes</li>
</div>
  • Apples
  • Oranges
  • Pears
  • Grapes
const items = []
cy.get('#items li')
  .each(($li) => items.push($li.text()))
  .then(() => {
    cy.log(items.join(', '))
  })

# Collect the items then assert the list

<ul id="items">
  <li>Apples</li>
  <li>Oranges</li>
  <li>Pears</li>
  <li>Grapes</li>
</div>
  • Apples
  • Oranges
  • Pears
  • Grapes
const items = []
cy.get('#items li').each(($li) => items.push($li.text()))
// the items reference is set once
// and the new items are added by the above commands
// that is why we don't have to use "cy.then"
cy.wrap(items).should('deep.equal', [
  'Apples',
  'Oranges',
  'Pears',
  'Grapes',
])

Tip: you can create a flexible OR assertion using match and a predicate callback. For example, the next assertion passes if any element gets true from the predicate function.

// verify the list using .should("match", callback) assertion
// passes if at least for one element the callback returns true
cy.get('#items li').should('match', (k, li) => {
  // this predicate function gets called once for each element
  // "li" is the DOM element
  // we should return true|false if the text is what we expected to find
  return li.innerText === 'Nope' || li.innerText === 'Oranges'
})

# Confirm the number of items

Let's imagine the page shows the number of items in the list, and we want to confirm the displayed number is correct.

<div>There are <span id="items-count">4</span> items</div>
<ul id="items">
  <li>Apples</li>
  <li>Oranges</li>
  <li>Pears</li>
  <li>Grapes</li>
</div>
There are 4 items
  • Apples
  • Oranges
  • Pears
  • Grapes

Because the commands are asynchronous we cannot get the number "4" and immediately use it to assert the number of <LI> items. Instead we need to use .then callback.

cy.get('#items-count')
  .invoke('text')
  .then(parseInt)
  .then((n) => {
    cy.get('#items li').should('have.length', n)
  })

# with text matching

Sometimes the number of items is a part of the text and needs to be extracted first before parsing into a number. Notice the number "4" is hiding inside the brackets and does not have its own element to query.

<div id="items-intro">There are [ 4 ] items</div>
<ul id="my-items">
  <li>Apples</li>
  <li>Oranges</li>
  <li>Pears</li>
  <li>Grapes</li>
</div>
There are [ 4 ] items
  • Apples
  • Oranges
  • Pears
  • Grapes

We need to grab the entire text

cy.get('#items-intro')
  .invoke('text')
  .then((s) => {
    // by adding an assertion here we print
    // the text in the command log for simple debugging
    expect(s).to.be.a('string')
    const matches = /\[\s*(\d+)\s*\]/.exec(s)
    return matches[1]
  })
  .then(parseInt)
  // another assertion to log the parsed number in the command log
  .should('be.a', 'number')
  // we expect between 1 and 10 items
  .and('be.within', 1, 10)
  .then((n) => {
    cy.get('#my-items li').should('have.length', n)
  })

# Text at index K

Let's say we want to robustly check each item's text. But the items can be added dynamically, and the item's order matters.

<ul id="fruits"></ul>
<script>
  const fruits = document.getElementById('fruits')
  function addFruit(name, timeout) {
    setTimeout(function () {
      const li = document.createElement('li')
      li.appendChild(document.createTextNode(name))
      fruits.appendChild(li)
    }, timeout)
  }
  // insert each list item asynchronously
  addFruit('apples', 500)
  addFruit('kiwi', 1000)
  addFruit('grapes', 1600)
</script>

    Let's check if "apples" is in the list at first position. Then "kiwi", followed by "grapes"

    // check each fruit in this list
    const fruits = ['apples', 'kiwi', 'grapes']
    fruits.forEach((fruit, k) => {
      // the order matters, thus we use the index k
      // to form CSS "nth-child" selector
      // Note: the CSS selector starts at 1, thus
      // we need to add 1 to the index k
      cy.contains(`#fruits li:nth-child(${k + 1})`, fruit)
    })
    

    # Filter items by text

    Let's get all items that contain the words "one" or "two", ignoring the others. We can get all list items, then filter the items by text using the cy.filter (opens new window) command.

    <ol>
      <li>four</li>
      <li>one</li>
      <li>one</li>
      <li>three</li>
      <li>two</li>
      <li>one</li>
      <li>five</li>
      <li>six</li>
    </ol>
    
    1. four
    2. one
    3. one
    4. three
    5. two
    6. one
    7. five
    8. six
    cy.get('ol li')
      // check if all items have loaded
      .should('have.length.greaterThan', 5)
      .filter((k, el) => {
        return el.innerText === 'one' || el.innerText === 'two'
      })
      .should('have.length', 4)
    

    # Index of the element with the text

    Watch the explanation for this example in Confirm The Index Of The Element With The Given Text (opens new window).

    <ul id="fruits">
      <li>Apples</li>
      <li>Oranges</li>
      <li>Pears</li>
      <li>Grapes</li>
    </div>
    
    • Apples
    • Oranges
    • Pears
    • Grapes

    If we want to find the list element containing the text "Pear", and confirm it is the element at index 2 (the index starts at zero), we can invoke the jQuery method index (opens new window).

    cy.contains('#fruits li', 'Pear')
      .invoke('index')
      .should('equal', 2)