Chrome Debugging Protocol interface
npm install ox-chrome-remote-interfacePatched version of https://github.com/cyrus-and/chrome-remote-interface
[travis]: https://travis-ci.org/cyrus-and/chrome-remote-interface
[Chrome Debugging Protocol] interface that helps to instrument Chrome (or any
other suitable implementation) by providing a simple
abstraction of commands and notifications using a straightforward JavaScript
API.
This module is one of the many [third-party protocol clients][3rd-party].
[3rd-party]: https://developer.chrome.com/devtools/docs/debugging-clients#chrome-remote-interface
https://github.com and dumps every request made:
js
const CDP = require('chrome-remote-interface');
async function example() {
let client;
try {
// connect to endpoint
client = await CDP();
// extract domains
const {Network, Page} = client;
// setup handlers
Network.requestWillBeSent((params) => {
console.log(params.request.url);
});
// enable events then start!
await Network.enable();
await Page.enable();
await Page.navigate({url: 'https://github.com'});
await Page.loadEventFired();
} catch (err) {
console.error(err);
} finally {
if (client) {
await client.close();
}
}
}
example();
`
Find more examples in the [wiki]. You may also want to take a look at the [FAQ].
[wiki]: https://github.com/cyrus-and/chrome-remote-interface/wiki
[async-await-example]: https://github.com/cyrus-and/chrome-remote-interface/wiki/Async-await-example
[FAQ]: https://github.com/cyrus-and/chrome-remote-interface#faq
Installation
npm install chrome-remote-interface
Install globally (-g) to just use the bundled client.
Implementations
This module should work with every application implementing the
[Chrome Debugging Protocol]. In particular, it has been tested against the
following implementations:
Implementation | Protocol version | [Protocol] | [List] | [New] | [Activate] | [Close] | [Version]
---------------------------|--------------------|------------|--------|-------|------------|---------|-----------
[Chrome][1.1] | [tip-of-tree][1.2] | yes¹ | yes | yes | yes | yes | yes
[Opera][2.1] | [tip-of-tree][2.2] | yes | yes | yes | yes | yes | yes
[Node.js][3.1] ([v6.3.0]+) | [node][3.2] | yes | no | no | no | no | yes
[Safari (iOS)][4.1] | [partial][4.2] | no | yes | no | no | no | no
[Edge][5.1] | [partial][5.2] | yes | yes | no | no | no | yes
[Firefox (Nightly)][6.1] | [partial][6.2] | yes | yes | no | yes | yes | yes
¹ Not available on [Chrome for Android][chrome-mobile-protocol].
[chrome-mobile-protocol]: https://bugs.chromium.org/p/chromium/issues/detail?id=824626#c4
[1.1]: #chromechromium
[1.2]: https://chromedevtools.github.io/devtools-protocol/tot/
[2.1]: #opera
[2.2]: https://chromedevtools.github.io/devtools-protocol/tot/
[3.1]: #nodejs
[3.2]: https://chromedevtools.github.io/devtools-protocol/v8/
[4.1]: #safari-ios
[4.2]: http://trac.webkit.org/browser/trunk/Source/JavaScriptCore/inspector/protocol
[5.1]: #edge
[5.2]: https://docs.microsoft.com/en-us/microsoft-edge/devtools-protocol/0.1/domains/
[6.1]: #firefox-nightly
[6.2]: https://firefox-source-docs.mozilla.org/remote/index.html
[v6.3.0]: https://nodejs.org/en/blog/release/v6.3.0/
[Protocol]: #cdpprotocoloptions-callback
[List]: #cdplistoptions-callback
[New]: #cdpnewoptions-callback
[Activate]: #cdpactivateoptions-callback
[Close]: #cdpcloseoptions-callback
[Version]: #cdpversionoptions-callback
The meaning of target varies according to the implementation, for example,
each Chrome tab represents a target whereas for Node.js a target is the
currently inspected script.
Setup
An instance of either Chrome itself or another implementation needs to be
running on a known port in order to use this module (defaults to
localhost:9222).
$3
#### Desktop
Start Chrome with the --remote-debugging-port option, for example:
google-chrome --remote-debugging-port=9222
##### Headless
Since version 59, additionally use the --headless option, for example:
google-chrome --headless --remote-debugging-port=9222
#### Android
Plug the device and enable the [port forwarding][adb], for example:
adb forward tcp:9222 localabstract:chrome_devtools_remote
[adb]: https://developer.chrome.com/devtools/docs/remote-debugging-legacy
##### WebView
In order to be inspectable, a WebView must
be [configured for debugging][webview] and the corresponding process ID must be
known. There are several ways to obtain it, for example:
adb shell grep -a webview_devtools_remote /proc/net/unix
Finally, port forwarding can be enabled as follows:
adb forward tcp:9222 localabstract:webview_devtools_remote_
[webview]: https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews#configure_webviews_for_debugging
$3
Start Opera with the --remote-debugging-port option, for example:
opera --remote-debugging-port=9222
$3
Start Node.js with the --inspect option, for example:
node --inspect=9222 script.js
$3
Install and run the [iOS WebKit Debug Proxy][iwdp]. Then use it with the local
option set to true to use the local version of the protocol or pass a custom
descriptor upon connection (protocol option).
[iwdp]: https://github.com/google/ios-webkit-debug-proxy
$3
Start Edge with the --devtools-server-port option, for example:
MicrosoftEdge.exe --devtools-server-port 9222 about:blank
Please find more information [here][edge-devtools].
[edge-devtools]: https://docs.microsoft.com/en-us/microsoft-edge/devtools-protocol/
$3
Start Firefox with the --remote-debugging-port option, for example:
firefox --remote-debugging-port 9222
Bear in mind that this is an experimental feature of Firefox.
Bundled client
This module comes with a bundled client application that can be used to
interactively control a remote instance.
$3
The bundled client exposes subcommands to interact with the HTTP frontend
(e.g., List, New, etc.),
run with --help to display the list of available options.
Here are some examples:
`js
$ chrome-remote-interface new 'http://example.com'
{
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/b049bb56-de7d-424c-a331-6ae44cf7ae01",
"id": "b049bb56-de7d-424c-a331-6ae44cf7ae01",
"thumbnailUrl": "/thumb/b049bb56-de7d-424c-a331-6ae44cf7ae01",
"title": "",
"type": "page",
"url": "http://example.com/",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/b049bb56-de7d-424c-a331-6ae44cf7ae01"
}
$ chrome-remote-interface close 'b049bb56-de7d-424c-a331-6ae44cf7ae01'
`
$3
Using the inspect subcommand it is possible to perform command execution
and event binding in a REPL fashion that provides completion.
Here is a sample session:
`js
$ chrome-remote-interface inspect
>>> Runtime.evaluate({expression: 'window.location.toString()'})
{ result: { type: 'string', value: 'about:blank' } }
>>> Page.enable()
{}
>>> Page.loadEventFired(console.log)
[Function]
>>> Page.navigate({url: 'https://github.com'})
{ frameId: 'E1657E22F06E6E0BE13DFA8130C20298',
loaderId: '439236ADE39978F98C20E8939A32D3A5' }
>>> { timestamp: 7454.721299 } // from Page.loadEventFired
>>> Runtime.evaluate({expression: 'window.location.toString()'})
{ result: { type: 'string', value: 'https://github.com/' } }
`
Additionally there are some custom commands available:
`js
>>> .help
[...]
.reset Remove all the registered event handlers
.target Display the current target
`
Embedded documentation
In both the REPL and the regular API every object of the protocol is decorated
with the meta information found within the descriptor. In addition The
category field is added, which determines if the member is a command, an
event or a type.
For example to learn how to call Page.navigate:
`js
>>> Page.navigate
{ [Function]
category: 'command',
parameters: { url: { type: 'string', description: 'URL to navigate the page to.' } },
returns:
[ { name: 'frameId',
'$ref': 'FrameId',
hidden: true,
description: 'Frame id that will be navigated.' } ],
description: 'Navigates current page to the given URL.',
handlers: [ 'browser', 'renderer' ] }
`
To learn about the parameters returned by the Network.requestWillBeSent event:
`js
>>> Network.requestWillBeSent
{ [Function]
category: 'event',
description: 'Fired when page is about to send HTTP request.',
parameters:
{ requestId: { '$ref': 'RequestId', description: 'Request identifier.' },
frameId:
{ '$ref': 'Page.FrameId',
description: 'Frame identifier.',
hidden: true },
loaderId: { '$ref': 'LoaderId', description: 'Loader identifier.' },
documentURL:
{ type: 'string',
description: 'URL of the document this request is loaded for.' },
request: { '$ref': 'Request', description: 'Request data.' },
timestamp: { '$ref': 'Timestamp', description: 'Timestamp.' },
wallTime:
{ '$ref': 'Timestamp',
hidden: true,
description: 'UTC Timestamp.' },
initiator: { '$ref': 'Initiator', description: 'Request initiator.' },
redirectResponse:
{ optional: true,
'$ref': 'Response',
description: 'Redirect response data.' },
type:
{ '$ref': 'Page.ResourceType',
optional: true,
hidden: true,
description: 'Type of this resource.' } } }
`
To inspect the Network.Request (note that unlike commands and events, types
are named in upper camel case) type:
`js
>>> Network.Request
{ category: 'type',
id: 'Request',
type: 'object',
description: 'HTTP request data.',
properties:
{ url: { type: 'string', description: 'Request URL.' },
method: { type: 'string', description: 'HTTP request method.' },
headers: { '$ref': 'Headers', description: 'HTTP request headers.' },
postData:
{ type: 'string',
optional: true,
description: 'HTTP POST request data.' },
mixedContentType:
{ optional: true,
type: 'string',
enum: [Object],
description: 'The mixed content status of the request, as defined in http://www.w3.org/TR/mixed-content/' },
initialPriority:
{ '$ref': 'ResourcePriority',
description: 'Priority of the resource request at the time request is sent.' } } }
`
Chrome Debugging Protocol versions
By default chrome-remote-interface asks the remote instance to provide its
own protocol.
This behavior can be changed by setting the local option to true
upon connection, in which case the [local version] of
the protocol descriptor is used. This file is manually updated from time to time
using scripts/update-protocol.sh and pushed to this repository.
To further override the above behavior there are basically two options:
- pass a custom protocol descriptor upon connection
(protocol option);
- use the raw version of the commands
and events interface to use bleeding-edge features that
do not appear in the [local version] of the protocol descriptor;
[local version]: lib/protocol.json
Browser usage
This module is able to run within a web context, with obvious limitations
though, namely external HTTP requests
(List, New, etc.) cannot
be performed directly, for this reason the user must provide a global
criRequest in order to use them:
`js
function criRequest(options, callback) {}
`
options is the same object used by the Node.js http module and callback is
a function taking two arguments: err (JavaScript Error object or null) and
data (string result).
$3
It just works, simply require this module:
`js
const CDP = require('chrome-remote-interface');
`
$3
To generate a JavaScript file that can be used with a
API
The API consists of three parts:
- DevTools methods (for those implementations that support
them, e.g., List, New,
etc.);
- connection establishment;
- the actual protocol interaction.
$3
Connects to a remote instance using the [Chrome Debugging Protocol].
options is an object with the following optional properties:
- host: HTTP frontend host. Defaults to localhost;
- port: HTTP frontend port. Defaults to 9222;
- secure: HTTPS/WSS frontend. Defaults to false;
- useHostName: do not perform a DNS lookup of the host. Defaults to false;
- alterPath: a function taking and returning the path fragment of a URL
before that a request happens. Defaults to the identity function;
- target: determines which target this client should attach to. The behavior
changes according to the type:
- a function that takes the array returned by the List method and returns
a target or its numeric index relative to the array;
- a target object like those returned by the New and List methods;
- a string representing the raw WebSocket URL, in this case host and
port are not used to fetch the target list, yet they are used to complete
the URL if relative;
- a string representing the target id.
Defaults to a function which returns the first available target according to
the implementation (note that at most one connection can be established to the
same target);
- protocol: [Chrome Debugging Protocol] descriptor object. Defaults to use the
protocol chosen according to the local option;
- local: a boolean indicating whether the protocol must be fetched remotely
or if the local version must be used. It has no effect if the protocol
option is set. Defaults to false.
These options are also valid properties of all the instances of the CDP
class. In addition to that, the webSocketUrl field contains the currently used
WebSocket URL.
callback is a listener automatically added to the connect event of the
returned EventEmitter. When callback is omitted a Promise object is
returned which becomes fulfilled if the connect event is triggered and
rejected if the error event is triggered.
The EventEmitter supports the following events:
#### Event: 'connect'
`js
function (client) {}
`
Emitted when the connection to the WebSocket is established.
client is an instance of the CDP class.
#### Event: 'error'
`js
function (err) {}
`
Emitted when http://host:port/json cannot be reached or if it is not possible
to connect to the WebSocket.
err is an instance of Error.
$3
Fetch the [Chrome Debugging Protocol] descriptor.
options is an object with the following optional properties:
- host: HTTP frontend host. Defaults to localhost;
- port: HTTP frontend port. Defaults to 9222;
- secure: HTTPS/WSS frontend. Defaults to false;
- useHostName: do not perform a DNS lookup of the host. Defaults to false;
- alterPath: a function taking and returning the path fragment of a URL
before that a request happens. Defaults to the identity function;
- local: a boolean indicating whether the protocol must be fetched remotely
or if the local version must be returned. Defaults to false.
callback is executed when the protocol is fetched, it gets the following
arguments:
- err: a Error object indicating the success status;
- protocol: the [Chrome Debugging Protocol] descriptor.
When callback is omitted a Promise object is returned.
For example:
`js
const CDP = require('chrome-remote-interface');
CDP.Protocol((err, protocol) => {
if (!err) {
console.log(JSON.stringify(protocol, null, 4));
}
});
`
$3
Request the list of the available open targets/tabs of the remote instance.
options is an object with the following optional properties:
- host: HTTP frontend host. Defaults to localhost;
- port: HTTP frontend port. Defaults to 9222;
- secure: HTTPS/WSS frontend. Defaults to false;
- useHostName: do not perform a DNS lookup of the host. Defaults to false;
- alterPath: a function taking and returning the path fragment of a URL
before that a request happens. Defaults to the identity function.
callback is executed when the list is correctly received, it gets the
following arguments:
- err: a Error object indicating the success status;
- targets: the array returned by http://host:port/json/list containing the
target list.
When callback is omitted a Promise object is returned.
For example:
`js
const CDP = require('chrome-remote-interface');
CDP.List((err, targets) => {
if (!err) {
console.log(targets);
}
});
`
$3
Create a new target/tab in the remote instance.
options is an object with the following optional properties:
- host: HTTP frontend host. Defaults to localhost;
- port: HTTP frontend port. Defaults to 9222;
- secure: HTTPS/WSS frontend. Defaults to false;
- useHostName: do not perform a DNS lookup of the host. Defaults to false;
- alterPath: a function taking and returning the path fragment of a URL
before that a request happens. Defaults to the identity function;
- url: URL to load in the new target/tab. Defaults to about:blank.
callback is executed when the target is created, it gets the following
arguments:
- err: a Error object indicating the success status;
- target: the object returned by http://host:port/json/new containing the
target.
When callback is omitted a Promise object is returned.
For example:
`js
const CDP = require('chrome-remote-interface');
CDP.New((err, target) => {
if (!err) {
console.log(target);
}
});
`
$3
Activate an open target/tab of the remote instance.
options is an object with the following properties:
- host: HTTP frontend host. Defaults to localhost;
- port: HTTP frontend port. Defaults to 9222;
- secure: HTTPS/WSS frontend. Defaults to false;
- useHostName: do not perform a DNS lookup of the host. Defaults to false;
- alterPath: a function taking and returning the path fragment of a URL
before that a request happens. Defaults to the identity function;
- id: Target id. Required, no default.
callback is executed when the response to the activation request is
received. It gets the following arguments:
- err: a Error object indicating the success status;
When callback is omitted a Promise object is returned.
For example:
`js
const CDP = require('chrome-remote-interface');
CDP.Activate({id: 'CC46FBFA-3BDA-493B-B2E4-2BE6EB0D97EC'}, (err) => {
if (!err) {
console.log('target is activated');
}
});
`
$3
Close an open target/tab of the remote instance.
options is an object with the following properties:
- host: HTTP frontend host. Defaults to localhost;
- port: HTTP frontend port. Defaults to 9222;
- secure: HTTPS/WSS frontend. Defaults to false;
- useHostName: do not perform a DNS lookup of the host. Defaults to false;
- alterPath: a function taking and returning the path fragment of a URL
before that a request happens. Defaults to the identity function;
- id: Target id. Required, no default.
callback is executed when the response to the close request is received. It
gets the following arguments:
- err: a Error object indicating the success status;
When callback is omitted a Promise object is returned.
For example:
`js
const CDP = require('chrome-remote-interface');
CDP.Close({id: 'CC46FBFA-3BDA-493B-B2E4-2BE6EB0D97EC'}, (err) => {
if (!err) {
console.log('target is closing');
}
});
`
Note that the callback is fired when the target is queued for removal, but the
actual removal will occur asynchronously.
$3
Request version information from the remote instance.
options is an object with the following optional properties:
- host: HTTP frontend host. Defaults to localhost;
- port: HTTP frontend port. Defaults to 9222;
- secure: HTTPS/WSS frontend. Defaults to false;
- useHostName: do not perform a DNS lookup of the host. Defaults to false;
- alterPath: a function taking and returning the path fragment of a URL
before that a request happens. Defaults to the identity function.
callback is executed when the version information is correctly received, it
gets the following arguments:
- err: a Error object indicating the success status;
- info: a JSON object returned by http://host:port/json/version containing
the version information.
When callback is omitted a Promise object is returned.
For example:
`js
const CDP = require('chrome-remote-interface');
CDP.Version((err, info) => {
if (!err) {
console.log(info);
}
});
`
$3
#### Event: 'event'
`js
function (message) {}
`
Emitted when the remote instance sends any notification through the WebSocket.
message is the object received, it has the following properties:
- method: a string describing the notification (e.g.,
'Network.requestWillBeSent');
- params: an object containing the payload.
Refer to the [Chrome Debugging Protocol] specification for more information.
For example:
`js
client.on('event', (message) => {
if (message.method === 'Network.requestWillBeSent') {
console.log(message.params);
}
});
`
#### Event: '.'
`js
function (params) {}
`
Emitted when the remote instance sends a notification for
through the WebSocket.
params is an object containing the payload.
This is just a utility event which allows to easily listen for specific
notifications (see 'event'), for example:
`js
client.on('Network.requestWillBeSent', console.log);
`
Additionally, the equivalent syntax is available, for example:
`js
client.Network.on('requestWillBeSent', console.log);
`
#### Event: 'ready'
`js
function () {}
`
Emitted every time that there are no more pending commands waiting for a
response from the remote instance. The interaction is asynchronous so the only
way to serialize a sequence of commands is to use the callback provided by
the send method. This event acts as a
barrier and it is useful to avoid the callback hell in certain simple
situations.
Users are encouraged to extensively check the response of each method and should
prefer the promises API when dealing with complex asynchronous program flows.
For example to load a URL only after having enabled the notifications of both
Network and Page domains:
`js
client.Network.enable();
client.Page.enable();
client.once('ready', () => {
client.Page.navigate({url: 'https://github.com'});
});
`
In this particular case, not enforcing this kind of serialization may cause that
the remote instance does not properly deliver the desired notifications the
client.
#### Event: 'disconnect'
`js
function () {}
`
Emitted when the instance closes the WebSocket connection.
This may happen for example when the user opens DevTools or when the tab is
closed.
#### client.send(method, [params], [callback])
Issue a command to the remote instance.
method is a string describing the command.
params is an object containing the payload.
callback is executed when the remote instance sends a response to this
command, it gets the following arguments:
- error: a boolean value indicating the success status, as reported by the
remote instance;
- response: an object containing either the response (result field, if
error === false) or the indication of the error (error field, if error
).
When callback is omitted a Promise object is returned instead, with the
fulfilled/rejected states implemented according to the error parameter. The
Error object returned contains two additional parameters: request and
response which contain the raw massages, useful for debugging purposes. In
case of low-level WebSocket errors, the error parameter contains the
originating Error object and no response is returned.
Note that the field id mentioned in the [Chrome Debugging Protocol]
specification is managed internally and it is not exposed to the user.
For example:
`js
client.send('Page.navigate', {url: 'https://github.com'}, console.log);
`
#### client..([params], [callback])
Just a shorthand for:
`js
client.send('.', params, callback);
`
For example:
`js
client.Page.navigate({url: 'https://github.com'}, console.log);
`
#### client..([callback])
Just a shorthand for:
`js
client.on('.', callback);
`
When callback is omitted the event is registered only once and a Promise
object is returned.
When callback is provided, it returns a function that can be used to
unsubscribe callback from the event, it can be useful when anonymous functions
are used as callbacks.
For example:
`js
const unsubscribe = client.Network.requestWillBeSent((params) => {
console.log(params.request.url);
});
unsubscribe();
`
#### client.close([callback])
Close the connection to the remote instance.
callback is executed when the WebSocket is successfully closed.
When callback is omitted a Promise object is returned.
FAQ
$3
This means that the Chrome version that you are using does not support
Domain.method. The solution is to update to a newer version.
See here for more information.
$3
This means that you are providing a custom protocol descriptor
(CDP({protocol: customProtocol})) which declares Domain.method while the
Chrome version that you are using does not support it.
To inspect the currently available protocol descriptor use:
`
$ chrome-remote-interface inspect
`
See here for more information.
$3
Bear in mind that --headless Chrome is relatively new and there are kinks (in
Chrome) that are being worked out. If you believe you have encountered a bug,
take a look at the open issues, especially [external issues].
[external issues]: https://github.com/cyrus-and/chrome-remote-interface/issues?q=label%3A%22external+issue%22
$3
This happens because the size of /dev/shm is set to 64MB by default in Docker
and may not be enough for Chrome to navigate certain web pages.
You can change this value by running your container with, say,
--shm-size=256m.
$3
This is thrown by Runtime.evaluate when the browser-side promise gets
collected by the Chrome's garbage collector, this happens when the whole
JavaScript execution environment is invalidated, e.g., a when page is navigated
or reloaded while a promise is still waiting to be resolved.
Here is an example:
`
$ chrome-remote-interface inspect
>>> Runtime.evaluate({expression: new Promise(() => {}), awaitPromise: true})
>>> Page.reload() // then wait several seconds
{ result: {} }
{ error: { code: -32000, message: 'Promise was collected' } }
``