четверг, 10 марта 2011 г.

pywinauto: Берегись event'ов, ибо они подлые

Предисловие
Речь в данном посте пойдет о подводных камнях при использовании стороннего программного обеспечения посредством средства автоматизации действий pywinauto, а именно о событиях. Как всем известно GUI-библиотеки строятся в стиле событийного (event-driven) программирования. Таким образом, любое действие приложения является ответом на действие пользователя. То есть внутри существует некий цикл, который ходит по очереди событий, формируемой действиями пользователя и запускает ответы. Таким образом, получаем ответ на конкретное событие. С этим все понятно.

Суть проблемы
Проблемы возникают, когда разработчик не знает, как именно обрабатываются события в используемой посредством pywinauto программы. Рассмотрим это дело на более конкретном примере - Adobe Reader 9. Этот замечательный софт позволяет нам открывать PDF-документы, а еще он имеет замечательную форму поиска, с которой можно поиграться. Выглядит она так, как показано на рис. 1.
Рис 1. Форма поиска Adobe Reader.
Итак, из того, что нам интересно, мы видим, что форма имеет текстовое поле ввода поисковой строки, флажки конфигурации параметров поиска, и, собственно, кнопку "Search", которая выполняет поиск (также искать можно, нажав Enter, когда фокус установлен в поле ввода строки поиска). Итак, найти текст довольно просто. Делаем для поля ввода:
edit_box.TypeKeys("Some text{ENTER}", with_spaces=True)
Ну, как и ожидалось, текст найден, все ок. Другое дело, когда мы хотим поискать только целые слова. Для этого нам нужно установить флажок "Whole words only". Естественно, находим этот флажок, устанавливаем его и делаем такой же поиск, вот только поставим задержку, чтобы убедится в том, что флажок установлен.
import time
...
wwo_flag.Check()
time.sleep(1)
edit_box.TypeKeys("Some text{ENTER}", with_spaces=True)

И, как совершенно не ожидалось, получаем тот же результат поиска. Флажок установился, а поиск происходит так, будто бы флажок вообще не ставили. Изменим код следующим образом, чтобы мы не устанавливали флажок, а "кликали" на него:
import time
...
if wwo_flag.GetCheckState() != 1:
    wwo_flag.Click()
time.sleep(1)
edit_box.TypeKeys("Some text{ENTER}", with_spaces=True)
Запускаем поиск - все работает. Можно только предположить (скорее всего, конечно, так и есть), что параметры поиска предустанавливаются заранее на "клик" по флажку, а не анализируются при инициации поиска (нажатие на кнопку Search дает тот же эффект, что и Enter в поле ввода строки).

Заключение
Стоит заметить, что этот случай еще легко локализируемый. Если подумать, можно придумать гораздо более извилистые вещи с разными контролами, все-таки, в данном случае работаем с черным ящиком, и с этим ничего не поделаешь. Не попадайтесь...

5 комментариев:

  1. А как мы определяем какой из фагов активируем, их ж 4 в окне, почему активруется именно нужный или не все 4!?

    ОтветитьУдалить
  2. @reason89
    Это уже проблема разработчика =) Здесь я не показывал, но можно делать по разному. Например, проитерировать по children'ам окна и определить, что это именно этот флажок. В данном случае он должен быть
    1. type(control) == CheckBox
    2. 'Whole words only' in control.Texts.
    После этого сохраняем флажок в переменную wwo_flags и уже работаем с этой переменной.

    ОтветитьУдалить
  3. Добрый день, Ростислав

    Подскажите, как быть с pywinauto. Пишу скрипт, который автоматически инсталлирует FSViewer.
    И проблема в следующем: чтобы закончить установку, нужно ткнуть несколько раз далее в инсталляторе.

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

    На данный момент есть такой вариант
    app.Dlg.Wait("exists enabled visible ready")
    app.Dlg.Button1.Click()
    app.Dlg.Wait("exists enabled visible ready")
    app.Dlg.Button2.Click()

    Но если, будет тормоз в установке, то все идет к чертям. Использовать time.sleep как-то некошерно.

    Думал, про такой вариант, но не работает
    app.Dlg.Wait("exists enabled visible ready")
    app.Dlg.Button1.Click()
    app.Dlg.WaitNot("exists enabled visible ready", 2, 2)
    app.Dlg.Wait("exists enabled visible ready")
    app.Dlg.Button2.Click()

    ОтветитьУдалить
  4. Тимур, гляньте в документацию по модулю pywinauto.timings. Там есть интересная функции WaintUntil, WaitUntilPasses. Прелесть в том, что они с интервалом пробуют выполнить некую функцию проверки. Когда функция возвратит True - можно выполнять действие. Также можно указать таймаут, когда функция перестанет пытаться.

    Ну, это кошерный вариант time.sleep.

    ОтветитьУдалить
  5. Ссылка - http://pywinauto.googlecode.com/hg/pywinauto/docs/code/pywinauto.timings.html

    ОтветитьУдалить

В этом гаджете обнаружена ошибка