📸

IVRy のモバイルアプリの通話自動テストへの取り組み

に公開

IVRy で QA エンジニアをしている関 ( @IvryQa )です。

IVRyでは、モバイルアプリ(iOS / Android)を提供しており、それらのQAや自動化も担当しています。
モバイルアプリでは、発着信や不在通知の確認、ウィジェットからの電話履歴確認など、日々の業務で使われる機能が搭載されています。

電話機能

ウィジェット機能
ウィジェット機能のキャプチャ

このモバイルアプリの QA を進める上で、OS や端末によるバリエーションの多さに加え、発着信のように実際に電話をかけて挙動を確認するケースも多く、手動テストの負担が大きくなってきています。

そこで、テストにかかる工数を改善するため、モバイルエンジニアのメンバーにも協力してもらいながら、モバイル領域の自動テスト環境を整える取り組みを進めてきました。

今回の記事では、こうしたモバイルアプリ特有の事情を踏まえつつ、どのように自動化を実現していったのか、その過程や工夫をまとめています。

モバイルアプリの中でも通話機能が絡むテストは、Web アプリ向けの E2E テスト[1] とはまったく性質が異なり、私自身、この領域に約1年ほど試行錯誤しながら向き合ってきました。同じような課題に取り組んでいる方への参考になれば幸いです。

導入の背景と目的

IVRyのモバイルアプリ開発は、ここ半年でエンジニアメンバーも増えて、加速し、リリース頻度も上がってきました。新機能だけではなく、リファクタリングやライブラリ更新も増え、その度に行うリグレッションテストの工数が大きくなっています。

また、iOS / Android の両プラットフォームに対応しているため、OS や端末による挙動の違いも無視できません。思わぬ影響範囲が出ることもあり、確認すべきパターンは多岐にわたります。さらに電話は社会インフラ的な性質が強く、"つながること" が前提のため、確実な動作確認が求められます。

こうした背景から、QAがボトルネックになることを避け、開発者も効率的にテストできるよう、リグレッションテストの自動化は必須だと感じていました。中長期的には次の3点を実現できる状態を目指し、リグレッションテストの自動化に取り組み始めました。

  1. リリース時のリグレッションテストの効率化
  2. 開発側でもセルフチェックできる仕組みの構築(可能であればビルド直後での早期に不具合を検知する)
  3. iOS / Android のクロスプラットフォーム検証を効率化する

自動化を進めるうえで見えてきた課題

モバイルアプリ、特に電話機能を含む領域の自動テストを進めるにあたって大きな壁がありました。まずは、 Appium[2] や Maestro[3] をはじめとしたモバイル向けの E2E テスト基盤や、ローコード系の自動テストサービス[4] で実現できないか検証しましたが、いずれも課題がありました。

これらのツールは前提として アプリ内で完結する UI 操作 を想定しているため、OS 標準 UI(着信画面・通話 UI など)や着信の仕組みのように、アプリ外で処理される要素を操作できません。その結果、期待していたような通話領域の自動化は実現できませんでした。

▫️ アイブリーアプリの着信時と通話時の挙動イメージ

▫️ 各ツールで実現できたこと・できなかったこと
※検証項目は他にもありますが、検証期間が限られていたため、電話機能やウィジェット機能に絞って検証を進めました。

テストケース 優先度 必須 Appium Airtest CodeceptJS
iOS Android iOS Android iOS Android
要素チェック
・特定の要素が表示されているか ⚪︎ ⚪︎ ⚪︎ ⚪︎ ⚪︎ ⚪︎
ウィジェット
・電話履歴が表示されているか × × × ×
本人確認(カメラ)
・本人確認のカメラで撮影時の自動化 - - - - - -
電話(発信)
・発信 × × × ×
電話(着信)
・着信(通知のポップアップが表示されるか) × × × ×
電話(端末設定)

課題を踏まえてたどり着いたのが Airtest

さまざまなE2Eツールを検証して行った結果、最終的に これなら通話領域の自動化ができるかもしれない と判断したのが Airtest でした。
Airtest は、アプリ内だけでなく OS が表示する通話 UI までも画像として検出・操作できるため、通話機能を含む IVRy のモバイルアプリ特有の制約を避けてテストを行うことができました。

Airtest とは

Airtest[5] は Python ベースのスクリプトで動作するフレームワークで、Android / iOS の両プラットフォームをカバーしています。
ゲームの自動テストでよく使われることから、複雑な UI の画像認識に強いのが特徴になっています。

モバイル向けのテストフレームワークは、 アプリが描画している UI コンポーネント を前提に操作しますが、Airtest は OS が表示する UI(着信画面、通知、ネイティブの通話 UI など)も画像として扱える ため、従来のツールでは触れなかった領域まで自動化できる点が大きな強みとなっています。

たとえば、Android の着信画面で表示される緑色の 応答 ボタンや、Android のスワイプ操作などもAirtestなら画像認識によって直接扱うことができます。

