開発
開発
プラットフォームを選択

Meta Questスクリプト化対応テストサービスを使用してE2Eテストを有効にする

Meta Quest開発者向けの、デバイス上で自動化されるエンドツーエンド(E2E)テストには課題があります。ユーザー重視のデバイス機能が、テストの障害になることがあるためです。例えば、モーダルシステムダイアログによってテストの起動が妨げられたり、長時間にわたるパフォーマンステストの最中にテストデバイスが自動スリープに入ったりする可能性があります。Meta Questスクリプト化対応テストサービスを使用すると、これらの機能を無効化し、自動化されたテストを確実に実行できます。さらに、デバイスをテスト用に設定したり反復テスト用にリセットしたりするために必要な手動のステップの数が大幅に削減され、完全に撤廃されるケースも多数あります。このサービスの目的は、開発者がより効率的かつ大規模にテストを実施できるようにするため、ハードウェア構成やテストプロセスを合理化することです。

ユースケース

Meta Quest OS v44には、認定されたMeta開発者が、自動化テストの実行などの開発者タスクを円滑に実施することを目的として、自身のデバイスをリセットしたり特定のシステム機能を無効にすることを可能にしたりするテストサービスが組み込まれています。特に、新しいサービスでは、開発者はヘッドセットを装着しなくても、ヘッドセット上でシンプルなADBコマンドを使って以下を行うことができます。
  • プロパティを設定/取得
    • [Allow USB Debugging (USBデバッグを許可する)]ダイアログや、[Allow connected device to access files (接続されているデバイスによるファイルへのアクセスを許可する)]ダイアログなど、特定のモーダル(ブロッキング)システムダイアログを有効/無効にする。
    • 境界線を有効/無効にする。
    • 自動スリープを有効/無効にする。
  • デバイスをリセット
    • デバイスを工場出荷時の設定にリセットする。
    • WiFiを設定する。
    • テストユーザーとしてログインする。
DEVELOPER USE ONLY
これらの機能の開発者以外による使用や、Meta Questソフトウェア開発以外の目的での使用はサポートされていません。そのような使い方を試みた場合、ヘッドセットの寿命が短くなる可能性があります。

前提条件

  • ADBとファストブートが環境設定中に開発ワークステーションにインストールされていない場合は、インストールします。これらは、関連するAndroid SDKプラットフォームツールパッケージをインストールすることによってインストールできます。
  • ヘッドセットをMeta Quest OS v44以上にアップデートします。
  • テストユーザーをまだ作成していなければ、作成します。テストユーザーのメールアドレス、パスワード、PINをメモしておきます。
  • 開発者アカウントまたは作成したいずれかのテストユーザーアカウントでログインし、デバイスを初回の使用に備えて準備します。これは、ヘッドセットを装着して標準ユーザーの設定フローに従うか、Meta Quest開発者ハブの「Setup New Device (新デバイスの設定)」機能を使って行うことができます。

使用方法

この機能には、以下で説明するADBコマンドを使ってスクリプトでアクセスすることができます。その際に、ヘッドセットを装着する必要はありません。この方法は、ローカルで自動テストを実行したり、スケールテスト用にデバイスラボを管理したりする場合に最適です。
テストサービスには、次の2つの主要な機能があります。
  • GET_PROPERTYSET_PROPERTY - これらを使用して、自動化テストに干渉する恐れのあるサービスをコントロールできます。
  • WIPE_DEVICESETUP_FOR_TEST - これらを使用して、デバイスを既知の状態にリセットし、順次実行するテストが互いに干渉し合わないようにすることができます。
これらの機能の使用方法については、以下のセクションで詳しく説明します。

自動化に干渉するサービスのコントロール(SET/GET_PROPERTY)

SET_PROPERTYコマンドとGET_PROPERTYコマンドは、同期的に実行し、適切な戻り値を提供します。SET_PROPERTYはコマンドの成否を返し、失敗した場合には対応するエラーメッセージを返します。GET_PROPERTYはシンプルに、サポートされているすべてのプロパティの現在設定されている値を含んだバンドルを返します。
// Disable three system features that would otherwise interfere with an E2E test.
// Note that a developer (or test user) must be logged on and you must specify
// the Store PIN associated with that logged in account.
> adb shell content call --uri content://com.oculus.rc --method SET_PROPERTY \
   --extra 'disable_guardian:b:true'  \
   --extra 'disable_dialogs:b:true'   \
   --extra 'disable_autosleep:b:true' \
   --extra 'PIN:s:1234'
