Take a better picture with puppeteer.
Tags: puppeteer, javascript, typescript
Published: 2019-11-10 10:07:52

---
title: Take a better picture with puppeteer.
published: true
description: 
tags: #puppeteer #javascript #typescript
---

This article is also available on [medium](https://medium.com/@TETRA2000/take-a-better-picture-with-puppeteer-e56e83ed6b38).

# Take a better picture with puppeteer.

Puppeteer is awesome. It allows me to capture screenshots of web a lot easier.

But sometimes I can't get intended result because of animations within pages.
For example, I'll write these kind of code when I'm trying to capture [SFMOMA](https://www.sfmoma.org/).

```typescript
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://www.sfmoma.org/", {
  waitUntil: "networkidle0",
});
await page.screenshot({ path: "example.png" });
```

This will results in this image.

![Screenshot before animation](https://thepracticaldev.s3.amazonaws.com/i/cuj750ory87vavrdtdas.png)


This capture is taken before animation have finished.

Expected result is this.

How should I avoid this?

## Plan 1: Sleep fixed time.

Most easiest way is just sleep specific time. This is useful when I know duration of animations.

```typescript
function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

await page.goto("https://www.sfmoma.org/", {
  waitUntil: "networkidle0",
});
sleep(1000);
await page.screenshot({ path: "example.png" });
```

But usually I have to specify longer time than actual animation for safe. This will problematic when I'm using puppeteer on CI. I don't want to waste CI's build time. 

## Plan 2: Speed up!!

Another way is to speed up CSS animations. I can fast forward them by using DevTools Protocol.

```javascript
await page._client.send('Animation.setPlaybackRate', { playbackRate: 2 });
```

[Virtually fast forward to take a screenshot after animations #453](https://github.com/GoogleChrome/puppeteer/issues/453#issuecomment-324310130)

[Animation.setPlaybackRate](https://chromedevtools.github.io/devtools-protocol/tot/Animation#method-setPlaybackRate)


## Plan 3: Find the page movement

Finally I'll find page movement by capturing multiple screenshots.
I use [blink-diff](https://github.com/yahoo/blink-diff) to find differences between images.

Then I wrote this small function.

```typescript
import * as BlinkDiff from "blink-diff";
import { Page } from "puppeteer";

export async function waitTillPageStoped(
  page: Page,
  interval: number = 200,
  timeout: number = 3000,
  fullPage: boolean = true,
): Promise<boolean> {
  const t0 = new Date().getTime();
  let previousBuffer: Buffer;
  while (new Date().getTime() - t0 < timeout) {
    await sleep(interval);

    const currentBuffer: Buffer = Buffer.from(await page.screenshot({
      encoding: "base64",
      fullPage,
    }), "base64");
    if (previousBuffer == null) {
      previousBuffer = currentBuffer;
      continue;
    }

    const diff = new BlinkDiff({ imageA: previousBuffer, imageB: currentBuffer });
    const result = await diff.runWithPromise();
    if (result.differences === 0) {
      return true;
    }

    previousBuffer = currentBuffer;
  }

  throw new Error("Timeouted!!");
}
```

This captures a screenshot in each interval specified till timed out. If no pixel differences found, simply stop waiting.

I can use this function like this.

```typescript
await page.goto("https://www.sfmoma.org/", {
  waitUntil: "networkidle0",
});
await waitTillPageStoped(page);
await page.screenshot({ path: "example.png" });
```

And I've got an expected result!!

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/wqbo43qwq8wruec3ui7z.png)