user1128912
user1128912

Reputation: 191

Cypress tests - check the element value on the last table row

I'm doing some cypress tests on a rails/react app and I need to check if the value inputted in the form on the last row is, lets say, "Another Random Text". In the provided html below, it's on the 2nd row but it could be in any other last row number.

---- CYPRESS ----

What didn't work

cy.get('.form-something').last().should('have.value', 'Another Random Text')

because it returns cy.should() failed because this element is detached from the DOM.

And by using eq() I couldn't address the last row, just the first or the 2nd last.

Can anyone shine a light? Thank you in advance

---- HTML ------

<table class="table table-flat">
   <thead>
      <tr>
         <th style="width: 50%;">State</th>
         <th>Generic State</th>
         <th style="min-width: 100px;"></th>
      </tr>
   </thead>
   <tbody>
      <tr class="index-0" data-qa="s-3313">
         <td><input class="form-something" type="text" name="name" value="Random Text"></td>
         <td data-qa="generic-state">Additional</td>
         <td><button class="btn btn-danger btn-sm" data-qa="remove-state"><i class="fa fa-trash"></i></button></td>
      </tr>
      <tr class="index-1" data-qa="s-3314">
         <td><input class="form-something" type="text" name="name" value="Another Random Text"></td>
         <td data-qa="generic-state">Other</td>
         <td><button class="btn btn-danger btn-sm" data-qa="remove-state"><i class="fa fa-trash"></i></button></td>
      </tr>
      <tr>
         <td colspan="2"></td>
         <td><button class="btn btn-success btn-sm" data-qa="add-new-state"><i class="fa fa-plus mr-2"></i>Add</button></td>
      </tr>
   </tbody>
</table>

Upvotes: 0

Views: 3947

Answers (5)

Alapan Das
Alapan Das

Reputation: 18624

You can also use an each loop to find the element with the desired value and then apply the assertion like this:

cy.get('.form-something')
  .should('be.visible')
  .each(($ele) => {
    if ($ele.val().trim() == 'Another Random Text') {
      cy.wrap($ele).should('have.value', 'Another Random Text') //Apply assertion
      return false //Exit loop once element is found
    }
  })

Upvotes: 0

Zaista
Zaista

Reputation: 1422

One safe bet when dealing with element is detached from the DOM errors is to leverage the cypress retry-ability in a proper way.

The trick is to make sure that selecting action is right next to the assertion. In your example that's not the case, because it's broken by the .last() call.

You can try something like this, which should do the trick:

cy.get('.form-something[value="Another Random Text"]').should('exist')

To explain the reasoning behind the answer above: frameworks like React usually load the empty table first, and then fill it up (rerender it). Cypress being too fast often grabs the empty table before its filled, which is afterward rerendered when table data is fetched, resulting in that detached error.

Edit

To solve a problem mentioned in the comment, when you need control of value property rather than value attribute, you can use this:

cy.get('.form-something').should(inputElements => {
    expect(inputElements[inputElements.length - 1]).to.have.value('Some test');
});

This will make sure that the last .form-something element contains the right text, and it won't match even if that exact text is in any element other than the last. You can even add multiple assertions that would all together benefit from build-in retry-ability.

Upvotes: 1

TesterDick
TesterDick

Reputation: 10550

Using an alias may help your test.

cy.get('.form-something').last().as('lastInput')
cy.get('@lastInput').should('have.value', 'Another Random Text')

It looks a bit unnecessary, but alias has built-in protection against detached-from-dom errors.

Here is one of the Cypress tests that verifies the feature,

it('can find a custom alias again when detached from DOM', () => {
  cy
  .get('foobarbazquux:last').as('foo')   // creating an alias
  .then(() => {
    // remove the existing foobarbazquux
    cy.$$('foobarbazquux').remove()      // detaching from DOM

    // and cause it to be re-rendered
    cy.$$('body').append(cy.$$('<foobarbazquux>asdf</foobarbazquux>'))
  }).get('@foo').should('contain', 'asdf') 
})

Upvotes: 2

kegne
kegne

Reputation: 853

You can also use .contains() based on the "Other" text

cy.get('table').should('be.visible')
  .contains('td', 'Other')                     // locate by text 
  .prev()                                      // previous cell
  .find('input.form-something')                // get the input
  .should('have.value', 'Another Random Text')

Upvotes: 2

Fody
Fody

Reputation: 32052

If the table get's re-displayed after the text is entered (or some other action like clicking "Add"), elements you query before the action are discarded by the app and replaced by a new version.

Your test still has a reference to the original element, so it's not yet garbage-collected but it is detached from the DOM

So this fails,

cy.get('.form-something').last()
  .type('Another Random Text')                   // action causes detached element
  .should('have.value', 'Another Random Text')

but this maybe succeeds

cy.get('.form-something').last()
  .type('Another Random Text')

cy.get('.form-something').last()
  .should('have.value', 'Another Random Text')

but it's safer to include the full table selector

cy.get('table tbody tr td .form-something').last()
  .should('have.value', 'Another Random Text')     // requery all parts from table down

Upvotes: 2

Related Questions