Skip to content

Commit a21a910

Browse files
committed
ci(e2e_mobile): address review feeback
1 parent c6b5fad commit a21a910

File tree

2 files changed

+137
-113
lines changed

2 files changed

+137
-113
lines changed

ci/Jenkinsfile.test-e2e.android

Lines changed: 92 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
#!/usr/bin/env groovy
22
library '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-
84
pipeline {
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+
259247
def 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-
}

scripts/upload_browserstack_apk.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Upload APK to BrowserStack and output JSON response to stdout
5+
# Requires environment variables:
6+
# APK_PATH - Path to the APK file
7+
# BROWSERSTACK_USERNAME - BrowserStack username
8+
# BROWSERSTACK_ACCESS_KEY - BrowserStack access key
9+
# BUILD_NUMBER - Build number (optional, defaults to current timestamp)
10+
11+
if [[ -z "${APK_PATH:-}" ]]; then
12+
echo "Error: APK_PATH environment variable is required" >&2
13+
exit 1
14+
fi
15+
16+
if [[ ! -f "${APK_PATH}" ]]; then
17+
echo "Error: APK_PATH does not exist or is not a file: ${APK_PATH}" >&2
18+
exit 1
19+
fi
20+
21+
if [[ ! -r "${APK_PATH}" ]]; then
22+
echo "Error: APK_PATH is not readable: ${APK_PATH}" >&2
23+
exit 1
24+
fi
25+
26+
if [[ -z "${BROWSERSTACK_USERNAME:-}" ]]; then
27+
echo "Error: BROWSERSTACK_USERNAME environment variable is required" >&2
28+
exit 1
29+
fi
30+
31+
if [[ -z "${BROWSERSTACK_ACCESS_KEY:-}" ]]; then
32+
echo "Error: BROWSERSTACK_ACCESS_KEY environment variable is required" >&2
33+
exit 1
34+
fi
35+
36+
APK_NAME=$(basename "${APK_PATH}")
37+
SANITIZED_NAME=$(printf '%s' "${APK_NAME}" | tr -cs '[:alnum:]._-' '-' | cut -c1-80)
38+
BUILD_ID="${BUILD_NUMBER:-$(date +%s)}"
39+
CUSTOM_ID="${SANITIZED_NAME}-${BUILD_ID}"
40+
41+
curl -s -u "${BROWSERSTACK_USERNAME}:${BROWSERSTACK_ACCESS_KEY}" \
42+
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
43+
-F "file=@${APK_PATH}" \
44+
-F "custom_id=${CUSTOM_ID}"
45+

0 commit comments

Comments
 (0)