Тема
Прогоны
Эндпоинты прогонов: импорт отчётов из CI, ручные прогоны и их результаты, корзина, конфигурации, статусы результатов, исследовательские сессии, майлстоуны, экспорт отчётов и публичные ссылки на них.
Список и чтение прогонов
GET /api/runs/search— одна страница прогонов, сначала новые, со счётчиками, но без результатов.?mine=1— только прогоны, где у вызывающего осталась работа: выданные ему результаты, которые ещё не выполнены, или выданный ему прогон, который ещё идёт. У каждого естьmineLeft— сколько его результатов не проверено.?q=TC-100— прогоны, в которых есть этот кейс, вдобавок к прогонам с таким названием.?milestone=MS-3— см. Майлстоуны.
GET /api/runs/summary— итоги по всем прогонам и окружения, которые в них указаны.GET /api/runs— самые новые прогоны вместе с результатами, оставлен для API-клиентов: 500, если?limit=не задаёт иначе, не больше 5000, и не больше прогонов, чем вмещают 50 000 результатов в сумме (KVALLI_MAX_LISTED_RESULTS). Если более старые прогоны отброшены из-за этого, приходит заголовокX-Kvalli-Truncated: true.GET /api/runs/:id— один прогон с результатами,reruns(прогоны, запущенные как его перезапуски) и первым миллионом символов загруженного отчёта вlogs(logsTruncated: true, если отчёт длиннее).GET /api/runs/:id/report— весь отчёт в том виде, в каком его отправили, как скачивание.DELETE /api/runs/:id— удалить прогон (он попадает в корзину).
Изменения прогона возвращают прогон, но без logs.
PATCH /api/runs/:id/items/:itemId/case{ caseId }— привязать результат к кейсу (runs:writeиcases:write). Кейс запоминает ключи, под которыми известен автотест, так что тот же тест в следующем отчёте привяжется сам. Ответ —{ run, learned }.GET /api/runs/defect-tracker—{ "ready": true }, если трекер задач подключён и по результату можно завести дефект (runs:read).
Корзина
GET /api/trash— удалённые кейсы и прогоны проекта, сначала новые, с указанием, кто и когда их удалил.POST /api/trash/cases/:id/restore,POST /api/trash/runs/:id/restore— восстановить один.DELETE /api/trash/cases/:id,DELETE /api/trash/runs/:id— удалить навсегда, вместе с файлами.POST /api/trash/{kind}/restore,POST /api/trash/{kind}/purge{ ids }дляcases,runs,plans,milestones,sessionsиrequirements— до 500 за раз, с теми же правами, что нужны для удаления (удаление навсегда требует ещёcases:delete). Ответ —{ done: [...ids], failed: [{ id, error }] }.
Тест-планы, майлстоуны, сессии и требования указываются по ключам в том виде, в каком они показаны (TP-3, MS-2, ES-4, ключ требования); GET /api/trash перечисляет их как { plans: { items, total }, … }. Восстановленный майлстоун снова собирает под собой прогоны, тест-планы, сессии и вложенные майлстоуны, которые всё ещё ни к чему не отнесены; сессия возвращается со своими заметками, требование — со связями с кейсами. Ежедневная очистка удаляет то, что пролежало в корзине 30 дней. DELETE /api/testcases/:id и DELETE /api/runs/:id перемещают записи в корзину, а не удаляют их.
Импорт отчёта из CI
POST /api/runs/import — импорт отчёта из CI. Максимальный размер — 50 МБ. Нужно право runs:import: оно есть у каждой роли, которая записывает прогоны, и у ключа с runs:write.
Архивы не распаковываются
На сервере нет обработки архивов: здесь ничего не распаковывается. ZIP с результатами Allure распаковывает в браузере диалог импорта, который загружает объединённый JSON через POST /api/runs; пайплайн не может отправить архив на этот маршрут.
Что принимает маршрут и как он определяет формат:
| Тело | Как читается |
|---|---|
{ name?, fileType?, rawContent, source?, author?, milestone? } | fileType как указан, junit, если не указан |
тело не JSON (text/*, application/xml, application/octet-stream, отправка формы), начинающееся с < | JUnit XML |
тело не JSON, не начинающееся с < | Playwright JSON |
JSON-объект без rawContent | Playwright JSON |
Таким образом, тело без обёртки — это всегда JUnit или Playwright. Чтобы импортировать что-то другое — Allure CSV или результаты Allure, уже распакованные в JSON, — оберните тело и назовите формат: { fileType: "allure-csv", rawContent: "…" }. На этом маршруте fileType не сверяется со списком; проверяет его POST /api/runs (junit, playwright, csv, allure-csv, allure, allure-zip), и allure-zip там тоже означает уже распакованный JSON, а не байты архива. Пустое тело получает 400.
?environment=(или?env=) и?release=(или?version=) записывают, где выполнялся прогон и на какой версии. Необязательный свободный текст до 64 символов; обёрнутое тело может передать их как поля. Один и тот же набор, падающий на staging и проходящий на review-стенде, — это два факта, а не один нестабильный тест.?milestone=относит прогон к майлстоуну, названному ключом (MS-3) или точным названием (без учёта регистра). Несуществующий или завершённый майлстоун не ломает загрузку: прогон ни к чему не относится, а ответ объясняет причину вwarning.buildId,branch,commit,buildUrl(в запросе или в обёрнутом теле) записывают сборку CI. СbuildIdповторная загрузка той же сборки отвечает200с уже созданным прогоном (duplicate: true), а не создаёт второй.- Наборы JUnit могут быть вложенными; падение сохраняет сообщение и текст как стек. Результат теста Playwright — его итоговый результат (прошёл при повторе — значит прошёл, с пометкой «нестабилен»), и каждый проект Playwright — отдельный результат.
x-api-key, который недействителен, истёк или отозван, получает401; он никогда не принимается за другого вызывающего. Так отправляют отчёты клиенты из раздела Отправка результатов из CI.
POST /api/runs { name, fileType, rawContent, environment?, release?, milestoneKey?, source? } — загрузка, которую отправляет диалог импорта (только JSON).
Привязка результатов к кейсам
- Ключи кейсов в названиях тестов (
TC-101 checkout succeeds,@case: TC-101) привязывают каждый результат к его тест-кейсу; то же делают JUnit-свойство<property name="case" value="TC-101"/>у теста и, в отчётах Playwright, аннотации и теги. - Результат, не называющий кейс, привязывается по автотесту.
automationRefкейса — полное имя теста в том виде, в каком его пишет Allure (module#testдля pytest,pkg.Class.methodдля JUnit и TestNG,file.spec.ts › Describe › titleилиfile.spec.ts:21:3для Playwright, vitest и jest). Оно сопоставляется с classname и name в JUnit, с файлом, блоками describe, названием и позицией в Playwright или сfullNameрезультата Allure. - Метка
AS_IDрезультата Allure илиallure_idв Allure CSV привязывает его к кейсу, перенесённому из этого тест-кейса TestOps. - Совпадение, общее для нескольких кейсов, ничего не привязывает; ключ кейса в тесте всегда важнее. Кейсы, импортированные из Allure TestOps, несут свой
automationRef, поэтому их результаты привязываются без изменений в тестах. POST|PUT /api/testcasesпринимаютautomationRef— автотест, которому соответствует кейс; очистка значением""отвязывает будущие результаты.- Результат Playwright сохраняет шаги
test.step(вложенные — под именем родителя:Log in › Submit the form), и из JSON-репортера, и из репортера Kvalli; шаг, который упал, — failed.
Кейсы из результатов прогона
POST /api/runs/:id/cases-from-results { itemIds?, intoFolder? } (runs:write и cases:write) — для каждого теста CI-прогона, чьи результаты не привязаны ни к одному кейсу, создаёт черновик кейса. В интерфейсе это кнопка «Создать кейсы (N)» в прогоне.
- Название — название теста. Имя, записанное как код, переводится в слова:
addsItem— «Adds item»,test_pays_by_card— «Pays by card». - Папки лежат внутри
intoFolder(интерфейс передаёт имя прогона без номера сборки):- Playwright — spec-файл и блоки describe;
- JUnit — класс без пакета:
com.shop.CartTest$Checkout→Cart›Checkout; - pytest — модуль и класс:
tests.test_cart.TestCart→cart›Cart; - vitest и jest — файл и блоки describe.
- Шаги кейса — шаги
test.stepиз результата; если тест шёл в нескольких браузерах, берётся тот, где шагов больше. automationRef— полное имя теста, поэтому результаты этого прогона привязываются сразу, а следующего отчёта — сами.- Один кейс на тест, в скольких бы браузерах и с какими бы параметрами он ни шёл (
test_x[a-1]иtest_x[a-2]— один кейс). Тест, которому уже соответствует какой-то кейс, привязывается к нему, а не получает второй. Повторный вызов ничего не создаёт. - Учитываются только результаты с ключами автотеста, до 1000 за вызов;
itemIdsсужает выбор. - Ответ:
{ run, created: [{ key, title }], reused, linked };201, если кейс создан, иначе200.
Прогон, который заполняется по ходу тестов
Так работает kvalli watch (см. CI) — как allurectl watch в Allure TestOps. Все три запроса требуют runs:import.
POST /api/runs/live{ name?, fileType?, source?, environment?, release?, buildId?, branch?, commit?, buildUrl?, pipelineId? }— открыть пустой прогон в статусеrunningсlive: true(201). Тот жеbuildIdвозвращает уже открытый прогон (200,duplicate: true). Прогон сразу связывается со своим пайплайном, поэтому запуск плана видит его в прогрессе.POST /api/runs/:id/live{ fileType, rawContent }— результаты, закончившиеся с прошлой порции. Результат Allure узнаётся поhistoryId: повтор теста из более поздней порции заменяет его прежнюю попытку. Ответ — счётчики прогона на сейчас и сохранённые результаты этой порции (tests, с ихidдля файлов). Статус остаётсяrunning. Завершённый прогон отвечает409.POST /api/runs/:id/finish{ durationMs? }— завершить: статус по результатам, длительность — сколько шла джоба. Повторный вызов ничего не меняет; воркфлоу получают событие импорта один раз.
Пока прогон идёт, его страница обновляется сама и показывает «Результаты поступают».
Вебхуки пайплайнов
GET /api/hooks, POST /api/hooks { sourceId }, DELETE /api/hooks/:id — вебхуки пайплайнов для репозиториев-источников проекта (settings:write, модуль ci).
POSTсоздаёт вебхук источника или выдаёт ему новый секрет и отвечает{ hook: { id, path, provider }, secret }; секрет больше никогда не показывается.POST /api/hooks/gitlab/:idиPOST /api/hooks/github/:id— вызовы со стороны провайдера: без сессии, проверяются секретом (GitLab —X-Gitlab-Token, GitHub —X-Hub-Signature-256), сразу отвечают 202. Завершённый пайплайн становится прогоном так же, как при импорте (см. Отправка результатов из CI).
Ручные прогоны
Запуск
POST /api/runs/manual — запустить прогон, который будет выполнять человек, по тест-плану (planKey) или по списку caseIds, с необязательными assignee, environment, release и milestoneKey (404 для несуществующего майлстоуна, 409 для завершённого).
configurations— варианты, выбранные в каждой группе, например{ "Browser": ["Chrome", "Firefox"], "OS": ["Windows"] }, — запускает по прогону на каждую комбинацию, с названиями видаName · Chrome · Windows, каждый сconfiguration: [{ group, option }]. В ответеrun— первый прогон,runs— все. Названия сравниваются без учёта регистра; название, которого нет в проекте, или больше 25 прогонов —400.- Каждый результат начинается как
untested; если тест-план называет кейс, которого больше нет, он указан вmissing. - Кейс с
parametersдаёт по результату на каждую строку, с названием видаTC-7 Title [browser=chrome]; значения подставляются в скопированные шаги и сохраняются на результате какparameters: [{ name, value }].
Результаты
PATCH /api/runs/:id/items/:itemId— записать вердикт (passed,failed,blocked,skipped,untestedили ключ одного из собственных статусов результатов проекта) и необязательныйcomment. Статус проекта хранится как базовый вердикт, которому он соответствует, и указывается рядом вcustomStatus. Прогон пересчитывается по результатам: он остаётсяrunning, пока что-то не проверено, а падение заглушённого кейса не роняет его.PATCH /api/runs/:id/verdicts(runs:write) — записать вердикт сразу для нескольких результатов одного прогона одним запросом, чтобы отметить пачку в окне ручного прогона. К каждому применяются те же правила, что и для одиночного маршрута.GET /api/runs/:id/items/:itemId/history(runs:read) — как этот тест проходил в более ранних прогонах проекта; это показывает панель результата рядом с падением, и так становится виден нестабильный тест.PATCH /api/runs/:id/items/:itemId/steps/:index— записать результат одного шага (status:passed,failed,blocked,skipped,untested) и то, что на самом деле наблюдалось (actual; пустая строка очищает его).indexсчитается с 0 по шагам, которые прогон скопировал из кейса при запуске. Вердикт кейса не меняется.404для шага за пределами списка.PATCH /api/runs/:id/assignee—{ itemIds, assignee }выдаёт результаты человеку или забирает их приassignee: null; id, не являющиеся результатами этого прогона, пропускаются, аassignedговорит, сколько изменено. После этого у каждого результата есть свойassignee.
Файлы результатов
POST /api/runs/:id/items/:itemId/attachments— сохранить файл у одного результата: скриншот падения, видео, трассу. Файл — это сырое тело запроса (curl --data-binary @shot.png -H "Content-Type: image/png"), имя задаётся вX-Filename(в percent-encoding для имён не в ASCII) или в?filename=. До 25 МБ на файл и 50 файлов на результат;404для результата не из указанного прогона. Нужноruns:import, так что файлы может отправлять CI-ключ.GET /api/runs/:idперечисляет файлы каждого результата вattachments.GET|DELETE /api/runs/:id/items/:itemId/attachments/:attachmentId— отдать или удалить файл. Отдаётся как скачивание;?inline=1показывает изображение или видео на месте. HTML, SVG и другие типы документов всегда отдаются как скачивание. Удаление прогона удаляет его файлы.
Дефекты по результатам
POST /api/runs/:id/items/:itemId/defect — завести дефект по упавшему или заблокированному результату в настроенном трекере и записать ключ задачи на этом результате. Любой другой вердикт получает 409, как и повторная попытка, когда ключ уже записан.
Перезапуск и закрытие
POST /api/runs/:id/rerun— новый ручной прогон по кейсам этого прогона в статусахfailedиblocked(scope: "failed", по умолчанию) или по всем ("all", копия), с необязательнымиnameиassignee. Окружение, релиз, тест-план и исполнитель каждого кейса переносятся, а новый прогон указывает своё происхождение вrerunOf. Параметризованный кейс перезапускается для тех строк, которые выполняли выбранные результаты, с теми значениями, что у них были, даже если его таблица с тех пор изменилась. На импортированном прогоне берутся результаты, привязанные к кейсу, по одному на кейс; непривязанные результаты считаются вnotRerunnable.400, если перезапускать нечего.POST /api/runs/:id/close— сделать вердикты окончательными: доPOST /api/runs/:id/reopenвердикты, результаты шагов, исполнители и файлы прогона получают409. Если остались непроверенные кейсы, ответ —409с их числом вuntested, если толькоskipUntested: trueне помечает их пропущенными. Прогон записываетclosedAtиclosedBy.
Конфигурации
То, на чём выполняется прогон и что не указано в кейсах: браузер, операционная система, устройство.
GET /api/configurations— группы проекта,{ id, name, options }, по порядку.POST /api/configurations{ name, options },PUT /api/configurations/:id{ name?, options? }иDELETE /api/configurations/:idтребуютsettings:write.
Название группы уникально без учёта регистра; варианты хранятся обрезанными по краям, каждый один раз, не больше 30. Прогон хранит свою комбинацию как текст, поэтому переименование или удаление варианта никогда не меняет прогон. Перезапуск сохраняет конфигурацию прогона, который он перезапускает.
Статусы результатов
GET /api/result-statuses— собственные статусы проекта,{ key, label, base }, например «Пройден с замечаниями», который считается какpassed.POST /api/result-statuses{ label, base, key? },PUT /:key{ label?, base? },DELETE /:keyтребуютsettings:write.
Ключ строится из названия; имена базовых вердиктов заняты. Изменение того, чем считается статус, или его удаление не переписывает результаты, которым он уже присвоен. В интерфейсе — «Настройки → Статусы результатов».
Исследовательские сессии
Чартер, который исследуют в рамках таймбокса (ES-1, ES-2, … в каждом проекте).
GET /api/sessions(сначала идущие, сnoteCounts),GET /:key(сnotes).POST /api/sessions{ charter, area?, tester?, environment?, timeboxMinutes? (60), milestoneKey? },PUT /:key,POST /:key/complete{ summary? },POST /:key/reopen,DELETE /:key.POST /:key/notes{ kind: note | bug | question | idea, body }— только пока сессия идёт (409после завершения);PATCHиDELETE /:key/notes/:noteId.POST /:key/notes/:noteId/defect{ title?, dryRun? }заводит заметку как дефект в настроенном трекере и записывает на нейdefectKey(dryRun: trueотвечает200с названием и описанием, которые были бы отправлены, и ничего не записывает).POST /:key/notes/:noteId/caseделает из заметки черновик кейса (с названием по её первой строке, в папке Exploratory / область сессии) и записываетcaseKey.
Каждое из двух последних действий выполняется один раз: повторный вызов получает 409.
Майлстоуны
Релиз, спринт или тестовый цикл, к которому относятся прогоны. Ключи — MS-1, MS-2, … в каждом проекте. Майлстоун может быть вложен в один другой (спринт в свой релиз), на один уровень, и тогда считает прогоны вложенных майлстоунов своими.
GET /api/milestones— все майлстоуны, сначала открытые и с ближайшим сроком, каждый сprogress:runs,openRuns(ручные прогоны, которые никто не закрыл, и импортированные прогоны, которые ещё идут) и его результаты по вердиктам (results,passed,failed,blocked,skipped,untested).GET /api/milestones/:key—{ milestone, children, runs }: майлстоун, вложенные в него майлстоуны и прогоны, отнесённые к любому из них.POST /api/milestones{ name, description?, parentKey?, startDate?, dueDate? }иPUT /api/milestones/:keyс любым из этих полей (nullочищает поле). Даты в форматеYYYY-MM-DD; срок раньше даты начала, родитель, который сам вложен в другой, или майлстоун с вложенными майлстоунами, помещаемый в другой, получают400. Нужноruns:write.POST /api/milestones/:key/complete— майлстоун завершён и больше не принимает прогоны. Если остались открытые прогоны, ответ —409с их числом вopenRuns, если только не переданоforce: true.POST /api/milestones/:key/reopenотменяет завершение.DELETE /api/milestones/:key— его прогоны сохраняются и ни к чему не относятся; вложенные майлстоуны остаются сами по себе.PATCH /api/runs/:id/milestone—{ milestoneKey }относит прогон к майлстоуну или, приnull, ни к какому. Прогон несётmilestoneKeyиmilestoneName;GET /api/runs/search?milestone=MS-3выводит прогоны, отнесённые к нему или к вложенному в него майлстоуну. Перезапуск остаётся в майлстоуне своего прогона, пока тот открыт.
Экспорт отчётов и публичные ссылки
Эндпоинты /api/reports входят в модуль analytics (тариф Pro).
GET /api/reports/skills(runs:read) — навыки отчётов, по которым агент может написать отчёт; из них выбирает панель «Отчёты».GET /api/reports/dashboard(runs:read) — дашборд проекта в виде закреплённых на нём разделов.POST /api/reports/dashboard/sections(agents:write) закрепляет раздел, аDELETE /api/reports/dashboard/sections/:index(agents:write) убирает раздел на этой позиции. Дашборд принадлежит проекту и общий для всех его участников, поэтому для закрепления нужно больше, чемruns:read.POST /api/reports/generateвозвращает отчёт как данные. Роль безmodels:use(наблюдатель) получает отчёты, макет которых задан навыком, без текстовых частей; всё, что модель написала бы или скомпоновала, получает403. Панель «Отчёты» сохраняет отчёт как HTML, как книгу Excel (обзорный лист и по листу на раздел, числа — числами) или печатает его (сохранение в PDF в диалоге печати браузера).POST /api/reports/shares{ report, expiresInDays? (30, up to 90) }(нужноreports:share: лид, администратор) — ссылка только для чтения для людей без учётной записи. Страница рендерится на сервере из данных отчёта, с экранированием и без скриптов, и отдаётся по адресу/share/<token>с CSP, которая ничего не загружает, сnoindexи без кеширования.urlв ответе — единственный раз, когда выдаётся ссылка: хранится только хеш токена. Срок — 30 дней по умолчанию, не больше 90.GET /api/reports/shares— ссылки проекта с числом просмотров (views).DELETE /api/reports/shares/:id(reports:share) — сразу отзывает ссылку; это может сделать только её автор или администратор.
Истёкшая, отозванная или неизвестная ссылка отвечает одной и той же страницей 404. GET /share/:token — публичная страница без сессии; токен — единственные учётные данные.