
Пришло, пришло время (экспертов по опенсорсу) моей истории опенсорс-проекта. Было столько всего, что до технических деталей (почти) не дойдёт – они есть и в более древних свитках статьях.
Началось всё в 2010 году. Я только закончил аспирантуру и попал в новый для меня проект в Интеле фулл-тайм разработчиком. Мне нужно было внедрить автоматическое тестирование десктопных GUI приложений для внутреннего пользования. Опыт подобный уже был накоплен с середины 2006-го тоже в Интеле, но подходящий инструмент надо было еще найти или даже создать. Получилось что-то среднее: найти и воскресить!
Находка на закате
Этой находкой стала Python библиотека pywinauto, которая в 2010 году лежала на платформе Google Code, вскоре закрытой, имея там около 200 звёзд. И так получилось, что первый автор, американец Марк МакМэхон (Mark McMahon), закончил её дописывать и поддерживать как раз в 2010-м, а первые коммиты датировались 2006-м. Это был закат первой волны её разработки. Но как поётся в старой песне, «ведь конец – это чьё-то начало»…
Несмотря на сложность задачи и внутренностей библиотеки, интерфейс у неё оказался довольно красивым и «питоническим» (pythonic). И она отлично умела прокликивать 32-битные приложения и получать тексты оттуда через Win32 API. Получилось быстро её применить в проекте и гонять базовые сценарии, уменьшая количество багов и нецензурных слов.
Портирование на 64 бита и модуль ctypes
Но скоро, буквально в 2011-м, следуя тренду, наш проект переходил на сборку под 64-битные системы, и тут начались глюки, крэши и всякое непредсказуемое поведение. Пришлось перелопатить Сишные структуры, унаследованные от класса ctypes.Structure, подобрав размеры и выравнивание (атрибут _pack). И всё получилось.
На какое-то время наступил мир период расширения тестовой базы и покрытия. И мы жили несколько лет с внутренней версией библиотеки, а снаружи ничего этого не было.
Разведка боем: преподавание
Ещё с 2009-го я преподавал в магистратуре ННГУ спецкурс по языку Python с уклоном в автоматизацию тестов и давал студентам весьма творческие задания (многие до сих пор вспоминают с содроганием :)), включая фактически разведку некоторых технологий. Особенно меня интересовал MS UI Automation API, потому что было известно, что старым Win32 API нельзя просветить, например, WPF приложения и некоторые другие.
В районе 2012-го были даже дикие эксперименты с интерпретатором IronPython. «Железный» питон умел в .NET обёртку над нативной UIAutomationCore.dll – ведь она состояла из кастомных COM-интерфейсов, а win32com.client из библиотеки pywin32 этого не умеет.
Благодаря сообразительным студентам удалось найти пакет comtypes, который смог загрузить оттуда все COM-объекты в родном и привычном CPython, то есть в обычном питоне.
Возвращение в опенсорс
К концу 2014-го стало понятно, что UI Automation API в Интеловском проекте не особо нужен, а нужен он сообществу, просто людям, пытавшимся спрашивать «а можно ли вот это и ещё вот это». Так окончательно созрела мысль, что пора возвращать проект в опенсорс. Сначала это был личный (самый первый) репозиторий на гитхабе под названием pywinauto64. Но вскоре коллеги подсказали, как правильно выкладывать в опенсорс от имени компании, и я прошёл весь бюрократический процесс, включая автоматическую проверку лицензий, в итоге получив разрешение выложить и поддерживать проект на гитхабе, поставив копирайт Интела и узнав много интересного про лицензии и их совместимость. Потом этот опыт пригождался дважды совсем по другим темам.
В общем, в середине 2015-го на гитхабе появилась организация pywinauto и в ней репозиторий с таким же именем: pywinauto/pywinauto.
Продвижение
Первый пост на Reddit про воскрешение проекта сыграл тогда немалую роль в первичном притоке зарубежных юзеров, хотя следующие несколько постов уже не имели такого эффекта.
Затем была первая Хабростатья ещё про старую функциональность, потом вторая про поддержку UI Automation API, обе тоже в какой-то степени помогли продвижению.
В ноябре 2017-го съездил на SQA Days и даже познакомился там с человеком из компании SmartBear (которые делают Test Complete - этот продукт ещё будет упомянут).
Ну, и, конечно, техподдержка в GitHub issues и на StackOverflow (тег pywinauto) – пожалуй, самый важный ключ к росту пользовательской базы. Но об этом позже.
Из коммерческих применений был даже бот-кассир в платёжном шлюзе, через который заказывают еду студенты в одном американском университете.
Кто будет дальше развивать проект?
Логично было продолжить преподавательскую тему, и в 2015-м я решил предложить студентам научное руководство! Правда, кандидатскую я так и не защитил, но в то время быть кандидатом наук было не обязательно, чтобы стать научным руководителем. Сейчас есть workaround под названием «научный консультант». В общем, разрешение от кафедры было получено, и на сентябрьской встрече со студентами свежего третьего курса я рассказал о своей теме и был готов взять двоих. К счастью, откликнулось ровно двое (Александр и Иван), и они первые прошли со мной путь в 4 года сначала до бакалаврского, а затем и до магистерского диплома. В итоге они внесли немалый вклад в проект, как и некоторые сильные студенты позднее.
Примерно в то же время появился и первый опытный внешний контрибьютор Валентин, ещё с 90-х живший за рубежом (сейчас в Новой Зеландии), с которым мы долгое время мейнтейнили проект на пару, делая код-ревью друг другу, студентам и редким пулл реквестам от пробегавших мимо.
2015 и 2016 годы были самыми активными в плане разработки, "uia" backend увидел первый релиз 0.6.0 в октябре 2016-го, а последний релиз 0.6.8 с багфиксами – в октябре 2019-го.
А в июле 2017-го прекрасная Анна нарисовала по дружбе логотип, который с тех пор находится на странице организации pywinauto.

