Cypress-wait-until - wait for element attribute to change

24,173

Solution 1

This looks over complicated. I would approach it by waiting for initialValue, then waiting for a different value.

cy.get('input').should('have.value', 'myInitialValue');     // wait for initial value
cy.get('input').should('not.have.value', 'myInitialValue'); // wait for it to change

.should() will retry the assertion up to 4 seconds.

If by 'several seconds' you mean more than 4, you can add a timeout option, which is better than an arbitrary wait because it will continue as soon as the assertion passes.

I'm assuming your element is an input since you are accessing val not text.

Solution 2

I went through your function and I think there are several issues with the usage. This might be a cypress related issue or the wait until plugin.

Firstly, In your code, I think with elem you try to pass something like cy.get("path") The problem is when you first invoke elem and try to re-use elem, it becomes null. So if you expect cy.get('path') to run twice, it's not happening.

Secondly, For some reason, in your wait untill,

cy.waitUntil(() => elem.invoke('attr', 'value').then((val) => val != initialVal))

//This part becomes true all the time.
elem.invoke('attr', 'value').then((val) => val != initialVal))

Thirdly,

invoke('attr', 'value')

will not capture the changing attributes of the object. Please note these observations are gathered from trying out what you tried to do. Need to extra research to find out the exact behaviour of the functions.

Considering the above facts, I did a few changes in the code.

  • The main update of the changes are passing the CSS path instead of a cypress object (elem)
  • using js query selector operations to get the changing value ( to take the initial value we can use invoke )

Example 1: Getting the new value inside waitUntil and check for a variable change

const CheckElementChange = (path) => {
    //get the initial value of your object
    cy.get(path).invoke('attr', 'value').then($initialVal => {
        //It's better if you can do your click operation here

        //Wait untill the element changes
        cy.waitUntil(() =>
        cy.get(path).then($newVal => 
            $newVal[0].value !== $initialVal),
            //optional timeouts and error messages
            {
                errorMsg: "was expeting some other Value but got : " + $initialVal,
                timeout: 10000, 
                interval: 500 
              }
        ).then(() => {
            cy.log("Foudn a difference in values")
        })
})
}

Example 2: move waitUntill after the new value extraction and wait till newVal !== initialVal to be true

const CheckElementChangew = (path) => {
    //get the initial value of your object
    cy.get(path).invoke('attr', 'value').then($initialVal => {
        //It's better if you can do your click operation here

        //Wait untill the element changes
        cy.get(path).then($newVal => {
            cy.waitUntil(() => $newVal[0].value !== $initialVal, {
                //optional timeouts and error messages
                errorMsg: "was expeting some other Value but got : " + $initialVal,
                timeout: 10000, 
                interval: 500 
              }).then(() => {
                cy.log("Foudn a difference in values")
            })
        })
    })
}

Usage :

it('test',() => {
    CheckElementChange2("#fname");
})

Note: If you expect the value change upon your click, unless the value change has a time gap of a few seconds, It's better if you can do the click after the extraction of the initial value ( as commented on the codes above )

Share:
24,173

Related videos on Youtube

user13123513
Author by

user13123513

Updated on August 16, 2020

Comments

  • user13123513
    user13123513 over 3 years

    I am testing an app that has a button that causes a value in another element on screen to change, without reloading the page. However, that change in value can be almost instant, or take several seconds. Obviously, I don't want to use some arbitrary wait, and I don't want to use a (hard) assertion - that might cause the test to stop with a fail that isn't a fail.... so I've been looking at cypress-wait-until (https://www.npmjs.com/package/cypress-wait-until). I've written assorted variations along the lines of:

        waitForElemToChange(elem) {
            elem.invoke('attr', val').then((initialVal) => {
            cy.waitUntil(() => elem.invoke('attr', 'value').then((val) => val != initialVal))
        }
    

    But nothing has worked so far - most attempts have resulted in "Timed out retrying: cy.invoke() errored because your subject is: null. You cannot invoke any functions such as attr on a null value."

    Yet it's the same subject that just returned a value in the previous line??

  • Maya Paluy
    Maya Paluy about 3 years
    Great answer, working for me with text: cy.get('element').should('contain', 'some text');