PhantomJS × CasperJSによるブラウザテスト

前提

本記事の内容は以下の環境で行った

  • Debian 8.7
  • nodejs 7.4.0
  • npm 4.1.2

PhantomJSとは

JavaScriptでかけるヘッドレスブラウザ。レンダリングエンジンには、safariなどか多くのソフトウェアで用いられているWebKitが搭載されているため、基本的なJavaScriptを動かすことができる。

CasperJSとは

PhantomJSの用いてテストを作成できるテストフレームワーク。CasperJSはPhantomJSの他にも、SlimerJSというヘッドレスブラウザにも対応しているが、今回はPhantomJSと組み合わせることにする。

PhantomJS × CasperJS でできること

一言で言うなら、Webアプリケーションの自動テストが効率的に行える。PhantomJSの機能を用いてウェブブラウザの動作をしシュミレートし、各画面遷移が正常に行えるか、正しい値が表示されているかなどを、CasperJSの機能を用いて検証する。

これによって、従来は手作業でブラウザを操作し、正常動作しているかを検証していたものを自動化・高速化させることができるようになり、機能追加による既存機能のデグレーションをすぐに検知することができる。

PhantomJS / CasperJS のインストール

どちらもnpmで最新版をインストール可能

$ sudo npm install casperjs -g
$ sudo npm install phantomjs -g

コマンドで呼び出せることを確認

$ which casperjs
/usr/local/bin/casperjs
$ casperjs --version
1.1.3
$ which phantomjs
/usr/local/bin/phantomjs
$ phantomjs -v
2.1.1

ブラウザ操作 (基本編)

テストについては一旦後回しにして、まずはPhantomJSを用いたWebスクレイピングの例を以下に示す。
以下のスクリプトでは、boketeにアクセスし、トップページで取り上げられているボケの回答を標準出力する

// casper準備
var casper = require('casper').create();
// boketeにアクセス
casper.start("http://bokete.jp",function(){
  // 表示されたWebページ内から特定のDOMのテキストを取得
  var text = this.evaluate(function(){
    return document.querySelector('h3').innerText;
  });
  // 結果を標準出力
  this.echo(text);
});
// casper開始
casper.run();

実行。boketeのトップページに掲載されるボケはランダムなので、実行ごとに結果が異なる。ちなみに画像は取得していないため、回答だけ見るとモヤモヤする。

$ casperjs test.js
番組中、ふなっしーのファスナーが壊れて中の人達がうつるという放送事故発生
$ casperjs test.js
百人一首
$ casperjs test.js
事故物件の地縛霊が可愛い

最も重要なのが、this.evaluateのブロックだ。this.evaluateで指定した関数内は、現在開いているWebページをスコープにしている。つまり、関数内で書いたスクリプトは、Webページを対象に実行される。そのため、document.querySelector(‘h3’).innerText;で対象Webページ内のDOMを取得することができる。

ブラウザ操作 (応用編)

もう一つブラウザの例を以下に示す。ここでは、Twitterにアクセスし、ログインフォームにID/PWを入力してログインし、適当な文字列をツイートするところまでを自動化する。

// TwitterのID/PWを定義
var id = ‘hogehoge’;
var pw = ‘fugafuga’;

// Twitterのログイン画面にアクセスし、認証する
casper.start("https://twitter.com/?lang=ja",function(){
  this.fill('form.LoginForm' , {'session[username_or_email]': id, 'session[password]': pw} , true);
});

// 認証完了を待ち、Twitterに再度アクセス
casper.waitForUrl('https://twitter.com', function(){
  // ツイート投稿ページに移動
  this.thenOpen('https://twitter.com/intent/tweet', function(){
    // ツイートフォームにテキストを入力し、submit
    this.fill('#update-form', { 'status': 'CasperJSのテスト投稿' }, true);
  });
});

// casperを開始
casper.run();

実行すると、ちゃんと「CasperJSのテスト投稿」とツイートされることが確認できる。

このように、フォームへの入力や、POSTなども通常のブラウザと同じように行うことができる。

テスト

ブラウザテストを行う前に、簡単な単体テストの例を以下に示す。

var getA = function() {
  return 'A';
};

casper.test.begin('getAの単体テスト' , function(test) {
    test.assert(getA() === 'A' , 'Aが返却される');
    test.done();
});

上記のコードは、常に’A’を戻すという仕様の関数getAに対する単体テストである。
test.assertメソッドがテストの本体で、そこに真偽値の式と、テスト名を記述する。

これを実行すると、以下のようになる。なお、テストを行う場合はcasperjsコマンドとファイル名の間に”test”を挟む必要があるので注意。

$ casperjs test test.js
Test file: test.js
# getAの単体テスト
PASS Aが返却される
PASS getAの単体テスト (1 test)
PASS 1 test executed in 0.027s, 1 passed, 0 failed, 0 dubious, 0 skipped.

単体テストが成功したことがわかる。

次に、getA関数に下記のように意図的にバグを混入してもう一度テストする。

var getA = function() {
  return 'B';
};

以下のようにテストは失敗となり、バグが発生していることを確認することができる。

$ casperjs test test.js
Test file: test.js
# getAの単体テスト
FAIL Aが返却される
# type: assert
# file: test.js
# subject: false
FAIL 1 test executed in 0.032s, 0 passed, 1 failed, 0 dubious, 0 skipped.

Details for the 1 failed test:

In test.js
getAの単体テスト
assert: Aが返却される

ブラウザテスト

最後に、ブラウザ操作とテストを組み合わせたブラウザテストの例を示す。

例では、今日は平成何年?今現在は?を対象に、以下についてテストする

  • HTTP Statusが200(正常)である
  • ページタイトルが正常である
  • 表示される情報が正しい

以上をテストするコードを以下に示す

casper.test.begin('サンプルテスト' , 3 , function(test) {

  casper.start('https://date.yonelabo.com/' , function() {
    test.assertHttpStatus(200 , 'ステータスコードが200である');
    test.assertTitle('今日は平成何年?今現在は?' , 'ページタイトルが正しく取得できている');
  });

  casper.then(function() {
    var year = this.fetchText('#main');
    test.assert(year === '平成29年' , '年が正しく取得できる');
  });

  casper.run(function() {
    test.done();
  });

});

テストを実行すると、前述の3種類のテストが全て通っていることが確認できる。

$ casperjs test test.js
Test file: test.js
# サンプルテスト
PASS サンプルテスト (3 tests)
PASS ステータスコードが200である
PASS ページタイトルが正しく取得できている
PASS 年が正しく取得できる
PASS 3 tests executed in 0.477s, 3 passed, 0 failed, 0 dubious, 0 skipped.

上記の例のように、test.asertの他にも、test.assertHttpStatusや、test.assertTitleなど、よく用いられるアサーションが幾つか用意されている。これらを用いて、Webページが正しく表示できているかを検証することができる。

所感

今回、PhantomJS × CasperJSによるブラウザテストを試してみたが、使いやすいと思う部分、使いづらいと思う部分ともにいくつかあった。特に感じた部分は以下の通り。

  • 導入は比較的簡単だった
  • Node上でJavaScriptで書けるので、MEANのとの相性は良し
  • テストでなく、Webスクレイピングのみの使い方もできるのは嬉しい
  • 公式含めドキュメントが少なく、サンプルコードを書くだけでも苦労した
  • 立ち上がりが遅い
  • テストコード内にバグがある場合に気づきにくい
  • 入れ子が多くなり、可読性を損ないやすい
  • ↑故か、CoffeeScriptで書かれている例が多い

※ 所感の多くは、普段使っているテストフレームワークであるRspecとくらべて比較した場合の感想

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です