さらに Airtestは実行結果として、手テップごとのスクリーンショットやログをまとめたレポートを自動生成してくれます。

これにより、

  • OS 標準 UI(着信画面・通話 UI など)
  • プッシュ通知
  • アプリ外で表示されるポップアップ(着信時に全画面表示される画面)

といった、従来のテストツールでは操作できなかった領域までカバーできるようになりました。

全体的な構成

通話まわりの E2E テストは、GitHub Actions → 実行用マシン → 実機端末 という流れで動かす構成にしています。

Airtest を利用する上で、エミュレーターや実機端末など選択肢はありますが、今回は エミュレーターではなく、実機端末を直接接続してテストを実行する運用 を採用しました。

将来的に 通話中の音声入出力や対話ロジックの検証 にも広げていきたいこと、そしてまずは 小さく始めて確実に動く形を作りたかった ことが理由です。

テスト実行フロー(全体図)

  1. GitHub Actions(セルフホストランナー) [6] から Airtest の実行スクリプトを起動
  2. セルフホストランナー上の Airtest が 実機(iOS / Android)を操作
  3. テスト完了後、結果をSlack へ通知(GitHub Actionsの実行結果も一緒に通知)

現在は手動トリガーでテストを実行していますが、今後はアプリのビルド完了をトリガーに自動実行されるようにすることも検討しています。
ビルド生成直後に通話まわりの挙動を検証できるようにすることで、問題の早期検知とフィードバックサイクルの短縮につなげる狙いです。

実際に自動化しているテストケース例
通話機能まわりで実際に自動化しているシナリオの一例として、Android 実機からの発信テストを紹介します。

このテストでは、アプリからテスト用の 050 番号に発信し、以下を画像認識ベースで検証しています。

  • アプリ側での発信画面遷移
  • ダイヤルパッド上の電話番号が期待どおり表示されていること
  • OS の通話 UI で通話中ダイアログが表示されること
  • 終話後もアプリがクラッシュせず動作していること
# Android 実機からの発信テスト(抜粋)

from airtest.core.api import (
    Template,
    touch,
    assert_exists,
    sleep,
)
from common.android_operations import (
    AndroidOperations,
    setup_android_device_connection,
    setup_device_specific_images,
)

IVRY_STG_PACKAGE = "xxx"
OUTGOING_NUMBER = "xxx"  # テスト用番号

# 端末固有の画像パスを設定 & デバイス接続
setup_device_specific_images()
setup_android_device_connection(__file__)

android_ops = AndroidOperations(IVRY_STG_PACKAGE)

def test_outgoing_call():
    """アプリからの発信〜通話確認〜終話までを行う E2E テスト"""

    # アプリ起動
    if not android_ops.ensure_app_running():
        raise RuntimeError("アプリの起動に失敗しました")

    # 発信画面を開く
    def open_dialer():
        touch(Template("dial.png"))
    android_ops.safe_operation(open_dialer)

    # 電話番号を入力
    def input_phone_number():
        for digit in OUTGOING_NUMBER:
            touch(Template(f"dial_{digit}.png"))
    android_ops.safe_operation(input_phone_number)

    # 入力した番号が正しいことを画像で検証
    assert_exists(
        Template("outgoing_number_on_keypad.png", threshold=0.95),
        "ダイヤルパッド上の電話番号が期待値と異なります",
    )

    # 発信ボタンをタップ
    def start_call():
        touch(Template("dial_start.png"))
    android_ops.safe_operation(start_call)

    # 通話開始を待機(実際にはタイマーなどで判定してもよい)
    sleep(10)

    # 通話中ダイアログの表示を確認
    def check_call_dialog():
        assert_exists(
            Template("call_to_dialog.png"),
            "通話中ダイアログが表示されていません",
        )
    android_ops.safe_operation(check_call_dialog)

    # 終話
    def end_call():
        touch(Template("dial_end.png"))
    android_ops.safe_operation(end_call)

    # 通話終了後もアプリが生きていることを確認
    assert android_ops.is_app_running(), "終話後にアプリがクラッシュしています"

導入してよかったポイント

発信・着信まわりの自動テストが実現可能になったこと

これが一番大きい成果かなと思います!

発信や着信まわりの UI はアプリではなく OS 側で描画される仕様のため、一般的なモバイル向け E2E ツールでは操作できません。この部分がずっとネックになっていて、Appium や Maestro、ノーコード系サービスなど、見つけたツールはひと通り試してきたのですが、どれも通話まわりの操作までは対応できず、長いあいだ手詰まりが続いていました。
そんな中で、協力してくれたメンバーの知見もあり Airtest にたどり着けたことで、OS が描画する UI を画像認識で扱えるようになり、発着信まわりの自動化に現実的な道が開けることができました。

現状は比較的シンプルなシナリオから運用を始めていますが、今後はより複雑なケースや新機能も含めて自動化の幅を広げられる見込みが立ったことが大きいです。 自動化できないと思っていた領域に希望が見えたことは、今回の取り組みの中でも特に大きな収穫でした!

