翻訳記事‎ > ‎

Appium Pro #33: 画像による要素検索 Part 2

2018/09/09 0:06 に Naruhiko Ogasawara が投稿   [ 2018/09/09 0:11 に更新しました ]
[原文] Appium Pro Edition 33: Finding Elements By Image, Part 2
[翻訳] 小笠原徳彦

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

この記事はAppiumにおける特定のイメージと一致する画面上のリージョンを操作する2部構成のシリーズの第2部です。まだ第1部(原文)を読まれていない方は、まずそちらをお読みください!

今回は、-imageロケーター戦略により利用できる、Appiumの「画像による要素検索」機能の高度な利用テクニックをいくつか紹介します。「高度な」テクニックが必要になるのはなぜでしょう? 問題となるのは、画像認識というのはちょっとばかりややこしく、正しく画像一致をするためにはさまざまの方法があるからです。

例えば、デバイスのスクリーンショットよりも大きなスケールの参照画像を使うということもありえます。これにより、(OpenCVライブラリによって実装されている)画像一致のアルゴリズムが爆発してしまいます(スクリーンショットよりも大きな参照画像が与えられたときに、どうやってスクリーンショットの中でその画像を見つけることができるんでしょう?)。あるいは、画像によって見つけられた要素が、それを見つけたあとから、tap コマンドを送ろうとするまでの間に位置が動いたとしたら? あなたが操作したい要素の一部分になっていない画面上の座標をクリックすることになってしまうでしょう!

こういったそれぞれのケースについて、Appiumは助けとなるようなロジック(たとえば、参照画像を縮小したり、タップを実行しようとするときに自動的に画像を再検索して、必要なら位置を更新するようにしたり)を実行します。Appiumはそれ自体で、もっとも堅牢かつ信頼できる画像検索の体験を、あなたがそういった技法に詳しくなかったとしても、提供することができます。問題は、こういった「修正」のそれぞれにより、Appiumは、処理や、自動化エンジンとの対話に、見過ごせないほどの時間を費やしてしまう可能性があるということです。実行時間を考えて、デフォルトでは標準的な要素検索機能だけがオンになっています。ということで、その理由はともあれ、標準機能が十分でないときに、利用可能なオプションについて見ていきましょう。

これらのオプションはAppiumの設定(Settings)APIを通じて利用可能になっています。このAPIは、(Seleniumではなく)Appiumにおいてだけ利用可能なもので、ケイパビリティをテスト中に切り替えたり、リセットしたりできるものです。Desired Capabilitiesと同じような型のパラメータを用いることができますが、Appiumクライアントに任意のときに、望むときにはなんども設定を更新させることができることが違います。Javaクライアントでは、設定APIはHasSettingsインターフェースの背後に隠れています。AndroidDriverまたはIOSDriverオブジェクトを使っているなら、これらはこのインターフェースを実装しており、setSettingメソッドを提供しています。(いいかえれば、AppiumDriverオブジェクトを用いている場合は、最初にHasSettingsにキャストする必要があるということです)。

使い方はものすごくシンプルです:
driver.setSetting(setting, value);

基本的に、設定名(実際には、Setting列挙型の要素)と値を与えます。画像要素検索を操作するためにどうやってこれらを使うかを見ていきましょう。

画像一致のしきい値の変更

OpenCVでは、画像の一致というのは二値の結果として得られるわけではありません。代わりに、どれだけ一致しているかの尺度が、0から1の間の値として存在します。この値自体は恣意的ではありますが、1はピクセル単位で完全に一致していることを、0はまったく一致していないことを示します。経験的に、Appiumは標準の一致のしきい値を 0.4 に設定しています。これはつまり、デフォルトでは、0.4 より小さい一致度のどんな結果も不一致として判定されるということです。

理由はともあれ、状況によっては 0.4 は厳しすぎる(要素を見つけることができない)と思ったり、あるいはゆるすぎる(要素がないのに、あると判定されてしまう)ということがあるでしょう。そのときには IMAGE_MATCH_THRESHOLD 設定を用いて調整できます:
driver.setSetting(Setting.IMAGE_MATCH_THRESHOLD, 0.2);

どんな値を入れたらいいかはどうやったらわかるでしょう? 試行錯誤が必要でしょう。この値は恣意的(かつ非線形)だからです。幸いなことに、それぞれの画像検索操作のたびに上に示した設定APIを用いて異なったしきい値を設定できます。

画像要素タップ戦略の変更

画像要素を見つけたとして、そこに対して element.click() を呼んだとき、Appiumはその要素をどうタップしたらよいかをどうやって知ることができるでしょう? 残念ながら、魔法があるわけではなく、Appiumは単に一致したスクリーンの領域の境界を得て、その領域の中心座標に対してタップを行うだけです。Appiumにはある点をタップするためにいくつかの方法、例えばW3CのActions APIや、もっと古いTouch Actions API(これら二つのAPIにまつわる歴史的な説明についてはAppium Proのこの記事の冒頭をご覧ください)、があります。

