How to wait until all images have loaded before running Cypress test?

43 Views Asked by At

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?

1

There are 1 best solutions below

4
Aladin Spaz On BEST ANSWER

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)
      })
    })
  })
})

enter image description here


Have the images loaded?

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

enter image description here

"After" screenshot - img1 has loaded

enter image description here