Automate Headless Chrome -- start/stop Chrome instances, open & close tabs, and communicate with the target page.
- You must use version >= 59 of Chrome (currently that means Chrome Beta) or use Chrome Canary.
- Canary isn't supported on Linux platform.
npm install chromate
npm run sample
let {Chrome, Tab} = require('chromate');
// start a headless Chrome process
Chrome.start().then(chrome => {
let tab = new Tab({
verbose: true,
failonerror: false
});
tab.open(targetUrl)
.then(() => tab.evaluate('testResults'))
.then(res => console.log) // results...
.then(() => tab.close())
.then(() => {
Chrome.kill(chrome);
process.exit(0);
});
});
Handle events, including any chrome-remote-interface events.
new Tab(options)
.on('ready', (tab) => console.log('tab is ready', tab.client.target.id))
.on('load', () => console.log('page loaded'))
.on('console', (args) => console.log('console.* called', args))
.on('Network.requestWillBeSent', param => console.log('Getting resource', param.request.url))
.once('Runtime.consoleAPICalled', param => console.log('Runtime.consoleAPICalled called', param))
.open(targetUrl);
The ready
event is fired once when the target client is ready (this overrides the CDP ready
event). The target may
fire any number of custom events.
A target page may communicate back to the controlling process by calling console.debug(message)
,
where message
is {event, data}
. This is useful for running automated tests, such as for
replacing PhantomJS. See phantom-menace for example.
// useful for short messages (< 100 chars)
console.debug({
event: 'done',
data: JSON.stringify({foo: 1}) // must be stringify'd
});
// then, in runner process
new Tab()
.on('done', res => console.log); // {event: 'done', data: foo:1}}
A special function __chromate(message)
is injected in target
page to facilitate this, so that the above can be replaced by:
// in target (useful for any length message)
if (window.__chromate) __chromate({event: 'done', data: {foo:1}});;
The format of the message is flexible, but should be sensible. If no event
property is found in the message,
a 'data' event is triggered.
// in target
__chromate('foo');
console.debug({a:1});
// in runner
tab.on('data', res => console.log); // 'foo' and {a:1}
Often it's useful for the running script to inject custom JS into the target page. This can
be done thorough Page.addScriptToEvaluateOnLoad()
or the Runtime.evaluate() method.
Two helper methods are provided: tab.evaluate()
and tab.execute()
:
new Tab()
.on('done', (res, tab) => {
tab.evaluate('JSON.stringify(__coverage__)')
.then(result => console.log(result))
.then(() => tab.close());
})
.open(targetUrl);
Or execute a (named) function in target.
new Tab()
.open(targetUrl)
.then(tab => {
tab.execute('getResult').then(res => console.log);
})
// in target
function getResult() {
return JSON.stringify(result);
}
tab.execute()
takes additional parameters to pass as arguments to the function.
If the function is expected to return a Promise, pass a {awaitPromise: true}
as the
last argument.
See API docs.
Usage:
$ chromate
Usage: chromate start [<chrome flags>] | list | kill <pid> ... | killall | version | open <url> |
list-tabs | close <tabId> | close-tabs [--canary | --verbose | -v]
$ chromate start --window-size=800x600 --canary
42706: /Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary
--remote-debugging-port=9222 --headless --disable-gpu
--user-data-dir=/var/folders/jl/zr54cdxs08l1djw8s4ws2t540000gn/T/chrome-PdvzfF
--window-size=800x600 --canary --disable-translate --disable-extensions
--disable-background-networking --safebrowsing-disable-auto-update --disable-sync
--metrics-recording-only --disable-default-apps --no-first-run
--disable-background-timer-throttling --disable-renderer-backgrounding
--disable-device-discovery-notifications
$ chromate list
[ { pid: '42706',
command: '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
arguments:
[ '--remote-debugging-port=9222',
'--headless',
'--disable-gpu',
'--user-data-dir=/var/folders/jl/zr54cdxs08l1djw8s4ws2t540000gn/T/chrome-PdvzfF',
'--window-size=800x600',
'--canary',
'--disable-translate',
'--disable-extensions',
'--disable-background-networking',
'--safebrowsing-disable-auto-update',
'--disable-sync',
'--metrics-recording-only',
'--disable-default-apps',
'--no-first-run',
'--disable-background-timer-throttling',
'--disable-renderer-backgrounding',
'--disable-device-discovery-notifications' ],
ppid: '1' } ]
$ chromate killall
2
killall
returns the number of processes (including sub-processes) killed.
For list of Chrome Headless flags, see here. Of course, any Chrome flag can be specified.
To use a custom Chrome path and/or port, use:
$ CHROME_BIN=/path/to/chrome CHROME_PORT=9226 chromate start
$ chromate open https://github.com
{ description: '',
devtoolsFrontendUrl: '/devtools/inspector.html?ws=localhost:9222/devtools/page/904ddfa4-2344-4e45-a625-8261ffbee251',
id: '904ddfa4-2344-4e45-a625-8261ffbee251',
title: '',
type: 'page',
url: 'about:blank',
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/904ddfa4-2344-4e45-a625-8261ffbee251' }
$ chromate list-tabs
[ { description: '',
devtoolsFrontendUrl: '/devtools/inspector.html?ws=localhost:9222/devtools/page/e97b0e1e-1fb5-41be-83d2-bdb9fbc406bc',
id: 'e97b0e1e-1fb5-41be-83d2-bdb9fbc406bc',
title: 'The world's leading software development platform · GitHub',
type: 'page',
url: 'https://github.com/',
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/e97b0e1e-1fb5-41be-83d2-bdb9fbc406bc' },
{ description: '',
devtoolsFrontendUrl: '/devtools/inspector.html?ws=localhost:9222/devtools/page/e4c16358-7670-4deb-8b2e-29f802e599a3',
id: 'e4c16358-7670-4deb-8b2e-29f802e599a3',
title: 'about:blank',
type: 'page',
url: 'about:blank',
webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/e4c16358-7670-4deb-8b2e-29f802e599a3' } ]
$ chromate close-tabs
2
# npm i -g mocha (if you don't already have it)
npm test
- This library is based on the awesome work of chrome-remote-interface
- Idea for Chrome.ready() taken from Lighthouse
- DevTools Protocol Viewer complete reference.
- Getting Started with Headless Chrome
- v0.3.5 - Readme update.
- v0.3.4 - Added Chrome.settings.userDataDir. By default a temporary user data dir is used and cleaned up.
- v0.3.3 - fixed 'ps-node' reference
- v0.3.2 - fixed internal print() method
- v0.3.1 - added events 'abort', 'exception', and 'console.*'. Export chromate.version.
- v0.3.0 - tab.open(url) takes url rather than constructor. tab.execute can take a local function. Use ps-moos with fix for spaces in path.
- v0.2.0 - Added expression and function evaluation and __chromate global for general message passing. Events now get complete message, not just the data part. (May 2017)
- v0.1.x - Initial version (May 2017)
MIT