Документация Отправка результатов CI (External Runs)

Отправка результатов 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.