Or press ESC to close.

A different angle of image verification

Oct 30th 2022 5 min read
easy
python3.7.6
ui
web
kotlin1.6.21

Nowadays, it's almost impossible to open a website that doesn't contain a bunch of images. They make websites visually appealing and often provide valuable information related to the website's content. But why are they rarely verified?

If you ever worked with an automation framework in the production environment, you are probably aware that images don't get as much attention as some other elements on a website. One of the main reasons is that other elements like buttons and input fields get more user interactions and thus are considered to be more important. Usually, if there is any kind of verification around them then it's in a form of verifying their presence on the page. But is that enough?

Methods like Selenium's isDisplayed() check the presence of an element identified by a specific locator strategy (ID, XPath, CSS Selector...). If Selenium finds an element on the page that contains the same ID attribute that we specified, its isDisplayed() method will return true indicating that our element exists. What happens in case a developer puts the correct ID value for the img tag, but links the wrong image to it?

Clearly the right image won't be visible on the website. Maybe there will be no image at all, but the img tag will still live in the DOM. This creates a problem, since Selenium verifies an elemenent's presence by checking that the element's height and width are greater than 0px, that the visibility is not set to "hidden" and that the display property is not set to "none". In most real-world cases these properties have custom values that make the image occupy some space on the site. This means that the isDisplayed() method will return true even if no image gets loaded on the page.

Luckily, there is an easy solution for this. Images are essentially files, which means that we can read their content and compare their values.

We can achieve this assertion in Python using the PIL library, where the first step will be opening the actual and expected image:

              
from PIL import Image


actual_image = Image.open('images/original.png')
expected_image = Image.open('images/original_copy.png')
            

This will only open the files. The actual image data will be read once we try to process it. We will do this by calling the getdata() method on our variables. getdata() will return a sequence object containing pixel values.

              
actual_image_data = actual_image.getdata()
expected_image_data = expected_image.getdata()
            

Now before we compare the image data we just got, we need to cast it into a list because getdata() method returned a PIL data type that only supports certain sequence operations.

              
actual_image_data = list(actual_image_data)
expected_image_data = list(expected_image_data)
            

And the only step that remains now is to compare the two lists. We will do this with the help of Python's unittest module. It contains a method called assertEqual() that does exactly what its name says.

              
assertEqual(
  list(actual_image_data),
  list(expected_image_data)
))
            

Now, there are a couple of ways to run this code. The easiest one would be to wrap it in a test method a put it inside a test class. And after getting rid of some duplicate code, the entire example would look like this:

              
import unittest
from PIL import Image


class ImageTesting(unittest.TestCase):

  def test_image_by_data_value(self):
    actual_image = Image.open('images/original.png')
    expected_image = Image.open('images/original_copy.png')
    self.assertEqual(
      list(actual_image.getdata()),
      list(expected_image.getdata())
    )
            

Since Python utilizes a lot of libraries that make custom tasks look easy, we will also implement this assertion in Kotlin, to show that it can be easily achieved in other languages too.

The flow is pretty similar. This time, we will be using the Files class, which contains a static method for reading bytes from a file. After we read all bytes from our files, we will convert them into lists and make a comparison:

              
import org.testng.Assert.assertEquals
import org.testng.annotations.Test
import java.nio.file.Files
import java.nio.file.Path
                
                
class ImageTesting {
                
  @Test
  fun testImageByDataValue() {
      val actualImage = Files.readAllBytes(Path.of("original.png")).toList()
      val expectedImage = Files.readAllBytes(Path.of("original_copy.png")).toList()
      assertEquals(actualImage, expectedImage)
  }
}
            

On the following Github link, you can find the provided examples with images used for testing positive as well as negative scenarios. You will see that the images only differ in one pixel, which is enough for them not to be identical.

As a last note, since we talked about Selenium and the way it checks if elements are displayed, if in our case we assume that we have the image we want to verify (the expected image), we still need to get the actual image.

For that, in Python 3, we can use the request library:

              
import urllib.request

imageURL = "https://randomwebsite.com/images/image.jpg"
urllib.request.urlretrieve(imageURL, "downloadPath/image.jpg")   

            

And in Kotlin, we can use streams:

              
try {
  val inputStream = URL("pathToImageSource").openStream()
  Files.copy(inputStream, Paths.get("pathToSaveTheImage/image.png"))
} catch (exception: IOException) {
  //custom exception handling
}

            

That's it. Try it out. 🙂