Puppeteer
Puppeteer is a Node library
developed by the Chrome team. It provides a high-level API to control headless
(or full) Chrome. It's similar to other automated testing libraries like Phantom
and NightmareJS, but it only works with the latest versions of Chrome.
Among other things, Puppeteer can be used to easily take screenshots, create PDFs,
navigate pages, and fetch information about those pages. I recommend the library
if you want to quickly automate browser testing. It hides away the complexities
of the DevTools protocol and takes care of redundant tasks like launching a
debug instance of Chrome.
Install it:
npm i --save puppeteer
Example - print the user agent
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch();
console.log(await browser.version());
await browser.close();
})();
Example - taking a screenshot of the page
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.chromestatus.com', {waitUntil: 'networkidle2'});
await page.pdf({path: 'page.pdf', format: 'A4'});
await browser.close();
})();
Check out Puppeteer's
documentation
to learn more about the full API.
The CRI library
chrome-remote-interface
is a lower-level library than Puppeteer's API. I recommend it if you want to be
close to the metal and use the DevTools
protocol directly.
Launching Chrome
chrome-remote-interface doesn't launch Chrome for you, so you'll have to take
care of that yourself.
In the CLI section, we started Chrome manually using
--headless --remote-debugging-port=9222
. However, to fully automate tests, you'll
probably
want to spawn Chrome from your application.
One way is to use child_process
:
const execFile = require('child_process').execFile;
function launchHeadlessChrome(url, callback) {
// Assuming MacOSx.
const CHROME = '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome';
execFile(CHROME, ['--headless', '--disable-gpu', '--remote-debugging-port=9222', url], callback);
}
launchHeadlessChrome('https://www.chromestatus.com', (err, stdout, stderr) => {
...
});
But things get tricky if you want a portable solution that works across multiple
platforms. Just look at that hard-coded path to Chrome :(
Using ChromeLauncher
Lighthouse is a marvelous
tool for testing the quality of your web apps. A robust module for launching
Chrome was developed within Lighthouse and is now extracted for standalone use.
The chrome-launcher
NPM
module
will find where
Chrome is installed, set up a debug instance, launch the browser, and kill it
when your program is done. Best part is that it works cross-platform thanks to
Node!
By default, chrome-launcher
will try to launch Chrome Canary (if
it's
installed), but you can change that to manually select which Chrome to use. To
use it, first install from npm:
npm i --save chrome-launcher
Example - using chrome-launcher
to launch Headless
const chromeLauncher = require('chrome-launcher');
// Optional: set logging level of launcher to see its output.
// Install it using: npm i --save lighthouse-logger
// const log = require('lighthouse-logger');
// log.setLevel('info');
/**
* Launches a debugging instance of Chrome.
* @param {boolean=} headless True (default) launches Chrome in headless mode.
* False launches a full version of Chrome.
* @return {Promise<ChromeLauncher>}
*/
function launchChrome(headless=true) {
return chromeLauncher.launch({
// port: 9222, // Uncomment to force a specific port of your choice.
chromeFlags: [
'--window-size=412,732',
'--disable-gpu',
headless ? '--headless' : ''
]
});
}
launchChrome().then(chrome => {
console.log(`Chrome debuggable on port: ${chrome.port}`);
...
// chrome.kill();
});
Running this script doesn't do much, but you should see an instance of
Chrome fire up in the task manager that loaded about:blank
. Remember, there
won't be any browser UI. We're headless.
To control the browser, we need the DevTools protocol!
Retrieving information about the page
Let's install the library:
npm i --save chrome-remote-interface
Examples
Example - print the user agent
const CDP = require('chrome-remote-interface');
...
launchChrome().then(async chrome => {
const version = await CDP.Version({port: chrome.port});
console.log(version['User-Agent']);
});
Results in something like: HeadlessChrome/60.0.3082.0
Example - check if the site has a web
app manifest
const CDP = require('chrome-remote-interface');
...
(async function() {
const chrome = await launchChrome();
const protocol = await CDP({port: chrome.port});
// Extract the DevTools protocol domains we need and enable them.
// See API docs: https://chromedevtools.github.io/devtools-protocol/
const {Page} = protocol;
await Page.enable();
Page.navigate({url: 'https://www.chromestatus.com/'});
// Wait for window.onload before doing stuff.
Page.loadEventFired(async () => {
const manifest = await Page.getAppManifest();
if (manifest.url) {
console.log('Manifest: ' + manifest.url);
console.log(manifest.data);
} else {
console.log('Site has no app manifest');
}
protocol.close();
chrome.kill(); // Kill Chrome.
});
})();
Example - extract the <title>
of the page using DOM APIs.
const CDP = require('chrome-remote-interface');
...
(async function() {
const chrome = await launchChrome();
const protocol = await CDP({port: chrome.port});
// Extract the DevTools protocol domains we need and enable them.
// See API docs: https://chromedevtools.github.io/devtools-protocol/
const {Page, Runtime} = protocol;
await Promise.all([Page.enable(), Runtime.enable()]);
Page.navigate({url: 'https://www.chromestatus.com/'});
// Wait for window.onload before doing stuff.
Page.loadEventFired(async () => {
const js = "document.querySelector('title').textContent";
// Evaluate the JS expression in the page.
const result = await Runtime.evaluate({expression: js});
console.log('Title of page: ' + result.result.value);
protocol.close();
chrome.kill(); // Kill Chrome.
});
})();