ashotwrapper

AShotWrapper

back to the repository

What is this?

The AShot is WebDriver screenshot library in Java. My com.kazurayam.ashotwrapper.AShotWrapper is a thin wrapper class of the AShot library.

The com.kazurayam.ashotwrapper.AShotWrapper class simplifies using AShot in the Visual Inspection in Katalon Studio project.

What’s ++

The com.kazurayam.ashotwrapper.AShotWrapper class provides some additional features that the AShot library doesn’t.

  1. AShotWrapper optionally enables you to save screenshots in JPEG format while specifying compression quality. This is helpful in some cases to reduce the size of output files.

  2. AShotWrapper optionally enables you to “censor” screenshots. You can paint the HTML elements in the screenshot with grey color. Effectively the painted HTML elements are ignored when you perform visual comparisons.

How to use the AShotWrapper

The artifact is available at the Maven Central repository:

You can use Gradle or Maven to use this library in your Java/Groovy project. Assuming you use Gradle, you just want to wraite your `build.gradle:

implementation group: 'com.kazurayam', name: 'ashotwrapper', version: '0.2.0'

If you want to use this library in your Katalon Studio project, you want to download 2 jars into the Drivers folder of your Katalon Studio project.

Javadoc

Javadoc is here

Sample codes

Here I will present a JUnit5 test class com.kazurayam.ashotwrapper.samples.AShotWrapperDemo to demonstrate how to use the AShotWrapper class.

The test class starts with the package statement, import statements, class declaration and some common boilerplate methods; it is as follows:

package com.kazurayam.ashotwrapper.samples;

import com.kazurayam.ashotwrapper.AShotWrapper;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.*;

public class AShotWrapperDemo {

    private static final Path outputDir =
            Paths.get(".").resolve("docs/samples")
                    .resolve(AShotWrapperDemo.class.getName());
    private static WebDriver driver;
    private static final int timeout = 500;
    private AShotWrapper.Options options = null;

    @BeforeAll
    static void beforeAll() throws IOException {
        Path dir = outputDir;
        if (Files.exists(dir)) {
            // delete the directory to clear out using Java8 API
            Files.walk(dir)
                    .sorted(Comparator.reverseOrder())
                    .map(Path::toFile)
                    .forEach(File::delete);
        }
        Files.createDirectories(dir);
    }

    @BeforeEach
    void beforeEach(){
        WebDriverManager.chromedriver().setup();
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--no-sandbox");
        options.addArguments("--disable-dev-shm-usage");
        options.addArguments("--headless");
        driver = new ChromeDriver(options);
        driver.manage().timeouts().implicitlyWait(timeout, TimeUnit.MILLISECONDS);
        driver.manage().window().setSize(new Dimension(800, 400));
        //
        float dpr = AShotWrapper.DevicePixelRatioResolver.resolveDPR(driver);
        this.options = new AShotWrapper.Options.Builder().devicePixelRatio(dpr).build();
    }

    @AfterEach
    void tearDown(){
        if (driver != null) {
            driver.quit();
        }
    }

Now I will show each test methods that demonstrates how to use AShotWrapper, with resulting image files.

Save a screenshot of the entire page in PNG

The following code takes a screenshot of entire page view of the target URL, save the image in a PNG file.

    @Test
    void test_saveEntirePageImage() throws IOException {
        driver.navigate().to("https://www.iana.org/domains/reserved");
        File file = outputDir.resolve("test_saveEntirePageImage.png").toFile();
        AShotWrapper.saveEntirePageImage(driver, file);
        assertTrue(file.exists());
    }

OUTPUT: entire page screenshot in PNG

Save a screenshot of the current viewport in PNG

The following code takes a screenshot of current viewport of the target web page in the browser (not the entire page screenshot), save the image in a PNG file.

    @Test
    void test_savePageImage() throws IOException {
        driver.navigate().to("https://www.iana.org/domains/reserved");
        File file = outputDir.resolve("test_savePageImage.png").toFile();
        AShotWrapper.savePageImage(driver, file);
        assertTrue(file.exists());
    }

OUTPUT: current viewport screenshot in PNG

Save a screenshot of an element in the page in PNG

You can select a single HTML element in the target web page, take the screenshot of the element, and save the image into a PNG file.

    @Test
    void test_takeElementImage() throws IOException {
        driver.navigate().to("http://example.com");
        BufferedImage image = AShotWrapper.takeElementImage(driver,
                By.xpath("//body/div"),
                options);
        assertNotNull(image);
        File file = outputDir.resolve("test_takeWebElementImage.png").toFile();
        ImageIO.write(image, "PNG", file);
        assertTrue(file.exists());
    }

OUTPUT: element screenshot in PNG

Save a screenshot of the entire page in JPEG

You can save screenshot images into files in JPEG format. In some cases, a screenshot in JPEG of a web page can be much smaller than PNG.

    @Test
    void test_saveEntirePageImageAsJpeg() throws IOException {
        driver.navigate().to("https://www.iana.org/domains/reserved");
        File file = outputDir.resolve("test_saveEntirePageImageAsJpeg.jpg").toFile();
        AShotWrapper.saveEntirePageImageAsJpeg(driver, file, 0.7f);
        assertTrue(file.exists());
    }

OUTPUT: entire page screenshot in JPEG

You can also take screenshot of current viewport and selected HTML element in JPEG as well.

Study how to reduce file size of screenshots using JPEG format

Selenium WebDriver supports taking screenshot of the browser window. See tutorials. WebDriver produces files always in PNG format. Sometimes, screenshots in PNG format can be very large in byte size. For example, this sample screenshot in PNG is as large as 6.5 Mega bytes. If you are going to take many screenshots in your testing project, the size of screenshot images matters. Large image files are difficult to manage and utilize. So I want to make the screenshot image files as small as possible. But how to?

I found some libraries that compress a PNG file into another PNG file of smaller size, for example Pngquant. But I do not like to depend on those external libraries. I want a solution that I can use on top of Java8. A well-know resolution is to save screenshots in JPEG, not PNG, while specifying compression quality.

I have written a method writeJPEG which does this. With this method, AShotWrapper can save any BufferedImage object into JPEG file which specifying compression quality like 1.0f, 0.9f, 0.8f, 0.7f, …​ , 0.1f.

    /**
     * write a BufferedImage object into a file in JPEG format with some compression applied
     *
     * @param image BufferedImage
     * @param file File
     * @param compressionQuality [0.0f, 1.0f]
     * @throws IOException when some io failed
     */
    public static void writeJPEG(BufferedImage image, File file, float compressionQuality)
            throws IOException {
        Objects.requireNonNull(image);
        Objects.requireNonNull(file);
        if (compressionQuality < 0.1f || 1.0f < compressionQuality) {
            throw new IllegalArgumentException("compressionQuality must be in the range of [0.1f, 1.0f]");
        }
        ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
        ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
        jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        jpgWriteParam.setCompressionQuality(compressionQuality);
        //
        ImageOutputStream outputStream = new FileImageOutputStream(file);
        jpgWriter.setOutput(outputStream);
        IIOImage outputImage =
                new IIOImage(removeAlphaChannel(image), null, null);
        jpgWriter.write(null, outputImage, jpgWriteParam);
        jpgWriter.dispose();
    }

In theory, the smaller the compression quality is, we can expect the resulting JPEG file will have smaller size at the cost of poorer quality of image view.

Practically, how large the screenshots of web pages will be in PNG, in JPEG with 1.0f, 0.9f, …​ , 0.1f? Different design of web pages may result different file sizes. My ultimate question is, given a URL to take screenshot, which format should I use: PNG or JPEG? If I choose JPEG, then what value of compression quality should I specify?

In order to answer to the question, I have created a JUnit5 test, named FileSizeTest. This test targets 3 public URL, take screenshots in PNG and JPEG with varying compression quality; the test compiles tables where you can find how large in % each JPEG files are against the baseline PNG file.

Web page with lots of photos

A web page that is composed of lots of eye-catching photos

File Quality Size(bytes) % to PNG

offermanwoodshop.com.png

1.0

6,588,545

100%

offermanwoodshop.com-100.jpg

1.0

3,041,837

47%

offermanwoodshop.com-90.jpg

0.9

1,187,056

19%

offermanwoodshop.com-80.jpg

0.8

843,636

13%

offermanwoodshop.com-70.jpg

0.7

692,684

11%

offermanwoodshop.com-60.jpg

0.6

595,758

10%

offermanwoodshop.com-50.jpg

0.5

531,487

9%

offermanwoodshop.com-40.jpg

0.4

469,988

8%

offermanwoodshop.com-30.jpg

0.3

404,645

7%

offermanwoodshop.com-20.jpg

0.2

324,059

5%

offermanwoodshop.com-10.jpg

0.1

223,726

4%

Photo-rich page results in the screenshots of very large size. PNG is surprisingly lager than JPEG of the compression quality 1.0. PNG is not suitable for photo-rich pages. You should save the screenshots of photo-rich pages in JPEG, seriously.

Text-only web page

Text-rich page without eye-catching photos

File Quality Size(bytes) % to PNG

www.fsa.go.jp.png

1.0

295,798

100%

www.fsa.go.jp-100.jpg

1.0

569,674

192%

www.fsa.go.jp-90.jpg

0.9

280,978

95%

www.fsa.go.jp-80.jpg

0.8

214,878

73%

www.fsa.go.jp-70.jpg

0.7

182,895

62%

www.fsa.go.jp-60.jpg

0.6

161,678

55%

www.fsa.go.jp-50.jpg

0.5

146,857

50%

www.fsa.go.jp-40.jpg

0.4

133,145

46%

www.fsa.go.jp-30.jpg

0.3

117,464

40%

www.fsa.go.jp-20.jpg

0.2

98,488

34%

www.fsa.go.jp-10.jpg

0.1

73,360

25%

Text-rich page results in the screenshots of small size. PNG is smaller than JPEG of the compression quality 1.0. PNG is suitable for text-rich pages.

Ordinary web page with text and a few images

Mixture of texts and small number of images. There are a lot of web sites on the net like this.

File Quality Size(bytes) % to PNG

community.developer.atlassian.com.png

1.0

582,684

100%

community.developer.atlassian.com-100.jpg

1.0

1,288,390

221%

community.developer.atlassian.com-90.jpg

0.9

562,293

97%

community.developer.atlassian.com-80.jpg

0.8

410,043

71%

community.developer.atlassian.com-70.jpg

0.7

340,999

59%

community.developer.atlassian.com-60.jpg

0.6

295,414

51%

community.developer.atlassian.com-50.jpg

0.5

264,916

46%

community.developer.atlassian.com-40.jpg

0.4

236,851

41%

community.developer.atlassian.com-30.jpg

0.3

206,214

36%

community.developer.atlassian.com-20.jpg

0.2

169,583

30%

community.developer.atlassian.com-10.jpg

0.1

121,342

21%

PNG has smaller file size than JPEG of the compression quality 1.0. The JPEG of compression quality 0.9f is quite similar to PNG. Yes, JPEG could tuned to be smaller than PNG but is not so much significant.

How to ignore `<div>`s that keep on changing its view

Many websites contain <div> elements that display commercial advertisements that keep on changing dynamically everytime inquired. These dynamic HTML elements disturb visual comparison of 2 screenshots taken at different timings. These dynamic `<div>`s will result significant % of image difference. However, I am not interested in the image difference caused by the advertisements. Therefore, I want to ignore those dynamic HTML elements for more accurate image comparison. How can I do it?

You can optionally gray paint the square regions of the selected HTML elements in the screenshot images. I would call it : Censoring (検閲、塗りつぶし). See an example of censored page image as follows:

    @Test
    void test_censor_on_insensitive_page() throws IOException {
        driver.manage().window().setSize(new Dimension(1024, 600));
        driver.navigate().to("http://devadmin.kazurayam.com/");
        // no censor
        File file1 = outputDir.resolve("no_censor.png").toFile();
        AShotWrapper.saveEntirePageImage(driver, file1);
        // with censor
        AShotWrapper.Options options =
                new AShotWrapper.Options.Builder()
                        .addIgnoredElement(
                                By.xpath("//span[@id='clock']"))
                        .build();
        File file2 = outputDir.resolve("with_censor.png").toFile();
        AShotWrapper.saveEntirePageImage(driver, options, file2);
    }
Censoring example
original with censoring

no censor

with censor

Censoring example

Please find the clock area is painted gray. if you do image comparison of 2 screenshots both of which are censored, the painted area would be the same. It would no longer cause any significant image difference.