目次
前提
本記事の内容は以下の環境で行った
- 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とくらべて比較した場合の感想