Автоматический рекордер скриптов
Это, пожалуй, первая попытка реализовать мечту: мониторинг действий юзера и запись их в скрипт, чтобы он потом ещё и заработал! И багу случайную воспроизводил. Технологии для этого, казалось, есть, и мы решили попытаться вместе с Александром, одним из лучших моих студентов, взять эту высоту.
И хотя нам удалось разработать прототип, работающий худо-бедно на Блокноте, постепенно вскрылись фундаментальные проблемы у самой задачи, а может, у нашего подхода к ней. Мы запоминали всю текущую иерархию элементов на окне и это было долго! Ну, как долго… секунд 2-3, но юзеру хочется кликать быстрее. Пришлось даже искусственно затуманивать окно, чтобы не давать юзеру ничего кликать, пока мы обновляем snapshot иерархии.
В общем, урок был вынесен такой: задача, замыкающая 20% экосистемы, потребовала 80% усилий. И по правилу Парето эту задачу следует отложить, что мы в итоге и сделали. Хотя Александр, конечно же, защитился на «отлично» и с прототипом.
Сейчас мне видится, что реализация рекордера должна быть на нативном языке типа C/C++ или Rust, но на горизонте эта активность пока не просматривается. Хотя технологии для этого есть на всех основных осях: Windows (и "uia", и "win32"), Linux и mac OS (да-да, правильно писать с маленькой буквы).

Долгий путь к Линуксу
С Иваном мы начали с базовых вещей: клавиатуры и мыши. Был найден python-xlib, X клиент на чистом питоне, разумеется, тоже в полумёртвом состоянии, только под Python 2.7 да ещё и с лицензией GPL, о вирусном эффекте которой я уже знал. Пришлось поднапрячься и уговорить всех шестерых контрибьюторов поменять лицензию на LGPL. На более свободную MIT или BSD 3-clause они не согласились, но и на том спасибо! Кстати, для самого pywinauto лицензию я тоже поменял когда-то: с LGPL на BSD 3-clause, получив даже ответ от Марка МакМэхона, чтоб я брал полный ownership себе (спасибо, Марк!).
Добавить поддержку Python 3.x было делом техники, админские права на PyPI мне оформили. Так и зажили. Кстати, в python-xlib чаще присылают сразу пулл реквесты, чем issues заводят – хорошая линуксовая традиция! Хоть я и виндовозник тот ещё :) силу сообщества ценю.
Минимум зависимостей – наше всё!
Дальше встал вопрос, как юзать AT-SPI, линуксовую технологию доступа к текстам и иерархии элементов. pyatspi2 поддерживается где-то не на гитхабе и имеет ещё какие-то зависимости типа pyGObject и прочего такого – в общем, мрак. Чтобы не связываться, решено было грузить libatspi.so
напрямую, ведь мы уже умеем в ctypes. :) Так и сделали в итоге, оставив лишь зависимость от python-xlib. Правда, за 4 года, к магистерскому диплому Ивана, мы не успели зарелизиться, т.к. хотелось достичь бОльшей стабильности. Следующий студент натыкался разок на подвисание, видимо, из-за багов где-то в поддержке AT-SPI со стороны приложений – оно сбивает механизм таймаута из-за блокировки, и эту проблему так и не решили, кстати. Но в целом, новому бэкенду можно присвоить «бета качество» и отправить в свободное плавание. Об этом чуть позже.
Техническая поддержка, StackOverflow и статистика
Это, пожалуй, один из самых развивающих моментов: общение с юзерами на английском – часто ломаном, иногда с обеих сторон :) Вчитываясь в описания багов, постепенно получил навык понимания оттенков смысла, каких-то устойчивых выражений на английском. Ну, и, конечно, стали понятны проблемы и чаяния тех, кому фич всё ещё не хватает.
Ради спортивного интереса довольно долго вёл статистику по похожим конкурирующим продуктам: это звёзды, число вопросов на StackOverflow по тегу (вместе с процентом отвеченных, потом StackOverflow убрал эту инфу) и по поиску (число упоминаний). Последний snapshot лежит на вики здесь: UI Automation tools ratings. К слову Appium Desktop отправили в архив, интегрировав десктопную функциональность в основной Appium, так что теперь сложнее понять, насколько Appium именно для десктопов более популярен, чем pywinauto.

