A returning subject on this blog, how to automate device screenshots with Node.js and Chrome. This post will cover installation and running the script on either Mac OS or Linux. If you’re brave, you can use Windows too 😉

Update: A Chrome update actually broke the code for full page screenshots using forceViewport, the code samples have been updated to support the change.

Full error message:

(node:30456) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Emulation.forceViewport is not a function (node:30456) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Automating Screenshots of your Website

There’s a ton of services for this out there, but if you have some kind of edge case or simply just need a better way of taking screenshots, please read on.

The example code makes use of async and await, so please be on Node.js 7.8+.

As always I recommend NVM for installing different versions of Node.

Getting / Starting Chrome Headless

Assuming you want to run this on a linux server, after installing Google Chrome via apt/pacman/yum, run: google-chrome --version

Expected output: Google Chrome 59.0.3071.115 (or higher)

Install Guides for:

Ubuntu apt-get install google-chrome-stable

ArchLinux

To start the headless chrome browser, simply run:

google-chrome --headless --hide-scrollbars --remote-debugging-port=9222 --disable-gpu

Note: Your terminal will now be occupied by running that chrome browser in headless mode, if you want to detach it and keep it in the background, append a & to the command like so:

google-chrome --headless --hide-scrollbars --remote-debugging-port=9222 --disable-gpu &

Another tip, if you want to know the process ID of your chrome headless browser, append &; echo $! , which will output the process ID of what you just started:

google-chrome --headless --hide-scrollbars --remote-debugging-port=9222 --disable-gpu &; echo $!

so you can use that to kill the process, if you want to restart it or just be rid of it.

Taking Screenshots with Headless Chrome

Let’s get to the code! If you copy the below and save it as screenshot.js and in the same directory run

npm install minimist chrome-remote-interface node screenshot.js

Copy & paste ready code 😉

const CDP = require('chrome-remote-interface'); const argv = require('minimist')(process.argv.slice(2)); const fs = require('fs'); const targetURL = argv.url || 'https://jonathanmh.com'; const viewport = [1440,900]; const screenshotDelay = 2000; // ms const fullPage = argv.fullPage || false; if(fullPage){ console.log("will capture full page") } CDP(async function(client){ const {DOM, Emulation, Network, Page, Runtime} = client; // Enable events on domains we are interested in. await Page.enable(); await DOM.enable(); await Network.enable(); // change these for your tests or make them configurable via argv var device = { width: viewport[0], height: viewport[1], deviceScaleFactor: 0, mobile: false, fitWindow: false }; // set viewport and visible size await Emulation.setDeviceMetricsOverride(device); await Emulation.setVisibleSize({width: viewport[0], height: viewport[1]}); await Page.navigate({url: targetURL}); Page.loadEventFired(async() => { if (fullPage) { const {root: {nodeId: documentNodeId}} = await DOM.getDocument(); const {nodeId: bodyNodeId} = await DOM.querySelector({ selector: 'body', nodeId: documentNodeId, }); const {model: {height}} = await DOM.getBoxModel({nodeId: bodyNodeId}); await Emulation.setVisibleSize({width: device.width, height: height}); await Emulation.setDeviceMetricsOverride({width: device.width, height:height, screenWidth: device.width, screenHeight: height, deviceScaleFactor: 1, fitWindow: false, mobile: false}); await Emulation.setPageScaleFactor({pageScaleFactor:1}); } }); setTimeout(async function() { const screenshot = await Page.captureScreenshot({format: "png", fromSurface: true}); const buffer = new Buffer(screenshot.data, 'base64'); fs.writeFile('desktop.png', buffer, 'base64', function(err) { if (err) { console.error(err); } else { console.log('Screenshot saved'); } }); client.close(); }, screenshotDelay); }).on('error', err => { console.error('Cannot connect to browser:', err); });

You should be good to go.

You can specify the URL by running:

node screenshot.js --url https://gegenwind.dk

To make a full page screenshot (careful, this behaviour is still buggy), simply include --fullPage true in your command.

Credits for code to:

this post on medium which didn’t work for me on Mac OS X and also I needed the delay much higher than none

some other snippets I can’t seem to dig up any more

Automated Mobile Testing

In order to test which devices have which real and which CSS resolution you can have a look at mydevice.io which has a reasonable collection of smartphones and their resolutions and their device pixel ratios.

If you want to check out your own screen/phone you can use the device pixel ratio test.

To use headless chrome and node to test how your page looks on mobile simply change the following defaults (or change them via command line arguments):

const viewport = [375,667]; // iPhone 7

and

var device = { width: viewport[0], height: viewport[1], deviceScaleFactor: 3, mobile: true, fitWindow: false };

Voilá!

Summary

Thank you very much for reading, actually the post I once upon a time about phantom.js was pretty successful, so I felt a bit nostalgic writing this post.

Why do you want to take screenshots? What’s your use case? I’m super curious!

Further Reading

Check out the Google Blog Post