はじめに
以前、Appium を使って WebView で表示しているコンテンツの中身にアクセスする方法をご紹介しました。
-
-
参考AppiumでWebView内部の要素にアクセスする方法
Appiumを使って、WebViewを扱うモバイルアプリの試験を行う場合、表示しているウェブページにアクセスできず苦戦している方も多いのではないでしょうか。そこで、WebViewを扱うAndroidアプリを対象に、どのようにアクセスすればよいのかを解説します。
続きを見る
最後のサンプルコードですが、実行結果に少し違和感を覚えた方もいらっしゃるかと思います。
取得したエレメントの座標値が、実際のスクリーン上の座標値と異なっています。
なぜこのようなことが起きるのかを順番に説明し、解決方法を解説します。
不可解な座標値
エミュレータの画面は 幅1080ピクセル、高さ2220ピクセルの Google Pixel 3a を使用していました。

Appium 公式ページの「ダウンロード」ボタンは、画面上では以下の位置にあるように見えます。
- X座標:幅1080ピクセルの1/4程度、270ピクセルから始まっている
- Y座標:画面中央の1110ピクセルより下に描画されている
一方、getElementRect() で取得した左上の座標は、(x: 106, y: 400)になっています。
command
INFO webdriver: COMMAND getElementRect("e0458b00-7222-46a0-b26e-c65f9ca3b311")
INFO webdriver: [GET] http://127.0.0.1:4723/wd/hub/session/2d2063db-a44c-4b49-8530-da40bcacd962/element/e0458b00-7222-46a0-b26e-c65f9ca3b3
11/rect
INFO webdriver: RESULT { height: 45, width: 180, x: 106.22159576416016, y: 399.8238830566406 }

getElementRect が示す座標の意味
getElementRect で指定したエレメントは、WebView 内に表示されているエレメントを指定しました。
つまり、WebView 内に表示されている Webコンテンツの座標系で計算されています。
スマホの Chrome アプリで「PC版サイト」として表示した場合、文字が小さく表示されます。
実際の画面の解像度で表示されるため、7インチ程度のスマホ画面だと小さくなってしまうのです。

この問題を回避するため、ViewPort という考え方が導入されています。
スマホのブラウザや WebView エレメントは、この ViewPort と呼ばれる領域にレンダリングします。
document.documentElement.clientWidth で確認すると、幅は480ピクセルになっています。
一般的なスマホの場合、幅480、高さ960ピクセル程度の領域に設定されています。
参考
ViewPort についての詳細は、Qiita記事 などをご覧ください。
重要ポイント
WebView 内の要素に対する getElementRect は、この ViewPort 上の座標を返します。

画面上の座標を取得する方法
では、どのように画面上の座標を取得すればよいのでしょうか?
最後にその方法を説明いたします。
Appium の API である touchAction() などは、画面上の座標を指定して利用します。
試験の自動化が目的で Appium を利用する場合が多く、人が操作する「タップ」を模擬する必要があります。

ViewPortとWebViewの座標関係
step
1WebViewの座標を取得
先ず、Nativeアプリ側の WebView の座標を取得します。
WebView 自体は Native アプリのエレメントになるため、画面上の座標値が返ります。
1 2 3 |
let webview = await client.$("android.webkit.WebView"); let webview_rect = await client.getElementRect(webview.elementId); |
step
2ViewPortの座標を取得
次に、ViewPort の座標を取得します。
Appium の execute() を使用して、WebView 内の JavaScript から値を取得します。
1 2 3 4 |
let vp_info = await client.execute(function() { return { vp_w: window.innerWidth, vp_h: window.innerHeight }; }); |
step
3比率から画面上の座標を計算
次に、スクリーン上の座標系と WebView の座標系の比率を計算します。
1 2 3 |
let ratioX = webview_rect.width / vp_info.vp_w; let ratioY = webview_rect.height / vp_info.vp_h; |
拡大率となるこの ratioX と ratioY を使って、画面上の座標を計算します。
step
4WebViewが配置されている位置(オフセット)を加算
最後に、最初に取得した WebView の画面上の位置(offset)を考慮して、
取得したいWebView内のエレメントの座標を算出します。
1 2 3 4 5 6 |
let elem = await client.$("#downloadLink"); let rect = await client.getElementRect(elem.elementId); console.log("x: " + (rect.x * ratioX + webview_rect.x) + ", y: " + rect.y * ratioY + webview_rect.y); |
サンプルプログラム
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
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); // Get rectangle of WebView in screen canvas let webview = await client.$("android.webkit.WebView"); let webview_rect = await client.getElementRect(webview.elementId); // Switch to WebView context let contextNames = await client.getContexts(); for (var i = 0; i < contextNames.length; i++) { if (contextNames[i].indexOf("WEBVIEW") != -1) { await client.switchContext(contextNames[i]); break; } } // Get ViewPort size let vp_info = await client.execute(function() { return { vp_w: window.innerWidth, vp_h: window.innerHeight }; }); // Calculate ratio let ratioX = webview_rect.width / vp_info.vp_w; let ratioY = webview_rect.height / vp_info.vp_h; // Link button let elem = await client.$("#downloadLink"); let rect = await client.getElementRect(elem.elementId); console.log("x: " + (rect.x * ratioX + webview_rect.x) + ", y: " + rect.y * ratioY + webview_rect.y); SyncWait(5000); await client.deleteSession(); } main(); |
