Документация Тест-планы

Тест-планы

Тест-планы — это главный оркестратор в Mockarty. План объединяет упорядоченный список элементов, которые ссылаются на существующие ресурсы (функциональные коллекции, конфигурации фаззинга, хаос-эксперименты, нагрузочные конфиги, контракты), и запускает их вместе как единый согласованный прогон с общим Allure-совместимым отчётом.

Об URL в примерах: все примеры используют адрес localhost:5770 по умолчанию. Если ваш инстанс запущен на удалённом сервере, замените localhost:5770 на его реальный адрес (например, https://mockarty.company.com). Подробности в разделе Полезные функции и советы.

Связанные страницы: Тест-планы в CI/CD · Рецепты API тест-планов · Нагрузочное тестирование · Хаос-инженерия · Фаззинг API · Контрактное тестирование · Вебхуки и обратные вызовы

Зачем нужны тест-планы

Без плана каждый тип тестов живёт в своём силосе: вы запускаете фаззинг, потом функциональную коллекцию, потом хаос-эксперимент и вставляете три отдельных отчёта в комментарий CI.

С планом вы настраиваете набор один раз и:

  • запускаете всё одним шагом CI с единым кодом выхода;
  • получаете единый объединённый отчёт — Allure JSON/ZIP, JUnit XML, Markdown, отдельный HTML или нативный Mockarty JSON — со всеми шагами, вложениями и метриками по каждому элементу;
  • планируете весь набор (ночная регрессия, ежечасный смоук) без жонглирования отдельными кронами;
  • разветвляете события run_started / run_finished / run_failed / item_failed на одну webhook-точку.

Ключевые понятия

  • План (Plan) — определение верхнего уровня: name, description, namespace, список items[], необязательный schedule выполнения и связанные расписания/вебхуки. Каждый план получает UUID и короткий числовой ID (например #42) — они взаимозаменяемы.
  • Элемент (Item) — один шаг плана: type задаёт, что запускать, order — место в плане, а dependsOn при необходимости связывает шаг с другими. Для большинства типов нужен refId (UUID существующего ресурса); для sleep вместо него используют parameters.durationMs.
  • Прогон (Run) — конкретное выполнение плана. План можно запускать многократно; у каждого прогона свой UUID и статус по каждому элементу.
  • Отчёт (Report) — артефакт, создаваемый для каждого прогона. Доступен в шести форматах: Allure JSON (сводка), Allure ZIP (result-*.json + вложения), JUnit XML, Markdown-саммари, отдельный HTML (печатный, Save-as-PDF) и нативный Mockarty unified JSON.
  • Расписание (Schedule) — правило срабатывания, прикреплённое к плану: cron, once или interval. У плана может быть любое число расписаний.
  • Вебхук (Webhook) — HTTPS-точка, которую уведомляют при смене состояния прогона.
  • Разовый прогон (Ad-hoc run) — одноразовый эфемерный план, диспетчеризуемый одним API-вызовом. Удобен для CI-пайплайнов, собирающих элементы динамически.

Типы элементов

Тип Что выполняет Исходный ресурс
functional Коллекцию API Tester UUID функциональной коллекции
load Нагрузочный скрипт (k6-совместимый) UUID perf-конфигурации
fuzz Фаззинг-кампанию API UUID fuzz-конфигурации
chaos Хаос-эксперимент UUID хаос-эксперимента
contract Валидацию контрактов / проверку провайдера UUID contract-конфигурации
test_plan Другой тест-план UUID плана
test_case Кейс TCM UUID кейса
sleep Паузу между шагами Ресурс не нужен; задайте parameters.durationMs
ui_test Записанный UI-тест UUID UI-теста
temporal_probe Ожидание ожидаемого асинхронного эффекта UUID Temporal Probe
bot_scenario Сценарий диалога с ботом UUID сценария

Сохранённый тест-план принимает все 11 типов. Endpoint разового прогона принимает пять основных типов тестов выше, а также test_case, ui_test, temporal_probe и bot_scenario; типы test_plan и sleep в нём недоступны. Для основных типов он также принимает алиасы collection, perf_config, fuzz_config, chaos_experiment и contract_config. Набор полей выбора и готовых методов зависит от клиента; если нужного типа в клиенте нет, используйте API.

Числовые идентификаторы

Каждый план получает короткий монотонный ID в дополнение к UUID. UI показывает его как бейдж (#42). REST endpoints с параметром idOrNumericID прозрачно принимают обе формы:

GET /api/v1/namespaces/default/test-plans/42/runs/<runId>/report
GET /api/v1/namespaces/default/test-plans/5c0f13e4-.../runs/<runId>/report

CLI убирает ведущий # сам: mockarty-cli testplan get '#42'.

Каталог планов

/ui/test-plans показывает каждый план пространства имён карточкой:

  • Название, описание и бейджи шагов — по бейджу на каждый тип шага со счётчиком: состав плана (функциональные, нагрузочные, fuzz, chaos, …) виден без открытия.
  • Тренд — последние прогоны цветными сегментами (от старых к новым). Клик по сегменту открывает этот прогон. Под полоской — общее число прогонов.
  • Последний прогон — статус с относительным временем старта и длительностью. У выполняющегося плана появляется живой бейдж «Выполняется» со ссылкой на активный прогон; список обновляется автоматически, пока что-то выполняется.
  • Расписание — когда сработает ближайшее включённое расписание.
  • Действия — открыть, запустить, удалить.

Поиск ищет по названию, описанию и числовому ID — фильтр работает на сервере, то есть по всем страницам, а не только по видимой. Те же данные доступны из API: GET /api/v1/test-plans возвращает для каждого плана lastRun, recentRuns, runCount и nextSchedule и принимает тот же параметр search.

Создание плана

Страница тест-планов

Веб-интерфейс

  1. Откройте /ui/test-plans (Test Plans в боковой панели).
  2. Нажмите + New Test Plan.
  3. Заполните Name, опционально Description.
  4. Нажмите Добавить элементы, чтобы открыть пикер. В нём по вкладке на каждый
    источник — тест-кейсы, коллекции API, UI-тесты, нагрузка, фаззинг, хаос,
    контрактные эксперименты и вложенные планы. Ищите внутри вкладки, отмечайте всё
    нужное (выбор сохраняется при переключении вкладок) и нажмите Добавить,
    чтобы добавить всё разом. Каждый элемент сохраняет своё настоящее имя, и план
    читается как чек-лист.
  5. Переупорядочьте элементы перетаскиванием. Задайте delayAfterMs при необходимости паузы после элемента.
  6. Нажмите Save — план будет создан в текущем namespace.
  7. Откроется страница плана /ui/test-plans/<id>.

Если у элемента или папки моков в выборе ресурса нет названия, пикер
показывает понятную подпись. Внутренний ID сохраняется для выполнения плана,
но не появляется в подсказке при наведении.
Сначала пикер показывает до 200 ресурсов. Кнопка Загрузить ещё открывает
следующую страницу, а поиск по названию находит ресурс за пределами первой
страницы. Если следующая страница не загрузилась, уже полученные строки
остаются на месте и появляется кнопка Повторить.

Если закрыть редактор плана с несохранёнными изменениями, Mockarty попросит подтвердить их сброс. Ссылка на несуществующий план покажет отдельное сообщение и переход к списку; при временной ошибке загрузки доступна кнопка Повторить.

CLI

# plan.yaml
name: Nightly regression
description: Functional + fuzz + chaos against staging
items:
  - order: 1
    type: functional
    resourceId: 11111111-1111-1111-1111-111111111111
  - order: 2
    type: fuzz
    resourceId: 22222222-2222-2222-2222-222222222222
  - order: 3
    type: chaos
    resourceId: 33333333-3333-3333-3333-333333333333
mockarty-cli testplan create -f plan.yaml
# → Created plan <uuid> (#42)

API

cURL

curl -X POST http://localhost:5770/api/v1/test-plans \
  -H "X-API-Key: $MOCKARTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "namespace": "default",
    "name": "Nightly regression",
    "description": "Functional + fuzz + chaos against staging",
    "items": [
      {"order": 1, "type": "functional", "refId": "11111111-1111-1111-1111-111111111111"},
      {"order": 2, "type": "fuzz",       "refId": "22222222-2222-2222-2222-222222222222"},
      {"order": 3, "type": "chaos",      "refId": "33333333-3333-3333-3333-333333333333"}
    ]
  }'

Go

plan, err := client.TestPlans().Create(ctx, mockarty.TestPlan{
    Namespace:   "default",
    Name:        "Nightly regression",
    Description: "Functional + fuzz + chaos against staging",
    Items: []mockarty.TestPlanItem{
        {Order: 1, Type: mockarty.PlanItemTypeFunctional, ResourceID: "11111111-..."},
        {Order: 2, Type: mockarty.PlanItemTypeFuzz,       ResourceID: "22222222-..."},
        {Order: 3, Type: mockarty.PlanItemTypeChaos,      ResourceID: "33333333-..."},
    },
})

Python

plan = client.test_plans.create(TestPlan(
    namespace="default",
    name="Nightly regression",
    description="Functional + fuzz + chaos against staging",
    items=[
        TestPlanItem(order=1, type="functional", ref_id="11111111-..."),
        TestPlanItem(order=2, type="fuzz",       ref_id="22222222-..."),
        TestPlanItem(order=3, type="chaos",      ref_id="33333333-..."),
    ],
))

Java

TestPlan created = client.testPlans().create(new TestPlan()
    .setNamespace("default")
    .setName("Nightly regression")
    .setItems(List.of(
        new TestPlanItem().setOrder(1).setType("functional").setResourceId("11111111-..."),
        new TestPlanItem().setOrder(2).setType("fuzz").setResourceId("22222222-..."),
        new TestPlanItem().setOrder(3).setType("chaos").setResourceId("33333333-...")
    )));

Справочник полей плана

Поле Тип Обязательное Примечания
namespace string да Если опущено, используется namespace вызывающего.
name string да 1..200 символов.
description string нет Свободный текст.
executionMode enum нет Режим выполнения плана (предпочтительный): fifo, parallel или dag. Пустое значение — авто-определение (есть Gates → DAG, иначе FIFO).
schedule string нет Устаревшее поле — принимает те же sentinel-значения parallel / dag ИЛИ cron-выражение из 5/6 полей. Сохранено для обратной совместимости; для типизированного режима используйте executionMode, для cron-расписаний — API per-plan расписаний (ниже).
items[] array да Минимум один элемент. Каждое order уникально в плане.
items[].type enum да Один из 11 типов в разделе Типы элементов.
items[].refId UUID кроме sleep UUID исходного ресурса; для sleep не нужен.
items[].order int да >= 0, уникально в плане.
items[].dependsOn массив UUID нет Учитывается только в DAG-режиме. Элемент не может зависеть от себя.
items[].delayAfterMs int64 нет Пауза после завершения элемента.

Режимы выполнения

Установите executionMode в одно из значений:

  • fifo — элементы идут друг за другом по возрастанию order. Используется по умолчанию, если в плане нет Gates.
  • parallel — все элементы диспетчеризуются параллельно.
  • dag — учитывает dependsOn и Gates у элементов; элементы с упавшими зависимостями пропускаются с skipReason: dependency_failed. Выбирается автоматически, если в плане есть Gates, а executionMode пустой.

Поле schedule по-прежнему принимает sentinel-значения parallel / dag и cron-выражения (5/6 полей) — для обратной совместимости. Новым интеграциям рекомендуется использовать executionMode для типизированного режима и API per-plan расписаний (ниже) для cron-срабатываний.

Примеры CLI:

# Установить типизированный режим
mockarty-cli testplan patch plan-abc --execution-mode parallel
mockarty-cli testplan patch '#42' --execution-mode dag

# Устаревшее одиночное cron-поле (сохранено для обратной совместимости)
mockarty-cli testplan patch plan-abc --schedule-cron '0 2 * * *'

Импорт плана (миграция)

Переезжаете с Allure TestOps или Test IT? Существующий тест-план можно
импортировать в Mockarty, а не пересобирать вручную.

  1. Откройте Test Plans, нажмите Импорт в панели.
  2. Выберите формат источника:
    • Allure TestOps (testplan.json) — загрузите testplan.json из вашего
      Allure (Mockarty экспортирует тот же формат, поэтому экспортированный план
      импортируется обратно без потерь).
    • Экспорт Test IT — загрузите экспорт Test IT; Mockarty сначала
      импортирует кейсы, затем собирает план, который их запускает.
  3. При желании задайте имя плана и нажмите Импорт.

Каждая запись в файле сопоставляется с кейсом Mockarty по имени (или по id
Mockarty). Результат покажет, сколько кейсов найдено, и перечислит селекторы,
которые не разрешились — сначала импортируйте эти кейсы (Test Cases →
Импорт), затем повторите импорт плана. План создаётся, только если найден
хотя бы один кейс, поэтому пустого плана не возникнет.

Для автоматизации тот же импорт доступен через API —
POST /api/v1/test-plans/import/allure с телом testplan.json, либо импорт
кейсов Test IT с ?createPlan=true.

Запуск плана

Триггеры

  • Вручную из UI — кнопка Run на странице плана.
  • CLI — mockarty-cli testplan run <plan-id|#numeric>.
  • API — POST /api/v1/test-plans/:id/run.
  • Расписание — любое включённое расписание cron/once/interval, прикреплённое к плану.
  • Ad-hoc — разовый план, не попадающий в каталог (см. ниже).

Тело триггера запуска (необязательное)

{
  "items": [1, 3],
  "mode": "parallel"
}
  • items — подмножество order-ов элементов, которые нужно запустить. Опустите, чтобы запустить всё.
  • mode — переопределение режима только для этого прогона (sequential, parallel, dag, timed).

Ожидание завершения

POST /run отвечает 202 Accepted с телом {runId, planId, status}; оркестратор выполняет план асинхронно на сервере. Отмена клиентского запроса не прерывает прогон — используйте POST /runs/:runId/cancel. Отмена останавливает и то, что прогон запустил вне узла: распределённую нагрузочную кампанию и UI-тест, который воспроизводится в раннере-расширении браузера.

Три способа заблокироваться до конца прогона:

  1. --wait в CLI: mockarty-cli testplan run <plan> --wait --timeout 5m. Коды выхода: 0 завершено, 1 упало, 2 отменено, 3 таймаут.
  2. SDK WaitForRun (Go / Python sync / Java): опрашивает /runs/:runId с настраиваемым интервалом.
  3. SSE-поток: GET /api/v1/namespaces/:ns/test-plans/:planRef/runs/:runID/stream шлёт события run.started, item.started, item.finished, run.completed и периодические heartbeat. CLI оборачивает это в mockarty-cli testplan stream <runID> --plan <plan>.

Что означает completed

Прогон отвечает completed только если все элементы завершились и хотя бы один из них действительно проверил то, на что ссылается. Прогон, у которого все элементы были пропущены из-за отсутствия сущностей, помечается failed, а не completed: релизный гейт не должен читать «ничего не проверено» как зелёное. Причина видна в счётчиках: failedItems остаётся 0 (ничего не упало), а notVerifiedItems показывает, сколько элементов пропущено из-за отсутствующей сущности, при этом у каждого элемента сохраняется свой статус skipped и skipReason: entity_not_found.

Остальные причины пропуска (строка удалена во время прогона, нет координатора, каскадный отказ, таймаут, план удалён под прогоном) по-прежнему оставляют прогон completed — они тоже означают «не проверено», но на сегодняшнем вердикте для них держатся существующие пайплайны.

Пауза и продолжение прогона

Откройте прогон и нажмите Пауза рядом с Отменить прогон. На паузе прогон не запускает следующие элементы; уже идущие элементы доигрывают как обычно, а у прогона статус На паузе. Нажмите Продолжить — следующий элемент стартует в течение пары секунд. Прогон на паузе можно и отменить.

Из скрипта или пайплайна:

curl -X POST http://localhost:5770/api/v1/test-plans/runs/<runId>/pause  -H "Authorization: Bearer $MOCKARTY_TOKEN"
curl -X POST http://localhost:5770/api/v1/test-plans/runs/<runId>/resume -H "Authorization: Bearer $MOCKARTY_TOKEN"

Оба запроса возвращают прогон; пока он на паузе, в нём есть поле pausedAt. Повторная пауза или продолжение незапаузенного прогона ничего не меняют; завершённый прогон отвечает 409. ИИ-агенты используют инструменты pause_test_plan_run и resume_test_plan_run.

Какие требования проверил прогон

Если тест-кейсы связаны с требованиями в вики (панель Трассировка на странице требования или связи Документация у кейса), на вкладке Обзор прогона есть строка Требования: сколько требований этот прогон проверил, сколько падает и сколько не запускалось. Раскройте её — там каждое требование с вердиктом и ссылкой на страницу:

  • Падает — хотя бы один связанный кейс упал в этом прогоне;
  • Проверено — хотя бы один связанный кейс прошёл и ни один не упал;
  • Не запускалось — связанные кейсы пропущены или не стартовали.

Тот же ответ для скриптов и релизных гейтов: GET /api/v1/test-plans/runs/<runId>/requirements возвращает requirements (у каждого verification и его cases), casesWithoutRequirement и summary. ИИ-агенты используют get_test_plan_run_requirements. Страницы требований, к которым у вас нет доступа, не показываются.

Разовые прогоны (ad-hoc master run)

Используйте POST /api/v1/namespaces/:namespace/test-runs/ad-hoc, когда нужен прогон без предварительного создания плана (типично для динамических CI-пайплайнов). Сервер создаёт скрытый план, запускает его элементы и отвечает {run_id, plan_id, status, _links}:

Добавьте необязательное "tags": ["release-42", "smoke"], чтобы пометить прогон при создании. Метки обрезаются по краям, приводятся к нижнему регистру и удаляются дубликаты; каждая метка может содержать латинские буквы, цифры, ., _ или - (до 64 байт, не более 32 меток на прогон). Некорректные метки отклоняют запрос до создания плана. При передаче меток ответ содержит tags, а GET /api/v1/test-plans/runs/<runId> возвращает сохранённые значения.

Чтобы добавить метки к существующему прогону без замены остальных, отправьте POST /api/v1/namespaces/<namespace>/test-plan-runs/<runId>/tags/merge с телом {"tags":["release-42"]}. Ответ содержит полный список и changed (false при повторном запросе). Метка релиза не меняется. Нужна хотя бы одна метка; неизвестные поля и общий список длиннее 32 меток дают 400. При одновременном изменении сервер может ответить 409 — повторите тот же запрос. Существующий endpoint /tags по-прежнему заменяет весь список.

curl -X POST http://localhost:5770/api/v1/namespaces/default/test-runs/ad-hoc \
  -H "X-API-Key: $MOCKARTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "pr-1234 smoke",
    "items": [
      {"order": 1, "type": "functional", "ref_id": "11111111-..."},
      {"order": 2, "type": "contract",   "ref_id": "44444444-..."}
    ]
  }'

URL-ы для дальнейшего взаимодействия вернутся в блоке _links. Ad-hoc-планы скрыты от GET /test-plans, но их прогоны и отчёты работают как у обычных планов.

Внешний адаптер может создать пустой запуск с зарегистрированным source, например allurectl или testit-cli, и "items":[]. Запуск остаётся в статусе pending, пока адаптер выгружает результаты кейсов; после выгрузки вызовите POST /api/v1/test-plans/runs/<runId>/finish. Завершение без единого результата отмечает запуск как неуспешный. Для прогонов с исполняемыми элементами нужен оркестратор; если он недоступен, создание вернёт 503.

Для повторов CI добавьте к запросу "idempotency_key":"job-4211". Ключ действует внутри namespace и source. Повтор того же запроса вернёт исходные run_id и plan_id с "replayed":true даже после потери ответа или перезапуска сервера; изменение запроса с прежним ключом даст 409. Новому заданию нужен новый ключ. Длина ключа — 1–256 байт; сервер сохраняет только его хеш.

Расписания

У каждого плана может быть любое количество расписаний разных видов. Они отделены от поля schedule плана (которое задаёт режим, а не правило срабатывания).

Endpoints:

GET    /api/v1/test-plans/:id/schedules
POST   /api/v1/test-plans/:id/schedules
PATCH  /api/v1/test-plans/:id/schedules/:scheduleId
DELETE /api/v1/test-plans/:id/schedules/:scheduleId

Виды расписаний и их payload

Вид Payload Примечания
cron {"expr": "0 2 * * *"} Cron из 5 или 6 полей. 6-польная форма добавляет секунды.
once {"fire_at": "2026-05-01T00:00:00Z"} RFC 3339. Срабатывает один раз и автоматически отключается.
interval {"every_seconds": 900} Срабатывает каждые N секунд. Минимум 10 секунд.

Каждое расписание также имеет:

  • name — обязательное, до 200 символов;
  • timezone — имя IANA (например Europe/Moscow), по умолчанию UTC;
  • enabled — выключение без удаления.

CLI

# cron — каждую ночь в 02:00 по Москве
mockarty-cli testplan schedule create plan-abc \
    --name nightly --kind cron \
    --payload '0 2 * * *' --timezone Europe/Moscow

# once — срабатывание по wall-clock
mockarty-cli testplan schedule create plan-abc \
    --name launch --kind once \
    --payload 2026-05-01T00:00:00Z

# interval — каждые 15 минут
mockarty-cli testplan schedule create plan-abc \
    --name smoke --kind interval --payload 15m

mockarty-cli testplan schedule list plan-abc
mockarty-cli testplan schedule update plan-abc <scheduleId> --enabled=false
mockarty-cli testplan schedule delete plan-abc <scheduleId>

CLI транслирует сокращённый --payload в JSON-форму, которую ждёт сервер.

Создание расписания через API

curl -X POST http://localhost:5770/api/v1/test-plans/<plan>/schedules \
  -H "X-API-Key: $MOCKARTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "nightly",
    "kind": "cron",
    "timezone": "Europe/Moscow",
    "payload": {"expr": "0 2 * * *"}
  }'

