#!/usr/bin/env groovy library 'status-jenkins-lib@v1.9.46' pipeline { agent { dockerfile { label 'linuxcontainer' filename 'tests.Dockerfile' dir 'ci' args '--user jenkins' } } parameters { string( name: 'CHANGE_ID', description: 'Passed from PR CI Job, optional', defaultValue: '' ) string( name: 'GIT_REF', description: 'Git branch to checkout.', defaultValue: 'master' ) string( name: 'BUILD_SOURCE', description: 'URL to APK OR path to Jenkins build.', defaultValue: '' ) string( name: 'BROWSERSTACK_APP_ID', description: 'Existing BrowserStack app identifier (bs://...). Leave empty to upload from BUILD_SOURCE.', defaultValue: '' ) choice( name: 'TEST_DEVICE_ID', description: 'BrowserStack device id to run on (leave empty to use the environment default).', choices: ['', 'galaxy_tab_s10p_android_15', 'pixel_8_android_14', 'pixel_7_android_13', 'samsung_s23_android_13'] ) string( name: 'PYTEST_ARGS', description: 'Pytest flags. Default runs gate tests for PRs. Use "-n=2 -m smoke tests" for nightly/full runs.', defaultValue: '-n=2 -m gate tests' ) } options { timestamps() timeout(time: 120, unit: 'MINUTES') buildDiscarder(logRotator( daysToKeepStr: '30', numToKeepStr: '30', artifactNumToKeepStr: '30', )) disableRestartFromStage() } environment { PLATFORM = 'tests/e2e-android' VIRTUAL_ENV = "${env.WORKSPACE_TMP}/venv-appium" PYTHONUNBUFFERED = "1" CHANGE_ID = "${params.CHANGE_ID}" TEST_DEVICE_ID = "${params.TEST_DEVICE_ID}" BROWSERSTACK_APP_ID = "${params.BROWSERSTACK_APP_ID?.trim() ?: ''}" BROWSERSTACK_PROJECT_NAME = "Mobile E2E ${getBuildTypeFromUpstream()}" } stages { stage('Prep') { steps { script { setNewBuildName() updateGitHubStatus() } } } stage('Setup Python environment') { steps { dir('test/e2e_appium') { sh """#!/bin/bash set -euo pipefail python3 -m venv ${VIRTUAL_ENV} source ${VIRTUAL_ENV}/bin/activate pip install --upgrade pip pip install -r requirements.txt """ } } } /* Copies APK from Jenkins build job (skipped when BUILD_SOURCE is a URL) */ stage('Fetch APK from Build') { when { allOf { expression { !params.BROWSERSTACK_APP_ID?.trim() } expression { !params.BUILD_SOURCE?.startsWith('http') } } } steps { timeout(5) { script { dir('test/e2e_appium') { copyArtifacts( projectName: params.BUILD_SOURCE, filter: 'pkg/*.apk', selector: lastWithArtifacts(), target: './pkg/', flatten: true ) } } } } } /* Uploads local APK or passes URL for BrowserStack to download */ stage('Provide APK to BrowserStack') { when { expression { !params.BROWSERSTACK_APP_ID?.trim() } } steps { script { def autSource = params.BUILD_SOURCE?.startsWith('http') ? params.BUILD_SOURCE : utils.findFile('test/e2e_appium/pkg/*.apk') withCredentials([ usernamePassword( credentialsId: 'browserstack-status-mobile-android', usernameVariable: 'BROWSERSTACK_USERNAME', passwordVariable: 'BROWSERSTACK_ACCESS_KEY' ) ]) { def response = sh( script: "./scripts/upload_browserstack_apk.sh '${autSource}'", returnStdout: true ).trim() def result = readJSON text: response if (!result?.app_url) { error("BrowserStack upload failed: ${response}") } env.UPLOADED_BROWSERSTACK_APP_ID = result.app_url env.BROWSERSTACK_BUILD_NAME = result.custom_id env.BROWSERSTACK_BUILD_IDENTIFIER = env.BUILD_NUMBER } } } } stage('Run pytest suite') { steps{ lock(resource: 'e2e-android') { script { env.E2E_RUN_ID = env.BUILD_TAG def appId = env.UPLOADED_BROWSERSTACK_APP_ID ?: params.BROWSERSTACK_APP_ID dir('test/e2e_appium') { withCredentials([ usernamePassword( credentialsId: 'browserstack-status-mobile-android', usernameVariable: 'BROWSERSTACK_USERNAME', passwordVariable: 'BROWSERSTACK_ACCESS_KEY' ), string( credentialsId: 'wallet-test-user-seed', variable: 'WALLET_TEST_USER_SEED' ) ]) { sh "BROWSERSTACK_APP_ID=${appId} ${VIRTUAL_ENV}/bin/python -m pytest --env browserstack ${params.PYTEST_ARGS?.trim() ?: ''}" } } } } } } } post { always { script { junit allowEmptyResults: true, testResults: "test/e2e_appium/reports/${env.BUILD_TAG}/pytest_results_*.xml" archiveArtifacts artifacts: "test/e2e_appium/reports/${env.BUILD_TAG}/**/*", allowEmptyArchive: true env.PKG_URL = "${env.BUILD_URL}testReport/" updateGitHubStatus() } } success { script { github.notifyPR(true) } } failure { script { github.notifyPR(false) } } cleanup { cleanWs(disableDeferredWipeout: true) dir(env.WORKSPACE_TMP) { deleteDir() } } } } def setNewBuildName() { if (utils.isPRBuild()) { currentBuild.displayName = "PR${env.CHANGE_ID}" } } def updateGitHubStatus() { if (utils.isPRBuild()) { github.statusUpdate( context: 'jenkins/prs/tests/e2e-android', commit: env.GIT_COMMIT, repo_url: 'https://github.com/status-im/status-app' ) } } def getBuildTypeFromUpstream() { def upstream = currentBuild.upstreamBuilds?.getAt(0) if (upstream) { def upstreamJob = upstream.getFullProjectName() if (upstreamJob.contains('/prs/')) return 'PR' if (upstreamJob.contains('/nightly/')) return 'Nightly' if (upstreamJob.contains('/release/')) return 'Release' } return 'Manual' }