AngularJS + Cordova なプロジェクトに Protractor + Appium を導入して iOS シミュレータで E2E テストを動かす
タイトルが長くなってしまったが、
- AngularJS に Cordova を取り込んだプロジェクトにおいて、
- Protractor を使った E2E テストを行いたいが、
- iOS シミュレータ上で E2E テストを動かすために Appium というツールを併用する
という流れである。
Protractor は AngularJS 向けに作られた E2E テストツール。
一方 Appium は Selenium WebDriver の一種で、E2E テストツールと WebDriver の間に割り込んで、iOS シミュレータや iOS 実機を操作してくれる。細かな仕組みは厄介なので、とりあえず「iOS シミュレータで E2E テストする時に使う Selenium の亜種」ぐらいに思っておけばよいかと。
- 参考 : Appiumの仕組みと使い方 - Qiita
- 参考 : SeleniumのUIテスト自動化をiOS/AndroidにもたらすAppiumの基礎知識とインストール方法、基本的な使い方 : スマホ向け無料システムテスト自動化ツール(8) - @IT
iOS シミュレータの動作、および Appium の動作のために色々とインストールが必要なモノが多いので、それらをまとめて紹介する。
実際のコードはコチラ
今回紹介したパッケージとサンプルコードを利用し、AngularJS + Cordova なアプリで Appium を使った E2E テストを行うサンプルプロジェクトを作成した。以下を参考にしてほしい。
XCode のライセンスに同意する
AppStore から XCode をインストールした後、コマンドラインでライセンスに同意しないといけない。
$ sudo xcodebuild -license
このコマンドを叩くと何やら文章が出てくる。Space
キーで抜けたら「agree
」と入力して Enter することでライセンス認証ができる。
Homebrew でインストールするモノ
Homebrew より
- Carthage
- iOS WebKit DebugProxy
- Lib iMobile Device
をインストールする。
Lib iMobile Device は iOS WebKit Debug Proxy が依存しているのか、一緒にインストールされるが、最新版 (--HEAD
) にアップデートしないと上手く動かないので更新をかけておく。
$ brew install carthage ios-webkit-debug-proxy
$ brew upgrade libimobiledevice --HEAD
# これで以下3つが追加されていれば OK
$ brew list
carthage # Cathage : appium-xcuitest-driver が依存している
git
ios-webkit-debug-proxy # iOS WebKit Debug Proxy : Safari インスペクタでデバッグする際に使用する
libimobiledevice # Lib iMobile Device : USB 接続された iOS デバイスの情報を取得するモノ
RubyGems でインストールが必要なモノ
RubyGems で XC Pretty というツールを入れておく。
$ gem install xcpretty
# これで以下2つがインストールされていれば OK
$ gem list
*** LOCAL GEMS ***
rouge (2.0.7) # Rouge : XCPretty が依存しているシンタックスハイライタ
xcpretty (0.2.8) # XCPretty : XCode のログを整形して表示する
npm でグローバルインストールが必要なモノ
最後に npm でグローバルインストールしておくモノたち。
- appium-doctor … コレは必須ではないが Appium が動作する環境かチェックできるため入れておく
- authorize-ios
- deviceconsole
- ios-deploy
- ios-sim
# グローバルインストールする
$ npm install -g appium-doctor authorize-ios deviceconsole ios-deploy ios-sim
# iOS の認証を行う
$ sudo authorize-ios
# Appium が動作する環境になっているかチェックする
# 何か問題があれば別途確認する
$ appium-doctor --ios
# 以下がインストールされていれば OK
$ npm list -g
├── appium-doctor@1.4.2 # Appium Doctor : Appium が使える環境かチェックする
├── authorize-ios@1.0.5 # Authorize iOS : iOS の認証を行う
├── deviceconsole@1.0.1 # Device Console : iOS のログを整形する
├── ios-deploy@1.9.1 # iOS Deploy : XCode を経由せず iOS アプリをインストールするために使用する
├── ios-sim@6.0.0 # iOS Sim : XCode を経由せず iOS アプリを起動するために使用する
これで Mac 環境全体で用意しておくモノは終わり。
プロジェクトに Protractor と Appium をインストールする
いよいよプロジェクトの準備。Protractor と Appium、そしてその連携に必要な WD (WebDriver) と WD Bridge というツールを入れる。
$ npm i -D protractor wd wd-bridge appium
今回は Gulp スクリプトから Protractor を実行したいのと、Appium サーバの起動も Gulp スクリプトに定義したいため、以下も入れておく。Jasmine-Spec-Reporter は Protractor の実行結果を整形して出力するためのモノ。
$ npm i -D gulp-protractor gulp-shell jasmine-spec-reporter
Appium の設定ファイルを作る
Appium はサーバとして起動させるため、その設定ファイルを用意する。nodeConfig.json
という名前でプロジェクト直下に以下のようなファイルを作る。
{
"configuration": {
"cleanUpCycle": 2000,
"timeout": 30000,
"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
"url": "http://127.0.0.1:4723/wd/hub",
"host": "127.0.0.1",
"port": 4723,
"maxSession": 1
}
}
何か色々な文献を見て研究した結果、こういう設定ファイルに落ち着いたけど、何が必要で何が不要かもう忘れてしまった。大体はデフォルト設定だと思うのだが、これでいいと思う。
続いて gulpfile.js
に Appium サーバを起動するタスクを作成する。
const gulp = require('gulp');
const $ = require('gulp-load-plugins')();
gulp.task('appium', $.shell.task(
['appium --nodeconfig ./nodeConfig.json --session-override'],
{ quiet: false }
));
要するにコンソールで $ appium --nodeconfig ./nodeConfig.json --session-override
と打てば良いだけなのだが、コレを $ gulp appium
で呼べるようにするために Gulp-Shell を使っている。
Protractor の設定ファイルを用意する
続いて Protractor の設定ファイルの用意。protractor.conf.js
なんて名前でプロジェクト直下にファイルを作って、以下のように書く。
exports.config = {
// Appium サーバの URL
seleniumAddress: 'http://127.0.0.1:4723/wd/hub',
// Base URL : 未指定で OK
baseUrl: '',
// Capabilities
capabilities: {
// Browser Name : Cordova アプリの場合は空にしておく
browserName: '',
// Auto WebView : AngularJS などハイブリッドアプリの場合は true にしておく
autoWebview: true,
// App : アプリファイルのパスを指定する
app: './platforms/ios/build/emulator/【アプリ名】.app',
// Bundle ID : config.xml に記載のアプリの識別子を指定する
bundleId: '【アプリの識別子】',
// Device Name : iOS シミュレータで E2E テストをする場合はシミュレータのデバイスを指定する
deviceName: 'iPhone 7',
// UDID : iOS 実機で E2E テストをする場合に使用する。今回は割愛
// udid: '',
// Platform Name : 「iOS」で OK
platformName: 'iOS',
// Platform Version : XCode でインストールしている SDK のバージョンに合わせておく
platformVersion: '10.3',
// Full Reset : iOS シミュレータの場合に起動する度にアプリを再インストールするか否かの設定
fullReset: false,
// Auto WebView Timeout : タイムアウトの設定
autoWebviewTimeout: 20000,
// Auto Accept Alerts : 認証系のアラートを自動的に許可する
autoAcceptAlerts: true
},
onPrepare: () => {
// Jasmine Spec Reporter の設定 (テスト結果をコンソールにイイカンジに出力する)
const SpecReporter = require('jasmine-spec-reporter').SpecReporter;
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: true,
displayPending: false
},
summary: {
displayPending: false
}
}));
// Implicitly Wait
browser.manage().timeouts().implicitlyWait(20000);
// この設定が謎。環境によっては false にしないと Click 等の動作を待たなくなる場合もあったが
// 検証時は true にしておかないとアプリが正しく動作してくれなかった
browser.ignoreSynchronization = true;
// Wd (Web Driver)・Wd Bridge の設定
const wd = require('wd');
const protractor = require('protractor');
const wdBridge = require('wd-bridge')(protractor, wd);
wdBridge.initFromProtractor(exports.config);
// Cordova アプリ向けに URL の表記を http プロトコルから file プロトコルに直す
const defer = protractor.promise.defer();
browser.executeScript('return window.location;')
.then((location) => {
browser.resetUrl = 'file://'
browser.baseUrl = location.origin + location.pathname;
defer.fulfill();
});
return defer.promise;
},
// Spec ファイルの指定
specs: ['./e2e/**/*.spec.js'],
// Jasmine Node の設定
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 500000,
includeStackTrace: true,
isVerbose: true,
print: () => {}
}
};
長ったらしいが、この設定に落ち着くのに時間がかかった。
特に browser.ignoreSynchronization
の設定が難儀で、アプリによって true
じゃないとダメだったり、false
じゃないとダメだったりでよく分からない。AngularJS を HTML に埋め込むやり方の違いによるものかなと思慮。
あと Gulp スクリプトから Protractor を起動するためのタスクを作る。
gulp.task('e2e', (done) => {
gulp
.src('./e2e/**/*.spec.js')
.pipe($.protractor.protractor({
configFile: 'protractor.conf.js'
}))
.on('error', (error) => {
throw error;
})
.on('end', () => {
done();
});
});
これは単純に Gulp-Protractor から Protractor を実行するだけ。
E2E テストコードを書いてみる
では実際にこの設定で Protractor と Appium を動かすため、簡単な E2E テストコードを書いてみる。
テスト対象とするのは AngularJS の公式ページのトップにある、「テキストボックスに文字列を入力すると、その文字列が h1 要素に表示される」というモノ。実コードは GitHub で見てみてください。
テストコード e2e/index.spec.js
はこんな感じ。
describe('トップページ', () => {
beforeEach(() => {
// トップページを開き直す
browser.get('');
});
it('名前を入れると表示される', () => {
// テキストボックスに文字を入力する
element(by.id('your-name')).sendKeys('My Name');
// h1#message に入力した文字を含めたテキストが出力されているべき
expect(element(by.id('message')).getText()).toBe('Hello My Name!');
// 目視で iOS シミュレータの動作を見てみたいのでココでは2秒停止させてみた
browser.sleep(2000);
});
});
Protractor は Jasmine ベースでテストコードが書ける。細かな書き方は別途 Protractor の使い方を調べてみてほしい。
実際に E2E テストを行ってみる
ようやく E2E テストの実施にこぎつけた…。
予め Cordova アプリをビルドしておく。実際にテストする iOS シミュレータでアプリが起動するところまでやっておくと良いだろう。
# 何らかのビルドタスクで www ディレクトリに成果物を作り…
$ npm run build
# www ディレクトリのブツを Cordova ビルドして iOS シミュレータにインストールする
$ npm run cordova emulate --target=iPhone-7
アプリの準備ができたら、まず1つ目のターミナルで、Gulp の appium
タスクを実行する。
$ npm run gulp appium
これで Appium サーバが立ち上がるので、このターミナルはこの状態で放っておく。
次に、2つ目の別のターミナルを開き、コチラで Gulp の e2e
タスクを叩いて Protractor を実行する。
$ npm run gulp e2e
e2e
タスクを実行すると iOS シミュレータが起動し、iOS 上で操作を再現するための WebDriver アプリが自動的にインストールされる。その後テスト対象の Cordova アプリが開き、E2E テストが開始される。
結果は e2e
タスクを実行したコンソールに表示される他、iOS シミュレータとの接続状況は appium
タスクを実行したコンソールにも随時表示されている。
これにて Protractor + Appium による、AngularJS + Cordova アプリの E2E テストが出来た。
iOS 実機でも E2E テストを実行することは可能だが、ローカルにインストールした appium に対して追加で設定が必要だったりしてそこが難儀なので、別途紹介する。