ProtractorはAngularJSでE2Eテストをするための環境です。 セットアップ編・書き方編の2回に分けてテストを始めるまでのステップを紹介します。

この記事を書いた環境

  • Mac OSX 10.9
  • AngularJS 1.4
  • Protractor 2.1

Protractorとは

E2Eテストをするには、まずブラウザを起動してAngularJSのページを表示し、DOMを介してページ内のコンテンツにアクセスする事が必要になります。Protractorはそれらに必要な環境が含まれています。

  1. ブラウザ操作ツール(Selenium)
    • コンテンツにアクセスするAPI(WebDriverJS)
    • テスト用フレームワーク(Jasmine)
    • セットアップに必要なユーティリティ(webdriver-manager)
    • WebDriverJSをAngularJS用に拡張(ProtractorAPI)

これらがどのように関連しているのか詳しく知りたい方は Protractor How It Works を参考にしてください。

セットアップ編

1. 必要な環境を整える

1-1. npm(Protractorの実行環境)

$brew install npm

1-2. Java(Seleniumのローカル実行環境)

http://www.oracle.com/technetwork/java/javase/downloads/index.html

Seleniumの実行環境についての詳細は http://www.seleniumhq.org/download/ をご覧ください。

1-3. Protractor

// package.json
"protractor": "^2.1.0"
$npm install

インストール後のディレクトリはこのようになります。

/node_modules
    /bin
        /protractor
        /webdriver-manager

1-4. Selenium/Jasmine

ユーティリティのwebdriver-managerを使ってSelenium/Jasmineをインストールします。

$node_modules/bin/webdriver-manager update

2. 設定ファイルを作る

conf.js

exports.config = {
    specs: ['tests/*e2e.spec.js'] // テストするファイルを指定
};

3. サンプル実行するためのテストを作る

tests/e2e.spec.js

describe("protractorテストサンプル", function () {
    it("test", function () {
        expect('hello'.length).toBe(5);
    });
});

4. サーバーを変更する(省略可)

Setting Up the Selenium Server

Protractor付属のSeleniumを使う(デフォルト)

