Number Of Elements

Imagine you want to confirm the number of list items. The best test syntax depends on how the elements are loaded: are they present already or load dynamically? Do you know the exact number or are there several possible answers? Can the number of elements be zero? The following examples show every scenario.

📺 watch video Confirm The Number Of Elements In Different Scenariosopen in new window

Static list

Let's start with the simplest case: the static list with a known number of elements to expect.

<ul id="fruits">
  <li>Apples</li>
  <li>Pears</li>
  <li>Kiwi</li>
</ul>

Let's confirm we have three fruits

cy.get('#fruits li').should('have.length', 3)

Dynamic list

Let's say the list with three elements appears after a delay

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    document.querySelector('ul#fruits').innerHTML = `
      <li>Apples</li>
      <li>Pears</li>
      <li>Kiwi</li>
    `
  }, 1000)
</script>

Let's confirm we have three fruits. Notice the test does not change because the cy.get(...).should(...) query chain retries until the elements appear.

cy.get('#fruits li').should('have.length', 3)

Slowly loading dynamic list

Let's see a scenario where each element loads slowly

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    document.querySelector('ul#fruits').innerHTML =
      '<li>Apples</li>'
  }, 2000)
  setTimeout(function () {
    document.querySelector('ul#fruits').innerHTML +=
      '<li>Pears</li>'
  }, 4000)
  setTimeout(function () {
    document.querySelector('ul#fruits').innerHTML +=
      '<li>Kiwi</li>'
  }, 6000)
</script>

We want to wait for 6 seconds for all elements to load. We can increase the command timeout by adding a section to the cy.get command

cy.get('#fruits li', { timeout: 6500 }).should('have.length', 3)

Let's say the list with three elements appears after a delay

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    document.querySelector('ul#fruits').innerHTML = `
      <li>Apples</li>
      <li>Pears</li>
      <li>Kiwi</li>
    `
  }, 1000)
</script>

Let's confirm we have three fruits and then print the number of elements. By default cy.get command yields the jQuery element with found elements. We want to print its length property. We can chain the commands to form a single test.

cy.get('#fruits li')
  .should('have.length', 3)
  .its('length')
  .then((n) => cy.log(`found ${n} elements`))

Let's say the list with three elements appears after a delay

We can print the number of elements using query cy.print from cypress-mapopen in new window plugin.

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    document.querySelector('ul#fruits').innerHTML = `
      <li>Apples</li>
      <li>Pears</li>
      <li>Kiwi</li>
    `
  }, 1000)
</script>
cy.get('#fruits li')
  .its('length')
  .print('found %d elements')
  // the number is passed to the assertion
  .should('equal', 3)

Confirm the first element

Let's confirm the first element's text. We first confirm the list loads 3 elements and then get the first element and confirm its text.

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    document.querySelector('ul#fruits').innerHTML = `
      <li>Apples</li>
      <li>Pears</li>
      <li>Kiwi</li>
    `
  }, 1000)
</script>
cy.get('#fruits li')
  .should('have.length', 3)
  .first()
  .should('have.text', 'Apples')

Several possible numbers

If the list can have two possible lengths, we can query its length property and add an assertion to it. In the example below the list can have either 3 or 5 items.

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    if (Math.random() < 0.5) {
      document.querySelector('ul#fruits').innerHTML = `
        <li>Apples</li>
        <li>Pears</li>
        <li>Kiwi</li>
      `
    } else {
      document.querySelector('ul#fruits').innerHTML = `
        <li>Apples</li>
        <li>Pears</li>
        <li>Kiwi</li>
        <li>Grapes</li>
        <li>Bananas</li>
      `
    }
  }, 1000)
</script>

The chain cy.get(...).its(...).should(...) retries if the assertion does not pass.

cy.get('#fruits li')
  .its('length')
  .should('be.oneOf', [3, 5])
  .then((n) => cy.log(`found ${n} elements`))

Range of possible values

If the number of items can be between two limits, then we can use another assertion

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    if (Math.random() < 0.5) {
      document.querySelector('ul#fruits').innerHTML = `
        <li>Apples</li>
        <li>Pears</li>
        <li>Kiwi</li>
      `
    } else {
      document.querySelector('ul#fruits').innerHTML = `
        <li>Apples</li>
        <li>Pears</li>
        <li>Kiwi</li>
        <li>Grapes</li>
        <li>Bananas</li>
      `
    }
  }, 1000)
</script>

Let's use within Chai assertion.

cy.get('#fruits li')
  .its('length')
  .should('be.within', 3, 5)
  .then((n) => cy.log(`found ${n} elements`))

Number of elements follows a rule

