1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/supernatural-fork-uiautomator2

Клонировать/Скачать
XPATH.md 17 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 04.06.2025 13:30 653646a

Расширение uiautomator2 для xpath

Перед использованием этого плагина рекомендуется ознакомиться с основами XPath. В интернете можно найти множество полезных ресурсов. Вот некоторые из них:

Код не был полностью протестирован и может содержать ошибки. Любые замечания и отзывы приветствуются.

Принцип работы

  1. Используя интерфейс dump_hierarchy библиотеки uiautomator2, получаем текущий UI-интерфейс (богатый XML).
  2. Затем с помощью библиотеки lxml парсим XML и находим соответствующие xpath, после чего выполняем команду click.

В настоящее время обнаружено, что lxml поддерживает только XPath 1.0. Если кто-то знает, как поддержать XPath 2.0, пожалуйста, дайте знать.

Принцип работы мониторинга уведомлений

Используя hierarchy, можно получить информацию обо всех элементах интерфейса (включая уведомления и кнопки для нажатия). Предположим, что есть кнопки "Пропустить" и "Знаю". Кнопка, которую нужно нажать, называется "Плеер".1. Получаем XML текущего интерфейса (через функцию dump_hierarchy). 2. Проверяем наличие кнопок "Пропустить" и "Знаю". Если они есть, нажимаем их и возвращаемся к шагу 1. 3. Проверяем наличие кнопки "Плеер". Если она есть, нажимаем её и завершаем. Если кнопка не найдена, возвращаемся к шагу 1 и продолжаем до тех пор, пока не превысит количество попыток.## Метод установки

pip3 install -U uiautomator2

Метод использования

В настоящее время этот плагин уже встроен в uiautomator2, поэтому не требуется регистрация плагина.

Простой пример использования

Вот простой пример, чтобы показать, как это работает:

import uiautomator2 as u2

def main():
    d = u2.connect()
    d.app_start("com.netease.cloudmusic", stop=True)

    d.xpath('//*[@text="Персональный FM"]').click()
    
    #
    # Продвинутый пример (локация элементов)
    #

    # @-начальные
    d.xpath('@personal-fm') # эквивалентно d.xpath('//*[@resource-id="personal-fm"]')
    # Локация элементов с несколькими условиями, аналогично AND
    d.xpath('//android.widget.Button').xpath('//*[@text="Персональный FM"]')
    
    d.xpath('//*[@text="Персональный FM"]').parent() # локация родительского элемента
    d.xpath('//*[@text="Персональный FM"]').parent("@android:list") # локация родительского элемента, соответствующего условиям

Ниже приведен код без import и main, предполагается, что переменная d уже существует.

Операции с XPathSelector

sl = d.xpath("@com.example:id/home_searchedit") # sl — это объект XPathSelector

# Нажатие
sl.click()
sl.click(timeout=10) # Указание времени ожидания
sl.click_exists() # Нажатие, если элемент существует, возвращает, был ли нажат элемент
sl.click_exists(timeout=10) # Ожидание до 10 секунд

sl.match() # Возвращает None, если не соответствует, иначе возвращает XMLElement

