読者です 読者をやめる 読者になる 読者になる

ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

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 つのコマンドが定義されています。

前者は同期的なスクリプト実行をサポートし、後者は非同期的なスクリプト実行をサポートする、という風に思ってしまうところですが、なんとどちらも非同期のスクリプト実行をサポートしています! (な、なんだってー

どちらも渡されたスクリプト (リクエストボディに含まれる script プロパティの値) を関数本体 (FunctionBody) として扱って promise-calling の形で実行するのですが、前者の方は関数の返り値をレスポンスに使うのに対して、後者の方は Promiseresolve を引数リストに追加したうえでスクリプトを呼び出し、関数の返り値を無視する (つまり、引数リストに追加された 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 勧告よりも古い仕様である SeleniumJSON 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
}

ちなみにこれも W3C 勧告とは違う JSON wire protocol に沿ったものになっています。

geckodriver と ChromeDriver の現状

上記 Driver のそれぞれの実装状況を見てみました。

geckodriver の実装状況

  • W3C 勧告のコマンド (Execute Script コマンドと Execute Async Script コマンド) は実装されている。 Promise も扱える。
  • JSON wire protocol で定義されていたエンドポイントはバージョン 0.16.0 で削除済み!

ChromeDriver の実装状況

  • W3C 勧告のコマンド (Execute Script コマンドと Execute Async Script コマンド) は実装されていない。
  • JSON wire protocol で定義されていたエンドポイントは使用可能。 (Promise は扱えない。)

どちらでも動かすために

WebDriverIO の実装を見ると、古い仕様のエンドポイントでコマンドを発行してみて、エラーになったら新しい仕様でコマンドを発行しなおす、ということをやっていました。

厳しい世界ですね……。

*1:W3C 勧告を読んだ感じ、なんか remote end steps の文書がおかしい感じがしますね。 まあ意図は掴めるんだけども。