Yeomanのgenerator-angularで作ったプロジェクトにE2EテストフレームワークのProtractorを導入してCoffeeScriptで書いたテストを実行する

generator-angularはkarma,jasmineのtestは用意されているけど、e2eテストはAngular Scenario Runner(非推奨)が入ってはいるものの使えるようになってないので、Protractorを入れる。

前提

yo angular --coffee でフロントエンド部を生成済みとする*1

作業

% cd ngapp
% bower uninstall -D angular-scenario
% npm install -D protractor
% npm install -D grunt-protractor-runner
% npm install -D protractor-coffee-preprocessor
% npm install -D grunt-exec
% mkdir test/e2e

test/下にprotractorの設定ファイルを置いてみた

// test/protractor.conf.js

"use strict";

exports.config = {
  // Seleniumサーバーのアドレス
  seleniumAddress: "http://localhost:4444/wd/hub",
  // テストで利用するブラウザの条件を設定
  // 詳細は https://code.google.com/p/selenium/wiki/DesiredCapabilities
  capabilities: {
    browserName: "chrome"
  },
  // テスト対象のspecファイルのパス(この設定ファイルからの相対パス)
  specs: [
    "e2e/**/*.coffee"
  ],
  // テスト対象のアプリケーションのベースURL
  baseUrl: "http://localhost:9001",
  framework: "jasmine",
  plugins: [
    "protractor-coffee-preprocessor"
  ],
  // Disable animations so e2e tests run more quickly
  onPrepare: function () {
    // Disable animations so e2e tests run more quickly
    var disableNgAnimate = function () {
      angular.module("disableNgAnimate", []).run(["$animate", function ($animate) {
        $animate.enabled(false);
      }]);
    };

    browser.addMockModule("disableNgAnimate", disableNgAnimate);

    // Store the name of the browser that's currently being used.
    browser.getCapabilities().then(function (caps) {
      browser.params.browser = caps.get("browserName");
    });
  },
  jasmineNodeOpts: {
    showColors: true,
    isVerbose: false,
    defaultTimeoutInterval: 30000
  }
};

Gruntfile.coffeeに追加。

# Gruntfile.coffee
module.exports = (grunt) ->
  grunt.initConfig
# ................................省略.................................

  # E2E test
    protractor:
      options:
        keepAlive: true
        noColor: false
      coffee:
        configFile: "test/protractor.conf.js"

    exec:
      webdriverUpdate: "node_modules/protractor/bin/webdriver-manager update"
# ................................省略.................................
   grunt.registerTask "test", [
    "exec:webdriverUpdate"    # <- 追加
     "clean:server"
     "concurrent:test"
     "autoprefixer"
     "connect:test"
     "karma"
    "protractor"  # <- 追加
   ]

上記は手抜きでtestのタスクに混ぜてしまっているが、ちゃんとunitテストのとは別にtest:protractorとかにしといた方がいいと思う。

正しく設定出来てテストが動くか確認のために、サンプルを用意。 こちらのを使わせていただきました。HTMLにidが入っていなかったのでそこだけ追加。

<!-- main.html-->
<div class="jumbotron">
  <h1>'Allo, 'Allo!</h1>
  <p class="lead">
    <img src="images/yeoman.png" alt="I'm Yeoman"><br>
    Always a pleasure scaffolding your apps.
  </p>
  <p><a class="btn btn-lg btn-success" ng-href="#">Splendid!<span class="glyphicon glyphicon-ok"></span></a></p>
  <p><a class="btn btn-lg btn-success" ng-click="click()" id="ShowListBtn">Add List<span class="glyphicon glyphicon-ok"></span></a></p>
</div>
<div class="row marketing" ng-if="visibleList">
  <ul id="awesomeThings" ng-repeat="awesomeThing in awesomeThings">
    <li>{{awesomeThing}}</li>
  </ul>
</div>

テスト記述

# test/e2e/main_spec.coffee
'use strict'

describe 'E2ETestSample',->
  beforeEach ->
    browser.get('/#/')

  it 'ボタン押下後、3つのリストが出る事の確認',->
    expect(element.all(By.repeater('awesomeThing in awesomeThings')).count()).toEqual(0)
    button = browser.findElement(By.css('#ShowListBtn'))
    button.click()
    expect(element.all(By.repeater('awesomeThing in awesomeThings')).count()).toEqual(3)

テスト実行

% node_modules/protractor/bin/webdriver-manager update
% node_modules/protractor/bin/webdriver-manager start
% grunt test

OK.

f:id:yuichi_katahira:20140818120356p:plain

上記ではseleniumのスタートを手動で叩いているけど、seleniumをStandalone Server as a Serviceとするのが良さ気↓

追記

grunt testを、unit,e2e,全部実行としてみた。

# Gruntfile.coffee

  grunt.registerTask "test", (target) ->
    if target is "unit"
      grunt.task.run([
        "clean:server"
        "concurrent:test"
        "autoprefixer"
        "connect:test"
        "karma"
      ])
    else if target is "e2e"
      grunt.task.run([
        "exec:webdriverUpdate"
        "clean:server"
        "concurrent:test"
        "autoprefixer"
        "connect:test"
        "protractor"
      ])
    else
      grunt.task.run([
        "exec:webdriverUpdate"
        "clean:server"
        "concurrent:test"
        "autoprefixer"
        "connect:test"
        "karma"
        "protractor"
      ])

これでgrunt test:unitでkarma, grunt test:e2eでprotractor, grunt testで両方実行出来る。

参考

*1:generator-angular