本記事はDMMグループ Advent Calendar 2019の24日目の記事です。
どうも、DMM.comの動画配信事業部というところでエンジニアやってます@_tinojiという者です。3年連続でクリスマスイブを担当させていただけて光栄です
クリスマスといえばE2Eテストですね。
今年、動画プレイヤーのちょっとしたE2Eテストを導入して、今も元気に隣の席のMac miniとWindowsマシンが ログイン=>動画再生
の操作をテストしてくれています。動画プレイヤーのテストというニッチなアレなのですが、意外にも汎用的なエッセンスが多く含まれていて、入門にいいのでは?と思ったので、ハンズオン形式の記事にしてみた次第です。
tl;dr
書いてあること
- 動画プレイヤーを再生するE2EテストをKatalon Studioを使ってやってみる
- CIで実行してみる
- Jenkinsで定期実行してヘルスチェックっぽくしてみる
- 令和になってもE2Eはつらい
書いてないこと
- E2Eテストの思想、お作法、ベストプラクティスなど
- Katalon Studioの細かい説明・使い方など
E2Eテスト
E2Eテストとは簡単に言うと「ブラウザでポチポチする動作を自動で行うことでWebアプリケーション/サイトが正常に動作しているかを確かめる」テストのことです。Webにおけるテストの中で、最も上位の階層に位置するテストです。
本気でやろうとすると大体つらい思いをすることで有名です。
動画プレイヤーでもE2Eテストしたい
仮に、とある動画配信サービスを提供しているWebサイトがあり、そのサービスは(サポート切れ間近のもの含めた)各種OS・(サポート切れ間近のもの含めた)各種ブラウザ・(サポート切れ間近のもの含めた)各種ブラウザプラグインをサポートしていて、またユーザーのニーズに応えるため様々な課金体系(従量課金・月額サブスクリプション・無料コンテンツなどなど)があり、さらに作品のジャンルなどに応じてページやドメインが分かれている、としましょう。
おそらくそのサイトの色んなところには、それぞれ少しずつフロント側の実装やプレイヤー自体の実装が異なる動画プレイヤーが埋め込まれていることでしょう。例えばその数が10個だとしましょう(実際にはモバイルアプリやテレビ用アプリなども大量にあると思いますが、今回はWebに限定します)。
そしてサポートしているOSがメジャーバージョンの区別を含めて5つ、サポートしているブラウザが5種類だとします。
さて、これら全てに関わる動画再生ライブラリの修正を行ったとするとどうなるでしょうか?もし網羅するなら10×5×5=250
個のテストをしないといけないですね!アハハ。
自分はこういったリリース前検証に使いたくてE2Eテストを導入しました。また、ブラウザのバージョンアップ等によって動画が再生できなくなっていないかをヘルスチェックしたい、という要求もありました。
ツール
E2Eテスト関連のツールはかなりたくさんあります。Seleniumをラップしたようなフレームワーク・GUIツールだけでもそんなにいっぱいあるのか・・・という気持ちです。なかなかスクラッチでSeleniumのコードをガリガリ書くのは大変なので、なんらかのツールを使うのはほぼマストな気がします。
- GUIテストツール一覧
- Best Automation Testing Tools for 2020 (Top 10 reviews)
- 30 Best GUI Testing Tools For GUI Test Automation
Katalon Studio
今回はKatalon Studioというツールを使ってみました。E2Eテストツール選定時にありがちな要件と、Katalon Studioがそれらをどう満たすかをまとめてみました。
-
とりあえず無料で運用してみたい
- 基本的な機能は無償使用可能。
- version 7.0.0リリース時から大きな改変があったので注意(参照)。
-
操作のレコーディング機能が欲しい
- ある。
-
リポジトリでテストケースを管理して、PushするとCIでテストしてほしい
- Dockerイメージがあり、CircleCIで実行可能。
-
定期実行してヘルスチェックを行いたい
- テストスイートをコマンドラインから実行可能で、ジョブスケジューラと組み合わせれば定期実行できる。
-
そこそこいい感じのGUIだと嬉しい
- Eclipseライクで十分使えるGUI。
Katalon Studioについては、以下の記事で分かりやすく紹介されています。
Katalon Studioとはどんなソフトウェアか
ハンズオン
やっていきます。動画プレイヤーにはhls.jsのデモページでも使いましょう。まぁvideoタグで再生できればなんでもOKです。
使用したバージョンは以下です。Katalon StudioにDockerが追いついていなかったので、少しバージョンがずれています。
- Katalon Studio: v7.2.1
- Docker image(katalonstudio/katalon): v7.1.2
- OS: macOS 10.14.6
- ChromeDriver: 79.0.3945.36
- Chrome: 79.0.3945.88
サンプルのリポジトリ
完成形はこちらのリポジトリにまとめてあります。
https://github.com/tinoji/katalon-video-player-test
レコーディングする
Katalon Studioをダウンロードし、プロジェクトを新規作成します。アカウントのアクティベートが必要なので適当にサインアップします。プロジェクトの作成直後に表示されるヘルプページにある通り、↓このボタンを押すとレコーディングできます。
レコーダーウィンドウが起動します。URLに https://hls-js.netlify.com/demo/ を入力します。URLを入力したあと、右上からChromeアイコンを選んでクリックするとレコーディングが始まります。
レコーディングが始まったら、以下の操作を行います。
プレイヤー部分をクリックして再生を開始 => もう一度クリックして再生を停止 => ブラウザを閉じる
するとこんな感じに操作が記録されます。OKを押すとテストケースが生成されます。
ChromeDriverのダウンロード(optional)
前述のバージョン通りにやると起きませんが、Chromeのバージョン次第では以下のようなエラーが起きることがあります。Seleniumでもよく見るやつです。
公式からChromeDriverを落として、Katalon Studioが参照するパスに置けばOKです。
・・・と思ったらGUIでポチポチ更新できました。Tools > Update WebDrivers
です
Actionの追加
ネットワークが遅くて動画の再生がなかなか開始されない、というような状況を考慮しておきたいので、再生開始/停止の操作の間に適当にDelayを入れます。
テストケースの実行
テストケースを実行すると先程の操作が再現され、親の顔より見たBig Buck Bunnyが再生されます。簡単ですね。
ループを使ってみる
実際にやるときは複数の動画でテストしたいはずです。hls.jsのデモページではm3u8のURLを入れるとその動画が再生できるようになっています。URLはこのへんから拾ってくればよいでしょう。
もう一度レコーディングしてみます。起動 => m3u8のURLを入力 => Applyボタンをクリック => 再生 => 停止
をレコーディングすると、こんな感じになるはずです。
URLを入力 => 再生
の部分を繰り返せば複数の動画を再生できます。こういうときはFor Loop
Statementを使います。
さっきの操作をループ配下にドラッグして移動させます。ループはList型にして回します。リストをこんな感じで定義して、、、
Set Text
の値はVariable
に変更し、ループで指定した変数と同名にします。
この辺の設定はGUIでやっているとちょっと分かりにくいのですが、コードで見るとなんてことないです。エディタの下の方にあるScript
タブをクリックするとコードが表示されます。
WebUI.openBrowser('')
WebUI.navigateToUrl('https://hls-js.netlify.com/demo/')
for (def url : ['https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8'
, 'https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8']) {
WebUI.setText(findTestObject('Object Repository/Page_hlsjs demo/input_here_streamURL'), url)
WebUI.click(findTestObject('Object Repository/Page_hlsjs demo/div_Persist _235cfb'))
WebUI.click(findTestObject('Object Repository/Page_hlsjs demo/video_Apply_video_1'))
}
WebUI.closeBrowser()
なんてことないfor文であることが分かります。ポチポチしていてよく分からなくなったらコードを書いちゃえばいいです。コードを編集すると操作アイテムの方にも反映されます。
テストの成功条件を変更する
このままでは、動画をクリックして再生ができていなかったとしても、クリックができていればテストが通ってしまいます。本当に再生が行われたかどうかをテストする必要があります。videoタグのプロパティを使う方法ぐらいしか思いつかなかったので、それでやります。
こんなjavascriptを実行すると、再生を停止したところまでの時間が取れます。
document.getElementsByTagName("video")[0].played.end(0)
全く再生されてないと例外になります。
Uncaught DOMException: Failed to execute 'end' on 'TimeRanges': The index provided (0) is greater than or equal to the maximum bound (0).
at <anonymous>:1:50
例外が吐かれた場合はもちろん、「Delayした時間に対して再生した時間が短すぎてもNG」という条件にしてみます。
Execute JavaScript
というkeyword(Katalon StudioではUI操作を含むあらゆるアクションをkeywordと呼ぶらしい?)を使用して実行し、変数に代入します。GUIで作ってもいいですし、コードで書いてもOKです。コードで書くとこんな感じです。
Number playedDuration = WebUI.executeJavaScript('return document.getElementsByTagName("video")[0].played.end(0)', [])
playedDuration
が閾値よりも小さいかどうかはVerify Less Than
keywordで判断可能です。
if (!WebUI.verifyLessThan(THRESHOLD, playedDuration)) {
// ここでテストを落とす
}
if文の中でテストを落としてメッセージを出すのはFailure Handlingと、KeywordUtilのmarkErrorAndStop()
を使えば可能です。packageをインポートする必要がありますが、Ctrl+Shift+O
で自動インポート可能です(自動ではインポートされないものもあるので注意・・・ )。
if (!WebUI.verifyLessThan(THRESHOLD, playedDuration, FailureHandling.OPTIONAL)) {
KeywordUtil.markFailedAndStop("Played duration is too short. Maybe failed to play the video: " + url)
}
ちなみに、こういったtipsはフォーラムを探すと見つけやすいです。
Katalon Community: FailureHandling.OPTIONAL custom message print issue using KeywordUtil
Delayの時間と閾値を適当に決めて実行してみると、想定通りの結果になると思います。
Test Dataを使用する
以上で一通りのテストができるようになりました。あとはテストケースをゴリゴリ増やしていけばいいのですが、リストで定義したURLが増えてくると、一覧性に欠けたりテストケースをまたげなかったりして面倒になってきます。Test Data
という概念があるのでそれを使うと解決できます。
Data Files > New > Test Data
から作成します。Data Type
はExcelファイルとかも使えますが、Internal Data
にします。
for文で使うときはこんな感じです。先程のfor文を書き換えて同様に動くことを確認してみてください。
InternalData urls = findTestData('test_urls')
for (def index : (0..urls.getRowNumbers() - 1)) {
String url = urls.internallyGetValue('URL', index)
...
}
Custom Keywordを定義する
ちょっとコードが長くなってきました。メソッド化したいところですが、テストケース内に定義すると別のテストケースで使えないので、Custom Keyword
というものを使ってみます。Keywords > New > Keyword
で作成します。パッケージを作成することもできるのでその中に作ってもOKです。
public class VideoPlayer {
static final int PLAY_DELAY = 20
static final int PLAYED_DURATION_THRESHOLD = 5
/**
* Verify video played
* @param dataFile data file name
*/
@Keyword
public static void verifyPlayed(String dataFile) {
InternalData urls = findTestData(dataFile)
for (def index : (0..urls.getRowNumbers() - 1)) {
String url = urls.internallyGetValue('URL', index)
WebUI.setText(findTestObject('Object Repository/Page_hlsjs demo/input_here_streamURL'), url)
WebUI.click(findTestObject('Object Repository/Page_hlsjs demo/div_Persist _235cfb'))
WebUI.delay(PLAY_DELAY)
Number playedDuration = WebUI.executeJavaScript('return document.getElementsByTagName("video")[0].played.end(0)', [])
if (!WebUI.verifyLessThan(PLAYED_DURATION_THRESHOLD, playedDuration, FailureHandling.OPTIONAL)) {
KeywordUtil.markFailedAndStop("Played duration is too short. Maybe failed to play the video: " + url)
}
}
}
}
こんな風にすると、Test Data名を渡せばテストができて、呼び出し元(テストケース)は一行で済みます。
VideoPlayer.verifyPlayed('test_urls')
ループ自体はテストケースのコード内で回して、メソッドにはURL自体を渡した方がシンプルかもしれません。よしなにどうぞ。
コマンドラインからテストを実行する
以上でテストのコード自体は大体いい感じになりました。以降でCIや定期実行をやっていくのですが、その際にコマンドラインから実行することになるので、その方法を紹介しておきます。このへんのドキュメントはこちら。
テストスイートの作成
コマンドラインで実行できるのはここまでで作成したTest Case
ではなくTest Suite
(複数のテストケースをまとめたもの)です。まずはこれを作ります。Test Suites > New > Test Suite
からテストケースを選択するだけです。
コマンドの生成
作成したら、テスト実行ボタンの近くにあるターミナルっぽいボタンを押します。
テストスイートを選択してGenerate Commandします。Run with
はChromeで。
こんなコマンドが出力されます。
./katalonc -noSplash -runMode=console -projectPath="/Users/kikuchi-hiroaki/Desktop/katalon-video-player-test/katalon-video-player-test.prj" -retry=0 -testSuitePath="Test Suites/TestSuite01" -executionProfile="default" -browserType="Chrome" -apiKey="**********"
ドキュメントに書いてありますが、バージョン6まではKatalon Studio自体のバイナリでコマンドラインから実行できたのが、バージョン7からKatalon Studio Engineが必要になりました。ダウンロードページからダウンロードしてApplicationsディレクトリに移動しておきます。
Macの場合katalonc
は/Applications/Katalon\ Studio\ Engine.app/Contents/MacOS
にあるので、そこにcdするかフルパスを指定して使用します。
ターミナルから実際に実行して動作を確認します。Katalon Studioから行っていた動作が再現されて、テストケースをPassすればOKです。
CIでテストを回す
リポジトリにテストケースを追加していく形になりますが、「テストケース(あるいはコード)自体が間違っている」という状況を防止するためにも、PRを出したタイミングなどにCIでチェックしてほしいところです。
Katalon StudioにはDockerイメージがあるので、これを使用してCircleCIでテストを回してみます。もちろんGitHub Actionsを使ってもOKです。Dockerイメージの使い方はREADMEに書いてあります。イメージのバージョンによってコマンドが異なるので注意です。7.1.2ではこんな感じです。
katalon-execute.sh -browserType="Chrome" -retry=0 -statusDelay=15 -testSuitePath="Test Suites/TestSuite01"
執筆時点ではドキュメントやREADMEに記載がないのですが、先程と同様にAPI Keyが必要です。CircleCIの環境変数に設定して以下を追加します。
-apiKey=$KATALON_API_KEY
CircleCIでビルドして通ればOKです
定期実行でヘルスチェック
最後に定期実行をやってみます。色々方法はあると思いますが今回はJenkinsを使います。
(※Jenkins自体のインストール・セットアップは割愛)
と言っても、git pull=>さっきのコマンドを実行
をやるだけです。フリースタイルプロジェクトを作成します。
定期実行したいので「ビルド・トリガ」は「定期的に実行」にします。
「ビルド」に「シェルの実行」を追加し、コマンドラインで実行したときのコマンドを設定します。
ただし、リポジトリはJenkinsのワークスペースディレクトリ配下にダウンロードされるので、-projectPath
オプションだけ変更する必要があります。一度適当に設定してビルドしてみて、リポジトリの場所を特定してからやってもいいです。
/Applications/Katalon\ Studio\ Engine.app/Contents/MacOS/katalonc -noSplash -runMode=console -projectPath="/Users/kikuchi-hiroaki/.jenkins/workspace/katalon-video-player-test/katalon-video-player-test.prj" -retry=0 -testSuitePath="Test Suites/TestSuite01" -executionProfile="default" -browserType="Chrome" -apiKey="***********"
定期実行を設定しましたが、動作を確認したいのでプロジェクトを保存した後「ビルド実行」を手動でやってみます。
ビルドが成功し、コンソール出力を見てテストが通ってそうならOKです。JUnitのレポートがReports
ディレクトリあたりに保存されるので見てみます。PASSEDになってますね
<testcase name="Test Cases/Chrome" time="97.306" classname="Test Cases/Chrome" status="PASSED">
プラグイン
Jenkinsに入れておくとよさそうなプラグインを挙げておきます。
-
Slack Notification
- ビルド結果をSlackに通知するのに使用。
-
Naginator
- ビルドを再スケジュールするために使用。
- E2Eテストは往々にして不安定で、問題がなくても落ちてしまうことがあるので、何回かリトライさせてます。
異なる環境でのテスト
開発環境やステージング環境でテストしたい場合は、例えばプロキシが使用できます。コマンドオプションもあるので、さっきのコマンドに追加すればOKです。
hostsで切り替えることもできますが、Jenkinsでやろうとするとそこそこ面倒です。古典的にやるなら、hostsファイルをスイッチするシェルスクリプトを用意してJenkinsでテスト前に実行するとかですかねぇ。Win/Macともに権限周りが面倒ですが・・・。
お約束: つらいところ
お疲れ様でした〜!
E2Eテストの話をしたらつらいことを述べる、というのが業界のお約束な気がするので、最後にまとめておきます。
- GUIで簡単にできると思いきや、いつの間にかガッツリコード書いてる感
- ハンズオンでお察しの通り。
- とは言ってもスクラッチでSeleniumのコードを書くよりKatalonのライブラリを使うほうがまだ楽なので・・・。
- クロスプラットフォーム・クロスブラウザ
- つらい。闇。
- 例えばWindows+ChromeではChromeDriver設定におまじないが必要だったり、当然ブラウザごとに仕様が違ったり(動画のautoplay周りとかだるかったです)。
- 参照: クロスブラウザテストの闇と闇と闇
- Katalonの公式Dockerイメージ
- なぜか運用がとても雑です。
- masterブランチ1本運用(masterのCIがよく落ちてる)、ドキュメントとの乖離など、ちょっとお粗末な点が目立ちます。
- (1人でメンテしてて大変そうなので協力してあげるとよさそう・・・)
- メンヘラと付き合っているかのような感覚
- すごく、丁寧に、ミスのないように、扱わないと、ダメです。