JavaScript
PhantomJS
Nightmare

PhantomJSラッパーのNightmareでUIをテストする

More than 3 years have passed since last update.


はじめに

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』をインストール後、再度インストールしたところ、エラーログが出なくなりました。


サンプル

今回、テスト対象はTodoMVCBackbone.js版Todoリストを使用します。

Nightmareでのテストの書き方は、NightmareのGithubリポジトリにあるテストコードを参考にしています。


インストール

> npm install --save-dev mocha

> npm install --save-dev should


テストコード


todoMvc_test.js

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に渡す関数内から外部を参照できなかったため、同じ処理を何度も書かないといけなかったのが残念でした(エンターキー押下処理とか)。どうにか工夫すればできるのかな…。


参考