# Ожидание появления соответствующего элемента, возвращает XMLElement
# По умолчанию время ожидания составляет 10 секунд
el = sl.wait()
el = sl.wait(timeout=15) # Ожидание 15 секунд, если элемент не найден, возвращает None
```# Ожидание исчезновения элемента
sl.wait_gone()
sl.wait_gone(timeout=15) 

# Аналогично wait, но если элемент не найден, выбрасывает исключение XPathElementNotFoundError
el = sl.get() 
el = sl.get(timeout=15)

# Изменение времени ожидания по умолчанию на 15 секунд
d.xpath.global_set("timeout", 15)
d.xpath.implicitly_wait(15) # Эквивалент предыдущей строке

print(sl.exists) # Возвращает, существует ли элемент (bool)
sl.get_last_match() # Возвращает последний найденный XMLElement

sl.get_text() # Получение текста элемента
sl.set_text("") # Очистка текстового поля
sl.set_text("hello world") # Ввод текста "hello world" в текстовое поле

# Перебор всех соответствующих элементов
for el in d.xpath('//android.widget.EditText').all():
    print("rect:", el.rect) # Выводит кортеж: (x, y, width, height)
    print("center:", el.center())
    el.click() # Операция нажатия
    print(el.elem) # Выводит узел, распарсенный lxml
    print(el.text)

# Не протестированные методы
# Нажатие на элемент, находящийся в координатах (50%, 50%) внутри элемента
d.xpath("//*").position(0.5, 0.5).click() 

Операции с XMLElement

# Объект, возвращаемый XPathSelector.get(), называется XMLElement
el = d.xpath("@com.example:id/home_searchedit").get()

lx, ly, width, height = el.rect # Получение координат верхнего левого угла и размеров
lx, ly, rx, ry = el.bounds # Координаты верхнего левого и нижнего правого углов
x, y = el.center() # Получение координат центра элемента
x, y = el.offset(0.5, 0.5) # Аналогично center()

# Отправка нажатия
el.click()

# Вывод текстового содержимого
print(el.text) # Получение атрибутов элемента, возвращает словарь
print(el.attrib)

# Скриншот элемента (работает путем сначала получения полного скриншота, а затем вырезания)
el.screenshot()

# Перемещение элемента
el.swipe("right") # left, right, up, down
el.swipe("right", scale=0.9) # scale по умолчанию 0.9, что означает, что расстояние перемещения составляет 90% ширины элемента, а для перемещения вверх — 90% высоты

Слайдинг до определенной позиции

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

from uiautomator2 import connect_usb, Direction

d = connect_usb()

d.scroll_to("Заказать")
d.scroll_to("Заказать", Direction.FORWARD) # По умолчанию это скролл вниз, но можно также использовать BACKWARD, HORIZ_FORWARD (горизонтально), HORIZ_BACKWARD (горизонтально в обратную сторону)
d.scroll_to("Заказать", Direction.HORIZ_FORWARD, max_swipes=5)

# Также можно скроллить внутри определенного элемента
d.xpath('@com.taobao.taobao:id/dx_root').scroll(Direction.HORIZ_FORWARD)
d.xpath('@com.taobao.taobao:id/dx_root').scroll_to("Заказать", Direction.HORIZ_FORWARD)

Полный пример

import uiautomator2 as u2
from uiautomator2 import Direction

def main():
    d = u2.connect()
    d.app_start("com.netease.cloudmusic", stop=True)

    # шаги
    d.xpath("//*[@text='Личный FM']/../android.widget.ImageView").click()
    d.xpath("Следующий трек").click()
```    # Мониторим появление окна 2 секунды, время может быть больше 2 секунд
    d.xpath.sleep_watch(2)
    d.xpath("Перейти на уровень выше").click()
    
    d.xpath("Перейти на уровень выше").click(watch=False) # клик без активации мониторинга
    d.xpath("Перейти на уровень выше").click(timeout=5.0) # ждать 5 секунд    d.xpath.watch_background() # включить мониторинг в фоновом режиме, по умолчанию проверка каждые 4 секунды
    d.xpath.watch_background(interval=2.0) # проверка каждые 2 секунды
    d.xpath.watch_stop() # остановить мониторинг

    for el in d.xpath('//android.widget.EditText').all():
        print("rect:", el.rect) # выводит кортеж: (левая_x, верхняя_y, ширина, высота)
        print("bounds:", el.bounds) # выводит кортеж: (левая, верхняя, правая, нижняя)
        print("center:", el.center())
        el.click() # операция клика
        print(el.elem) # выводит узел lxml

    # скролл
    el = d.xpath('@com.taobao.taobao:id/fl_banner_container').get()

    # с правой стороны на левую
    el.swipe(Direction.HORIZ_FORWARD) 
    el.swipe(Direction.LEFT) # с правой стороны на левую

    # с нижней стороны на верхнюю
    el.swipe(Direction.FORWARD)
    el.swipe(Direction.UP)

    el.swipe("право", scale=0.9) # масштаб по умолчанию 0.9, расстояние скролла составляет 80% ширины элемента, центр скролла совпадает с центром элемента
    el.swipe("вверх", scale=0.5) # расстояние скролла составляет 50% высоты элемента

    # scroll отличается от swipe тем, что возвращает булево значение, указывающее, появился ли новый элемент
    el.scroll(Direction.FORWARD) # вниз
    el.scroll(Direction.BACKWARD) # вверх
    el.scroll(Direction.HORIZ_FORWARD) # горизонтально вперед
    el.scroll(Direction.HORIZ_BACKWARD) # горизонтально назад

    если el.scroll("forward"):
        print("Можно продолжить прокрутку")

