🧊

WebRTCなコードをE2Eテストする

という試みをやってみたのでその学びをメモ。

もちろん全てのケースをカバーできたわけではなく、無限に気になることはあるけど、まあきっかけといことで。

環境はmacOSです。CIで動かすのは続編としてまたいつかブログに書きます。

何を使うか

いわゆるE2E(URLでページ開いてボタン押したらどうでこうで)をやりたい場合、フロントエンド的な観測範囲だと現時点ではこの2択になるのかなーと思う。

他にオススメあれば知りたいです。

Selenium

https://github.com/webrtc/samples/tree/gh-pages/test とかでも使われてる例があって身近ではあるけど、正直使いたくない・・。

https://github.com/webdriverio/webdriverio とかあわせていれれば多少のシンタックスはマシになるけど、もう少し気の利いたやつが欲しい。

そもそも2つしか選択肢ないけど、最後の手段として一旦置き。

TestCafe

Selenium(もといWebDriver)に依存してないし、シンタックスもモダン。`expect()`とかのアサーション機能も同梱されててわかってる感。

これでやりたいことができれば何の不満もないなーということで、ちょっと触ってみたところ、いけそうだったので今回はTestCafeを採用。

というわけで。

TestCafeでWebRTCなコードをE2Eテストする

GitHub - leader22/webrtc-e2e: E2E test for WebRTC app with TestCafe.

このリポジトリには簡単なシナリオしか置いてないけど、概ね次のような仕様。

  • WebRTCの通信を、1ページ内で行う
    • 1ページに2つのPeerを用意する
  • ボタンを押すとP2P開始
    • 両者ともに通信できてることを検証する

`/tests`配下とか見てもらえれば、割とシュッとE2Eのテストができてるのがわかるかと。

以下はハマりどころのメモです。

素直に`getUserMedia()`できない

というのも、gUMすると出る「許可しますか?」ダイアログ。あれがTestCafeからさわれない。なんてこった。

Permission dialogs aren't handled · Issue #1727 · DevExpress/testcafe · GitHub

なので、基本的にはFakeデバイスを使うしかない。

Chromeなら、`--use-fake-device-for-media-stream --use-fake-ui-for-media-stream`を付けて起動する。

Firefoxなら、`{ fake: true }`を渡してgUMする。

もしくは、別の方法で`MediaStream`を生み出して使う。

getUserMedia()以外でMediaStreamを用意するには - console.lealog();

httpsOnlyなAPIは使えない

↑のとも関連するけど・・。

TestCafeの実行イメージはこんな感じ。

  • TestCafeが母艦となるページを開く
  • その中で、対象のページを順次開いてテスト実行する

で、この母艦となるページが`https`ではないので、結局最終的に叩かれるページのAPIが`http`で使えないやつの場合は使えない。

Run testcafe with https protocol · Issue #1985 · DevExpress/testcafe · GitHub

うーん、つらい。

`localhost`おすすめ

↑な理由で、だいたいテストを実行したいページを自作するハメになると思う・・。

その際のハマりポイントをもうひとつ。

TestCafeは仕組み的に、HTMLファイルを用意して`file://`でアクセスしてテストってことができる。

ただこれだと、`http://localhost` だと通るのに、 `file://` だと使えないAPIとかでコケる可能性がある。`HTMLCanvasElement#captureStream()`とか。

なので、TestCafeでテスト実行するときにだけWebサーバーを建ててそこでページを開く方針がオススメ。

Command Line Interface | TestCafe

それ用のコマンドライン引数もある。

ブラウザを引数つきで起動する

やっとさっきの話でFakeデバイスを使いたい場合の、ブラウザのフラグをどう渡すか。

結論としては、現状の`testcafe`コマンドからは渡せません!

Allow passing flags for browsers · Issue #905 · DevExpress/testcafe · GitHub

なので自分でNodeのスクリプトを書いて、そこで指定する。
こんな感じ。

webrtc-e2e/run.js at master · leader22/webrtc-e2e · GitHub

テストのスピードを指定する

TestCafeが実行するブラウザ操作やテストのアサーションなど、各種挙動の機敏さを指定できる。

`speed`っていうまんまなパラメータがあって、これを指定するのが重要。

というのも、WebRTCみたく通信のラグを考慮しないといけない場合、フルスピードで動かすとだいたいコケる。

Command Line Interface | TestCafe

指定できるのは`0.01`から`1`で、ローカルどうしの通信だと、だいたい`0.5`くらいがちょうどいい体感です。

実はテストコード中で、任意の箇所でsleepするみたいなAPIもTestCafeは用意してある。(`t.wait()`ってやつ)
テストコードを汚す覚悟でそっちを使うのもアリかと。

Chrome上でWebAudioは使えない

これはあまり深追いしてないのでもしかしたら違うかも。

807017 - WebAudio user-gesture warning for audio context prevents any audio output - chromium - Monorail

というように、WebAudioで音を鳴らすのにユーザー入力を介する必要が出てくる。

そしてこの「ユーザー入力」を、TestCafeで起こしたクリックとかのイベントで賄えないっぽい。

なので、どうあがいてもこの制限を突破することができずに詰む。

ちなみに、Firefoxだと動く。(そのうち動かなくなりそうでもあるけど・・)

WebRTCで通信してることをどう判断するか その1

実際に、

  • ボタンを押してP2Pでつながったこと
  • `video`の表示が更新されてること

を、どうやってテストするのかという話。

ケースに応じて2つの観点をチェックした。

ひとつは、`RTCPeerConnection`とかSDKのイベントを使って、`console.log()`する方法。

TestCafeのテストコードで使える`getBrowserConsoleMessages()`ってのを使うと、その時点でのコンソールの出力を読める。

これで、既定の回数イベントのログがあるかどうかを見る。

WebRTCで通信してることをどう判断するか その2

もうひとつ、`video`の表示をチェックする方法。

これも単純で、`video`を`canvas`に転写して、`toDataURL()`で比較する。

通信開始前に取得したURLと、通信開始後に取得したURLが異なる場合、なんらかの変化があった = 通信できたとする。

ここで1つハマったのは、

  • とある`MediaStream`がある
  • ローカルではそれを`video.srcObject`で表示してる
  • そのMediaStreamをWebRTC経由でリモートに送って、同様に`video`で表示

この場合、一見どちらの`video`にも同じ映像が表示(遅延を考慮しない場合、というか、単色静止画のストリームだったとして)されていても、`toDataURL()`すると異なる結果になる。

WebRTCで通信するときのエンコード処理みたいなので微妙に色味が変わるからなんやろうと予想。

おわりに

という感じで色々ハマりポイントはあったけど、いちおうやりたかったことはできた。

あとはこれをCIで動かすところと、異なるブラウザ同士でどうにか通信するテストを書けないかを模索してみて、また何かあれば書くかも。