はじめに
この記事は、React Native による Android アプリケーションに WebdriverIO + Appium + mocha + chai で自動E2Eテストを導入した際に気をつけたことをまとめたものになります。
既存の React Native プロジェクトに新しく自動テストを導入したいと考えている方の参考になれば幸いです。
E2Eテストとは
E2E (End to End) テストとは、「システム利用者の画面操作により、システムが想定通りに動作することを確認する」テストのことです。
今回構築する自動テストの概要
- Android実機上で Appium がサーバー、PC上で WebdriverIO がクライアントとなり、HTTPコマンドをやりとりすることで画面を操作します
- テストコードは mocha と chai を使って書きます
- テスト対象の React Native な Android アプリはAPKファイルで用意します
- 今回は自動E2Eテストの導入ということで、「アプリの初期画面に特定のコンポーネントが存在すること」をテストしてみます
WebdriverIOのインストール
プロジェクトのルートディレクトリで以下コマンドを実行します。
npm install @wdio/cli
WebdriverIOのセットアップ
プロジェクトのルートディレクトリでnpx wdio config
を実行すると、WebdriverIO のセットアップが始まります。
選択肢は以下のように選択します。
? Where is your automation backend located? On my local machine
? Which framework do you want to use? mocha
? Do you want to run WebdriverIO commands synchronous or asynchronous? sync
? Where are your test specs located? ./test/specs/**/*.js
? Do you want WebdriverIO to autogenerate some test files? Yes
? Do you want to use page objects (https://martinfowler.com/bliki/PageObject.html)? No
? Are you using a compiler? Babel (https://babeljs.io/)
? Which reporter do you want to use? spec
? Do you want to add a service to your test setup? appium
? What is the base url? http://localhost
セットアップが終わると、package.json
のdevDependencies
内に以下の記述が追加されたことを確認できます(バージョンは一例であり、この限りではありません)。
"devDependencies": {
"@wdio/appium-service": "^7.12.5",
"@wdio/local-runner": "^7.12.5",
"@wdio/mocha-framework": "^7.12.5",
"@wdio/spec-reporter": "^7.12.5",
"@wdio/sync": "^7.12.5",
"appium": "^1.21.0"
}
以上で WebdriverIO のセットアップは完了です。
wdio.conf.jsの編集
WebdriverIO のセットアップの過程で、プロジェクトのルートディレクトリにwdio.conf.js
というファイルが生成されているはずです。
そのwdio.conf.js
内のcapabilities
の項目を以下に置き換えます。
capabilities: [{
maxInstances: 1,
platformName: 'Android',
deviceName: 'hoge',
app: 'APKファイルへの絶対パス',
automationName: 'UiAutomator2',
testEnv: 'Android',
}]
deviceName:
はなんでも良いです。
app:
は、今回テストする React Native プロジェクトの APK ファイルへの絶対パスを指定します。
APKファイルは、npm run android
を実行するとプロジェクトのルートディレクトリ/android/app/build/outputs/apk/debug/app-debug.apk
に保存されます(デバッグビルドの場合)。
これで WebdriverIO と Appium を使う準備が整いました。
chaiのインストール
mocha は WebdriverIO のセットアップ過程で一緒にインストールされますが、 chai は手動でインストールする必要があります。
プロジェクトのルートディレクトリで以下コマンドを実行します。
npm install --save-dev chai
これでchaiによるアサーションをテストコードに記述できるようになりました。
テストコードを書く
WebdriverIO のセットアップの過程でプロジェクトのルートディレクトリ/test/specs/example.e2e.js
というファイルが生成されています。
このファイルの中身を以下に置き換えます。
const Chai = require('../../node_modules/chai');
const expect = Chai.expect;
describe('お試しテスト:', () => {
it('テストボタンが表示されること', () => {
$('~TIDTestButton').waitForDisplayed({ timeout: 60000 });
expect($('~TIDTestButton').isExisting()).to.be.true;
});
});
waitForDisplayed()
はコンポーネントが画面に表示されるのを待つ関数です。
ここではTIDTestButton
というテストIDを持つコンポーネントが画面に表示されるまでに、待ち時間として60000ms=60秒を指定しています。
60秒待っても表示されなかった場合はテスト失敗となります。
isExisting()
はコンポーネントが画面上に存在しているかを見る関数です。存在する場合は true を返します。
.to.be.true
の部分で、TIDTestButton
というテストIDを持つコンポーネントが画面上に存在することをアサートしています。
テストIDを仕込む
AndroidではaccessibilityLabel
というラベルでテストIDを指定します。
UIを構成するファイル内で、以下のようにaccessibilityLabel
をセットします。
<Button {...{accessibilityLabel: 'TIDTestButton'}} title={'Test'} />
これで、TestボタンにTIDTestButton
というテストIDを仕込むことができました。
npm test コマンドを追加
WebdriverIO によるテストを実行するコマンドはnpx wdio run ./wdio.conf.js --spec example.e2e.js
というコマンドです。
これを毎回コンソールに打ち込むのは大変なので、もう少し簡単なコマンドで実行できるようにします。
package.json
のscripts
内に以下の記述を追加してください。
"scripts": {
"test": "npx wdio run ./wdio.conf.js --spec example.e2e.js"
}
これで、プロジェクトのルートディレクトリでnpm test
コマンドを叩くだけで WebdriverIO による自動テストが走るようになりました。
テスト前の準備
ついにテストを実行してみます。
まず、Android実機をPCに有線接続します。
接続した際に「USBデバッグを許可しますか?」と聞かれたら、許可を選択します。
Android実機がPCに接続できたかどうかは、adb devices
コマンドで確認できます。
% adb devices
List of devices attached
デバイス名 device
このように出力されたら接続できています。
接続できない場合は、ケーブルを抜いてもう一度挿すなどしてみてください。
なお、今回テストに用いるAPKファイルがデバッグビルドしたものである場合は、テスト実行する際に Metro Bundler を立ち上げておく必要があります。必要に応じて立ち上げてください。
Metro Bundler を立ち上げるコマンドは以下です。
% react-native start
テストを実行してみる
それではいよいよ、プロジェクトのルートディレクトリでnpm test
コマンドを叩きます。
% npm test
> sampleApp@0.0.1 test /~~/sampleApp
> npx wdio run ./wdio.conf.js --spec example.e2e.js
Execution of 1 workers started at 2021-09-20T02:42:03.283Z
2021-09-20T02:42:03.308Z INFO @wdio/cli:launcher: Run onPrepare hook
2021-09-20T02:42:03.853Z INFO @wdio/cli:launcher: Run onWorkerStart hook
2021-09-20T02:42:03.854Z INFO @wdio/local-runner: Start worker 0-0 with arg: run,./wdio.conf.js,--spec,example.e2e.js
[0-0] 2021-09-20T02:42:03.921Z WARN @wdio/utils:shim: You are running tests with @wdio/sync which will be discontinued starting Node.js v16.Read more on https://github.com/webdriverio/webdriverio/discussions/6702
[0-0] 2021-09-20T02:42:04.244Z INFO @wdio/local-runner: Run worker command: run
[0-0] RUNNING in APKファイルへの絶対パス - /test/specs/example.e2e.js
[0-0] 2021-09-20T02:42:04.672Z INFO webdriver: Initiate new session using the WebDriver protocol
[0-0] 2021-09-20T02:42:04.708Z INFO webdriver: [POST] http://localhost:4723/session
[0-0] 2021-09-20T02:42:04.708Z INFO webdriver: DATA {
[0-0] capabilities: {
[0-0] alwaysMatch: {
[0-0] platformName: 'Android',
[0-0] deviceName: 'hoge',
[0-0] app: 'APKファイルへの絶対パス',
[0-0] automationName: 'UiAutomator2',
[0-0] autoGrantPermissions: true,
[0-0] testEnv: 'Android'
[0-0] },
[0-0] firstMatch: [ {} ]
[0-0] },
[0-0] desiredCapabilities: {
[0-0] platformName: 'Android',
[0-0] deviceName: 'hoge',
[0-0] app: 'APKファイルへの絶対パス',
[0-0] automationName: 'UiAutomator2',
[0-0] autoGrantPermissions: true,
[0-0] testEnv: 'Android'
[0-0] }
[0-0] }
~中略~
[0-0] 2021-09-20T02:42:12.904Z INFO webdriver: COMMAND findElement("accessibility id", "TIDTestButton")
[0-0] 2021-09-20T02:42:12.905Z INFO webdriver: [POST] http://localhost:4723/session/259a2883-8f45-454f-8f7c-074540435211/element
[0-0] 2021-09-20T02:42:12.905Z INFO webdriver: DATA { using: 'accessibility id', value: 'TIDTestButton' }
[0-0] 2021-09-20T02:42:13.393Z INFO webdriver: RESULT {
[0-0] 'element-6066-11e4-a52e-4f735466cecf': '00000000-0000-073e-ffff-ffff00000012',
[0-0] ELEMENT: '00000000-0000-073e-ffff-ffff00000012'
[0-0] }
[0-0] 2021-09-20T02:42:13.401Z INFO webdriver: COMMAND isElementDisplayed("00000000-0000-073e-ffff-ffff00000012")
[0-0] 2021-09-20T02:42:13.401Z INFO webdriver: [GET] http://localhost:4723/session/259a2883-8f45-454f-8f7c-074540435211/element/00000000-0000-073e-ffff-ffff00000012/displayed
[0-0] 2021-09-20T02:42:13.438Z INFO webdriver: RESULT true
[0-0] 2021-09-20T02:42:13.438Z INFO webdriver: COMMAND findElement("accessibility id", "TIDTestButton")
[0-0] 2021-09-20T02:42:13.438Z INFO webdriver: [POST] http://localhost:4723/session/259a2883-8f45-454f-8f7c-074540435211/element
[0-0] 2021-09-20T02:42:13.438Z INFO webdriver: DATA { using: 'accessibility id', value: 'TIDTestButton' }
[0-0] 2021-09-20T02:42:13.535Z INFO webdriver: RESULT {
[0-0] 'element-6066-11e4-a52e-4f735466cecf': '00000000-0000-073e-ffff-ffff00000012',
[0-0] ELEMENT: '00000000-0000-073e-ffff-ffff00000012'
[0-0] }
[0-0] 2021-09-20T02:42:13.543Z INFO webdriver: COMMAND findElements("accessibility id", "TIDTestButton")
[0-0] 2021-09-20T02:42:13.544Z INFO webdriver: [POST] http://localhost:4723/session/259a2883-8f45-454f-8f7c-074540435211/elements
[0-0] 2021-09-20T02:42:13.544Z INFO webdriver: DATA { using: 'accessibility id', value: 'TIDTestButton' }
[0-0] 2021-09-20T02:42:13.644Z INFO webdriver: RESULT [
[0-0] {
[0-0] 'element-6066-11e4-a52e-4f735466cecf': '00000000-0000-073e-ffff-ffff00000012',
[0-0] ELEMENT: '00000000-0000-073e-ffff-ffff00000012'
[0-0] }
[0-0] ]
[0-0] 2021-09-20T02:42:13.652Z INFO webdriver: COMMAND deleteSession()
[0-0] 2021-09-20T02:42:13.652Z INFO webdriver: [DELETE] http://localhost:4723/session/259a2883-8f45-454f-8f7c-074540435211
[0-0] PASSED in APKファイルへの絶対パス - /test/specs/example.e2e.js
2021-09-20T02:42:14.031Z INFO @wdio/cli:launcher: Run onComplete hook
"spec" Reporter:
------------------------------------------------------------------
[デバイス名 Android 10 #0-0] Running: デバイス名 on Android 10 executing APKファイルへの絶対パス
[デバイス名 Android 10 #0-0] Session ID: 259a2883-8f45-454f-8f7c-074540435211
[デバイス名 Android 10 #0-0]
[デバイス名 Android 10 #0-0] » /test/specs/example.e2e.js
[デバイス名 Android 10 #0-0] お試しテスト:
[デバイス名 Android 10 #0-0] ✓ テストボタンが表示されること
[デバイス名 Android 10 #0-0]
[デバイス名 Android 10 #0-0] 1 passing (2s)
Spec Files: 1 passed, 1 total (100% completed) in 00:00:10
2021-09-20T02:42:14.033Z INFO @wdio/local-runner: Shutting down spawned worker
2021-09-20T02:42:14.287Z INFO @wdio/local-runner: Waiting for 0 to shut down gracefully
2021-09-20T02:42:14.288Z INFO @wdio/local-runner: shutting down
こんな感じで出力されたらテスト成功です。
上手くいかないとき
何らかの理由で上手くいかない場合は、大体はエラー文を見ればわかりますが、主に以下を疑ってみてください。
-
package.json
に"@wdio/sync"
が追加されているか -
wdio.conf.js
に設定したAPKファイルへの絶対パスは正しいか -
npx wdio run ./wdio.conf.js --spec example.e2e.js
コマンドで指定しているファイルが存在するか、ファイル名は正しいか - デバッグビルドしたAPKファイルを使用する場合は Metro Bundler が立ち上がっているか
- テストしたいコンポーネントが画面上にあるか(スクロールしないと画面上に現れない場合は検出できないので、スクロール操作を行うテストコードを書く必要があります)