アプリ開発やサイト制作のスマホ端末実機検証・テスト-Remote TestKit

Remote TestKit の使い方

Appium連携のチュートリアル

概要

Appiumは、スマホやタブレットのテスト自動化を実現するためのテストフレームワークのひとつです。 以前より Remote TestKit では仮想 ADB 機能や Xcode連携機能 (Xcode connector) を用いることで、ローカル PC 上に構築された Appium 環境を用いたテスト自動化が可能でした。新しく、Remote TestKit Appium Cloudにより、Appium サーバ環境を構築することなく簡便に Appium のテストスクリプトを実行することが可能となり、CI 環境との連携も容易に実現することが可能となりました。
※Remote TestKit Appium CloudはFlat 3以上のプランをご契約しているお客様のみご利用になります。

Remote TestKit がサポートする Appium 連携について

■Remote TestKit を用いない場合の Appium 環境


一般的には Appium によるテスト自動化を行うためには下図に示すとおり、Appium を動作させるローカル PC 内に、Node.js や Appium Server などの実行環境を整えた上で、テストスクリプトを作成してスクリプトを実行させる必要がありました。 またテストスクリプトで用いる端末もローカル PC に接続された実機端末のみが対象となり、Android/iOS シミュレータを用いらざるを得ないケースも多々ありました。

■Remote TestKit の仮想adb機能/Xcode連携を用いた Appium 環境


Remote TestKit では従来より、仮想 ADB 機能や Xcode連携機能 (Xcode connector) を用いることで、クラウド上の端末を用いた Appium 環境を構築することが可能です。この構成では低レイヤーでクラウド上の端末と連携するため Appium 以外のテストフレームワークとの連携が可能というメリットがあります。 ※iOS 端末においては Xcode を用いたデバッグで一部機能制限があります。

■Remote TestKit Appium Cloudを用いたAppium環境


Remote TestKit Appium Cloudを利用することで、Appium を用いたテスト自動化においてはユーザがテストスクリプトさえ用意すれば Node.js や Appium server 等の実行環境を整えることなく動作可能となりました。また本機能を用いることで、社内外の CI 環境との連携も用意に実現することが可能となり、本来業務としてのテストスクリプトの開発に集中することができます。新機能を用いた Appium 環境は下記のとおりとなります。
※現時点では Android4.4以上またはiOS10.3以上の端末に対応
※Remote TestKit Appium CloudはFlat 3以上のプランをご契約しているお客様のみご利用になります。

各種 Appium 環境との比較

前述した 3 つの環境についての比較は下記表のとおりとなります。

RTK Appium Cloudを用いた環境 RTK 仮想adb機能/Xcode連携を用いた環境 RTK なしの環境
Server 環境構築 不要 必要 必要
Client ライブラリ 必要 必要 必要
スマートフォン実機 不要 不要 必要
テストスクリプト実行場所 ローカル PC ローカル PC ローカル PC
Appium以外の連携 不可
CI 環境との連携 簡易 ThriftAPI による連携が必要 実機連携はやや困難

解決される課題

テスト自動化に取り組まれている企業で課題となるのが CI(継続的インテグレーション) での実機検証です。 市中のCIサービスは海外のサービスが多く、端末ラインナップも海外端末がほとんどです。一方 Jenkins 等を用いて自社内に CI 環境を構築する場合は、実機検証環境を用意し運用することは大変です。今回リリースされる Remote TestKit の新しい Appium 機能を用いることで、CI 環境から実機検証を比較的容易に実現することが可能となります

Remote TestKit Appium Cloudを用いた Appium 連携の手順

基本的には、ローカル PC 上の Appium 環境で動かしているテストスクリプトの Capabilities の設定を変更するだけで Appium自動テストクラウドをご利用いただけます。 以下 Ruby 言語によるテストスクリプトの例を挙げながら説明いたします。まずはじめに Ruby が実行可能な PC を準備してください。


Appium Client ライブラリのインストール

Appium Client ライブラリを下記の手順でインストールします。

gem install selenium-webdriver -v 3.142.7
gem install appium_lib -v 11.2.0
gem install appium_lib_core -v 4.7.1

Python 言語でテストスクリプトを実行する際には下記の手順でインストールします。

