インターフェースの応用

本章では、より高度な自動化インターフェースについて説明します。これらのインターフェースを使用することで、さまざまな細かい操作を実行できます。本章の内容は多岐にわたります。初めて触れる方は、各セクションをじっくりと読み進めることをお勧めします。

豆知識

自動化コードを作成する際は、リモートデスクトップの右側のターミナルで直接 lamda と入力し、内部で以下のテストコードを実行するか、要素の選択やクリックテストを自身で行うことができます。これにより、作成や検証の速度を上げられます。

要素の取得

基礎知識や前文ですでに多少の理解があるかもしれません。セレクタを使用して関連する要素を見つけ出し、操作を行います。セレクタパラメータの取得方法についてもご覧になったことでしょう。これから説明する内容は、この要素を中心に展開します。下の画像の右側で「同意」という要素の関連情報を見ることができます。

サンプル要素

注目

左側のインターフェースで直接クリックした要素は、実際の要素とは限りません。他の要素とサイズや位置が重なっている可能性があるからです。通常、位置やサイズが重複する複数の要素は右側の情報バーに表示されます。上下にスクロールして、どれが本当に必要な要素かを確認できます。また、左側の選択インターフェースで TAB キーを押すことで、手動ですべての要素を巡回することも可能です。

上記の要素を取得する際、通常は text を使用します。text を利用する条件は、現在の画面に「同意」という text を持つ別の要素が存在しないことです。これが最も簡単な方法です。次に resourceId を利用することもできますが、ここでの resourceId は一意の ID ではなく、リソース ID を表しており、同一インターフェース内に同じリソース ID を持つ要素が多数存在する場合があることに注意してください。packageNamecheckable などの他のフィールドは一般的にあまり使用されませんが、textresourceIddescription などが利用できない場合に試すことができます。以下のいずれかの方法でこの要素を取得できます。

element = d(text="同意")
element = d(text="同意", resourceId="com.tencent.news:id/btm_first_agree")
element = d(resourceId="com.tencent.news:id/btm_first_agree")

要素のクリック

次のインターフェースを呼び出すと、通常の要素クリック操作が実行され、コンテキスト内で「同意」を手動クリックした場合と同様の効果が得られます。

element.click()

クリック位置を指定する必要がある場合は、クリックインターフェースの呼び出し時に corner パラメータを指定します。たとえば、Corner.COR_CENTER は要素の中心点をクリックすることを表し、左上または右下(Corner.COR_BOTTOMRIGHT)をクリックすることもできます。

element.click_exists(corner=Corner.COR_TOPLEFT)

その要素に対して長押し操作を実行します。要素が存在しない場合は例外がスローされます。このインターフェースも corner をサポートしますが、長押し時間の指定はできません。

element.long_click()

要素が存在する場合はクリックし、存在しない場合はこのインターフェースを呼び出しても例外は発生しません。このインターフェースも同様に corner をサポートします。

element.click_exists()
>>> element.click_exists()
True

存在確認

多くの場合、後続の操作に進む前に要素の存在を確認する必要があります。さもなければ、後続のフローで例外が発生したり、誤った画面で誤った操作が行われたりする恐れがあります。そのような場合は、以下のインターフェースを使用して存在判定を行えます。

element.exists()

要素情報

場合によっては、要素の座標や領域情報、要素上のテキストや説明などの文字列情報を取得したいことがあります。以下のインターフェースを使用して要素情報を読み取ります。

element.info()

上記のテスト要素の場合、出力される情報は次のようになります。

>>> info = element.info()
>>> print (info)
bounds { ... }
className: "android.widget.TextView"
clickable: true
enabled: true
focusable: true
packageName: "com.tencent.news"
resourceName: "com.tencent.news:id/btn_first_agree"
text: "\345\220\214\346\204\217"
visibleBounds { ... }

ヒント

上記の表示情報に description などの一部のフィールドが欠けていることに気付かれるかもしれません。このような場合、通常は該当フィールドの値が空または false であることを示します。それでも、対応する属性を介して関連フィールドの値を正常にアクセスできます。

この情報は少し複雑ですが、これは protobuf のデフォルトの表示形式です。対応する属性に直接アクセスすることで、実際の値を出力できます。たとえば、要素の text の値を読み取るには、次のように使用するだけで済みます。

>>> info = element.info()
>>> print (info.text)
同意

もちろん、要素の領域座標に関連する情報も含まれているため、それらにもアクセスできます。たとえば、要素に対応する領域情報を取得したい場合、以下のようにして領域情報を出力したり、変数に保存して後続の操作に利用したりできます。

>>> info = element.info()
>>> print (info.bounds)

出力または返される値は領域情報(Bounds)であり、これは一部のスクリーンショットインターフェースでも使用されるパラメータです。このパラメータをスクリーンショットインターフェースに渡すことで、要素単体のスクリーンショットを実現できます。ただし、より簡単な方法がすでに用意されています。

