Selenium — Visual Regression Testing

Minhazul Billah
5 min readAug 29, 2023

Making sure the user experience is seamless and consistent is crucial in the always changing world of web development. Users engage visually with websites and programs, which are more than simply lines of code. User pleasure and trust can be significantly impacted by a single unintentional aesthetic alteration. Visual Regression Testing serves as an essential safety net in this situation.

A branch of software testing called visual regression testing looks for visual differences across various web application versions. It deals with the problem of preserving a constant visual appearance during numerous releases, upgrades, and user interactions. Developers and QA teams may quickly spot and fix unintentional visual changes that might have happened as a result of coding changes by automating the process of taking and comparing screenshots.

Imagine a situation where a seemingly simple code change modifies the distance between items or modifies the button’s color. Traditional functional testing may miss these changes, but they can have a big impact on the application’s overall aesthetics and usability. Visual regression testing is the watchful guardian who makes sure each pixel on the screen remains as intended, boosting user pleasure and the app’s credibility.

We will explore Java and Selenium-based Visual Regression Testing in this tutorial. We’ll look at how these technologies work together to offer a potent method for automating the identification of visual inconsistencies, allowing programmers to spot problems early and maintain a high-quality user experience. Understanding and using Visual Regression Testing can be a game-changer in your web development journey, whether you’re an experienced QA expert or a developer looking to improve the quality of your applications.

Setting Up the Environment

Make that Java, Selenium WebDriver, and an appropriate browser driver (such GeckoDriver for Firefox) are properly configured before starting to write any code. In this example, the setup of the driver is handled by WebDriverManager.

WebDriverManager.firefoxdriver().clearDriverCache().setup();

Capturing Screenshots

In the code, we capture full-page screenshots of web pages using Selenium’s TakesScreenshot interface. This enables us to save screenshots for further comparisons.

TakesScreenshot screenshotDriver = (TakesScreenshot) driver;
File screenshotFile = screenshotDriver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(screenshotFile, screenShotPath.toFile());

Processing Link Chunks

we process links and their corresponding screenshots to identify discrepancies.

for (String link : linksChunk) {
driver.get(link);
takeScreenshot(driver, link, folderPath);
// ...
}

Parallel Execution For Taking Screenshots using Multi-Threading

To expedite the process, we employ parallel execution by dividing the links into chunks and running multiple threads concurrently.

ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

for (int i = 0; i < THREAD_COUNT; i++) {
final int chunkIndex = i;
executor.execute(() -> {
// Process linksChunk concurrently
// ...
latch.countDown();
});
}
executor.shutdown();
latch.await();

Handling Missed Links

Inevitably, some links might not be processed due to various reasons. The code also handles missed links and retries processing.

List<String> missedLinks = getMissedLinksFromDatabase(tableName);
if (!missedLinks.isEmpty()) {
processMissedLinks(options, folderPath, tableName);
}

Database Integration

The code integrates with a SQLite database to store links and track missed links.

// Insert links to the database
insertLinksToDatabase(links, tableName);

// Retrieve links from the database
links = getLinksFromDatabase(tableName);

Now that we have finished taking screenshots from list of urls, so it’s time to compare these screenshots.

Comparison Model Setup

A FileWriter to store difference information and hash maps to track differences and height variations are two key components that the code initializes.