Appiumのデフォルトのタップ戦略(W3C Actionsを利用)を使うことができない場合(つまりW3C Actions APIをサポートするように更新されていないドライバーを使っている場合)、IMAGE_ELEMENT_TAP_STRATEGY 設定を用いて、常に古いAPIにフォールバックさせることができます:
driver.setSetting(Setting.IMAGE_ELEMENT_TAP_STRATEGY, "touchActions");
(設定可能な値は "w3cActions” か “touchActions” の二通りです)

スクリーンショットとデバイスサイズの不一致の修正

Appiumの画像一致アルゴリズムは二つのイメージを操作します: ベース画像(その中の要素を探したいと思っているもの)と、参照画像またはテンプレート(探したい要素に紐付いているもの)です。ベース画像については気にする必要はありません: Appiumはデバイスのスクリーンショット、つまりそのときにスクリーン上で起きていることを示すもの、を利用するからです。しかし、ベースイメージ(スクリーンショット)とスクリーンそのもののディメンジョンが異なっていたらどうでしょう? そのときは一致アルゴリズムはスクリーンショットを元に一致した座標を返しますが、それはデバイスのものとは異なってしまいます。もし不運にもデバイスがタップされていたなら、意図とは違うどこかに対してタップされてしまうでしょう。

スクリーンショットとスクリーン自体のディメンジョンが異なるというのはどうして起きるのでしょうか? さまざまな理由がありますが、少なくともプラットフォーム間でピクセルの拡大縮小が扱われる方法によります。たとえばiOSでは、スクリーンは375ピクセル幅(「論理的」幅)だと返してきますが、ありがたいことに得られるスクリーンショットは750ピクセル幅になるのです(「Retina」幅!)。

これらのディメンジョンを正しくすることはとても重要なのでAppiumはデフォルトでそうするようになっています。しかし、このためにAppiumにCPUサイクルを費やせたくないというときは、これをオプトアウトできます:
driver.setSetting(Setting.FIX_IMAGE_FIND_SCREENSHOT_DIMENSIONS, false);

参照画像サイズの修正

上でも述べたように、一致アルゴリズムが動くようにするには、参照画像(テンプレート)はスクリーンショットよりも小さくなくてはなりません。参照画像を作るとき(たとえば要素のスクリーンショットを手動で切り抜いたりするとき)、テンプレートのディメンジョンについて絶対に確信をもてないときもときどきあります。テンプレートをキャプチャーしたときのやりかたによってはAppiumが判定するスクリーンショットのディメンジョンよりも大きいディメンジョンを持つこともありうるのです。

もし、テンプレート画像のサイズによらず、正しく動作するようにしたいのであれば、Appiumにテンプレート画像をリサイズさせることができます:
driver.setSetting(Setting.FIX_IMAGE_FIND_SCREENSHOT_DIMENSIONS, false);

デフォルトでは、テンプレートが誤っている可能性があるということを示す有意義なフィードバックが得られなくなってしまう可能性があるので、Appiumはこのようなリサイズは行わないようになっています(リサイズに余計な計算時間とエネルギーが消費されることはともかくとして)。

画像要素のゾンビ性のチェック

Seleniumの世界から来た人には、「ゾンビ(Stale)要素」というコンセプトをご存知の方もいるでしょう。これは、ある要素が見つかってから、なんらかの操作を行うまでのあいだに、どうにかしてなくなってしまったような要素のことを言います。このような場合、行いたかった動作(タップ、キー入力、など)を完了することはできず、StaleElementException がスローされます。

画像要素についても同じような状況が起こりえます。ある要素(画像一致アルゴリズムにより返された座標の組によって表現される)が、見つかってからタップされるまでの間に消えてしまったときはどうでしょう? Appiumで要素がなくなったあとに座標をタップすることを考え、タップを要求されたときにはもう一度マッチを行うようになっています。マッチがもう一度成功し、前と同じ座標が得られたときに、そこをタップするようになっています。マッチが失敗したとき、あるいは座標が異なっていた場合は、代わりに StaleElementException が返されます。

ちょっとしたパフォーマンス向上のためにこの安全チェックをやめたい場合は、以下のようにオフにできます:
driver.setSetting(Setting.CHECK_IMAGE_ELEMENT_STALENESS, false);

要素の自動的なリフレッシュ

デフォルトでは、Appiumはイメージがゾンビになった場合例外によってそれを知らせます。しかし、要素が消えたわけではなく、単に場所が移動しただけならどうでしょう? その場合はたぶん前の位置の代わりに新しい位置で単にタップすればOKでしょう。タップを要求したときに自動的に新しい一致位置を決定してほしい場合は、UPDATE_IMAGE_ELEMENT_POSITION 設定によって指定できます:
driver.setSetting(Setting.UPDATE_IMAGE_ELEMENT_POSITION, true);

この設定は通常は false になっており、要素の検出からタップまでの間になにかが変わったことを知らせることで、通常の状態を維持するようになっています。

Comments