## Правила XPath
Чтобы быстрее писать скрипты, мы определили несколько упрощённых правил для XPath.**Правило 1**

`//` в начале означает стандартный XPath.

**Правило 2**

`@` в начале означает локализацию по `resourceId`.

`@smartisanos:id/right_container` эквивалентно 
`//*[@resource-id="smartisanos:id/right_container"]`

**Правило 3**

`^` в начале означает регулярное выражение.

`^.*道了` эквивалентно `//*[re:match(text(), '^.*道了')]`

**Правило 4**

> Вдохновлен SQL like

`知道%` соответствует тексту, начинающемуся с `知道`, эквивалентно `//*[starts-with(text(), '知道')]`

`%知道` соответствует тексту, заканчивающемуся на `知道`, эквивалентно `//*[ends-with(text(), '知道')]`

`%知道%` соответствует тексту, содержащему `知道`, эквивалентно `//*[contains(text(), '知道')]`

**~~Правило 5~~ (эта функция удалена)**

> Дополнительно от Selenium PageObjects

`$知道` соответствует тексту, определенному через `d.xpath.global_set("alias", dict)` словарём, если не существует, будет использоваться `知道` для соответствия

**Последнее правило**

соответствует тексту и описанию элемента

например `搜索` эквивалентно XPath `//*[@text="搜索" or @content-desc="搜索" or @resource-id="搜索"]`

## Особые примечания
- Иногда `className` содержит символ `$`, который является недопустимым в XML, поэтому все такие символы заменены на `-`

## Некоторые продвинутые методы использования XPath

Все элементы

//*

resource-id содержит слово login

//*[contains(@resource-id, 'login')]

Кнопка содержит слово "账号" или "帐号"

//android.widget.Button[contains(@text, '账号') or contains(@text, '帐号')]

Второй ImageView из всех ImageView

(//android.widget.ImageView)[2]

(//android.widget.ImageView)[last()]

# Элемент содержит имя "ImageView"
//*[contains(name(), "ImageView")]

## Некоторые полезные сайты
- [XPath playground](https://scrapinghub.github.io/xpath-playground/)
- [Некоторые продвинутые методы использования XPath-Жаншу](https://www.jianshu.com/p/4fef4142b33f)
- [XPath Quicksheet](https://devhints.io/xpath)

Если у вас есть другие материалы, пожалуйста, добавьте их через [Issues](https://github.com/openatx/uiautomator2/issues/new)

## Устаревшие функции
**Определение псевдонимов** больше не поддерживается с версии `1.3.4`

Этот подход похож на [PageObjects](https://selenium-python.readthedocs.io/page-objects.html) в Selenium

```python
# Здесь используется Python 3. Для Python 2 строковые литералы нужно определить как u"Меню", обратите внимание на u перед строкой
d.xpath.global_set("alias", {
    "Меню": "@com.netease.cloudmusic:id/qh", # TODO(ssx): возможно, мы сможем поддерживать P("@com.netease.cloudmusic:id/qh", wait_timeout=2) в будущем
    "Настройки": "//android.widget.TextView[@text='Настройки']",
})
# Здесь нужно $开头
d.xpath("$меню").click() # эквивалентно d.xpath()
d.xpath("$настройки").click()

d.xpath("$меню").click()
# эквивалентно d.xpath("@com.netease.cloudmusic:id/qh").click()

d.xpath("$小吃").click() # здесь будет выброшено исключение XPathError, так как нет такого alias как小吃

# параметр alias_strict
d.xpath.global_set("alias_strict", False) # по умолчанию True
d.xpath("$小吃").click() # здесь будет выполнено корректно
# эквивалентно
d.xpath('//*[@text="小吃" or @content-desc="小吃"]').click()

Изменение уровня логирования для xpath

по умолчанию logging.INFOметод изменения

import logging

d.xpath.logger.setLevel(logging.DEBUG)

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/supernatural-fork-uiautomator2.git
git@api.gitlife.ru:oschina-mirror/supernatural-fork-uiautomator2.git
oschina-mirror
supernatural-fork-uiautomator2
supernatural-fork-uiautomator2
master