Take screenshot of an URL using puppeteer

Capture a URL screenshot with Puppeteer inside a small Chromium Docker image.

2 min read Updated

A containerised Puppeteer script that captures a screenshot of a URL with a configurable viewport, wait time and output path. The Dockerfile installs Chromium, and the CLI parses the options. The files are reproduced below.

Usage

sh
# build
docker build . -t screenshot

# execution
docker run -it --rm -v $(pwd):/output screenshot \
  --url https://google.com \
  --wait 3000 \
  --size 1024x768 \
  --output /output/screenshot.png

Dockerfile

dockerfile
FROM alpine:edge

ENV NODE_ENV='production'
ENV NO_COLOR='true'

# Installs latest Chromium (89) package.
RUN apk add --no-cache \
      chromium \
      nss \
      freetype \
      freetype-dev \
      harfbuzz \
      ca-certificates \
      ttf-freefont \
      nodejs \
      yarn

# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

# Puppeteer v6.0.0 works with Chromium 89.
RUN yarn add puppeteer@6.0.0

# Add user so we don't need --no-sandbox.
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
    && mkdir -p /home/pptruser/Downloads /app \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /app

COPY . /app/screenshot/
WORKDIR /app/screenshot

# Run everything after as non-privileged user.
USER pptruser

ENTRYPOINT ["node", "/app/screenshot"]

package.json

json
{
  "name": "screenshot",
  "version": "1.0.0",
  "description": "Take screenshot of an URL",
  "main": "screenshot.mjs",
  "type": "module",
  "scripts": {},
  "author": "Sébastien Demanou",
  "license": "MIT"
}

screenshot.mjs

javascript
import puppeteer from 'puppeteer';
import commander from 'commander';

function parseIntValidator(value) {
  // parseInt takes a string and a radix
  const parsedValue = Number.parseInt(value, 10);

  if (isNaN(parsedValue)) {
    throw new commander.InvalidOptionArgumentError('Not a number.');
  }

  return parsedValue;
}

const program = new commander.Command();

program.name('screenshot');
program.version('1.0.0');
program.description('Take screenshot of an URL');

program
  .requiredOption('-u, --url <url>', 'Page URL')
  .option('-s, --size <size>', 'Screenshot size', '1024x768')
  .requiredOption('-o, --output <file>', 'Output filename')
  .option('-w, --wait <milliseconds>', 'Pauses execution for the given number of milliseconds before taking screenshot', parseIntValidator)
  .action(async () => {
    const { url, size, wait, output, outputVariables } = program.opts();
    const [ width, height ] = size.toLowerCase().split(/x/);

    const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] });
    const page = await browser.newPage();

    await page.setViewport({
      width: Number.parseInt(width, 10),
      height: Number.parseInt(height, 10),
      deviceScaleFactor: 1,
    });

    try {
      process.stdout.write(`Opening page ${url}\n`);
      await page.goto(url);
  
      if (wait) {
        process.stdout.write(`Wait for timeout ${wait}ms\n`);
        await page.waitForTimeout(wait);
      }
  
      process.stdout.write(`Taking screenshot of ${url}...\n`);
      await page.screenshot({ path: output });
      process.stdout.write(`Screenshot saved to ${output}\n`);
      await page.close();
      await browser.close();
      process.exit(0);
    } catch (err) {
      console.error(err);
      await page.close();
      await browser.close();
      process.exit(1);
    }
  });

program.parse();

Search articles

Type to filter articles. Use the arrow keys to move through results and Enter to open one. Press Escape to close.