How can I select specific sibling of an element in table?

Viewed 53

I am pointing to the middle table cell and I can able to get text of the next cell using the next() method but when I tried to use parent() I am getting full row text which is three cells. Below is the HTML code. Kindly help.

cy.get('[name="courses"] > tbody >tr >td:nth-child(2)').each((e1, index) => {
  const course = e1.text()
  
  if (course.includes('Python')) {
    cy.get('[name="courses"] > tbody >tr >td:nth-child(2)').eq(index).parent()
        .then((price) => {
      const courseprice = price.text()
      // expect(courseprice).to.be.equal('25')
      cy.log(courseprice)
    })
  }
})

enter image description here

enter image description here

enter image description here

5 Answers

The reason the text is the entire text is that it combines the text of all child elements. To avoid this, we can take a few different routes:

First solution: we can just check if the parent contains the value:

cy.get('[name="courses"] > tbody >tr >td:nth-child(2)')
  .eq(index)
  .parent()
  .should('contain.text', 25);

Second solution: grab the third row TD directly

cy.get('[name="courses"] > tbody >tr >td:nth-child(3)') // nth-child is index-1
  .should('have.text', 25);

After using parent, you can use within to go inside the parent tr and access each td elements like this:

cy.get('[name="courses"] > tbody >tr >td:nth-child(2)')
  .eq(index)
  .parent()
  .within(() => {
    cy.get('td').eq(0).should('have.text', 'Rahul Shetty')
    cy.get('td')
      .eq(1)
      .should(
        'have.text',
        'Master Selenium Automation is simple Python Language'
      )
    cy.get('td').eq(2).should('have.text', '25')
  })

You can reach the first td based on the second td like this:

cy.contains('td', 'Master Selenium Automation is simple Python Language')
  .parent()
  .within(() => {
    cy.get('td').eq(0).should('have.text', 'Rahul Shetty')
  })

It would be easier to iterate over the rows and check the 2nd instead of iterating over the 2nd and then travers up and down the DOM to get the price and then use cypress-if

cy.get('[name="courses"] > tbody >tr')
  .should("have.length.greaterThan", 0)
  .each($row =>{
    cy.wrap($row)
      .contains("td", "Python", { timeout: 0 })
      .if("visible")
      .then(() => {
        cy.log("Price of Python course");
        cy.wrap($row).contains("td", "25").should("be.visible");
      })
      .else()
      .log("Instructor does NOT sell Python");
   }
})

Here is a minimal reproducible example.

Another option, and my preferred way is to use find I would also recommend not using then or or other nested function like each if you don't need it. And even though you can use jquery selectors like nth-child(), using the eq() makes it a bit more readable (IMHO). Also nth-child index is not 0-based but 1-based, so nth-child(2) returns the 2nd column, not the 3rd.

for example, this will give you the 8th row and 3rd column

cy.get('[name="courses"] > tbody >tr')
  .eq(7)
  .find('td')
  .eq(2)

if I repeat anything more than twice in a test, I tend to write helper functions in the spec.

// define somewhere at the beginning of the file (or add to commands.js)
const getTableCell(r,c) => cy.get('[name="courses"] > tbody >tr')
  .eq(r)
  .find('td')
  .eq(c)

describe('‍♂️', () => {
  it('', () => {
    getTableCell(0,0).should('have.text', 'Instructor')
    getTableCell(0,1).should('have.text', 'Course')
    getTableCell(0,2).should('have.text', 'Price')
    getTableCell(1,0).should('have.text', 'Rahul Shetty')
    getTableCell(1,1).should('contain.text', 'Appium')
    getTableCell(1,2).should('have.text', '25')
    // etc...
  })
})

in some cases, depending on your html markup, the text might be padded with white space. In that case you might want to use contain.text instead of have.text but that could also cause issues, eg. 125 would match contain.text("25").


another option

cy.get('[name="courses"] > tbody >tr') // select all rows
  .contains("Python") // find 1st matched row
  .find("td") // find all columns in row
  .eq(2) // pick 3rd column
  .should("have.text", "25")  // make asserion

Additionally, you can use find if you want to find a child element from a selector. In this case I added .find('td').eq(2). But note that this example shows a really convoluted way to search up and down the DOM.

cy.get('[name="courses"] > tbody >tr >td:nth-child(2)').each((e1, index)=>{
        const course = e1.text()
        if(course.includes('Python')){
            cy.get('[name="courses"] > tbody >tr >td:nth-child(2)').eq(index).parent().find('td').eq(2).then((price)=>{
                const courseprice = price.text()
                // expect(courseprice).to.be.equal('25')
                cy.log(courseprice)
            })
        }
    })

For the first row containing "Python", .contains(selector, text) is the shortest and .next() gives you the price column.

cy.contains('[name="courses"] tbody tr td:nth-child(2)', 'Python') // first match
  .prev()                                             // move left one cell
  .invoke('text')
  .as('instructor')

cy.get('@instructor').then(instructorName => {   // "Rahul Shetty"
  ...
})

For multiple rows containing "Python", change to the pseudo-selector :contains()

cy.get('[name="courses"] tbody tr td:nth-child(2):contains("Python")')   // all matches
  .prev()
  .then($instructors => {
    const names = [...$instructors].map(el => el.innerText)
    cy.wrap(names).as('instructors')
  })

cy.get('@instructor').then(instructorName => {   // ["Rahul Shetty", ...]
  ...
})
Related