Skip to main content
Glama

AutoPlayQA

Фреймворк автоматизации QA-тестирования Android-игр — детерминированный движок задач «глаза (восприятие) + руки (действия)» с гейтингом по распознаванию. Мозг отдаётся внешнему ИИ-агенту (Claude Code / Codex через MCP или CLI), сам проект не вызывает ни одной LLM — нулевая стоимость токенов.

Фреймворк не привязан ни к одной игре: систему тегов сцен, JSON задач, шаблонные изображения и YOLO-модели предоставляет сторона интеграции (проект, в котором находится тестируемая игра); этот репозиторий отвечает только за общую часть — канал восприятия, бэкенд действий, движок задач с гейтингом по распознаванию, судебно-техническую фиксацию QA и интерфейсы MCP / CLI.

Python Platform Device MCP LLM

Содержание

Related MCP server: scrcpy-mcp

Возможности

Восприятие и действия (детерминированные глаза и руки)

  • 📱 Автоматическое распознавание подключённых Android-эмуляторов и реальных устройств (ADB); поддержка беспроводного adb: connect / pair (Android 11+) / tcpip — переключение в один клик, автоподключение при запуске из списка config

  • 🤖 Одно устройство — один агент, пул агентов управляет несколькими устройствами

  • 👁️ Бесплатный двухканальный поиск на экране: сопоставление элементов из uiautomator dump → локальный OCR (rapidocr, работает и для игр с отрисовкой в единый Surface)

  • 🧩 Сопоставление шаблонов OpenCV: распознаёт графику, невидимую для текстового канала (игровые здания / иконки и прочие чистые спрайты) — многошкальное сканирование + маска альфа-канала + NMS для нескольких экземпляров; capture_template для сбора → find_template для поиска / в задачах — гейтинг по распознаванию template для кликов

  • 🧷 Сопоставление признаков ORB: «брат» шаблонного сопоставления, устойчивый к деформации — описывает локальные ключевые точки, а не попиксельную корреляцию, выдерживает небольшие изменения версий / масштабирование и поворот / частичное перекрытие; в задачах — гейтинг по распознаванию feature. Подходит только для якорей с богатой текстурой (у плоских одноцветных иконок не извлекаются ключевые точки — для них по-прежнему template)

  • 🔎 Обнаружение объектов YOLO (опционально, инференс через onnxruntime, без PyTorch): обученная модель находит + классифицирует объекты в кадре, устойчива к смещению, масштабированию и перекрытию (слабое место сопоставления шаблонов); модель обучает и предоставляет сторона интеграции — положите .onnx в task/models/ и она включится, detect_objects для детекции / в задачах — гейтинг по распознаванию yolo, при отсутствии модели автоматически лениво уступает место; версия модели записана в task/models/models.json (имя файла → version / date / notes / classes / training_ref), list_yolo_classes заодно сообщает версию агенту

  • 🧭 Классификация сцен (scene): на весь экран отвечает «на каком я сейчас экране» — для подтверждения позиции после сбоя и проверки веток исключений (не возвращает координаты, не используется для якорного позиционирования). Во фреймворке встроен только один тег blank (почти чёрный / погасший экран / пустой кадр), плюс не-сценовые сигналы other_app и никогда не угадываемый unknown; остальные теги регистрирует сторона интеграции через register_scene_probe(label, fn, *, description=..., order=...) (также есть unregister_scene_probe / clear_scene_probes / registered_scene_probes). expected сопоставляется по префиксу с точкой ("popup" попадает в popup.error, "menu" — в menu.settings); MCP classify_scene возвращает текущую taxonomy

  • 🏷️ Размеченное изображение Set-of-Marks: скриншот с наложенными номерными бейджами (красный = кликабельный элемент, синий = чистый текст), агент кликает по номеру (click_index), не угадывая координаты

  • 🎯 Клик / перетаскивание / ввод текста / нажатие клавиш / ожидание

  • Мультитач-жесты без root: app_process поднимает лёгкий dex-хелпер, использующий скрытый системный injectInputEvent (тот же путь привилегий, что и input) — без root, без записи в /dev/input (в современных MIUI / HyperOS shell-домен закрыт SELinux) можно инжектить мультитач MotionEvent; действие gesture принимает последовательность кадров frames или удобный параметр pinch — решает проблему динамических жестов: масштабирование двумя пальцами / поворот / перетаскивание двумя пальцами (dex не входит в репозиторий, собирается из проверяемого исходного кода по injector/build.ps1)

Схема разметки Set-of-Marks (синтетический экран, не реальный игровой скриншот): screenshot_marked помечает кликабельные элементы красными номерами, чистый текст — синими и возвращает таблицу индексов; агент, приняв эстафету, кликает по номеру click_index(N), не угадывая координаты.

