Тест-планы
Тест-планы — это главный оркестратор в 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.
Создание плана

Веб-интерфейс
- Откройте
/ui/test-plans(Test Plans в боковой панели). - Нажмите + New Test Plan.
- Заполните
Name, опциональноDescription. - Нажмите Добавить элементы, чтобы открыть пикер. В нём по вкладке на каждый
источник — тест-кейсы, коллекции API, UI-тесты, нагрузка, фаззинг, хаос,
контрактные эксперименты и вложенные планы. Ищите внутри вкладки, отмечайте всё
нужное (выбор сохраняется при переключении вкладок) и нажмите Добавить,
чтобы добавить всё разом. Каждый элемент сохраняет своё настоящее имя, и план
читается как чек-лист. - Переупорядочьте элементы перетаскиванием. Задайте
delayAfterMsпри необходимости паузы после элемента. - Нажмите Save — план будет создан в текущем namespace.
- Откроется страница плана
/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, а не пересобирать вручную.
- Откройте Test Plans, нажмите Импорт в панели.
- Выберите формат источника:
- Allure TestOps (testplan.json) — загрузите
testplan.jsonиз вашего
Allure (Mockarty экспортирует тот же формат, поэтому экспортированный план
импортируется обратно без потерь). - Экспорт Test IT — загрузите экспорт Test IT; Mockarty сначала
импортирует кейсы, затем собирает план, который их запускает.
- Allure TestOps (testplan.json) — загрузите
- При желании задайте имя плана и нажмите Импорт.
Каждая запись в файле сопоставляется с кейсом 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-тест, который воспроизводится в раннере-расширении браузера.
Три способа заблокироваться до конца прогона:
--waitв CLI:mockarty-cli testplan run <plan> --wait --timeout 5m. Коды выхода:0завершено,1упало,2отменено,3таймаут.- SDK
WaitForRun(Go / Python sync / Java): опрашивает/runs/:runIdс настраиваемым интервалом. - 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.