Конкретно по тегу pywinauto сейчас чуть больше тысячи вопросов, из которых на 370+ ответил я, не считая почти сотни ответов под другими тегами типа ui-automation, куда заходил больше ради продвижения проекта и отвечал на вопросы, где люди не знали, какую либу юзать. Недавно репутация по инерции доползла до 10К, дав доступ к модераторским тулам. Впрочем, прежней активности от меня давно нет, да и вопросы новые стали реже задавать – думаю, это общая тенденция, т.к. находить ответы на старые вопросы помогает ИИ, и новые вопросы стали задавать уже после того, как ИИ не справился.
В общем, насобирать серебряный бейдж по довольно редкому тегу – это для упоротых. :) И я им был :)
По итогу даже родились два новых туториала в документации: Getting Started Guide и Remote Execution Guide. В последний внёс вклад ещё в Интеле мой тогда будущий руководитель Алексей (я был в его команде в 2016-2020): он делился опытом использования VNC Server для запуска GUI тестов на удалённых машинах.
Большой рефакторинг
Насмотревшись на разные сценарии, постепенно появилась в голове картинка, как переделать интерфейс, чтобы он был близок к идеальному и гибок максимально. И на него было потрачено несколько студенто-лет, хотя шло это дело медленно и печально. Я понял, что студентам такие темы давать не стоит. Но и сам чуть совсем не потерял мотивацию, пока это делал.
Хотя в мотивацию, допускаю, вмешался ковид, который я перенес тяжеловато, учитывая ещё и резкий переход на удалёнку, затем внезапную смену работы через полгода, ну и вообще темп жизни ускорился – кому я рассказываю! :)
В общем, рефакторинг тоже чуть-чуть недоделан, в основном в плане backward compatibility, которую частично всё равно придётся сломать, но можно минимизировать.
Поддержка mac OS и стажировка иностранного студента
Один из моих студентов, Артём, оказался маководом и разработчиком под iOS, поэтому он раскапывал технологию доступа к UI элементам на mac OS с 2016-го по 2020-й. Достаточно полная реализация работы с клавиатурой и мышью появилась весной 2019-го, и мы перешли к обходу иерархии UI элементов.
Интересное событие произошло летом 2019-го: ко мне попросился на летнюю стажировку французский студент из Нантского политеха Бриак Ле Гуэн: причём он вышел на меня сам и сказал, что я единственный из преподавателей в Нижнем Новгороде, кто откликнулся. Это был новый опыт, и я пошёл в международный отдел ННГУ, где добрые девушки помогли мне оформить Бриака на официальную стажировку по какой-то программе обмена (если я правильно помню), и ему даже платилась какая-то небольшая стипендия. Отдельно отмечу его пунктуальность: он всегда приходил на встречи заранее и, когда пару раз задерживался, всегда писал об этом минут за 20, а то и раньше. Задача у него была хоть и не самая простая, но сама технология была уже разведана. Плюс в какой-то момент подключился Артём и обнаружил, что в пакете pyObjC в определённой версии есть баг, из-за которого происходило зависание, но в новой версии он, слава богу, был исправлен. Видимо, Артёма мотивировало летом присутствие Бриака. В общем, сплошные плюсы. В итоге Бриак успешно написал базовые классы для запуска приложений и обхода иерархии, что для двух месяцев стажировки было очень даже неплохо. Затем он успешно защитил летнее задание в своём родном университете. Интересная практика, стимулирующая самостоятельность и мобильность.
После Артёма, который дописал немалую часть тестов, с 2020-го по 2022-й Даша делала врапперы для конкретных типов UI элементов. Текущее состояние ветки с поддержкой mac OS я бы оценил как «альфа качество». В данный момент ею никто не занимается. Надеюсь тоже добраться до неё и зарелизить в одном из мажорных релизов типа 0.8.0 или 0.9.0.
DLL injection для кастомных элементов и бэкендов
Кроме доделки старых фич, по-прежнему было интересно открывать новые технологии и начать, наконец, конкурировать с коммерческими инструментами: Test Complete и Squish. Саму идею нам подсказал доклад Владимира Перевалова (из Дойче банка на тот момент) на том же SQA Days 2017 – всё-таки полезно ездить на конференции! Он рассказывал, как делается DLL injection в процесс тестируемого приложения, там же на стороне приложения поднимается .NET framework и просвечиваются все внутренние свойства GUI приложения через .NET рефлексию. И хотя весь код Владимир не мог раскрыть, нам хватило и маленького proof-of-concept: vperevalov/WPFA. Спасибо большое за него!
Это позволяет поддержать даже кастомные коммерческие наборы UI элементов! Это одна из основных оставшихся болей юзеров, не считая поддержки других осей.
Первым из студентов этот путь протоптал Борис (drinkertea), который делал прототип рекордера скриптов для "win32" бэкенда, который требовал Сишной библиотеки на стороне приложения для выставления application-specific хуков на события с UI элементами. Он защитил бакалаврский диплом в 2019-м, а уже позже осенью 2019-го сделал небольшой прототип injected DLL для Qt QML приложений в своей ветке, за что ему отдельное большое спасибо!
Затем уже студент магистратуры Илья (eltimen) сделал очень качественную работу и реализовал целый "wpf" бэкенд фактически в одно лицо за два года (осень 2020 – весна 2022) – это очень круто! Мне до сих пор стыдно, что эта работа ещё не в релизе, т.к. хочется вынести небольшой подпроект pywinauto/injected в отдельный пакет на PyPI, ведь он может быть использован совершенно отдельно от наших задач, а текущая реализация использует Git submodule, что не очень удобно в поддержке.