Вебхуки

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

Поддерживаемые события

Событие Когда срабатывает
run_started Оркестратор начал диспетчеризацию элементов.
run_finished Все элементы достигли терминального состояния (passed/failed/skipped/cancelled).
run_failed Прогон завершился, есть хотя бы один упавший элемент.
item_failed Отдельный элемент перешёл в failed.

При создании webhook-а выбирайте любое подмножество. CLI сейчас принимает через --event значения run_started, run_finished, item_failed; полный словарь из четырёх событий доступен через API и SDK.

Подписание

Каждая доставка подписывается:

  • X-Mockarty-Signature: sha256=<hex> — HMAC-SHA256 по сырому телу запроса с использованием поля secret вебхука. Аутентифицирует тело; проверяйте против сырых полученных байт.
  • X-Mockarty-Timestamp: <RFC3339Nano> — время отправки, для логов и грубой проверки свежести. Не входит в подпись, поэтому сам по себе не защищает от replay (атакующий, переотправляя тело, может подставить свежий timestamp). Для защиты от replay обрабатывайте доставки идемпотентно — дедуплицируйте по идентификатору события, а не полагайтесь на timestamp.

Mockarty принимает как чистый hex, так и формат sha256=<hex> для совместимости с приёмниками GitHub/GitLab. Секрет в режиме write-only: сервер возвращает пустую строку при чтении, поэтому ротация — через PATCH { "secret": "<new>" }.