Result: Bundle[{Success=true}]

// Query the currently set property values.
> adb shell content call --uri content://com.oculus.rc --method GET_PROPERTY
Result: Bundle[{disable_guardian=true, disable_dialogs=true, disable_autosleep=true}]

// Simulate putting on the headset by triggering the proximity sensor.
> adb shell am broadcast -a com.oculus.vrpowermanager.prox_close

// Install your APK and execute your test here. E.g.,
> adb install /path/to/my_app.apk
> adb shell am start -S com.my.app.packagename/.MainTestActivity

// Re-enable the system features.
> adb shell content call --uri content://com.oculus.rc --method SET_PROPERTY \
   --extra 'disable_guardian:b:false'  \
   --extra 'disable_dialogs:b:false'   \
   --extra 'disable_autosleep:b:false' \
   --extra 'PIN:s:1234'

// Simulate taking the headset off by triggering the proximity sensor.
> adb shell am broadcast -a com.oculus.vrpowermanager.prox_far
スクリーンバーンインや急速なバッテリー消費など、意図しない副作用を避けるため、テストコードを実行した後は、無効にしたすべてのプロパティを有効に戻してください。デバイスを工場出荷時の設定にリセットすることで、これらのすべてのプロパティをデフォルト値に戻すこともできます。しかし、再起動してもデバイスはリセットされません。

デバイスをワイプしてテストに備える(WIPE_DEVICE/SETUP_FOR_TEST)

反復して行う各E2Eテスト実行を独立させ向上させるにためのベストプラクティスは、デバイスをワイプして既知の状態からテストを開始することです。そうすることで、特定のテスト実行が、前回の実行から何らかの影響を受けにくくなります。このセクションでは、WIPE_DEVICEとSETUP_FOR_TESTへの呼び出しをつなげて、デバイスをワイプしテストに備える方法を説明します。フローの例を以下に示します。
// Factory reset the device. Note that if a user is logged on, it must be a
// developer (or test user) and you must specify the Store PIN associated with
// that account.
> adb shell content call --uri content://com.oculus.rc --method WIPE_DEVICE \
    --extra 'PIN:s:1234'
Result: Bundle[{Message=WIPE_DATA Pending, Success=true}]

// ...wait for the device to re-start…

// Login your test user (it must be a test user) and connect to local wifi.
> adb shell content call --uri content://com.oculus.rc --method SETUP_FOR_TEST \
    --extra 'WIFI_SSID:s:my_wifi_ssid' \
    --extra 'WIFI_PWD:s:my_wifi_password' \
    --extra 'WIFI_AUTH:s:WPA' \
    --extra 'EMAIL:s:my_test_user@tfbnw.net' \
    --extra 'PWD:s:my_test_user_password'

// ...wait for the device to re-start...

// [Optional] Disable auto-sleep. Note that disable_guardian and disable_dialogs
// was already completed as part of the SETUP_FOR_TEST call above.
> adb shell content call --uri content://com.oculus.rc --method SET_PROPERTY \
   --extra 'disable_autosleep:b:true' \
   --extra 'PIN:s:1234'
Result: Bundle[{Success=true}]

// Install your APK and execute your test here. E.g.,
> adb install /path/to/my_app.apk
> adb shell am start -S com.my.app.packagename/.MainTestActivity

// Repeat the process again for your next test, starting with WIPE_DEVICE.

デバイスのワイプおよび設定用のPythonヘルパースクリプト

このフローを簡素化するために、ADBコマンドを1つのPythonメソッド呼び出しに抽象化する、Pythonヘルパースクリプトのサンプルが含まれています。適切なパラメーターを指定してsetupDeviceForTest()を呼び出すだけで、次の操作が行われます。
  1. WIPE_DEVICEを呼び出す。
  2. デバイスの再起動を待機する。
  3. SETUP_FOR_TESTを呼び出す。
  4. デバイスの再起動を待機する。