// conf.js
exports.config = {
    // この設定はデフォルトのため省略可
    seleniumAddress: 'http://localhost:4444/wd/hub'

ビルトインでないSeleniumを使う場合

// conf.js
exports.config = {
    seleniumServerJar: '../../selenium-server-standalone-x.x.x.jar',
    seleniumPort: 4444

Sauce Labsを利用する

// conf.js
exports.config = {
    sauceUser: [user],
    sauceKey: [key]

Seleniumを使わない

// conf.js
exports.config = {
    // 後述のブラウザ設定はfirefox/chromeの二択となる
    directConnect: true

5. ブラウザを変更する(省略可)

Setting Up the Browser

firefox(デフォルト)

// conf.js
exports.config = {
    capabilities:{
        'browserName': 'firefox'
}

その他のブラウザ

// conf.js
exports.config = {
    capabilities:{
        'browserName': 'chrome'
}

それ以外のブラウザは サポートしているブラウザ を参考にしてください。

6. テスト用フレームワークを変更する(省略可)

Choosing a Framework

Jasmine2がデフォルトですが、Mochaなど別のフレームワークに変更可能です。

7. テストを実行する

webdriver-managerを起動

$node_module/protractor/bin/webdriver-manager start

Seleniumが起動しコンソールのプロセスを専有します。 Ctrl+Cを押すと終了します。

Protractor実行

// さきほど作った設定ファイルのパスを渡す
$node_module/protractor/bin/protractor conf.js

結果

1

テストが通りました! これでセットアップは完了です。

デバッガが使える

Debugging Protractor Tests

Protractorを実行するとブラウザが起動して実際の画面を見ることができますが、あっという間にテストが終わってしまうため隅々まで確認するのは困難です。

デバッガを使うとテストの途中で動きを一旦停止できるため、画面の確認などにとても便利です。

pause()

// tests/e2e.js
describe("protractorテストサンプル", function () {
    it("test", function () {
        expect('hello'.length).toBe(5);
        
        // ここでストップ
+       brower.pause(); 
    });
});

2

コンソールを見るとready(待機状態)になっています。

------- WebDriver Debugger -------
Hit SIGUSR1 - starting debugger agent.
debugger listening on port 5858
 ready

インタラクティブモードを使ってみましょう。

$repl

Protractor APIを使ってターミナル上でDOMを検索する事ができます。

$element(by.id('name'));

{ ptor_:
    { ocntrolFlow: [Function],
      schedule: [Function],
      setFileDetector: [Function],
    ...

Ctrl+Cでインタラクティブモードを終了します。

gruntからテストを実行する

テストは通るようになりましたが、毎回Seleniumを起動、Protractorを実行、という手順を踏むのはとても面倒です。

gruntにタスク登録して簡単に呼び出せるようにしておきましょう。

プラグインのインストール

// package.json
grunt-protractor-runner: "^2.0.0",
grunt-protractor-webdriver: "^0.2.0"
$npm install

gruntfileに追加

// gruntfile.js

grunt.initConfig({
  protractor: {
    options: {
      noColor: false,
      args: {}
    },
    my_project: {
      options: {
        configFile: "conf.js"
      }
    },
  },
})

// プラグインのタスクを登録
grunt.loadNpmTasks('grunt-protractor-runner');
grunt.loadNpmTasks('grunt-protractor-webdriver');

// webdriver-managerを起動してからprotractorを実行する
grunt.registerTask('test:protractor', 'run all protractor tests', function () {
    grunt.task.run('protractor_webdriver');
    grunt.task.run('protractor');
});

ローカル開発環境でgruntからテストする

// grunt上のWebサーバー起動、Protractorとは別に定義が必要
$grunt serve&

// protractorを実行
$grunt protractor

これでだいぶ便利になりましたね。

[参考情報]セットアップ中のエラー

セットアップ中のエラーを参考情報として掲載します。

injectエラー

Error: Error while waiting for Protractor to sync with the page: "[ng:test] no injector found for element argument to getTestability\nhttp://errors.angularjs.org/1.3.8/ng/test"
   Stacktrace:
     Error: Error while waiting for Protractor to sync with the page: "[ng:test] no injector found for element argument to getTestability\nhttp://errors.angularjs.org/1.3.8/ng/test"
    at Error (<anonymous>)

こちらのエラーが表示される事もあるようです。

Error: Angular could not be found on the page

原因

divタグにng-appを定義していたのが原因でした。

<div ng-app="myApp">

解決策 1

htmlまたはbodyに書きます。

<html ng-app="myApp">

解決策 2

それ以外のタグを使う場合はrootElementを指定します。

https://github.com/angular/protractor/blob/master/docs/referenceConf.js


タイムアウト

Timed out waiting for page to load after 10000ms

https://github.com/angular/protractor/blob/master/docs/timeouts.md

protractor, WebDriver, jasmineとそれぞれタイムアウトがあります。 上記URLを参考にタイムアウトの時間を調整します。


Seleniumが動かない

Starting Selenium server
Warning: Selenium Standalone is not present. Install with webdriver-manager update --standalone
 Use --force to continue.

原因

Protractorをグローバルインストールしていたのが原因で、webdriver-manager updateしてもプロジェクト内にSeleniumがインストールされていませんでした。

$which protractor --> /usr/local/bin/protractor
$which webdriver-manager --> /usr/local/bin/webdriver-manager (update/startはこれに対して実行されていた)

解決策 1

パスを指定してアップデートします。

$node_modules/bin/webdriver-manager update

解決策 2

npm runを使うとプロジェクト内のwebdriver-managerに対してのコマンドになります。

$npm run webdriver-manager update

ブラウザが古い

/node_modules/selenium-webdriver/lib/atoms/error.js:113
  var template = new Error(this.message);
                 ^
UnknownError: unknown error: Chrome version must be >= 40.0.2214.0
  (Driver info: chromedriver=2.15.322448 (52179c1b310fec1797c81ea9a20326839860b7d3),platform=Linux 3.14.28-031428-generic x86_64) (WARNING: The server did not provide any stacktrace information)

原因

Protractorが期待するブラウザのバージョンを満たしていないとこのエラーになります。

解決

ブラウザをバージョンアップします。


ログインできない

プロジェクト内のページにログイン認証を設けている場合、E2Eテストでブラウザを表示すると「未ログイン」の状態になります。 ごく一般的な仕組みですが、Protractorでどうやって「ログイン済」にするか悩みました。

解決策 1

設定のonPrepareでテスト開始前の挙動を指定する事ができます。 テスト実行用ユーザーを用意できるのであれば、この仕組みを使うことができます。

How do I deal with my log-in page?

// conf.js
onPrepare: function () {
    browser.driver.get('http://localhost/login');
    
    browser.driver.findElement(by.id('username')).sendKeys('user');
    browser.driver.findElement(by.id('password')).sendKeys('pass');
    browser.driver.findElement(by.id('_submit')).click();

    return browser.driver.wait(function() {
        return browser.driver.getCurrentUrl().then(function(url) {
            return true;
        });
    }, 5000);
}

参考

書き方編で紹介するDOMの指定とは異なっています。ログインページがAngularJSでない時はProtractorAPIでなく、上記のようにWebDriverJSのAPIを使用します。

解決策 2

ログイン不要のHTMLを用意します。E2Eテストはこのページに対して行います。

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="angular.js"></script>
        <script type="text/javascript" src="controller.js"></script>
    </head>
        <script>
        // <![CDATA[
        (function (angular, global) {
            angular
                .module('test-project', ['my-app', 'ngMockE2E'])
                .run(function ($httpBackend) {
                    // APIは全てモックする
                    $httpBackend.whenGET(/views.*/).respond(200).passThrough();
                    $httpBackend.whenGET(/api_v1_get_items/).respond(200, {id:1, name: 'test'});
                })
            ;
        })(angular, this);
        // ]]>
        </script>
    </head>

    <body ng-app="test-project">
        <!-- コントローラ側の$routeProviderでビューを切換える -->
        <div ng-view=""></div>
    </body>
</html>

まとめ

今回はセットアップからテストを実行するまでの方法を紹介しました。 次回はProtractorを使ったユニットテストの書き方を紹介する予定です。