Движок задач (детерминированное воспроизведение)

  • 🔁 Движок задач с гейтингом по распознаванию: конечный автомат задач JSON (распознавание подтверждает достижение ожидаемого экрана, только затем выполняется действие), поддержка ветвлений, восстановления по таймауту, продолжения с точки останова — один прогон, воспроизведение с нулевой стоимостью токенов

  • 🧩 Задачи компонуемы: includes — общие файлы узлов (обработка типовых всплывающих окон пишется один раз и используется везде) + custom — внутрипроцессные детерминированные действия (встроенные swipe_until — свайп до цели, launch_app — холодный запуск, gm_command — отправка GM-команд и т.д.)

  • 🗂️ Кэш якорей воспроизведения: OCR сначала проверяет кэш ROI, затем откатывается к полному экрану для ускорения; смещение якоря сообщается как finding anchor_drift, а не молча самолечится

  • ⏭️ Пропуск после сообщения (bug-skip): при обнаружении бага (срабатывание watchdog / crash·ANR в logcat) можно зафиксировать улики и перейти к узлу восстановления, не прерывая тест; чистые зависания / таймауты никогда не вызывают переход (это работа on_timeout) — только сообщённые баги, а не зависания, меняют маршрут

  • 🧹 Белый список безобидных всплывающих окон: поле popups в задаче явно перечисляет известные безобидные всплывающие окна (пользовательское соглашение / внутриигровые предупреждения и т.п. ожидаемый шум) — при зависании распознавания они автоматически устраняются и не фиксируются как finding; не перечисленные окна по-прежнему приводят к таймауту / обнаружению watchdog — различает «шум» и «аномалию», не глотает баги молча

  • 🔙 Запасной вариант BACK: если после очистки белого списка всё ещё зависло (неизвестное всплывающее окно на весь экран) — сначала фиксирует finding на этом кадре как улику, затем один раз нажимает BACK и, подтвердив пиксельной разницей, что экран действительно изменился, даёт ещё один раунд распознавания; если у узла есть on_timeout, уступает написанной автором ветке восстановления и никогда не переходит (переход — работа bug-skip)

  • 🩺 Здоровье якорей: каждый раунд статистика источников попадания узла (прямое попадание / восстановление по таймауту / помощь всплывающего окна / запасной вариант BACK / дрейф) → node_stats; узлы, которые проходят только за счёт запасных вариантов, помечаются anchor_rot_suspect (гниение якоря задачи, а не баг игры); CLI task health агрегирует тренды между прогонами, task lint перед сохранением проверяет хрупкие конструкции (W001-W007)

  • 🔗 Непрерывный прогон набора (suite): несколько кейсов в одном JSON набора (cases + обязательные resume_after/case_entry/landing, без значений по умолчанию), общий холодный запуск + вход в систему, каждый кейс — отдельный прогон, отдельный каталог findings; при сбое кейса — по on_case_failure перезапуск / пропуск / прерывание

QA-фиксация (аномалия = тестовое открытие)

  • 🔬 Три типа триггеров (findings записываются всегда, не зависят от debug-переключателя): watchdogs на уровне задачи — негативные утверждения (запрещённый текст / белый экран не должен появляться), поле finding узла (самоотчёт всплывающих окон / веток ошибок), мониторинг logcat crash·ANR — при попадании фиксируется одна находка

  • 🛫 Триггер — сразу улики: скриншот на месте (кадр ошибки) + ui_dump при сбое, плюс «чёрный ящик» — контекст за ~60 с до проблемы: фрагмент logcat · временная линия процесса · ролик экрана устройства (настоящий MP4)

  • 📦 Доставка результатов: результат прогона содержит findings (даже при успехе задачи), весь каталог улик можно экспортировать (скриншоты + логи + видео + report.json, самодостаточные относительные пути)

  • 📄 Человекочитаемый отчёт: те же данные рендерятся в report.html — один файл без внешних ссылок, открывается двойным кликом офлайн, не ломается при пересылке по почте; скриншоты встроены <img>, видео — <video>, logcat и временная линия процесса свёрнуты — для коллег по QA, которые не читают JSON

  • 🧾 Сохранение улик: outputs/findings/<дата>/<устройство>/<run_id>/ — самодостаточно и просматриваемо; при запуске очищаются каталоги дат старше findings.retention_days (по умолчанию 14 дней); при наличии findings.export_dir прогоны с findings автоматически упаковываются в один zip (время_задача_устройство_статус.zip)

  • 🛰️ Страж в «пустом окне»: после завершения задачи / при передаче между agent-ами прогон движка уже закрыт, экран и logcat никто не контролирует — фоновый мониторинг кадров ставит стража, который использует уже существующие кадры (без дополнительных скриншотов и лишних adb-запросов) и продолжает проверять зависание на белом экране (N подряд кадров со stddev серого ниже порога = один эпизод, сообщается один раз, после восстановления перевооружается) и crash / ANR; при попадании записывается обычный прогон findings (имя задачи monitor_sentinel), к уликам добавляется без потерь оригинальный кадр. Гейтинг по устройству: пока движок работает на устройстве A, страж на устройстве B продолжает следить

  • 📣 Пуш результатов: после завершения без присмотра не нужно идти в каталог — при наличии findings.notifiers (кастомный робот Feishu / универсальный webhook) каждый прогон отправляет одно резюме на китайском (задача / устройство / статус / количество по уровням / первые 3 findings / пути к отчёту и пакету улик); фильтры min_findings, on_status — чистый прогон по умолчанию не шумит, сбой отправки только пишется в лог и никогда не влияет на результат

Схема структуры офлайн-отчёта report.html (синтетический экран, не реальный игровой скриншот): одна finding = скриншот-улика кадра с ошибкой + поля + встроенное видео + сворачиваемые фрагменты logcat и временная линия процесса; один файл без внешних ссылок, открывается офлайн двойным кликом, не ломается при пересылке.

Производительность и интеграция

  • ⚡ Бэкенд скриншотов на потоке кадров scrcpy (по умолчанию): постоянный поток H.264, локальное декодирование ~13 мс/кадр, запасной вариант screencap (при любой ошибке автоматический откат, при повторных ошибках — отключение); если нужны точные пиксели или scrcpy недоступен — capture.backend: screencap

  • 🔌 MCP-сервер: Claude Code / Codex — подключи и работай

  • 📝 Несколько способов создания задач: рукописный JSON / разведка агентом на реальном устройстве / запись по наблюдению (пользователь демонстрирует вручную, агент мониторит и генерирует) / черновик записи CLI-сессии

  • 🎨 Визуальная оркестрация: веб-редактор на холсте в pipeline_editor/ (проверка истинности + lint, извлечение ROI/шаблонов из скриншотов, подсветка реального прогона, совместная работа с агентом через встроенный MCP в реальном времени) — см. Визуальная оркестрация: PipelineEditor

Быстрый старт

Окружение

# Python 3.11 环境(conda / venv 均可)
conda create -n autoplayqa python=3.11 -y
conda activate autoplayqa
pip install -r requirements.txt

# adb 需在 PATH(Android SDK platform-tools 默认安装位置)
$env:PATH = "$env:LOCALAPPDATA\Android\Sdk\platform-tools;$env:PATH"