メソッドから応答があると、デバイスはAPKをインストールしてテストを実行できます。
# Ready the connected device to run a test by factory resetting the device,
# connecting to wifi and logging in a test user.
#
# testUserEmail/testUserPassword: specify the credentials of an existing Oculus test
#   user (https://developers.meta.com/horizon/resources/test-users). This is the
#   user that will be logged into the device after it's been reset. Generally, these
#   email addresses are in the @tfbnw.net domain.
#
# testUserPin: if the device is in a logged in state, then it must be a developer
#   or test user that's logged in AND (for security purposes) this parameter must
#   specify that user's Store PIN. If calling this method when the device
#   is in a non-logged in state, this parameter is ignored.
#   Note that the logged in user may be different from the test user specified above.
#
# wifiSSID/wifiPassowrd: specify valid credentials for a wifi network that is in range..
#
# deviceId: specify the device's serial number--only required if multiple headsets
#   are connected to the host machine.
def setupDeviceForTest(
    testUserEmail,
    testUserPassword,
    testUserPin,
    wifiSSID,
    wifiPassword,
    deviceId=None,
):
    # Factory reset the device to get a clean test environment. This is a requirement
    # of the subsequent SETUP_FOR_TEST call. WIPE_DEVICE will reset the device but also
    # preserve ADB access after the subsequent reboot.
    result = __runAdbShell(
        f"content call --uri content://com.oculus.rc --method WIPE_DEVICE "
        f"--extra 'PIN:s:{testUserPin}'",
        deviceId,
    )
    if result.returncode == 0 and "Success=true" in result.stdout:
        __runAdbCommand("wait-for-disconnect", deviceId)
        __waitForDeviceBootCompleted(40, deviceId)
    else:
        print(
            f"WIPE_DEVICE call failed: returncode={result.returncode}; {result.stdout}; {result.stderr};"
        )
        return -1

    # Connect to wifi and log in the test user.
    result = __runAdbShell(
        f"content call --uri content://com.oculus.rc --method SETUP_FOR_TEST "
        f"--extra 'WIFI_SSID:s:{wifiSSID}' --extra 'WIFI_PWD:s:{wifiPassword}' --extra 'WIFI_AUTH:s:WPA' "
        f"--extra 'EMAIL:s:{testUserEmail}' --extra 'PWD:s:{testUserPassword}'",
        deviceId,
    )
    if result.returncode == 0 and "Success=true" in result.stdout:
        __runAdbCommand("wait-for-disconnect", deviceId)
        __waitForDeviceBootCompleted(40, deviceId)
        __waitForDumpSys("Horizon logged in: true", 10, deviceId)
    else:
        print(
            f"SETUP_FOR_TEST call failed: returncode={result.returncode}; {result.stdout}; {result.stderr};"
        )
        return -1


# Install the specified APK and launch the app.
def installAndStartApp(apkPath, packageName, activityName, deviceId=None):
    __runAdbCommand(f"install {apkPath}", deviceId)
    __runAdbShell(f"am start -S {packageName}/{activityName}", deviceId)


def sleepHeadset(deviceId=None):
    __runAdbShell("input keyevent POWER", deviceId)


def __runShellCommand(command):
    print(f"SHELL: {command}")
    split = command.split()
    result = subprocess.run(split, capture_output=True, text=True)
    return result


def __getDeviceArg(deviceId):
    if deviceId is None:
        return " "
    else:
        return f" -s {deviceId} "


def __runAdbShell(command, deviceId):
    return __runShellCommand("adb" + __getDeviceArg(deviceId) + "shell " + command)


def __runAdbCommand(command, deviceId):
    return __runShellCommand("adb" + __getDeviceArg(deviceId) + command)


def __waitForProperty(property, maxSeconds, deviceId):
    print(f"Waiting for {property} to turn true")
    start = time.time()
    while "1" not in __runAdbShell(f"getprop {property}", deviceId).stdout:
        if time.time() - start > maxSeconds:
            raise RuntimeError(f"timed out while waiting for {property} to turn true")
        __sleep(2)
    print(f"{property} is true")


def __waitForDeviceBootCompleted(maxSeconds, deviceId):
    __runAdbCommand("wait-for-device", deviceId)
    __waitForProperty("sys.boot_completed", maxSeconds, deviceId)
    __sleep(2)


