> ## Content Index
> Fetch the complete content index at: https://catalins.tech/llms.txt
> Use this file to discover other available public pages before exploring further.

# Compare Website Screenshots With Node.JS And Puppeteer
- URL: https://catalins.tech/compare-website-screenshots-nodejs-puppeteer/
- Published: 2022-10-26T14:01:41.000Z
- Updated: 2022-11-14T19:14:21.000Z
- Author: Dmytro Krasun
- Tags: Node, Developer Tools

In this tutorial, we will build a simple application that takes multiple screenshots of a webpage and detect the differences. But first, why would you want to do that?

You might want to test the webpage after you deploy new changes and compare the two versions. Or, perhaps you want to keep track of a page and want to be notified about changes.

## Take a webpage screenshot

Let's start implementing the application!

### Screenshot with Puppeteer

Puppeteer is a library that implements the Chrome DevTools Protocol to automate everything you can do in the browser. Puppeteer is not the only library that allows you to take screenshots of a webpage. You can also use Playwright, which supports most modern browsers. Puppeteer is oriented more towards headless Chrome and Chromium. Although it also supports Mozilla Firefox.

Open the terminal and install Puppeteer as follows:

```bash
npm i puppeteer

```

Now create a new file, `take.js,` and write the following piece of code:

```javascript
// take.js

'use strict';
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
    try {
      const page = await browser.newPage();
      await page.goto('https://catalins.tech/');

      await page.screenshot({ path: 'catalins.tech.png' });
    } catch (e) {
      console.log(e)
    } finally {
      await browser.close();
    }
})();

```

The above code opens the website `catalins.tech` and takes a screenshot of the page. Run the script with the following command:

```bash
node take.js

```

You should get this screenshot:

