6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

シェルスクリプトからwebsocat(WebSocket)でChrome DevToolsに接続しブラウザを自動操作する

Last updated at Posted at 2021-03-23

はじめに

この間「シェルスクリプト用の WebDriver bindings」を作ったのですが、どうやら世の中は更に進んでおり、Chrome Dev Protocol (CDP) 経由でより高機能な Chrome DevTools の機能による自動操作が使われてきているということを知りました。WebDriver は W3C で標準化されてるので多くのブラウザで使えるというメリットがあるとは言え、機能やパフォーマンスは DevTools の方が優れており、Chrome 以外のブラウザにも搭載されつつあるので対応させたいところです。しかし CDP では HTTP ではなく WebSocket を使います。はたしてそれがシェルスクリプトから呼び出せるのか?ということでやってみました。

websocat

WebSocket は ハンドシェイクにこそ HTTP と同じ形式を使用しますが HTTP ベースのプロトコルではありません。HTTP であれば curlwget コマンドが使えそうですが CDP は WebSocket なので使うことができません。そもそもシェルスクリプトは POSIX 準拠の範囲ではネットワークに対応していません。bash, ksh であれば /dev/tcp から、zsh であれば net/tcp モジュールを使って TCP プロトコルを使うことが可能ですが WebSocket に対応させるのは大変ですし、どちらにしろ使えるシェルが限られます。

そこでいろいろと探してみるとどうやら websocat を使えば WebSocket 通信が出来きそうだということがわかりました。そして都合のいいことに CDP 呼び出しのサンプルまで書いてありました。(読みやすいように整形しています。JSONは実際は一行です。)

$ chromium --remote-debugging-port=9222 &

$ curl -sg http://127.0.0.1:9222/json/new \
  | grep webSocketDebuggerUrl | cut -d'"' -f4 | head -1
ws://127.0.0.1:9222/devtools/page/A331E56CCB8615EB4FCB720425A82259

$ echo 'Page.navigate {"url":"https://example.com"}' | websocat -n1 --jsonrpc \ 
  ws://127.0.0.1:9222/devtools/page/A331E56CCB8615EB4FCB720425A82259
{
  "id":2,
  "result":{
    "frameId":"A331E56CCB8615EB4FCB720425A82259",
    "loaderId":"EF5AAD19F2F8BB27FAF55F94FFB27DF9"
  }
}

この通りにやれば動くであろうということでやってみたのですが以下のようなエラーがでて動きませんでした。

{
  "error":{
    "code":-32600,
    "message":"Message has property other than 'id', 'method', 'sessionId', 'params'"
  }
}

問題を解決する鍵は 'Page.navigate {"url":"https://example.com"}'--jsonrpc です。前者は websocat が対応してる特殊なフォーマットで --jsonrpc オプションによって以下のような JSON-RPC 2.0 形式に変換されます。

{
  "jsonrpc": "2.0",
  "method": "Page.navigate",
  "params": {"url":"https://example.com"},
  "id": 1
}

エラーメッセージから jsonrpc キーが余計なのでは?と思い、以下のように jsonrpc を削って JSON で直接送信してみると動作しました。

echo '{"method":"Page.navigate","params":{"url":"https://example.com"},"id":1}' \
  | websocat -n1 ws://... 

しかし params キーにオブジェクトが使えるのは、ここによると JSON-RPC 2.0 からのようなのでフォーマット自体は 2.0 で間違いなさそうです。おそらく CDP 側で余計なバリデーションをしてるバグがあるような気がします。

Selenium/WebDriver bindings for shell script との統合

さて私がやりたいのは Selenium/WebDriver bindings for shell script との統合です。とりあえず簡易的に組み込んでみました。

# !/bin/sh

set -eu

. ./lib/webdriver.sh

chrome_options() {
  echo '{ "args": [] }'
  # echo '{ "args": ["--headless"] }'
}

WebDriver driver="$(ChromeDriver chrome_options "http://localhost:9515")"
debugger=$(driver capabilities | jq -r '.["goog:chromeOptions"].debuggerAddress')
wsdebugger=$(curl -sg "http://$debugger/json/new" | jq -r .webSocketDebuggerUrl)
json='{"method": "Page.navigate", "params": {"url":"https://example.com"}, "id": 1}'
echo "$json" | websocat -n1 "$wsdebugger"

sleep 3
driver quit
unset -f element driver

接続先アドレスを WebDriver から取得しているだけでやっていることは大して変わりません。動作させることは出来たのでこれをわかりやすいインターフェースに変更するすれば終わりです。例えばこんな感じにすると良さそうです。

WebDriver driver="$(ChromeDriver chrome_options "http://localhost:9515")"
driver Page.navigate '{"url":"https://example.com"}'

あとは双方向通信が必要な場合はどうするんだ?という問題があるのですが、たぶん多くは双方向通信は必要ないと思うので大部分の CDP の機能は使えるんじゃないかと思います。WebSocket から知らなかったので調べるのに時間がかかりましたが、websocat のおかげで思ったより簡単に DevTools の機能が使えそうです。なお CDP でどんな機能が使えるかは https://chromedevtools.github.io/devtools-protocol/ を参照してください。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?