# 介面進階操作

本章節為您介紹更深層的自動化介面，您可以透過這些介面來完成各種細緻的操作，本章的內容較多，如果您是第一次接觸，我們建議您耐心地看完每個節點。

```{tip}
編寫自動化程式碼時，您可以直接在遠端桌面的右側終端機輸入指令 `lamda`，並在其中執行以下測試程式碼，或自行進行元素選取測試、點擊測試等，這樣可以加快您編寫及驗證的速度。
```

## 取得元素

您在基礎知識或前文中可能已經對它有一些了解了，您需要透過選擇器來找到相關的元素才能進行操作。您應該也看到了在什麼地方可以取得選擇器參數。現在我們下面的一些介紹將圍繞著這個元素進行。您可以在這張圖片的右側看到「同意」這個元素的相關資訊。

![樣本元素](/assets/images/auto-eyeselect.png)

```{attention}
您在左側介面直接點擊的元素可能並不是實際元素，因為它可能與其他元素的大小及位置重疊。通常情況下，多個位置大小重疊的元素我們也會顯示在右側資訊欄內，您可以上下滾動查看哪個才是真正需要的。您也可以透過在左側選擇介面按下 TAB 鍵的方式來手動遍歷所有元素。
```

對於以上元素，我們取得它一般是透過 `text`，使用 `text` 的條件是目前的介面沒有另一個元素的 `text` 也是「同意」，這是最簡便的方法。其次您也可以選用 `resourceId`，不過您需要注意這裡的 `resourceId` 代表的並不是唯一 ID，它代表的是資源 ID，而一個介面內可能包含很多個相同資源 ID 的元素。其他的一些如 `packageName`、`checkable` 等一般不常用，但如果在 `text`、`resourceId`、`description` 等都沒有的情況下，可以嘗試使用這些欄位。我們可以透過如下幾種方式取得這個元素。

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

## 元素點擊

呼叫如下介面進行一次普通的元素點擊操作，上下文中將會為您實現手動點擊「同意」的效果。

```python
element.click()
```

如果您需要指定點擊元素的位置，您可以指定 `corner`，`Corner.COR_CENTER` 代表點擊該元素的中心點，您也可以點擊它的左上角或右下角（`COR_BOTTOMRIGHT`）。

```python
element.click_exists(corner=Corner.COR_TOPLEFT)
```

在該元素上執行長按操作，不存在則拋出例外。該介面也支援 `corner`，無法指定長按時間。

```python
element.long_click()
```

元素存在則點擊，如果元素不存在，呼叫此介面不會引發例外。該介面同樣支援 `corner`。

```python
element.click_exists()
```

```python
>>> element.click_exists()
True
```

## 是否存在

很多情況下，在進一步操作時需要對元素的存在性進行檢查，否則後續流程可能會出現例外，或在錯誤的介面操作錯誤的事情。您可能需要在某些情況下使用如下介面進行存在性判斷。

```python
element.exists()
```

## 元素資訊

一些情況下，您可能會想要取得元素的部分資訊，例如您可能會想要得到元素對應的座標、區域，或元素上包含的文字或描述等字串資訊。您可以透過如下介面來讀取元素資訊。

```python
element.info()
```

在我們上述的測試元素中，這個測試元素輸出的資訊為：

```python
>>> 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 { ... }
```

```{hint}
您可能發現像上面這種列印資訊中少了一些欄位，例如 `description` 等，這種情況通常代表這個欄位是空值或為 `false`，您仍然可以透過屬性正常存取相關的欄位來取得其值。
```

可以看到這個資訊相對來說還是有點複雜的，這是 protobuf 預設的列印格式，您可以直接透過存取對應的屬性來列印出其實際的值。例如想要讀取元素 `text` 的值，您可以直接像下面這樣使用。

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

當然，裡面還有一些元素區域座標相關的資訊，您也可以存取它們。例如您現在想要取得該元素對應的區域資訊，您可以像下面這樣使用來列印區域資訊，您也可以將其取得為後續操作的變數。

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

輸出或返回的值是一個區域資訊（Bound），您後面會發現這是某個截圖介面也會用到的參數。是的，您可以將這個參數提交給截圖介面來實現為這個元素單獨截圖，不過我們早已為您封裝好了。

您可能也想要取得元素的寬度和高度用來計算某些偏移，例如計算其他相對元素的偏移，您可以：
```python
>>> info = element.info()
>>> print (info.bounds.width, info.bounds.height)
484 138
```

或者，取得該元素的中心點，或角點如左上角、右下角這種。當然，以下介面通常返回的都是 Point 資訊，您也可以從 Point 物件中取得對應的 X、Y 軸裝置螢幕座標。
```python
>>> info = element.info()
>>> print (info.bounds.center())
x: 792
y: 1908
>>> print (info.bounds.center().x)
792
```

以下呼叫用來取得元素的角點座標，範例取得了元素左上角的座標，除此之外還支援取得如 `bottom-right`、`top-right`、`bottom-left` 等共四個角點的座標。

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

## 元素遍歷

您也可以遍歷選擇器選擇到的所有元素。正常情況下，上下文中這個元素可能只有一個，所以如果您測試，請選擇其他選擇器進行測試，直接在選擇器上使用 for 迴圈或其他方式進行遍歷即可。

```python
for i in element: print (i)
```