![A screenshot of catalins.tech website](https://cdn.hashnode.com/res/hashnode/image/upload/v1666710860448/qka3lb8q7.png)

Before going further, I want to highlight that the resulting screenshot might not look good on the Apple Retina displays. To fix it, set `deviceScaleFactor` to `2` for the viewport:

```javascript
// take.js

'use strict';
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://catalins.tech/');

    await page.setViewport({width: 800, height: 600, deviceScaleFactor: 2});

    await page.screenshot({ path: 'catalins.tech_@2.png' });
   } catch (e) {
     console.log(e)
   } finally {
     await browser.close();
   }
})();

```

Now, if you rerun the script, you should get the following picture:

![A screenshot with a higher device scale ratio for the catalins.tech website](https://cdn.hashnode.com/res/hashnode/image/upload/v1666710964027/lBLDqIdmX.png)

If you compare the previous screenshot with this one, you can see that the above one has a higher quality.

### Or with screenshot API

In some cases, Puppeteer might not be the best tool for the job. If you need to take many screenshots of different sites, you will need to scale, monitor, and support Puppeteer servers. It can become costly very quickly.

In that case, you can use any [screenshot API](https://screenshotone.com/the-best-screenshot-api/) to take screenshots for you and offload the burden of dealing with Puppeteer infrastructure.

I am the author of [ScreenshotOne](https://screenshotone.com/) (a screenshot API). I fixed and handled many corner cases when dealing with Puppeteer and simplified it as a simple API.

You can use the SDK or send regular HTTP requests. I will show an example of taking webpage screenshots with the ScreenshotOne SDK. Let's first install it:

```bash
npm install screenshotone-api-sdk --save

```

Open the `take.js` file and write the following code:

```javascript
// take.js 

const fs = require('fs');
const screenshotone = require('screenshotone-api-sdk');
(async () => {
  // create API client 
  // sign up at https://screenshotone.com/ to get your access and secret keys
  const client = new screenshotone.Client("<your access key>", "<your secret key>");

  // set up options
  const options = screenshotone.TakeOptions
    .url("https://catalins.tech")
    .viewportWidth(800)
    .viewportHeight(600)
    .deviceScaleFactor(2);

  const imageBlob = await client.take(options);
  const buffer = Buffer.from(await imageBlob.arrayBuffer());
  fs.writeFileSync("catalins.tech_screenshotone_@2.png", buffer)
  // the screenshot is stored in the catalins.tech_screenshotone_@2.png file
})();

```

The result, as you might expect, is the same:

![A screenshot of the catalins.tech taken with ScreenshotOne.com](https://cdn.hashnode.com/res/hashnode/image/upload/v1666711032343/pPHb4f6xS.png)

## Compare screenshots with Pixelmatch

Once we have the screenshots, we can start comparing them. There is a wide variety of image comparison methods due to nuances in the image formats and the level of noise you can tolerate.

I picked up the most straightforward library to compare images — [pixelmatch](https://www.npmjs.com/package/pixelmatch).

But before comparing, we'll modify the webpage by removing the navigation links. Then we'll take another screenshot.

```javascript
// take.js

'use strict';
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://catalins.tech/');

    // remove the header links
    await page.addStyleTag({content: '.blog-sub-header { visibility: hidden !important; }'});

    await page.screenshot({ path: 'catalins.tech_without_header_links.png' });
    } catch (e) {
      console.log(e)
    } finally {
      await browser.close();
    }
})();

```

Running the code results in the following image:

![A screenshot of the catalins.tech website without the header links](https://cdn.hashnode.com/res/hashnode/image/upload/v1666711061113/8jEzqTOd8.png)

As you can see, the navigation links are gone. The code should detect the difference when we compare the above image with the previous one (with navigation links).

Now, let's install the `pixelmatch` library and detect the differences:

```bash
npm install pixelmatch

```

Create a new file, `diff.js,` and write the following code:

```javascript
// diff.js

'use strict';

const fs = require('fs');
const PNG = require('pngjs').PNG;
const pixelmatch = require('pixelmatch');

const img1 = PNG.sync.read(fs.readFileSync('catalins.tech.png'));
const img2 = PNG.sync.read(fs.readFileSync('catalins.tech_without_header_links.png'));
const { width, height } = img1;
const diff = new PNG({ width, height });

const result = pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 });

console.log(`Different pixels: ${result}`);

fs.writeFileSync('diff.png', PNG.sync.write(diff));

```

Then run the script:

```bash
node diff.js

```

The output is:

```
Different pixels: 1875

```

And the resulting image with the difference:

![The difference of the screenshots](https://cdn.hashnode.com/res/hashnode/image/upload/v1666711079703/PZ3EqcBJ5.png)

The `threshold` parameter allows you to tune the sensitivity of the comparison: the less the threshold, the more sensitive the comparison, and vice versa.

To act on the detected difference in the screenshots, you need to use the result with the number of pixels. If there is a difference, you can send an email, push notification or apply any logic you wish.

You can combine all the code in one file as follows:

```javascript
// take_and_diff.js

'use strict';

const puppeteer = require('puppeteer');
const fs = require('fs');
const PNG = require('pngjs').PNG;
const pixelmatch = require('pixelmatch');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://catalins.tech/');

    await page.screenshot({ path: 'catalins.tech.png' });

    // remove the header links, 
    // but in the real case, you would use an old or a fresh screenshot of the same site to compare
    await page.addStyleTag({ content: '.blog-sub-header { visibility: hidden !important; }' });
    await page.screenshot({ path: 'catalins.tech_without_header_links.png' });

    const img1 = PNG.sync.read(fs.readFileSync('catalins.tech.png'));
    const img2 = PNG.sync.read(fs.readFileSync('catalins.tech_without_header_links.png'));
    const { width, height } = img1;
    const diff = new PNG({ width, height });

    const result = pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 });
    if (result > 0) {
      console.log(`Different pixels: ${result}`);
      // send an email with the difference 
      // or perform any other kind of logic
      fs.writeFileSync('diff.png', PNG.sync.write(diff));
    } else {
      console.log('The screenshots are the same');
    }
  } catch (e) {
    console.log(e)
  } finally {
    await browser.close();
  }
})();

```

## Summary

The quickest way to take screenshots of a webpage and compare them is to use the:

- `Puppeteer` library for screenshots
- `Pixelmatch` for detecting differences.

> If you are looking to learn Node.js or get better at it, check the [Complete Node.js Developer course](https://academy.zerotomastery.io/a/aff%5Fdcjk8l8t/external?affcode=441520%5Fzj%5Ftadya).