Руководство пользователя

Как пользоваться HybridQA

YAML-сценарии → детерминированные движки (Playwright / Appium) → AI self-healing как fallback. Один раннер в Docker, параллельный прогон до 20 джоб, flaky-карантин и метрики в Prometheus/Grafana. От быстрого старта до CI-пайплайна — на одной странице.

Ruby 3.3 Playwright Appium OmniParser L2 LLM Vision L3 Flutter Web / APK GitLab CI
01

Требования

Сервер / стенд

  • Docker 24+ и Docker Compose v2
  • 4+ ГБ RAM (OmniParser на CPU парсит скриншот ~10 сек)
  • Для mobile-тестов: Linux с включённым KVM (/dev/kvm)
  • Доступ к внешним AI API — только если нужен режим live

Локальная разработка

  • Ruby 3.3+ (bundle install)
  • Playwright-браузер не нужен локально — он в sidecar-контейнере
  • Для Flutter-моста: Dart SDK (см. docs/dart_integration.md)
  • Проверки перед коммитом: rubocop и rspec

Внешние ключи (ANTHROPIC_API_KEY, CAPSOLVER_API_KEY, TWOCAPTCHA_API_KEY) в CI задаются только через CI/CD Variables GitLab (protected + masked). Без них пайплайн работает в replay-режиме.

02

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

Три команды от чистого репозитория до зелёного прогона:

  1. 1

    Поднять стек

    Runner, fixture-site, Redis, OmniParser, браузерный sidecar и метрики:

    docker compose up -d
  2. 2

    Прогнать демо-сценарий

    Ожидаемый вывод — [PASS], exit code 0:

    docker compose run --rm -T runner run dsl/examples/fixture_site.yml
  3. 3

    Проверить свои сценарии

    Валидация DSL без запуска браузера — удобно для быстрой итерации:

    docker compose run --rm -T runner run dsl/my_scenario.yml --dry

Без Docker (локально, только валидация и отчётность):

bundle install
bundle exec ruby bin/orchestrator run dsl/examples/fixture_site.yml --dry
bundle exec rubocop && bundle exec rspec --tag '~external'

Важно про окружение

