とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど

開催予定の勉強会

読者登録と各種SNSのフォローもよろしくお願いいたします。

Flutterで作ったネイティブアプリをAppiumでUIテストしてみた(node.js/Android編)

こんにちは。話題のFlutter使ってアプリ開発を始めてみたので、UIテストについてAppiumを使った自動化についてインストールの流れからテスト実行までの流れを記載していきます。 iOS版もまとめて掲載しようと思ったのですが、諸事情で別記事で投稿する予定です。

前提

  • Android StudioでFlutterを使用して作成したプロジェクトに最初からあるカウンターのサンプルアプリを利用

環境

  • MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports) / macOS Monterey / Intel CPU
  • Android Studio Bumblebee | 2021.1.1 Patch 1
  • Androidエミュレーター(デバイス:Pixel 4(Android 12.0))

Appiumのデスクトップ版をインストール

次のサイトからお使いの環境にあったファイルをダウンロードしてください。

github.com

インストーラの手順に従ってインストールが完了したら、アプリケーションを起動してください。 次のような画面が起動したら成功です。

Appium Inspectorをインストール

次のサイトからお使いの環境にあったファイルをダウンロードしてください。

github.com

インストーラの手順に従ってインストールが完了したら、アプリケーションを起動してください。 次のような画面が起動したら成功です。

Appiumサーバー起動

このアプリケーションのインストールが完了したので実際に動かしていきましょう。 Appium Desktop アプリを起動したら以下の 値を設定してください。 既に設定されている場合は値があっているかを確認してください。

項目 設定値
Host 0.0.0.0
Port 4723

値の入力が完了したら、Start Serverを押下して実行してください。 次の画像のように画面が切り替わって、最終行にAppium REST http interface listener started on 0.0.0.0:4723と表示されていたら成功です。 一部画面の一部のみ表示しています。

ここまででアプリのデバイスと通信するためのサーバーの準備が完了しました。 次は、Appium Inspectorを使って 実際にデバイスに接続してみましょう。

Appium Inspectorでデバイスに接続する

前の手順でインストールしたAppium Inspectorを起動してください。 接続に必要な情報を一通り入力していきましょう。

項目 設定値
Remote Host 127.0.0.1
Remote Port 4723
Remote Path /wd/hub

次にDesired Capabilitiesの値を設定していきます。 入力フォームで1つずつ入力していくか、 右側にあるJSONの編集機能で次の内容を貼り付けて編集してください。 環境によって内容が異なるものは記載を省略しています。

{
  "platformName": "android",
  "platformVersion": "{{ここに記載してください}}",
  "deviceName": "{{ここに記載してください}}",
  "automationName": "Appium",
  "app": "{{ここに記載してください}}"
}

deviceNameに記載するデバイス名の調べ方

次のコマンドを実行して. デバイス名を確認できます。 実行する前に利用する端末が接続されているか、 エミュレーターが起動されている必要があります。

adb devices

次のように何らかのデバイスが認識されたら、emulator-5554の部分をJSONに記載します。

adb devices
List of devices attached
emulator-5554   device

このように表示されたら正しく認識できていない可能性があります。 エミュレーターが起動されているか、USB接続されているデバイスがAndroid Studioで認識されているか確認してください。

$ adb devices
List of devices attached

appに記載するAPKファイルパスの調べ方

Android Studioを表示して、左上にあるプロジェクト名をクリック→build(以下のパス順にたどる) build/app/outputs/flutter-apk/app-debug.apkのファイルが見つかったらそこまでのフルパスを記載してください。

一通り入力入力が完了したら実行してみましょう。右下にあるStart Sessionを押下してください。 記載した内容に間違いがなければappで指定したアプリが起動されて表示されているはずです。

E2E系のツールを利用したことがある方であれば、Select Elementsで要素を選択するとXPATHが取得できるのでやり慣れた感じになると思います。 基本的にこの画面はテストコードを作成する前の必要な情報取得するためのツールだと思ってください。

テストコードを書いて実行

Android Studioで初めてプロジェクト作成した時にあるカウンターアプリを使ってテストコードを書いてみます。

テスト観点

右下のカウントアップボタンを押下したら、画面中央のカウンターが+1されるか

処理の流れ

  1. 現在の画面中央のカウンター値を取得する
  2. カウントアップボタンを押下する
  3. 画面中央のカウンター値を取得する
  4. 最初のカウンター値に+1加えたものとボタン押下後のカウンター値が一致するかチェックする

以上の流れをコードにするとこのような形になります。 値の一致/不一致について簡易的にassertを利用しています。

事前に以下のパッケージをインストールしてください。

npm install node ts-node typescript webdriverio

コード

import assert from "assert"
import { remote, RemoteOptions } from "webdriverio"

async function main() {
  const wait = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

  const option: RemoteOptions = {
    hostname: "0.0.0.0",
    path: "/wd/hub",
    port: 4723,
    capabilities: {
      platformName: "{{プラットフォーム名}}",
      platformVersion: "{{プラットフォーム名のバージョン}}",
      deviceName: "{{デバイス名}}",
      app: "{{APKファイルのパス}}",
      automationName: "Appium",
      newCommandTimeout: 60,  
    }
  }

  const browser = await remote(option)

  // カウンター値の取得(クリック前)
  const beforeValue = await (async () => {
    const element = await browser.findElement("xpath", `//android.view.View[@content-desc="0"]`)
    const value = await browser.$(element).getAttribute("content-desc")
    return value
  })()

  assert(beforeValue == "0");

  // カウントアップボタンを押下
  {
    const element = await browser.findElement("xpath", `//android.widget.Button[@content-desc="Increment"]`)
    browser.$(element).click()
  }

  await wait(1000)

  // カウンター値の取得(クリック後)
  const afterValue = await (async () => {
    const element = await browser.findElement("xpath", `//android.view.View[@content-desc="1"]`)
    const value = await browser.$(element).getAttribute("content-desc")
    return value
  })()

  assert(afterValue == "1");

  await browser.deleteSession()  
}

main()

remote(option, modifier)

WebdriverIOセッションを開始する

webdriver.io

findElement(using, value)

要素を検索する usingは、XPath形式を利用するため、xpathを指定している。

webdriver.io

$(…)

要素から値を取り出す

webdriver.io

deleteSession()

接続を終了して現在のセッションをクローズする

webdriver.io

Flutter Widget Testとの使い分けについて

UIテストは、環境依存する部分を除きFlutter Widgetのテストで実現する方がFlutter標準のテストツールでもあるのでよさそう。 環境依存などの観点で確認が必要な場合はAppiumで実際にエミュレーターやデバイスを使って確認することができるため、より実際に近い環境でテストが実施できそうですね。