Всем привет! Меня зовут Павел, я технический лидер тестирования в Альфа-Банке в направлении мобильной разработки.
Через плагин withAllureUpload для Jenkins нельзя из нескольких джоб залить отчёт автотестов в один запуск TestOps. Готовых решений в интернете не нашёл, и даже поддержка TestOps не смогла нормально подсказать, как из нескольких Jenkins Jobs отправлять результат в один запуск TestOps. Методом проб и ошибок это сделать удалось — в статье расписал решения, как это работает через терминальную программу.
Относительно недавно, в начале года Банк переехал на новую ТМС Allure Testops. Автотесты (сейчас их >200) запускаются через CI Jenkins, и запускаются параллельно на нескольких девайсах одновременно.
Во время пилотного запуска мы столкнулись с рядом неудобств при запуске всех автотестов в одной джобе, не считая того, что разбирать логи в таких джобах очень сложно. Поэтому начали искать решение, как разбить запуск автотестов на несколько джоб, но чтобы результаты из этих джоб пушились в один агрегированный запуск в TestOps.
Изначально решили скопировать все результаты нисходящих джоб в восходящую и уже из одной Jenkins джобы отправить результаты в один запуск TestOps. Но столкнулись с такой проблемой, что дефолтный плагин TestOps withAllureUpload так не работает. Вместо этого мы получали пустой запуск без результатов.
Далее попробовали передавать параметр ALLURE_JOB_RUN_ID.
(этот параметр передаётся от TestOps в Jenkins джобу при создании джобы из TestOps) из восходящей джобы в нисходящую, а уже из нисходящих джоб отправлять через плагин withAllureUpload. Но нисходящая джоба даже не запускалась, а падала с ошибкой. Потому что плагин создаёт новый объект, а если объект уже есть, то плагин падает с ошибкой.

После поисков везде, где только возможно, и множества попыток, решение было найдено в документации самого TestOps. Делается это через allurectl — приложение командной строки. Но предыдущие неудачные попытки также принесли свои плоды. Смогли проработать два решения отправки результатов автотестов в один агрегированный запуск TestOps из разных Jenkins джоб, которые описал ниже.
Способ №1. Отправка результатов из нисходящих Jenkins джоб в один агрегированный запуск TestOps
Этот способ подходит, когда вы создаёте запуск из TestOps и в восходящей Jenkins джобе есть параметр ALLURE_JOB_RUN_ID.
, который передаётся нисходящим джобам. Таким образом, каждая нисходящая джоба может передавать свои результаты автотестов сразу после своего завершения. Однако при таком флоу в нисходящих Jenkins джобах не будет генерироваться ссылка на запуск TestOps, как показано на скринах ниже.


