CI as a Service ブラウザを使ったJavaScriptのテストをCIサービスで動かす方法のまとめ
CI as a Service
Travis CIを始めとするウェブサービスとして使えるCIを使って、
JavaScriptのブラウザテスト(ブラウザ上でJavaScriptを走らせて行うユニットテスト)をやる方法をサービスごとにまとめてみました。
テストフレームワークとして Buster.JS を使用して行います。
Karma (旧Testacular) では公式サイトにも Karma – Travis CI でCI Serviceとの連携方法が記載されているのでそちらも参考にして下さい。
今回紹介するCI Servicesは以下のものです。
テスト実行の流れ
Jepso CI を除いては、テスト実行の流れ自体は同じなので先に解説します。
- Capture用のローカルサーバを立てる
- テストしたいブラウザで
capture URL
へアクセスする - コマンドラインからテストを実行する
- Captureされてるブラウザでテストを実行した結果が得られる
これはJsTestDriverの流れを組んでいるTest Runnerには大体共通してると思います。
この一連の流れをCIサービス上で実行して、Githubにpushするたびに自動でテストを行うようにするのが今回の目的です。
どのCIサービスも裏側ではUbuntu上で動いていて、apt-getやnpmを使ってテスト環境を事前に準備する必要があります。
また、Travis CIやdrone.ioには最初からNode.js環境なども用意されているので、BuildHiveに比べると事前の準備は簡潔に済みます。
Travis CI
Travis CIでは.travis.ymlという設定ファイルを書くことで、テスト環境を揃えることができます。
サンプルプロジェクトは以下においてあります。
テスト環境のセットアップ
サンプルの Browser_CI_as_a_Service / .travis.yml を見ていきます。
env:
- DISPLAY=:99.0
before_script:
- sh -e /etc/init.d/xvfb start
- sh .travis/scripts/setup_busterjs.sh
script:
- npm test
language: node_js
node_js:
- 0.8
Travis CIではデフォルトでNode.js(npm)、PhantomJS、Firefoxの環境が入っているため、before_scriptでインストールするものは、Buster.JSのみとなります。
Travis CIにデフォルトで入っているものは以下で見られます
.travis.yml内に language: node_js
を記述しておくと、自動で npm install
を実行してくれるので、
レポジトリの Browser_CI_as_a_Service / package.json の devDependencies
に Buster.JS を追加しておきます。
{
"author" : "azu",
"name" : "Browser_CI_as_a_Service",
"description" : "Buster.JS with CI Service example",
"version" : "0.0.1",
"scripts" : {
"test" : "node_modules/.bin/buster-test -r specification"
},
"devDependencies" : {
"buster" : "~0.6"
},
"engines" : {
"node" : "~0.8"
}
}
これで、自動で npm install
が実行されれば、テスト環境が揃います。
テストの実行を設定
.travis.yml の事前準備の部分を見ていきます
env:
- DISPLAY=:99.0
before_script:
- sh -e /etc/init.d/xvfb start
- sh -e .travis/scripts/setup_busterjs.sh
before_script:
で実行しているsetup_busterjs.sh は以下のような内容になってます。
#!/bin/sh
node_modules/.bin/buster-server &
sleep 5
firefox http://localhost:1111/capture &
sleep 5
phantomjs node_modules/buster/script/phantom.js http://localhost:1111/capture &
sleep 5
if [ -x "google-chrome" ]; then
google-chrome –no-default-browser-check –no-first-run –disable-default-apps http://localhost:1111/capture &
sleep 5
fi
ここでやっていることを簡単にまとめると、以下のとおりです。
- GUIテスト用にxvfbをstartする(before_script:)
- Capture用のローカルサーバ(buster server)を立てる(setup_busterjs.sh)
- Firefox/PhantomJSでCature URLにアクセスする(setup_busterjs.sh)
詳しくはTravis CI: GUI & Headless browser testing on travis-ci.orgを参照してください。
後は、テストを実行するだけです。
.travis.ymlの script:
で npm test
を実行するようにしているので、npm test
でBuster.JSのテストが実行されるように package.json
に設定します。
(language: node_jsが設定されるなら省略可能です)
script:
- npm test
package.json
に以下のように書いて、npm test
でBuster.JSのテスト実行されるようにしました。
"scripts" : {
"test" : "node_modules/.bin/buster-test -r specification"
}
Travis CIの有効化
最後に、Travis CIとGithubのレポジトリを連携させます。
Repositories から テストを走らせたいレポジトリを選んでONにします。
Travis CI側で有効にすると、Github側のService Hooksが自動で有効になるので、後はGithubへPushすれば自動でCIが走るようになります。
Travis CIではTravis CI ClientというCLIクライアントがあり、CLIから特定のレポジトリのCIの実行状態や手動での再実行などTravis CIで行う操作をCLI上から行うことができます。
Travis CI Clientは実行結果の取得や過去の履歴などもちゃんと見られるので結構便利です。
より詳しい設定方法等は以下を参照して下さい
- Travis CI: Documentation
- Travis CIでブラウザテスト — The little book of Buster.JS 1.0 documentation
- Karma – Travis CI
drone.io
次は、 drone.io でのテスト環境を揃える方法についてです。
drone.ioではGithub/Bitbucket/Google Codeと連携できますが、今回は先ほどと同様のレポジトリを使うのでGithubの場合です。
また、drone.ioもNode.js環境が用意されていますが、PhantomJSとGoogle Chromeを追加でインストールして、
Firefox/PhantomJS/Google Chromeの3つでテストを動かしたいと思います。
drone.ioもTravis CIと同様にオープンなコードなら無料で殆ど利用できるようになっています。
(前月ぐらいまでは soft limitsな50回/monthの制限が書いてありましたがUnlimitedになってました)
テスト環境のセットアップ
drone.ioはTravis CIのように設定ファイル(.travis.yml)は用意する必要なく、直接ウェブでテスト環境を準備するスクリプトを書くようになっています。
なので、最初にGithubのレポジトリからdrone.ioにプロジェクトを作成します。
まず、 New Project から Githubを選択します。
レポジトリの一覧から、CIをしたいレポジトリを選択して、言語の選択ではNode.jsを選択します。
(言語とかは後から変更できるので適当でも大丈夫です)
そうするとビルドスクリプトを書く画面になるので、テスト環境を準備していきます。
Commandsの部分にビルドスクリプトを書いていきます。
sudo start xvfb
npm -d install
sh -e .travis/scripts/install_phantomjs.sh
sh -e .travis/scripts/install_chrome.sh
# start buster server
node_modules/.bin/buster-server &
sleep 5
# start browsers
firefox http://localhost:1111/capture &
sleep 5
phantomjs node_modules/buster/script/phantom.js http://localhost:1111/capture &
sleep 5
google-chrome --no-default-browser-check --no-first-run --disable-default-apps http://localhost:1111/capture &
npm test
npm install
でBuster.JSをインストールして、
PhantomJS(バイナリでインストールしたかったのでnpm経由)とGoogle Chrome(apt-getで取得してインストール)しています。
scriptはazu/Browser_CI_as_a_Service · GitHubにまとめてあります。
(jubianchi/travisci-helper · GitHub
こちらのスクリプトも参考になります)
後は、Travis CIと同じでbuster server
を起動して、ブラウザでCapture URLにアクセスして、
npm test
でテストを動かすという感じです。
Travis CIの setup_busterjs.sh を実行させようと思ったのですが、なぜかテストが成功しても終了しないという感じになったので直接書いています。
今回紹介したビルドスクリプトは Build Setup | Browser_CI_as_a_Service から参照できます。
drone.io はTravisCIよりスマートな感じなので、初めてCIサービスを試す場合はこちらのほうが試行錯誤しやすいかもしれません。
BuildHive
基本的にJenkinsの画面なので、人によっては馴染みやすいかもしれません。
BuildHiveもdrone.ioと同じく、ウェブ上でビルドスクリプトを書いてテスト環境を揃えます。
デフォルトでNode.js環境がないため、そこから環境を揃えていく必要があります。
ただし、Travis CIやdrone.ioと違って、一回ごとに仮想環境がリセットされないで継続する感じなので、少しセットアップ方法が変わります。
Add Git Repository からGithubのプロジェクトを選んで有効化します。
設定 を 選ぶとシェルスクリプトを書く欄が出てくるので、この部分にビルドスクリプトを書いていきます。
export PATH=$HOME/.nodebrew/current/bin:$PATH
if [ ! -x "$HOME/.nodebrew/current/bin/nodebrew" ]; then
curl https://raw.github.com/hokaccha/nodebrew/master/nodebrew | perl - setup
nodebrew install-binary stable
nodebrew use stable
fi
if [ -e /tmp/.X1-lock ]; then
rm /tmp/.X1-lock # たまにロックがかかって死ぬので
fi
if [ -z "$DISPLAY" ]; then
export DISPLAY=:1
Xvfb :1 &
fi
npm -d install
node_modules/.bin/buster-server &
sleep 5
firefox http://localhost:1111/capture &
sleep 5
node_modules/phantomjs/bin/phantomjs node_modules/buster/script/phantom.js http://localhost:1111/capture &
sleep 5
npm test
Node.jsはnodebrewを使ってバイナリからインストールしています。
後は、他のものと同じでXvfbをstartして、Capture用のローカルサーバを起動して、Capture URLにアクセスさせて、
npm test
するという感じです。
正直、ちゃんとした書き方がよくわからなかったので、適当な部分が多いです。
(Node.jsのアップデートとかも対応したい… buildhive for node)
毎回リセットされない前提で書く必要があるので、どなたかがもう少し良いものを書いてくれるはず。
Jepso CI
最後は Jepso CI についてです。
他のCIサービスが特にJavaScriptに限定されないのに対して、Jepso CIはHTMLとJavaScriptというものに限定される作りになっているので全く別の仕組みです。
Sauce や testling-ci の方に近いジャンルのCIサービスです。
また、まだ安定してるとは言えないので、実験的でテスト結果も安定しないのもあるため実用で使うのは諦めて下さい。
(2013-03-20)
サンプルプロジェクトはazu/BusterJS_JepsoCI · GitHubに置いてあります。
仕組み
他のCIサービスとは異なる仕組みで、基本的にローカルサーバなどは使わないで、
QUnitのようなテストを走らせるための静的なHTMLファイルと、そのファイルへのパスを書いた .jepso-ci.json
という設定ファイルだけです。
今回のプロジェクト(azu/BusterJS_JepsoCI · GitHub)ではルートに、テストを走らせるための test.html
をおいてるので、
.jepso-ci.json
は以下のような内容が入ります。
{
"url": "/test.html"
}
次に、BusterJS_JepsoCI / test.html を見ていきます。
Jepso CIのテストの成否判定は、test.html内の2つのプロパティによって判定されます。
window.testsPassed;// bool
window.completedTests;// number
Buster.JSのtest runnerとなる test.html
は以下のような感じです。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Buster.JS</title>
<link rel="stylesheet" href="http://cdn.busterjs.org/releases/latest/buster-test.css">
<script type="text/javascript" src="http://cdn.busterjs.org/releases/latest/buster-test.js"></script>
<span class="c"><!-- jepso-ci --></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="c1">// start test</span>
<span class="nx">buster</span><span class="p">.</span><span class="nx">testRunner</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">"suite:start"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">results</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">completedTests</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">testsPassed</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">});</span>
<span class="c1">// each passed test</span>
<span class="nx">buster</span><span class="p">.</span><span class="nx">assertions</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">"pass"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">completedTests</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">});</span>
<span class="c1">// each failure test</span>
<span class="nx">buster</span><span class="p">.</span><span class="nx">assertions</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">"failure"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">testsPassed</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">});</span>
<span class="c1">// finish all test</span>
<span class="nx">buster</span><span class="p">.</span><span class="nx">testRunner</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">"suite:end"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">results</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">testsPassed</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">ok</span><span class="p">;</span><span class="c1">// result boolean</span>
<span class="p">});</span>
<span class="nt"></script></span>
<span class="c"><!--test source--></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"./src/hello.js"</span> <span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"./tests/async-test.js"</span> <span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"./tests/hello-test.js"</span> <span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></script></span>
</head>
<body>
</body>
</html>
結論をいうと、test.htmlを実行した結果 window.testsPassed
が true
ならテストが通った事になります。
window.completedTests
は非同期のテストがある場合に、++していくとtimeoutを伸ばしてくれるためにあるようです。
実際の処理内容を見ていくと、Buster.JSは .on
でテスト実行時のイベントを設定できるので、以下のような処理をイベントに仕込んでいます。
buster.testRunner.on("suite:start", function… はテスト開始時に初期化
buster.assertions.on("pass", function... でテストが通るたびにインクリメント
buster.assertions.on("failure", function... で一つでも失敗したらテスト失敗
buster.testRunner.on("suite:end", function... でテスト終了時にテスト結果を代入
後は、テストファイルを読み込ん実行するだけです。
(単純に*.jsファイルをscriptタグで読み込んでる)
Jepso CIとGithub連携
Githubとの連携はPost-Receive Hooksの”WebHook URLs”に https://jepso-ci.com/api/hook
と設定するだけです。
後は、GithubにpushすればJepso CIが動作するようになります。
Jepso CIのページは https://jepso-ci.com/<user>/<repogitory>
にアクセスすると表示されます。
HTMLを使ったとてもシンプルな仕組みとなっていて、ビルドステータスもSVGで書かれていたりして面白いサービスだと思います。
公式にも幾つかサンプルが用意されています。
Jepso CIは静的なHTMLでテストを動かしてるので、以下のテンプレートも参考になるかもしれません。
また、testling-ciもHTMLをベースとしたテストができるようになったようです。(2013-03-21)
FAQ
Travis CIやBuildHiveではChromeは使えないの?
Travis CI がUbuntu-64bitに移行した際にChromeが動かなくなっています。(インストールはできます)
Chromium/Chrome does not work in an OpenVZ container · Issue #938 · travis-ci/travis-ci のIssueが解決するまでは使えないです。
追記 : Travis CIとChromeについて
記事ではChromeはインストールしていませんでしたが、以下のような変更を入れることで、
Travis CI上でもChromeを動作させることができます。
- fix Google-Chrome for Travis CI · d736273 · azu/Browser_CI_as_a_Service
- 修正したコミット
- Travis CI – Free Hosted Continuous Integration Platform for the Open Source Community
- 実際に動いてる様子
- Chromium/Chrome does not work in an OpenVZ container · Issue #938 · travis-ci/travis-ci
具体的には以下のようなことが必要になります。
/dev/shm
のパーミッションを修正する
sudo chmod 1777 /dev/shm
- Chromeの起動引数に
--no-sandbox
をつける
こうすることで、Travis CIの仮想マシン上でもGoogleChromeを動かすことができるようになるので、
Chromeでテストを実行させることが可能です。(--no-sandbox
の副作用が何かあるかもしれませんが)
BuildHive では sudo
が使えなかったので、上手くインストール出来ませんでしたが、上手くやればインストールできるかもしれません
もっといろんなブラウザのバージョンで試したいんだけど?
今回紹介した Travis CI、drone.io、BuildHive はブラウザに特化した作りでは無いので、自前でバージョンごとのブラウザを用意すればできますが、負荷が大きそうなのでそういう用途は避けましょう。(また全体として成功か失敗かなのであんまりそういう用途ではない気がする)
BrowserStack や testling-ci 、Sauce Labs などブラウザに特化したサービスを利用するか自前のサーバ等でやりましょう。
CI Servicesの環境に何が入ってるかもっとしりたい
公式サイトにサーバのスペックは書いてあると思います。
デフォルトに入ってるものについてはTravis CIはtravis-ci/travis-cookbooks · GitHubを見ると大体わかります。
gildegoma/travis-ci-inspector · GitHubのように走査して何が入ってるか調べる地道な方法も利用できます。
(対話側のコンソールみたいなのが欲しいですね…)
Example Project
今回使用したサンプルプロジェクトは以下のURLで公開しています。
Test Tools
今回は Buster.JS を使用しましたが、Test Toolとしては他に以下のようなものもあります。
Special Thanks
- ようせいさん(@kyo_ago)
お知らせ欄
JavaScript Primerの書籍版がAmazonで購入できます。
JavaScriptに関する最新情報は週一でJSer.infoを更新しています。
GitHub Sponsorsでの支援を募集しています。