Skip to content

Результаты тестов из CI ​

Kvalli принимает JUnit XML (JUnit, pytest, Jest, Go, .NET, Cypress и большинство других раннеров) и Playwright JSON. Каждая загрузка становится прогоном с веткой, коммитом и ссылкой на сборку того CI-задания, откуда она пришла. Тест связывается с тест-кейсом по ключу кейса в названии, аннотации, теге или (в JUnit) свойстве case: TC-101, @case: TC-101.

Что нужно ​

Два значения, заданных как секреты CI:

  • KVALLI_URL — адрес вашего Kvalli, например https://kvalli.example.com;
  • KVALLI_API_KEY — ключ из Настройки → API-ключи.

Ключ принадлежит одному проекту, и прогон попадает туда. Права по умолчанию (testrun:write, cases:read) — ровно то, что нужно для загрузки, и всё, что ключ умеет: присылать отчёты и их файлы. Закрывать, открывать заново, удалять и перемечать прогоны, трогать майлстоуны и сессии он не может — это runs:write.

API-ключи — часть модуля ci, поэтому загрузка из CI требует тарифа Pro или Enterprise. На бесплатном Starter импортируйте файлы результатов вручную в рабочем пространстве.

Клиенты ​

Клиенты раздаёт сам ваш Kvalli, поэтому их версия всегда совпадает с установкой и ничего не нужно ставить из публичного реестра:

КлиентГде взять
CLI (Node 18+)$KVALLI_URL/api/downloads/kvalli.mjs
Репортер Playwright$KVALLI_URL/api/downloads/kvalli-playwright-reporter.mjs
Плагин pytest$KVALLI_URL/api/downloads/pytest_kvalli.py

Вебхук вместо шага загрузки ​

Репозиторий может сам позвать Kvalli, когда пайплайн закончился, — в пайплайн ничего добавлять не нужно. В Источники откройте источник репозитория и нажмите Создать вебхук в блоке «Вебхук пайплайна». Нужны право settings:write, модуль ci и подключение GitLab или GitHub в настройках. Kvalli покажет URL и секрет. Секрет показывается один раз; Новый секрет заменяет его.

  • GitLab: Settings → Webhooks → Add new webhook. URL, секрет как Secret token, триггер Pipeline events. Тесты читаются из тест-отчёта пайплайна, поэтому публикуйте его:

    yaml
    e2e:
      script: npx playwright test --reporter=junit
      artifacts:
        when: always
        reports:
          junit: results.xml
  • GitHub: Settings → Webhooks → Add webhook. URL как Payload URL, content type application/json, секрет и в Let me select individual events только Workflow runs. GitHub не хранит тест-отчёты, поэтому упавший запуск приходит своими упавшими джобами. Чтобы получать результаты по каждому тесту, оставьте шаг загрузки ниже с buildId, равным id запуска (${{ github.run_id }}).

Что приходит: пайплайн, который прошёл или упал (не идущий и не отменённый), становится прогоном под источником «CI Pipeline» с веткой, коммитом и ссылкой. Результаты — по тестам из тест-отчёта, а если отчёта нет и пайплайн упал, — каждая упавшая джоба как упавший тест с концом её лога. Так даже сборка, сломавшаяся до запуска тестов, становится падением, которое можно разобрать.

Id пайплайна — это build id прогона, поэтому пайплайн, который ещё и загружает результаты с buildId=$CI_PIPELINE_ID, даёт один прогон, а не два. Прогон запускает те же воркфлоу, что и загрузка из CI: шаблон «Разбор падений» — это «каждое падение в main разбирает Агент 3».

Вызов проверяется до того, как что-либо прочитано: X-Gitlab-Token у GitLab должен совпадать с секретом, X-Hub-Signature-256 у GitHub должен подписывать тело этим секретом, а событие должно относиться к репозиторию источника. Kvalli отвечает сразу и читает результаты потом; источник показывает последний вызов и что из него вышло. Сам Kvalli никуда ничего не отправляет — только отвечает на вызов.

Любой раннер: CLI ​

