Отправка результатов CI-тестов в Mockarty (External Runs)
Отправляйте результаты из любого тест-фреймворка — go test, pytest, JUnit,
Playwright, собственный раннер — в Mockarty TCM. Каждый отправленный прогон
становится прогоном тест-кейса со степами, вложениями и историей и виден в тех
же отчётах, что и прогоны, выполненные самим Mockarty.
Два способа отправки:
| Режим | Когда использовать |
|---|---|
Одним запросом — один POST с готовым прогоном целиком |
Прогон уже завершён (например, вы парсите JUnit XML в конце джобы) |
| Потоковый lifecycle — create → степы по мере выполнения → finish | Длинные CI-джобы: степы приходят по мере готовности, ретраи безопасны, можно прикладывать файлы |
Архив allure-results — загрузка всей папки allure-results |
Ваш фреймворк уже пишет Allure-результаты, и вы хотите одну загрузку в конце джобы |
Все режимы привязаны к пространству имён и аутентифицируются вашим API-токеном
(Authorization: Bearer <token>).
Для отправки результатов, загрузки архива Allure и изменения потокового
прогона токену требуется test_case:write. Для чтения потокового прогона или
скачивания его вложения требуется test_case:read в том же пространстве.
Одним запросом
curl -X POST "http://localhost:5770/api/v1/namespaces/<ns>/tcm/external-runs" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"caseName": "checkout smoke",
"framework": "pytest",
"status": "passed",
"idempotencyKey": "job-4211/result-17",
"steps": [
{"name": "login", "status": "passed"},
{"name": "add to cart", "status": "passed"}
]
}'
Пакетный вариант принимает несколько прогонов сразу:
POST .../tcm/external-runs/batch.
Необязательный idempotencyKey рекомендуется для CI. Повтор того же
payload с тем же ключом возвращает прежний runId, в том числе после
перезапуска сервера; изменение данных с прежним ключом даёт 409.
Используйте отдельный ключ для каждой попытки результата. Без ключа
каждый принятый POST создаёт новую попытку.
Потоковый lifecycle
Lifecycle-эндпоинты живут под .../tcm/external-runs/lifecycle — их использует
пакет externalruns Go SDK.
1. Создайте прогон
curl -X POST "http://localhost:5770/api/v1/namespaces/<ns>/tcm/external-runs/lifecycle" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "nightly regression", "framework": "go-test", "external_id": "ci-run-4211"}'
В ответе — id прогона, который используют все следующие вызовы,
и монотонная revision (она же возвращается в заголовке ETag).
external_id делает создание идемпотентным: при ретрае CI-джобы повторное
создание с тем же external_id вернёт существующий прогон, а не дубликат.
2. Отправляйте степы по мере выполнения
curl -X POST ".../tcm/external-runs/lifecycle/<runId>/steps" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
-H 'If-Match: "<revision>"' \
-H "Content-Type: application/json" \
-d '{"steps": [
{"step_key": "auth-01", "name": "login", "status": "passed", "duration_ms": 420},
{"step_key": "cart-01", "name": "add to cart", "status": "failed",
"message": "expected 200, got 500"}
]}'
У каждого степа есть step_key, который выбираете вы. Повторная отправка степа
с тем же step_key обновляет его, а не дублирует — ретраи батчей безопасны.
Вложенные степы задаются через parent_key. Передавайте актуальную
кавыченную revision в If-Match, чтобы запоздавший CI-worker не перезаписал
новые шаги: устаревшая ревизия даёт HTTP 409. Заголовок необязателен для совместимости с
клиентами, выпущенными до появления fence.
3. Приложите файлы (опционально)
curl -X POST ".../tcm/external-runs/lifecycle/<runId>/attachments" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
-H 'If-Match: "<revision>"' \
-F "file=@screenshot.png"
Один файл на запрос, до 25 МиБ каждый.
4. Завершите прогон
curl -X POST ".../tcm/external-runs/lifecycle/<runId>/finish" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
-H 'If-Match: "<revision>"' \
-H "Content-Type: application/json" \
-d '{}'
Пустое тело допустимо — статус прогона выводится из степов (есть failed →
failed, есть broken → broken, иначе passed). Либо задайте явно:
{"status": "failed", "summary": "..."}. На finish прогон попадает в TCM, в
ответе приходят привязанные id кейса/прогона; дальше он виден в «Тестовых
прогонах» и истории кейса как обычный прогон. Для следующего If-Match
используйте новую ревизию из каждого ответа; в Go, Python и Java SDK для этого
есть явные lifecycle-методы *AtRevision.
Просмотр прогонов
# Один прогон со степами и вложениями
curl ".../tcm/external-runs/lifecycle/<runId>" -H "Authorization: Bearer $MOCKARTY_API_TOKEN"
# Список (фильтры: suite_id, framework, status, external_id; cursor-пагинация)
curl ".../tcm/external-runs/lifecycle?status=failed&limit=20" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN"
Архив allure-results
Если ваш фреймворк уже пишет каталог allure-results, заархивируйте его и
отправьте одним запросом. Каждый *-result.json становится прогоном тест-кейса;
кейсы, которых ещё нет, создаются автоматически и раскладываются по папкам,
выведенным из Allure-меток suite / feature.
cd allure-results && zip -qr ../allure-results.zip . && cd ..
curl -X POST "http://localhost:5770/api/v1/namespaces/<ns>/tcm/allure-results" \
-H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @allure-results.zip
Multipart тоже работает — -F "archive=@allure-results.zip".
Необязательные query-параметры:
| Параметр | Что делает |
|---|---|
placeInTree=false |
Оставить новые кейсы в корне пространства имён, не строя дерево папок по suite/feature |
launchId=<runId> |
Привязать environment.properties / categories.json / executor.json из архива к этому запуску, чтобы отчёт их показал |
Как читать ответ
Код ответа говорит, что реально загрузилось — по нему и стройте гейт в пайплайне:
| Код | outcome |
Значение |
|---|---|---|
200 |
ok |
Все результаты из архива сохранены |
200 |
metadata_only |
Результатов в архиве не было, только метаданные запуска — они сохранены |
207 |
partial |
Часть результатов отклонена или не удалось сохранить запрошенные метаданные запуска. Загрузка неполная — смотрите errors |
422 |
rejected |
Результаты не сохранены. Либо в архиве нет файлов *-result.json, либо отвергнуты все результаты, в том числе с отсутствующим вложением |
400 |
— | Загруженное не читается как zip или не указано пространство имён |
429 |
— | Слишком много одновременных загрузок — повторите после задержки из Retry-After |
Тело ответа одинаковое во всех случаях:
{
"namespace": "qa",
"outcome": "partial",
"message": "stored 8 of 10 result(s); 2 rejected — the run history is incomplete, see `errors`",
"results": 10,
"ingested": 8,
"created": 3,
"skipped": 1,
"failed": 1,
"placed": 3,
"attachments": 12,
"errors": [
"unparseable result JSON: unexpected end of JSON input",
"checkout.test_pay: case name is required"
]
}
ingested— сколько результатов сохранено; именно этот счётчик проверяйте
в пайплайне.results— сколько результатов было в архиве, поэтому
ingested/resultsдаёт честное «загрузилось N из M».created— сколько кейсов создано заново,placed— сколько разложено по
папкам,attachments— сколько вложений найдено в архиве.skipped— файлы результатов, которые не удалось прочитать,failed—
прочитанные, но отвергнутые.errorsназывает каждый и причину (не более 50
причин; при обрезании список прямо это сообщает).
Самая частая причина 422 — загрузили сгенерированный каталог allure-report
вместо исходного allure-results, который пишет адаптер.
Go SDK
Пакет externalruns оборачивает lifecycle-эндпоинты:
import "github.com/mockarty/mockarty-go/externalruns"
runs, _ := externalruns.NewClient("http://localhost:5770", "<ns>", apiToken)
run, _ := runs.CreateRun(ctx, externalruns.CreateRunRequest{
Name: "nightly regression", Framework: "go-test",
})
defer runs.FinishRun(ctx, run.ID, externalruns.FinishRunRequest{})
Полный пример с автоматической записью пер-RPC степов — в
SDK protocol clients.
Полезно знать
- Ретраи безопасны на всём пути:
external_idдедуплицирует создание
прогона,step_key— степы. - Под большой параллельной CI-нагрузкой
finishможет ответить429с
заголовкомRetry-After— повторите после указанной задержки. - Мигрируете с TestIT, Allure, Zephyr, TestRail или QASE? См.
гайд по интеграции с TestIT — импортёры используют
тот же ingest.