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.pngDockerfile
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();