Василий Рябов vasily-v-ryabov из компании Aquantia объясняет, как с помощью Python можно наладить тестирование десктопных интерфейсов. Из лекции вы узнаете об инструментах open source и поддержке accessibility-технологий в библиотеке pywinauto. Видео и расшифровка в основном предназначены для тех, кто занимается тестированием софта для Windows, но про Linux и macOS автор тоже немного рассказывает.
— Всем привет. У меня технический доклад про автоматизацию десктопных GUI-тестов. Речь пойдет сугубо про open source, про платные не буду упоминать. Почти все решения open source, которыми можно пользоваться, будут упомянуты.
Не претендую на полный обзор и сравнение, но расскажу, чем я пользовался и над чем наше небольшое сообщество работает.
Сама по себе задача десктопного GUI по сравнению с вебом сложнее, потому что нет единых стандартов. Например, для отсылки каких-то управляющих воздействий она еще может решаться более-менее легко, но не так все просто.
Также нам нужно уметь получать текст из контролов, чтобы верифицировать какую-то информацию, но не только для этого. Еще нам нужно знать, куда именно именно кликнуть, чтобы не кликать просто абы куда.
Есть три распространенных подхода. Самый ненадежный — взяли захардкоженные координаты, куда-то ткнули, пальцем в небо. Раз, и разрешение экрана изменилось, тема изменилась, размер окна, окошко открылось в другом месте, еще что-то, все поехало и поплыло. Поддерживать невозможно, проще руками тестировать.
Второй подход более надежный, но тоже не такой уж стабильный — на основе распознавания образов. Сейчас есть инструменты, которые получили некоторую популярность. Здесь не идет речь о распознавании текста. Скорее просто поиск похожего квадратика или прямоугольника на экране, чтобы туда можно было кликнуть. Самый популярный из них — Sikuli. Lackey — аналог на чистом Python. В основном доклад будет про инструменты на Python, но про другие языки тоже буду упоминать.
Наконец, самый надежный и быстрый подход — на основе accessibility-технологий. К сожалению, он не всегда применим — если совсем некуда деваться, можно и распознавать изображение как-то.
Какие бывают accessibility-технологии? В основном вы о них уже слышали: старый добрый Win32 API и самая последняя технология от Microsoft, не такая уж и новая — MS UI Automation, который частично включает в себя Win32 API, но его иерархия по-другому устроена, не так все просто. Она гораздо больше приложений поддерживает в себе.
Наверняка многие из вас пользовались такими инспекторами объектов, как Spy++. Это лучший друг автоматизатора. Для UI Automation есть свой инспектор объектов, который тоже позволяет просмотреть иерархию окон и узнать, как там расположено дерево. Это Insect.exe, он расположен в Windows SDK в папке Program Files. Если все установлено, можете его найти.
Еще есть технологии на Linux и на Мас, о них упомяну только на одном слайде. Они существуют, есть даже инструменты, которые их используют.
Сначала поговорим про инструменты open source для старого доброго Win32 API. Он хоть и старый, но шустрый.
Есть довольно известные инструменты, такие как AutoIt и AutoHotkey. У каждого из них есть собственные скриптовые языки, что немного не универсально, но они популярны и давно существуют на рынке. Хотя тот же AutoIt скорее позиционируется разработчиками для админских задач, автоматизации, для того, чтобы что-нибудь поставить. Тем не менее, многие тестировщики приспособили его именно для GUI-тестов.
В свое время я натолкнулся на библиотеку на Python, которая называется pywinauto. Тогда я еще не знал ни про AutoIt, ни про AutoHotkey. Она на чистом Python, довольно красивый интерфейс, лицензия тоже удобная. В свое время я выбрал для своих задач pywinauto.
Для иллюстрации — пример того, насколько просто можно писать код на pywinauto. Просто запускаем приложение, дальше обращаемся по имени главного окна, как к атрибуту объекта. Просто как к члену класса обращаемся. Затем также обращаемся к тому же combo box — именно к тому, у которого рядом написано Color. Выбираем по тексту все. Кликаем на кнопочки и контролируем, что у нас окно перестало быть visible, что оно закрылось.
Выглядит довольно удобно, но мы еще разберем подробнее, как это все работает. Под этой простой внешней концепцией много чего лежит.
Для начала немного об истории. Библиотека появилась в 2006-м, до 2010 года развивалась автором Марком Макмэхоном. Как раз в 2010-м я начал ее использовать, а она как раз перестала поддерживаться. Так вышло, это жизнь. Тогда мы в наших приложениях как раз переходили на 64 бита и, когда написали первые тесты, поняли, что надо уже и 64 бита тестировать. Пришлось внутренности библиотеки самому перетряхнуть.
Мы использовали какое-то время свой внутренний клон в Intel, с этим клоном очень долго жили, он был довольно стабильный, и к концу 2014-го пришло понимание, что нужно вернуть это в open source, поскольку проект не собирался сам оживать. Пришлось оживить его.
Заодно портировали на третий Python со второго. После всех бюрократических шагов выпустили новый мажорный релиз версии 0.5.4, и я продолжил заниматься этим проектом в свободное от работы время.
Следующий мажорный релиз, который вышел этой осенью, поддерживал технологию MS UI Automation, то есть гораздо больший спектр приложений. Он уже вышел от сообщества разработчиков. Мы продолжаем поддерживать эту библиотеку, есть еще много планов по поддержке других платформ.
О самой технологии UI Automation. Название вы слышали, наверное. Некоторые думают, что это чисто .NET-технология. На самом деле это не так. Есть обертка на .NET. А само ядро этой библиотеки, для клиентской части, имеет нативный COM-интерфейс.
Есть другие инструменты open source, на том же C#, который через .NET-использует эту технологию. Это TestStack.White, который уже довольно давно существует, и более молодой Winium.Desktop, который предлагает интерфейс в Selenium-стиле.
И той, и другой библиотекой вполне можно пользоваться, если вы предпочитаете C#. Я в свое время давал студентам задание перетащить с помощью drag-and-drop из explorer.exe какой-нибудь файлик в тот же Яндекс или Google Диск. И с помощью White, и с помощью Winium они нормально справлялись, оба инструмента вполне юзабельны. Единственное, не вполне видно, как они на C# могли бы стать в будущем кроссплатформенными.
Библиотека pywinauto пока по популярности посерединке между ними, все впереди.
UI Automation использовали бы все, если бы она была совсем идеальной. К сожалению, она получше, чем Win32 API, но некоторая громоздкость все равно есть. Плюс использовать .NET обертку может оказаться не очень хорошо, потому что иногда легко упустить некоторые элементы управления. Есть редко воспроизводящийся, но известный баг.
Плюс СОМ-интерфейс хоть и нативный, но не совсем стандартный. Это не IDispatch, как в MS Excel или Word. Используются свои кастомные интерфейсы.
И конечно, технологии тоже не все могут. У Java своя самобытная оконная система. GTK+, по-моему. Вообще с accessibility-технологиями на Windows у них никак. Это не 80% рынка, если говорить про Java и особенно про GTK+. Поэтому можно покрыть большой спектр приложений.
В свое время мы пробовали использовать UI Automation из-под .NET. Есть интерпретатор Python, который прямо на .NET работает, «железный Python», но он сам по себе тоже не идеален, приходилось костылизировать его с помощью маленькой C#-библиотеки, потому что там есть неприятная бага с ArrayList. Тем более, что проект уже какое-то время как собрался умирать. Его вроде опять воскрешают, если говорить про «железный Python». В общем, не вариант.
Но нашлось решение для чистого Python, называют CPython. Это Python, который всем доступен на python.org или ActiveState Python.
Есть сторонний нестандартный питоновский проект, который называется Com Types. Он поддерживает эти кастомные СОМ-интерфейсы. Он нас реально спас и позволил многое сделать. Конечно, у него есть свои ограничения. Судя по всему, есть глубокая и небольшая, но неприятная и труднонаходимая бага, которая не позволяет подгружать из библиотеки функции, позволяющие обрабатывать кастомные контролы. Может, в будущем кто-то решит эту проблему, но надеяться на кого-то сложно.
Тем не менее, все стандартные контролы мы можем поддержать. Изменения, которые в код на pywinauto нужно внести: мы ввели понятие низкоуровневого слоя, оставили поддержку Win32 API и добавили то, что назвали еще одним бэкендом, — uia.
Сначала отличие только в том, что мы запускаем приложение не просто как объект application, а указываем, что он у нас поддерживает другой бэкенд. И все. Дальше почти все то же самое. Не совсем — иерархия окон немного отличается для разных accessibility-технологий. Но принципиально подход такой же.
Что это за подход? В первую очередь, нужна начальная точка, от которой будем плясать. Это объект application. Можно и стартовать его, и подключиться к уже запущенному приложению. Есть большой спектр критериев, по которым мы способны подключиться: по exe, по окнам каким-то. Критериев может быть больше, здесь указаны только примеры.
В некоторых десктопных приложениях, особенно на Windows 10, даже иерархия окон может быть размазана по нескольким процессам — как, например, у калькулятора. Чтобы по всей этой иерархии бегать, иногда удобнее взять и от корневого элемента плясать, т объекта desktop, который мы недавно реализовали специально для этих целей.
От приложения или корневого элемента мы можем создать спецификацию окна. Это как раз коренная концепция, самая основа устройства интерфейса pywinauto. Спецификация окна — мы просто его описываем. Оно может не существовать, а может уже не существовать на экране. И от этого описания можем искать элемент управления.
После того, как мы его нашли, у нас создается объект Wrapper — обертка, привязанная к реально существующей кнопке, реально существующему editbox, что-то зеленое с щупальцами, которое умеет дергать за контрол и управлять им.
Дальше подробнее посмотрим, какие виды описаний можно создавать.
Первый и самый простой — просто идем от приложения по имени главного окна, затем по имени кнопки, например. Но у нас есть ограничение на доступ к объекту через точку, по атрибуту. Если текст по-русски или по-китайски написан — что делать? Придется использовать доступ по ключу, как к обычному питоновскому словарю. Это почти то же самое.
Если быть совсем точным, это эквивалентно третьему варианту. Поиск окна происходит приближенно с точностью до опечаток, потому что пробелы нельзя вписывать в доступ по атрибуту, в первый вариант. Так что мы можем более детально это использовать — создавая детальные описания. Здесь может быть не один критерий, а, как показано внизу, сразу два или больше, чтобы текст был такой же и чтобы у нас тип контрола совпадал. Когда мы указываем тип контрола явно, он работает побыстрее. Это небольшой трюк по написанию более быстрого кода.
Как вся эта кухня работает? От спецификации окна нужно как-то создать Wrapper. Первая синяя строчка и вторая работают совершенно одинаково, делают то же самое, просто cоздание объекта Wrapper скрывается средствами Python. Python еще не такое позволяет сделать.
Если мы без клика это сделаем, а просто напишем такой statement внизу, они вернут немного разные вещи. Первый вариант вернет спецификацию окна, второй — Wrapper, с которым можно работать. При откладке, когда разрабатываете тест, удобно явно вызвать Wrapper-объект и смотреть, какие у него есть методы, что мы можем делать с контролом. В продакшен-коде, чтобы не загромождать, можно эти Wrapper объекты убрать. Тогда они будут автоматически создаваться, а в них начнет вызываться метод Click или еще что-то.
До этого мы внутри не указывали явно, объект какого класса мы создаем. Pywinauto умеет автоматически искать нужный класс и создавать нужный класс нужного Wrapper. Работает это через метакласс в Python. Еще одна черная магия питоновская. Это некий реестр объектов, называется registry pattern. Он хранит некоторые реестры классов. В зависимости от того, какого типа у вас контрол — обычно это строчка class name, — он может создавать объект нужно класса.
Если мы в базовый класс, который называется HwndWrapper, вызываем, то вторая веточка new_class автоматически ищет нужный Wrapper и создает объект этого класса. А если мы создаем производный класс, он его создает попросту явно. Если захотим, можем явно создать указанный класс, но в большинстве случаев это не нужно, что немного снижает входной порог для людей с небольшим опытом программирования или без него.
Так реализуется производный класс от HwndWrapper. Здесь указывается, class name какого типа принимаются. Это поддержка стандартных ComboBox и WindowsForms. Дальше у нас есть методы, которые умеют дергать элементы управления.
Рассмотрим часть примера с перетаскиванием из explorer.exe куда-нибудь в Яндекс.Диск, но пока без Яндекс.Диска. Пример не такой уж тривиальный, в explorer с помощью Win32 API не все можно сделать. У нас открыта папка pywinauto, мы хотим кликнуть на файл и посмотреть его properties, учебный пример.
Чтобы его выполнить, нужно не так много строчек на pywinauto. Мы подключаемся к тому explorer, у которого нужный нам заголовок окна. Переключились как на активное окно. Дальше — список, в котором перечислены файлы.
Нужно еще добавить функцию, которая ждет, пока использование процессора не упадет, потому что тут ленивая инициализация, об этом позже. Тоже одна дополнительная строчка.
Мы делаем right_click_input, он кликает, вызываем меню properties в контекстном меню одной строчкой. Потом, поскольку properties открываются не в explorer.exe, а в другом процессе, мы идем к нему через объект desktop, получаем диалог и просто жмем на кнопку Cancel. Все это занимает е так много.
Результат.
Немного магии. Суперзрение.
Где взять идентификаторы, по которым можем общаться к элементам управления? Допустим, есть главное окошко, у него есть заголовок, он виден. Дальше можно напечатать все идентификаторы. Некоторые вещи можно реально скопипастить в код на Python, в свой скрипт.
Например, у ComboBox, у атрибута, есть два предлагаемых имени: просто ComboBox и border sized ComboBox. Слева — просто статический контрол.
У нас есть пять способов сколдовать это заклинание. Мы можем обратиться просто по тексту, кнопка ОК. Можем сказать, что это не просто ОК, не просто какая-то надпись, а еще и кнопочка,. Мы можем, если много кнопок и у всех одинаковый текст, обратиться просто по индексу: button1, button2 и т. д. Если контролы динамические, EditBox, текст в нем постоянно меняется, мы туда что-то вводим и удаляем, то нужно что-то статическое, чтобы каждый раз обращаться к контролу одинаково. Статическим может быть какой-нибудь лейбл слева или TextBox. В случае с Tab Control и другими элементами списка можем обратиться по типу контрола и по имени одной из вкладок. Внутренний текст тоже может участвовать в этом имени. Такая схема полно покрывает способы обращения к элементам управления.
Есть совсем детальный способ — когда мы создаем Windows-спецификацию вызовом метода Window. Неважно, как создана спецификация. По ней мы можем ждать, когда он появится или когда исчезнет, переключит простые состояния типа enabled или visible.
Другая полезная штука, когда пригождается для того же explorer, — ждать, когда загрузка процессора упадет. Причем не для всей системы, а для этого конкретного процесса. Такие вещи в Win32 API есть, мы их реализовали через такую простую обертку. Мы ждем, что загрузка процессора для указанного процесса упадет нижу 10% и, если в течение 10 секунд этого не произошло, кидаем исключение.
Также для ожиданий есть методы, которые не кидают исключения, а просто возвращают true или false, exist, visible и т. д.
Может, это выглядит понятным, но тема десктопной автоматизации сложная, случается много подводных камней.
Требуется определенный опыт, чтобы писать GUI тесты. Опыт программирования очень желателен.
Про общие проблемы, которые не зависят от библиотеки. Вы можете встретить их как с pywinauto, так и с любой другой библиотекой.
Часто для графических тестов нужен активный десктоп. Если вы залочили систему и ушли обедать, активного десктопа нет. Если вы зашли по remote desktop и свернули его, у вас опять нет активного десктопа. А если развернули, то есть. Вы же хотите, чтобы скрипт бежал, тестировал что-то, пока вы обедаете. Для remote desktop вы можете его не сворачивать, а выйти из полноэкранного режима, оставив его в режиме окна, запустить скрипт и быстро переключиться на свой локальный лэптоп, например. В этом время скрипт отлично работает, но способ немножко колхозный, потому что вам нужно выключить лэптоп и уйти домой в конце концов.
Чтобы пускать тесты в лабе, нужно настроить VNC-сервер, и даже если мы отключаемся от сессии, активный десктоп все равно отключается. Это один из трюков, который пришлось разучить и применить.
И разумеется, если вы запускаете скрипт из-под того же jenkins-slave, который не дай бог идет как сервис, то вновь ничего не получится. Это ограничение операционной системы. Работать с графическим интерфейсом как из-под сервиса нельзя.
Немного о других платформах. На Linux тоже есть питоновский пакет — pyatspi2, использующий технологию atspi. На OS X есть pyatom. Они не очень удобные для использования и требуют, в частности, компиляции. Pywinauto компиляции не требует, просто ставится одной строчкой. Джависты рекомендуют библиотеку Jemmy из тех, которыми можно реально пользоваться.
В последнее время наше небольшое сообщество ведет работы в этом направлении. Технологии atspi на Linux и OS X. Есть простенький прототип, который пока только в процессе изучения.
Что касается Java, там даже не приступили. Но из более-менее юзабельных есть библиотека JPype, которая из чистого CPython позволяет вызвать код Java. Я знаю, есть Jython, который работает прямо на виртуальной машине Java. Это тоже питоновский интерпретатор, у него тоже есть особенности, не очень хорошо совместимые с обычным CPython. Более перспективным направлением при развитии в сторону Java я бы видел JPype.
Конечно, хочется еще больше фич. В pywinauto уже заложены и другие вещи, но они пока слабо используются. Реализовали глобальные хуки, когда мы можем подписаться на событие в операционной системе. Нажалась кнопка на клавиатуре, и по ней мы хотим запустить цепочку действий с графическим интерфейсом, большую и длинную. Такое уже реализовано, и в следующий релиз, который появится на днях, войдет апдейт более хорошего качества.
В технологии UI Automation есть еще более детальные обработчики событий, на которые можно подписаться. Это не просто низкоуровневые нажатия клавиатуры, а например, закрытие окошка, появление окошка или кнопочки или изменение свойства окошка. Можно многое сделать и выполнить record/replay, запись и воспроизведение, но указанная фича очень сложная. Для реализации нужно много ресурсов, а мы этим занимаемся в свободное время, и пока все отложено.
Еще заложена фича по тестированию локализации — чтобы можно было бы написать один скрипт и, может, одно условие туда добавить. Прогнали его на английском интерфейсе, а потом тот же скрипт гоняем на русском, на китайском, на тайском, на любом языке, и он работает также, если там сильно не изменен дизайн в графическом интерфейсе.
Есть поиск типичных ошибок. Для всего окна запустили, проверили, что EditBoxes не позволяют вводить какие-то экстремальные значения или что-то в таком духе. Эта функциональность заложена, но ее не очень много, мало проверок. Все перечисленное можно расширять, делать погружение, но пока не было времени.
Заложена большая функциональность и есть потенциал. Поскольку это open source, любая помощь приветствуется. Мы будем рады.
Инструменты для тестирования GUI. Как он сам по себе тестируется? Конечно, есть модульные тесты, они написаны с помощью стандартного питоновского модуля unittest. Есть автоматические билды по каждому коммиту, пул-реквесты на GitHub в обязательном порядке проверя.тся тестами в облаке на Windows и уже на Linux. Маленький модуль на Linux тоже уже проверяется автоматически.
Есть статические анализаторы, которые очень помогают отследить грубые и даже не очень грубые ошибки.
Процесс налажен, мы стараемся и покрытие кода держать по всем Python. Агрегирование — порядка 95%, все получается.
И конечно, вся перечисленная работа была бы невозможна без людей, которые постоянно входят в наше сообщество. Это два матерых разработчика. К сожалению, один из них сейчас уже не так активен, а Валентин очень много помогает по всем вопросам. Два талантливых студента вместе с ним делали систему поддержки UIAutomation и нескольких бэкендов. Еще один студент занимается разработкой модулей на Linux, еще один уже вникает в accessibility API от Apple, только начал заниматься указанной темой. Надеемся, года через два-три pywinauto действительно станет кроссплатформенным и полезным в этом деле.
Документация — в основном на английском, есть пара статей на Хабре, планируем писать новые статьи. Еще многое можно делать в плане развития сообщества, чтобы привлекать людей. Пока стараемся отвечать на вопросы на Stack Overflow, мониторим почту, стараемся оказывать максимальную помощь пользователям. Мы надеемся, что кто-то из пользователей постепенно будет переходить в контрибьюторы. На этом все, спасибо.
— Всем привет. У меня технический доклад про автоматизацию десктопных GUI-тестов. Речь пойдет сугубо про open source, про платные не буду упоминать. Почти все решения open source, которыми можно пользоваться, будут упомянуты.
Не претендую на полный обзор и сравнение, но расскажу, чем я пользовался и над чем наше небольшое сообщество работает.
Сама по себе задача десктопного GUI по сравнению с вебом сложнее, потому что нет единых стандартов. Например, для отсылки каких-то управляющих воздействий она еще может решаться более-менее легко, но не так все просто.
Также нам нужно уметь получать текст из контролов, чтобы верифицировать какую-то информацию, но не только для этого. Еще нам нужно знать, куда именно именно кликнуть, чтобы не кликать просто абы куда.
Есть три распространенных подхода. Самый ненадежный — взяли захардкоженные координаты, куда-то ткнули, пальцем в небо. Раз, и разрешение экрана изменилось, тема изменилась, размер окна, окошко открылось в другом месте, еще что-то, все поехало и поплыло. Поддерживать невозможно, проще руками тестировать.
Второй подход более надежный, но тоже не такой уж стабильный — на основе распознавания образов. Сейчас есть инструменты, которые получили некоторую популярность. Здесь не идет речь о распознавании текста. Скорее просто поиск похожего квадратика или прямоугольника на экране, чтобы туда можно было кликнуть. Самый популярный из них — Sikuli. Lackey — аналог на чистом Python. В основном доклад будет про инструменты на Python, но про другие языки тоже буду упоминать.
Наконец, самый надежный и быстрый подход — на основе accessibility-технологий. К сожалению, он не всегда применим — если совсем некуда деваться, можно и распознавать изображение как-то.
Какие бывают accessibility-технологии? В основном вы о них уже слышали: старый добрый Win32 API и самая последняя технология от Microsoft, не такая уж и новая — MS UI Automation, который частично включает в себя Win32 API, но его иерархия по-другому устроена, не так все просто. Она гораздо больше приложений поддерживает в себе.
Наверняка многие из вас пользовались такими инспекторами объектов, как Spy++. Это лучший друг автоматизатора. Для UI Automation есть свой инспектор объектов, который тоже позволяет просмотреть иерархию окон и узнать, как там расположено дерево. Это Insect.exe, он расположен в Windows SDK в папке Program Files. Если все установлено, можете его найти.
Еще есть технологии на Linux и на Мас, о них упомяну только на одном слайде. Они существуют, есть даже инструменты, которые их используют.
Сначала поговорим про инструменты open source для старого доброго Win32 API. Он хоть и старый, но шустрый.
Есть довольно известные инструменты, такие как AutoIt и AutoHotkey. У каждого из них есть собственные скриптовые языки, что немного не универсально, но они популярны и давно существуют на рынке. Хотя тот же AutoIt скорее позиционируется разработчиками для админских задач, автоматизации, для того, чтобы что-нибудь поставить. Тем не менее, многие тестировщики приспособили его именно для GUI-тестов.
В свое время я натолкнулся на библиотеку на Python, которая называется pywinauto. Тогда я еще не знал ни про AutoIt, ни про AutoHotkey. Она на чистом Python, довольно красивый интерфейс, лицензия тоже удобная. В свое время я выбрал для своих задач pywinauto.
Для иллюстрации — пример того, насколько просто можно писать код на pywinauto. Просто запускаем приложение, дальше обращаемся по имени главного окна, как к атрибуту объекта. Просто как к члену класса обращаемся. Затем также обращаемся к тому же combo box — именно к тому, у которого рядом написано Color. Выбираем по тексту все. Кликаем на кнопочки и контролируем, что у нас окно перестало быть visible, что оно закрылось.
Выглядит довольно удобно, но мы еще разберем подробнее, как это все работает. Под этой простой внешней концепцией много чего лежит.
Для начала немного об истории. Библиотека появилась в 2006-м, до 2010 года развивалась автором Марком Макмэхоном. Как раз в 2010-м я начал ее использовать, а она как раз перестала поддерживаться. Так вышло, это жизнь. Тогда мы в наших приложениях как раз переходили на 64 бита и, когда написали первые тесты, поняли, что надо уже и 64 бита тестировать. Пришлось внутренности библиотеки самому перетряхнуть.
Мы использовали какое-то время свой внутренний клон в Intel, с этим клоном очень долго жили, он был довольно стабильный, и к концу 2014-го пришло понимание, что нужно вернуть это в open source, поскольку проект не собирался сам оживать. Пришлось оживить его.
Заодно портировали на третий Python со второго. После всех бюрократических шагов выпустили новый мажорный релиз версии 0.5.4, и я продолжил заниматься этим проектом в свободное от работы время.
Следующий мажорный релиз, который вышел этой осенью, поддерживал технологию MS UI Automation, то есть гораздо больший спектр приложений. Он уже вышел от сообщества разработчиков. Мы продолжаем поддерживать эту библиотеку, есть еще много планов по поддержке других платформ.
О самой технологии UI Automation. Название вы слышали, наверное. Некоторые думают, что это чисто .NET-технология. На самом деле это не так. Есть обертка на .NET. А само ядро этой библиотеки, для клиентской части, имеет нативный COM-интерфейс.
Есть другие инструменты open source, на том же C#, который через .NET-использует эту технологию. Это TestStack.White, который уже довольно давно существует, и более молодой Winium.Desktop, который предлагает интерфейс в Selenium-стиле.
И той, и другой библиотекой вполне можно пользоваться, если вы предпочитаете C#. Я в свое время давал студентам задание перетащить с помощью drag-and-drop из explorer.exe какой-нибудь файлик в тот же Яндекс или Google Диск. И с помощью White, и с помощью Winium они нормально справлялись, оба инструмента вполне юзабельны. Единственное, не вполне видно, как они на C# могли бы стать в будущем кроссплатформенными.
Библиотека pywinauto пока по популярности посерединке между ними, все впереди.
UI Automation использовали бы все, если бы она была совсем идеальной. К сожалению, она получше, чем Win32 API, но некоторая громоздкость все равно есть. Плюс использовать .NET обертку может оказаться не очень хорошо, потому что иногда легко упустить некоторые элементы управления. Есть редко воспроизводящийся, но известный баг.
Плюс СОМ-интерфейс хоть и нативный, но не совсем стандартный. Это не IDispatch, как в MS Excel или Word. Используются свои кастомные интерфейсы.
И конечно, технологии тоже не все могут. У Java своя самобытная оконная система. GTK+, по-моему. Вообще с accessibility-технологиями на Windows у них никак. Это не 80% рынка, если говорить про Java и особенно про GTK+. Поэтому можно покрыть большой спектр приложений.
В свое время мы пробовали использовать UI Automation из-под .NET. Есть интерпретатор Python, который прямо на .NET работает, «железный Python», но он сам по себе тоже не идеален, приходилось костылизировать его с помощью маленькой C#-библиотеки, потому что там есть неприятная бага с ArrayList. Тем более, что проект уже какое-то время как собрался умирать. Его вроде опять воскрешают, если говорить про «железный Python». В общем, не вариант.
Но нашлось решение для чистого Python, называют CPython. Это Python, который всем доступен на python.org или ActiveState Python.
Есть сторонний нестандартный питоновский проект, который называется Com Types. Он поддерживает эти кастомные СОМ-интерфейсы. Он нас реально спас и позволил многое сделать. Конечно, у него есть свои ограничения. Судя по всему, есть глубокая и небольшая, но неприятная и труднонаходимая бага, которая не позволяет подгружать из библиотеки функции, позволяющие обрабатывать кастомные контролы. Может, в будущем кто-то решит эту проблему, но надеяться на кого-то сложно.
Тем не менее, все стандартные контролы мы можем поддержать. Изменения, которые в код на pywinauto нужно внести: мы ввели понятие низкоуровневого слоя, оставили поддержку Win32 API и добавили то, что назвали еще одним бэкендом, — uia.
Сначала отличие только в том, что мы запускаем приложение не просто как объект application, а указываем, что он у нас поддерживает другой бэкенд. И все. Дальше почти все то же самое. Не совсем — иерархия окон немного отличается для разных accessibility-технологий. Но принципиально подход такой же.
Что это за подход? В первую очередь, нужна начальная точка, от которой будем плясать. Это объект application. Можно и стартовать его, и подключиться к уже запущенному приложению. Есть большой спектр критериев, по которым мы способны подключиться: по exe, по окнам каким-то. Критериев может быть больше, здесь указаны только примеры.
В некоторых десктопных приложениях, особенно на Windows 10, даже иерархия окон может быть размазана по нескольким процессам — как, например, у калькулятора. Чтобы по всей этой иерархии бегать, иногда удобнее взять и от корневого элемента плясать, т объекта desktop, который мы недавно реализовали специально для этих целей.
От приложения или корневого элемента мы можем создать спецификацию окна. Это как раз коренная концепция, самая основа устройства интерфейса pywinauto. Спецификация окна — мы просто его описываем. Оно может не существовать, а может уже не существовать на экране. И от этого описания можем искать элемент управления.
После того, как мы его нашли, у нас создается объект Wrapper — обертка, привязанная к реально существующей кнопке, реально существующему editbox, что-то зеленое с щупальцами, которое умеет дергать за контрол и управлять им.
Дальше подробнее посмотрим, какие виды описаний можно создавать.
Первый и самый простой — просто идем от приложения по имени главного окна, затем по имени кнопки, например. Но у нас есть ограничение на доступ к объекту через точку, по атрибуту. Если текст по-русски или по-китайски написан — что делать? Придется использовать доступ по ключу, как к обычному питоновскому словарю. Это почти то же самое.
Если быть совсем точным, это эквивалентно третьему варианту. Поиск окна происходит приближенно с точностью до опечаток, потому что пробелы нельзя вписывать в доступ по атрибуту, в первый вариант. Так что мы можем более детально это использовать — создавая детальные описания. Здесь может быть не один критерий, а, как показано внизу, сразу два или больше, чтобы текст был такой же и чтобы у нас тип контрола совпадал. Когда мы указываем тип контрола явно, он работает побыстрее. Это небольшой трюк по написанию более быстрого кода.
Как вся эта кухня работает? От спецификации окна нужно как-то создать Wrapper. Первая синяя строчка и вторая работают совершенно одинаково, делают то же самое, просто cоздание объекта Wrapper скрывается средствами Python. Python еще не такое позволяет сделать.
Если мы без клика это сделаем, а просто напишем такой statement внизу, они вернут немного разные вещи. Первый вариант вернет спецификацию окна, второй — Wrapper, с которым можно работать. При откладке, когда разрабатываете тест, удобно явно вызвать Wrapper-объект и смотреть, какие у него есть методы, что мы можем делать с контролом. В продакшен-коде, чтобы не загромождать, можно эти Wrapper объекты убрать. Тогда они будут автоматически создаваться, а в них начнет вызываться метод Click или еще что-то.
До этого мы внутри не указывали явно, объект какого класса мы создаем. Pywinauto умеет автоматически искать нужный класс и создавать нужный класс нужного Wrapper. Работает это через метакласс в Python. Еще одна черная магия питоновская. Это некий реестр объектов, называется registry pattern. Он хранит некоторые реестры классов. В зависимости от того, какого типа у вас контрол — обычно это строчка class name, — он может создавать объект нужно класса.
Если мы в базовый класс, который называется HwndWrapper, вызываем, то вторая веточка new_class автоматически ищет нужный Wrapper и создает объект этого класса. А если мы создаем производный класс, он его создает попросту явно. Если захотим, можем явно создать указанный класс, но в большинстве случаев это не нужно, что немного снижает входной порог для людей с небольшим опытом программирования или без него.
Так реализуется производный класс от HwndWrapper. Здесь указывается, class name какого типа принимаются. Это поддержка стандартных ComboBox и WindowsForms. Дальше у нас есть методы, которые умеют дергать элементы управления.
Рассмотрим часть примера с перетаскиванием из explorer.exe куда-нибудь в Яндекс.Диск, но пока без Яндекс.Диска. Пример не такой уж тривиальный, в explorer с помощью Win32 API не все можно сделать. У нас открыта папка pywinauto, мы хотим кликнуть на файл и посмотреть его properties, учебный пример.
Чтобы его выполнить, нужно не так много строчек на pywinauto. Мы подключаемся к тому explorer, у которого нужный нам заголовок окна. Переключились как на активное окно. Дальше — список, в котором перечислены файлы.
Нужно еще добавить функцию, которая ждет, пока использование процессора не упадет, потому что тут ленивая инициализация, об этом позже. Тоже одна дополнительная строчка.
Мы делаем right_click_input, он кликает, вызываем меню properties в контекстном меню одной строчкой. Потом, поскольку properties открываются не в explorer.exe, а в другом процессе, мы идем к нему через объект desktop, получаем диалог и просто жмем на кнопку Cancel. Все это занимает е так много.
Результат.
Немного магии. Суперзрение.
Где взять идентификаторы, по которым можем общаться к элементам управления? Допустим, есть главное окошко, у него есть заголовок, он виден. Дальше можно напечатать все идентификаторы. Некоторые вещи можно реально скопипастить в код на Python, в свой скрипт.
Например, у ComboBox, у атрибута, есть два предлагаемых имени: просто ComboBox и border sized ComboBox. Слева — просто статический контрол.
У нас есть пять способов сколдовать это заклинание. Мы можем обратиться просто по тексту, кнопка ОК. Можем сказать, что это не просто ОК, не просто какая-то надпись, а еще и кнопочка,. Мы можем, если много кнопок и у всех одинаковый текст, обратиться просто по индексу: button1, button2 и т. д. Если контролы динамические, EditBox, текст в нем постоянно меняется, мы туда что-то вводим и удаляем, то нужно что-то статическое, чтобы каждый раз обращаться к контролу одинаково. Статическим может быть какой-нибудь лейбл слева или TextBox. В случае с Tab Control и другими элементами списка можем обратиться по типу контрола и по имени одной из вкладок. Внутренний текст тоже может участвовать в этом имени. Такая схема полно покрывает способы обращения к элементам управления.
Есть совсем детальный способ — когда мы создаем Windows-спецификацию вызовом метода Window. Неважно, как создана спецификация. По ней мы можем ждать, когда он появится или когда исчезнет, переключит простые состояния типа enabled или visible.
Другая полезная штука, когда пригождается для того же explorer, — ждать, когда загрузка процессора упадет. Причем не для всей системы, а для этого конкретного процесса. Такие вещи в Win32 API есть, мы их реализовали через такую простую обертку. Мы ждем, что загрузка процессора для указанного процесса упадет нижу 10% и, если в течение 10 секунд этого не произошло, кидаем исключение.
Также для ожиданий есть методы, которые не кидают исключения, а просто возвращают true или false, exist, visible и т. д.
Может, это выглядит понятным, но тема десктопной автоматизации сложная, случается много подводных камней.
Требуется определенный опыт, чтобы писать GUI тесты. Опыт программирования очень желателен.
Про общие проблемы, которые не зависят от библиотеки. Вы можете встретить их как с pywinauto, так и с любой другой библиотекой.
Часто для графических тестов нужен активный десктоп. Если вы залочили систему и ушли обедать, активного десктопа нет. Если вы зашли по remote desktop и свернули его, у вас опять нет активного десктопа. А если развернули, то есть. Вы же хотите, чтобы скрипт бежал, тестировал что-то, пока вы обедаете. Для remote desktop вы можете его не сворачивать, а выйти из полноэкранного режима, оставив его в режиме окна, запустить скрипт и быстро переключиться на свой локальный лэптоп, например. В этом время скрипт отлично работает, но способ немножко колхозный, потому что вам нужно выключить лэптоп и уйти домой в конце концов.
Чтобы пускать тесты в лабе, нужно настроить VNC-сервер, и даже если мы отключаемся от сессии, активный десктоп все равно отключается. Это один из трюков, который пришлось разучить и применить.
И разумеется, если вы запускаете скрипт из-под того же jenkins-slave, который не дай бог идет как сервис, то вновь ничего не получится. Это ограничение операционной системы. Работать с графическим интерфейсом как из-под сервиса нельзя.
Немного о других платформах. На Linux тоже есть питоновский пакет — pyatspi2, использующий технологию atspi. На OS X есть pyatom. Они не очень удобные для использования и требуют, в частности, компиляции. Pywinauto компиляции не требует, просто ставится одной строчкой. Джависты рекомендуют библиотеку Jemmy из тех, которыми можно реально пользоваться.
В последнее время наше небольшое сообщество ведет работы в этом направлении. Технологии atspi на Linux и OS X. Есть простенький прототип, который пока только в процессе изучения.
Что касается Java, там даже не приступили. Но из более-менее юзабельных есть библиотека JPype, которая из чистого CPython позволяет вызвать код Java. Я знаю, есть Jython, который работает прямо на виртуальной машине Java. Это тоже питоновский интерпретатор, у него тоже есть особенности, не очень хорошо совместимые с обычным CPython. Более перспективным направлением при развитии в сторону Java я бы видел JPype.
Конечно, хочется еще больше фич. В pywinauto уже заложены и другие вещи, но они пока слабо используются. Реализовали глобальные хуки, когда мы можем подписаться на событие в операционной системе. Нажалась кнопка на клавиатуре, и по ней мы хотим запустить цепочку действий с графическим интерфейсом, большую и длинную. Такое уже реализовано, и в следующий релиз, который появится на днях, войдет апдейт более хорошего качества.
В технологии UI Automation есть еще более детальные обработчики событий, на которые можно подписаться. Это не просто низкоуровневые нажатия клавиатуры, а например, закрытие окошка, появление окошка или кнопочки или изменение свойства окошка. Можно многое сделать и выполнить record/replay, запись и воспроизведение, но указанная фича очень сложная. Для реализации нужно много ресурсов, а мы этим занимаемся в свободное время, и пока все отложено.
Еще заложена фича по тестированию локализации — чтобы можно было бы написать один скрипт и, может, одно условие туда добавить. Прогнали его на английском интерфейсе, а потом тот же скрипт гоняем на русском, на китайском, на тайском, на любом языке, и он работает также, если там сильно не изменен дизайн в графическом интерфейсе.
Есть поиск типичных ошибок. Для всего окна запустили, проверили, что EditBoxes не позволяют вводить какие-то экстремальные значения или что-то в таком духе. Эта функциональность заложена, но ее не очень много, мало проверок. Все перечисленное можно расширять, делать погружение, но пока не было времени.
Заложена большая функциональность и есть потенциал. Поскольку это open source, любая помощь приветствуется. Мы будем рады.
Инструменты для тестирования GUI. Как он сам по себе тестируется? Конечно, есть модульные тесты, они написаны с помощью стандартного питоновского модуля unittest. Есть автоматические билды по каждому коммиту, пул-реквесты на GitHub в обязательном порядке проверя.тся тестами в облаке на Windows и уже на Linux. Маленький модуль на Linux тоже уже проверяется автоматически.
Есть статические анализаторы, которые очень помогают отследить грубые и даже не очень грубые ошибки.
Процесс налажен, мы стараемся и покрытие кода держать по всем Python. Агрегирование — порядка 95%, все получается.
И конечно, вся перечисленная работа была бы невозможна без людей, которые постоянно входят в наше сообщество. Это два матерых разработчика. К сожалению, один из них сейчас уже не так активен, а Валентин очень много помогает по всем вопросам. Два талантливых студента вместе с ним делали систему поддержки UIAutomation и нескольких бэкендов. Еще один студент занимается разработкой модулей на Linux, еще один уже вникает в accessibility API от Apple, только начал заниматься указанной темой. Надеемся, года через два-три pywinauto действительно станет кроссплатформенным и полезным в этом деле.
Документация — в основном на английском, есть пара статей на Хабре, планируем писать новые статьи. Еще многое можно делать в плане развития сообщества, чтобы привлекать людей. Пока стараемся отвечать на вопросы на Stack Overflow, мониторим почту, стараемся оказывать максимальную помощь пользователям. Мы надеемся, что кто-то из пользователей постепенно будет переходить в контрибьюторы. На этом все, спасибо.