Каталоги dsl/, logs/ и artifacts/ смонтированы в runner-контейнер. Всё, что лежит в logs/*.jsonl (прогоны, heal-события, AI-вызовы), — вход для отчётов и детекции flaky.

03

Команды CLI

Единая точка входа — bin/orchestrator (в контейнере: docker compose run --rm -T runner <команда>).

КомандаЧто делает
run PATHПрогон YAML-файла или каталога *.yml. Ключи: -j N — параллельно до 20 fork-джоб; --dry — только валидация; --mode record|replay|live — режим AI; --strict-quarantine — quarantine-падения как жёсткие
flaky-detect [LOG]Детекция flaky по истории прогонов → logs/flaky_quarantine.json; --days, --min-runs, --max-rate (exit 1 при превышении — gate в CI)
heal-report [LOG]Markdown-отчёт self-healing suggestions за окно --days (default 7) — еженедельно уходит в MR
metricsJSON-снимок счётчиков self-healing
finetune-dataset [LOG]Сборка vision-SFT датасета из логов AI-вызовов (LLaMA-Factory sharegpt+images)
fixtures export / fixtures-importВыгрузка и загрузка AI-fixtures для replay без внешних API
versionВерсия CLI

Параллельный прогон

docker compose run --rm -T runner run dsl/ -j 20

Воркеры живут в форках родительского процесса (copy-on-write), результаты собираются в единый отчёт атомарным append в JSONL. Падение воркера не роняет прогон — сценарий помечается ошибкой, остальные продолжают.

04

Структура DSL-сценария

Сценарий — YAML-файл: шапка с таргетом, список шагов. Валидация — dry-schema, ошибки ловятся до запуска движка.

name: Login smoke
target: web                # web → Playwright, apk → Appium
start_url: https://example.com

steps:
  - action: navigate
    url: /login
  - action: fill
    selector: { test_id: email }
    value: "user@example.com"
  - action: click
    selector: { test_id: submit }
    description: sign in button   # подпись для AI-healing, если селектор устарел
  - action: assert
    selector: { test_id: dashboard }
    expectation:
      contains: Welcome
ЭлементПоддержка
Действияnavigate, click, fill, extract, paginate, wait, solve_captcha, assert
Селекторыtest_id, css, xpath, aria_label, text; для Flutter — key/type через тестовый мост
Таргетыweb (Playwright, включая Flutter Web), apk (Appium + flutter-driver)
Карантинquarantine: true в шапке сценария или автоматически из flaky-списка
Полные примеры — в dsl/examples/: web, Flutter Web, Flutter APK. Каталог dsl/ смонтирован в runner, перезапуск не требует пересборки.
05

Self-healing селекторов

Если селектор перестал находить элемент, оркестратор не падает сразу, а идёт вниз по цепочке эвристик:

L1 — кэш Redis (тот же селектор уже чинился?) → L2 — OmniParser (локально, CPU, ~10s) → L3 — LLM Vision (Claude / GPT-4V / локальная LLM)
  • L1 — мгновенно: координаты/локатор из кэша, suggested_at в истории.
  • L2 — OmniParser sidecar: скриншот → детект элементов, сопоставление по описанию (coverage/jaccard/levenshtein, порог 0.40).
  • L3 — Vision-модель, когда L2 не нашёл: получаем точку/селектор, клик по координатам.
  • Успех L2/L3 кэшируется и попадает в отчёт как suggestion — разработчику предлагается новый селектор.
  • Circuit breaker: N подряд неудач подряд → слой отключается на cooldown, тесты живут на детерминированном уровне.
# отчёт suggestions за неделю (кладётся в MR автоматически)
docker compose run --rm -T runner heal-report logs/heal_events.jsonl --days 7

Что считается успехом

Метрики hybridqa_heal_total{level,result} и hybridqa_ai_calls_total показывают долю автоисправлений, долю кэш-попаданий и расход AI-бюджета. DoD этапа — ≥50% L3 закрывается локальной моделью.

06

Режимы AI

Режим задаётся флагом --mode или переменной AI_MODE:

REPLAY

По умолчанию в CI

AI-ответы берутся из кэша/фикстур (prompt-hash → response). Внешние API не вызываются; cache-miss падает с сигналом «нужен record-прогон». Так MR-пайплайны детерминированы и бесплатны.

run dsl/ --mode replay
RECORD

Пополнение фикстур

Реальные вызовы AI пишутся в кэш. После прогона выгружаем их в JSONL и коммитим — replay-джобы снова становятся самодостаточными.

run dsl/ --mode record
fixtures-export ai_fixtures/responses.jsonl
LIVE

Полный AI

Свежие вызовы LLM Vision для heal-случаев, которых нет в кэше. Работает только с заданными ключами и в рамках дневного бюджета (AI_BUDGET_USD_PER_JOB).

run dsl/ --mode live
BUDGET

Жёсткий лимит денег

Счётчик токенов на джобу/сценарий/день. На 80% бюджета — алерт, при исчерпании — circuit breaker, дальше только детерминированные уровни.

AI_BUDGET_USD_PER_JOB=0.50 run dsl/
07

Flaky-детекция и quarantine

Сценарий считается flaky, если за окно наблюдения он собрал достаточно прогонов (--min-runs, default 5) и показал и pass, и fail — осцилляция при достаточной выборке.

  1. 1

    Собрать историю

    Каждый прогон пишет строку в logs/scenario_runs.jsonl (атомарный append, работает и из параллельных воркеров).

  2. 2

    Запустить детектор

    flaky-detect logs/scenario_runs.jsonl --days 7 --min-runs 5

    Результат — logs/flaky_quarantine.json со статистикой по каждому flaky-сценарию (runs/passes/fails/pass_rate).

  3. 3

    Карантин применяется автоматически

    На следующем прогоне ScenarioRunner помечает такие сценарии quarantine: падение выводится как [QUARANTINE-FAIL], попадает в отчёт, но exit code остаётся 0 — CI не падает.

# gate в CI: уронить пайплайн, если доля flaky выше порога
flaky-detect logs/scenario_runs.jsonl --max-rate 0.05

Выйти из карантина

Починили сценарий — удалите его из logs/flaky_quarantine.json (или перегенерируйте файл flaky-detect'ом после стабильных прогонов). Путь к списку переопределяется переменной FLAKY_LIST.

08

Метрики и мониторинг

Экспортёр читает JSONL-логи и отдаёт Prometheus-метрики; Prometheus скрейпит его каждые 15 секунд, Grafana рисует дашборд HybridQA.

ЭндпоинтЧто внутри
http://localhost:9100/metricsЭкспортёр: hybridqa_scenario_runs_total, hybridqa_heal_total, hybridqa_ai_*
http://localhost:9090Prometheus: цель hybridqa, алерты, raw-запросы
http://localhost:3001Grafana: дашборд HybridQA (pass rate, heal по уровням, AI cost/latency)
curl -s localhost:9100/metrics | grep hybridqa_
docker compose run --rm -T runner metrics

Порты публикуются на 127.0.0.1 хоста. Grafana — на 3001, потому что 3000 может быть занят другим сервисом. Подробнее — docs/metrics.md.

09

CI/CD (GitLab)

СтадияДжобы
testbin/ci/run_tests.sh all в контейнере ruby:3.3: rubocop, rspec (без @external), валидация всех dsl/**/*.yml через run --dry
buildbuild:harbor (только default-ветка/тег): сборка и push hybridqa, hybridqa-playwright, hybridqa-omniparser в Harbor с кэшем --cache-from latest
deploydeploy:server — кнопка: rsync compose-файлов, pull образов из Harbor и compose up в /srv/projects/hybridqa (без сборки на сервере) + Telegram; deploy_failure_notification — алерт при падении
# e2e-прогон такой же, как в CI (тесты):
./bin/ci/run_tests.sh all
# или по шагам: ./bin/ci/run_tests.sh lint | spec | dsl
  • MR-пайплайны: только стадия test — без сборки образов и деплоя.
  • Деплой — ручная кнопка deploy:server на default-ветке/теге; .env на сервере не перезаписывается.
  • Переменные CI/CD: HARBOR_HOST/PROJECT/USERNAME/PASSWORD, DEPLOY_PATH, TELEGRAM_BOT_TOKEN, TELEGRAM_SOCKS5_PROXY.
