Cypress command to wait for any CSS/DOM property to be idle after a specified number of frames.
pnpm add cypress-wait-frames
Import the package in your commands.js:
import 'cypress-wait-frames'
If using TypeScript, install the latest version of csstype for CSS properties autocompletion:
pnpm add -D csstype
Cypress retry ability on built-in assertions is very powerful and most likely you don't need this package or to use cy.wait
. For example:
cy.scrollTo(0, 1200)
// No need for cy.wait(t) to make sure scroll is completed
cy.window().eq('scrollY').should('be.approximately', 1200, 2) // Will retry until it passes
Documentation on retry-ability is very detailed and it might already contain the answer you're looking for.
There are cases where it's impossible to retain retry-ability and you might find yourself guessing timings using cy.wait
.
For example when asserting properties not available within Cypress queries:
cy.get('h1').eq(15).scrollIntoView()
// Need to add cy.wait(t) to make sure scroll is completed
cy.get('h1')
.eq(15)
.then((el) => {
cy.wrap(el[0].getBoundingClientRect().top).should('be.approximately', 0, 2)
})
Or when futher assertions must rely on 100% accurate values:
cy.get('#TriggerComplexTransition').click()
// Need to add cy.wait(t) to make sure desktopHeight is accurate
cy.viewport('macbook-15').get('#TransitionedElement').invoke('height').as('desktopHeight')
cy.get('@desktopHeight').then((desktopHeight) => {
cy.viewport('ipad-2')
.get('#TransitionedElement')
.invoke('height')
.should('be.equal', desktopHeight / 2)
})
But scenarios can be disparate and more complex. Bottom line is that if you find yourself using cy.wait()
as last resort, this package might be for you.
Instead of guessing timings using cy.wait
you can use cy.waitFrames
to wait for a one or more properties to be idle after a specified number of frames.
Specify a subject
to watch, one or more property
and a number of frames
. Command will resolve once queried properties haven't changed for that number of frames.
cy.get('h1').eq(15).scrollIntoView()
cy.waitFrames({
subject: () => cy.get('h1').eq(15),
property: 'getBoundingClientRect.top',
frames: 20 // Wait for the property to be idle for 20 frames
}).then(([{ value }]) => {
cy.wrap(value).should('be.approximately', 0, 2) // Passes in any environment
})
You can also use it to just to wait for a property to be idle:
Cypress.Commands.add('waitForResize', () => {
cy.waitFrames({
subject: cy.window,
property: 'outerWidth',
frames: 20
})
})
cy.waitForResize()
cy.log('Resized!') // This is executed once outerWidth isn't changed for 20 frames.
Property | Default | Type | Description | Required |
---|---|---|---|---|
subject |
undefined | () => Chainable<T> | Chainable to watch for properties. | β |
property |
undefined | string | string[] | One or more properties to watch. | β |
frames |
20 | number | Number of frames to wait. | β |
timeout |
30 * 1000 | number | Timeout in milliseconds. | β |
A Cypress Promise which resolves to an array of objects (one for each property) or throws an error if timeout
is reached:
Property | Type | Description |
---|---|---|
subject |
AUTWindow | Document | HTMLElement | JQuery<HTMLElement> |
Subject yielded from subject option chainer. |
value |
string | number | null | undefined |
Property value at which the function resolved. |
property |
string |
Awaited property name. |
time |
DOMHighResTimestamp |
Time in ms that took to resolve since invoking. |
cy.waitFrames({
subject: cy.window
})
π‘ Use cy.window
to watch for window-only DOM properties like scrollY
or outerWidth
.
cy.waitFrames({
subject: cy.document
})
π‘ Use cy.document
to watch for DOM/CSS properties on the documentElement
such as clientWidth
, pointer-events
, overflow
etc.
cy.waitFrames({
subject: () => cy.get('a').eq(0) // or () => cy.get('.my-selector')
})
π‘ Use () => cy.get
to watch for DOM/CSS properties on any other HTMLElement.
cy.get
, make sure to pass a function which returns the chainable.
cy.waitFrames({
subject: cy.window,
property: 'scrollY',
frames: 10
})
cy.waitFrames({
subject: () => cy.get('.my-element'),
property: 'background-color', // or '--my-var'
frames: 10
})
π‘ Use kebab-case for CSS properties. getComputedStyle
is used internally to get the values.
cy.waitFrames({
subject: cy.window,
property: 'visualViewport.offsetTop'
})
cy.waitFrames({
subject: () => cy.get('.my-element'),
property: 'getBoundingClientRect.top'
})
Methods with arity greater than 0 are not supported, e.g. getAttribute('href')
.
You can watch for multiple properties as well:
cy.waitFrames({
subject: () => cy.get('.my-element'),
property: ['background-color', 'scrollTop', 'getBoundingClientRect.top'],
frames: 10
})
π‘ waitFrames
will resolve once all properties are idle.
MIT