要素の幅や高さを取得してオフセットを計算する(例:他の要素の相対オフセット)といった用途にも使えます。次のように使用します。

>>> info = element.info()
>>> print (info.bounds.width, info.bounds.height)
484 138

または、要素の中心点や角の点(左上、右下など)を取得します。以下のインターフェースは通常 Point オブジェクトを返し、Point オブジェクトから対応する X および Y のデバイス画面座標を取得することもできます。

>>> info = element.info()
>>> print (info.bounds.center())
x: 792
y: 1908
>>> print (info.bounds.center().x)
792

以下の呼び出しは要素の角の座標を取得します。例では要素の左上隅の座標を取得していますが、これに加えて bottom-righttop-rightbottom-left の合計 4 つの角の座標の取得がサポートされています。

>>> info = element.info()
>>> print (info.bounds.corner("top-left"))
x: 550
y: 1839
>>> print (info.bounds.corner("top-left").x)
550

要素の巡回

セレクタが選択したすべての要素を巡回することもできます。通常、現在のコンテキストではそのセレクタに一致する要素は 1 つだけかもしれません。巡回をテストする場合は、複数の要素に一致するセレクタを選択してください。セレクタに対して直接 for ループなどを使用して巡回できます。

for i in element: print (i)

あるいは、複数の一致要素があるとわかっていて、特定の N 番目の一致要素を取得したい場合は、以下のインターフェースを使用します。

element_3rd = element.get(3)

要素のカウント

通常、このインターフェースを直接使用することはありません。以下の呼び出しにより、現在のセレクタに一致する要素の数を取得できます。

>>> element.count()
1

要素のスクリーンショット

要素レベルでのスクリーンショットをサポートしており、全画面のスクリーンショットから切り抜くことなく、要素の画像を単独で切り取ることができます。

element.screenshot(quality=60)

スクリーンショット後、直接 getvalue() メソッドを使用してバイナリデータを取得したり、PIL Image に直接渡したりできます。