pip3 install Appium-Python-Client==1.3.0
もしくは
pip install Appium-Python-Client==1.3.0

接続先の説明

Appium クライアント(テストスクリプト)の接続先として通常ローカル PC 上で実行しているときは "http://localhost:4723/wd/hub" としている部分を "https://gwjp.appkitbox.com/wd/hub" を指定します。

・Rubyでのコード例
@driver = Appium::Driver.new(
    caps: {
        ...
    },
    appium_lib: {
        #server_url: 'http://localhost:4723/wd/hub',
        server_url: 'https://gwjp.appkitbox.com/wd/hub',
        wait: 60
    }
).start_driver

ユーザ認証

Appium クライアントの接続時の認証には、Remote TestKit のユーザ名・パスワードを Capabilities に指定するか、アクセストークンを Capabilities に指定します。アクセストークンはソフトウェア版のメニューから生成することが可能です。生成することが可能です。

Capabilities名
userName Remote TestKit のユーザ名
password Remote TestKit のパスワード
accessToken Remote TestKit のトークン

・Rubyでのコード例(ユーザ名・パスワード認証の場合)

@driver = Appium::Driver.new(
    caps: {
        userName: "Remote TestKitのユーザ名",
        password: "Remote TestKitのパスワード",
        ...
    },
    appium_lib: {
        ...
    }
).start_driver

・Rubyでのコード例(アクセストークン認証の場合)

@driver = Appium::Driver.new(
    caps: {
        accessToken: "Remote TestKitのアクセストークン",
        ...
    },
    appium_lib: {
        ...
    }
).start_driver

・アクセストークンの取得方法

ソフトウェア版のメニューから生成することが可能です。ソフトウェア版の右上メニューからアクセストークン設定をクリックすることでアクセストークンを取得することができます。アクセストークンを再生成する場合は、再生成ボタンをクリックしてください。




Appium のバージョン指定

Android端末に対してはAppiumのバージョンが指定可能なものは1.7.2,1.8.0,1.8.1,1.9.0,1.9.1,1.10.0,1.10.1,1.11.0,1.11.1,1.12.0,1.12.1,1.13.0,1.14.0,1.14.2,1.15.1,1.16.0,1.17.0,1.17.1,1.18.0,1.18.1,1.18.2,1.18.3,1.19.0,1.19.1,1.20.0,1.20.1,1.20.2,1.21.0となります。

iOS端末はバージョン指定をすることが出来ません。iOS端末が接続されているサーバで設定された Appium のバージョンに依存した固定のバージョンとなります。

Capabilities名
appiumVersion 1.7.2,1.8.0,1.8.1,1.9.0,1.9.1,1.10.0,1.10.1,1.11.0,1.11.1,1.12.0,1.12.1,1.13.0,1.14.0,1.14.2,1.15.1,1.16.0,1.17.0,1.17.1,1.18.0,1.18.1,1.18.2,1.18.3,1.19.0,1.19.1,1.20.0,1.20.1,1.20.2,1.21.0のいずれか指定

端末指定

「機種名のみ」「機種名 & バージョン」の二種類の方法で、Appium を実行する端末を指定できます。機種名には正規表現も指定することができます。

  • 指定する端末は、Web クライアントや PC クライアントで確認します。
  • 機種名のみで指定する場合には、下記の Capabilities を指定します。
Capabilities名
platformName AndroidまたはiOSを指定
deviceName 機種名を指定 "全体一致" → "スペースによる区切りありの前方一致" → "前方一致の正規表現" の優先順位で判定(大文字・小文字の区別なし)
・Rubyでのコード例(Nexus 5を指定)
@driver = Appium::Driver.new(
    caps: {
        ...
        platformName: 'Android',
        deviceName: 'Nexus 5X',
    },
    appium_lib: {
        ...
    }
).start_driver

  • 機種名 & バージョンやデバイスIDで指定する場合には、下記の Capabilities を指定します。
  • 機種名で絞り込んだ後、バージョンやデバイスIDで判定されて、端末が決定されます。