Текущее состояние проекта и планы
В промежутке между 2022 и 2024 годами я почти не занимался проектом (были другие профессиональные вызовы), а студенты в основном были не очень мотивированные, поэтому почти ничего не двигалось. Однако в зимние каникулы 2025-го получилось заняться воскрешением CI-скриптов с учётом всех изменений вокруг, и ветка "atspi" с примерно 250 коммитами заехала, наконец, в master.
Внешний контрибьютор из Японии Jun Komoda также проявляет активность в comtypes, где он уже мейнтейнер, и pywinauto, за что ему большое спасибо! Он специализируется на типизации и идёт своим путём самурая, а дополнительные глаза на код ревью и множество фиксов приносят пользу проекту. К тому же, есть потенциал, что он поможет собрать сообщество - это то, чего у меня не получилось за эти годы, я больше технарь.
За долгое время в опенсорсе я привык не ставить конкретных дедлайнов по релизам (это не очень хорошо, но как есть). Но незакрытый гештальт мотивирует меня переварить крупные недоделанные фичи. И несмотря на очередную смену работы осенью 2024-го, ритм жизни постепенно приходит к норме (вызовы рассасываются), поэтому смотрю на следующие пару лет с оптимизмом.
Следующий челлендж в моей опенсорсной жизни – это вклад в сам Python. В этом направлении, благодаря работе в период 2022-2024, есть идеи, которыми я поделюсь в докладе на Positive Hack Days 24 мая в Лужниках. Хотя это уже мой третий "ПэХаДэйз", на этот раз мне выпала честь открывать секцию Python Day докладом про строки и их внутренности. Приходите! Надеюсь, будет интересно и в этот раз не слишком жёстко. :-)

Ещё я не написал про зеркалирование pywinauto и его зависимостей на гитхабе с репозиториями на GitVerse, где я уже зареган. Но настройка зеркалирования и его тестирование ещё впереди. :-) Ведь в проекте, кроме issues, есть milestones, вики и куча меток на issues.
Продолжение следует... :-)
=