10

Web-UI: тесты через браузер

Сервис web (тот же образ, что и runner, порт 8090) — создание и редактирование DSL, запуск прогонов и cron-расписание без консоли.

СтраницаЧто делает
/Дашборд: сценарии, cron-расписания, последние прогоны
/newНовый тест: имя + YAML, валидация DSL при сохранении
/nl«Из описания»: текст на русском → черновик DSL (детерминированные правила, без LLM)
/editРедактор; «Сохранить» / «Запустить» (запуск сначала сохраняет текст)
/loginВход администратора (пароль из ADMIN_TOKEN)
/runsИстория прогонов и лог (автообновление, пока идёт)
# на стенде
docker compose up -d web            # http://<стенд>:8090/
# локально
bundle exec ruby bin/web           # WEB_PORT=8090
«Из описания» собирает шаги из фраз вида «открой вкладка, авторизуйся по кнопке, зайди по ссылке, пройди по всем страницам и собери информацию». Селекторы-плейсхолдеры и нераспознанные фразы — предупреждениями в редакторе; правьте и запускайте или ставьте в cron.
  • Пользовательские тесты живут в dsl/user/ — при деплое исключены из rsync --delete и не вычищаются.
  • Примеры репозитория (dsl/examples/) правятся только через «Копию» (copy-on-write); сценарий с невалидным DSL можно открыть кнопкой «Править» и починить.
  • Удаление сценариев — только администратору: войдите через /login (пароль ADMIN_TOKEN из .env), на дашборде появится кнопка «Удалить» (для dsl/user/). Вместе со сценарием счищаются его cron-расписания.
  • Cron — data/schedules.json (rufus-scheduler в процессе web); запуск — отдельный процесс bin/orchestrator, логи в logs/web_runs/.
  • API для других сервисов — /api/v1/* на том же порту: Bearer $API_TOKEN, приём DSL (POST /scenarios), запуск (POST /runs), удаление (DELETE /scenarios/<path>), расписания (/schedules); ответы JSON.
11

Решение проблем

СимптомПричина и что делать
DSL error: ... до запуска Валидация dry-schema: проверьте действие/селектор по таблице в разделе 04; --dry покажет ошибку без браузера
omniparser: unavailable в heal Sidecar не поднялся или занят: docker compose ps omniparser, логи и healthcheck на :8001/health
cache-miss → тест падает Replay-режим без фикстуры: сделайте record-прогон (--mode record) и закоммитьте ai_fixtures/
[QUARANTINE-FAIL], но CI зелёный Так и задумано: сценарий в flaky-списке, его падение не блокирует пайплайн (см. раздел 07)
--strict-quarantine Ключ для локального «жёсткого» прогона: quarantine-падения снова дают exit 1
Прогон виснет на heal Скриншот парсится на CPU ~10 сек; таймаут L2 задаётся OMNIPARSER_TIMEOUT (default 60)

Где что читать

ARCHITECTURE.md — дизайн и решённые компромиссы; docs/metrics.md — метрики; docs/heal-report.md — отчёт self-healing; docs/apk_testing.md — Flutter APK; docs/dart_integration.md — Dart-мост; docs/finetuning.md — дообучение модели.