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

sumioの技術メモ

Androidについての記事が多くなると思います。

第1回Android Test Casual Talks でuiautomatorの話をしました #androidtest

12/13(金)に開催された Android Test Casual Talks #1 でLTをしました。以下が発表資料です。

uiautomatorを使うときのひと工夫」というタイトルで、 拙作のUiautomator Unicode Input Helperの紹介をベースに、 日本語環境でuiautomatorを快適に利用する工夫について発表しました。

勉強会では初めての発表だったということもあり、 かなりドキドキしていたのですが、皆さんに興味を持って聴いていただけたようで、 ほっとしています。

LTの補足

発表の中で、 UiObject.clearTextField() の実装を日本語ロケール向けにローカライズを行った、 というお話もさせていただきました。 それについて、帰宅後に少し調べてみたところ、 意外と簡単に国際化対応できそうな事に気付きましたので、 このブログで補足させてください。

このような対応が必要となる原因は、 UiObject.clearTextField()実装で、 以下のように"Select all"がハードコーディングされているからでした。

public void clearTextField() throws UiObjectNotFoundException {
    // (省略)
    getInteractionController().longTapNoSync(rect.left + 20, rect.centerY());
    // check if the edit menu is open
    UiObject selectAll 
        = new UiObject(new UiSelector().descriptionContains("Select all"));
    if(selectAll.waitForExists(50))
        selectAll.click();
    // wait for the selection
    SystemClock.sleep(250);
    // delete it
    getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
}

"Select all"を表すリソースIDは R.string.selectAll なので、通常のAndroidアプリであれば"Select all"context.getString(android.R.string.selectAll) (contextContextインスタンス) に置き換えれば自然に国際化できるのですが、 uiautomatorのテストコードからはContextを取得する手段が無いため、 getString()できないだろうと思い込んでいました。

ところが、その思い込みは間違いで、システムリソース (android.R.から始まるリソース)に限り Resources.getSystem() を使えば、Context無しにリソースにアクセスできることが分かりました*1。 恥ずかしながら、このようなAPIが有ることを今まで知りませんでした。

手持ちの以下の2つの端末で確認してみた感じでは、 uiautomatorから上記を呼び出すと、端末の言語設定に応じて、 適切な文字列を返してくれているようです。

ですので、原理的には、以下のように書き換えれば、 端末の言語設定にかかわらず、clearTextField()が動作するはずです。 AOSPへのバグ報告は別途行いたいと思います。

public void clearTextField() throws UiObjectNotFoundException {
    // (省略)
    String selectAllDesc 
        = Resources.getSystem().getString(android.R.string.selectAll);
    UiObject selectAll 
        = new UiObject(new UiSelector().descriptionContains(selectAllDesc));
    // (省略)
}

各自で回避する場合の多言語対応版は以下のようになります。

public void myClearTextField(UiObject editText)
         throws UiObjectNotFoundException {
    editText.longClickTopLeft();
    // "Select all"のリソース文字列を取得
    String selectAllDesc 
            = Resources.getSystem().getString(android.R.string.selectAll);
    UiObject selectAll 
            = new UiObject(new UiSelector().descriptionContains(selectAllDesc));
    if (selectAll.waitForExists(50))
        selectAll.click();
    SystemClock.sleep(250);
    getUiDevice().pressDelete();
}

さいごに

他の皆さんのLTも、どれも新しい発見があり、とても興味深く聴かせていただきました。 これからも、このような勉強会で引き続き情報交換していきたいです。

今回の勉強会を主催し、LTに誘って下さった@hotchemiさんをはじめとする運営の皆さん、 コメント下さった参加者の皆さん、本当にありがとうございました。

*1:実は、「Contextを取得できない」というのも勘違いの可能性が高いです。Android4.3からはUiAutomatorTestCaseがInstrumentationTestCaseを継承するようになっていました。ということは、getInstrumentation().getContext()が呼び出せるかもしれません。今度調べてみようと思います。