Capabilities名
platformName AndroidまたはiOSを指定
deviceName 機種名を指定 "全体一致" → "スペースによる区切りありの前方一致" → "前方一致の正規表現" の優先順位で判定(大文字・小文字の区別なし)
platformVersion バージョンを指定 PC クライアントの OS カラムの "Android" "iOS" を除いた部分を指定 "全体一致" → "前方一致の正規表現" の優先順位で判定
udid 端末のデバイスIDを指定 "全体一致"で判定
・Rubyでのコード例(Nexus 5X の version8 を指定)
@driver = Appium::Driver.new(
    caps: {
        ...
        platformName: 'Android',
        deviceName: 'Nexus 5X',
        platformVersion: '8',
    },
    appium_lib: {
        ...
    }
).start_driver
・Rubyでのコード例(iPhone 7の version と デバイスID を指定)
@driver = Appium::Driver.new(
    caps: {
        ...
        platformName: 'iOS',
        deviceName: 'iPhone 7',
        platformVersion: '11.1',
        udid: 'aea01650edff7cf42bc72c73e882b8ade545db85',
    },
    appium_lib: {
        ...
    }
).start_driver

APIを使った共有ストレージへのアプリのアップロード

Remote TestKitにはアプリストレージ機能というものがあります。これは、PCクライアントのメニューからアプリケーションファイルをストレージへアップロードする機能です。
このメニューの代替として、REST APIを使ってコマンドラインからストレージへアプリをアップロードする機能です。CI環境の利用を前提としたお客様などに便利な機能です。

概要

APIを使ってapk/ipaファイルをRemote TestKitのストレージにアップロードする。

URL

https://gwjp.appkitbox.com/storage/xxxxxxxx

xxxxxxxxにアップロードするファイル名を指定してください。

Method Type

POST

curlコマンドでの実行例
RTK_USERNAME=xxxxxxxx
RTK_ACCESSTOKEN=xxxxxxxx
RTK_FILENAME=xxxxxxxx
curl -u $RTK_USERNAME:$RTK_ACCESSTOKEN -X POST -H 'Content-Type: application/octet-stream' https://gwjp.appkitbox.com/storage/$RTK_FILENAME --data-binary @/some/path/to/file

注意事項

ご利用にあたって、以下の注意事項があります。

  • 日本語のファイル名を指定することはできません。
  • 同じハッシュのファイルをアップロードすることはできません。

アプリのインストール

方法1.

Remote TestKit からアクセス可能なお客様ご自身の公開サーバ等にapkまたはipaをアップロードして下記の Capabilities を指定します。 ※ 必要に応じて社内の Firewall 等の設定に Remote TestKit からのアクセス許可設定を追加してください。アクセス元 IP アドレスは FAQ に記載があります。

Capabilities名
app apkまたはipaをアップロードした URL を指定
bundleId ipaのbundleId名 ※ipaの場合必須
・Rubyでのコード例
@driver = Appium::Driver.new(
    caps: {
        ...
        app: "https://appkitbox.com/test/sample.apk",
    },
    appium_lib: {
        ...
    }
).start_driver

方法2.

Remote TestKit の クライアント版にログインして、メニュ-より、アプリストレージ機能を使って apk または ipa をアップロードした後に、下記の Capabilities を指定してください。

Capabilities名
app アップロードした apk または ipa ファイル名を指定 同名ファイルが複数アップロードされている場合には、最新のアップロードファイルが使われます
・Rubyでのコード例
@driver = Appium::Driver.new(
    caps: {
        ...
        app: "sample.apk",
    },
    appium_lib: {
        ...
    }
).start_driver

自動再署名機能の指定

ipaをインストールする際、自動再署名が行われます。必要に応じて下記のCapabilitiesを指定していただく事で、自動再署名をOFFにできます。

Capabilities名
resigningEnabled TrueまたはFalse (※Falseを指定で自動再署名がOFF)

実行中の画面の確認

Appium サーバに接続後に画面を取得するための snapshotUrl および screenUrl が発行されます。capability を使って snapshotUrl または screenUrl を取得し、ブラウザで開くことで、現在実行中の画面が閲覧可能です。snapshotUrl はワンショットで静止画像を取得する機能に対して、screenUrl は定期的に静止画像が更新され動画風に画面を確認できる機能となっております。プログラム内から画像を取得する場合には snapshotUrl 機能を用い、ブラウザで確認する場合には screenUrl 機能を用いると便利です。

