ProtractorはAngularJSでE2Eテストをするための環境です。 セットアップ編・書き方編の2回に分けてテストを始めるまでのステップを紹介します。
この記事を書いた環境
- Mac OSX 10.9
- AngularJS 1.4
- Protractor 2.1
Protractorとは
E2Eテストをするには、まずブラウザを起動してAngularJSのページを表示し、DOMを介してページ内のコンテンツにアクセスする事が必要になります。Protractorはそれらに必要な環境が含まれています。
- ブラウザ操作ツール(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. ブラウザを変更する(省略可)
firefox(デフォルト)
// conf.js
exports.config = {
capabilities:{
'browserName': 'firefox'
}
その他のブラウザ
// conf.js
exports.config = {
capabilities:{
'browserName': 'chrome'
}
それ以外のブラウザは サポートしているブラウザ を参考にしてください。
6. テスト用フレームワークを変更する(省略可)
Jasmine2がデフォルトですが、Mochaなど別のフレームワークに変更可能です。
7. テストを実行する
webdriver-managerを起動
$node_module/protractor/bin/webdriver-manager start
Seleniumが起動しコンソールのプロセスを専有します。 Ctrl+Cを押すと終了します。
Protractor実行
// さきほど作った設定ファイルのパスを渡す
$node_module/protractor/bin/protractor conf.js
結果
テストが通りました! これでセットアップは完了です。
デバッガが使える
Protractorを実行するとブラウザが起動して実際の画面を見ることができますが、あっという間にテストが終わってしまうため隅々まで確認するのは困難です。
デバッガを使うとテストの途中で動きを一旦停止できるため、画面の確認などにとても便利です。
pause()
// tests/e2e.js
describe("protractorテストサンプル", function () {
it("test", function () {
expect('hello'.length).toBe(5);
// ここでストップ
+ brower.pause();
});
});
コンソールを見ると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にタスク登録して簡単に呼び出せるようにしておきましょう。
プラグインのインストール
- grunt-protractor-webdriver webdriverを自動でスタート
- grunt-protractor-runner Protractorを実行する
// 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を使ったユニットテストの書き方を紹介する予定です。