В феврале этого года я [писал на Хабре](https://habr.com/ru/articles/883590/) про автоматизацию тестов для САПР. Мы делали систему с записью действий в JSON и воспроизведением через pyautogui. Работало. Но только для одного конкретного проекта.

С тех пор фреймворк вырос. Сильно. Из узкоспециализированного решения для промышленного ПО превратился в универсальный инструмент. Теперь работает с чем угодно - офисные пакеты, банковские клиенты, CAD-системы.

Что изменилось? Убрал привязку к конкретному софту. Добавил умный поиск элементов вместо тупых координат. Сделал так, чтобы QA мог записать тест без единой строки кода. Прикрутил UI-ассерты, мониторинг системы, файловые проверки.

Короче, то что начиналось как решение для одной задачи, выросло в полноценный фреймворк. И оказалось полезным не только мне.

Что меня бесило в обычных GUI-тестах

Давайте честно. Если вы хоть раз писали UI-автотесты, вы знаете боль:

  • Координаты - это ад. Поменялось разрешение? Сломалось. Обновили интерфейс? Сломалось. Запустили на другой машине? Сломалось.

  • Селекторы - это тоже ад, но другой. XPath который работал вчера, сегодня не работает. #button-submit-form-container-wrapper-inner - и это реальный ID из продакшена.

  • Thick-клиенты - вообще беда. Офисные пакеты, всякие САПР, банковские клиенты. Selenium тут не поможет. Sikuli - костыль на костыле.

  • Порог входа - чтобы написать автотест, нужно быть программистом. QA-инженерам приходится учить Python/Java, а они хотят просто проверить, что кнопка работает.

Я работал в проекте, где были виртуальные рабочие места с кучей импортозамещённых приложений. Windows-окружение, куча legacy софта, ручной регресс занимал недели. И думал: "Должен же быть способ проще?"

Про культуру (или её отсутствие)

Знаете, что меня удивляло на прошлых местах работы? Никто не делился своими наработками. Серьёзно. У кого-то был крутой скрипт для автоматизации - молчок. Кто-то запилил фреймворк - держит при себе.

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

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

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

С другой стороны - может, и хорошо, что пришлось делать с нуля. Хотя бы теперь хочется делиться открыто. Пусть даже через статьи и публикации в интернете.

Да, весь этот фреймворк я делал один. Не потому что супергерой, а потому что так сложилось. Рекордер, раннер, систему скоринга, мониторинг - всё по частям, в свободное время. Месяца 3-4 месяца ушло. Зато теперь понимаю каждую строчку кода и знаю, где что лежит.

Что я построил (и почему это работает)

Идея простая до безобразия: записал действия мышью и клавиатурой в JSON, потом проиграл обратно.

Но дьявол, как всегда, в деталях.

Вот как это выглядит схематично:

Архитектура в двух словах

Проект состоит из двух частей:

1. Рекордер - записывает ваши действия

Запускаешь скрипт, жмёшь горячую клавишу - началась запись. Кликаешь мышкой, вводишь текст, жмёшь кнопки. Останавливаешь той же клавишей. Сохраняешь другой. Всё.

Фишка в том, что рекордер не просто пишет координаты. Он захватывает метаданные UI-элементов через Windows UI Automation:

{
  "element": {
    "name": "Сохранить",
    "role": "button",
    "bounds": [x, y, width, height]
  },
  "locators": [
    {"type": "id", "value": "<element_id>"},
    {"type": "role+name", "value": ["button", "Сохранить"]},
    {"type": "path", "value": [
      {"role": "window", "name": "<AppMainWindow>"},
      {"role": "toolbar", "name": "<MainToolbar>"},
      {"role": "button", "name": "Сохранить"}
    ]}
  ]
}

Это значит, что при воспроизведении раннер не тупо кликает по координатам. Он ищет элемент через UI Automation - сначала по идентификатору, потом по роли и имени, потом по пути в дереве интерфейса. Координаты - только запасной вариант.

2. Раннер - воспроизводит и проверяет

Раннер берёт JSON-файлы из указанной папки, последовательно их выполняет и собирает отчёт в HTML.

Вот что происходит при запуске теста:

JSON-сценарии - это просто

Вот минимальный пример теста:

{
  "name": "OpenHelpDialog",
  "description": "Открыть меню Справка и проверить диалог",
  "app": "<path_to_app>",
  "window_pattern": ".*MainWindow.*",
  "check_type": "window_exists",
  "steps": [
    {"action": "click", "target": "HelpMenu"},
    {"action": "click", "target": "AboutItem"},
    {"action": "wait", "duration": 0.2}
  ]
}

Читается как документация. Не нужен программист, чтобы понять, что тут происходит.

Почему Windows и Linux?

Windows - потому что там работает 90% нашего софта. UI Automation - родная технология, работает отлично через системную библиотеку.

Linux - потому что хотелось. Сделал базовую поддержку, но без UI Automation там всё хуже. Пока в статусе "работает, но не для прода".

Практические нюансы (где я набил шишки)

Медленные окна и нестабильность UI

Офисные приложения и файловые менеджеры любят "думать". Открываешь окно, а оно ещё 2 секунды дорисовывается. Кликаешь по кнопке, а UI-дерево меняется прямо во время клика.

Решение: таймауты и стабилизация.

  • Перед любыми действиями раннер ждёт, пока окно стабилизируется

  • Для окон с лентой первым действием кликаем главную вкладку - это стабилизирует активную панель

  • Между действиями - небольшая пауза по умолчанию

Звучит как костыль? Да. Но работает.

Неожиданные диалоги

Открываешь файл, а там вылезает диалог "Включить макросы?" или "Обновление доступно". Тест падает, потому что не ожидал этого окна.

Решение: паттерны вместо точных строк в заголовках окон.

"window_pattern": ".*<AppName>.*"

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

Проблемы с фокусом

Windows - странная штука. Иногда окно "как бы" в фокусе, но клики не работают. Или фокус улетает на другой монитор (привет, RDP).

Решение: явная установка фокуса через системную библиотеку автоматизации. Плюс сохраняем идентификатор процесса, чтобы при повторном фокусе точно попасть в нужное окно.

Умный поиск окон

Знаете, что меня удивило? Окна не ищутся по принципу "всё или ничего". Раннер умеет находить "наиболее похожее" окно.

Реальность грязная. Заголовок окна постоянно меняется. То путь к файлу дописывается, то версия приложения. То локализацию поменяли. Если тупо требовать 100% совпадение - тесты падают каждый второй запуск.

Поэтому есть эвристики. Раннер смотрит на несколько признаков окна сразу. Какие-то важнее, какие-то менее. Выбирает окно, которое "больше всего похоже" на то, что записывали.

Пример. Записали тест для "Документ1.txt - Редактор". Открыли "Документ2.txt - Редактор". Тест находит окно. Потому что достаточно признаков совпало - это то же приложение, та же структура заголовка.

Или другое. Окно называлось "Settings", стало "Настройки" после смены языка. Ок, заголовок не совпал. Но другие характеристики окна те же - раннер понимает, что это оно.

Это не магия. Иногда всё равно приходится обновлять метаданные. Но количество "упал непонятно почему" снижается заметно. Тест стал терпимее к мелким изменениям.

Ввод текста - это отдельный квест

Три способа ввести текст:

  1. Через системные события - надёжно, но медленно. Хорошо для системных диалогов.

  2. Через буфер обмена - быстро, но иногда фокус "уплывает" и текст вставляется не туда.

  3. Эмуляция печати - средне по скорости, но ломается на нелатинице и спецсимволах.

Решение: умный режим. Раннер пробует методы по порядку: системные события → буфер → эмуляция. Первый, что сработал - и ок.

{"action": "input", "text": "<some_text>", "method": "auto"}

Что упрощает JSON-структура

  • Версионирование - храним тесты в Git, видим изменения в diff

  • Модульность - один тест = один JSON. Легко комбинировать, переиспользовать

  • Отладка - открыл JSON, увидел шаг, который упал, поправил

  • Расширяемость - добавить новый тип проверки = добавить класс проверки в код раннера

Типы проверок (скриншоты vs UI-ассерты)

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

  • Медленно (скриншоты весят, сравнение тоже не быстрое)

  • Ложные срабатывания (сглаживание шрифтов, рендиринг, тени)

  • Хрупко (поменялась кнопка на 2 пикселя - тест упал)

Потом добавил UI-ассерты. Проверяем не "как выглядит", а "что есть".

Проверка окна

Проверяет, что окно с нужными параметрами существует:

{
  "check_type": "window_exists",
  "window": {
    "class": "<WindowClass>",
    "title_pattern": ".*<AppName>.*"
  }
}

Быстро, стабильно, не зависит от визуальных изменений.

Проверка чекбокса

{
  "check_type": "checkbox_state",
  "expected_state": "checked"
}

Проверка выбранного значения

{
  "check_type": "selected_value",
  "expected": "Опция 1"
}

Бонусы, которые просто работают

Автопроверка закрытых окон

Это мелочь, но приятная. Раннер сам отслеживает, какие окна ты закрывал во время теста. Alt+F4 нажал? Кнопку "Закрыть" кликнул? Запоминает.

После выполнения всех шагов автоматически проверяет - а закрылись ли они на самом деле? Не висят ли где-то в фоне?

Знаете, сколько раз у меня тесты "проходили", а потом оказывалось, что приложение осталось открытым и жрёт память? Теперь это ловится автоматом.

Проверки файлов

Не только UI. Можно проверить файловую систему.

"Этот файл должен появиться после теста". "Этот должен измениться". "А этот - остаться без изменений".

Для тестов типа "Сохранить документ", "Экспортировать отчёт" - незаменимо. Раннер снимает снимок файла до действий (хеш, размер, время модификации), потом проверяет после.

Пример из жизни. Тест "Сохранить как PDF". Кликаем кнопки, вводим путь. Тест проходит. А PDF не создался - диалог закрылся, но сохранение упало молча. Раньше это находили только вручную. Теперь тест сам скажет "файл не появился".

Fallback на координаты

Помните, я говорил про умный поиск через UI Automation? Так вот, если он не сработал - раннер откатывается на координаты.

То есть тест не падает сразу намертво. Сначала пытается найти элемент "правильно", не получилось - кликает туда, куда записали при создании теста.

Да, координаты хрупкие. Да, могут не попасть. Но это лучше, чем сразу упасть с ошибкой "элемент не найден". Иногда это спасает.

Захват состояния без клика

В рекордере есть фича. Нужно зафиксировать состояние чекбокса, но не кликать по нему? Наводишь курсор, жмёшь специальную клавишу - рекордер запоминает состояние элемента под курсором.

Потом раннер проверит: "чекбокс должен быть включен, как записали". Без лишних действий.

Раньше приходилось костылить - кликать, снова кликать, чтобы вернуть в исходное состояние. Теперь просто навёл и зафиксировал.

Для 80% тестов этого достаточно. Скриншоты оставил для случаев, когда нужно проверить внешний вид (дизайн, графики, кастомные контролы).

Мониторинг и отчёты

Бонусом прикрутил мониторинг системы. Во время выполнения тестов раннер собирает:

  • CPU и RAM в реальном времени

  • Топ процессов по потреблению ресурсов

  • Предупреждения, если CPU > 85% или RAM > 85%

Всё это попадает в HTML-отчёт. Удобно, когда тест упал не из-за бага, а потому что машина тормозила.

Отчёты формируются автоматически: таблица с результатами (зелёный/красный), скриншоты при фейлах, дифф-изображения, логи. Открываешь в браузере и сразу видно, что сломалось.

Где это может быть полезно

Я делал это для себя, но понял, что подходит для:

  • Импортозамещение - российские офисные пакеты и операционки. Всё это thick-клиенты, которые плохо автоматизируются стандартными инструментами.

  • Legacy Windows-приложения - банковские клиенты, CAD-системы, SCADA, старые ERP-системы.

  • Виртуальные рабочие места - образы с набором софта. Регресс-тестирование всего образа занимало недели. Теперь - часы.

  • Проекты без программистов - QA-инженеры могут записывать тесты сами, не зная языки программирования.

Бизнес-эффекты (почему это важно компаниям)

Окей, давайте про деньги и время. Потому что начальству обычно нужны цифры, а не "мне нравится этот подход".

Ручной регресс съедает время

На прошлом проекте у нас был образ виртуального рабочего места. Десяток приложений, которые надо проверить перед релизом. Ручная проверка занимала у одного QA примерно 2 недели. Два человека - месяц работы на регресс. Каждый релиз.

С автотестами это сократилось до 3-4 дней. Где-то 60-70% экономии времени. Не идеал, но уже можно дышать. И это реальные цифры, не из презентации для инвесторов.

QA перестают быть узким местом

Знаете, что бесит руководителей проектов? Когда тесты пишет один автоматизатор на всю команду. Он становится бутылочным горлышком. Заболел, ушёл в отпуск, уволился - всё, автотесты стоят.

Тут другая история. QA-инженер записывает тесты сам. Без кода. Нажал кнопки, сохранил JSON, готово. Порог входа низкий. Не нужно учить Python полгода. Это разгружает автоматизаторов и даёт QA больше независимости.

Разработчики тратят меньше времени на поддержку

У нас был случай. Обновили интерфейс - поменяли расположение кнопок. Selenium-тесты полегли пачкой. Автоматизатор две недели чинил XPath и CSS-селекторы. Две недели!

С этим фреймворком быстрее. Элементы ищутся через UI Automation - по роли, имени, идентификатору. Если кнопка "Сохранить" переехала на другое место, но осталась кнопкой "Сохранить" - тест найдёт её. Иногда вообще ничего править не нужно. Иногда - минут 10 на пачку тестов.

Thick-клиенты больше не "чёрная дыра"

Раньше толстые приложения либо не тестировали автоматически вообще, либо пытались костылями типа Sikuli. Скриншоты, поиск по картинкам - это всё работает через раз и ломается от малейших изменений.

Здесь автоматизация через системный UI Automation. Тот же подход, что в Selenium, но для десктопа. Это открывает автоматизацию для целого пласта приложений, которые раньше были "вне зоны доступа". Офисные пакеты, CAD, банковские клиенты, всякие SCADA.

Релизы идут быстрее

Простая математика. Раньше: регресс 2 недели → блокирует релиз → релиз раз в месяц, если повезёт. Теперь: регресс 3 дня → можно выпускать каждую неделю. Или чаще.

На практике видел ускорение в 2-3 раза точно. Где-то доходило до 5 раз, но там много факторов. Зависит от того, сколько у вас вообще ручных проверок было.

Меньше "флакающих" тестов

Знаете эту боль? Тест то падает, то проходит. Без причины. Проверили вручную - всё работает. Перезапустили тест - прошёл. Это выматывает. Команда перестаёт доверять автотестам.

Тут стабильнее, потому что поиск через UI Automation менее хрупкий, чем клики по координатам или по сложным XPath. Не идеально, конечно. Бывают зависания окон, медленные приложения. Но в целом процент ложных падений ниже.

Импортозамещение и кроссплатформенность

Если у вас проект с российским софтом или переход на него - это больная тема. Многие инструменты заточены под западные продукты. А тут можно тестировать отечественные офисные пакеты, операционки, что угодно. Работает на Windows, есть базовая поддержка Linux.

Для компаний, которые строят решения на импортозамещении - это не просто удобно, это критично. Потому что альтернатив мало.

VDI и виртуальные рабочие места

Автотесты можно гонять прямо внутри виртуальных машин и VDI-сред. Проверили образ, зафиксировали результат. Не нужно разворачивать тестовые стенды на физических машинах. Всё внутри виртуалки.

Для компаний с большими парками виртуальных рабочих мест (банки, госсектор, корпорации) - это экономия инфраструктуры и времени на подготовку окружения.

Итого: считаем выгоду

Давайте грубо прикинем. Команда из 10 QA. Половина времени - ручной регресс. Средняя зарплата - ну, сами знаете. Экономия 50-70% времени на регресс = можно либо меньше людей нанимать, либо те же люди делают больше полезной работы (исследовательское тестирование, улучшение процессов).

Плюс ускорение релизов. Быстрее релизы - быстрее фичи до пользователей - быстрее обратная связь. Это уже влияет на продукт.

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

Всё это конвертируется в деньги. Не сразу, но конвертируется.

Демо: как это выглядит

Запись теста

python recorder.py
  • Горячая клавиша 1 - старт/стоп записи

  • Горячая клавиша 2 - вставить паузу

  • Горячая клавиша 3 - пометить момент для финальной проверки (захватит метаданные окна)

  • Горячая клавиша 4 - захватить метаданные под курсором без клика (для чекбоксов, списков)

  • Горячая клавиша 5 - завершить и сохранить

Файл сохраняется в JSON, можно сразу запускать.

Воспроизведение

python runner.py

Раннер собирает все JSON из указанной папки, выполняет по порядку, формирует отчёт.

Пример логов

[INFO] Выполняется тест: test_file_manager.json
[INFO] Запуск приложения: <app_path>
[INFO] Окно найдено: <WindowTitle>
[INFO] Выполнение действий: 2 шага
[INFO] Шаг 1/2: клик по координатам [x, y]
[INFO] Шаг 2/2: клик по координатам [x, y]
[INFO] Проверка: window_exists -> PASS
[SUCCESS] Тест пройден: test_file_manager.json

Всё прозрачно. Если упало - видно на каком шаге.

Что можно улучшить (и куда расти)

Сейчас фреймворк работает, но есть идеи, как сделать его умнее.

Вот как AI может помочь:

AI-автогенерация тестов

Представьте: запускаете приложение, говорите "создай тест, который откроет файл и сохранит его". AI анализирует UI-дерево, находит нужные элементы, генерирует JSON. Уже экспериментирую с мультимодальными моделями для распознавания интерфейсов.

Самовосстановление при изменении интерфейса

Тест упал, потому что кнопка переехала? AI может попробовать найти её по похожему имени или роли, обновить метаданные и продолжить. Это снизит количество ложных падений.

Работа с "чёрными ящиками"

Есть приложения, которые рендерят UI в Canvas или Direct3D. UI Automation их не видит. Тут нужны OCR + компьютерное зрение. Можно натравить модель на скриншот и спросить: "где тут кнопка Сохранить?" Получить координаты, кликнуть.

Уменьшение ручного регресса

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

Выводы (или почему это не серебряная пуля)

Я не хочу продавать вам идею, что это решает все проблемы. Нет.

Минусы:

  • Всё ещё зависит от координат в крайних случаях (когда UI Automation не помогает)

  • Windows-центричность (Linux-поддержка сырая)

  • Не подходит для веб-приложений (там Selenium/Playwright лучше)

  • Нужно записывать тесты вручную (хотя это быстрее, чем писать код)

Плюсы:

  • Низкий порог входа (записал, проиграл, готово)

  • Работает с thick-клиентами, где ничего другое не работает

  • JSON понятен не-программистам

  • Стабильнее, чем клики по координатам (благодаря UI Automation)

Это не замена Selenium. Это инструмент для другой задачи - автоматизации десктопных приложений, которые игнорируются в мире автотестов.

Комментарии (4)


  1. Alex-ZiX
    15.11.2025 13:44

    Статья отличная, но я не понял - решение открытое, его можно попробовать у себя?


    1. Babaji Автор
      15.11.2025 13:44

      Благодарю за отзыв!

      Полностью открытым решение быть не может оно включает внутренние компоненты и интеграции, завязанные на корпоративную инфраструктуру, CI и окружение. Однако концепция остаётся универсальной, и её можно воспроизвести в облегчённом виде на любой машине:

      запись действий + UI-метаданных,

      сценарии в виде JSON,

      раннер, который воспроизводит шаги и выполняет проверки.

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


  1. viordash
    15.11.2025 13:44

    А можете рассказать про аналог UI Automation в линуксе? Интересно как там это возможно.

    Насколько надежные тесты получаются? Можете ли вы показать видео с записью и воспроизведением? Есть ли визуализация фиксируемого компонента при записи скрипта? Есть ли возможность вкладывать один скрипт в другой, чтобы например одинаковую последовательность не записывать каждый раз?

    Когда-то я делал подобный проект, но только под windows. До конца так и не довел, не смог добиться 100% стабильности определения элементов.


  1. Babaji Автор
    15.11.2025 13:44

    Спасибо за вопросы - отвечу кратко и в рамках того, что можно описывать публично.

    1. Аналог UI Automation в Linux
      Прямого аналога Windows UIA нет.
      Есть AT-SPI, но поддержка фрагментирована и зависит от тулкита (GTK/Qt). Поэтому в Linux работает только облегчённый режим: базовые операции доступны, но без полноценного дерева UI. Это скорее вспомогательная возможность, а не основная платформа.

    2. Надёжность тестов
      На Windows тесты получаются устойчивые именно за счёт многослойного поиска элементов и использования нескольких источников метаданных.
      Мы не стремимся к 100% стабильности - она недостижима в принципе - но достигаем высокой повторяемости при реальных сценариях.

    3. Продемонстрировать запись/воспроизведение
      Рабочие записи привязаны к корпоративным приложениям, поэтому показывать их нельзя.
      Думаю над подготовкой краткого публичного демо на нейтральном приложении.

    4. Визуализация фиксируемых элементов
      Визуализация есть, но она утилитарная: отображает структуру метаданных элемента и путь в UI-дереве.
      Это внутренний технический инструмент, он не предназначен для широкого использования.

    5. Вкладывание скриптов
      Да, есть механизм компоновки сценариев, чтобы переиспользовать повторяющиеся последовательности действий. Это позволяет сократить объём тестов и уменьшить рутинные правки при изменениях UI.

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