Способ 1: MCP (рекомендуется, Claude Code / Codex как мозг)

Скопируйте .mcp.json.example в .mcp.json, замените command на абсолютный путь к интерпретатору Python вашего окружения; после этого запустите Claude Code в каталоге проекта — сервер autoplayqa будет обнаружен автоматически.

В шаблоне также есть второй pipeline-editor (http, http://127.0.0.1:8930/mcp): встроенный MCP-интерфейс редактирования в бэкенде визуального редактора PipelineEditor — доступен только после запуска редактора, если он не запущен, агент автоматически использует только stdio autoplayqa (инструменты редактирования имеют те же имена и семантику, просто пользователь не видит живой холст). Разделение обязанностей см. в docs/MCP_INTEGRATION.md.

Codex CLI — добавьте в ~/.codex/config.toml (пути настройте под свою машину):

[mcp_servers.autoplayqa]
command = "C:\\path\\to\\python.exe"
args = ["C:\\path\\to\\autoplayqa\\mcp_server.py"]

Затем просто скажите агенту: «Подключи устройство, открой настройки и установи яркость на 50%, по завершении сохрани как задачу».

Список инструментов MCP

Категория

Инструменты

Устройства

list_devices, connect_device / disconnect_device / enable_wireless / pair_device (беспроводной adb)

Восприятие

screenshot (возвращает путь к PNG, можно сразу посмотреть), screenshot_marked (размеченное изображение Set-of-Marks, в сочетании с click_index для клика по номеру) — оба возвращают изображение с нормализацией короткой стороны до 720p для экономии токенов, full_resolution=true — оригинал (распознавание и координаты таблицы элементов всегда в исходных пикселях устройства), ui_dump, find_text (dump→OCR бесплатное позиционирование), ocr, find_template (сопоставление шаблонов OpenCV для иконок/спрайтов) / capture_template (вырезать экран и сохранить шаблон) / list_templates, detect_objects (YOLO-детекция+классификация) / list_yolo_classes, classify_scene (классификация всего экрана, возвращает текущую taxonomy — теги регистрирует сторона интеграции)

Действия

click / click_index (клик по N-му элементу из последнего screenshot_marked), swipe, input_text, press_key

Запись

calibrate_touch (калибровка сенсорной панели → пиксели дисплея), record_gestures_start / record_gestures_stop (запись жестов реальными пальцами через getevent, результат в outputs/recordings/<timestamp>/), record_actions_start / record_actions_stop (журнал действий агента: самостоятельное исследование / архив при передаче)

Мониторинг

start_monitor / get_new_frames / stop_monitor (фоновый сбор кадров с интервалом на диск, агент по курсору инкрементально забирает пути и сам выбирает кадры для чтения; sentinel=true по умолчанию включает стража, все три инструмента возвращают статистику sentinel, stop_monitor прилагает путь к отчёту стража за этот раунд)

Задачи

get_task_schema, list_tasks, get_task_steps / _step_outline навигацией по номерам шагов), save_task (возвращает lint_warnings), run_task (синхронно блокирует, возвращает по завершении; export_to — экспорт findings по факту), start_task / get_run_status (фоновый запуск длинных задач + опрос прогресса), list_suites / run_suite (непрерывный прогон набора: один вход в систему, несколько кейсов, фоновый запуск, опрос через get_run_status, включая прогресс кейсов), validate_task / lint_saved_task (проверка без сохранения / проверка сохранённой задачи), get_step_labels / list_includes / list_custom_actions (маппинг номеров шагов / общие фрагменты узлов / зарегистрированные custom-действия), clear_replay_cache

Способ 2: локальный CLI

python main.py       # 交互式 CLI(确保 adb 已在 PATH)

Взгляд на интерактивную CLI-сессию (вывод — схема, не реальная запись).

Команда

Описание

device list / agent list / agent select <i|id|all>

Управление устройствами и агентами

device connect <ip[:port]> / device disconnect [addr] / device tcpip <id> / device pair <addr> <code>

Беспроводное adb-подключение

click <x> <y> / drag <x1> <y1> <x2> <y2> [ms] / input <text>

Прямые действия

action "<команда>" или ввод на естественном языке

Локальный разбор: явные координаты через regex → dump/OCR-поиск текста (например, «нажми кнопку настроек»)

task list / task show <name> / task run <name>

Управление и запуск задач (show сначала печатает план потока, отсортированный по номерам шагов, затем исходный текст)

task suites / task suite <name> [device]

Последовательный прогон наборов: один вход в систему, затем несколько сценариев подряд; при падении — перезапуск по стратегии

task resume <name> <node>

Передача от агента: продолжение выполнения с указанного шага

task renumber <name>

Пересчёт номеров шагов по текущему графу и запись обратно в файл

task lint <name>

Проверка надёжности задачи (W001-W007, только предупреждения, не блокирует)

task health [name] [--days N]

Агрегация node_stats по запускам, отслеживание тенденций деградации якорей

task handoffs [name] [--days N]

Агрегация журналов действий передачи агенту, подсказка, какие узлы передачи можно зафиксировать как детерминированные

task cache status / task cache clear

Просмотр / очистка кэша якорей воспроизведения

record on/off/status + task save <name>

Запись сессии → черновик задачи для воспроизведения

record gestures start/stop/status [device]

Запись жестов реальными пальцами через getevent (мультитач / долгое нажатие / сегментированные свайпы + калибровка касаний)

debug on/off

Отладка с сохранением на диск (аннотированные скриншоты, кандидаты распознавания, trace)

Движок задач (ядро)

