LoginSignup
50
51

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-10-21

はじめに

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

参考

50
51
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
51