読者です 読者をやめる 読者になる 読者になる

Appiumを使ってMobile Safari上でSeleniumのテストケースを実行する

Selenium iOS

SeleniumiPhone DriverはDEPRECATEDらしいので、代替として紹介されている Appium を試してみた。

Appium is an open source test automation framework for use with native and hybrid mobile apps. It drives iOS and Android apps using the WebDriver JSON wire protocol.

http://appium.io/

最初は、AppiumはSelenium WebDriverとしてWebのテストだけに使えるものだと思っていたが、実はiOS/AndroidのネイティブUIの自動操作・自動テストにも使える(むしろそっちがメイン)ということを知って、さらに興味をそそられた。

が、ドキュメントが少なく、地雷があり過ぎる印象があるので、軽くメモを残しておく。環境はMacを想定しています。

インストール

公式のドキュメントにはdmgを使う方法と、npmで入れる方法の二通りが記載されているが、どっちもどっちという感じがする。

dmgを使うとAppium.appというGUIアプリをインストールできるが、これはnodeとnode_modulesをラッピングしたごく単純なアプリ。

$ nvm install v0.10.7
$ npm install -g appium

でnpmでインストールしたのと実質的に同じものであるはずなのだが、npmで入れると後述のMobile SafariのテストがInstrumentsのタイムアウトで動かなかったので、何か手順が不足しているのかもしれない。

dmgの方のソースはこれっぽい。

iOSアプリをテストする

Appium.appを使う場合は、App Pathにアプリのパスを指定する。

アプリのパスはXcodeデバッグビルドした場合には、

~/Library/Application Support/iPhone Simulator/6.0/Applications/049AE569-A182-439A-BA3A-36D02DB4BB09/TestApp.app

のようなパスになると思う。

アプリを指定したら、"Lauch"ボタンでSeleniumのRemoteWebDriverが起動する。

その上で、Appiumのサンプルコードを参考に、次のような感じでテストを書く。

import unittest
import os
from random import randint
from selenium import webdriver


class TestSequenceFunctions(unittest.TestCase):
    def setUp(self):
        # set up appium
        self.driver = webdriver.Remote(
            command_executor='http://127.0.0.1:4723/wd/hub',
            desired_capabilities={
                'browserName': 'iOS',
                'platform': 'Mac',
                'version': '6.0',
            })
        self._values = []

    def _populate(self):
        # populate text fields with two random number
        elems = self.driver.find_elements_by_tag_name('textField')
        for elem in elems:
            rndNum = randint(0, 10)
            elem.send_keys(rndNum)
            self._values.append(rndNum)

    def test_ui_computation(self):
        # populate text fields with values
        self._populate()
        # trigger computation by using the button
        buttons = self.driver.find_elements_by_tag_name("button")
        buttons[0].click()
        # is sum equal ?
        texts = self.driver.find_elements_by_tag_name("staticText")
        self.assertEqual(int(texts[0].text), self._values[0] + self._values[1])

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


if __name__ == '__main__':
    unittest.main()

Mobile Safariでテストを実行する

いくつか地雷がある。

npmでインストールしたappiumに接続してもタイムアウトになる
$ npm install -g appium
$ appium --force-iphone --safari -a 0.0.0.0 -p 4723
info: Appium REST http interface listener started on 0.0.0.0:4723

でAppium.appと同じことをやっていると思え、実際にSelenium RemoteWebDriverで接続しに行っても処理がスタートした雰囲気があるのだが、Instrumentsのタイムアウト?でテストが実行できない。

Appium.appで頑張る方針に切り替えたのであまり深追いしていない。

配布されているAppium.appにはバグがあるかもしれない

現時点での最新版0.5.2のAppium.appでMobile Safariを起動しようとすると、

Could not find mobile safari with version '6.0': Error: EACCES, mkdir '/tmpAppium-MobileSafari.app'

というエラーになる。これはgithubのIssue 635として報告されている。

すでに修正済みなのでdmgも修正されるだろうが、僕は手っ取り早く直したかったので、バンドル内のファイルに、

/Applications/Appium.app/Contents/Resources/node_modules/appium/app/helpers.js

このコミットと同じ修正を加えた。

appにsafariを指定しなくてはいけない

Safariを起動するには、次のどれかの方法でappにsafariを指定しなくてはいけない。

  • desired_capabilitiesに'app': 'safari'を追加する
  • Appium.appを使う場合
    • Appium.appの"Use Mobile Safari"にチェックを入れる
  • npmのappiumを使う場合
    • "--safari" オプションをつけてappiumを起動する
driver.quit() で例外が発生する

地雷を乗り越えてようやくテストを実行できるところまで来ても、tearDownに書いた driver.quit() で例外が発生して、毎回Appiumのプロセスが死んでしまう。これも深追いしていない。

# -*- coding: utf-8 -*-
import unittest
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class MobileSafariTest(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Remote(
            command_executor='http://127.0.0.1:4723/wd/hub',
            desired_capabilities={
                'platform': 'Mac',
                'device': 'iPhone Simulator',
                'version': '6.1',
                'app': 'safari'
                })

    def tearDown(self):
        try:
            self.driver.quit()
        except:
            pass

    def test_title(self):
        self.driver.get('http://m.yahoo.co.jp/')

        input_element = self.driver.find_element_by_name('p')
        input_element.send_keys("Appium")
        input_element.submit()

        WebDriverWait(self.driver, 10).until(EC.title_contains("Appium"))
        self.assertRegexpMatches(self.driver.title, r'^Appium')

if __name__ == '__main__':
    unittest.main()

まとめ

  • iOSアプリ、Androidアプリの自動操作、自動テストにはAppiumは有望そう
  • ドキュメントが少なく、バグなのか、使い方が間違っているのか分からないことが多い点がキツイ