def __waitForCommand(command, targetString, maxSeconds):
    print(f"Waiting for command '{command}' to return '{targetString}'")
    start = time.time()
    while True:
        result = __runShellCommand(command)
        # Break the loop if we find the target string in stdout or stderr.
        if (
            result.stderr.find(targetString) >= 0
            or result.stdout.find(targetString) >= 0
        ):
            break
        # Raise an exception if we don't find the target in time.
        if time.time() - start > maxSeconds:
            raise RuntimeError(
                f"timed out while waiting for command '{command}' to return '{targetString}'. \n"
                + f"STDOUT: {result.stdout} \n"
                + f"STDERR: {result.stderr} \n"
            )
        __sleep(2)
    print("Found return string: " + targetString)


def __waitForDumpSys(targetString, maxSeconds, deviceId):
    __waitForCommand(
        "adb" + __getDeviceArg(deviceId) + "shell dumpsys CompanionService",
        targetString,
        maxSeconds,
    )


def __sleep(seconds):
    print(f"SLEEP {seconds}s")
    time.sleep(seconds)

よくある質問

WIPE_DEVICE / SETUP_FOR_TESTフローの後でデバイスを起動したときに何らかのOSダイアログが表示される場合、問題になりますか?
そのようなダイアログによってプログラムを使用したテストの起動が(adb installおよび adb shell am startの実行時に)妨げられる場合に限り、問題になります。これらの機能では、障害となるダイアログのみ排除されます。その他の障害にならないダイアログは、変わらず表示されるはずです。
SETUP_FOR_TESTが正常に返ってきましたが、デバイスが再起動しません。何が原因ですか?
SETUP_FOR_TEST呼び出しによって{Message=Login/Wifi Pending, Success=true}が返されてもデバイスが再起動しない場合、通常は指定されたWiFi認証情報か、テストユーザーのログイン認証情報のいずれかに問題があります。デバイスのlogcatでSkip NUXを検索すれば、詳しい情報を入手できます。例えば次のような、問題の原因を示すエラーメッセージを確認できるかもしれません。
  • [Skip NUX] Failed to connect to WiFi.
  • [Skip NUX] Failed to login.
スペースは、(WiFi SSIDなどで使用する場合)バックスラッシュでエスケープされる必要がある点に注意してください。
私は1日のうちに数百回もSETUP_FOR_TESTを呼び出しますが、テストユーザーのログインが時々失敗し、意味不明なエラーメッセージが表示されます。どう対処すればよいですか?
同じテストユーザーを使用して1日に数十回もしくは数百回ログインした場合、認証サービスはテストユーザーによるログインをスロットリングすることがあります。この制限を回避する最良の方法は、追加のテストユーザーを作成して、それらのユーザーの間でローテーションすることです。
これらの機能は、プロビジョニングしていないデバイスでも作動しますか?
いいえ。当社では、プロビジョニングされていないデバイスのサポートは提供していません。
これは、ホストコンピューターに複数のデバイスを接続している場合であっても機能しますか?
はい。-sパラメーターを使って対象となるデバイスのシリアル番号を指定することで、ADBコマンドを正しいデバイスにルーティングすることができます。adb devicesで、接続されているすべてのデバイスのシリアル番号を確認できます。
私のデバイスが接続されていることをADBが認識しないのはなぜですか?
前提条件のセクションで説明したように、最初に従来の方法でデバイスにログインして開発者モードを有効にする必要があります。こうすることで、デバイスが接続されたときにADBが認識できるようになります。このアクセスは、デバイスを従来の方法(fastboot erase userdataなど)で工場出荷時の設定にリセットした場合は失われ、再ログインが必要になります。こうした状況を避けるため、デバイスのリセットが必要な場合はWIPE_DEVICEコマンドのみを使用し、その直後に有効なSETUP_FOR_TESTを呼び出します。

追加のテストリソース

Questスクリプト化対応テストサービスによってデバイスでE2Eテストを実行する準備を整えることはできますが、テストの実行や検証は行いません。そうするためには、テストドライバーやテストフレームワークが必要です。これに関しては、以下のようないくつかの選択肢があります。

参考情報

ナビゲーションロゴ
日本語
© 2026 Meta