はじめに
Appium を使って、モバイルアプリの試験自動化を検討されている方も多いと思います。
しかし、非常に基本的な試験コードの書き方しか Web 上に見当たらず、
諦めモードに入っているのではないでしょうか。
WebView で表示しているコンテンツの中身にアクセスできず、私も苦戦していました。
そこで今回は、WebView を扱う Android アプリの試験を対象に、
どのように WebView の中身にアクセスすればよいのかを解説します。

Androidアプリ側の変更(前準備)
Appium から WebView にアクセスするには、アプリ内で使用する WebView に対して追加設定が必要です。

関連記事で紹介したサンプルコードでは、以下のように1行追加する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 |
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Web Viewの初期設定 webView = (WebView) findViewById(R.id.webView); webView.setWebContentsDebuggingEnabled(true); // ★追加★ webView.setWebViewClient(new WebViewClient()); webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl(URL); } |
Appium 公式ドキュメントで、Hybrid Apps 向けに以下の記載があります。
WebView を使用する際は、setWebContentsDebuggingEnabled の設定が必要になります。
Appium comes with built-in hybrid support via Chromedriver, which allow the automation of any Chrome-backed Android web views.
There is an additional step necessary within your app build, unfortunately. As described in the Android remote debugging docs it is necessary to set to true the setWebContentsDebuggingEnabled property on the android.webkit.WebView element.
Once you have set your desired capabilities and started an Appium session, follow the generalized instructions above.
対象のコンテキストを検索・切替
Android アプリで WebView を使用する場合、実行する世界が異なります。
Android アプリそのものは Java/Kotlin の世界(NATIVE_APP)で実行され、
WebView は Chrome などと同じ HTML/JavaScript の世界(WEBVIEW_XXXX)で実行されます。

step
1コンテキスト一覧の取得
先ずは、Appium の WebDriverIO を通して、これら世界のコンテキスト一覧を取得する必要があります。
具体的には、getContexts という API を使用します。
getContexts
let contextNames = await client.getContexts();
step
2対象のコンテキストへ切替
一覧が取得出来たら、試験したい対象のコンテキストへ切り替える必要があります。
具体的には、switchContext という API を使用します。
1 2 3 4 5 6 7 |
for (var i = 0; i < contextNames.length; i++) { if (contextNames[i].indexOf("WEBVIEW") != -1) { await client.switchContext(contextNames[i]); break; } } |

条件は必要に応じて変更可能です。1つ1つの WebView に、特殊な名前が付いています。
WebView 内部の要素にアクセス
コンテキストを切り替えた後は、Appium API ドキュメントに記載されている API が利用できます。
DOM系は「Element」、操作系は「Interactions」、JS Runtime系は「Web」あたりでしょうか。
以下、Appium 公式ページの Download ボタンの座標を取得するサンプルコードです。

Appium公式ページをAndroidエミュレータで表示した画面
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 |
// javascript 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:\\Application\\WebViewApps\\app\\build\\intermediates\\apk\\debug\\app-debug.apk", appPackage: "com.example.webviewapps", appActivity: ".MainActivity", automationName: "UiAutomator2" } }; async function main () { const client = await wdio.remote(opts); 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; } } // Webページのタイトル let title = await client.getTitle(); // Download ボタンの座標 let elem = await client.$("#downloadLink"); // xpath で指定 let rect = await elem.getElementRect(elem.elementId); // elem.elementId でエレメント指定 //await client.logEvent('appium', rect.x.toString()); //await client.logEvent('appium', rect.y.toString()); //await client.logEvent('appium', rect.width.toString()); //await client.logEvent('appium', rect.height.toString()); await client.deleteSession(); } main(); |
UI試験用コード実行結果
それではさっそく実行してみましょう。
Appium サーバー(Appium Desktop)を起動し、コマンドプロンプトから以下を実行します。
command
$ node test_webview.js
コンテキスト
command
INFO webdriver: COMMAND getContexts()
INFO webdriver: [GET] http://127.0.0.1:4723/wd/hub/session/2d2063db-a44c-4b49-8530-da40bcacd962/contexts
INFO webdriver: RESULT [ 'NATIVE_APP', 'WEBVIEW_com.example.webviewapps' ]
INFO webdriver: COMMAND switchContext("WEBVIEW_com.example.webviewapps")
INFO webdriver: [POST] http://127.0.0.1:4723/wd/hub/session/2d2063db-a44c-4b49-8530-da40bcacd962/context
INFO webdriver: DATA { name: 'WEBVIEW_com.example.webviewapps' }
getContexts の実行結果から、「NATIVE_APP, WEBVIEW_com....」のコンテキストが取得できています。
また、switchContext で対象の WebView を指定できていることが分かります。
Webページのタイトル取得
command
INFO webdriver: COMMAND getTitle()
INFO webdriver: [GET] http://127.0.0.1:4723/wd/hub/session/2d2063db-a44c-4b49-8530-da40bcacd962/title
INFO webdriver: RESULT Appium: Mobile App Automation Made Awesome.
getTitle の実行結果から、「Appium: Mobile App ...」のタイトルが取得できています。
これは、ブラウザでアクセスした際にタブへ表示される内容と合致していますね。
Element の座標取得
command
INFO webdriver: COMMAND findElement("css selector", "#downloadLink")
INFO webdriver: [POST] http://127.0.0.1:4723/wd/hub/session/2d2063db-a44c-4b49-8530-da40bcacd962/element
INFO webdriver: DATA { using: 'css selector', value: '#downloadLink' }
INFO webdriver: RESULT {
ELEMENT: 'e0458b00-7222-46a0-b26e-c65f9ca3b311',
'element-6066-11e4-a52e-4f735466cecf': 'e0458b00-7222-46a0-b26e-c65f9ca3b311'
}
先ずは、エレメントの取得です。
xpath で指定した downloadLink という ID のエレメント情報を取得出来ていることが分かります。
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 }
エレメントの elementId を指定して、getElementRect が実行されています。
WebView 上でのポジション(x, y, width, height)が数値として返されています。

トラブルシューティング
No chromedriver found
switchContext を実行した際に、「No chromedriver found.」というようなエラーが出る場合があります。
Proxy 環境下で Appium を実行しているなど、何かしらの理由で ChromeDriver のインストールに失敗しています。
以下の場所に「chromedriver_win32_vxx.x.xxxx.xx.exe」がない場合はこれに該当します。
Appiumインストール場所
C:\Users\whoami\AppData\Local\Programs\Appium Server GUI\resources\
app\node_modules\appium\node_modules\appium-chromedriver\chromedriver\win
WebDriver for Chrome のページから、Android エミュレータ上で実行している Chrome バージョンに合った
ChromeDriver をダウンロードし、上記場所に配置すれば解消します。