Защита от SSRF

Исходящие URL валидируются и при создании/обновлении, И при установлении соединения. Mockarty отбрасывает:

  • plain-текстовые http:// (требуется HTTPS);
  • URL со встроенными учётными данными (https://user:pass@host);
  • IP-литералы loopback, RFC 1918 (частные сети), link-local и multicast;
  • хосты, соответствующие localhost, *.internal, *.cluster.local.

Если DNS-имя резолвится в момент коннекта в заблокированный адрес, доставка падает — это защищает от DNS-rebinding атак.

Повторы

Каждый webhook хранит retryCount (по умолчанию 3, максимум 10) и backoffSeconds (по умолчанию 5, максимум 3600). Диспетчер повторяет при 5xx и транспортных ошибках с экспоненциальной паузой.

Payload

{
  "timestamp": "2026-04-19T10:15:00Z",
  "started_at": "2026-04-19T10:10:00Z",
  "finished_at": "2026-04-19T10:14:58Z",
  "event_type": "run_finished",
  "test_plan_id": "5c0f13e4-...",
  "numeric_id": 42,
  "test_plan_name": "Nightly regression",
  "run_id": "8a1f62d0-...",
  "status": "failed",
  "duration_ms": 298000,
  "item_summary": {"total": 3, "passed": 2, "failed": 1, "skipped": 0},
  "failed_items": [
    {"order": 3, "type": "chaos", "status": "failed", "error": "timeout", "run_id": "..."}
  ]
}

Создание вебхука

cURL

curl -X POST http://localhost:5770/api/v1/test-plans/<plan>/webhooks \
  -H "X-API-Key: $MOCKARTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ci-slack",
    "url": "https://hooks.example.com/mockarty",
    "secret": "keep-me-safe",
    "events": ["run_finished", "item_failed"],
    "retryCount": 3,
    "backoffSeconds": 5
  }'

