これは ドリコム Advent Calendar 2017 の11日目です。
10日目はhisano1210さんによる、『デザイナーへターミナルのすゝめ』です。
はじめに
自分の所属しているチームではブラウザゲームのフロントエンド基盤開発を行っています。基盤として提供するとなると品質は重要ですよね。
特にモバイル向けとなると実機でのパフォーマンスなどシビアに見る必要があります。
という訳で、パフォーマンスなどの品質を定量的に評価するために**【モバイル実機 × ブラウザ × 自動テスト】**の環境を構築しました。
業務で構築したものは複雑なテストや複数台の端末を使用するためなかなかヘビーな仕上がりですが、できる限りシンプルでミニマムな構成に整理したうえで、モバイル実機でブラウザ自動テストが行える構成をご紹介します。(使用モジュール等の詳しい説明は割愛します。インストールの項目などで公式サイトへのリンクを貼っているので、そちらをご確認ください。)
環境情報
Mac
ソフトウェア | バージョン |
---|---|
macOS | 10.12.6 (Sierra) |
Homebrew | 1.3.8 |
Node.js | 8.4.0 |
npm | 5.4.2 |
Xcode | 9.1 |
Android SDK Tools | 25.2.4 |
Android SDK Platform-tools | 25.0.2 |
モバイル実機
機種 | OS |
---|---|
Galaxy S7 edge | Android 7.0 |
iPhone X | iOS 11.1.1 |
構築手順
前提
- HomebrewやNode.jsがインストールされている
- Androidアプリがビルドできる状態である(開発者モードで動かせればOK)
- Wildcardな開発用のProvisioningProfileでiOSアプリのビルドができる状態である
- 環境構築するディレクトリで
npm init
などの基本的な設定が終わっている
Appiumのインストール
モバイル用のテスト自動化OSSであるAppiumをnodeモジュールとしてインストール
$ npm install appium --save-dev
Appiumは、ブラウザテストのデファクトスタンダードであるSeleniumと同様のAPIでモバイルテストを行う事ができます。
日本SeleniumユーザーコミュニティでもSeleniumと共に扱われているので、モバイルブラウザのテストを行うならAppiumを使用するのが良いと思います。
Appiumのセットアップ
appiumの構築状況チェックツールであるappium-doctorもインストール
npm install appium-doctor --save-dev
appium-doctorを実行して構築状況を確認
$ node node_modules/appium-doctor/
下図のように全部のチェックが通るまで、指摘内容を確認しつつ設定を進める
Android用の追加セットアップ
特になし
iOS用の追加セットアップ
iOS WebKit Debug Proxy のセットアップ
PCからモバイルSafariを操作するためのOSSであるiOS WebKit Debug Proxyをインストール
$ brew install ios-webkit-debug-proxy
iOS実機にてSafariのWebインスペクタを有効にする
設定 > Safari > 詳細 > Webインスペクタ
XCUITest Driverのセットアップ
appiumでXCUITestを使うためのDriverの依存パッケージをインストール
$ brew install libimobiledevice --HEAD
$ brew install ios-deploy
ios-deployのインストールでXcodeのコマンドラインツールが見つからないエラーが出たら下記コマンドでインストール
$ xcode-select --install
後述のテスト実行時にエラーxcodebuild failed with code 65
が出る場合はWebDriverAgentのビルドに失敗しています(ここが一番ハマります)。下記コマンドでWebDriverAgentのXcodeプロジェクトを開いてWebDriverAgentLib
とWebDriverAgentRunner
に対してWildcardなProvisioningProfileなどを設定し、一度手動でビルドしてみてください(詳しくはこちら)
$ open -a xcode ./node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent/WebDriverAgent.xcodeproj
WebdriverIOをインストール
Node.js環境でAppiumを操作するためのOSSライブラリであるWebdriverIOをインストール
$ npm install webdriverio --save-dev
WebdriverIOはAppium公式のGetting Startedでも使われています。
また、@cognitomさんが投稿されている[Seleniumアレルギーのための処方箋]でWebDriver(Selenium)クライアントの比較をされていますが、WebdriverIOが良い感じですね。
Mochaをインストール
Node.js環境で動くテストフレームワークであるMochaをインストール
$ npm install mocha --save-dev
MochaはWebdriverIOのテストフレームワークとして公式サポートされています。
他にJasmineなども使えますが、今回は社内で使い慣れたMochaを選択しました。(参考)
構成まとめ
iOS用のmacOSパッケージ類
- ios-webkit-debug-proxy
- libimobiledevice
- ios-deploy
Nodeモジュール類
{
"private": true,
"dependencies": {},
"devDependencies": {
"appium": "^1.7.1",
"appium-doctor": "^1.4.3",
"mocha": "^4.0.1",
"webdriverio": "^4.9.11"
}
}
WebdriverIOの実行
Appiumを起動する
WebdriverIOを実行する前にAppiumを起動する
$ node node_modules/appium/
[Appium] Welcome to Appium v1.7.1
[Appium] Appium REST http interface listener started on 0.0.0.0:4723
ログがこんな感じに出たらhttp://0.0.0.0:4723でAppiumが動いています
止める(Ctrl+C)までターミナルの1画面を占有するので、テストは別タブで実行します
Androidで実行
AndroidのChromeでGoogle検索ページを開いてタイトルを取得するコードの例
const wdio = require('webdriverio');
const options = {
port: 4723, // Appiumのポート番号に合わせる
desiredCapabilities: {
platformName: 'Android', // Androidで動かすことを指定
deviceName: 'Galaxy S7 edge', // これはなんでも良い
browserName: 'Chrome', // ブラウザのテストであることを指定
automationName: 'UiAutomator2', // 実機用のテストフレームワークを指定
}
};
wdio.remote(options).init()
.url('https://www.google.co.jp')
.getTitle().then((title) => {
console.log('Title : ' + title);
})
.end()
.catch((error) =>{
console.log(error);
});
実機をUSBでMacにつないで実行
$ node example.js
Title : Google
$
タイトルが取得できました
iOSで実行
iOSのSafariでGoogle検索ページを開いてタイトルを取得するコードの例
const wdio = require('webdriverio');
const options = {
port: 4723, // Appiumのポート番号に合わせる
desiredCapabilities: {
platformName: 'iOS', // iOSで動かすことを指定
deviceName: 'iPhone X', // これはなんでも良い
browserName: 'Safari', // ブラウザのテストであることを指定
udid: '<udid>', // 実機のUDID(これを指定すると実機で動く)
automationName: 'XCUITest', // 実機用のテストフレームワークを指定
xcodeOrgId: '<Team ID>', // https://developer.apple.com/account/#/membershipのTeam ID
startIWDP: true, // ios_webkit_debug_proxyの自動実行を指示
}
};
wdio.remote(options).init()
.url('https://www.google.co.jp')
.getTitle().then((title) => {
console.log('Title : ' + title);
})
.end()
.catch((error) =>{
console.log(error);
});
実機をUSBでMacにつないで実行
$ node example.js
Title : Google
$
Android同様、取得できました
MochaとWebdriverIOを組み合わせる
とりあえず組み合わせる
Android版でWebdriverIOとMochaを組み合わせたテストコード作成
const wdio = require('webdriverio');
const assert = require('assert');
const options = {
port: 4723, // Appiumのポート番号に合わせる
desiredCapabilities: {
platformName: 'Android', // Androidで動かすことを指定
deviceName: 'Galaxy S7 edge', // これはなんでも良い
browserName: 'Chrome', // ブラウザのテストであることを指定
automationName: 'UiAutomator2', // 実機用のテストフレームワークを指定
}
};
// wdioのインスタンスを作成
const browser = wdio.remote(options);
describe('テストグループ', function() {
// デフォルトだと2000msでタイムアウトになってしまうので延長設定
this.timeout(60000);
before((done) => {
// 初期化が完了したらコールバック.
browser.init().call(done);
});
after(function(){
browser.end();
});
it('Google検索ページのタイトル取得', () => {
return browser
.url('https://www.google.co.jp')
.getTitle().then((title) => {
assert.equal(title, 'Google');
});
});
it('GitHubトップページのタイトル取得', () => {
return browser
.url('https://github.com')
.getTitle().then((title) => {
assert.equal(title, 'The world\'s leading software development platform · GitHub');
});
});
});
mochaを実行する npm script としてtest
を追加
{
"private": true,
"scripts": {
"test": "mocha test.js"
},
"dependencies": {},
"devDependencies": {
"appium": "^1.7.1",
"appium-doctor": "^1.4.3",
"mocha": "^4.0.1",
"webdriverio": "^4.9.11"
}
}
npm script 実行
$ npm run test
> @ test /Users/Hoge/...
> mocha test.js
テストグループ
✓ Google検索ページのタイトル取得 (2614ms)
✓ GitHubトップページのタイトル取得 (5964ms)
2 passing (44s)
$
テスト成功です
良い感じに組み合わせる
WebdriverIOのオプション定義を、共通、Android、iOSで分割
exports.wdio = {
port: 4723
};
const base = require('./conf.base')
exports.wdio = Object.assign(base.wdio, {
desiredCapabilities: {
platformName: 'Android', // Androidで動かすことを指定
deviceName: 'Galaxy S7 edge', // これはなんでも良い
browserName: 'Chrome', // ブラウザのテストであることを指定
automationName: 'UiAutomator2', // 実機用のテストフレームワークを指定
}
});
const base = require('./conf.base')
exports.wdio = Object.assign(base.wdio, {
desiredCapabilities: {
platformName: 'iOS', // iOSで動かすことを指定
deviceName: 'iPhone X', // これはなんでも良い
browserName: 'Safari', // ブラウザのテストであることを指定
udid: '<udid>', // 実機のUDID(これを指定すると実機で動く)
automationName: 'XCUITest', // 実機用のテストフレームワークを指定
xcodeOrgId: '<Team ID>', // https://developer.apple.com/account/#/membershipのTeam ID
startIWDP: true, // ios_webkit_debug_proxyの自動実行を指示
}
});
良い感じのディレクトリ構造にする
root/
└─ test/
├─ conf.base.js 共通定義
├─ conf.android.js Android用定義
├─ conf.ios.js iOS用定義
└─ spec/test.js テストコード
テストコードを変更し、渡された環境変数を元に定義ファイルを呼び分けるようにする
// 環境変数で渡された名前から対象のコンフィグをロード
const config = require(`../conf.${process.env.NODE_TEST_DEVICE}`);
// wdioのインスタンスを作成
const browser = wdio.remote(config.wdio);
環境変数を設定しつつmochaを実行するtest:android
とtest:ios
の npm script を記述
{
"private": true,
"scripts": {
"test:android": "NODE_TEST_DEVICE=android mocha ./test/spec",
"test:ios": "NODE_TEST_DEVICE=ios mocha ./test/spec"
},
"dependencies": {},
"devDependencies": {
"appium": "^1.7.1",
"appium-doctor": "^1.4.3",
"mocha": "^4.0.1",
"webdriverio": "^4.9.11"
}
}
これで、Androidのテストはこんな感じで実行できます
$ npm run test:android
iOSのテスト実行はこうです
$ npm run test:ios
良い感じにシンプルでミニマムな構成に仕上がったんじゃないかなと思うので、こちらに置いておきます。
さいごに
今回は簡単なテストを動かすところまででしたが、業務ではパフォーマンスの計測やcanvas描画の差分確認などに活用しています。是非みなさんも色々試してみてください!(とりあえずAndroidから始めるとサクッといけますよ)