・Rubyでのコード例
snapshotUrl = @driver.capabilities['snapshotUrl']

実行中の Appium 実行ログの確認

Appium サーバの実行ログを取得するためには、ログレベルを Capabilities に指定し、capability を使って serverLogUrl を取得することでブラウザ等で開くことで、Appium のログが取得可能です。 取得可能な Appium ログはセッションを開始してから取得時までのすべてのログとなり、最終出力(通常はセッション終了ログ)から 10 分の間、ログを取得可能となります。

Capabilities名
logLevel "info", "warn", "error", "debug" のいずれかを指定。デフォルト値は "info"
・Rubyでのコード例
serverLogUrl = @driver.capabilities['serverLogUrl']

Appiumセッションの強制削除

Appiumのテストケースを実行時に、エラーが発生したりした際にクライアントが強制終了するなどで正常にセッションの削除が実行されないとセッションが残りますが、同一の端末指定条件で再度テストを行った際にはセッションを上書きし再度同一端末でテストを実行することが可能です。ただし、この場合はセッションが上書きされるため、同一ケーパビリティで複数端末を同時にテストすることができません。複数端末を同時にテストしたい場合には"noSessionOverwriting"ケーパビリティにtrue(真偽値)を指定してください。これにより、Appium Gatewayのセッションで利用中の端末が端末選択からスキップされて、セッションが上書きされないようになります。

Capabilities名
noSessionOverwriting true, false のどちらかを指定。デフォルト値は false
・Rubyでのコード例
@driver = Appium::Driver.new(
    caps: {
        ...
        noSessionOverwriting: true,
    },
    appium_lib: {
        ...
    }
).start_driver

端末レンタルの自動返却

指定の時間Appium サーバ経由で同一端末にアクセスがない場合には、自動返却が行われます。指定が無い場合、10分後に自動返却されます。

Capabilities名
deviceKeepingTimeout "300000 ~ 1800000"[ミリ秒]の表記。5~30分の間の時間を指定。

端末レンタルの自動延長

レンタル残り時間が 10分未満になった場合に、Appium サーバ経由で同一端末にアクセスがあれば、自動的に 30 分間のレンタル自動延長が行われます。


端末レンタルの強制返却

端末は利用がないと自動返却されますが、すぐに別の端末で自動検証を行いたいときには強制的に端末を返却することが可能です。テストコードを実行中にreturnDeviceUrlが発行されます。capability を使って returnDeviceUrl を取得し、テストコードの中で該当URLへアクセスすることで強制的に端末を返却できます。


・Rubyでのコード例
returnDeviceUrl = @driver.capabilities['returnDeviceUrl']
system("curl #{returnDeviceUrl}")

最新のChrome Driverでの実行

最新のChrome Driverで動作しない場合は、Capabilityに下記設定を追記する必要があります。


・rubyでの設定例の抜粋
caps: {
    'chromeOptions': {'w3c': false},
},

Google 検索を行う Ruby のテストスクリプト

以下のスクリプトを test.rb 等の名前でローカル PC に保存してください。

require 'rubygems'
require 'test/unit'
require 'selenium-webdriver'
require 'appium_lib'

# get userName, password from Environment variable
RTK_USERNAME = ENV['RTK_USERNAME']
RTK_PASSWORD = ENV['RTK_PASSWORD']
unless RTK_USERNAME && RTK_PASSWORD then
    puts "Environment variable error"
    exit(0)
end


class OpenUrlTest < Test::Unit::TestCase
    def setup
        opts = {
            caps: {
                userName: RTK_USERNAME,
                password: RTK_PASSWORD,
                deviceName: 'Nexus 5',
                platformName: 'Android',
                browserName: 'Chrome',
                chromeOptions: {'w3c': false}
            },
            appium_lib: {
                server_url: 'https://gwjp.appkitbox.com/wd/hub',
                wait: 60
            }
        }
        @driver = Appium::Driver.new(opts).start_driver
    end

    def teardown
        @driver.quit()
    end

    def test_google_search
        puts @driver.capabilities['snapshotUrl']
        # Open URL
        url = "https://www.google.com/"
        puts "Open URL: " + url
        @driver.get(url)
        element = @driver.find_element(:name, 'q')
        sleep(5)
        @driver.save_screenshot('capture_01.png')

        # Input keys
        word = "Remote testKit"
        puts "Input Keys: " + word
        element.send_keys(word)
        element.send_keys(:enter)
        sleep(5)
        @driver.save_screenshot('capture_02.png')

        # Get value
        value = @driver.find_element(:name, 'q').value
        puts "Text field value=" + value
        assert_equal true, value == "Remote testKit"
    end
