3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React Native な Android アプリに WebdriverIO + Appium + mocha + chai で自動E2Eテストを導入してみた

Last updated at Posted at 2021-09-20

はじめに

この記事は、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.jsondevDependencies内に以下の記述が追加されたことを確認できます(バージョンは一例であり、この限りではありません)。

package.json
"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の項目を以下に置き換えます。

wdio.conf.js
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というファイルが生成されています。

このファイルの中身を以下に置き換えます。

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.jsonscripts内に以下の記述を追加してください。

package.json
"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 が立ち上がっているか
  • テストしたいコンポーネントが画面上にあるか(スクロールしないと画面上に現れない場合は検出できないので、スクロール操作を行うテストコードを書く必要があります)
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?