cypresscypress-conditional-testingpercy

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


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?


Solution

  • 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