Настройки восходящей джобы
В pipeline восходящей джобы нужно при запуске нисходящей джобе передать параметр ALLURE_JOB_RUN_ID.
.
stage('Android Tests SmzUserGoToSmzScreenFromMainPageTest') {
steps {
script {
smzBuild1 = build job: params.DOWNSTREAM_JOB,
parameters: [
string(name: 'branchName', value: 'master'),
string(name: 'reportPortalTags', value: 'scheduledRegressLaunch'),
string(name: 'platform', value: 'ANDROID'),
string(name: 'selenoidDeviceName', value: 'Samsung Galaxy A55 5G,Xiaomi Redmi Note 13 Pro,Google Pixel 4a,Samsung Galaxy S21+ 5G'),
string(name: 'selenoidEmulatorVersion', value: '13.0'),
string(name: 'isRetryEnable', value: 'false'),
string(name: 'useLatestRCBuild', value: 'true'),
string(name: 'rpLaunchName', value: 'AM.Smz_entry_point_cycle'),
string(name: 'runTest', value: 'SmzUserGoToSmzScreenFromMainPageTest'),
string(name: 'runInSelenoid', value: 'true'),
string(name: 'ALLURE_JOB_RUN_ID', value: "${params.ALLURE_JOB_RUN_ID}") //Передаём., как параметр ALLURE_JOB_RUN_ID
],
propagate: false
echo "SMZ build number - ${smzBuild1.number}" // Исправлена переменная
}
}
}
Настройки нисходящей джобы
Состоит из двух шагов:
№1. Сначала скачиваем приложение командной строки allurectl.
stage('Install allurectl') {
steps {
script {
sh '''
if ! command -v allurectl &> /dev/null; then
echo "Installing allurectl..."
curl -sL https://github.com/allure-framework/allurectl/releases/latest/download/allurectl_linux_amd64 -o allurectl
chmod +x allurectl
./allurectl --version
else
echo "allurectl already installed"
fi
'''
}
}
}
№2. Затем настраиваем отправку результатов автотестов в агрегированный запуск TestOps.
stage('Running tests') {
steps {
script {
// Дефолтное значение для запуска всех тестов в проекте
def testToRun = "ru.testing.*"
println("Chosen test parameter as ${params.runTest}")
if ("${params.runTest}" != "") {
// Для запуска конкретного теста
testToRun = "${params.runTest}"
} else if ("${params.runCycle}" == FULL_REGRESS) {
// Для запуска всех регрессных тестов
testToRun = "ru.testing.regress.*"
gradleArgs.remove("-Dorg.gradle.project.tags=FULL_REGRESS")
}
withCredentials([string(credentialsId: 'testops-token', variable: 'ALLURE_TOKEN')]) {
sh """
# Запуск тестов с мониторингом через allurectl
./allurectl watch \
--endpoint https://testops \
--project-id 4 \
--launch-name "AM.Regress-#${env.BUILD_NUMBER}" \
--launch-tags "Jenkins_Android,regression" \
--results build/allure-results -- ./gradlew --info clean test ${gradleArgs.join(' ')} --tests "${testToRun}"
"""
}
}
}
post {
always {
allure includeProperties: false,
jdk: '',
results: [[path: 'build/allure-results']]
}
}
}
Так как мы передали параметр ALLURE_JOB_RUN_ID.
нисходящей джобе, allurectl не будет создавать новый объект, а запушит отчёты автотестов в уже существующий запуск TestOps.
Полезная ссылка: официальная документация TestOps.
Способ №2. Отправка результатов из восходящей Jenkins джобы в один агрегированный запуск TestOps
Этот способ подходит, когда вы запускаете джобу из Jenkins, и вам нужно отправить результаты автотестов из нескольких нисходящих джоб. В этом случае у восходящей джобы нет параметра ALLURE_JOB_RUN_ID.
и передавать нисходящим джобам нечего.
Поэтому флоу для такой передачи будет несколько иным. Также для этого способа нужен установленный в Jenkins плагин copyArtifacts, чтобы можно было копировать результаты нисходящих джоб в восходящую.
Настройка восходящей джобы
В восходящей джобе, как и в прошлом решении, не будет сгенерирована ссылка на запуск TestOps.
№1. Добавляем в pipeline запуск нисходящих джоб.
stage("Android Tests ${cycle}") {
downstreamJob = build job: params.DOWNSTREAM_JOB,
parameters: [
string(name: 'branchName', value: 'master'),
string(name: 'platform', value: 'ANDROID'),
string(name: 'selenoidDeviceName', value: selenoidDeviceNameList),
string(name: 'selenoidEmulatorVersion', value: '13.0'),
booleanParam(name: 'isRetryEnable', value: 'false'),
string(name: 'useLatestRCBuild', value: 'true'),
string(name: 'rpLaunchName', value: cycle),
string(name: 'runCycle', value: cycle),
string(name: 'runTest', value: ''),
string(name: 'runInSelenoid', value: 'true')
],
propagate: false
// Проверка на null перед использованием
if (downstreamJob?.number) {
echo "Build number for ${cycle}: ${downstreamJob.number}"
} else {
echo "Build for ${cycle} failed to start"
}
}
№2. Вторым пунктом добавляем шаг сбора логов из нисходящих джоб.
stage("Copy Results From ${cycle}") {
script {
if (downstreamJob?.number) {
copyArtifacts(
projectName: params.DOWNSTREAM_JOB,
selector: specific("${downstreamJob.number}"),
filter: 'build/allure-results/**/*',
target: ".",
flatten: false
)
sh "ls -la build/allure-results/"
} else {
echo "No artifacts to copy for ${cycle}"
}
}
}
№3. Устанавливаем приложение командной строки allurectl.
stages {
stage('Install allurectl') {
steps {
script {
sh """
if ! command -v ./allurectl &> /dev/null; then
echo "Installing allurectl..."
curl -sL https://github.com/allure-framework/allurectl/releases/latest/download/allurectl_linux_amd64 -o allurectl
chmod +x allurectl
./allurectl --version
else
echo "allurectl already installed"
fi
"""
}
}
}
Этот шаг может быть любым по счету, главное, чтобы он выполнялся до отправки результатов в TestOps. Здесь уже кому как больше нравится.
№4. И, наконец, отправляем результаты в TestOps.
stage("Push report ${cycle}") {
script {
if (downstreamJob?.number) {
withCredentials([string(credentialsId: 'testops-token', variable: 'ALLURE_TOKEN')]) {
def uploadOutput = sh(
script: """
./allurectl upload \
--endpoint https://testops \
--project-id 4 \
--launch-name "AM ANDROID Regress - #${env.BUILD_NUMBER}" \
--launch-tags "Android,AM,regress" \
"build/allure-results"
""",
returnStdout: true
).trim()
if (uploadOutput =~ /Report link: /) {
reportUrl = (uploadOutput =~ /Report link: (https?:\/\/\S+)/)[0][1]
}
currentBuild.description = "Ссылка на запуск Allure TestOps - ${reportUrl}"
}
sh "rm -f build/allure-results/*" //очищаем директорию с результатами
}
}
}
[!NOTE]
Обратите внимание, что при отправке отчетов без запуска автотестов нужно использовать командуupload
, а неwatch
, как в способе №1
[ВАЖНО!]
После отправки результатов в TestOps обязательно очищайте директориюbuild/allure‑results
, иначе в TestOps будут дублироваться результаты автотестов!
Настройка нисходящей джобы
В нисходящей джобе нужно добавить в pipeline только архивирование сырых результатов allure после автотестов.
post {
always {
// Архивируем сырые результаты для копирования
archiveArtifacts(
artifacts: 'build/allure-results/**/*',
fingerprint: true,
allowEmptyArchive: true
)
allure includeProperties: false,
jdk: '',
results: [[path: 'build/allure-results']]
}
}
Также у нисходящих джоб будет работать плагин withAllureUpload, и результаты каждой нисходящей джобы также можно отправлять в TestOps отдельным запуском! Здесь уже как вам нужно и/или как нравится.
Полезные ссылки:
Подведение итогов
Есть как минимум два способа отправить результаты автотестов из нескольких Jenkins джоб в один агрегированный запуск TestOps. Надеюсь статья будет полезной. Спасибо за внимание и до новых встреч!!!