本文翻譯自:chrome-remote-interface
原文更新時間:July 21,2017
譯者:Pandorymhtml
Chrome 調試協議 的接口,他提供一個使用 JavaScript API 的簡單的命令和通知抽象,幫助使用 Chrome(或任何其餘合適的實現)。前端
這個模塊是眾多 第三方協議客戶端 之一。node
下列片斷加載https://github.com
,並轉儲每個請求:webpack
const CDP = require('chrome-remote-interface'); CDP((client) => { // extract domains const {Network, Page} = client; // setup handlers Network.requestWillBeSent((params) => { console.log(params.request.url); }); Page.loadEventFired(() => { client.close(); }); // enable events then start! Promise.all([ Network.enable(), Page.enable() ]).then(() => { return Page.navigate({url: 'https://github.com'}); }).catch((err) => { console.error(err); client.close(); }); }).on('error', (err) => { // cannot connect to the remote endpoint console.error(err); });
在 維基 中尋找更多的例子,特別注意,上面的例子能夠改寫成 async / await
原型。ios
你可能還想查看 FAQ。git
npm install chrome-remote-interface
全局安裝(-g
)能夠使用附隨的客戶端。github
這個模塊應該和每個實現了 Chrome 調試協議 的應用程序一塊兒工做。特別是,它已經對如下實現進行了測試:web
Implementation | Protocol version | Protocol | List | New | Activate | Close | Version |
---|---|---|---|---|---|---|---|
Google Chrome | tip-of-tree | yes | yes | yes | yes | yes | yes |
Microsoft Edge | partial | yes | yes | no | no | no | yes |
Node.js (v6.3.0+) | node | yes | no | no | no | no | yes |
Safari (iOS) | partial | no | yes | no | no | no | no |
目標(target)的含義根據實現而差異,例如,每個 Chrome 標籤表明一個目標,而對於 Node.js 一個目標是當前視察的腳本。chrome
為了使用這個模塊,Chrome 自身或其餘實現的實例 須要運行在一個已知端口(默認為localhost:9222
)。shell
啟動 Chrome 時,使用--remote-debugging-port
選項,例如:
google-chrome --remote-debugging-port=9222
在 59 之後的版本,額外使用--headless
選項,例如:
google-chrome --headless --remote-debugging-port=9222
插入設備,並啟動 端口轉發, 例如:
adb forward tcp:9222 localabstract:chrome_devtools_remote
為了視察,WebView 必需 已配置調試,並且對應的進程 ID 必需是已知的。有幾種方法能夠獲得它,例如:
adb shell grep -a webview_devtools_remote /proc/net/unix
最後,能夠使用以下方式啟動端口轉發:
adb forward tcp:9222 localabstract:webview_devtools_remote_<pid>
安裝並運行 Edge 診斷配適器(Diagnostics Adapter)。
啟動 Node.js 使附加--inspect
選項,例如:
node --inspect=9222 script.js
安裝並運行 iOS WebKit 調試代理(Debug Proxy)。
這個模塊有一個附隨的客戶端應用程序,他可用於交互式的控制遠程實例。
該附隨的客戶端提供用於與 HTTP 前端交互的子命令(例如,List、New 等),運行--help
以顯示一個可用選項的列表。
下面是一些例子:
$ 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'
使用子命令inspect
能夠以 REPL 方式進行 命令執行 和 事件綁定。不像常規的 API,該回調被重寫了,以方便顯示命令的返回值和事件的信息。還有,這裏的事件綁定是簡化的,執行一個速寫方法(例如Page.loadEventFired()
)切換事件註冊。
記住,該 REPL 接口提供了實現。
這是一個簡單的片斷:
$ chrome-remote-interface inspect >>> Runtime.evaluate({expression: 'window.location.toString()'}) { result: { result: { type: 'string', value: 'https://www.google.it/_/chrome/newtab?espv=2&ie=UTF-8' }, wasThrown: false } } >>> Page.enable() { result: {} } >>> Page.loadEventFired() // registered { 'Page.loadEventFired': true } >>> Page.loadEventFired() // unregistered { 'Page.loadEventFired': false } >>> Page.loadEventFired() // registered { 'Page.loadEventFired': true } >>> Page.navigate({url: 'https://github.com'}) { result: { frameId: '28677.1' } } { 'Page.loadEventFired': { timestamp: 21385.383076 } } >>> Runtime.evaluate({expression: 'window.location.toString()'}) { result: { result: { type: 'string', value: 'https://github.com/' }, wasThrown: false } }
為了減少來自事件監聽器的數據顯示量,能夠提供一個過濾函數。在這個例子中,只顯示資源的 URL:
$ chrome-remote-interface inspect >>> Network.enable() { result: {} } >>> Network.requestWillBeSent(params => params.request.url) { 'Network.requestWillBeSent': 'params => params.request.url' } >>> Page.navigate({url: 'https://www.wikipedia.org'}) { 'Network.requestWillBeSent': 'https://www.wikipedia.org/' } { result: { frameId: '5530.1' } } { 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia_wordmark.png' } { 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2.png' } { 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/js/index-3b68787aa6.js' } { 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/js/gt-ie9-c84bf66d33.js' } { 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/img/sprite-bookshelf_icons.png?16ed124e8ca7c5ce9d463e8f99b2064427366360' } { 'Network.requestWillBeSent': 'https://www.wikipedia.org/portal/wikipedia.org/assets/img/sprite-project-logos.png?9afc01c5efe0a8fb6512c776955e2ad3eb48fbca' }
在 REPL和常規 API 中的每個協議對象都擁有在元信息中找到的描述符。此外,還添加了一個category
字段,譯確定成員是command
、event
或type
。
例如,學習如何調用Page.navigate
:
>>> 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' ] }
學習來自Network.requestWillBeSent
事件的參數:
>>> 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.' } } }
視察Network.Request
類型(注意,不一樣於命令和時間,類型使用大駝峯拼寫法):
>>> 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-remote-interface
默認使用本地版本的協議描述符。不停的更新這個文件,使用scripts/update-protocol.sh
並且推送到這個倉庫。
這種行為能夠改變在 connection 的remote
選項為true
,在這種情況,遠程實例要求本身提供協議描述符。
Chrome < 60.0.3097.0 不支持這麼作,因此當這種情況,將從源倉庫獲取協議描述符。
有三個選項能夠覆蓋上述行為:
使用特定的協議描述符 connection (protocol
選項);
用原始版本的命令和事件接口來使用前沿特性,這不會出現在 本地版本 的協議描述符;
更新scripts/update-protocol.sh
的本地副本(當使用npm install
獲取時不存在)。
這個模塊能夠運行在 Web 上下文,但有明顯的侷限性,即外部 HTTP請求(List,New 等)不能直接執行,所以為了使用它 用戶必須提供一個全局 criRequest
:
function criRequest(options, callback) {}
options
與在 Node.js http
模塊中的寫法一樣,並且callback
是一個接受兩個參數的函數:err
(JavaScript Error
對象,或null
)和data
(字符串返回值)。
只須要使用這個模塊,他就會工做:
const CDP = require('chrome-remote-interface');
使用 non-minified 版本手動運行 webpack :
DEBUG=true npm run webpack
生成一個 JavaScript 文件,這能夠使用 <script>
標籤。
在根目錄運行npm install
;
手動運行 webpack:
TARGET=var npm run webpack TARGET=var DEBUG=true npm run webpack
像這樣使用:
<script> function criRequest(options, callback) { /*...*/ } </script> <script src="chrome-remote-interface.js"></script>
該 API 由三部分組成:
DevTools 方法(對於那些 實現 的支持,例如,List、New 等);
創建連接;
該實際上的協議接口;
使用 Chrome 調試協議 連接一個遠程實例。options
是一個對象,並有下列可選屬性:
host
:HTTP 前端主機。默認為localhost
;
port
:HTTP 前端端口。默認為9222
;
secure
:HTTPS/WSS 前端。默認為false
;
target
:確定這個客戶端應該連接到哪個目標。行為根據類型的變化而變化:
一個function
,該函數以List
方法返回的數組做為參數,並返回一個目標、或目標在數組中的數值索引;
一個目標object
,如New
或List
方法的返回值;
一個string
,他表示原始 WebSocket URL,在這種情況,host
和port
不用於獲取目標列表,可是他們將用於使相對 URL 變得完整。
一個string
,他表示目標 id。
默認是一個實現了返回第一個可用目標的函數。(注意,最多可在同一目標上創建一個連接);
protocol
:Chrome 調試協議 描述符對象。默認使用根據遠程選項選擇的協議;
remote
:一個布爾值,指示協議是否必須遠程獲取,或者必須使用本地版本。若是設置了protocol
選項,這個選擇不會生效。默認值為false
。
這些選項在該CDP
類實例的全部操做都是有效屬性。除此以外, webSocketURL
字段包含當前使用的 WebSocket URL。
callback
是一個監聽器,將自動添加到返回EventEmitter
的connect
事件。當callback
缺省時,將返回一個Promise
對象,若是觸發connect
事件即為成功(fulfilled),若是觸發error
事件即為失敗(rejected)。
該EventEmitter
支持下列事件:
function (client) {}
當到 WebSocket 的連接已創建時觸發。
client
是一個CDP
類的實例。
function (err) {}
當不能訪問http://host:port/json
,或不能連接到 WebSocket 時觸發。
err
是一個Error
的實例。
取得該 Chrome 調試協議 描述符。
options
是一個對象,並有下列可選屬性:
host
:HTTP 前端主機。默認為localhost
;
port
:HTTP 前端端口。默認為9222
;
secure
:HTTPS/WSS 前端。默認為false
;
remote
:一個布爾值,指示協議是否必須遠程獲取,或者必須使用本地版本。若是不能完成請求,則使用本地版本。默認值為false
。
callback
在取得協議後執行,他將獲得下列參數:
err
:一個Error
對象,指明成功狀態;
protocol
:一個對象,擁有下列屬性:
remote
:一個布爾值,指明返回的描述符是不是遠程版本(取決於用戶選擇或錯誤);
descriptor
:該 Chrome 調試協議 描述符。
當callback
缺省時,將返回一個Promise
對象。
For example:
const CDP = require('chrome-remote-interface'); CDP.Protocol(function (err, protocol) { if (!err) { console.log(JSON.stringify(protocol.descriptor, null, 4)); } });
請求遠程實例的打開的可用目標 / 標籤的列表。
options
是一個對象,並有下列可選屬性:
host
:HTTP 前端主機。默認為localhost
;
port
:HTTP 前端端口。默認為9222
;
secure
:HTTPS/WSS 前端。默認為false
;
callback
在正確取得列表後執行,他將獲得下列參數:
err
:一個Error
對象,指明成功狀態;
targets
:該數組返回`http://host:port/json/list中包含的目標列表。
當callback
缺省時,將返回一個Promise
對象。
For example:
const CDP = require('chrome-remote-interface'); CDP.List(function (err, targets) { if (!err) { console.log(targets); } });
在遠程實例中創建一個新的目標 / 標籤。
options
是一個對象,並有下列可選屬性:
host
:HTTP 前端主機。默認為localhost
;
port
:HTTP 前端端口。默認為9222
;
secure
:HTTPS/WSS 前端。默認為false
;
url
:URL 用於載入新目標 / 標籤。默認為about:blank
。
callback
在創建目標後執行,他將獲得下列參數:
err
:一個Error
對象,指明成功狀態;
targets
:該對象,來自http://host:port/json/new
中包含的目標。
當callback
缺省時,將返回一個Promise
對象。
For example:
const CDP = require('chrome-remote-interface'); CDP.New(function (err, target) { if (!err) { console.log(target); } });
在遠程實例中激活一個目標 / 標籤。
options
是一個對象,並有下列可選屬性:
host
:HTTP 前端主機。默認為localhost
;
port
:HTTP 前端端口。默認為9222
;
secure
:HTTPS/WSS 前端。默認為false
;
id
:目標 id。必填,無默認值。
callback
在激活請求收到響應後執行,他將獲得下列參數:
err
:一個Error
對象,指明成功狀態;
當callback
缺省時,將返回一個Promise
對象。
For example:
const CDP = require('chrome-remote-interface'); CDP.Activate({'id': 'CC46FBFA-3BDA-493B-B2E4-2BE6EB0D97EC'}, function (err) { if (!err) { console.log('target is activated'); } });
關閉一個在遠程實例中打開的目標 / 標籤。
options
是一個對象,並有下列可選屬性:
host
:HTTP 前端主機。默認為localhost
;
port
:HTTP 前端端口。默認為9222
;
secure
:HTTPS/WSS 前端。默認為false
;
id
:目標 id。必填,無默認值。
callback
在關閉請求收到響應後執行,他將獲得下列參數:
err
:一個Error
對象,指明成功狀態;
當callback
缺省時,將返回一個Promise
對象。
For example:
const CDP = require('chrome-remote-interface'); CDP.Close({'id': 'CC46FBFA-3BDA-493B-B2E4-2BE6EB0D97EC'}, function (err) { if (!err) { console.log('target is closing'); } });
注意,當目標排隊等待刪除時,該回調將被觸發,但真正的刪除將異步執行。
從遠程實例請求版本信息。
options
是一個對象,並有下列可選屬性:
host
:HTTP 前端主機。默認為localhost
;
port
:HTTP 前端端口。默認為9222
;
secure
:HTTPS/WSS 前端。默認為false
;
callback
在正確獲得版本信息後執行,他將獲得下列參數:
err
:一個Error
對象,指明成功狀態;
info
:一個 JSON 對象,來自http://host:port/json/version
包含的版本信息。
當callback
缺省時,將返回一個Promise
對象。
For example:
const CDP = require('chrome-remote-interface'); CDP.Version(function (err, info) { if (!err) { console.log(info); } });
function (message) {}
當遠程實例通過 WebSocket 發送任何通知時觸發。
message
是接收的對象,他有下列屬性:
method
:一個字符串,描述該通知(例如)'Network.requestWillBeSent'
);
params
:一個對象,包含有效載荷(payload)。
參考 Chrome 調試協議 規格獲取更多信息。
For example:
client.on('event', function (message) { if (message.method === 'Network.requestWillBeSent') { console.log(message.params); } });
function (params) {}
當遠程實例通過 WebSocket 發送關於<domain>.<method>
的通知時觸發。
params
:一個對象,包含有效載荷(payload)。
這是一個實用的時間,他能夠很容易的偵聽特定的時間(看 'event'
),for example:
client.on('Network.requestWillBeSent', console.log);
function () {}
每次不存在 等待遠程實例響應的命令 時觸發。交互是異步的,序列化命令隊列的惟一方式是使用 send 方法提供的回調。這個時間起到一個屏障做用,並且在某些簡單情況下,能夠避免回調地獄。
鼓勵使用者廣泛的檢查每個方法的響應,並且在處理複雜的異步程序流是應該更喜歡 promises API。
例如,僅在啟用了Network
和Page
域的通知後才加載 URL:
client.Network.enable(); client.Page.enable(); client.once('ready', function () { client.Page.navigate({'url': 'https://github.com'}); });
在這個特殊的情況下,不強制序列化執行可能導致遠程實例不能正確的交付通知該客戶端。
function () {}
當實例關閉該 WebSocket 連接時觸發。
這可能發生在當用戶打開 DevTools 或關閉標籤。
向遠程實例發出一個命令。
method
是一個描述命令的字符串。
params
是一個對象,包含有效載荷。
callback
是在收到遠程實例關於這個命令的響應時執行,他將獲得下列參數:
error
:一個布爾值,指明成功狀態,來自遠程實例;
response
:一個對象,包含響應(result
字段,若是error === false
)或者錯誤知識(error
字段,若是`error === true)。
當callback
缺省時,將返回一個Promise
對象,並且 fulfilled/rejected 狀態取決於error
屬性。
在低等級 WebSocket 錯誤的情況下,該error
包含產生的Error
對象,並且沒有response
返回。
注意,他的字段id
在 Chrome 調試協議 中提到是內部管理的,並且不向用戶揭露。
For example:
client.send('Page.navigate', {'url': 'https://github.com'}, console.log);
就是一個速寫:
client.send('<domain>.<method>', params, callback);
For example:
client.Page.navigate({'url': 'https://github.com'}, console.log);
就是一個速寫:
client.on('<domain>.<event>', callback);
惟一的區別是,當callback
省卻時,事件只註冊一次,並返回一個Promise
對象。
For example:
client.Network.requestWillBeSent(console.log);
關閉到遠程實例的連接。
callback
當該 WebSocket 成功關閉時執行。
當callback
缺省時,將返回一個Promise
對象。
調用Domain.method
,我獲得提示Domain.method is not a function
這意味著你正在使用的協議描述符中不包含Domain.method
。若是你確信你的 Chrome 實例支持這種方法,你能夠直接調用它:
client.send('Domain.method', ...);
或者詢問 Chrome 正確的協議描述符是什麼:
CDP({remote: true});
看 這裡,獲得更多信息。
調用Domain.method
,我獲得提示Domain.method wasn't found
這意味著你正在使用的協議描述符包含一個不受 Chrome 實例支持的方法。最有可能的緣由是,你正在嘗試使用一個最前沿的功能,試試升級到新的 Chrome 版本吧。
看 這裡,獲得更多信息。
檢查正確的協議描述符:
$ chrome-remote-interface inspect --remote
Headless Chrome 問題?
記住,--headless
Chrome 是相對較新的,他的操做指南(in Chrome)還在制定中。若是你認為遇到了一個 Bug,那麼就看看這些開放性 issues,尤爲是 外部 issues。
為什麼當我在 Docker 容器中運行 Chrome 時,個人程序會停滯或表現得出乎意料呢?
這是因為在 Docker 的默認設置中,/dev/shm
的大小被設置為 64MB,這可能不足以讓 Chrome 跳轉到某些網頁。
你能夠改變這個值,在運行你的容器時附帶說,--shm-size=256m
。