end

iPhone端末で電卓で計算を行う Ruby のテストスクリプト

以下のスクリプトを test.rb 等の名前でローカル PC に保存してください。

# gem install selenium-webdriver
# gem install appium_lib
# export RTK_USERNAME=xxxx
# export RTK_PASSWORD=xxxx

require 'rubygems'
require 'test/unit'
require 'selenium-webdriver'
require 'appium_lib'

# get userName, password from Environment variable
RTK_USERNAME = ENV['RTK_USERNAME']
RTK_PASSWORD = ENV['RTK_PASSWORD']
unless RTK_USERNAME && RTK_PASSWORD
  puts 'Environment variable error'
  exit(0)
end

class ContactsIOsTests < Test::Unit::TestCase
  def setup
    opts = {
      caps: {
        # get userName, password from Environment variable
        userName: RTK_USERNAME,
        password: RTK_PASSWORD,
        deviceName: 'iPhone 8.*',
        platformName: 'iOS',
        platformVersion: '14.0.1',
        bundleId: 'com.apple.calculator'
      },
      appium_lib: {
        server_url: 'https://gwjp.appkitbox.com/wd/hub',
        wait: 60
      }
    }
    @driver = Appium::Driver.new(opts).start_driver
  end

  def teardown
    @driver.quit
  end

  def test_google_search
    puts @driver.capabilities['snapshotUrl']
    @driver.save_screenshot('capture_01.png')

    el1 = @driver.find_element(:accessibility_id, '1')
    el1.click
    @driver.save_screenshot('capture_02.png')

    is_lang_english = true
    begin
      el2 = @driver.find_element(:accessibility_id, 'multiply')
      el2.click
    rescue StandardError
      is_lang_english = false
      el2 = @driver.find_element(:accessibility_id, '乗算')
      el2.click
    end
    @driver.save_screenshot('capture_03.png')

    el3 = @driver.find_element(:accessibility_id, '3')
    el3.click
    @driver.save_screenshot('capture_04.png')

    el2.click
    @driver.save_screenshot('capture_05.png')

    el3.click
    @driver.save_screenshot('capture_06.png')

    if is_lang_english
      el4 = @driver.find_element(:accessibility_id, 'equals')
      el4.click
      @driver.save_screenshot('capture_07.png')

      el5 = @driver.find_element(:accessibility_id, 'Result')
      value = el5.attribute('value')

      puts 'Text field value=' + value
      assert_equal(value, '9')

      el6 = @driver.find_element(:accessibility_id, 'clear')
      el6.click
      @driver.save_screenshot('capture_08.png')

    else
      el4 = @driver.find_element(:accessibility_id, '計算実行')
      el4.click
      @driver.save_screenshot('capture_07.png')

      el5 = @driver.find_element(:accessibility_id, '結果')
      value = el5.attribute('value')

      puts 'Text field value=' + value
      assert_equal(value, '9')

      el6 = @driver.find_element(:accessibility_id, '消去')
      el6.click
      @driver.save_screenshot('capture_08.png')
    end
  end
end

本テストスクリプトを実行するに当たり、環境変数に Remote TestKit のユーザ名、パスワードを設定します。ターミナルより以下を実行します。

export RTK_USERNAME=xxxx
export RTK_PASSWORD=xxxx

Proxyを通してAppium Cloudに接続したい場合

Proxy対応はテストスクリプトを記述するライブラリによってサポートの有無が異なります。
例えばPythonは標準でProxy対応のサポートがないため、以下のようにMonkeyPatchを利用します。
サンプルコード内のPROXY_URLはご自身の環境に合わせて設定してください。

import os
import sys
import unittest
from time import sleep
from appium import webdriver

# ------------------------------------------------------------------------------------------
# The original selenium.webdriver.remote.remote_connection is not implemented to access
# the endpoint via proxy. So We wrote a monkey patch for the proxy.
import certifi
import urllib3
from selenium.webdriver.remote.remote_connection import RemoteConnection