Задача — это конечный автомат с распознаванием-гейтом (task/task_definitions/*.json): каждый узел сначала распознаёт (ui_text сопоставление элементов управления / ocr / template сопоставление иконок / feature ORB-признаки / yolo обнаружение объектов / scene распознавание всего экрана / blank_screen / always), подтверждает интерфейс, только затем выполняет действие, после чего опрашивает список кандидатов next для перехода — кто первым распознан, тот и срабатывает (естественная поддержка ветвлений по всплывающим окнам); при таймауте переход по узлу восстановления on_timeout.

flowchart TD
    R["识别当前节点锚点<br/>ui_text / ocr / template / feature / yolo"] -->|命中| ACT["执行动作<br/>click · swipe · custom · agent"]
    ACT --> NEXT{"轮询 next 候选<br/>谁先识别命中走谁"}
    NEXT -->|命中某候选| R
    R -->|迟迟不命中| POP["popups 良性弹窗白名单清扫<br/>(不记 finding)"]
    POP -->|仍卡住| BK["BACK 兜底<br/>先记 finding 留证再按 BACK"]
    BK -->|还不行| TO["on_timeout 恢复节点"]
    ACT -->|agent 动作| HALT["挂起 agent_required<br/>外部智能体接手 → start_after 续跑"]
    WD["watchdog 断言 / logcat 崩溃·ANR"] -->|"命中:记 finding + 截图留证"| SKIP["bug-skip:skip_to / on_finding<br/>跳恢复节点继续测"]
{
  "entry": "点击设置",
  "nodes": {
    "点击设置": {
      "step": "1",
      "recognition": {"type": "ui_text", "expected": "设置"},
      "action": {"type": "click", "target": "recognized"},
      "next": ["进入显示设置"]
    },
    "进入显示设置": {
      "step": "2",
      "recognition": {"type": "ui_text", "expected": "显示"},
      "action": {"type": "click", "target": "recognized"},
      "next": [],
      "on_timeout": "点击设置"
    }
  }
}

Шаги, требующие интеллектуального решения, используют действие agent: движок приостанавливается и возвращает status=agent_required + текст инструкции, агент выполняет этот шаг с помощью инструментов устройства, затем продолжает через run_task(start_after=<узел>). Полный формат см. в get_task_schema или action/action_schema.py, диаграмму последовательности передачи туда-обратно см. в docs/MCP_INTEGRATION.md.

Номер шага (step): узел может иметь только для чтения поле step — номер шага, обозначающий порядок выполнения — основная ветка (от entry по next[0]) — целые числа 1, 2, 3…, резервные ветки (on_timeout / next[1:]) помечаются номерами с точкой, например 2.1, 2.1.1, недостижимые узлы — ?. Задача — это граф, а не список; просто читая JSON сверху вниз, порядок выполнения не виден. Номер шага позволяет человеку/агенту с одного взгляда определить позицию узла в потоке. Движок его не читает — чисто для навигации. Номер шага вычисляется из графа в реальном времени: CLI task renumber <name> пересчитывает по текущему графу и записывает step обратно в файл (в начало узла), task show <name> сначала печатает план потока, отсортированный по номерам шагов, затем исходный текст; MCP get_task дополнительно возвращает _steps (отображение имя→номер шага) + _step_outline (список потока). После редактирования задачи повторный запуск renumber обновляет номера, не оставляя устаревших.

Фоновое выполнение (долгие задачи): run_task синхронно блокирует, возвращается только после завершения, без прогресса в процессе — для долгих задач / полного смоук-прогона это плохой опыт. Вместо этого используйте start_task — сразу получаете run_id, затем опрашиваете get_run_status(run_id): возвращает status (running / agent_required / done / error) + current_node + steps + elapsed_s, в конечном состоянии — полный результат, изоморфный run_task (steps / findings / report / handoff). Движок — синглтон, в один момент времени допускается только один фоновый run; при agent_required выполните шаг по result.handoff, затем продолжите через start_task(start_after=<узел>).

Последовательный прогон наборов (suite): несколько сценариев используют один общий холодный старт + вход в систему подряд (task/task_definitions/suites/*.json; MCP list_suites / run_suite, CLI task suites / task suite <name>). JSON набора объявляет список cases + resume_after (узел, с которого продолжить, пропустив вступление) / case_entry (точка входа в тело сценария) / landing (спецификация распознавания для проверки целевого экрана между двумя сценариями) — все три поля обязательны, значений по умолчанию в фреймворке нет, validate_suite проверяет перед запуском, при отсутствии поля — сразу ошибка. Первый сценарий — полный холодный старт, последующие используют resume_after для пропуска повторного входа, но каждый сценарий — отдельный run (отдельная директория findings / report.json); при падении сценария (зависание / краш / несовпадение целевого экрана) обработка по on_case_failure: restart_retry (по умолчанию, холодный перезапуск и повтор) / restart_continue (без повтора, следующий сценарий как обычно с холодного старта) / abort (прекращение набора, оставшиеся сценарии помечаются как пропущенные).

{
  "name": "smoke_mini",
  "cases": ["chat_smoke", "main_smoke"],
  "resume_after": "主场景确认",
  "case_entry": "用例开始",
  "landing": {"type": "ocr", "expected": "主界面", "roi": [0, 2280, 1080, 2448]}
}

Общие узлы (includes): общие узлы, такие как обработка всплывающих окон, размещаются в task/task_definitions/common/*.json (только "nodes", одноуровневые ссылки), в задаче подключаются через "includes": ["common/popups.json"]. Межфайловые ссылки next/on_timeout проверяются целиком на объединённой таблице узлов, запуск только при полном прохождении (атомарная загрузка). Дублирующиеся имена узлов по умолчанию — ошибка (strict), при "on_conflict": "overwrite" загружающий перезаписывает — главный файл объединяется последним, поэтому задача может специализировать общие узлы. При сохранении задачи ссылки сохраняются, обновление include-файла вступает в силу при следующем запуске ссылающейся задачи.

Детерминированные сложные шаги (custom): многошаговая детерминированная логика между одиночным атомарным adb-действием и приостановкой агента (не требует интеллектуального решения), пишется как Python-обработчик, регистрируется через @register("имя") в task/custom_actions/, в задаче ссылается через {"type": "custom", "name": "swipe_until", "params": {...}}, при загрузке проверяется регистрация. Встроенные: swipe_until (повторные свайпы до совпадения распознавания, поиск цели в списках / на прокручиваемых страницах), launch_app (пробуждение экрана + запуск приложения, холодный старт), gm_command (отправка команд через GM-панель, автоматическая обработка метода ввода), ensure_checkbox (переключение в целевое состояние), set_text_field (очистка и ввод). Новый task/custom_actions/<модуль>.py автоматически обнаруживается и регистрируется пакетом (pkgutil сканирует по имени для import), ручная правка __init__.py не нужна; при ошибке import модуля — немедленное исключение (fail-fast), без тихого превращения в «не зарегистрировано» во время выполнения.

Пропуск после репорта (bug-skip): после обнаружения и репорта бага можно не прерывать, а перейти к указанному узлу восстановления и продолжить тестирование. Двухуровневая аннотация — skip_to в watchdog (срабатывание — переход, наивысший приоритет, перекрывает fail_task) + задача-уровень on_finding (глобальный запасной целевой узел, также покрывает баги без watchdog, такие как краши logcat / ANR). Ключевые ограничения: переход только при «записи нового finding» (один watchdog максимум один переход за раунд, дедупликация через множество seen), и источник срабатывания — только watchdog + краши logcat / ANR — чистое распознавание-таймаут (зависание, без бага) никогда не переходит, по-прежнему идёт по on_timeout. При загрузке проверяется существование узлов, на которые ссылаются skip_to / on_finding.

В конкретной задаче это выглядит так (приведённый выше основной пример — чистый поток, эти поля там не встречались — задача-уровень watchdogs / on_finding, узел-уровень finding):

{
  "entry": "开始战斗",
  "on_finding": "回到主界面",
  "watchdogs": [
    {"type": "ocr", "expected": "网络错误", "skip_to": "回到主界面", "message": "战斗中弹出网络错误"},
    {"type": "blank_screen", "fail_task": true, "message": "黑屏卡死"}
  ],
  "nodes": {
    "开始战斗": {
      "recognition": {"type": "ui_text", "expected": "开始"},
      "action": {"type": "click", "target": "recognized"},
      "next": ["结算页", "战斗失败弹窗"]
    },
    "结算页": {
      "recognition": {"type": "ocr", "expected": "胜利"},
      "action": {"type": "none"},
      "next": []
    },
    "战斗失败弹窗": {
      "recognition": {"type": "ocr", "expected": "战斗失败"},
      "finding": {"severity": "warning", "message": "战斗失败弹窗(异常分支,自我上报)"},
      "action": {"type": "click", "target": "recognized"},
      "next": ["回到主界面"]
    },
    "回到主界面": {
      "recognition": {"type": "ui_text", "expected": "主界面"},
      "action": {"type": "none"},
      "next": []
    }
  }
}
  • watchdogs[0].skip_to: в бою OCR распознал «сетевая ошибка» → запись finding, затем переход на вернуться на главный экран, продолжение теста;

  • watchdogs[1].fail_task: чёрный экран → сразу провал задачи (без skip_to для него);

  • задача-уровень on_finding: краши logcat / ANR — баги без соответствующего watchdog, единый запасной переход на вернуться на главный экран;

  • узел окно_поражения_в_бою.finding: аномальная ветка при входе сама себя репортит, не полагаясь на watchdog.

Белый список безвредных всплывающих окон (popups): поле popups задачи явно перечисляет известные безвредные всплывающие окна (пользовательское соглашение, внутриигровые предупреждения и прочий ожидаемый шум) и их распознавание + действие устранения (только click / key / gesture). Сканирование и устранение только при зависании распознавания, finding не записывается (скриншоты — узкое место производительности, поэтому без дополнительных затрат на каждом шаге); всплывающие окна, не внесённые в белый список, по-прежнему зависают в таймаут / обнаруживаются watchdog — в отличие от форка, который молча глотает всплывающие окна, этот проект придерживается принципа «аномалия = обнаружение, без тихого самовосстановления». Имена устранённых всплывающих окон возвращаются в result["popups_dismissed"].

Запасной вариант BACK для неизвестных всплывающих окон (back_fallback): если белый список исчерпан, а зависание осталось — значит, экран перекрыт чем-то непредвиденным. Движок сначала фиксирует finding unknown_popup_backoff на текущем кадре (сначала улика — BACK может стереть сцену), затем нажимает BACK один раз и с помощью пиксельной разности подтверждает, что экран действительно изменился, прежде чем дать ещё один раунд распознавания. Он только снимает зависание, не переходит (переход — работа bug-skip); при наличии у узла собственного on_timeout полностью уступает написанной автором ветке восстановления, поэтому покрывает только те тупики, которые иначе гарантированно упали бы. config engine.back_fallback включён по умолчанию, в JSON задачи "back_fallback": false отключает для конкретной задачи.

Здоровье якорей (node_stats / task health / task lint): движок по узлам подсчитывает источники попаданий — прямое попадание, восстановление по таймауту, помощь всплывающих окон, запасной BACK, дрейф якоря — выдаёт result["node_stats"] и записывает в report.json. Узел, который в течение раунда неоднократно проходит только через запасные варианты, или якорь которого последовательно смещается, получает предупреждающий finding anchor_rot_suspect: это гниение якоря задачи, а не баг игры (пороги engine.rot_suspect_timeouts / engine.drift_tolerance_px). CLI task health [name] [--days N] офлайн-агрегирует node_stats исторических запусков для просмотра тенденций; task lint <name> (при сохранении save_task тоже автоматически запускается) проверяет «легальные, но хрупкие» конструкции — мёртвые узлы без ветки восстановления, ветки, похожие на ошибку, но не репортящие, задачи холодного старта без белого списка всплывающих окон, наличие якоря при жёстко прописанных координатах, ноль QA-ассертов во всей задаче; по умолчанию только предупреждает, config lint.strict: true меняет на отказ в сохранении.

Создание задач

Четыре способа, в порядке рекомендации:

Способ

Как

Сценарий применения

Наблюдательная запись (рекомендуется)

Скажите агенту «я пройду вручную, а ты записывай», затем продемонстрируйте процесс на телефоне; агент через MCP синхронно делает скриншоты/распознаёт каждый шаг, напрямую создавая задачу с распознаванием-драйвером, проверенную на реальном устройстве. Полный процесс (включая точный захват касаний через getevent, калибровку при смене устройства) см. в .claude/skills/live-record/SKILL.md

Умеете оперировать, но не можете объяснить шаги; длинный процесс

Генерация разведкой агента

Опишите цель (например, «открой настройки, установи яркость 50%, сохрани как задачу»), агент с помощью screenshot/ui_dump/find_text шаг за шагом подтверждает якоря и выполняет, затем save_task сохраняет, run_task проверяет. Выбор каналов / QA-ассерты / итерации воспроизведения см. в .claude/skills/author-task/SKILL.md

Можете сформулировать цель словами

Ручной JSON

По формату get_task_schema (или action/action_schema.py) напрямую пишите task/task_definitions/*.json; как выбирать каналы, какие QA-ассерты добавить — см. .claude/skills/author-task/SKILL.md

Формат знаком, процесс простой

CLI-запись черновика

В CLI record on → ввод команд для операций → record offtask save <name>, создаётся черновик слепого воспроизведения (always распознавание + буквальные действия), затем передаётся агенту для переработки в версию с распознаванием-драйвером

Быстрая офлайн-фиксация каркаса

Независимо от способа, при написании задачи действует единое соглашение: для якорей распознавания приоритет ui_text (системные интерфейсы) / ocr (текст на едином Surface в играх) / template (иконки, текстуры и прочие элементы без текста) / feature (богатые текстурой якоря, которые слегка меняются) / yolo (обученное обнаружение объектов, устойчивость к деформации и перекрытию) / scene (отвечает только «где я», не выдаёт координаты), действия используют "target": "recognized" без жёстко прописанных координат; узлы аномальных веток, такие как всплывающие окна, получают поле finding, задача-уровень — watchdogs негативные ассерты — этот проект позиционируется как QA-инструмент тестирования, аномалии должны репортиться с уликами, а не тихо обходиться.

Визуальная оркестрация: PipelineEditor

Задачи можно не только писать, но и рисовать. pipeline_editor/ (FastAPI + React, распространяется вместе с этим репозиторием) — веб-визуальный редактор JSON задач, напрямую изменяет task/task_definitions/<имя_задачи>.json, без отдельной копии:

  • 🎨 Оркестрация на холсте: перетаскивание и соединение линиями для оркестрации конечного автомата — сплошная линия = next (на ребре указан приоритет распознавания), оранжевый пунктир = on_timeout; узлы, импортированные через includes, — серые с замком, только для чтения, при сохранении автоматически исключаются, никогда не фиксируются в главном файле

  • Проверка истинности + lint: пауза 0,8 секунды — текущий граф автоматически отправляется на бэкенд для сухого прогона task_loader.resolve_task и lint_task, редактор не дублирует никакие правила проверки, ошибки, видимые на холсте, — это те же ошибки, которые выдаст движок

  • 🎯 Скриншот-ROI / шаблоны: рядом с полем roi — прицел, перетаскивание рамки на полноразмерном скриншоте реального устройства записывает координаты, можно прямо на месте «OCR-пробное чтение / пробное сопоставление шаблона» для просмотра рамки попадания и оценки; поле template позволяет вырезать новый шаблон прямо из скриншота и сохранить на диск

  • ▶️ Подсветка реального запуска: фоновый поток запускает движок, WebSocket пушит каждый шаг, холст в реальном времени подсвечивает текущий узел + траекторию visited; остановка через кооперативную чистую завершающую процедуру движка, report и цепочка улик полные

  • 🤝 MCP-взаимодействие: бэкенд встраивает MCP-сервер редактирования в /mcp, как только агент сохраняет файл, холст пользователя автоматически перезагружается за ~2 секунды (при несохранённых изменениях — баннер конфликта) — человек рисует, AI правит JSON, пишут в один и тот же файл, проходят одну и ту же проверку

Одна команда в корне репозитория поднимает всё (бэкенд :8930 + фронтенд :5173, браузер открывает напечатанный адрес):

powershell -File editor.ps1 -Python <python>   # 转发到 pipeline_editor\scripts\dev.ps1,参数语义一致

Зависимости фронтенда устанавливаются один раз ( cd pipeline_editor\frontend; npm install ); зависимости бэкенда уже включены в корневой requirements.txt. Полное руководство см. в pipeline_editor/README.md (документация в pipeline_editor/docs/).

Структура каталогов

flowchart TD
    BRAIN["外部智能体(大脑)<br/>Claude Code / Codex"] -->|"MCP(stdio)"| MCP
    USER["用户"] -->|交互式命令| CLI

    subgraph L1["接口层"]
        MCP["mcp_server.py"]
        CLI["main.py + user_interface/"]
    end
    subgraph L2["任务层"]
        TASK["task/<br/>识别门控引擎 · findings · suite · lint · 健康度"]
        AGT["agent/<br/>设备 Agent 池"]
    end
    subgraph L3["感知 / 执行层"]
        PER["perception/<br/>截图 scrcpy · OCR · dump · 模板/特征/YOLO · 场景 · logcat"]
        ACTL["action/<br/>click · swipe · 手势注入"]
    end
    subgraph L4["基础层"]
        CORE["core/<br/>配置 · ADB 设备 · 日志"]
        UTIL["utils/"]
    end

    L1 --> L2
    L2 --> L3
    L3 --> L4

Зависимости только сверху вниз: слой восприятия / исполнения не может импортировать слой задач, базовый слой не может импортировать верхние слои. Проект сам по себе — ноль вызовов LLM: интеллект всегда на стороне внешнего агента.

autoplayqa/
├── mcp_server.py                 # MCP 入口(FastMCP / stdio):感知/动作/任务工具全集,装配复用 bootstrap.py
├── main.py                       # CLI 入口:config → 设备 → 感知 → 解析 → 任务引擎 → Agent 池 → CLI,装配复用 bootstrap.py
├── bootstrap.py                  # 双入口共用装配层:load_app(读配置建日志)+ build_runtime(拼感知/任务对象图)
├── .mcp.json.example             # Claude Code 自动发现配置模板(复制为 .mcp.json 改 Python 路径;含 autoplayqa + pipeline-editor 两个 server)
├── config.yaml.example           # 配置模板(缺省走默认值,无需任何凭证即可启动)
├── requirements.txt              # 依赖(框架 + PipelineEditor 后端,一次装齐)
├── pytest.ini                    # 测试范围:一条 pytest 同时跑 tests/ 与 pipeline_editor/tests/
├── editor.ps1                    # PipelineEditor 启动薄包装(转发 pipeline_editor\scripts\dev.ps1)
│
├── core/                         # 基础设施
│   ├── config.py                 #   配置加载(缺 config.yaml 返回空走默认)
│   ├── device_manager.py         #   ADB 设备发现 + 无线连接(connect/disconnect/pair/tcpip,启动自动连)
│   ├── text_resolver.py          #   LLM-free 指令解析:显式坐标正则 → 屏幕定位 → 失败引导走 MCP
│   ├── adb_timeout.py            #   全局 adb 超时:config `adb.timeout_s` 统一设定,卡死的 adb 调用不再无限等待
│   ├── notifier.py               #   run 汇总推送(飞书机器人 / 通用 webhook,一 run 一条,失败只记日志)
│   └── logger.py                 #   日志
│
├── agent/                        # 一设备一 Agent
│   ├── agent_pool.py             #   多设备 Agent 选择与分发
│   └── device_agent.py           #   单设备执行 + verify_steps 逐步像素差分校验
│
├── action/                       # 动作执行
│   ├── action_executor.py        #   动作路由(click/drag/input_text/wait/key/gesture)
│   ├── action_schema.py          #   动作 + 任务 JSON 格式 schema(TASK_SCHEMA_DOC 文档源)
│   └── backends/
│       ├── adb_backend.py        #     adb shell input 后端
│       └── motionevent_backend.py#     无 root 多指 MotionEvent 注入(app_process + dex helper)
│
├── perception/                   # 确定性感知(眼睛)
│   ├── screenshot_capturer.py    #   截图统筹(raw screencap 本地组装,热路径免 PNG 编解码)
│   ├── scrcpy_stream.py          #   默认 scrcpy 帧流后端(H.264 本地解码 ~13ms/帧,失败回退 screencap)
│   ├── ui_dump_matcher.py        #   uiautomator dump 控件匹配(tty 失败回退文件 dump)
│   ├── ocr_engine.py             #   rapidocr 本地 OCR(懒加载)
│   ├── template_matcher.py       #   OpenCV 模板匹配(多尺度 + 掩膜 + 多实例 NMS)
│   ├── feature_matcher.py        #   ORB 特征匹配(抗小改版/缩放/遮挡,需纹理丰富锚点)
│   ├── yolo_detector.py          #   YOLO 目标检测(onnxruntime,可选,无模型自动让位)
│   ├── scene_classifier.py       #   整屏场景分类(内置只有 blank;其余标签由接入方 register_scene_probe 注册)
│   ├── ui_detector.py            #   两级免费定位编排:dump → OCR
│   ├── screen_marker.py          #   Set-of-Marks 标注图(序号徽标,配合 click_index)
│   ├── screen_recorder.py        #   设备端 screenrecord 滚动分段录屏(findings 黑匣子视频)
│   └── logcat_monitor.py         #   轮询式 crash / ANR 检测(FATAL EXCEPTION / Fatal signal / ANR in)
│
├── task/                         # 识别门控任务引擎(核心)
│   ├── task_engine.py            #   状态机:识别→动作→next 轮询;agent 挂起交接 / 续跑 / bug-skip / 弹窗清扫
│   ├── suite_runner.py           #   套件连跑:登录一次连跑多个用例,冷启动只付一次,跑挂按策略重启/重试
│   ├── task_loader.py            #   加载校验(includes 合并、节点引用整体校验、custom 注册校验、suite 校验)
│   ├── recognizers.py            #   识别通道:ui_text / ocr / template / feature / yolo / scene / always / blank_screen
│   ├── findings.py               #   QA 发现一等公民:触发即留证 + 飞行记录仪黑匣子 + 保留策略 + 导出 zip + run 汇总推送
│   ├── sentinel.py               #   空窗期哨兵:搭后台帧监控查白屏 / crash,写成独立 findings run(monitor_sentinel)
│   ├── report_html.py            #   report.json → 自包含离线 report.html(截图/录屏/日志内嵌)
│   ├── replay_cache.py           #   回放锚点缓存(ROI 提速,锚点移位上报 anchor_drift)
│   ├── task_lint.py              #   任务加固体检 W001-W007(save_task / CLI task lint)
│   ├── anchor_health.py          #   跨 run 聚合 node_stats 巡检锚点腐烂(CLI task health)
│   ├── step_numbering.py         #   任务步骤编号(step 字段 / 流程大纲,引擎不读,纯导航)
│   ├── task_editor.py            #   录制会话 → 确定性回放草稿
│   ├── custom_actions/           #   进程内确定性动作:目录内新建 <模块>.py 即自动发现注册;内置 swipe_until / launch_app / gm_command / ensure_checkbox / set_text_field / click_topmost_text
│   ├── task_definitions/         #   任务 JSON(含 common/ 共享节点文件、suites/ 套件 JSON)——接入方资产,默认不入库
│   ├── templates/                #   模板匹配图库(feature 通道共用)——接入方采集,默认不入库
│   └── models/                   #   YOLO 模型库(接入方放入 .onnx 即启用;版本记 models.json,换模型必须同步更新)
│
├── record/                       # getevent 手势录制
│   ├── gesture_recorder.py       #   getevent -lt 流 → tap/长按/滑动/多指分段 + 面板→显示像素校准
│   ├── record_session.py         #   录制会话状态(MCP / CLI 共用的启停与产物落盘)
│   └── frame_stream.py           #   可选无 glow 帧流(复用 scrcpy v3.1 server,缺则回退 screencap)
│
├── injector/                     # 无 root 多指注入 dex helper
│   ├── GameInjector.java         #   可审源码(调隐藏 InputManager.injectInputEvent)
│   └── build.ps1                 #   构建脚本(dex 不入库,按此自建)
│
├── user_interface/               # 本地 CLI
│   ├── cli_handler.py            #   命令分发与交互循环
│   └── command_parser.py         #   命令 / 自然语言解析
│
├── utils/                        # 工具
│   ├── debug_tracer.py           #   调试落盘 outputs/debug/
│   ├── image_annotator.py        #   图片标注
│   └── helpers.py                #   像素差分等通用助手
│
├── pipeline_editor/              # 可视化任务编排器(FastAPI + React):画布编排 · 真值校验 · 截图取 ROI/模板 · 真机运行高亮 · 内嵌编辑面 MCP(backend/ frontend/ docs/ tests/)
│
├── vendor/                       # 第三方二进制(scrcpy-server-v3.1,版本须与代码常量一致)
├── tests/                        # 单元测试(subprocess 全 mock,免真机)
├── outputs/                      # 运行时产物:截图 / 日志 / debug / findings(自包含证据夹)/ recordings
│
├── training/                     # YOLO 训练流水线(离线工具线,运行时代码不得 import)
│   ├── preannotate.py            #   已有模型预标注新帧,人工只做订正
│   ├── build_increment.py        #   增量数据集拼装(旧集 + 新标注)
│   └── train_and_export.py       #   训练 → 校验 → 导出 onnx → 部署到 task/models/
│
└── docs/                         # 使用手册与图示(`images/` 下是本 README 的插图)

Тестирование

python -m pytest              # 全量:框架 tests/ + PipelineEditor 后端 pipeline_editor/tests/
python -m pytest tests -q     # 只跑框架
python -m pytest pipeline_editor/tests -q   # 只跑编辑器后端

Оба набора не зависят от реального устройства (subprocess всегда мокается), область совместного прогона определяется testpaths в корневом pytest.ini.

О проекте

  • Позиционирование: фреймворк автоматизации QA-тестирования Android-игр. Предоставляет детерминированное восприятие устройства (глаза) и операции (руки), движок задач с распознаванием-гейтом; суждение и оркестрация передаются внешнему AI-агенту, сам проект — ноль вызовов больших моделей, ноль токенов.

  • Универсальное vs специализированное: сторона фреймворка — универсальные возможности, не зависящие от игры (каналы восприятия / бэкенды действий / движок задач / сбор улик findings / интерфейсы MCP и CLI); игро-специфичная часть предоставляется интеграторомtask/task_definitions/ задачи и наборы, task/templates/ изображения шаблонов, task/models/ YOLO-модели, а также сцены, зарегистрированные через register_scene_probe, — всё это локальные / активы проекта интегратора, по умолчанию не входят в репозиторий.

  • Платформа / стек: Windows + Android (ADB, эмулятор / реальное устройство); Python 3.11; rapidocr локальный OCR, OpenCV сопоставление шаблонов / ORB-сопоставление признаков, YOLO обнаружение объектов (onnxruntime, опционально), правило-основанная классификация сцен, uiautomator dump, скриншоты по умолчанию через поток кадров scrcpy.

  • Способы интеграции: MCP-сервер (Claude Code / Codex — подключи и работай) или локальный интерактивный CLI.

  • Проектная направленность: QA-инструмент тестирования — аномалия = обнаружение тестом, репорт с уликами (ассерты watchdogs / finding узла / мониторинг крашей / улики бортового самописца), а не тихий обход.

Благодарности и заимствования

Этот проект — независимая реализация (Python + React, без переиспользования кода). Ниже — документированные заимствования из истории инженерных итераций, за которые выражаем благодарность:

  • MaaFramework: Идейный источник движка задач — JSON задачи организует поток как «распознавание-подтверждение → выполнение действия → опрос кандидатов next (кто первый распознан, тот и срабатывает)», таймаут идёт по ветке восстановления, первая версия движка построена по его Pipeline-подходу; последующие возможности узлов — комбинированное распознавание (all_of/any_of), действие-уровень repeat серийная отправка, задача-уровень блок defaults — также заимствованы из его конвейерных свойств.

  • better-genshin-impact: По его инженерной практике реализованы три вещи: сторож в окне простоя (после завершения задачи / во время передачи агенту фоновый мониторинг кадров продолжает проверять белый экран и краши), push результатов findings (по завершении run — одно IM / webhook-сводное сообщение), манифест моделей (версионированная регистрация YOLO-моделей в models.json).

  • MaaPipelineEditor: Интерактивная парадигма визуального оркестратора pipeline_editor/ в этом репозитории взята за образец (оркестрация линиями на холсте, панель свойств, синхронизация JSON в реальном времени, встроенные вспомогательные инструменты распознавания), подробнее см. в pipeline_editor/README.md.

F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that enables AI agents to play-test Unity games by capturing screenshots and simulating inputs like taps, drags, and key presses, acting as a Playwright for Unity.
    3
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    MCP server that gives AI agents full vision and control over Android devices via ADB and scrcpy. Supports screenshots, input, apps, UI automation, shell, files, and clipboard.
    38
    259
    73
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    An MCP server that lets AI agents control iOS and Android devices (tap, scroll, type, take screenshots, read UI trees, and run code). Works with multiple devices at the same time.
    198
    40
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/WizardHeHeJun/AutoPlayQA'

If you have feedback or need assistance with the MCP directory API, please join our Discord server