WebDriver によるスクリプト実行の現状 (geckodriver と ChromeDriver)
WebDriver とは、Web ブラウザを外部から操作するための標準化された API です。 詳細は先日書きましたのでご参照ください!
今回は、WebDriver のコマンドで JS スクリプトを実行させる方法の説明です。 W3C WebDriver API を見ながらどういう API になっているのか説明します。 また、geckodriver および ChromeDriver での現在の実装状況についても書いています。
これらは 2017 年 5 月 10 日現在の情報ですので、最新の情報は最新の W3C 勧告や Driver 実装を見てください。
スクリプトを実行させる API
W3C WebDriver での仕様
W3C WebDriver 勧告では、スクリプトを実行するための下記の 2 つのコマンドが定義されています。
- Execute Script コマンド (
POST /session/{session_id}/execute/sync
) - Execute Async Script コマンド (
POST /session/{session_id}/execute/async
)
前者は同期的なスクリプト実行をサポートし、後者は非同期的なスクリプト実行をサポートする、という風に思ってしまうところですが、なんとどちらも非同期のスクリプト実行をサポートしています! (な、なんだってー
どちらも渡されたスクリプト (リクエストボディに含まれる script
プロパティの値) を関数本体 (FunctionBody) として扱って promise-calling の形で実行するのですが、前者の方は関数の返り値をレスポンスに使うのに対して、後者の方は Promise
の resolve
を引数リストに追加したうえでスクリプトを呼び出し、関数の返り値を無視する (つまり、引数リストに追加された resolve
を関数内で呼ぶことで結果を渡す) という違いがあります。 *1
前者の方は以下のようなスクリプトを受け付けるわけですね。
var waitTime = arguments[0] || 2000; var p = new Promise(function (resolve, reject) { setTimeout(function () { resolve() }, waitTime); }).then(function () { return "Hello!"; }); return p;
後者で同様の処理を実行させるには、以下のようにする必要があります。
// 引数リストの最後に追加された resolve 関数を受け取る。 var callback = arguments[arguments.length - 1]; var waitTime = arguments[0] || 2000; var p = new Promise(function (resolve, reject) { setTimeout(function () { resolve() }, waitTime); }).then(function () { return "Hello!"; }); // 引数リストの最後に追加された resolve 関数を呼ぶことで結果を返す。 callback(p);
ちなみに関数に渡される引数は、 リクエストボディの args
プロパティで指定できます。 つまり、リクエストボディは以下の形式です。
{ "script": "return 100 + arguments[0];", "args": [200] }
JSON wire protocol での仕様
さて、どちらも非同期なスクリプト実行を扱えるのであれば、なぜ W3C WebDriver には 2 つのスクリプト実行コマンドが用意されているのでしょうか? おそらく、W3C 勧告よりも古い仕様である Selenium の JSON wire protocol から引き継いだものだと思われます。 JSON wire protocol でも 2 つのスクリプト実行コマンドが用意されていました。
これら 2 つは、(W3C 勧告の 2 つとは違って) 前者が同期的なスクリプト実行用、後者が非同期的なスクリプト実行用と、明確に役割が分かれています。 リクエストボディの型や、非同期実行での結果の返し方 (引数リストの最後に追加されたコールバック関数に値を渡す) などは W3C 勧告のコマンドと同じです。 ただし、これらは Promise
を扱えません。
JSON wire protocol でスクリプトをそのまま W3C 勧告の仕様にあった Driver 実装でも使えるように、W3C 勧告の方でも 2 種類のコマンドが定義されたのだろうと思われます。
ChromeDriver で非同期スクリプトを実行すると
普通に ChromeDriver で非同期スクリプトを実行させようとすると、以下のメッセージが返ってくることがあります。
"value": { "message": "asynchronous script timeout: result was not received in 0 seconds..." }
これは、スクリプトのタイムアウトが 0 s に設定されているためです。 タイムアウト時間を設定することができるので、先にタイムアウト時間を設定する必要があります。 POST /session/{session_id}/timeouts
というエンドポイントに、以下のようなリクエストボディで HTTP リクエストを発行しましょう。
{ "type": "script", "ms": 2000 }
geckodriver と ChromeDriver の現状
- geckodriver 0.16.1
- ChromeDriver 2.29.461571
上記 Driver のそれぞれの実装状況を見てみました。
geckodriver の実装状況
ChromeDriver の実装状況
どちらでも動かすために
WebDriverIO の実装を見ると、古い仕様のエンドポイントでコマンドを発行してみて、エラーになったら新しい仕様でコマンドを発行しなおす、ということをやっていました。
厳しい世界ですね……。