static FileWriter fileWriter;
static {
try {
fileWriter = new FileWriter("Difference.txt");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static PrintWriter printWriter = new PrintWriter(fileWriter);
static HashMap<String, Float> diffMap = new HashMap<>();
static HashMap<String, Integer> heightDiffMap = new HashMap<>();

Test Method — Comparison

The process of comparing images is orchestrated by the comparison test method. Two folders of photographs are compared, differences are found, and the resulting images are saved.

@Test
public void comparison() throws IOException {
String folder1 = "ProdShots";
String folder2 = "LowerShots";
String resultFolder = "ResultedImage";

// ...

for (File file1 : files1) {
String fileName = file1.getName().replaceFirst("[.][^.]+$", "");
String folder2FilePath = folder2 + "/" + file1.getName();
File file2 = new File(folder2FilePath);

if (file2.exists()) {
BufferedImage img1 = ImageIO.read(file1);
BufferedImage img2 = ImageIO.read(new File(folder2 + "/" + file1.getName()));

BufferedImage diffImg = getDifferenceImage(img1, img2, fileName);
String resultFileName = resultFolder + "/Resulted" + fileName + ".png";
ImageIO.write(diffImg, "png", new File(resultFileName));
}
}

// ... (Process difference details)
printWriter.close();
}

Helper Methods for Comparison

The code provides two essential helper methods, getDifferencePercent and getDifferenceImage, which facilitate image comparison.

@Test
public static float getDifferencePercent(BufferedImage img1, BufferedImage img2) throws IOException {
int width = img1.getWidth();
int height = img1.getHeight();

long diff = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
diff += pixelDiff(img1.getRGB(x, y), img2.getRGB(x, y));
}
}
long maxDiff = 3L * 255 * width * height;

return (float) (100.0 * diff / maxDiff);
}

The getDifferencePercent method calculates the difference percentage between two images. It iterates over corresponding pixels in both images, calculating the difference. The calculated diff is then normalized against the maximum possible difference, resulting in a percentage difference.

@Test
public static BufferedImage getDifferenceImage(BufferedImage img1, BufferedImage img2, String fileName) throws IOException {
// ...

BufferedImage diffImg = new BufferedImage(img1.getWidth(), img1.getHeight(), BufferedImage.TYPE_INT_ARGB);

// Compare the pixels of the two images
float difference = getDifferencePercent(img1, img2);
diffMap.put(fileName, difference);

for (int x = 0; x < img1.getWidth(); x++) {
for (int y = 0; y < img1.getHeight(); y++) {
int rgb1 = img1.getRGB(x, y);
int rgb2 = img2.getRGB(x, y);

// If the pixels are not the same, set the pixel in the difference image to red
if (rgb1 != rgb2) {
diffImg.setRGB(x, y, 0xFFFF0000); // Red pixel
} else {
diffImg.setRGB(x, y, rgb1);
}
}
}

return diffImg;
}

The getDifferenceImage method generates a difference image by comparing corresponding pixels between two images. It calculates the percentage difference using getDifferencePercent, and then iterates through the images' pixels. If the pixels at the same position differ, the corresponding pixel in the difference image is set to red, indicating the difference.

Reporting, Test Method: genReport

The genReport test method orchestrates the entire process of generating the CSV report. It reads data from the "Difference.txt" file, processes it, and then writes the formatted information into a CSV file named "Report.csv"

@Test
public void genReport() throws IOException {
File reportFile = new File("Difference.txt");
BufferedReader reportReader = new BufferedReader(new FileReader(reportFile));
CSVPrinter printer = new CSVPrinter(new FileWriter("Report.csv"), CSVFormat.DEFAULT);
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy' 'HH:mm:ss:S");

String regex = "^([^:]+):\\s+(?:[^=]+?)=([\\d\\.]+),\\s+(?:[^=]+?)=([\\d\\.]+)";

printer.printRecord("Filename", "Difference Percentage", "Height Mismatch", "Timestamp");

String line;
while ((line = reportReader.readLine()) != null) {
Matcher matcher = Pattern.compile(regex).matcher(line);
if (matcher.matches()) {
String fileName = matcher.group(1).trim();
String difference = matcher.group(2).trim();
String heightMismatch = matcher.group(3).trim();
String timeStamp = simpleDateFormat.format(timestamp);
printer.printRecord(fileName, difference, heightMismatch, timeStamp);
}
}

reportReader.close();
printer.close();
}

Each of these code snippets serves as an example of a distinct aspect of software testing automation. The value of automation in improving testing efficiency, accuracy, and consistency is demonstrated by these techniques, which range from visual regression testing with Selenium Java to automating picture comparison and generating test results. You may improve your testing methods and guarantee the delivery of high-quality software solutions that satisfy user expectations and standards by comprehending and putting these strategies into effect.

Connect with me on LinkedIn: https://www.linkedin.com/in/minhazbillah

--

--