11#! /usr/bin/env groovy
22library ' status-jenkins-lib@v1.9.27'
33
4- /* Options section can't access functions in objects. */
5- def isPRBuild = utils. isPRBuild()
6- def isNightlyBuild = utils. isNightlyBuild()
7-
84pipeline {
95 agent {
106 dockerfile {
@@ -35,13 +31,13 @@ pipeline {
3531 )
3632 string(
3733 name : ' BROWSERSTACK_APP_ID' ,
38- description : ' Existing BrowserStack app identifier (lt ://...). Leave empty to upload from BUILD_SOURCE.' ,
34+ description : ' Existing BrowserStack app identifier (bs ://...). Leave empty to upload from BUILD_SOURCE.' ,
3935 defaultValue : ' '
4036 )
41- string (
42- name : ' DEVICE_ID ' ,
37+ choice (
38+ name : ' TEST_DEVICE_ID ' ,
4339 description : ' BrowserStack device id to run on (leave empty to use the environment default).' ,
44- defaultValue : ' '
40+ choices : [ ' ' , ' galaxy_tab_s10p_android_15 ' , ' pixel_8_android_14 ' , ' pixel_7_android_13 ' , ' samsung_s23_android_13 ' ]
4541 )
4642 string(
4743 name : ' PYTEST_ARGS' ,
@@ -64,35 +60,15 @@ pipeline {
6460 environment {
6561 VIRTUAL_ENV = " ${ env.WORKSPACE_TMP} /venv-appium"
6662 PYTHONUNBUFFERED = " 1"
67- BUILD_SOURCE = " ${ params.BUILD_SOURCE} "
68- BROWSERSTACK_APP_ID_PARAM = " ${ params.BROWSERSTACK_APP_ID} "
69- PYTEST_ARGS = " ${ params.PYTEST_ARGS} "
70- DEVICE_ID = " ${ params.DEVICE_ID} "
63+ TEST_DEVICE_ID = " ${ params.TEST_DEVICE_ID} "
64+ BROWSERSTACK_PROJECT_NAME = " Mobile E2E ${ utils.getBuildType()} "
7165 }
7266
7367 stages {
74- stage(' Checkout Reference' ) {
75- steps {
76- script {
77- def branch = params. GIT_REF ?: ' origin/master'
78- checkout([
79- $class : ' GitSCM' ,
80- branches : [[name : branch]],
81- doGenerateSubmoduleConfigurations : false ,
82- extensions : scm. extensions,
83- submoduleCfg : [],
84- userRemoteConfigs : scm. userRemoteConfigs
85- ])
86- env. GIT_COMMIT = sh(script : ' git rev-parse HEAD' , returnStdout : true ). trim()
87- echo " Checked out ${ branch} @ ${ env.GIT_COMMIT} "
88- }
89- }
90- }
91-
9268 stage(' Prep' ) {
9369 steps {
9470 script {
95- setBrowserStackProjectName ()
71+ setNewBuildName ()
9672 updateGitHubStatus()
9773 }
9874 }
@@ -112,39 +88,72 @@ pipeline {
11288 }
11389 }
11490
115- stage(' Acquire APK' ) {
91+ stage(' Use provided BrowserStack app' ) {
92+ when {
93+ expression { params. BROWSERSTACK_APP_ID ?. trim() }
94+ }
11695 steps {
11796 script {
118- if (env. BROWSERSTACK_APP_ID_PARAM ?. trim()) {
119- env. BROWSERSTACK_APP_ID = env. BROWSERSTACK_APP_ID_PARAM . trim()
120- echo " Using provided BrowserStack app id: ${ env.BROWSERSTACK_APP_ID} "
121- } else {
122- if (! env. BUILD_SOURCE ?. trim()) {
123- error(' Specify BUILD_SOURCE when BROWSERSTACK_APP_ID is empty.' )
124- }
125- sh " mkdir -p pkg"
126- if (env. BUILD_SOURCE . startsWith(' http' )) {
127- sh """
128- set -euo pipefail
129- curl -L "${ env.BUILD_SOURCE} " -o pkg/downloaded.apk
130- """
131- } else {
132- copyArtifacts(
133- projectName : env. BUILD_SOURCE ,
134- filter : ' pkg/*.apk' ,
135- selector : lastWithArtifacts(),
136- target : ' pkg/'
137- )
138- }
139- def apkPath = utils. findFile(' pkg/*.apk' )
140- if (! apkPath) {
141- error(" Unable to locate APK under pkg/. Ensure BUILD_SOURCE produces pkg/*.apk artifacts." )
142- }
143- env. APK_PATH = apkPath
144- echo " APK ready at ${ env.APK_PATH} "
145- }
97+ env. BROWSERSTACK_APP_ID = params. BROWSERSTACK_APP_ID . trim()
98+ echo " Using provided BrowserStack app id: ${ env.BROWSERSTACK_APP_ID} "
99+ }
100+ }
101+ }
102+
103+ stage(' Download' ) {
104+ when {
105+ allOf {
106+ expression { ! params. BROWSERSTACK_APP_ID ?. trim() }
107+ expression { params. BUILD_SOURCE ?. startsWith(' http' ) }
108+ }
109+ }
110+ steps { timeout(5 ) { script { dir(' test/e2e_appium' ) {
111+ if (! params. BUILD_SOURCE ?. trim()) {
112+ error(' Specify BUILD_SOURCE when BROWSERSTACK_APP_ID is empty.' )
113+ }
114+ sh ' mkdir -p ./pkg/'
115+ fileOperations([
116+ fileDownloadOperation(
117+ url : params. BUILD_SOURCE ,
118+ targetFileName : ' downloaded.apk' ,
119+ targetLocation : ' ./pkg/' ,
120+ userName : ' ' ,
121+ password : ' ' ,
122+ )
123+ ])
124+ def apkPath = utils. findFile(' test/e2e_appium/pkg/*.apk' )
125+ if (! apkPath) {
126+ error(" Unable to locate APK under pkg/. Ensure BUILD_SOURCE produces pkg/*.apk artifacts." )
127+ }
128+ env. APK_PATH = apkPath
129+ echo " APK ready at ${ env.APK_PATH} "
130+ } } } }
131+ }
132+
133+ stage(' Copy' ) {
134+ when {
135+ allOf {
136+ expression { ! params. BROWSERSTACK_APP_ID ?. trim() }
137+ expression { ! params. BUILD_SOURCE ?. startsWith(' http' ) }
146138 }
147139 }
140+ steps { timeout(5 ) { script { dir(' test/e2e_appium' ) {
141+ if (! params. BUILD_SOURCE ?. trim()) {
142+ error(' Specify BUILD_SOURCE when BROWSERSTACK_APP_ID is empty.' )
143+ }
144+ copyArtifacts(
145+ projectName : params. BUILD_SOURCE ,
146+ filter : ' pkg/*.apk' ,
147+ selector : lastWithArtifacts(),
148+ target : ' ./'
149+ )
150+ def apkPath = utils. findFile(' test/e2e_appium/pkg/*.apk' )
151+ if (! apkPath) {
152+ error(" Unable to locate APK under pkg/. Ensure BUILD_SOURCE produces pkg/*.apk artifacts." )
153+ }
154+ env. APK_PATH = apkPath
155+ echo " APK ready at ${ env.APK_PATH} "
156+ } } } }
148157 }
149158
150159 stage(' Upload APK to BrowserStack' ) {
@@ -161,17 +170,7 @@ pipeline {
161170 )
162171 ]) {
163172 def response = sh(
164- script : """
165- set -euo pipefail
166- APK_NAME=\$ (basename "${ env.APK_PATH} ")
167- CUSTOM_ID=\$ (printf '%s' "\$ {APK_NAME}" | tr -cs '[:alnum:]._-' '-' | cut -c1-80)
168- CUSTOM_ID="\$ {CUSTOM_ID}-${ env.BUILD_NUMBER ?: System.currentTimeMillis()} "
169- curl -s -u "${ BROWSERSTACK_USERNAME} :${ BROWSERSTACK_ACCESS_KEY} " \\
170- -X POST "https://api-cloud.browserstack.com/app-automate/upload" \\
171- -F "file=@${ env.APK_PATH} " \\
172- -F "custom_id=\$ {CUSTOM_ID}" \\
173- | tee upload_response.json
174- """ ,
173+ script : " ./scripts/upload_browserstack_apk.sh" ,
175174 returnStdout : true
176175 ). trim()
177176 def result = readJSON text : response
@@ -192,16 +191,7 @@ pipeline {
192191 steps {
193192 script {
194193 dir(' test/e2e_appium' ) {
195- if (! env. DEVICE_ID ?. trim()) {
196- echo ' DEVICE_ID not set; using framework defaults.'
197- } else {
198- echo " Using DEVICE_ID: ${ env.DEVICE_ID} "
199- }
200-
201- def pytestCmd = " python -m pytest --env browserstack"
202- if (env. PYTEST_ARGS ?. trim()) {
203- pytestCmd + = " ${ env.PYTEST_ARGS.trim()} "
204- }
194+ println (" Using TEST_DEVICE_ID: ${ env.TEST_DEVICE_ID} " )
205195
206196 withCredentials([
207197 usernamePassword(
@@ -210,26 +200,24 @@ pipeline {
210200 passwordVariable : ' BROWSERSTACK_ACCESS_KEY'
211201 )
212202 ]) {
213- withEnv([
214- " BROWSERSTACK_USERNAME=${ BROWSERSTACK_USERNAME} " ,
215- " BROWSERSTACK_ACCESS_KEY=${ BROWSERSTACK_ACCESS_KEY} " ,
216- " BROWSERSTACK_APP_ID=${ env.BROWSERSTACK_APP_ID} " ,
217- " BROWSERSTACK_BUILD_NAME=${ env.BROWSERSTACK_BUILD_NAME ?: ''} " ,
218- " BROWSERSTACK_BUILD_IDENTIFIER=${ env.BROWSERSTACK_BUILD_IDENTIFIER ?: ''} " ,
219- " BROWSERSTACK_PROJECT_NAME=${ env.BROWSERSTACK_PROJECT_NAME ?: ''} " ,
220- " TEST_DEVICE_ID=${ env.DEVICE_ID} "
221- ]) {
222- sh """
223- set -euo pipefail
224- source ${ VIRTUAL_ENV} /bin/activate
225- ${ pytestCmd}
226- """
227- }
203+ sh " ${ VIRTUAL_ENV} /bin/python -m pytest --env browserstack ${ params.PYTEST_ARGS?.trim() ?: ''} "
228204 }
229205 }
230206 }
231207 }
232208 }
209+
210+ stage(' Publish test results' ) {
211+ steps {
212+ script {
213+ def runId = env. E2E_RUN_ID ?. trim()
214+ def reportsPattern = runId ? " test/e2e_appium/reports/${ runId} /**/*.xml" : " test/e2e_appium/reports/**/*.xml"
215+ junit allowEmptyResults : true , testResults : reportsPattern
216+ def archivePattern = runId ? " test/e2e_appium/reports/${ runId} /**/*" : " test/e2e_appium/reports/**/*"
217+ archiveArtifacts artifacts : archivePattern, allowEmptyArchive : true
218+ }
219+ }
220+ }
233221 }
234222
235223 post {
@@ -243,19 +231,19 @@ pipeline {
243231 github. notifyPR(false )
244232 }
245233 }
246- always {
247- script {
248- def runId = env. E2E_RUN_ID ?. trim()
249- def reportsPattern = runId ? " test/e2e_appium/reports/${ runId} /**/*.xml" : " test/e2e_appium/reports/**/*.xml"
250- junit allowEmptyResults : true , testResults : reportsPattern
251- def archivePattern = runId ? " test/e2e_appium/reports/${ runId} /**/*" : " test/e2e_appium/reports/**/*"
252- archiveArtifacts artifacts : archivePattern, allowEmptyArchive : true
253- }
234+ cleanup {
254235 cleanWs(disableDeferredWipeout : true )
255236 }
256237 }
257238}
258239
240+ def setNewBuildName () {
241+ if (currentBuild. upstreamBuilds) {
242+ def parent = utils. parentOrCurrentBuild()
243+ currentBuild. displayName = parent. getFullDisplayName(). minus(' status-desktop » ' )
244+ }
245+ }
246+
259247def updateGitHubStatus () {
260248 if (params. BUILD_SOURCE ==~ / .*\/ PR-[0-9]+\/ ?$/ ) {
261249 github. statusUpdate(
@@ -266,12 +254,3 @@ def updateGitHubStatus() {
266254 }
267255}
268256
269- def setBrowserStackProjectName () {
270- if (isNightlyBuild) {
271- env. BROWSERSTACK_PROJECT_NAME = ' Mobile E2E Nightly'
272- } else if (isPRBuild) {
273- env. BROWSERSTACK_PROJECT_NAME = ' Mobile E2E PRs'
274- } else {
275- env. BROWSERSTACK_PROJECT_NAME = ' '
276- }
277- }
0 commit comments