Как подружить несколько Jenkins Jobs c одним агрегированным запуском Allure TestOps

от автора

Всем привет! Меня зовут Павел, я технический лидер тестирования в Альфа-Банке в направлении мобильной разработки.

Через плагин 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. Надеюсь статья будет полезной. Спасибо за внимание и до новых встреч!!!


ссылка на оригинал статьи https://habr.com/ru/articles/913716/