Check Broken Images Using Cypress

Check if any images on the page fail to load.

There is nothing more embarrassing than a broken image link right there in the middle of the page.

Oops, some images did not make it

Can we catch broken images using Cypress tests?

🎁 The source code for this blog post can be found in my recipe "Image Has Loaded" at glebbahmutov.com/cypress-examples.

📺 Watch this example in the video Check Broken Images On The Page.

Check a single image

Imagine a single <img> element. It loads the image using its src attribute. The source could be a link URL, or a data:... encoded image. Even if the URL is valid, the image itself could be corrupted and the browser might fail to decode it. Thus we want to check if the image loads, rather than simply checking the URL. A good way to check is to confirm the naturalWidth or naturalHeight of the img element. For successfully loaded and decoded images it should be positive.

1
2
3
4
5
6
<img
id="loads"
src="https://glebbahmutov.com/images/warming-stripes.png"
width="400"
height="50"
/>
1
2
3
4
cy.get('#loads')
.should('be.visible')
.and('have.prop', 'naturalWidth')
.should('be.greaterThan', 0)

The test verifies the image loads

If the src URL points at a non-existent image, the test catches it.

The test catches a broken image

📺 You can find this example explained in the video Check If An Image Loads.

Multiple images

Now let's check all images on the page. Rather than failing a test immediately, we will collect all broken image information before reporting it all at once. This allows us to judge how broken the page is and maybe even debug it faster. Imagine multiple images on the page. Some elements load the image from a remote URL, others use data:... encoding, or even an SVG element.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<img
id="img1"
src="https://glebbahmutov.com/images/warming-stripes.png"
width="400"
height="50"
alt="Warming stripes"
/>
<!-- image without the "src" attribute -->
<img id="img2" width="40" height="40" alt="Second image" />
<!-- image with data source -->
<img
id="img3"
width="40"
height="40"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
alt="red dot"
/>
<!-- image with data SVG -->
<img
src='data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve" height="100px" width="100px">
<g>
<path d="M28.1,36.6c4.6,1.9,12.2,1.6,20.9,1.1c8.9-0.4,19-0.9,28.9,0.9c6.3,1.2,11.9,3.1,16.8,6c-1.5-12.2-7.9-23.7-18.6-31.3 c-4.9-0.2-9.9,0.3-14.8,1.4C47.8,17.9,36.2,25.6,28.1,36.6z"/>
<path d="M70.3,9.8C57.5,3.4,42.8,3.6,30.5,9.5c-3,6-8.4,19.6-5.3,24.9c8.6-11.7,20.9-19.8,35.2-23.1C63.7,10.5,67,10,70.3,9.8z"/>
<path d="M16.5,51.3c0.6-1.7,1.2-3.4,2-5.1c-3.8-3.4-7.5-7-11-10.8c-2.1,6.1-2.8,12.5-2.3,18.7C9.6,51.1,13.4,50.2,16.5,51.3z"/>
<path d="M9,31.6c3.5,3.9,7.2,7.6,11.1,11.1c0.8-1.6,1.7-3.1,2.6-4.6c0.1-0.2,0.3-0.4,0.4-0.6c-2.9-3.3-3.1-9.2-0.6-17.6 c0.8-2.7,1.8-5.3,2.7-7.4c-5.2,3.4-9.8,8-13.3,13.7C10.8,27.9,9.8,29.7,9,31.6z"/>
<path d="M15.4,54.7c-2.6-1-6.1,0.7-9.7,3.4c1.2,6.6,3.9,13,8,18.5C13,69.3,13.5,61.8,15.4,54.7z"/>
<path d="M39.8,57.6C54.3,66.7,70,73,86.5,76.4c0.6-0.8,1.1-1.6,1.7-2.5c4.8-7.7,7-16.3,6.8-24.8c-13.8-9.3-31.3-8.4-45.8-7.7 c-9.5,0.5-17.8,0.9-23.2-1.7c-0.1,0.1-0.2,0.3-0.3,0.4c-1,1.7-2,3.4-2.9,5.1C28.2,49.7,33.8,53.9,39.8,57.6z"/>
<path d="M26.2,88.2c3.3,2,6.7,3.6,10.2,4.7c-3.5-6.2-6.3-12.6-8.8-18.5c-3.1-7.2-5.8-13.5-9-17.2c-1.9,8-2,16.4-0.3,24.7 C20.6,84.2,23.2,86.3,26.2,88.2z"/>
<path d="M30.9,73c2.9,6.8,6.1,14.4,10.5,21.2c15.6,3,32-2.3,42.6-14.6C67.7,76,52.2,69.6,37.9,60.7C32,57,26.5,53,21.3,48.6 c-0.6,1.5-1.2,3-1.7,4.6C24.1,57.1,27.3,64.5,30.9,73z"/>
</g>
</svg>'
alt="Basketball"
/>
<!-- image with broken data SVG -->
<img
src="data:image/svg+xml;utf8,<svg></svg>"
alt="Basketball 2"
/>

Let's check all images at once.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
if (brokenImages.length) {
throw new Error(
`Found ${
brokenImages.length
} broken images\n${brokenImages.join(', ')}`,
)
}
})

Two image elements did not load

By reporting id and alt attribute we can point the team to the right direction. We could also use cyclope plugin to save the entire page as static HTML snapshot to debug the markup.