UIの高度な操作

この章では、より高度な自動化インターフェースについて紹介します。これらのインターフェースを通じて、さまざまな詳細な操作を完了させることができます。この章は内容が多いため、初めての方は、各項目をじっくりと読むことをお勧めします。

豆知識

自動化コードを記述する際、リモートデスクトップの右側にあるターミナルで直接 `lamda` コマンドを入力し、その中で以下のテストコードを実行したり、自分で要素の選択テストやクリックテストを行ったりすることができます。これにより、コーディングと検証の速度を向上させることができます。

要素の取得

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

サンプル要素

注目

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

上記の要素を取得するには、一般的に text を使用します。text を使用する条件は、現在のUIに text が「同意」である他の要素が存在しないことです。これが最も簡単な方法です。次に、resourceId を使用することもできますが、ここでの resourceId は一意のIDではなく、リソースIDを表すことに注意してください。一つのUI内には、同じリソース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 は要素の中心点をクリックすることを意味し、左上隅(COR_TOPLEFT)や右下隅(COR_BOTTOMRIGHT)をクリックすることもできます。

element.click_exists(corner=Corner.COR_TOPLEFT)

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

element.long_click()

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

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

存在確認

多くの場合、次の操作に進む前に要素の存在を確認する必要があります。そうしないと、後続のプロセスで例外が発生したり、間違ったUIで誤った操作を行ったりする可能性があります。状況によっては、以下のインターフェースを使用して存在確認を行う必要があります。

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)

出力または返される値は領域情報(Bound)です。後で、これが一部のスクリーンショットインターフェースでも使用されるパラメータであることに気づくでしょう。そうです、このパラメータをスクリーンショットインターフェースに渡すことで、この要素だけのスクリーンショットを撮ることができますが、私たちはすでにその機能をラップして提供しています。

また、他の相対的な要素のオフセットを計算するなど、要素の幅と高さを取得したい場合もあるでしょう。その場合は、次のようにします。

>>> 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

もちろん、要素の出現を待つだけでなく、要素が消えるのを待つこともサポートしています。つまり、要素がUIから消えるまで待ち続けます。

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

注釈

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

通常のスワイプ

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

注目

手抜きをする場合、この操作にセレクターパラメータを提供する必要はありませんが、スワイプできない状況に遭遇した場合は、`scrollable` を持つ要素やリストの第一階層のコンテナなど、適切な要素にセレクター条件を自分で設定してください。
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右へスワイプ

高速スワイプ (Fling)

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

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` を持つ要素やリストの第一階層のコンテナなど、適切な要素にセレクター条件を自分で設定してください。
>>> 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 の下にある index が 0 の要素を取得します
form.child(index=1).sibling()
# このようにして、login_form と同じ階層にある「パスワードを忘れた場合」ボタンを見つけることもできます
# (実際には文字列で判断できるので、このようにする必要はありません。ここではデモとして示しています)
form.sibling(textContains="パスワードを忘れた場合")
# これら自体が element であり、element に対してあらゆる操作を行うことができます


# その他、下/左/右/上へ、最後までスワイプし続ける
# 必ずしも最後までスワイプできる、または最後までスワイプしたことを検出できるとは限らないため
# 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)
# 下から上へ step ステップ分スワイプ
d().scroll_from_bottom_to_top(step)
# 左から右へ step ステップ分スワイプ
d().scroll_from_left_to_right(step)
# 右から左へ 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)