sh
curl -sfL "$KVALLI_URL/api/downloads/kvalli.mjs" -o kvalli.mjs
node kvalli.mjs upload test-results/ --name "Unit tests" --env ci
  • Можно передать файлы и каталоги; в каталоге ищутся .xml и .json. Несколько JUnit-файлов (по одному на набор или воркер) становятся одним прогоном.
  • Каталог allure-results загружается так же, как его загружал allurectl: node kvalli.mjs upload allure-results. Контейнеры, вложения и служебные файлы (executor.json, categories.json, environment.*) пропускаются молча; из повторов теста остаётся последняя попытка; отправляются только поля, которые читает сервер, поэтому каталог большого набора укладывается в одну загрузку. Скриншоты и другие вложения упавших тестов прикрепляются к их результатам, как у Playwright.
  • --env, --release и --name описывают прогон. --no-fail не даёт неудачной загрузке уронить джобу.
  • Код выхода говорит, удалась ли загрузка, а не прошли ли тесты.
  • Для отчёта Playwright скриншоты, видео и трейсы упавших попыток отправляются вместе с прогоном и видны у каждого результата: --attachments failed (по умолчанию), all — ещё и файлы прошедших тестов, none — ничего; или KVALLI_ATTACHMENTS. Файлы читаются по путям из отчёта, поэтому загружайте из той же джобы, что запускала тесты. Файл больше 25 МБ или пропавший с диска пропускается и называется в логе.
  • В GitHub Actions, GitLab CI, Jenkins, Azure Pipelines и CircleCI ветка, коммит, ссылка на сборку и build id берутся из окружения. Build id делает загрузку идемпотентной: та же джоба, загрузившая дважды (повторённый шаг, второй репортер), даёт один прогон. Build id также различает ветви матрицы и параллельные узлы: имя раннера в GitHub, стадию в Jenkins, индекс узла в CircleCI, попытку в Azure и --shard у Playwright.
  • Если CI не различает ветви — матрица GitHub на одном self-hosted раннере, ячейки matrix в Jenkins на одной стадии, — задайте --shard или KVALLI_SHARD для каждой ветви (в GitHub — KVALLI_SHARD: ${{ strategy.job-index }}) или дайте каждой свой --name или --env. Иначе каждая ветвь после первой будет принята за её повтор, и её результаты отбросятся.
  • --build-id заменяет определённый id; shard к нему всё равно добавляется.

Результаты по ходу прогона ​

kvalli watch запускает тесты и отправляет каждый результат Allure, как только тест закончился, — как allurectl watch в Allure TestOps:

sh
node kvalli.mjs watch allure-results --name "E2E" -- npx playwright test
  • Прогон открывается до начала тестов и заполняется по ходу; его страница в Kvalli обновляется сама и показывает «Результаты поступают». Вердикт прогон получает, когда команда закончилась.
  • Если джобу убил таймаут или упал раннер, в Kvalli остаётся всё, что успело пройти. Загрузка в конце в таком случае не оставляла ничего.
  • Повтор теста заменяет его прежнюю попытку. Скриншоты и другие вложения упавших тестов прикрепляются к результатам, как при upload.
  • Новые результаты отправляются раз в 3 секунды (--interval). Файлы, которые лежали в каталоге до запуска, не отправляются — они от прошлого прогона.
  • Код выхода — код тестовой команды. Если тесты прошли, а результаты отправить не удалось, — 1, если не задан --no-fail. Если Kvalli был недоступен при старте, тесты всё равно идут, а результаты загружаются одним прогоном в конце.

Связь результатов без ключей кейсов ​

Тест, в котором не указан ключ кейса, связывается с кейсом, чьим автотестом он является — по полю кейса «Автотест», которое заполняет импорт из Allure TestOps. Полное имя записывается так, как его пишет Allure: module#test_name для pytest, package.Class.method для JUnit и TestNG, path/file.spec.ts › Describe › title или path/file.spec.ts:21:3 для Playwright, — и сравнивается с тем, что сказано в отчёте. Результаты Allure с id TestOps (метка AS_ID) связываются по нему. Ключ кейса, указанный в тесте, всегда важнее.

