Androidテストにおいて、画面をタップする必要が生じる場面があります。
Appiumの旧バージョンでは、以下のコードを使用してAndroid画面をタップするとエラーは発生しませんでした。 Map tap = new HashMap(); tap.put("tapCount", new Double(2)); tap.put("touchCount", new Double(1)); tap.put("duration", new Double(0.5)); tap.put("x", new Double(300)); tap.put("y", new Double(300)); driver.executeScript("mobile: tap", tap); driver.executeAsyncScript(script, args);
しかし、Appiumを新バージョンにアップグレード後、このコードを使用すると以下のエラーが発生します: org.openqa.selenium.WebDriverException: Method has not yet been implemented (WARNING: The server did not provide any stacktrace information)
そのため、このメソッドは使用できなくなりました。driver.tapを使用して画面をタップすると、 driver.tap(1, 100, 100, 200); 高い確率でエラーが発生し、「An unknown server-side error occurred」というエラーメッセージが表示されます。このようなエラーは、自動化の長期運用に支障をきたします。
資料を調査したところ、Appiumの公式資料によると、以下の方法で画面をタップするとエラーが発生せず安定しています: TouchAction action = new TouchAction(driver); action.tap(100, 100).perform();
これにより、画面タップ時のエラー問題が解決します。
https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/touch-actions.md
モバイルジェスチャーの自動化
Selenium WebDriver仕様では、特定の種類のモバイル操作はサポートされていますが、そのパラメータは常に基盤となるデバイス自動化(iOSの場合はUIAutomationなど)が提供する機能に容易にマッピングできるとは限りません。この点を考慮し、Appiumは最新の仕様(https://dvcs.w3.org/hg/webdriver/raw-file/tip/webdriver-spec.html#multiactions-1)で定義されている新しいTouchAction / MultiAction APIを実装しています。これは、元のJSON WireプロトコルにおけるTouchAction APIの初期バージョンとは異なります。
これらのAPIを使用すると、複数のアクチュエーターを組み合わせて任意のジェスチャーを構築できます。このAPIの使用例については、お使いの言語のAppiumクライアントドキュメントを参照してください。
TouchAction / MultiAction APIの概要
TouchAction
TouchActionオブジェクトは、イベントのチェーンを保持します。
すべてのAppiumクライアントライブラリでは、タッチオブジェクトが作成され、一連のイベントが与えられます。
仕様で利用可能なイベントは以下の通りです:
- press
- release
- moveTo
- tap
- wait
- longPress
- cancel
- perform
以下は疑似コードでのアクション作成例です:
TouchAction().press(el0).moveTo(el1).release()
上記は、ユーザーが要素を押し下げ、指を別の位置にスライドし、画面から指を離す動作をシミュレートします。
Appiumはイベントを順番に実行します。ジェスチャーのタイミングを制御するためにwaitイベントを追加できます。
moveToの座標は、現在の位置に対して相対的です。例えば、100,100から200,200までドラッグするには、以下のように実現できます:
.press(100,100) // 100,100から開始
.moveTo(100,100) // XとYをそれぞれ100増やし、200,200で終了
Appiumクライアントライブラリでは、これを実装する方法が異なります。例えば、moveToイベントに座標または要素を渡すことができます。座標と要素の両方を渡すと、座標は現在の位置に対してではなく、要素の位置に対して相対的と見なされます。
performイベントを呼び出すと、一連のイベント全体がAppiumに送信され、デバイス上でタッチジェスチャーが実行されます。
Appiumクライアントでは、TouchActionオブジェクトのperformイベントを呼び出すのではなく、ドライバーオブジェクトを介して直接TouchActionを実行することもできます。
疑似コードでは、以下の2つは同等です:
TouchAction().tap(el).perform()
driver.perform(TouchAction().tap(el))
MultiTouch
MultiTouchオブジェクトは、TouchActionのコレクションです。
MultiTouchジェスチャーには、addとperformの2つのメソッドしかありません。
addは、このMultiTouchに別のTouchActionを追加するために使用されます。
performが呼び出されると、MultiTouchに追加されたすべてのTouchActionがAppiumに送信され、同時に発生したかのように実行されます。Appiumは、まずすべてのTouchActionの最初のイベントを一緒に実行し、次に2番目のイベントを、というように順番に実行します。
2本の指でタップする疑似コードの例:
action0 = TouchAction().tap(el)
action1 = TouchAction().tap(el)
MultiAction().add(action0).add(action1).perform()
バグと回避策
iOS 7.0 - 8.xシミュレータには、ScrollViews、CollectionViews、TableViewsがUIAutomation(AppiumがiOSの裏側で使用する)によって開始されたジェスチャーを認識しないという不幸なバグが存在します。この問題を回避するために、これらのビューをスクロールするのに役立つ別の関数scrollへのアクセスを提供しています。
スクロール
この特別な機能へのアクセスを可能にするために、ドライバーのexecuteまたはexecuteScriptメソッドをオーバーライドし、コマンドの先頭にmobile:を接頭辞として付けます。以下に例を示します:
スクロールするには、スクロールする方向をパラメータとして渡します。
// JavaScript
driver.execute('mobile: scroll', {direction: 'down'})
// Java
JavascriptExecutor js = (JavascriptExecutor) driver;
HashMap<String, String> scrollObject = new HashMap<String, String>();
scrollObject.put("direction", "down");
js.executeScript("mobile: scroll", scrollObject);
# Ruby
execute_script 'mobile: scroll', direction: 'down'
# Python
driver.execute_script("mobile: scroll", {"direction": "down"})
// C#
Dictionary<string, string> scrollObject = new Dictionary<string, string>();
scrollObject.Add("direction", "down");
((IJavaScriptExecutor)driver).ExecuteScript("mobile: scroll", scrollObject));
$params = array(array('direction' => 'down'));
$driver->executeScript("mobile: scroll", $params);
方向と要素を使用してスクロールする例。
// JavaScript
driver.execute('mobile: scroll', {direction: 'down', element: element.value.ELEMENT});
// Java
JavascriptExecutor js = (JavascriptExecutor) driver;
HashMap<String, String> scrollObject = new HashMap<String, String>();
scrollObject.put("direction", "down");
scrollObject.put("element", ((RemoteWebElement) element).getId());
js.executeScript("mobile: scroll", scrollObject);
# Ruby
execute_script 'mobile: scroll', direction: 'down', element: element.ref
# Python
driver.execute_script("mobile: scroll", {"direction": "down", element: element.getAttribute("id")})
// C#
Dictionary<string, string> scrollObject = new Dictionary<string, string>();
scrollObject.Add("direction", "down");
scrollObject.Add("element", <element_id>);
((IJavaScriptExecutor)driver).ExecuteScript("mobile: scroll", scrollObject));
$params = array(array('direction' => 'down', 'element' => element.GetAttribute("id")));
$driver->executeScript("mobile: scroll", $params);
スライダーの自動化
iOS
// Java
// スライダーの値は0から1の間の数値の文字列表現です
// 例えば、"0.1"は10%、"1.0"は100%を意味します
WebElement slider = driver.findElement(By.xpath("//window[1]/slider[1]"));
slider.sendKeys("0.1");
Android
Androidでスライダーを操作する最良の方法は、TouchActionsを使用することです。