翻訳記事‎ > ‎

Appium Pro #32: 画像による要素検索 Part 1

2018/09/08 23:55 に Naruhiko Ogasawara が投稿   [ 2018/09/09 0:10 に更新しました ]
[原文] Appium Pro Edition 32: Finding Elements By Image, Part 1
[翻訳] 小笠原徳彦

翻訳者より: Appium ProはAppiumの創始者Jonathan LippsによるAppiumのトップコンサルタント企業Cloud Greyによるオンラインニュースレターです。Webでも読むことができますし登録するとメールで受信することができます。もしAppiumをお使い、あるいは関心をお持ちなら、ぜひ購読されることをおすすめします。

モバイル自動化の残念な現実の一つは、現実的にすべてのUI要素が自動化可能ではないということです! これは、要素がアクセシビリティや自動化のサポートがないカスタムクラスから作られていることから起こりえます。ある要素に適用できる唯一に特定可能なロケーター戦略やセレクターがない(自動化エンジンの視点からはすべての項目がおなじに見えるけれども、ユーザーには違っている動的リストビューを考えてみてください)ことからも起こりえます。あるいは古典的なUIコントロールが一切ない2Dまたは3Dのゲームを想像してみてください--レンダリングエンジンによって描かれたピクセルがあるだけなんです!

歴史的に、Appiumはこういったユースケースをサポートしようとしてきませんでしたし、XCUITestやUIAutomator2を通じた世界のサポートを提供するスタックしか持っていませんでした。もしこれらの技術で要素が見つけられないなら、Appiumは要素を見つけることができなかったのです。

しかし、これらの技術以外に、この問題を解決するのに用いることができるであろう巨大なハンマーが存在しています。ダモクレスの剣のように(ダモクレスのハンマー?)しばらく前からAppium開発者の頭から離れないでいたものです。使うべきか? 使うべきでないか? この巨大なハンマーとは、画像による要素検出です。要素を操作するために、その要素の画像的な特徴を用いるということが、これが巨大なハンマーである理由です。人間のユーザーが普通用いている戦略と同じなので、アプリケーションの種類も問いませんし、アプリ開発者がそれぞれの要素にテスト用のIDをつけるのを覚えているかどうかにもよらないのです。

Appiumチームはついにこの弾丸に噛み付いて、画像による要素検出機能の小さなセットをサポートすることに決めました。Appium 1.9.0から利用可能です。この記事では、これらの機能のもっとも一般的なユースケースを取り上げようと思います。画像による要素検索です。(このシリーズのPart 2では、この機能の変化形であるより高度なメソッドについて見ていきますが、いまのところは基本に限定します)。

画像要素(image element)とはなんでしょうか? 画像要素はクライアントコードでは他の要素とまったく同じように見えますが、新しい -image ロケーター戦略を用いていることだけが違います。典型的なセレクター("foo" のような)の代わりに、この新しいロケーター戦略に用いられる文字列はBase64でエンコードされた画像ファイルなのです。特定の検索アクションに用いられるこのファイルは、Appiumが画面上の領域上から、あなたが探している要素にもっとも近いリージョンを探すためのテンプレートとして用いられます。

つまり、もちろん、アプリの中で見つけたいものに一致するイメージを予め持っていなければならないということを意味します。Appiumはこのイメージを要素を見つけるのにどう使うのでしょう? 裏側を覗いてみれば、用意した参照用/テンプレート画像にもっとも近く一致したスクリーンショット上のエリアを見つけるのに OpenCVライブラリーを用いています。OpenCVはとても洗練されているので、画面上のものとリファレンス画像がいろいろと異なっていてもマッチは成功するでしょう。たとえば回転していたりサイズが異なっている場合でもです。

例の準備はいいでしょうか? では参りましょう。まだまだ新しい機能なのでクライアント、サーバーそれぞれの振る舞いはちょっと面取りされていないところがあります。それでも魔法のように働くんです! まずはアプリ側ですが、アプリに写真のリストを、ランダムな順番で表示する新機能を作りました。ある写真をタップすると、アラートがポップアップして写真の説明が表示されます。こんな感じです:

The photo view feature

画像マッチングがなければ、こういったシナリオを自動化することはできませんでした。どのイメージもUIツリーの中で特定できる情報を持っていませんし、ビューをロードするたびに順番も変わってしまうので、特定のイメージをタップしたいと思ったときに要素のインデックスをハードコードすることもできません。画像による検索(find-by-image)が助けになります! 実はこの戦略は、他の戦略を用いて要素を探すときと、同じように使います:
WebElement el = driver.findElementByImage(base64EncodedImageFile);
el.click();

ひとたび画像要素を得てしまえば、他の要素と同じようにクリックできます。(他のいくつかのコマンドも画像要素に対して動作しますが、当然、ほとんどの操作--例えばsendKeys--は利用できません。実際のUI要素への参照を持っているわけではなく、要素が位置していると信じている画面上のリージョンを持っているに過ぎないからです。)

もちろん、こうやって書くためにはイメージファイルのBase64エンコード版を持っていなければなりません。Java 8なら単にこう書けます:
// refImgFile という File があると仮定
Base64.getEncoder().encodeToString(Files.readAllBytes(refImgFile.toPath()));

これら全部をまとめると、上に書いたシナリオのための成功するテストを書くことができるようになります: アプリのPhotoビューに遷移し、好きな写真をタップし、その結果によって表示されたテキストを評価することでその画像をタップしたか確認します。写真を探すには、次のような参照テンプレートを使います:
The reference image, a Vancouver sunrise photo
該当するコードは次のようになります(今のところ、ボイラープレートは省略しています):
private String getReferenceImageB64() throws URISyntaxException, IOException {
    URL refImgUrl = getClass().getClassLoader().getResource("Edition031_Reference_Image.png");
    File refImgFile = Paths.get(refImgUrl.toURI()).toFile();
    return Base64.getEncoder().encodeToString(Files.readAllBytes(refImgFile.toPath()));
}

public void actualTest(AppiumDriver driver) throws URISyntaxException, IOException {
    WebDriverWait wait = new WebDriverWait(driver, 10);

    try {
        // 写真ビューに移動
        wait.until(ExpectedConditions.presenceOfElementLocated(photos)).click();

        // 参照画像テンプレートを使って正しい画像が出てくるまで待機しクリック

        By sunriseImage = MobileBy.image(getReferenceImageB64());
        wait.until(ExpectedConditions.presenceOfElementLocated(sunriseImage)).click();

        // 正しいイメージをクリックしたかを結果として表示されたアラートを見て確認
        wait.until(ExpectedConditions.alertIsPresent());
        String alertText = driver.switchTo().alert().getText();
        Assert.assertThat(alertText, Matchers.containsString("sunrise"));
    } finally {
        driver.quit();
    }
}

上の例では、プロジェクトのリソースファイルとして格納された参照画像のBase64エンコード版を得るヘルパー関数があることをご覧になれます。もちろん、プロジェクト内にある参照画像を得るのに違う技法を用いても構いません。

次回は、画像要素を検索する助けとして用意されたいくつかの特殊パラメータについて見ていこうと思います。それまでの間、完全なコードサンプルを見ておいてください。このコードを正しく実行するためには、最新のAppium Javaクライアントをソースから取ってきた上で(プロジェクトのbuild.gradleを参照してください)、最低でもAppium 1.9.0を用いてください。
Comments