Кейс запоминает, как его называют привязанные к нему результаты: testCaseId из результата Allure (он не зависит от строки, на которой стоит тест) и полное имя без номера строки. Поэтому полное имя вида file.spec.ts:21:3, которое сдвигается с каждой правкой выше теста, перестаёт быть проблемой после первого же прогона: результат находит кейс по testCaseId, даже если в файле два теста с одним названием. Хэш, который Allure TestOps хранит у кейса, импорт переносит туда же.

Автотесты тест-плана в CI ​

Как Allure TestOps, Kvalli запускает пайплайн для тест-плана, и джоба выполняет только его автотесты. На странице плана — «Запустить автотесты в CI» (нужно право pipelines:trigger и подключённый CI, как для обычного запуска пайплайна) или POST /api/plans/<план>/pipeline. В GitLab пайплайн получает переменную KVALLI_PLAN с ключом плана. В GitHub Actions inputs не передаются (GitHub отклоняет input, который workflow не объявил), поэтому в workflow ничего менять не нужно: kvalli testplan сам спрашивает у Kvalli, для какого плана запущен run, по GITHUB_RUN_ID (GET /api/pipelines/<id>/plan). Если Kvalli ещё не связал run с запуском, он подбирает запуск по ветке и файлу workflow за последние 15 минут; два плана, запущенные одновременно на одной ветке и workflow, не различить — шаг падает, а не угадывает.

Джоба записывает автотесты плана в файл, который читают адаптеры Allure (allure-playwright, allure-jest, allure-pytest, allure-java и другие), и запускает тесты как обычно:

yaml
e2e:
  variables:
    ALLURE_TESTPLAN_PATH: $CI_PROJECT_DIR/testplan.json
  script:
    - curl -sfL "$KVALLI_URL/api/downloads/kvalli.mjs" -o kvalli.mjs
    - node kvalli.mjs testplan   # без KVALLI_PLAN ничего не пишет: идут все тесты
    - npx playwright test || true
  after_script:
    - node kvalli.mjs upload allure-results --name "E2E"
  • Тест попадает в план по Allure ID (id кейса в TestOps для перенесённых кейсов) или по полному имени: полю «Автотест» и тем именам без номера строки, которые кейс узнал из результатов. Поэтому план работает и для кейсов, созданных в Kvalli и связанных с тестом по ключу TC-…, — после первого прогона.
  • Кейсы без автотеста в файл не попадают; страница плана и команда называют, сколько их, — их проходят вручную.
  • kvalli testplan берёт план из аргумента или KVALLI_PLAN, ключ — из KVALLI_API_KEY (хватает обычного ключа для загрузки). Если план прочитать не удалось, команда завершается с ошибкой, а не пускает весь набор под именем плана.
  • Результаты, загруженные из этого пайплайна, относятся к плану и видны в его прогрессе.

Метки прогона из окружения ​

Все три клиента берут три метки прогона из окружения, если они не заданы иначе. Это единственный способ задать их из пайплайна, не меняя код загрузки:

ПеременнаяЧто задаётПо умолчанию
KVALLI_RUN_NAMEНазвание прогона в спискеCLI оставляет пустым; репортер Playwright — Playwright, плагин pytest — pytest
KVALLI_ENVIRONMENTКакое окружение проверялось (staging, prod). Нестабильность считается внутри одного окружения, поэтому пишите его одинаково—
KVALLI_RELEASEЧто проверялось, например 1.4.2 или коммит—
sh
export KVALLI_ENVIRONMENT=staging
export KVALLI_RELEASE="$CI_COMMIT_TAG"
export KVALLI_RUN_NAME="E2E ($CI_JOB_NAME)"

Явное значение всегда важнее: --name, --env и --release у CLI, параметры name, environment и release у репортера Playwright, --kvalli-name, --kvalli-env и --kvalli-release у pytest. Репортер Playwright настраивается в playwright.config.ts и своих флагов командной строки не имеет, поэтому пайплайн, которому нужна разная метка на джобу, задаёт эти переменные, а не переписывает конфиг.

