Тема
Тест-кейсы
Эндпоинты репозитория тестов: кейсы, папки, общие шаги, поля кейсов, сохранённые виды, импорт из других систем и обсуждение кейсов.
Список и чтение
GET /api/testcases?limit=&offset=— кейсы проекта, сначала новые (created_at, затем номер кейса, так что страницы не повторяют и не пропускают кейсы), по одной странице:limit1–1000 (по умолчанию 500),offsetот 0 (по умолчанию 0). Тело ответа — по-прежнему простой массив кейсов; заголовокX-Total-Countговорит, сколько их в проекте, а пока остаются ещё, заголовокLink: </api/testcases?limit=500&offset=500>; rel="next"указывает следующую страницу (другие параметры запроса, напримерprojectId, в нём сохраняются).limitилиoffset, который не является целым числом в допустимом диапазоне (0,1001,-1,1e2, пустое значение, параметр указан дважды), получает400. Чтобы прочитать весь проект, следуйте поLink, пока он не исчезнет.GET /api/testcases/search— одна страница совпадений (её используют экраны).GET /api/testcases/stats— счётчики, которые показывает дашборд.GET /api/testcases/:id— один кейс.
Создание, изменение, удаление
POST /api/testcases— создать кейс.PUT /api/testcases/:id— изменить его; передайтеversionилиIf-Match, при несовпадении ответ —409с копией кейса на сервере.DELETE /api/testcases/:id— удалить его (кейс попадает в корзину, см. Корзина).
Массовые операции
POST /api/testcases/bulk— создать много кейсов (импорт CSV, принятый результат агента).POST /api/testcases/bulk-delete{ ids }— удалить много кейсов, не больше 1000.POST /api/testcases/bulk-update{ ids, changes, versions? }— изменить много кейсов сразу (не больше 2000).changesможет содержатьpriority,owner(пустое значение очищает его),mutedвместе сmuteReason,customField: { key, value? }(без значения поле очищается) иstatus. Изменение атрибутов сохраняет утверждение каждого кейса; каждый изменённый кейс получает ревизию. Дляstatusнужно правоcases:approve; кейс, уже находящийся в этом статусе, не трогается, а кейс, который нельзя перевести в этот статус, называется вrefused.versionsсопоставляет id кейса с версией, которую видел ревьюер: кейс, изменившийся с тех пор, отклоняется (сcurrentVersion), а не утверждается непросмотренным.POST /api/testcases/tags{ ids, add?, remove? }— изменить теги многих кейсов сразу, сохраняя их утверждение.
Утверждение
POST /api/testcases/:id/status { status, version? } переводит кейс между статусами draft, approved и deprecated (черновик, утверждён, устарел). Нужно право cases:approve (редактор, лид, администратор).
Утверждение собственной работы отклоняется с 403 для любой роли — то есть кейса, который написал вызывающий, или версии, в которой есть его правки с момента последнего утверждения, — если в проекте есть кто-то другой, кто может его утвердить. В команде без другого ревьюера автор может утвердить сам. GET /api/testcases/:id сообщает об этом в selfReview. Массовое утверждение называет такие кейсы в refused.
Для утверждения нужна version — версия, которую прочитал ревьюер (400 без неё, 409 с currentVersion, если кейс с тех пор изменился). Утверждение записывается к этой версии, поэтому последующая правка снова требует ревью.
История версий
GET /api/testcases/:id/revisions— сохранённые версии с разницей по полям (404для несуществующего кейса), сначала новые, постранично:?limit=(50 по умолчанию, не больше 200) и?before=— значениеnextBeforeпредыдущей страницы; на последней страницеnextBeforeнет.POST /api/testcases/:id/revisions/:version/restore{ version? }— записывает старую версию обратно как новую, а не переписывает историю.version— версия кейса на экране, целое число (400иначе); если есть более новая правка, ответ —409, как уPUT:{ error, code: "version_conflict", currentVersion, serverCase }. Поля, удалённые с тех пор, пропускаются, удалённые с тех пор варианты значений сохраняются, а поле, ставшее обязательным, сохраняет текущее значение кейса.
Файлы, прогоны и требования кейса
GET|POST /api/testcases/:id/attachments,GET|DELETE /api/testcases/:id/attachments/:attachmentId— файлы кейса, до 25 МБ каждый, всегда отдаются как скачивание.GET /api/testcases/:id/runs— прогоны, в которых тест заявил, что покрывает этот кейс.GET /api/testcases/:id/requirements— что этот кейс должен проверять.
Изменившиеся требования
Кейс, который Агент 1 написал по задаче Jira или YouTrack или по странице Confluence, хранит sourceRef (jira:KVQA-2, youtrack:SHOP-12, confluence:98307) и sourceVersion (последнее изменение задачи, версия страницы). Каждые шесть часов сервер заново читает эти источники (не больше 200 за проход; источник, который не удалось прочитать, ничего не меняет) и помечает isStale кейсы, написанные по более старой версии, — без новой версии кейса, так что утверждённый кейс остаётся утверждённым. ?drift=true выводит такие кейсы. Источники опрашиваются, а не присылают изменения: Kvalli не принимает вебхуки от трекеров.
POST /api/testcases/drift/check(cases:write) — прочитать источники этого проекта сейчас; ответ{ checked, markedStale, unreadable }.GET /api/testcases/:id/source— источник в текущем виде:{ ref, version, title, content, sourceType, label, caseVersion, changed }(content— то, по чему пишет Агент 1);404для кейса без такого источника,502, если его не удалось прочитать.POST /api/testcases/:id/source/accept(cases:write) — кейс сверен с текущим источником: пометка снимается,sourceVersionпереносится на текущую версию; содержимое кейса и его утверждение не меняются.
Папки
Кейс несёт folderPath — путь к его папке от корня (["Checkout", "Payments", "Cards"], или текст "Checkout/Payments/Cards" на входе). Имя папки не может содержать /, вложенность — до 12 уровней. suite и feature — первые два уровня, они синхронизируются с путём; вызывающий, который передаёт только их, всё равно размещает кейс, сохраняя всё, что лежит ниже этих двух уровней.
GET /api/testcases/search?folder=Checkout/Payments— кейсы в папке и во всех вложенных папках.GET /api/testcases/facets—folders: все папки с числом кейсов, лежащих непосредственно в них (у пустой папкиcount: 0).POST /api/testcases/folders{ path }— создать папку до того, как в неё что-то положено.PUT /api/testcases/folders{ path, newPath, merge? }— переименовать или переместить папку со всем содержимым. С существующей папкой она сливается только приmerge: true; без него ответ —409сcode: "folder_exists". Перемещённые кейсы получают новую версию и сохраняют утверждение.400при перемещении папки в саму себя,404для несуществующей папки.DELETE /api/testcases/folders{ path, deleteCases }— удалить папку и её подпапки; её кейсы поднимаются в родительскую папку (с верхнего уровня — вGeneral), еслиdeleteCasesне равно true.POST /api/testcases/move{ ids, folderPath }— положить кейсы в папку.PUT /api/testcases/suite/rename,DELETE /api/testcases/suite/delete,PUT /api/testcases/feature/rename,DELETE /api/testcases/feature/deleteпо-прежнему работают: suite — это папка верхнего уровня, feature — папка под ней.
Содержимое кейса
Кейс также несёт owner (ответственный) и muted с muteReason: падения заглушённого кейса записываются и учитываются, но не делают прогон красным.
preconditions— что должно выполняться до первого шага, отдельно от описания.automationRef— автотест, которому соответствует кейс; см. Привязка результатов к кейсам.
Общие шаги
Общий шаг — блок шагов, который хранится один раз и используется во многих кейсах. Кейс использует его, добавляя { "sharedStep": "SS-3" } в свои steps. При чтении кейса шаги блока подставляются на место ссылки, каждый с shared: { key, title, instance }; при сохранении шаги, прочитанные так, снова превращаются в ссылку, так что прочитанный и сохранённый кейс остаётся со ссылкой на общий шаг. Ссылка на общий шаг, которого нет в проекте, получает 400.
В кейсе не больше 500 шагов, общие шаги используются в нём не больше 50 раз, а с развёрнутыми общими шагами получается не больше 1000 шагов. Шаг — не больше 2000 символов на действие и 2000 на ожидаемый результат. Больше — 400.
GET /api/shared-steps— все общие шаги сusedBy.GET /api/shared-steps/:key— один общий шаг с кейсами (cases), которые его используют.POST /api/shared-steps{ title, steps }— создать.PUT /api/shared-steps/:key{ title?, steps?, version }— изменить его для всех кейсов, которые его используют (409, если он изменился послеversion;400с названием кейсов, если с более длинными шагами какой-то из них превысит 1000 шагов).DELETE /api/shared-steps/:key— удалить его и развернуть его шаги на месте в кейсах, которые его использовали.
Ручной прогон копирует шаги в том виде, в каком они есть при его запуске, поэтому последующее изменение общего шага не переписывает то, что уже было выполнено.
Параметры
parameters — { names, rows }: данные, с которыми выполняется кейс, по строке на итерацию, например { "names": ["browser"], "rows": [["chrome"], ["firefox"]] }. Шаг, общий шаг, используемый кейсом, предусловия и критерии приёмки записывают значение как {{browser}}; плейсхолдер, которому не соответствует ни один параметр, остаётся как есть. Не больше 20 параметров и 100 строк; имена состоят из букв, цифр, _, - и . и должны различаться не только регистром. Пустые строки отбрасываются. null снимает параметры с кейса. CSV Kvalli хранит таблицу как JSON в столбце parameters; списки значений параметров в экспортах Qase читаются как все их комбинации.
Теги
tags — метки поверх папок (массив или текст, разделённый запятыми). Хранятся обрезанными по краям, каждая один раз без учёта регистра; не больше 30 тегов по 64 символа.
GET /api/testcases/search?tags=smoke,mobile— кейсы, у которых есть все названные теги.GET /api/testcases/facets— списокtagsс их количеством.
Поля кейсов
customFields — значения собственных полей проекта, по ключу поля. Значения проверяются по определениям: ключ, которого нет ни у одного поля, или значение не того типа либо вне вариантов списка получает 400. Запрос, который передаёт customFields, должен заполнить все обязательные поля; запрос, который их не передаёт (агент, импорт), оставляет их как есть. Фильтр — ?cf.<key>=<value>; поле с множественным выбором совпадает по любому из своих значений.
GET /api/case-fields— поля проекта.POST /api/case-fields{ label, type, options?, required?, key? },PUT /api/case-fields/:key{ label?, options?, required?, position? }иDELETE /api/case-fields/:key— требуютsettings:write. Типы:text,textarea,number,select,multiselect,checkbox,date(YYYY-MM-DD),url(http или https). Ключ и тип поля после создания не меняются; удаление поля удаляет его значение из всех кейсов.GET /api/case-fields/:key/usage—{ cases }: у скольких кейсов в этом поле есть значение.
В интерфейсе поля настраиваются в разделе «Настройки → Поля кейсов».
Поиск и похожие кейсы
POST /api/testcases/best-match{ query }(cases:read) — «Упорядочить по совпадению»: кейсы проекта, содержащие слова запроса; сначала те, где есть вся фраза (в названии выше, чем в другом месте), затем по числу совпавших слов, у каждого —matchScore({ results, matches }). Модель не используется, поэтому эндпоинт работает на любом тарифе, включая Starter. Пустой запрос возвращает 10 самых новых кейсов.POST /api/testcases/similar{ title, steps?, excludeId?, limit? (5) }— кейсы, наиболее похожие на тот, что сейчас пишется, чтобы поймать дубликат до сохранения. Сравнение идёт по значимым словам (без служебных слов и окончаний): название — 60%, шаги — 40%, если шаги есть с обеих сторон. У каждого ответа естьscore(0–1, начиная с 0.5) и общие слова названия. Устаревшие кейсы иexcludeIdне учитываются. Редактор спрашивает подтверждение перед сохранением нового кейса, если его оценка относительно существующего — 0.8 или больше.
Сохранённые виды
GET|POST /api/saved-views, PUT /api/saved-views/:id, DELETE /api/saved-views/:id — сохранённые виды списка репозитория ({ name, shared, query: { folder, q, priority, review, owner, tag, fields, muted, drift } }).
Вид виден тому, кто его сохранил, а при shared — всему проекту. PUT принимает { query, version? } и меняет только сохранённый запрос; он запрещён (403) всем, кроме автора вида, включая обладателей settings:write. Удалить вид может его автор или пользователь с settings:write.
Импорт из других систем
POST /api/testcases/import{ content, format?, fileName?, intoFolder?, skipExisting?, dryRun?, testOpsProjectId?, folderFields? } — загрузить экспорт из другой системы: Qase JSON или CSV, выгрузку тест-кейсов Allure TestOps или TestRail в CSV либо собственный экспорт Kvalli в CSV или JSON. format: auto (по умолчанию, определяется по содержимому), kvalli-csv, kvalli-json, qase-json, qase-csv, testops-csv, testrail-csv.
Выгрузка TestRail (Test Cases → Export → CSV, все колонки):
- папки — из
Section Hierarchy(Regression Pack > Hosting > Отчёты); - шаблон «Test Case (Steps)»: шаги со строк под кейсом, каждый со своим ожидаемым результатом;
- шаблон «Test Case (Text)»: шаги, пронумерованные вручную (
1 Открыть,2.Войти,3) Проверить, строки с#), а нумерованный ожидаемый результат попадает в шаг с тем же номером; результат без своего шага остаётся общим ожидаемым результатом кейса, как записан; - шаблон «Checklist» — в описание;
- предусловия, постусловия и References переносятся, Created By — автор, Type, Automation Type и собственные колонки TestRail заполняют поля проекта;
sourceRef—testrail:C4643, повторный импорт не задваивает кейсы;- картинок, которые хранит TestRail, в файле нет: на их месте стоит «[TestRail attachment …]».
Выгрузка Allure TestOps (Тест-кейсы → Экспорт → CSV) читается так же, как проект через API, и превращается в те же кейсы:
шаги — из колонки
scenarioс вложенностью и ожидаемыми результатами; статус, владелец (роль Owner), теги, задачи и ссылки, автотест (full_name) переносятся; остальные роли и собственные поля заполняют поля проекта, недостающие поля создаются; несколько значений поля в файле записаны через запятую;папки — из полей
folderFields(например["Suite", "Project", "Feature"]), а без них — как при импорте через API без дерева команды;testOpsProjectId— номер проекта в TestOps, которого в файле нет: с нимsourceRefтакой же, как у импорта через API (testops:35:8120), и повторный импорт любым способом не задваивает кейсы;проект больше
KVALLI_MAX_IMPORT_CASESзаписывается частями;одиночная кавычка, которую TestOps не экранирует (
…'Got it"), читается как часть текста, а не как начало значения;в файле нет вложений, общих шагов (они выписаны в каждом кейсе), деревьев команды и hash автотеста, а у ссылок — только их имена: всё это переносит импорт через API.
Разделы и наборы становятся папками, разделённые шаги сохраняют ожидаемые результаты, предусловия, постусловия и ссылки попадают в описание.
Каждый кейс сохраняет своё происхождение в
sourceRef(qase:SHOP:45), поэтому приskipExisting(по умолчанию) повторный импорт того же файла добавляет только новое.dryRun: trueничего не записывает и отвечает, что было бы импортировано, с примером.400называет, что не так с файлом, который не удалось прочитать.Импортированные кейсы создаются черновиками, кроме тех, что в своей системе действуют (Active в Allure TestOps, actual в Qase): они приходят утверждёнными, если импортирующий может утверждать кейсы.
Лимит тела — 50 МБ, и не больше 4000 кейсов за один импорт (
KVALLI_MAX_IMPORT_CASES): файл больше отклоняется с просьбой загрузить его частями, потому что весь импорт — одна транзакция, и сбой посередине ничего не оставляет.Qase CSV проверен на примерах экспорта самого Qase, V1 и V2: экранированный текст шагов, список внутри неэкранированного текста шага V1 (остаётся в шаге),
steps_data(сохраняется как «Data: …» под действием), вложенные шаги (нумерация1.1.),is_muted(кейс приходит заглушённым),status(actual— утверждён,draft,deprecated) иparameters: строкиимя:значениестановятся таблицей параметров кейса, со всеми сочетаниями значений (групповые параметры Qase в файле выглядят так же, и импорт об этом предупреждает); текст другого вида остаётся в описании.Столбцы, для которых в Kvalli нет поля (
type,layer,behavior,automation,severity,status,milestone,is_flaky), заполняют поля проекта с тем же ключом, если значение подходит; сводка называет эти поля (mappedFields) и предупреждает о значениях, которые не подошли.
Импорт из Allure TestOps
/api/testcases/import/testops — перенос проекта из Allure TestOps, который читается через его API с персональным API-токеном (токен обменивается на JWT в /api/uaa/oauth/token и обменивается снова, если JWT истекает во время импорта). url и token можно не передавать — тогда используются сохранённые в настройках («Настройки → Импорт из TMS»).
POST /connection{ url?, token? }— чей это токен и какие проекты он видит.POST /{ url?, token?, projectId, filter? }— начать чтение проекта (202, задача);filter— фильтр TestOps, напримерtag = "regression". Чтение требует запроса на каждый кейс и поле, поэтому идёт в фоне.GET /jobs/:id—status(reading,ready,importing,done,failed,cancelled),startedAt,progress, а после чтения — число кейсов, общих шагов и файлов, собственные поля (fields), которые используют кейсы, и деревья проекта в TestOps (trees:nameиfields). Запрос, сорвавшийся по пути (обрыв соединения, ответ шлюза 502, 503 или 504), повторяется дважды с паузой; неверный адрес, отказ в соединении, сертификат и таймаут не повторяются.GET /jobs(GET /api/testcases/import/testops/jobs) — собственные задачи вызывающего в текущем проекте, сначала новые, каждая в том виде, в каком её возвращаетGET /jobs/:id.POST /jobs/:id/preview{ intoFolder?, skipExisting?, folderFields?, createFields? }— что сделает импорт, по уже прочитанным данным. Папки строятся изfolderFields, начиная с внешней; по умолчанию — дерево, которое команда создала в TestOps (кроме стандартных Features и Suites), а без него первое из Epic → Feature → Story, Parent Suite → Suite → Sub Suite, Component, что заполнено у кейсов.fieldsToCreate— поля проекта, которые импорт создаст.POST /jobs/:id/commit{ …, withAttachments?, createFields? }— записать (202; когда задача в статусеdone, она сообщаетresult). Проект большеKVALLI_MAX_IMPORT_CASESзаписывается частями этого размера, каждая своей транзакцией; если импорт остановился после первой части, записанное остаётся, а повторный импорт добавляет остальное.- Шаги сохраняют ожидаемые результаты (узел ожидаемого результата TestOps) и вложенность (как отступы
›); шаг из формы сценария более старой версии тоже читается. - Общие шаги становятся общими шагами Kvalli, используемыми по ссылке, и объединяются с одноимённым общим шагом из предыдущего импорта; общий шаг, использованный внутри другого, разворачивается в нём. Общий шаг из одного шага, повторяющего его имя, становится этим шагом, а не именем со вложенным повтором.
- Severity или Priority дают приоритет, участник Owner — ответственного, задачи и ссылки попадают в описание, остальные собственные поля заполняют одноимённые поля проекта, файлы (до 25 МБ) копируются. Полей, которых в проекте нет, импорт создаёт сам (
createFields, по умолчанию да): выбор из значений, если их не больше 100, множественный — если у кейса их несколько, иначе текст. - Каждый кейс хранит
sourceReftestops:<project>:<id>, поэтому повторный импорт добавляет только новое.
- Шаги сохраняют ожидаемые результаты (узел ожидаемого результата TestOps) и вложенность (как отступы
POST /history{ url?, token?, projectId, days? }— перенести запуски проекта за последниеdaysдней (по умолчанию 30, не больше 365) как прогоны (202, задача; нужныcases:writeиruns:import). Прогон датируется временем запуска, результаты связываются с перенесёнными кейсами по id кейса TestOps, у упавших читается текст ошибки (до 200 на запуск). Уже перенесённые запуски пропускаются. БольшеKVALLI_TESTOPS_MAX_LAUNCHES(3000) — берутся самые новые.GET /history/:id— ход задачи (progress, а в концеresult:launches,created,skipped,results,warnings),DELETE /history/:id— остановить; уже перенесённые прогоны остаются.DELETE /jobs/:id— остановить задачу. Задача хранится в памяти для запустившего её пользователя в течение часа после того, как она последний раз продвинулась или её смотрели; у одного пользователя не больше пяти задач, и запуск шестой вытесняет его самую старую.
Обсуждение и замечания
GET|POST /api/testcases/:id/comments({ body }, нужноcases:write),PATCH /api/testcases/:id/comments/:commentId({ body? , resolved? }) иDELETE— обсуждение кейса. Текст комментария меняет только автор; удалить комментарий может автор или ревьюер (cases:approve).POST /api/testcases/:id/request-changes{ body }(нужноcases:approve) — ревьюер возвращает кейс на доработку. Замечание сохраняется в обсуждении какkind: "changes_requested"к той версии кейса, на которой оно сделано, и остаётся открытым до разрешения (PATCH … { resolved: true }); утверждённый кейс возвращается в черновики. Утверждение кейса разрешает оставшиеся открытые замечания. Пока они есть, у кейса естьopenRemarks.