はじめに
PhantomJSのラッパーであるNightmareを使って、画面を操作するテストを行ってみます。
より本格的な検証になるよう、TodoMVCのサンプルをテスト対象として使用します。
ちなみに、Phantom.js関連のライブラリとしてはCasperJSがありますが、Nightmare本家トップページのサンプルを見るに、CasperJSよりもさらにシンプルに書けそうです。
インストール
> npm install nightmare
※Phantom.jsを事前にインストールしておいてください。
インストール時にエラーが出た場合
筆者のWindows 7環境でインストールしたところ、下記のエラーが発生しました。
C:\Users\**>npm install nightmare
|
> weak@0.3.3 install C:\Users\**\node_modules\nightmare\node_modules\phantom\node_modules\dnode\node_modules\weak
> node-gyp rebuild
C:\Users\**\node_modules\nightmare\node_modules\phantom\node_modules\dnode\node_modules\weak>node "C:\Program Fil
es\nodejs\node_modules\npm\bin\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild
Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
MSBUILD : error MSB3428: Visual C++ コンポーネント "VCBuild.exe" を読み込めませんでした。この問題を解決するには、次のいずれかを行ってください。 1) .NET F
ramework 2.0 SDK インストールする。 2) Microsoft Visua
l Studio 2005 をインストールする。 3) その他の場所にインストールされている場合、コンポーネントの場所をシステム パスに追加する。 [C:\Users\**\node_modules\nightmare\no
de_modules\phantom\node_modules\dnode\node_modules\weak\build\binding.sln]
gyp ERR! build error
gyp ERR! stack Error: `C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe` failed with exit code: 1
gyp ERR! stack at ChildProcess.onExit (C:\Program Files\nodejs\node_modules\npm\node_modules\node-gyp\lib\build.js:267:23)
gyp ERR! stack at ChildProcess.emit (events.js:98:17)
gyp ERR! stack at Process.ChildProcess._handle.onexit (child_process.js:810:12)
gyp ERR! System Windows_NT 6.1.7601
gyp ERR! command "node" "C:\\Program Files\\nodejs\\node_modules\\npm\\node_modules\\node-gyp\\bin\\node-gyp.js" "rebuild"
gyp ERR! cwd C:\Users\**\node_modules\nightmare\node_modules\phantom\node_modules\dnode\node_modules\weak
gyp ERR! node -v v0.10.32
gyp ERR! node-gyp -v v1.0.1
gyp ERR! not ok
npm WARN optional dep failed, continuing weak@0.3.3
nightmare@1.3.2 node_modules\nightmare
├── debug@0.7.4
├── defaults@1.0.0
├── once@1.3.0
├── clone@0.1.18
└── phantom@0.6.6 (win-spawn@2.0.0, traverse@0.6.6, shoe@0.0.15, dnode@1.2.0)
エラーログを見るに、node-gypのビルドに失敗しているようでした。
node-gypのGithubページにアクセスしてみると、『Installation』に下記のように記載されていました。
You will also need to install:
- On Unix:
python
(v2.7
recommended,v3.x.x
is not supported)make
- A proper C/C++ compiler toolchain, like GCC
- On Windows:
- Python (
v2.7.3
recommended,v3.x.x
is not supported)- Windows XP/Vista/7:
- Microsoft Visual Studio C++ 2010 (Express version works well)
- For 64-bit builds of node and native modules you will also need the Windows 7 64-bit SDK
- If the install fails, try uninstalling any C++ 2010 x64&x86 Redistributable that you have installed first.
- If you get errors that the 64-bit compilers are not installed you may also need the compiler update for the Windows SDK 7.1
- Windows 7/8:
- Microsoft Visual Studio C++ 2012 for Windows Desktop (Express version works well)
上記を参考に、『Microsoft Visual Studio C++ 2012 for Windows Desktop』をインストール後、再度インストールしたところ、エラーログが出なくなりました。
サンプル
今回、テスト対象はTodoMVCのBackbone.js版Todoリストを使用します。
Nightmareでのテストの書き方は、NightmareのGithubリポジトリにあるテストコードを参考にしています。
インストール
> npm install --save-dev mocha
> npm install --save-dev should
テストコード
var Nightmare = require('nightmare');
var should = require('should');
var settings = {
timeout: 20000,
applicationUrl: 'http://todomvc.com/examples/backbone/'
};
describe('画面遷移', function () {
this.timeout(settings.timeout);
it('TODOリストが表示できること', function (done) {
new Nightmare()
.goto(settings.applicationUrl)
.run(function () {
done();
});
});
});
describe('基本動作確認', function () {
this.timeout(settings.timeout);
var nightMare;
beforeEach(function (done) {
nightMare = new Nightmare()
.goto(settings.applicationUrl)
.evaluate(function () {
// 各テストは初期状態で実施するため、TODOリストデータをクリアする
localStorage.clear();
})
.goto(settings.applicationUrl);
done();
});
it('TODO追加ができること', function (done) {
var todoText = 'テストToDoそのいち';
nightMare
.type('#new-todo', todoText)
.evaluate(function () {
var e = $.Event('keypress');
e.which = 13;
// エンターキー押下
$('#new-todo').trigger(e);
// 追加されたToDo文字列を返す
return $('#todo-list li:last').find('label').text();
}, function (addedTodoText) {
addedTodoText.should.equal(todoText);
})
.run(done);
});
it('追加したTODOの数が表示されること', function (done) {
var todoText = 'テストToDo';
nightMare
.type('#new-todo', todoText)
.evaluate(function () {
var e = $.Event('keypress');
e.which = 13;
// エンターキー押下
$('#new-todo').trigger(e);
})
.evaluate(function () {
return $('#todo-count').find('strong').text();
}, function (todoCount) {
todoCount.should.be.equal('1');
})
.run(done);
});
it('複数のTODO追加ができること', function (done) {
var todoText1 = 'テストToDoそのいち';
var todoText2 = 'テストToDoそのに';
nightMare
.type('#new-todo', todoText1)
.evaluate(function () {
var e = $.Event('keypress');
e.which = 13;
// エンターキー押下
$('#new-todo').trigger(e);
// 追加されたToDo文字列を返す
return $('#todo-list li:last').find('label').text();
}, function (addedTodoText) {
addedTodoText.should.equal(todoText1);
})
.type('#new-todo', todoText2)
.evaluate(function () {
var e = $.Event('keypress');
e.which = 13;
// エンターキー押下
$('#new-todo').trigger(e);
// 追加されたToDo文字列を返す
return $('#todo-list li:last').find('label').text();
}, function (addedTodoText) {
addedTodoText.should.equal(todoText2);
})
.run(done);
});
it('複数追加したTODOの数が表示されること', function (done) {
var todoText = 'テストToDo';
nightMare
.type('#new-todo', todoText)
.evaluate(function () {
var e = $.Event('keypress');
e.which = 13;
// エンターキー押下
$('#new-todo').trigger(e);
})
.type('#new-todo', todoText)
.evaluate(function () {
var e = $.Event('keypress');
e.which = 13;
// エンターキー押下
$('#new-todo').trigger(e);
})
.evaluate(function () {
return $('#todo-count').find('strong').text();
}, function (todoCount) {
todoCount.should.be.equal('2');
})
.run(done);
});
it('追加したTODOを削除できること', function (done) {
var todoText = '削除対象TODO';
nightMare
.type('#new-todo', todoText)
.evaluate(function () {
var e = $.Event('keypress');
e.which = 13;
// エンターキー押下
$('#new-todo').trigger(e);
})
// TODO削除ボタンをクリックする
.click('#todo-list .destroy')
.evaluate(function () {
return $('#todo-list li:last').find('label').text();
}, function (addedTodoText) {
// TODO文字列が存在しないこと
addedTodoText.should.be.ng;
}).run(done);
});
it('追加したTODOを完了にできること', function (done) {
var todoText = '完了対象TODO';
nightMare
.type('#new-todo', todoText)
.evaluate(function () {
var e = $.Event('keypress');
e.which = 13;
// エンターキー押下
$('#new-todo').trigger(e);
})
// 完了チェックをクリックする
.click('#todo-list .toggle')
.evaluate(function () {
return $('#todo-list li:last').hasClass('completed');
}, function (isCompleted) {
// TODOが完了になっていること
isCompleted.should.be.ok;
})
.run(done);
});
it('追加したTODOを完了すると、カウントが0になること', function (done) {
var todoText = '完了対象TODO';
nightMare
.type('#new-todo', todoText)
.evaluate(function () {
var e = $.Event('keypress');
e.which = 13;
// エンターキー押下
$('#new-todo').trigger(e);
})
// 完了チェックをクリックする
.click('#todo-list .toggle')
.evaluate(function () {
return $('#todo-count').find('strong').text();
}, function (todoCount) {
todoCount.should.be.equal('0');
})
.run(done);
});
});
テストコード中で使用している関数は、NightmareのAPI一覧を参照してください。
今回、テスト対象のアプリがjQueryを読み込んでいるので、しれっとテストコード中でもjQueryを使用しています。
Nightmareのテストコードでは、テスト対象のページがjQueryを読み込んでいないため、goto()で画面遷移した後にinject('js', 'test/files/jquery-2.1.1.min.js')
を追加して、動的にjQueryを読み込ませていました。
テスト実行
テストコードの実行自体は、
> mocha
でも良いですが、package.jsonをnpm init
で生成する時にtest command: (mocha)
としておくと、
> npm test
でmochaが実行され、かつ見やすく整形されたテスト結果がコンソールに表示されます。
テスト実行結果
npm test
の実行結果は下記の通りです。
> nightmare-sample@1.0.0 test C:\Users\**\nightmare-sample
> mocha
画面遷移
√ TODOリストが表示できること (6867ms)
基本動作確認
√ TODO追加ができること (8651ms)
√ 追加したTODOの数が表示されること (10224ms)
√ 複数のTODO追加ができること (8804ms)
√ 複数追加したTODOの数が表示されること (8176ms)
√ 追加したTODOを削除できること (8303ms)
√ 追加したTODOを完了にできること (8552ms)
√ 追加したTODOを完了すると、カウントが0になること (8456ms)
8 passing (1m)
所感
APIがシンプルで、テストコードが書きやすいという印象でした。ただ、テスト結果を見ての通り、非常にテストが遅いのが難点ですね…。並行して実行したいところです。
また、NightmareのAPIに渡す関数内から外部を参照できなかったため、同じ処理を何度も書かないといけなかったのが残念でした(エンターキー押下処理とか)。どうにか工夫すればできるのかな…。