CLI

mockarty-cli testplan webhook create plan-abc \
    --name ci-slack \
    --url https://hooks.example.com/mockarty \
    --secret keep-me-safe \
    --event run_finished,item_failed

Go

hook, err := client.TestPlans().AddWebhook(ctx, planID, mockarty.Webhook{
    URL:     "https://hooks.example.com/mockarty",
    Secret:  "keep-me-safe",
    Events:  []string{"run_finished", "item_failed"},
    Enabled: true,
})

Проверка вебхука

POST /api/v1/test-plans/:id/webhooks/:webhookId/test (CLI: testplan webhook test) помещает в очередь синтетический run_started payload — удобно для end-to-end проверки получателя.

Отчёты

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

GET /api/v1/namespaces/:namespace/test-plans/:idOrNumericID/runs/:runID/report
GET /api/v1/namespaces/:namespace/test-plans/:idOrNumericID/runs/:runID/report.zip
GET /api/v1/namespaces/:namespace/test-plans/:idOrNumericID/runs/:runID/report.junit.xml
GET /api/v1/namespaces/:namespace/test-plans/:idOrNumericID/runs/:runID/report.md
GET /api/v1/namespaces/:namespace/test-plans/:idOrNumericID/runs/:runID/report.html
GET /api/v1/namespaces/:namespace/test-plans/:idOrNumericID/runs/:runID/report.unified.json
Endpoint Content-Type Типичный сценарий
/report application/json Allure-совместимый JSON-конверт — сводки по элементам со шагами, labels, параметрами, вложениями и метриками.
/report.zip application/zip Allure-архив (result-*.json + вложения), готовый для allure generate или загрузки в CI-артефакты.
/report.junit.xml application/xml JUnit XML по стандартной схеме — для Jenkins, GitLab, GitHub Actions JUnit-репортеров.
/report.md text/markdown Однострочный Markdown-саммари для Slack, email, wiki.
/report.html text/html Самодостаточный HTML-документ с inline-CSS — без внешних ассетов и без JavaScript. Открывается в любом браузере; печать в PDF — через Save-as-PDF.
/report.unified.json application/json Нативный конверт Mockarty (план + счётчики + per-item результаты) без специфики Allure.

