3

I have the following code:

cy.get('input:checkbox').first().as('firstCheckbox').then(() => {

    cy.get('@firstCheckbox').should('be.checked').click()
    cy.get('.globalMessage-display > :nth-child(1) > .alert').should('be.visible')
    cy.get('.globalMessage-display > :nth-child(1) > .alert').should('not.exist')
    cy.get('@firstCheckbox').should('not.be.checked').click()
    cy.get('.globalMessage-display > :nth-child(1) > .alert').should('be.visible')
    cy.get('.globalMessage-display > :nth-child(1) > .alert').should('not.exist')
    cy.get('@firstCheckbox').should('be.checked')

})

after cy.get('@firstCheckbox').should('be.checked').click() the checkbox is moved to the last index position instead of the first. According to my understanding of aliases this should not matter, because I reference it with ('@') and it doesn't matter at which position it is. Unfortunately the next cy.get('@firstCheckbox') accesses the new first element and not the one I originally referenced. I can't figure it out from the documentation either. where is the error?

https://docs.cypress.io/guides/core-concepts/variables-and-aliases

https://docs.cypress.io/api/commands/as

Update for @agoff:

it seems to work better with your code but there is a new problem I notice with the new code. if you uncheck the checkbox then the class is reloaded with js causing it to briefly disappear from the dom. After that it can't find it anymore and the test aborts. I also tried with reload but it can't find the element anymore. How to deal with this?

the checked checkbox class has the name custom-control-input ng-untouched ng-pristine ng-valid. after unchecking the class name changed to custom-control-input.ng-untouched.ng-valid.ng-dirty for a second and then the element is removed and reloaded to the DOM with with the class name custom-control-input ng-untouched ng-pristine ng-valid. the problem is here that the second cy.wrap($el) tries to find the element custom-control-input.ng-untouched.ng-valid.ng-dirty although the initial element with the class custom-control-input ng-untouched ng-pristine ng-valid was searched and found. that's something else I don't understand.

Fody
  • 23,754
  • 3
  • 20
  • 37
pontilicious
  • 239
  • 2
  • 12
  • What do you mean by this line `the checkbox is moved to the last index position instead of the first.` ? – Alapan Das Oct 15 '21 at 13:40
  • if i uncheck the checkbox then the index of the element changes from 0 to last position. so it is coded on our website. – pontilicious Oct 15 '21 at 14:58

2 Answers2

6

It happens if during the click event the app removes the input and appends a new one at the end. Then your alias is invalid, because it points to an element no longer in the DOM.

If I move the element within the click handler, your code works.

If I clone the element within the click handler, your code fails.

What to do?

Refresh your alias after the click()

cy.get('input:checkbox').first().as('myCheckbox').then(() => {

  cy.get('@myCheckbox').should('be.checked').click()
    .then(() => cy.get('input:checkbox').last().as('myCheckbox'))  // it's last now!

  cy.get('.globalMessage-display > :nth-child(1) > .alert').should('be.visible')
  cy.get('.globalMessage-display > :nth-child(1) > .alert').should('not.exist')

  cy.get('@myCheckbox').should('not.be.checked').click()
    .then(() => cy.get('input:checkbox').last().as('myCheckbox'))

  cy.get('.globalMessage-display > :nth-child(1) > .alert').should('be.visible')
  cy.get('.globalMessage-display > :nth-child(1) > .alert').should('not.exist')

  cy.get('@myCheckbox').should('be.checked')
})

The alias command has another trick as well.

If the element is cloned and replaced, the original element becomes detached from DOM.

The cy.get('@alias') detects detached elements and replays the original command to automatically refresh.

It doesn't work with .first() because the element changes position.

But if you use a data-cy attribute instead of .first(), your code will work.

cy.get('input:checkbox[data-cy="first-checkbox"]')
  .as('myCheckbox').then(() => {

  cy.get('@myCheckbox')
    .should('be.checked').click()    // cloned and moved to another position

  ...

  cy.get('@myCheckbox')                 // detached by previous click
    .should('not.be.checked').click()   // so alias replays original get
                                        // with [data-cy="first-checkbox"]
  ...
  cy.get('@myCheckbox').should('be.checked')  // as above
})

Here's the Cypress code that does it

let { subject, alias, command } = aliasObj

const resolveAlias = () => {
  
  if ($dom.isElement(subject)) {          // if this is a DOM element

    const replay = () => {
      cy.replayCommandsFrom(command)
      return undefined
    }

    if ($dom.isDetached(subject)) {     // is it detached?

      subject = subject.filter((index, el) => $dom.isAttached(el))  // all detached?

      if (!subject.length) {
        return replay()                // perform the replay
      }
    }
Fody
  • 23,754
  • 3
  • 20
  • 37
0

I think you are confusing and misusing aliases. When you use the .then(), you've already yielded the found element. So, instead of using the alias, you can reference the object directly (after wrapping it). I believe that something like this should fix your problem...

cy.get('input:checkbox').then(($el) => {
  cy.wrap($el).should('be.checked').click()
              .and('not.be.checked').click()
              .and('be.checked')
})
agoff
  • 5,818
  • 1
  • 7
  • 20
  • updated my initial question – pontilicious Oct 15 '21 at 16:03
  • Updated my answer -- you might be able to get around it by chaining the shoulds (now written as `and()`) instead of re-wrapping the element? – agoff Oct 15 '21 at 16:08
  • works but what do I do if I want to run other code between `clicks`? Then the code does not work unfortunately. and it is not very stable. sometimes it works, sometimes it breaks off after the `clicks` because it is too fast. `.wait` did not help there either – pontilicious Oct 15 '21 at 16:34
  • What exactly are you trying to do? My answer was in response to the provided code, and mimics that exact behavior. I think I'm a little confused about what exactly you'd like to do, from start to finish. – agoff Oct 15 '21 at 16:41
  • sorry for the confusion. i thought the simplified code above would be enough to get a solution. I have updated it so that the full extent of the test can be seen. – pontilicious Oct 15 '21 at 16:50
  • When you uncheck the box and it goes to the last index, does checking it always return it back to the first index? – agoff Oct 15 '21 at 18:27
  • yes, thats right. – pontilicious Oct 15 '21 at 18:46