或者如果您知道它存在多個匹配元素，想取得指定的第 N 個匹配元素，可以使用如下介面取得。

```python
element_3rd = element.get(3)
```

## 元素計數

通常情況下您不會直接用到此介面，以下呼叫可以取得您目前選擇器匹配到的元素個數。

```python
>>> element.count()
1
```

## 元素截圖

我們支援您進行元素級別的截圖，可以單獨截下元素的圖像而無需全螢幕截圖再行裁剪。

```python
element.screenshot(quality=60)
```

您可以在截圖後直接使用 `getvalue` 來取得截圖的二進位資料，或直接將其傳入 PIL Image。

```python
>>> 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...
```
或者如果您無需繼續處理，也可以選擇直接將截圖儲存到本機檔案中。
```python
>>> element.screenshot(quality=60).save("image.png")
```

## 等待元素

一些情況下，您可能需要判斷目前頁面是否載入完畢。通常情況下，您可以透過判斷相關元素是否已經顯示來判斷目前頁面是否已經載入完成。如下範例會等待「同意」元素出現，最長等待時間為 10 秒。

```{hint}
這裡的等待時長是毫秒，所以 10 秒鐘需要 *1000，10秒 = 10000毫秒。
```

```python
element.wait_for_exists(10*1000)
```

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

當然，我們不只支援您等待元素出現，也支援等待元素消失，也就是說一直到元素從介面消失。

```python
element.wait_until_gone(10*1000)
```

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

## 文字輸入

文字輸入相對來說是一個較需要注意的地方，我們不可能往一個同意按鈕上輸入文字，因為那是按鈕。現在我們重新拿一個輸入框元素來介紹，這個元素的基本資訊如下所示。

![文字輸入](/assets/images/input-text.png)

```{attention}
取得輸入框元素可能會有一些需要注意的地方，請您務必注意。在取得輸入框元素時，您的輸入法必須要處於**彈出狀態**再尋找相關元素，並且建議仔細尋找，否則您取得的可能並不是真正的輸入框。
```

```{hint}
在自動化程式碼的流程中，要讓輸入法處於彈出狀態，僅需要您編寫程式碼先行點擊父級顯示的輸入框即可。
```

對於上面這個輸入框，我們可以呼叫如下介面來向輸入框輸入「你好世界」這個字串，當然也支援您輸入英文或其他 unicode 字串，您只需要像下面這樣使用即可在框內輸入文字。

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

或者，您突發奇想，又想要取得這個輸入框目前顯示的文字內容，那麼您可以這樣呼叫。

```{attention}
您可以看到這裡我們換了一個選擇器。我們一開始的選擇器是以 `text` 進行的，但我們輸入 `text` 後，內容變了，所以該元素不再存在，因此換用另一個選擇器。使用合適的選擇器是很重要的，但是我們已經寫了就將錯就錯吧。
```

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

又或者是清空目前輸入的內容。通常輸入文字會自動清空之前的文字，但是您也可以手動清空。

```{hint}
其實連續使用按鍵介面循環按下 BACKSPACE 鍵也可以實現類似的效果。
```

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

```{note}
極端情況下，有些地方無法正常使用此介面輸入文字，我們正在支援。
```

## 普通滑動

使用如下介面來進行介面的滑動操作，例如列表的上下滑動翻頁。以下呼叫實現向上滑動，`step` 自行調整，越大滑動速度會越慢，比較適合精度要求較高的滑動。

```{attention}
偷懶的情況下，這個操作不需要提供選擇器參數，但是如果遇到無法滑動的情況，請自行設定選擇器條件為適合的元素，例如具有 `scrollable` 的元素或列表的第一級容器。
```

```python
d().swipe(direction=Direction.DIR_UP, step=32)
```

```python
>>> 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 | 向右滑動 |

## 快速滑動

快速滑動類似人快速滑動螢幕的行為，此操作會快速地滑動螢幕，適合模擬快速瀏覽類的操作。以下範例從上往下滑動螢幕，範例中選擇器為空，您仍然需要根據實際情況選擇是否填寫選擇器。

```python
d().fling_from_top_to_bottom()
```

從下往上的快速滑動

```python
d().fling_from_bottom_to_top()
```
從左往右的快速滑動
```python
d().fling_from_left_to_right()
```
從右往左的快速滑動

```python
d().fling_from_right_to_left()
```

```{attention}
偷懶的情況下，這個操作不需要提供選擇器參數，但是如果遇到無法滑動的情況，請自行設定選擇器條件為適合的元素，例如具有 `scrollable` 的元素或列表的第一級容器。
```

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

正在更新...

## 其他操作

```python
# 將此 APP 拖動歸類到「購物」資料夾（依據實際情況修改）
element.drag_to(Selector(text="購物"))

#########
# 尋找同級或子級元素
#########
# 有時候會有一些重複元素或無明顯特徵的元素，很難去定位
# 這時你可以透過尋找子級/同級元素的方法來縮小尋找範圍
# 子級元素，舉例為：一個聊天登入框，裡面的輸入框即為登入框的子級元素
# 同級元素，舉例為：聊天輸入框裡面的使用者名稱和密碼框為同級元素（正常情況下）
form = d(resourceId="login_form")
form.child(index=1)
# 這將取得 login_form 下 index 為 1 的元素
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)
```