Allure ZIP включает доступные вложения из пространства имён прогона плана.
Предел — 25 МиБ на файл и 100 МиБ вложений на весь архив. Если файл слишком
большой или хранилище вложений недоступно, ZIP содержит текстовую заметку с
причиной пропуска.

/report и /report.zip поддерживают If-None-Match через сильный ETag, так что CI-пайплайны, опрашивающие готовность отчёта, платят лишь за HTTP round-trip до его генерации. Остальные четыре формата регенерируются при каждом запросе (они дёшевы в построении) и всегда отражают самое свежее состояние прогона.

Скачивание из CLI

# Allure-архив (по умолчанию; рекомендуется для загрузки артефактов CI)
mockarty-cli testplan report <runID> --plan plan-abc --zip ./allure.zip

# Allure JSON-сводка
mockarty-cli testplan report <runID> --plan '#42' --format json -o report.json

# JUnit XML (публикация из CI)
mockarty-cli testplan report <runID> --plan plan-abc --format junit -o report.junit.xml

# Markdown-саммари (вложить в Slack / email)
mockarty-cli testplan report <runID> --plan plan-abc --format markdown -o report.md

# Самодостаточный HTML (открыть в браузере, Save-as-PDF)
mockarty-cli testplan report <runID> --plan plan-abc --format html -o report.html

