ブラウザゲームとかを作っているとなかなかテストが難しくどうしたらいいんだろうと調べていたらPhantomJS + Mochaという組み合わせを見つけたので勉強してみました。
PhantomJSとは?
ブラウザです。JavaScriptのAPIを通じて制御できるヘッドレス(画面のない)Webkitブラウザらしいです。 まだ試せてませんがCanvasやSVGにも対応してるっぽい。
インストール
http://phantomjs.org/download.htmlよりダウンロードしPATHを通すことで使用できます。
Macでhomebrewが入って入れば以下でOK。
$ brew update && brew install phantomjs
PhantomJS準備運動
まずは以下のようなhello.jsを用意します。
・hello.js
var page = require('webpage').create(); page.open('http://www.google.co.jp', function () { page.render('google.png'); phantom.exit(); });
以下のように実行します。
> phantomjs hello.js
これでGoogleのスクリーンショットがとれます。(スクリーンショットをとったのは節分です。鬼がかわいい) windowサイズもJavaScriptで変更できるし、これだけでも色々応用できそう。
Mochaとは?
node.jsやブラウザから実行できるテストフレームワークです。
インストール
npmでいれてもいいし、ブラウザで実行するなら"mocha.css"、"mocha.js"のみ持ってきたらよいようです。 今回の例ではブラウザで実行させるため、以下より"mocha.css"、"mocha.js"をダウンロードしておきます。
https://github.com/mochajs/mocha
あと、Mochaはテスティングフレームワークのみで、アサーションは別のライブラリを用いる必要があるようです。 調べたところ、今回はよさげなchaiを使用します。
こちらもブラウザで使用するためhttp://chaijs.com/chai.jsよりダウンロードしておきます。
Mocha準備運動
こんな感じでテストができるよーってサンプルです。 加算するadd関数をテストしています。
・test.html
<html> <head> <meta charset="utf-8" /> <link rel="stylesheet" href="mocha.css" /> </head> <body> <div id="mocha"></div> <script src="chai.js"></script> <script src="mocha.js"></script> <script> var add = function (arg1, arg2) { return arg1 + arg2; } mocha.setup('bdd'); expect = chai.expect; describe('add test', function() { it('should return 3 when arg1 is 1 and arg2 is 2', function() { expect(add(1,2)).to.be.equal(3); }); }); mocha.run(); </script> </body> </html>
test.htmlをブラウザで開くと以下のようにテスト結果が出力されます。
PhantomJS + MochaでのクライアントJavaScriptのテスト準備
ようやく本題。PhantomJS + Mochaでテストの準備をする。 で、いきなりですが、問題はMochaがPhantomJS上での動作に対応していないようです。 以下を参考にさせてもらいました。
PhantomJSを使ったスマホサイトテストの自動化(前編)|1 pixel|サイバーエージェント公式クリエイターズブログ
ここでPhantomJSで動作するよう修正したものが公開されているので使用させてもらいます。
feb0223/mocha-phantom · GitHub
テスト対象ページ
簡単に今回はこんなので試してみます。 画像を重ねておいて、クリックするとフェードインします。 バージョンはなんでもいいですが(たぶん)jqueryも使用します。
・index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <style> img.icon { position: absolute; top:50%; left:50%; margin:-40px 0 0 -40px; } img.icon-over { position: absolute; top:50%; left:50%; margin:-40px 0 0 -40px; z-index: 999; display:none; } </style> </head> <body> <img src="shadow.png" class="icon"> <img src="icon.png" class="icon-over"> <script src="jquery-1.11.2.min.js"></script> <script> jQuery(function($) { jQuery('img.icon').click( function () { console.log("click!!"); jQuery('img.icon-over').fadeIn(); } ); }); </script> </body> </html>
テストコード
こっからはCoffeeScriptです。 流れはこんな感じです。
- mocha, chaiの読み込み
- index.htmlをオープン、キャプチャをとり、jqueryを読み込み
- 画像の座標、フェードインする画像のdisplayプロパティを取得
- クリック前のdisplayプロパティが"none"であることをテスト
- 画像の座標をクリックし、1秒待つ
- クリック後のキャプチャを取得、クリック後のdisplayプロパティが"block"であることをテスト
これだけだけど、大分はまりました。
page = require('webpage').create() mocha = require('./lib/mocha-phantom.js').create(reporter:'spec', timeout:1000*10) chai = require('./lib/chai.js') expect = chai.expect mocha.setup('bdd') describe 'client javascript sample', -> # 画面のサイズを設定 page.viewportSize = width: 800 height: 600 before (done)-> # 指定したページをオープン page.open 'index.html', -> # ページ読み込み後のキャプチャを取得 page.render('before_click.png') # jqueryのインクルード page.includeJs "jquery-1.11.2.min.js", -> console.log "includeJS" done() page.onConsoleMessage = (msg, lineNum, sourceId)-> console.log msg return describe 'image click test', -> it 'fadein image when click image', ()-> # imageの座標を取得する position = page.evaluate -> $('img.icon').position() # fadeinする画像のdisplayプロパティを取得 displayVal = page.evaluate -> $('img.icon-over').css('display') # クリック前のdisplayプロパティが"none"であることをテスト it 'should fade in image property is none before click', -> expect(displayVal).to.be.equal("none") # imageの座標をクリック page.sendEvent('click', position.left+1, position.top+1) # fadeIn時間を待つ setTimeout ()-> # クリック後のキャプチャを取得 page.render('after_click.png') # fadeinした画像のdisplayプロパティを取得 displayVal = page.evaluate ()-> $('img.icon-over').css('display') # クリック後のdisplayプロパティが"block"であることをテスト it 'should fade in image property is none before click', -> expect(displayVal).to.be.equal("block") phantom.exit() , 1000 return return # テストの実行 mocha.run()
実行
> phantomjs test.js
テスト後に"before_click.png"と"after_click.png"が生成されているかと思います。
・before_click.png
・after_click.png
はまった点
page.evaluateを理解してなかった
Evaluates the given function in the context of the web page. The execution is sandboxed, the web page has no access to the phantom object and it can't probe its own setting.
evaluateメソッドはWeb Pageのコンテキストで評価されます。この実行はサンドボックス化されているため、PhantomJSと直接やりとりできません。
http://murayama.hatenablog.com/entry/2013/06/24/065633より
例えば、page.evaluate内でpage.evaluate ->console.log $('img.icon').position()
ってしてもWeb Pageのコンテキストで評価されてしまい、PhantomJS側には何も表示されません。これはonConsoleMessage
を使うことで対処できるようです。具体的にはページオープン時にpage.onConsoleMessage = (msg, lineNum, sourceId)-> console.log msg
としておけばPhantomJS側のコンソールに吐かれます。
こちらも上記参考サイトに詳細が書かれていますが、PhantomJS側とWeb Page側とで値をやり取りするには、evaluateメソッド呼び出しの引数や戻り値を使えばいいようです。
position = page.evaluate -> $('img.icon').position()
でWeb Page側の画像座標を取得したり、displayVal = page.evaluate -> $('img.icon-over').css('display')
でCSSプロパティを取得したりしています。
ただし
受け渡し可能なデータはJSONにシリアライズ可能なオブジェクトに限定されます。クロージャや関数オブジェクトを使ってやりとりすることはできません。
らしいです。
やってみて
- PhantomJS上でMochaを使うかどうかはおいといて、PhantomJSだけでも使いこなせればめちゃめちゃ便利な気がする
- アプリケーションによってはキャプチャの差分を取って合致しているかどうかの判定処理まで入れてしまえば完全な自動化もできる?(canvasで取り込んでpixelベースの比較をしてやるとか?わかんないけど)
- PhantomJSはwebkitなので、他のブラウザはseleniumとか使わなきゃ?
- スマホページでこそ真価を発揮するかも
- ただし、参考サイト(http://ameblo.jp/ca-1pixel/entry-11549761391.html)にあるように「JSでDomを追加してイベントを設定した要素に対し、イベントの発火ができない」とか不便な点もあるよう
- なかなかおもしろかった
リポジトリ
めんどくさいので全部ごっちゃだけどあげときます。
bokuweb/mocha-phantomjs-test · GitHub
参考にしたサイト
http://ameblo.jp/ca-1pixel/entry-11549761391.html http://murayama.hatenablog.com/entry/2013/06/24/065633 http://webtech-walker.com/archive/2012/10/mocha_phantomjs_travisci.html