>>> element.screenshot(quality=60).getvalue()
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xe2\x02(ICC_PROFILE\x00\x01\x01\x00\x00\x02\x18\x00\x00\x00\x00\x02\x10\x00\x00mntrRGB XYZ \x00\x00...

または、さらに処理を行わない場合は、スクリーンショットを直接ローカルファイルに保存することも選択できます。

>>> element.screenshot(quality=60).save("image.png")

要素の待機

場合によっては、現在のページが読み込み完了したかどうかを判定する必要があります。通常は、関連する要素が出現したかどうかを判断することでページの読み込み完了を確認します。以下の例では、「同意」要素が出現するのを最大待機時間 10 秒で待ちます。

ヒント

ここでの待機時間はミリ秒単位です。そのため、10 秒の場合は *1000 し、10秒 = 10000ミリ秒 となります。

element.wait_for_exists(10*1000)
>>> element.wait_for_exists(10*1000)
True

また、要素が画面から消えるのを待つ、つまり要素の消失を待つこともサポートしています。

element.wait_until_gone(10*1000)
>>> element.wait_until_gone(10*1000)
False

テキスト入力

テキスト入力は注意が必要な点です。ボタンにテキストを入力することはできません。なぜならそれはボタンだからです。ここでは入力ボックス要素を改めて選んで説明します。この要素の基本情報は以下のとおりです。

テキスト入力

注目

入力ボックス要素を取得する際の注意点:入力ボックス要素を取得する際は、入力メソッドが表示された状態である必要があり、その上で関連する要素を検索してください。また、慎重に探すことをお勧めします。さもないと、取得したものが実際の入力ボックスではない可能性があります。

ヒント

自動化フローにおいて、入力メソッドを表示状態にするには、コード内で親コンテナに表示されている入力ボックスを事前にクリックするだけで十分です。

上記の入力ボックスに対して、次のインターフェースを呼び出すことで「你好世界」という文字列を入力できます。英語や他の Unicode 文字列も入力可能で、以下のようにするだけでボックス内にテキストを入力できます。

>>> element = d(text="搜索感兴趣的内容")
>>> element.set_text("你好世界")
True

その入力ボックスに現在表示されているテキスト内容を取得したい場合は、次のように呼び出します。

注目

ここでセレクタを変更していることに注意してください。初期のセレクタは text 属性を使用していましたが、テキストを入力すると要素の内容が変わり、元のセレクタでは一致しなくなります。そのため、別のセレクタに切り替えました。適切なセレクタを選ぶことは重要ですが、この例はあくまでデモンストレーションであり、こうしておきます。

>>> element = d(className="android.widget.EditText")
>>> element.get_text()
'你好世界'

現在入力されている内容をクリアすることもできます。通常、テキストを入力すると既存のテキストは自動的にクリアされますが、手動でクリアすることも可能です。

ヒント

キーインターフェースを連続して呼び出し、BACKSPACE キーを繰り返し押下しても同様の効果が得られます。

>>> element = d(className="android.widget.EditText")
>>> element.clear_text_field( )
True

注釈

極端なケースでは、一部の場所でこのインターフェースを使用してテキストを正常に入力できない場合があります。現在対応を進めています。

通常のスワイプ

以下のインターフェースを使用して、リストの上下スワイプによるページめくりなど、画面のスワイプ操作を行います。以下の呼び出しは上方向へのスワイプを実行します。step 値が大きいほどスワイプ速度は遅くなり、精度の高いスワイプに適しています。

注目

単純なケースでは、この操作にセレクタパラメータを指定する必要はありません。スワイプできない場合は、scrollable 属性を持つ要素やリストの第1階層コンテナなど、適切な要素にセレクタ条件を設定してください。

d().swipe(direction=Direction.DIR_UP, step=32)
>>> element = d(resourceId="com.tencent.news:id/important_list_content")
>>> element.swipe(direction=Direction.DIR_UP, step=32)
True
方向指示子説明
Direction.DIR_UP上にスワイプ
Direction.DIR_LEFT左にスワイプ
Direction.DIR_DOWN下にスワイプ
Direction.DIR_RIGHT右にスワイプ

高速スワイプ(フリング)

高速スワイプは人が素早くスワイプする動作に似ており、この操作は画面を素早くスワイプするため、素早くブラウジングするような操作のシミュレーションに適しています。以下の例は上から下への画面スワイプであり、例ではセレクタは空ですが、実際の状況に応じてセレクタを記入するかどうかを判断する必要があります。

d().fling_from_top_to_bottom()

下から上への高速スワイプ:

d().fling_from_bottom_to_top()

左から右への高速スワイプ:

d().fling_from_left_to_right()

右から左への高速スワイプ:

d().fling_from_right_to_left()

注目

単純なケースでは、この操作にセレクタパラメータを指定する必要はありません。スワイプできない場合は、scrollable 属性を持つ要素やリストの第1階層コンテナなど、適切な要素にセレクタ条件を設定してください。

>>> element = d(resourceId="com.tencent.news:id/important_list_content")
>>> element.fling_from_bottom_to_top()
True

その他の操作

# このアプリを「ショッピング」フォルダにドラッグして分類する(実際の状況に合わせて変更)
element.drag_to(Selector(text="ショッピング"))

#########
# 兄弟要素または子要素の検索
#########
# 重複要素や特徴のない要素があり、位置特定が難しい場合があります。
# そのようなときは、子要素/兄弟要素を見つけることで検索範囲を絞り込めます。
# 子要素:例えばチャットのログインボックス内の入力ボックスは、ログインボックスの子要素です。
# 兄弟要素:例えばチャット入力ボックス内のユーザー名ボックスとパスワードボックスは(通常)兄弟要素です。
form = d(resourceId="login_form")
form.child(index=1)
# これで login_form 下のインデックスが 1 の子要素が取得されます。
form.child(index=1).sibling()
# あるいは、このようにして login_form と同階層の「パスワードを忘れた場合」ボタンを見つけられます
# (実際には文字列で判断できるので、このように行う必要はありません。ここではデモのためです)。
form.sibling(textContains="パスワードを忘れた場合")
# これら自体は要素なので、任意の要素操作を実行できます。


# その他:端まで達するまで下/左右にスワイプし続ける。
# 必ずしも端までスワイプできるとは限らず、また端まで達したことを検出できるとも限らないため、
# max_swipes パラメータは必須です。
d().fling_from_top_to_bottom_to_end(max_swipes=32)
d().fling_from_bottom_to_top_to_end(max_swipes=32)
d().fling_from_left_to_right_to_end(max_swipes=32)
d().fling_from_right_to_left_to_end(max_swipes=32)

#########
# scroll:より機械的なスワイプ
#########
step = 60
max_swipes = 32
# 上から下へスワイプ(ステップ数 step)
d().scroll_from_top_to_bottom(step)
# 下から上へスワイプ
d().scroll_from_bottom_to_top(step)
# 左から右へスワイプ
d().scroll_from_left_to_right(step)
# 右から左へスワイプ
d().scroll_from_right_to_left(step)

# その他:端まで達するまで下/左右にスワイプし続ける。
# 上記 fling の説明と同様です。
d().scroll_from_top_to_bottom_to_end(max_swipes, step)
d().scroll_from_bottom_to_top_to_end(max_swipes, step)
d().scroll_from_left_to_right_to_end(max_swipes, step)
d().scroll_from_right_to_left_to_end(max_swipes, step)