# Нативный unified JSON Mockarty
mockarty-cli testplan report <runID> --plan plan-abc --format unified -o report.unified.json

CLI требует --plan, потому что endpoint привязан к namespace, а сегмент namespace берётся из контекста клиента (флаг --namespace или переменная окружения MOCKARTY_NAMESPACE).

Скачивание из SDK

// Go SDK — каждый формат вызовом своего метода.
rep, err := client.TestPlans().GetRunReport(ctx, "default", planID, runID)              // Allure JSON
zipRC, err := client.TestPlans().GetRunReportZIP(ctx, "default", planID, runID)         // Allure ZIP
junit, err := client.TestPlans().GetRunReportJUnit(ctx, "default", planID, runID)       // JUnit XML bytes
md, err := client.TestPlans().GetRunReportMarkdown(ctx, "default", planID, runID)       // Markdown bytes
html, err := client.TestPlans().GetRunReportHTML(ctx, "default", planID, runID)         // Standalone HTML bytes
unified, err := client.TestPlans().GetRunReportUnified(ctx, "default", planID, runID)   // UnifiedReport (типизированный)
# Python SDK
allure   = client.test_plans.get_run_report(plan_ref, run_id)
junit    = client.test_plans.get_run_report_junit(plan_ref, run_id)
md       = client.test_plans.get_run_report_markdown(plan_ref, run_id)
html     = client.test_plans.get_run_report_html(plan_ref, run_id)
unified  = client.test_plans.get_run_report_unified(plan_ref, run_id)
with open("report.zip", "wb") as fh:
    client.test_plans.get_run_report_zip(plan_ref, run_id, fh)
