I have a Nextjs project that is using Percy (with Cypress integration) to run visual tests. My project fetches images from a CMS. How can I make sure all images on the page have loaded before (taking the snapshot) or checking elements?
I have seen an example that checks for broken images
const brokenImages = []
cy.get('img')
.each(($el, k) => {
if ($el.prop('naturalWidth') === 0) {
const id = $el.attr('id')
const alt = $el.attr('alt')
const info = `${id ? '#' + id : ''} ${alt ? alt : ''}`
brokenImages.push(info)
cy.log(`Broken image ${k + 1}: ${info}`)
}
})
.then(() => {
// report all broken images at once
// enable the condition to see the thrown error
if (false && brokenImages.length) {
throw new Error(
`Found ${
brokenImages.length
} broken images\n${brokenImages.join(', ')}`,
)
}
})
and another one that checks a single image's natural width (which doesn't work in Nextjs
cy.get('#loads')
.should('be.visible')
.and('have.prop', 'naturalWidth')
.should('be.greaterThan', 0)
Am I going to have to just wait a set number of seconds and hope they have loaded?
The property naturalWidth
is native to javascript and should not be affected by the NextJs framework,
i.e any <img>
on a running web page should return a non-zero naturalWidth property.
However, the images may be lazy-loaded in which case .scrollIntoView()
may fix your problem.
I ran this test on a selection of Nextjs sample pages and they all passed
describe('nextjs img loading examples', () => {
const sites = [
'https://image-component.nextjs.gallery/',
'https://app-router-contentful.vercel.app/',
'https://next-blog-prismic.vercel.app/',
'https://next-blog-umbraco-heartcore.vercel.app/'
]
sites.forEach(site => {
it(`verifies images for ${site}`, () => {
cy.visit(site);
cy.get('img').each(img => {
cy.wrap(img)
.scrollIntoView()
.its('0.naturalWidth') // Note "0" extracts element from jQuery wrapper
.should('be.greaterThan', 0)
})
})
})
})
According to the examples by Gleb Bahmutov Cypress examples (v13.6.5) - Image Has Loaded (the source for your first code block)
To check if an image has successfully loaded, check its property naturalWidth.
It is only set if the browser downloaded and displayed the image.
Based on Gleb's code - adding a delay to the first image we can demo with simple before/after screenshots
HTML
<img
id="img1"
src=""
width="400"
height="50"
alt="Warming stripes"
/>
<img
id="img3"
width="40"
height="40"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
alt="red dot"
/>
<script>
setTimeout(() => {
const img = document.getElementById('img1')
img.src = 'https://glebbahmutov.com/images/warming-stripes.png'
}, 3000)
</script>
Test
cy.screenshot('before')
cy.get('img').each(img => {
cy.wrap(img)
.scrollIntoView()
.its('0.naturalWidth')
.should('be.greaterThan', 0)
})
cy.screenshot('after')
"Before" screenshot - img1 has not yet loaded
"After" screenshot - img1 has loaded