RemoteConnection.__org__init__ = RemoteConnection.__init__


def patch_init(self, remote_server_addr, keep_alive=False, resolve_ip=True):
    print("\nMonkey patch version: selenium.webdriver.remote.remote_connection")
    RemoteConnection.__org__init__(self, remote_server_addr, keep_alive=keep_alive, resolve_ip=resolve_ip)

    if keep_alive:
        # Define proxy. Default value is squid port.
        PROXY_URL = "http://localhost:3128"
        self._conn = urllib3.ProxyManager(proxy_url=PROXY_URL, timeout=self._timeout)

        # If basic authentication is required, uncomment the following and define it.
        # headers = urllib3.util.make_headers(proxy_basic_auth="userid:password")
        # self._conn = urllib3.ProxyManager(
        #     proxy_url=PROXY_URL, proxy_headers=headers, cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())


RemoteConnection.__init__ = patch_init
# ------------------------------------------------------------------------------------------

# Specify userName, password to Environment variable
RTK_ACCESSTOKEN = os.environ.get('RTK_ACCESSTOKEN')

if not RTK_ACCESSTOKEN:
    print("Environment variable error")
    sys.exit()


class OpenUrlTest(unittest.TestCase):
    def setUp(self):
        caps = {
            'accessToken': RTK_ACCESSTOKEN,
            'deviceName': 'Pixel',
            'platformName': 'Android',
            'browserName': 'Chrome',
            'chromeOptions': {'w3c': False}
        }
        # Specify the endpoint
        self.driver = webdriver.Remote('https://gwjp.appkitbox.com/wd/hub', caps)
        print(f"command_executor={self.driver.command_executor}")
        print(f"proxy={self.driver.command_executor._conn.proxy}")

    def tearDown(self):
        self.driver.quit()

    def test_google_search(self):
        # print(self.driver.capabilities['snapshotUrl'])
        # Open URL
        url = "https://www.google.com/"
        print("Open URL: " + url)
        self.driver.get(url)
        element = self.driver.find_element_by_name('q')
        sleep(5)
        self.driver.save_screenshot('capture_01.png')

        # Input keys
        word = "Remote testKit"
        print("Input Keys: " + word)
        element.send_keys(word)
        element.submit
        sleep(5)
        self.driver.save_screenshot('capture_02.png')

        # Get value
        value = self.driver.find_element_by_name('q').get_attribute('value')
        print("Text field value=" + value)
        self.assertEqual(value, "Remote testKit")


if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(OpenUrlTest)
    unittest.TextTestRunner(verbosity=2).run(suite)

そのほかのサンプルコード

その他の言語やテストスクリプトのサンプルコードは下記のリポジトリを参照ください。

https://github.com/remotetestkit/sample-code

技術仕様および制限事項

2021.10月現在、以下の制限があります。

  • ※Remote TestKit Appium CloudはFlat 3以上のプランをご契約しているユーザの方が、無料でご利用になれる機能です。
  • Appium の仕様から対象となる端末は UiAutomator2 に対応しているAndroid4.4以上またはiOS10.3以上の端末以上に限られます。
  • Selenium client の Version4 をベースにしている、Appium クライアントライブラリは動作しませんので、対応する古いバージョンのクライアントライブラリでの利用をお願いいたします。
  • iOS 端末の場合はレンタルしたiOSのバージョンに最適なAppiumバージョンが選択されcapabilityで指定はできません。作成するクライアント側のテストスクリプトは、対応バージョンを想定した実装をお願い致します。
  • 端末レンタルの自動返却時間の指定が無い場合、10 分間 Appium サーバ経由で同一端末にアクセスがない場合には、自動返却が行われます。また 10分間の間は自動返却されないため、同じ端末に対して複数スクリプトを実行することができます。
  • サーバに対して負荷のかかる処理、メモリを大量に必要とする処理は強制終了する場合があります。
  • 必要に応じて社内の Firewall 等の設定に Remote TestKit からのアクセス許可設定を追加してください。アクセス元 IP アドレスは FAQ に記載があります。
  • プロキシ環境下での動作はサポートしておりません。