// Java SDK
AllureReport allure   = client.testPlans().getRunReport(ns, planRef, runId);
byte[] junit          = client.testPlans().getRunReportJUnit(ns, planRef, runId);
byte[] md             = client.testPlans().getRunReportMarkdown(ns, planRef, runId);
byte[] html           = client.testPlans().getRunReportHTML(ns, planRef, runId);
UnifiedReport unified = client.testPlans().getRunReportUnified(ns, planRef, runId);
try (FileOutputStream out = new FileOutputStream("report.zip")) {
    client.testPlans().getRunReportZip(ns, planRef, runId, out);
}

Сравнение запусков

Получите side-by-side диф между двумя запусками: что регрессировало, что улучшилось, что добавилось / удалилось и какие айтемы стали медленнее. Оба запуска ОБЯЗАНЫ принадлежать пространству вызывающего; на чужие id сервер отвечает 404. Сравнивать запуски разных планов разрешено — в ответе выставляется summary.differentPlans, чтобы клиент мог показать баннер.

GET /api/v1/test-plans/runs/compare?run_a=<runID>&run_b=<runID>

Передавайте более старый / базовый запуск как run_a и более новый / целевой как run_b, чтобы знаки регрессий и улучшений читались интуитивно.

Поле Что значит
runA, runB Конверт запуска: id, planId, planName, namespace, status, totalItems / passedItems / failedItems / skippedItems, startedAt / completedAt, durationMs.
items[] По-айтемный диф. a и b — состояние стороны (status, durationMs, attempts, error, present); diff.regressionType — одно из unchanged, pass_to_fail, fail_to_pass, skipped_to_ran, ran_to_skipped, fail_to_fail, pass_to_pass, added, removed. diff.isRegression / isImprovement / durationWorsened — удобные булевы флаги.
summary Агрегаты: regressions, improvements, passToFail, failToPass, skippedToRan, ranToSkipped, unchangedItems, addedItems[], removedItems[], totalA, totalB, differentPlans.

Веб-интерфейс

Откройте любой запуск, переключитесь на вкладку Compare, выберите второй запуск из выпадающего списка, включите «Show only differences», чтобы скрыть неизменённые айтемы. Карточки сводки красят регрессии красным, улучшения — зелёным. Если запуски относятся к разным планам, отдельный баннер делает это явным (added/removed будут большими).

CLI

mockarty-cli testplan compare-runs <runA> <runB>            # текстовая таблица, скрывает неизменённые строки
mockarty-cli testplan compare-runs <runA> <runB> --diff-only=false   # вывести и неизменённые
mockarty-cli testplan compare-runs <runA> <runB> -o json | jq .summary

Код выхода 1 — найдена хотя бы одна регрессия (удобно как CI-гейт).

SDK

// Go SDK
diff, err := client.TestPlans().CompareRuns(ctx, runA, runB)
fmt.Printf("regressions=%d improvements=%d\n", diff.Summary.Regressions, diff.Summary.Improvements)
# Python SDK
diff = client.test_plans.compare_runs(run_a, run_b)
print("regressions:", diff.summary.regressions)
// Java SDK
CompareResult diff = client.testPlans().compareRuns(runA, runB);
System.out.println("regressions=" + diff.getSummary().getRegressions());

Классификатор регрессий

regressionType для каждого айтема определяется по сравнению состояний сторон:

Код Статус A Статус B Смысл
pass_to_fail passed failed Регрессия.
fail_to_pass failed passed Улучшение.
skipped_to_ran skipped passed/failed В B айтем выполнился; считается регрессией только если упал.
ran_to_skipped passed/failed skipped Снижение покрытия — учтено как регрессия.
fail_to_fail failed failed По-прежнему падает.
pass_to_pass passed passed Оба зелёные; помечается durationWorsened, если B заметно медленнее.
added отсутствует присутствует Айтем есть только в B.
removed присутствует отсутствует Айтем есть только в A.
unchanged одинаково одинаково Статус и длительность не дрейфовали.

durationWorsened срабатывает, когда B длился минимум на 20% дольше A И абсолютная разница превышает 250 мс — мелкий шум на быстрых айтемах не загрязняет отчёт.

Частичные обновления (PATCH)

PATCH /api/v1/namespaces/:namespace/test-plans/:idOrNumericID применяет частичное обновление с оптимистичной блокировкой. Передавайте только те поля, которые хотите изменить; отсутствующие поля остаются как есть.

  • Поддерживает обновление name, description, schedule_cron, execution_mode, enabled.
  • execution_mode принимает fifo / parallel / dag (типизированный преемник sentinel-значений parallel/dag из schedule_cron).
  • Требует заголовок If-Match с текущим сильным валидатором плана (в кавычках).
  • Несовпадение возвращает 412 Precondition Failed; перечитайте план и повторите.
