Phantom.js+QUnitでjavascriptの自動テスト環境を作りたい

Phantom.jsって何?


PhantomJS is a headless WebKit with JavaScript API.

headless?


スクリーン表示の無い、CUIで動作するブラウザ

  • CUIで動作するので、自動テストに組みやすい
  • 中身はWebKitなので、jsやHTML5の動作が可能!

他に特徴は?

色んなテストフレームワークに対応

jasmine, capybara, QUnit, mocha, webdriver, yui test, busterjs, funcunit, robot framework

他のCUIブラウザと比べて軽い

ブラウザの動作をエミュレートしている訳ではない為

本当にHedless(LinuxX11を使ってない)

なので、AmazonEC2や、Herokuでも使える

インストールが簡単
  • ダウンロードしてすぐ使える
  • Macなら、Homebrewから取得可能
  • MacPortsは更新が止まってるらしい

導入


自分の環境は、MacPortsなので、手動でインストール

$ wget http://phantomjs.googlecode.com/files/phantomjs-1.7.0-macosx.zip
$ unzip phantomjs-1.7.0-macosx.zip
$ cd phantomjs-1.7.0-macosx
$ bin/phantomjs -v
1.7.0

簡単。

jsを使って動かす


まずは、hello world

console.log('hello, world!');
// メインスレッドが終わっても終了しないので、
// 明示的に終わらせる必要がある
phantom.exit();
$ bin/phantomjs test.js
Hello, world!

ページを読み込む

var page = require('webpage').create();
page.open('http://google.com', function () {
  // ページのキャプチャ
  page.render('google.png');
  phantom.exit();
});



標準だと、594x442px でレンダリングされてる
page.viewportSizeでサイズの変更が可能

page.viewportSize = { width: 480, height: 800 };

内部でjavascriptも動くの?


onloadで、div id="test" に、文字列を表示する、htmlを用意して実験

<html>
 <head>
  <meta charset="utf-8">
  <title>title</title>
  <script>
    window.onload = function(){
      document.getelementbyid("test").innerhtml='test';
    }
  </script>
 </head>
 <body>
   <div id="test1"></div>
 </body>
</html>


test.js にて、htmlを読み込んで、onloadで表示されるdiv id="test"の中の文字列を取得して表示する

var page = require('webpage').create();
page.open('01.html', function () { // ページアクセス
  setTimeout(function(){
    console.log(
      // ページ内でJSを動かすメソッド
      page.evaluate(function () {
        return document.getElementById("test").innerHTML;
      })
    );
    phantom.exit();
  }, 1000);
});
$ bin/phantomjs test.js
test


うごいた!

イベント送受信


今度はこんなHTMLを用意
div id="test" をクリックすると、confirmが出て分岐

// 〜 中略 〜
    window.onload = function(){
      document.getElementById("test").addEventListener("click", function(){
        if(confirm("click?")){
          alert("yes");
        }else{
          alert("no");
        }
      });
    }
  </script>
 </head>
 <body>
   <div id="test">click me!</div>
 </body>
</html>
var page = require('webpage').create();
page.open('01.html', function () { // ページアクセス
  page.onConfirm = function(msg){ // confirm発生時にトリガ
    console.log("confirm : "+msg);
    return true; // 真偽値でOK、キャンセルを決める
  }
  page.onAlert = function(msg){ // アラート発生時にトリガ
    console.log("alert : "+msg);
  }
  page.includeJs("jquery.min.js", function(){ // jQueryをinclude
    var offset = page.evaluate(function(){ // リンクのoffsetを取得
      return $("* test").offset();
    });
    page.sendEvent('click', offset.left+1, offset.top+1); // クリック
  });
});
$ bin/phantomjs test.js
alert : jquery.min.js *  includeJs完了時に、alertイベントが飛んでるっぽい
confirm : click?
alert : yes


イベントを受け取って、confirmでyesを選択している
ちなみに、confirmを受け取らない場合、デフォルトでは「いいえ」を選択している様子

QUnit連携


連携には、examples/run-QUnit.js を使う
試しに、QUnitのテストをPhantom.jsで実行してみる

$ examples/run-QUnit.js ../jquery-QUnit-4e03a4b/test/index.html; echo $1
'waitfor()' finished in 1901ms.
tests completed in 1771 milliseconds.
596 tests of 596 passed, 0 failed.bin/phantomjs
0
  • 成功の場合、戻り値は0
  • 失敗の場合は、1を返す
まとめ
  • 内部的には、QUnitをopenして、結果を出力するDOMをウォッチしているだけなので、ちょっといじれば、エラーが発生したテストを出力するとかできそう
  • 戻り値を返すので、Jenkinsへの組み込みも簡単にできそう
  • 全ブラウザのテストは無理だけど、CIに組み込めるのが良いと思った
  • デザイナさんがjavascript書いて、テストもQUnitで書くという状況ならば、Phantom.js と組み合わせて、CIに組み込むのがベター?