First table column

Rows with TD elements

📺 Watch this recipe explained in Find The Cells In The First Column Of The Second Tableopen in new window.

<table id="people" class="table table-bordered">
  <thead>
    <tr>
      <th>Name</th>
      <th>Age</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Joe</td>
      <td>42</td>
    </tr>
  </tbody>
</table>
<table id="fruit" class="table table-bordered">
  <thead>
    <tr>
      <th>Name</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Apple</td>
      <td>$1.49</td>
    </tr>
    <tr>
      <td>Pear</td>
      <td>$2.99</td>
    </tr>
  </tbody>
</table>

By default, cy.get starts its search from the root of the element.

// 🚨 INCORRECT
cy.log('**using cy.get**')
cy.get('table')
  .should('have.length', 2)
  .eq(1)
  .should('have.id', 'fruit')
  .get('tr td:nth-child(1)')
  // picks the "TR" elements from both tables
  .should('have.length', 3)

We can use cy.within to force cy.get to pick elements only from its parent subject.

cy.log('**using cy.within**')
cy.get('table')
  .should('have.length', 2)
  .eq(1)
  .should('have.id', 'fruit')
  .within(() => {
    cy.get('tr td:nth-child(1)').should('have.length', 2)
  })

If we use cy.findopen in new window command, we limit our query to the parent element.

cy.log('**using cy.find**')
cy.get('table')
  .should('have.length', 2)
  .eq(1)
  .should('have.id', 'fruit')
  .find('tr td:nth-child(1)')
  .should('have.length', 2)

We can even invoke the jQuery method $.find on the current subject to find the cells in the first column.

cy.log('**invoke $.find**')
cy.get('table')
  .should('have.length', 2)
  .eq(1)
  .should('have.id', 'fruit')
  .invoke('find', 'tr td:nth-child(1)')
  .should('have.length', 2)

Rows with TH and TD elements

Sometimes the table row starts with TH element followed by TD elements, making the td:nth-child(2) selector not so obvious.

📺 Watch this example explained in Adjacent Sibling CSS Selector Combinatoropen in new window.

<table id="fruit" class="table table-bordered">
  <thead>
    <tr>
      <th>#</th>
      <th>Name</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1</th>
      <td>Apple</td>
      <td>$1.49</td>
    </tr>
    <tr>
      <th>2</th>
      <td>Pear</td>
      <td>$2.99</td>
    </tr>
  </tbody>
</table>

We can solve this problem by coding the selection logic. Given the tbody element, find each TR element, and inside every row, grab the first TD element using the combination of jQuery $.map, $.map, and DOM querySelector commands.

cy.get('table')
  .should('have.id', 'fruit')
  .find('tbody')
  .then(($tbody) =>
    $tbody.find('tr').map((k, tr) => tr.querySelector('td')),
  )
  .should('have.length', 2)

The same logic can be implemented using purely cypress-mapopen in new window queries

cy.log('**using cypress-map**')
cy.get('table')
  .should('have.id', 'fruit')
  .find('tbody')
  .invoke('find', 'tr')
  // cy.map and cy.mapInvoke queries
  // come from the cypress-map plugin
  .mapInvoke('querySelector', 'td')
  .map('innerText')
  .should('deep.equal', ['Apple', 'Pear'])

Even though the TH is the first column, we can use TD:nth-child(2) selector to find the fruit name cells:

cy.log('**using nth-child(2)**')
cy.get('table')
  .should('have.id', 'fruit')
  .find('tbody tr td:nth-child(2)')
  .should('have.length', 2)

We can also find all TD elements immediately preceded TH by using the adjacent sibling combinatoropen in new window which in our case is th+td.

cy.log('**using cypress-map**')
cy.get('table')
  .should('have.id', 'fruit')
  .find('tbody')
  .find('tr th+td')
  .should('have.length', 2)