JavaScript
Node.js
PhantomJS
casperJs

PhantomJS × CasperJSによる初めてのブラウザテスト


概要

PhantomJSとCasperJSを使って、初心者向けにWebスクレイピングとE2Eテストに関してまとめてます。Seleniumだのmochaだのchaiだのkarmaなどは出てきません。


PhantomJSとは

PhantomJSは、node(JavaScript)で書けるヘッドレスブラウザ。ヘッドレスブラウザは、初心者向けに説明すると以下特徴を持つ。


  • コマンド上で動く画面の無いウェブブラウザ

  • 通常のWebブラウザを必要としない

  • その割に通常のWebブラウザの殆どの機能が使える

  • プログラムで動かすので、当然同じ動きを自動で繰り返し行わせられる


CasperJSとは

CasperJSは、PhantomJS(またはSlimerJS)を用いて、ブラウザテストを作成できるテストフレームワーク。ブラウザテストは初心者向けに説明すると以下の通りのテスト手法


  • PhantomJSのようなヘッドレスブラウザ上で、開発したWebアプリケーションを動かすこと

  • ヘッドレスブラウザでWebアプリケーションに対して特定の操作を行った際に、Webアプリケーションが意図した通りの振る舞いをするかを検証(テスト)すること

  • 繰り返しテストを回すことで、既存の機能が壊れていないかを検証すること


PhantomJS × CasperJS でできること

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

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


お試し環境

PhantomJS及びCasperJSを試す前に、試すための環境をサクッと準備する。本記事では、Dockerを用いて、PhantomJS,CasperJS共にインストール済みの環境を導入する。

$ docker run -it --name e2e_test vitr/casperjs bash

良い感じにインストールされてる

root@1144d08faeae:/home/casperjs-tests# phantomjs --version

2.1.1
root@1144d08faeae:/home/casperjs-tests# casperjs --version
1.1.4

vimとか必要に応じて追加インストールするが、その辺りは割愛。


ブラウザ操作 (基本編)

テストについては一旦後回しにして、まずはPhantomJS/CasperJSを用いたWebスクレイピングの例を以下に示す。

以下のスクリプトでは、boketeにアクセスし、トップページで取り上げられているボケの回答を標準出力する


bokete.js

// casper準備

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

実行。boketeのトップページに掲載されるボケはランダムなので、実行ごとに結果が異なる。ちなみに画像は取得していないため、回答だけ見るとモヤモヤする。(boketeのWebサイトの更新によっては上記コードは動作しなくなります)

root@1144d08faeae:~# casperjs bokete.js

サルも恋に落ちる。
root@1144d08faeae:~# casperjs bokete.js
素敵なリングね
root@1144d08faeae:~# casperjs bokete.js
私がおねしょ?証拠あるの?

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


ブラウザ操作 (応用編)

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


twitter.js

// TwitterのID/PWを定義

var id = 'hogehogehoge'
var pw = 'fugafugafuga'

// casper準備
var casper = require('casper').create();

// Twitterのログイン画面にアクセスし、認証する
casper.start('https://twitter.com/login',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のテスト投稿」とツイートされることが確認できる。(TwitterのWebサイトの更新によっては上記コードは動作しなくなります)

スクリーンショット 2018-04-24 23.28.18.png

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


CasperJSでの簡単なテスト例

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


a_test.js

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

root@1144d08faeae:~# casperjs test a_test.js
Test file: a_test.js
# getAの単体テスト
PASS Aが返却される
PASS getAの単体テスト
PASS 1 test executed in 0.027s, 1 passed, 0 failed, 0 dubious, 0 skipped.

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

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

var getA = function() {

return 'B';
};

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

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

root@1144d08faeae:~# casperjs test a_test.js

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

Details for the 1 failed test:

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


CasperJSによるブラウザテスト

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

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


  • 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 === '平成30年' , '年が正しく取得できる'); // 本来は決め打ちは望ましくない
});

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

});

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

root@1144d08faeae:~# casperjs test e2e.js

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

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

CasperJSのtesterモジュールまとめ


所感

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


  • 導入は比較的簡単だった

  • Node上でJavaScriptで書けるのが嬉しい

  • テストでなく、Webスクレイピングのみの使い方もできるのは嬉しい

  • 公式含めドキュメントが少なく、サンプルコードを書くだけでも苦労した

  • 立ち上がりが遅い

  • テストコード内にバグがある場合に気づきにくい

  • 入れ子が多くなり、可読性を損ないやすい

  • ↑故か、CoffeeScriptで書かれている例が多い

※ 所感の多くは、普段使っているテストフレームワークであるRspec+Capybaraと比べたもの。