# Расширенные операции с интерфейсом

В этой главе представлены более глубокие API для автоматизации, с помощью которых вы можете выполнять различные детализированные операции. Содержание этой главы довольно обширно, и если вы сталкиваетесь с этим впервые, мы рекомендуем вам терпеливо прочитать каждый раздел.

```{tip}
При написании кода автоматизации вы можете напрямую вводить команду `lamda` в терминале справа на удаленном рабочем столе. Там вы можете выполнять тестовый код, представленный ниже, или самостоятельно проводить тесты по выбору элементов, кликам и т.д., что ускорит процесс написания и проверки вашего кода.
```

## Получение элемента

Возможно, вы уже немного знакомы с этим из базовых знаний или предыдущих разделов. Вам необходимо найти соответствующие элементы с помощью селекторов, чтобы выполнять с ними операции. Вы также, вероятно, видели, где можно получить параметры селектора. Далее мы будем подробно рассматривать этот элемент. На изображении справа вы можете видеть информацию об элементе "同意" (Согласиться).

![Пример элемента](/assets/images/auto-eyeselect.png)

```{attention}
Элемент, на который вы нажимаете непосредственно в левой части интерфейса, может не быть фактическим элементом, так как он может перекрываться с другими элементами по размеру и положению. Обычно, если несколько элементов перекрываются по положению и размеру, мы отображаем их в информационной панели справа. Вы можете прокручивать вверх и вниз, чтобы найти нужный элемент. Вы также можете вручную перебирать все элементы, нажимая клавишу TAB в интерфейсе выбора слева.
```

Для получения вышеуказанного элемента мы обычно используем `text`. Условием для использования `text` является отсутствие на текущем экране другого элемента с таким же текстом "同意". Это самый простой способ. Кроме того, вы можете использовать `resourceId`, но следует помнить, что `resourceId` здесь не является уникальным идентификатором, а представляет собой идентификатор ресурса, и на одном экране может быть много элементов с одинаковым `resourceId`. Другие поля, такие как `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")
```

## Клик по элементу

Вызовите следующий API для выполнения обычного клика по элементу. В данном контексте это будет имитировать ручное нажатие на кнопку "Согласиться".

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

Если вам нужно указать конкретное место для клика на элементе, вы можете указать `corner`. `Corner.COR_CENTER` означает клик по центру элемента. Вы также можете кликнуть по его верхнему левому или правому нижнему углу (`COR_BOTTOMRIGHT`).

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

Выполняет длительное нажатие на элементе. Если элемент не существует, выбрасывается исключение. Этот API также поддерживает `corner`, но не позволяет указать продолжительность длительного нажатия.

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

Кликает по элементу, если он существует. Если элемент не существует, вызов этого API не вызовет исключения. Этот API также поддерживает `corner`.

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

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

## Проверка существования

Во многих случаях перед выполнением дальнейших действий необходимо проверить существование элемента, иначе последующие шаги могут вызвать ошибку или привести к неверным действиям на неправильном экране. В некоторых ситуациях вам может понадобиться использовать следующий API для проверки существования.

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

## Информация об элементе

В некоторых случаях вам может понадобиться получить частичную информацию об элементе, например, его координаты, область, текст или описание. Вы можете использовать следующий API для чтения информации об элементе.

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

Для нашего тестового элемента выше, этот API выведет следующую информацию:

```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). Позже вы обнаружите, что это тот же параметр, который используется в некоторых API для создания скриншотов. Да, вы можете передать этот параметр в API скриншота, чтобы сделать снимок только этого элемента, но мы уже сделали для вас удобную обертку.

Возможно, вы также захотите получить ширину и высоту элемента для расчета некоторых смещений, например, для расчета смещения относительно других элементов. Вы можете сделать это так:
```python
>>> info = element.info()
>>> print (info.bounds.width, info.bounds.height)
484 138
```

Или получить центральную точку элемента, или угловые точки, такие как верхний левый и правый нижний углы. Конечно, следующие API обычно возвращают информацию о точке (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-й из них, вы можете использовать следующий API.

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

## Подсчет элементов

Обычно вы не будете использовать этот API напрямую. Следующий вызов позволяет получить количество элементов, соответствующих вашему текущему селектору.

```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}
В процессе автоматизации, чтобы активировать метод ввода, вам просто нужно написать код, который сначала кликнет по родительскому элементу, отображающему поле ввода.
```

Для вышеуказанного поля ввода мы можем вызвать следующий API, чтобы ввести строку "你好世界" (Привет, мир). Конечно, поддерживается ввод английских или других unicode-строк. Вам просто нужно использовать его, как показано ниже, чтобы ввести текст в поле.

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

Или, если вам вдруг захотелось получить текущий текст, отображаемый в этом поле ввода, вы можете вызвать его так.

```{attention}
Вы можете заметить, что здесь мы изменили селектор. Изначально мы использовали селектор по `text`, но после ввода текста его содержимое изменилось, и элемент перестал существовать, поэтому мы переключились на другой селектор. Использование подходящего селектора очень важно, но раз уж мы так написали, оставим как есть.
```

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

Или же очистить текущее введенное содержимое. Обычно ввод текста автоматически очищает предыдущий текст, но вы также можете сделать это вручную.

```{hint}
На самом деле, аналогичного эффекта можно достичь, циклически нажимая клавишу BACKSPACE с помощью API нажатия клавиш.
```

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

```{note}
В крайних случаях ввод текста с помощью этого API может не работать в некоторых местах. Мы работаем над поддержкой таких случаев.
```

## Обычный свайп

Используйте следующий API для выполнения свайпа по экрану, например, для прокрутки списков вверх и вниз. Следующий вызов выполняет свайп вверх. Параметр `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 | Свайп вправо |

## Быстрый свайп (Fling)

Быстрый свайп имитирует быстрое движение пальцем по экрану. Эта операция быстро прокручивает экран и подходит для симуляции действий, таких как быстрый просмотр. В следующем примере экран прокручивается сверху вниз. Селектор в примере пуст, но вам все равно нужно решить, заполнять ли его в зависимости от конкретной ситуации.

```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
# Перетащить это приложение в папку "Покупки" (измените в соответствии с вашей ситуацией)
element.drag_to(Selector(text="购物"))

#########
# Поиск дочерних и соседних элементов
#########
# Иногда встречаются повторяющиеся элементы или элементы без явных признаков, которые трудно найти
# В этом случае можно сузить область поиска, используя методы поиска дочерних/соседних элементов
# Дочерний элемент, например: в окне входа в чат поля ввода являются дочерними элементами этого окна
# Соседние элементы, например: в окне входа в чат поля для имени пользователя и пароля являются соседними элементами (в обычной ситуации)
form = d(resourceId="login_form")
form.child(index=1)
# Это получит дочерний элемент login_form с индексом 1
form.child(index=1).sibling()
# Таким же образом можно найти соседнюю кнопку "Восстановить пароль"
# (На самом деле, ее можно найти по тексту, так что это необязательно, здесь просто для демонстрации)
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)
```