ETAG=$(curl -s http://localhost:5770/api/v1/test-plans/42 \
    -H "X-API-Key: $MOCKARTY_API_TOKEN" \
    | jq -r '.updatedAt' \
    | python3 -c 'import sys,datetime; t=datetime.datetime.fromisoformat(sys.stdin.read().strip().replace("Z","+00:00")); print(int(t.timestamp()*1000))')

curl -X PATCH http://localhost:5770/api/v1/namespaces/default/test-plans/42 \
  -H "X-API-Key: $MOCKARTY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "If-Match: \"$ETAG\"" \
  -d '{"description": "Updated nightly suite"}'

SDK Go и Python вычисляют etag автоматически, если вы не указали IfMatch; в условиях конкурирующих писателей передавайте его явно.

Мягкое удаление и восстановление

DELETE /api/v1/test-plans/:id — это мягкое удаление: план перемещается в Корзину, а не стирается. Его расписания, вебхуки, прогоны и артефакты закрываются одной операцией и больше не появляются в активных списках.

Восстановление и окончательное удаление происходят в Корзине (Settings → Recycle Bin) или через REST-endpoint-ы корзины. По умолчанию элементы лежат в корзине 7 дней, затем фоновый сборщик удаляет их. Роль Support может просматривать корзину по всем namespace, но не может окончательно удалять.

RBAC и изоляция namespace

  • Viewer — чтение планов, прогонов, отчётов.
  • Developer — полный CRUD планов, прогонов, расписаний, вебхуков.
  • Admin / Owner — дополнительно мягкое удаление, восстановление и окончательная чистка.
  • Support — просмотр корзины по всем namespace; окончательно удалять не может.

Все REST-точки обеспечивают изоляцию namespace. Запросы к плану или прогону из чужого namespace получают 404 Not Found (без утечки факта существования), а не 403 Forbidden.

Заметки для администраторов

Test Plans деградируют мягко в зависимости от того, что подключено на admin-узле:

  • Single-node / SQLite desktop — работает end-to-end, но без webhook-диспетчера и кросс-нодового SSE fan-out. Endpoint ad-hoc отвечает 503, если оркестратор не запущен.
  • Кластер / PostgreSQL — планировщик, webhook-диспетчер и cleaner артефактов работают только на лидере. Кросс-нодовые SSE-подписчики получают обновления через NOTIFY.

Релевантные переменные окружения

Переменная По умолчанию Назначение
MOCKARTY_BLOB_BACKEND fs Бэкенд хранения артефактов отчётов (Allure JSON/ZIP, кэш HTML) и TCM-вложений — теперь у них общий бэкенд. fs (локальная ФС) или s3 (любое S3-совместимое хранилище / MinIO). Для кластерных сборок ставьте s3, чтобы все реплики читали одни артефакты без общего тома. Полный набор MOCKARTY_BLOB_S3_* см. в TCM Вложения → Бэкенды хранилища.
MOCKARTY_BLOB_FS_ROOT ./data/blobs Корень файлового бэкенда (когда MOCKARTY_BLOB_BACKEND=fs). В кластере укажите общий том (NFS / PVC) либо переключитесь на бэкенд s3.
MOCKARTY_ARTIFACTS_PATH ./data/artifacts Устаревшая настройка сохранена для совместимости со старыми отчётами. Новые артефакты используют blob-бэкенд выше.
MOCKARTY_EXTERNAL_RUN_CONCURRENCY NumCPU × 8 (зажато [4, 4096]) Глобальный лимит одновременных загрузок результатов/Allure. При достижении лимита новые загрузки получают 429 Retry-After, а CLI/SDK повторяют с backoff — это сдерживает большой CI-флот вместо OOM админ-ноды.
MOCKARTY_EXTERNAL_RUN_SOURCE_CONCURRENCY global − max(2, global/8) Лимит этих слотов на один namespace. CI-флот одного шумного тенанта может занять не более стольких слотов из глобального пула, поэтому никогда не выберет весь пул и не заморит другие namespace. По умолчанию оставляет небольшой резерв ниже глобального лимита (пропускная способность одного тенанта остаётся почти полной).

Если бэкенд хранилища недоступен, Mockarty откатывается к генерации отчёта на лету. Остальное поведение (расписания, вебхуки, отчёты) настраивается во время выполнения через Admin Panel и REST API; других специфичных для Test Plans переменных окружения нет.

Поиск проблем

  • 503 на POST /test-runs/ad-hoc — оркестратор не подключён на этом узле. Используйте обычный POST /test-plans/:id/run или включите оркестратор в деплое.
  • Webhook не приходит — проверьте, что он enabled, URL по HTTPS и не внутренний адрес, а секрет совпадает на получателе. Дёрните mockarty-cli testplan webhook test <plan> <webhookId>.
  • Отчёт возвращает пустой JSON — прогон ещё идёт или все элементы пропущены (например dependency_failed). run-status показывает статус по каждому элементу.
  • Расписание не срабатывает — в кластере убедитесь, что есть лидер, и что расписание enabled; следующий момент срабатывания виден в списке расписаний.
  • 412 Precondition Failed на PATCH — локальный etag устарел. Перечитайте план и повторите с новым updatedAt.