#!/usr/bin/env groovy library 'status-jenkins-lib@v1.9.46' QT_VERSION = "6.11.0" pipeline { agent { label getAgentLabel() } parameters { string( name: 'GIT_REF', description: 'Git branch to checkout.', defaultValue: 'master' ) string( name: 'BUILD_SOURCE', description: 'URL to tar.gz file OR path to Jenkins build.', defaultValue: getDefaultBuildSource() ) string( name: 'TEST_NAME', description: 'Paste test name/part of test name to run specific test.', defaultValue: '' ) string( name: 'TEST_SCOPE_FLAG', description: 'Paste a known mark to run tests labeled with this mark (ignored when KEYCARD_TESTS is enabled)', defaultValue: getDefaultTestScopeFlag() ) booleanParam( name: 'KEYCARD_TESTS', description: 'Run @pytest.mark.keycard tests. Requires a build with USE_SIMULATED_KEYCARD=true.', defaultValue: false ) string( name: 'TESTRAIL_RUN_NAME', description: 'Test run name in Test Rail.', defaultValue: '' ) choice( name: 'LOG_LEVEL', description: 'Log level for pytest.', choices: ['INFO', 'DEBUG', 'TRACE', 'WARNING', 'CRITICAL'] ) choice( name: 'AGENT_NODE', description: 'Which Windows host to run on (leave default for random selection)', choices: [ '', 'windows-02', 'windows-03', 'windows-04-vm' ], ) } options { timestamps() /* Prevent Jenkins jobs from running forever */ timeout(time: 120, unit: 'MINUTES') /* manage how many builds we keep */ buildDiscarder(logRotator( daysToKeepStr: '60', numToKeepStr: '50', artifactNumToKeepStr: '50', )) disableRestartFromStage() } environment { PLATFORM = 'tests/e2e-windows' SQUISH_DIR = 'C:\\squish-runner-9.2.2-qt-6.11' PYTHONPATH = "${SQUISH_DIR}\\lib;${SQUISH_DIR}\\bin;${SQUISH_DIR}\\lib\\python;${PYTHONPATH}" /* To stop e2e tests using port 8545 */ STATUS_RUNTIME_HTTP_API = 'False' STATUS_RUNTIME_WS_API = 'False' /* Avoid race conditions with other builds using virtualenv. */ VIRTUAL_ENV = "${WORKSPACE_TMP}\\venv" PATH = "${VIRTUAL_ENV}\\bin;C:\\Qt\\${QT_VERSION}\\msvc2022_64\\bin;${PATH}" /* To store user configuratiin files in temp dir */ XDG_CONFIG_HOME = "${WORKSPACE_TMP}/config" TESTRAIL_URL = 'https://ethstatus.testrail.net' TESTRAIL_PROJECT_ID = 18 /* Override QT xcb plugin with linux to avoid errors like: * "Could not load the Qt platform plugin "xcb" in "" even though it was found." QT_QPA_PLATFORM = "linuxfb"*/ /* Runtime flag to make testing of the app easier. Helps avoiding banners */ STATUS_RUNTIME_TEST_MODE = 1 /* Logging rules let you enable or disable logging for categories */ /* QT_LOGGING_RULES = '*.warning=true' */ /* Set to a non-zero value to make Qt print out diagnostic information about the each (C++) plugin it tries to load. */ /* QT_DEBUG_PLUGINS = 0 */ /* Use local waku fleet */ E2E_LOCAL_WAKU_FLEET = '1' STATUS_FLEET = 'status-app.test' STATUS_FLEET_CONFIG_FILE = "${WORKSPACE}\\assets\\local-waku-fleets-config.json" /* Hack fix for params not being set in job on first run */ BUILD_SOURCE = "${params.BUILD_SOURCE}" TEST_NAME = "${params.TEST_NAME}" TEST_SCOPE_FLAG = "${params.TEST_SCOPE_FLAG}" TESTRAIL_RUN_NAME = "${params.TESTRAIL_RUN_NAME}" LOG_LEVEL = "${params.LOG_LEVEL}" /* Forces QT to use OpenGL for rendering instead of default Direct3D 11 */ QSG_RHI_BACKEND = 'opengl' } stages { stage('Cleanup Workspace') { steps { sh './scripts/clean-git.sh' } } stage('Prep') { steps { script { setNewBuildName() updateGitHubStatus() } } } stage('Deps') { steps { script { dir('test/e2e') { bat """ python310 -m venv ${VIRTUAL_ENV} ${VIRTUAL_ENV}\\Scripts\\python.exe -m pip install --upgrade pip ${VIRTUAL_ENV}\\Scripts\\python.exe -m pip install -r requirements.txt """ } } } } stage('Download') { when { expression { params.BUILD_SOURCE.startsWith('http') } } steps { timeout(5) { script { dir('test/e2e') { sh 'mkdir -p ./pkg/' setBuildDescFromFile(params.BUILD_SOURCE) fileOperations([ fileDownloadOperation( url: params.BUILD_SOURCE, targetFileName: 'StatusIm-Desktop.7z', targetLocation: './pkg/', userName: '', password: '', ) ]) } } } } } stage('Copy') { when { expression { ! params.BUILD_SOURCE.startsWith('http') } } steps { timeout(5) { script { dir('test/e2e') { copyArtifacts( projectName: params.BUILD_SOURCE, filter: 'pkg/*-x86_64.7z', selector: lastWithArtifacts(), target: './' ) setBuildDescFromFile(utils.findFile('pkg/*7z')) } } } } } stage('Unpack') { steps { timeout(5) { script { dir('test/e2e') { sh 'mkdir aut' sh "7z x '${utils.findFile('pkg/*.7z')}' -o'./aut'" env.AUT_PATH = utils.findFile('aut/Status/bin/Status.exe').replace('\\','/') } } } } } stage('Test') { steps { lock(resource: "e2e-windows-${env.NODE_NAME}", quantity: 1) { script { try { timeout(2) { sh "docker compose -p ${fleetProject()} -f docker-compose.waku.yml down --remove-orphans || true" sh "docker compose -p ${fleetProject()} -f docker-compose.waku.yml up -d" sh 'sleep 15' sh "python3 test/e2e/scripts/scan_waku_fleet.py --project ${fleetProject()}" } timeout(time: getTestStageTimeout()) { dir('test/e2e') { def flags = [] if (params.TEST_NAME) { flags.add("-k=${params.TEST_NAME}") } if (!params.KEYCARD_TESTS && params.TEST_SCOPE_FLAG) { flags.add(params.TEST_SCOPE_FLAG) } if (params.LOG_LEVEL) { flags.addAll(["--log-level=${params.LOG_LEVEL}", "--log-cli-level=${params.LOG_LEVEL}"]) } def flagStr = flags.join(' ') withCredentials([ usernamePassword(credentialsId: 'test-rail-api-devops', usernameVariable: 'TESTRAIL_USR', passwordVariable: 'TESTRAIL_PSW'), string(credentialsId: 'wallet-test-user-seed', variable: 'WALLET_TEST_USER_SEED') ]) { sh """ pushd configs ln -sf _local.ci.py _local.py || cp _local.ci.py _local.py popd """ bat """ ${VIRTUAL_ENV}\\Scripts\\python.exe -m pytest -m "${getPytestMarkerExpr()}" -v --reruns=1 --timeout=300 ${flagStr} --disable-warnings --alluredir=./allure-results -o timeout_func_only=true """ } } } } finally { sh "docker compose -p ${fleetProject()} -f docker-compose.waku.yml down --remove-orphans || true" } } } } } } post { always { script { dir('test/e2e') { archiveArtifacts( artifacts: 'local_run_results/*.log', allowEmptyArchive: true, ) /* Needed to categorize types of errors and add environment section in allure report. */ sh 'cp ext/allure_files/categories.json allure-results' sh 'cp ext/allure_files/environment.properties allure-results' allure([ results: [[path: 'allure-results']], reportBuildPolicy: 'ALWAYS', properties: [], jdk: '', ]) /* Link for Jenkins Builds GitHub comment. */ env.PKG_URL = "${env.BUILD_URL}allure/" updateGitHubStatus() } } } success { script { github.notifyPR(true) } } failure { script { github.notifyPR(false) discord.send( header: '**Desktop E2E test failure!**', cred: 'discord-status-desktop-e2e-webhook', ) } } cleanup { script { powershell ''' $paths = @( "C:\\Users\\jenkins\\AppData\\Local\\Status", "C:\\Users\\jenkins\\AppData\\Roaming\\Status" ) foreach ($path in $paths) { if (Test-Path $path) { Write-Host "Removing: $path" Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue } } # Clean-up registry entries $registryPath = "HKCU:\\Software\\Status\\Status Desktop" if (Test-Path $registryPath) { Remove-Item $registryPath -Recurse -Force -ErrorAction SilentlyContinue } ''' } cleanWs(disableDeferredWipeout: true) powershell "Remove-Item -Path '${env.WORKSPACE_TMP}' -Recurse -Force" } } } def fleetProject() { return "e2e-${env.BUILD_NUMBER}-${env.EXECUTOR_NUMBER}".toLowerCase() } def setNewBuildName() { def prMatch = params.BUILD_SOURCE =~ /PR-(\d+)/ if (prMatch) { currentBuild.displayName = "PR${prMatch[0][1]}" } } def setBuildDescFromFile(fileNameOrPath) { def tokens = utils.parseFilename(utils.baseName(fileNameOrPath)) if (tokens == null) { /* Fallback for regex fail. */ currentBuild.description = utils.baseName(fileNameOrPath) return } if (tokens.build && tokens.build.startsWith('pr')) { currentBuild.displayName = tokens.build.replace(/^pr/, 'PR-') } currentBuild.description = formatMap([ Node: NODE_NAME, Build: tokens.build, Commit: tokens.commit, Version: (tokens.tstamp ?: tokens.version), ]) } def updateGitHubStatus() { /* For PR builds update check status. */ if (params.BUILD_SOURCE ==~ /.*\/PR-[0-9]+\/?$/) { github.statusUpdate( context: 'jenkins/prs/tests/e2e-new.windows', commit: jenkins.getJobCommitByPath(params.BUILD_SOURCE), repo_url: 'https://github.com/status-im/status-app' ) } } def formatMap(Map data=[:]) { def text = '' data.each { key, val -> text += "${key}: ${val}
\n" } return text } def getDefaultBuildSource() { return '' } def getDefaultTestScopeFlag() { if (JOB_NAME == "status-app/systems/windows/x86_64/tests-e2e") { return '' } else { return '-m=critical' } } def getPytestMarkerExpr() { return params.KEYCARD_TESTS ? 'keycard' : 'not (keycard or benchmark)' } def getTestStageTimeout() { return (params.KEYCARD_TESTS || params.TEST_SCOPE_FLAG == '-m=critical') ? 40 : 120 } def getAgentLabel() { def baseLabel = "windows && x86_64 && qt-${QT_VERSION} && windows-e2e" if (params.AGENT_NODE && params.AGENT_NODE != '') { return "${baseLabel} && ${params.AGENT_NODE}" } return baseLabel }