По умолчанию в CI
AI-ответы берутся из кэша/фикстур (prompt-hash → response). Внешние API не вызываются; cache-miss падает с сигналом «нужен record-прогон». Так MR-пайплайны детерминированы и бесплатны.
run dsl/ --mode replayРуководство пользователя
YAML-сценарии → детерминированные движки (Playwright / Appium) → AI self-healing как fallback. Один раннер в Docker, параллельный прогон до 20 джоб, flaky-карантин и метрики в Prometheus/Grafana. От быстрого старта до CI-пайплайна — на одной странице.
/dev/kvm)livebundle install)rubocop и rspec
Внешние ключи (ANTHROPIC_API_KEY, CAPSOLVER_API_KEY,
TWOCAPTCHA_API_KEY) в CI задаются только через CI/CD Variables
GitLab (protected + masked). Без них пайплайн работает в replay-режиме.
Три команды от чистого репозитория до зелёного прогона:
Runner, fixture-site, Redis, OmniParser, браузерный sidecar и метрики:
docker compose up -dОжидаемый вывод — [PASS], exit code 0:
docker compose run --rm -T runner run dsl/examples/fixture_site.ymlВалидация 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.
Единая точка входа — 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 |
metrics | JSON-снимок счётчиков 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. Падение воркера не роняет прогон — сценарий помечается ошибкой, остальные продолжают.
Сценарий — 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, перезапуск не требует пересборки.
Если селектор перестал находить элемент, оркестратор не падает сразу, а идёт вниз по цепочке эвристик:
suggested_at в истории.# отчёт 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 закрывается
локальной моделью.
Режим задаётся флагом --mode или переменной AI_MODE:
AI-ответы берутся из кэша/фикстур (prompt-hash → response). Внешние API не вызываются; cache-miss падает с сигналом «нужен record-прогон». Так MR-пайплайны детерминированы и бесплатны.
run dsl/ --mode replayРеальные вызовы AI пишутся в кэш. После прогона выгружаем их в JSONL и коммитим — replay-джобы снова становятся самодостаточными.
run dsl/ --mode record
fixtures-export ai_fixtures/responses.jsonl
Свежие вызовы LLM Vision для heal-случаев, которых нет в кэше.
Работает только с заданными ключами и в рамках дневного бюджета
(AI_BUDGET_USD_PER_JOB).
run dsl/ --mode liveСчётчик токенов на джобу/сценарий/день. На 80% бюджета — алерт, при исчерпании — circuit breaker, дальше только детерминированные уровни.
AI_BUDGET_USD_PER_JOB=0.50 run dsl/
Сценарий считается flaky, если за окно наблюдения он собрал достаточно прогонов
(--min-runs, default 5) и показал и pass, и fail —
осцилляция при достаточной выборке.
Каждый прогон пишет строку в logs/scenario_runs.jsonl (атомарный append, работает и из параллельных воркеров).
flaky-detect logs/scenario_runs.jsonl --days 7 --min-runs 5Результат — logs/flaky_quarantine.json со статистикой по каждому flaky-сценарию (runs/passes/fails/pass_rate).
На следующем прогоне 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.
Экспортёр читает JSONL-логи и отдаёт Prometheus-метрики; Prometheus скрейпит его каждые 15 секунд, Grafana рисует дашборд HybridQA.
| Эндпоинт | Что внутри |
|---|---|
http://localhost:9100/metrics | Экспортёр: hybridqa_scenario_runs_total, hybridqa_heal_total, hybridqa_ai_* |
http://localhost:9090 | Prometheus: цель hybridqa, алерты, raw-запросы |
http://localhost:3001 | Grafana: дашборд 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.
| Стадия | Джобы |
|---|---|
| test | bin/ci/run_tests.sh all в контейнере ruby:3.3: rubocop, rspec (без @external), валидация всех dsl/**/*.yml через run --dry |
| build | build:harbor (только default-ветка/тег): сборка и push hybridqa, hybridqa-playwright, hybridqa-omniparser в Harbor с кэшем --cache-from latest |
| deploy | deploy: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 | dsltest — без сборки образов и деплоя.deploy:server на default-ветке/теге; .env на сервере не перезаписывается.HARBOR_HOST/PROJECT/USERNAME/PASSWORD, DEPLOY_PATH, TELEGRAM_BOT_TOKEN, TELEGRAM_SOCKS5_PROXY.
Сервис 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=8090dsl/user/ — при деплое
исключены из rsync --delete и не вычищаются.dsl/examples/) правятся только
через «Копию» (copy-on-write); сценарий с невалидным DSL можно
открыть кнопкой «Править» и починить./login (пароль ADMIN_TOKEN из .env), на
дашборде появится кнопка «Удалить» (для dsl/user/).
Вместе со сценарием счищаются его cron-расписания.data/schedules.json (rufus-scheduler в процессе
web); запуск — отдельный процесс bin/orchestrator,
логи в logs/web_runs/./api/v1/* на том же порту:
Bearer $API_TOKEN, приём DSL
(POST /scenarios), запуск (POST /runs),
удаление (DELETE /scenarios/<path>),
расписания (/schedules); ответы JSON.| Симптом | Причина и что делать |
|---|---|
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 — дообучение модели.