Let's say we only now that the number of loaded elements should be divisible by 3

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    if (Math.random() < 0.5) {
      document.querySelector('ul#fruits').innerHTML = `
        <li>Apples</li>
        <li>Pears</li>
        <li>Kiwi</li>
      `
    } else {
      document.querySelector('ul#fruits').innerHTML = `
        <li>Apples</li>
        <li>Pears</li>
        <li>Kiwi</li>
        <li>Grapes</li>
        <li>Bananas</li>
        <li>Plums</li>
      `
    }
  }, 1000)
</script>

We want to make sure the number of elements is divisible by 3.

cy.get('#fruits li')
  .its('length')
  // the predicate function should return a boolean result
  .should('satisfy', (n) => n % 3 === 0)
  .then((n) => cy.log(`found ${n} elements`))

Tip: you could also write a custom assertion function that can use Chai or any assertions

cy.get('#fruits li')
  .its('length')
  .should((n) => {
    expect(n % 3 === 0, `${n} elements`).to.be.true
  })

There could be zero or N elements

📺 Watch this example explained in video Zero Or Three Elements After Loading The Dataopen in new window.

Now imagine the list on the page is created from the data sent by the server. The server could send an empty list. How do we validate the page if there could be either zero or three elements? We need something observable on the page to let the test "know" that the data has been loaded and now it is safe to grab the elements.

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    if (Math.random() < 0.5) {
      document.querySelector('ul#fruits').innerHTML = `
        <li>Apples</li>
        <li>Pears</li>
        <li>Kiwi</li>
      `
    }
    // in all cases, set an attribute
    // to signal the data has been loaded
    document
      .querySelector('ul#fruits')
      .setAttribute('data-loaded', 'true')
  }, 1000)
</script>

Our test first waits for the data to load by checking the attribute

cy.get('ul#fruits')
  .should('have.attr', 'data-loaded', 'true')
  // find LI elements, but allow them NOT to exist
  .find('li')
  .should(Cypress._.noop)
  .its('length')
  .should('be.oneOf', [0, 3])

We can also check the jQuery object's length using should('satisfy', ...) or should(callback) assertions. Just make sure to check after the .should('have.attr', 'data-loaded', 'true') passed.

// using "satisfy" assertion
cy.get('ul#fruits li').should(
  'satisfy',
  ($li) => $li.length === 0 || $li.length === 3,
)
// using "should(callback)" assertion
cy.get('ul#fruits li').should(($li) => {
  expect($li.length, 'number of elements').to.be.oneOf([0, 3])
})

N elements or nothing found message

Imagine the page loads elements or shows "Nothing found" message. Can we confirm that one or the other happens?

📺 This example is explained in the video Check N Items Or That Nothing Was Foundopen in new window.

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    if (Math.random() < 0.5) {
      document.querySelector('ul#fruits').innerHTML = `
        <li>Apples</li>
        <li>Pears</li>
        <li>Kiwi</li>
      `
    } else {
      document.querySelector('ul#fruits').innerText =
        'Nothing found'
    }
  }, 1000)
</script>

Find the <ul id="fruits"> element with either LI elements inside or text "Nothing found" using jQuery selector :contains.

cy.get('ul#fruits li, ul#fruits:contains("Nothing found")')

If we want to confirm the number of LI elements, then we need more conditional logic.

// find LI elements inside the fruits element
// OR text "Nothing found"
cy.get('ul#fruits li, ul#fruits:contains("Nothing found")').then(
  ($el) => {
    if ($el.is('ul#fruits li')) {
      // we found "LI" elements inside the fruits list
      expect($el).to.have.length(3)
    }
  },
)

N elements or nothing found element

The previous example searched for exact text match "Nothing found". To make this more generic and avoid hard-coding the text, we can use a specific "Nothing found" data attribute instead.

<ul id="fruits"></ul>
<script>
  setTimeout(function () {
    if (Math.random() < 0.5) {
      document.querySelector('ul#fruits').innerHTML = `
        <li>Apples</li>
        <li>Pears</li>
        <li>Kiwi</li>
      `
    } else {
      document.querySelector('ul#fruits').innerHTML =
        '<div data-cy="zero-state">Nothing there...</div>'
    }
  }, 1000)
</script>

Find the <ul id="fruits"> element with either LI elements inside or the "zero state" element. Let's confirm the number of LI elements and zero state message if found.

cy.get('ul#fruits li, ul#fruits [data-cy=zero-state]').then(
  ($el) => {
    if ($el.is('li')) {
      // we found "LI" elements inside the fruits list
      expect($el).to.have.length(3)
    } else {
      // found zero state element, let's confirm its text
      expect($el).to.include.text('Nothing there')
    }
  },
)

See also