Playwright ​

ts
// playwright.config.ts — после того как репортер скачан рядом
reporter: [['list'], ['./kvalli-playwright-reporter.mjs', { name: 'E2E', environment: 'staging' }]]

Каждый проект (браузер) — отдельный результат. Тест, прошедший с повтора, записывается как прошедший с пометкой «нестабильный». Ключи кейсов читаются из названия, аннотаций (test.info().annotations.push({ type: 'case', description: 'TC-101' })) и тегов ({ tag: '@TC-101' }). Неудачная загрузка пишется в лог и никогда не роняет прогон тестов.

Скриншоты, видео и трейсы упавших тестов уходят с прогоном так же, как у CLI (attachments: 'failed' | 'all' | 'none' в параметрах репортера). Чтобы они были, задайте в use screenshot: 'only-on-failure', video: 'retain-on-failure' и trace: 'retain-on-failure'.

pytest ​

sh
curl -sfL "$KVALLI_URL/api/downloads/pytest_kvalli.py" -o pytest_kvalli.py
pytest -p pytest_kvalli --kvalli --kvalli-name "API tests" --kvalli-env staging
python
@pytest.mark.case("TC-101")
def test_checkout(): ...

Маркер добавляет ключ в JUnit-отчёт как свойство, поэтому названия тестов не меняются. --kvalli отправляет отчёт в конце сессии (если --junitxml не задан, JUnit-файл пишется во временное место). Код выхода pytest сохраняется; о неудачной загрузке только сообщается.

GitHub Actions ​

yaml
- name: Send results to Kvalli
  if: always()          # упавшие тесты — самые ценные результаты
  env:
    KVALLI_URL: ${{ secrets.KVALLI_URL }}
    KVALLI_API_KEY: ${{ secrets.KVALLI_API_KEY }}
  run: |
    curl -sfL "$KVALLI_URL/api/downloads/kvalli.mjs" -o kvalli.mjs
    node kvalli.mjs upload test-results/ --name "E2E" --env staging

GitLab CI ​

yaml
e2e:
  script:
    - npx playwright test --reporter=junit > results.xml || true
  after_script:
    - curl -sfL "$KVALLI_URL/api/downloads/kvalli.mjs" -o kvalli.mjs
    - node kvalli.mjs upload results.xml --name "E2E" --env staging

Без клиента ​

POST /api/runs/import с заголовком x-api-key принимает отчёт целым телом или обёрнутым в { rawContent, fileType, name, environment, release, buildId, branch, commit, buildUrl }. ?milestone=MS-3 (ключ или точное название) кладёт прогон в открытый майлстоун; клиенты этот параметр не задают. Подробнее — в API.

Запуск пайплайна из Kvalli — другое право: pipelines:trigger, которого нет у testrun:write ключа загрузки, и только на ветке по умолчанию или ветке autotest/*.

Агент 2 проверяет свои автотесты ​

Проверить в CI у merge request Агента 2 запускает эту ветку в вашем пайплайне и читает, что упало. Что нужно от пайплайна:

  • GitHub Actions: workflow с on: workflow_dispatch, указанный в CI-воркфлоу источника репозитория. GitHub запускает файл workflow из самой ветки, поэтому в ветку, созданную до появления workflow, сначала нужно влить ветку по умолчанию; Kvalli скажет об этом, а не запустит. GitLab CI: пайплайн, который можно создать через API на ветке autotest/*.
  • Пайплайн запускает сгенерированный тест и поднимает всё, что тесту нужно. Пайплайн, который только линтит или собирает, станет зелёным, ничего не доказав.
  • Результаты загружаются в Kvalli (любым клиентом выше, с build id) — тогда чтение точное: берётся результат именно этого теста с ошибкой и стеком. Без них Kvalli читает конец логов упавших джоб (до трёх; джобы, которым разрешено падать, пропускаются).

Падение, которое модель относит к коду теста, исправляется и коммитится в ту же ветку, не больше двух раз. Падение в продукте, окружении или других тестах сообщается, а тест остаётся как есть.

Документация описывает последний выпуск Kvalli.