辛いポイント

E2E テスト特有の 実行時間が長くなる といった大変さもありますが、今回の仕組みならではの辛さとしては、大きく二つあります。

一つ目は、メンテナンスが重くなりがちな点です。
Airtest は画像認識ベースのため、OS アップデートで UI が変わったり、アプリ側の画面デザインを変更しただけでも、テストがその影響を受けます。さらに、端末ごとの画面サイズや解像度の違いによってボタンの見え方が微妙に変わることもあり、そのたびにキャプチャを取り直したり、画像を差し替えたりする必要が出てきます。

二つ目は、誤操作のリスクです。
画像認識は強力な一方で、わずかな位置ズレや表示タイミングの遅れによって、意図しない場所をタップしてしまう可能性があります。特に発信テストでは、電話番号のボタンタップがずれてしまい、想定外の番号に電話をかけてしまう危険もあります。このため、判定ロジックやガード処理については、かなり慎重に設計・運用するようにしています。

今後の展望

モバイルアプリの通話自動テストは、まだ立ち上げ段階で、これから取り組みたい領域や課題がたくさんあります。冒頭で挙げた 3 つの狙いに対しても、現時点では通話領域の一部シナリオを自動化できたところまでで、リリース前のリグレッションテスト全体を置き換えられるほどのカバレッジや、開発側のセルフチェック用の自動実行までは至っていない状況です。

まずは iOS/ Android のシンプルな通話シナリオで足場を固めつつ、CI への組み込みなど段階的に進めていく想定です。そうした前提をふまえつつ、今後は次のような取り組みを進めていきたいと考えています。

① 発話テストの自動化
今回は、着信 → 応答 → 終話 や アプリからの発信 といったシンプルな操作をベースにシナリオを作成していますが、実際には、通話中の対話まで含めた検証が必要になります。こういった部分に関しても自動テストできるようにしたいと考えています。ただ、音声入力をどう扱うかが大きな課題で、現状では確立された方法が見つかっていなく、スピーカーを使って音声を流すなど、ハードを組み合わせた方式も検討しています。

② 通話状態・同時着信など複雑ケースへの対応
保留、転送、同時着信、着信拒否、通話中ポップアップなど、実運用では頻繁に起こるケースも自動化の対象に広げたいと考えています。
通話ロジックは条件が多く複雑ですが、ここをカバーできると品質の底上げに大きく寄与する領域です。

③ メンテナンス効率化
画像認識ベースのテストでは UI 変更によるスナップショット更新がどうしても発生します。
このあたりは LLM や MCP [7] を活用し、

  • 差分の自動検出
  • 更新スナップショットの自動生成
  • PR の自動作成
    などを行う仕組みを作り、負荷を軽減したいと考えています。

④ 電話機能以外の自動テスト拡充
まずは電話領域から着手していますが、アプリ全体で見ると自動化できる部分はまだまだ多くあります。
既存の仕組みにこだわらず、より適したツールや方式があれば積極的に取り入れながら、電話以外の領域にも広げていく予定です。

まとめ

IVRy では、モバイルアプリの品質向上とテストプロセスの効率化に向けて日々改善を進めており、その一環として今回紹介した 通話領域の自動テスト にも取り組んでいます。

現在はアプリチームとも連携しながら、環境や運用のブラッシュアップを継続的に進めています。たとえば、アプリのバージョンを指定してインストールし、自動テストを実行できる仕組みなど、より開発フローに組み込みやすい形へと改善が進んでいます。

今回は通話機能を中心に紹介しましたが、モバイルアプリのQAにはまだ多くの改善余地があり、今後も試行錯誤しながら取り組んでいく領域がたくさんあります。
音声 × モバイル × 自動化 という組み合わせは既存の事例がほとんどなく、これまでにも 通話録音の差分検知を試作した ように、自分たちで創意工夫をしながら進めていて、とても面白いチャレンジだと感じています。

もしこの領域に興味のある方がいれば、ぜひカジュアルにお話しできれば嬉しいです!

https://www.notion.so/ivry-jp/IVRy-e1d47e4a79ba4f9d8a891fc938e02271

https://herp.careers/v1/ivry/wmZiOSAmZ4SQ

脚注
  1. End-to-Endテスト。実際のユーザー操作に近い流れを再現して動作確認するテスト ↩︎

  2. Appium。モバイルアプリのUIテストに使われる自動化フレームワーク ↩︎

  3. Maestro。最近注目されている軽量モバイルE2Eツール ↩︎

  4. ノーコード・ローコードの自動テストサービス。UI操作をGUIで作成できるが OS標準UIには弱い ↩︎

  5. NetEase製の画像認識ベースの自動テストフレームワーク。ゲーム領域で広く使われている ↩︎

  6. GitHub Actions のセルフホストランナー。自前PCで Workflow を実行できる仕組み ↩︎

  7. Model Context Protocol。LLM と外部ツールを連携させる仕組み ↩︎

IVRyテックブログ

Discussion