はじめに
Appium を使って試験コードを書いていると、
- 意図したハンドラーにイベントが届かない
- ジェスチャーイベントを上手く発行できない
など、手作業で試験していた時の操作を模擬できなかった経験はないでしょうか?
本記事では、「なぜ上手くイベントが届かないのか」を説明し、その回避方法をご紹介いたします。
イベントハンドラーと配送
Appium でイベントがうまく届かない原因の説明前に、イベントの配送ルールについて説明します。
イベントハンドラーとは?
イベントハンドラーとは、特定のイベントが発生した際に実行するよう定められた処理のことです。
タップ、ダブルタップやスワイプなど、ユーザ操作に応じて動作するため、
それぞれのイベントに「イベントハンドラー」を登録する必要があります。
Android や iOS のスマホアプリでは、画面(Activity)や部品(Widget)単位ごとに
イベントハンドラーを登録できるようになっています。
イベントの配送
Android や iOS が指の操作を識別し、ジェスチャーイベントとしてアプリに配送され、
ハンドラーに登録した処理が実行されます。
ここで意識しないといけないのが、レイヤーです。
実際に表示される画面は様々な部品が重なって構成されており、以下のようなイメージになります。
Android などのアプリでは、アプリのレイヤーの上に通知を行う「システムアプリ」が載っていたり、
電話のための「通話アプリレイヤ」が通常アプリの上に重なっています。
ポイント
指で画面をタップした場合、まず最前面のレイヤーにイベントが配送されます。
ハンドラーが登録されていない場合や、処理条件が満たされなかった場合など、
イベントが消費されなかった・ハンドルされなかった場合に下位層のレイヤーに順次配送されます。
Appiumでイベント配送されない原因
Appium 公式のイベント発行 API を見てみると、エレメントを指定して発行するものが多くあります。
Appium API(JavaScript)
// クリック
$('#SomeId').click();
// ダブルタップ
driver.touchDoubleClick(element.elementId);
// ロングプレス
driver.touchPerform({ action: 'longPress', options: { element: element } });
つまり、画面を構成する単一の部品(Widget/Element)を指定して、イベントを発行することになります。
問題点
エレメントを指定してイベントを発行するため、先の「イベント配送」の仕組みにのることができず、
実際に指で操作した場合と異なる宛先にイベントが届いてしまうため、うまく配送されません。
解決策
Appium の試験コードから、実際のジェスチャー模擬する方法を紹介します。
Appium公式ページ
公式では、以下のページでジェスチャーイベントの生成方法が紹介されています。
紹介されている「mobile: xxxxGesture」のタグを使って、ジェスチャーイベントを発行します。
以下のようなジェスチャーイベントが用意されています。
Gestureの種類
// ロングタップ
mobile: longClickGesture
// ダブルタップ
mobile: doubleClickGesture
// シングルタップ
mobile: clickGesture
// ドラッグ
mobile: dragGesture
// フリング
mobile: flingGesture
// ピンチオープン
mobile: pinchOpenGesture
// ピンチクローズ
mobile: pinchCloseGesture
// スワイプ
mobile: swipeGesture
// スクロール
mobile: scrollGesture
複数の指で操作する複雑なジェスチャーまで対応しています。
これらは全て、エレメントではなく画面上の座標を指定して利用することができます。
参考
エレメントの座標を取得する方法は、「正しい座標を取得する方法」を参照ください。
JavaScript でのサンプル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
const wdio = require("webdriverio"); const assert = require("assert"); const opts = { path: '/wd/hub', port: 4723, capabilities: { platformName: "Android", platformVersion: "11", deviceName: "Android Emulator", app: "D:\\work\\appium\\WebViewApps\\app\\build\\intermediates\\apk\\debug\\app-debug.apk", appPackage: "com.example.webviewapps", appActivity: ".MainActivity", automationName: "UiAutomator2" } }; function SyncWait(waitMsec) { var startMsec = new Date(); while (new Date() - startMsec < waitMsec); } async function main () { const client = await wdio.remote(opts); // Wait for rendering to be done SyncWait(3000); // fire Gesture Event !! await client.execute('mobile: doubleClickGesture', {'x': 100, 'y': 100}); SyncWait(5000); await client.deleteSession(); } main(); |