Skip to content

Commit 162d5a5

Browse files
committed
ci(e2e_mobile): add Jenkinsfile for mobile e2e tests
1 parent 4fe762e commit 162d5a5

File tree

1 file changed

+277
-0
lines changed

1 file changed

+277
-0
lines changed

ci/Jenkinsfile.test-e2e.android

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
#!/usr/bin/env groovy
2+
3+
4+
/* Options section can't access functions in objects. */
5+
def isPRBuild = utils.isPRBuild()
6+
def isNightlyBuild = utils.isNightlyBuild()
7+
8+
pipeline {
9+
agent {
10+
dockerfile {
11+
label 'linuxcontainer'
12+
filename 'tests.Dockerfile'
13+
dir 'ci'
14+
args '--user jenkins'
15+
}
16+
}
17+
18+
parameters {
19+
gitParameter(
20+
name: 'GIT_REF',
21+
description: 'Git branch to checkout.',
22+
branchFilter: 'origin/(.*)',
23+
branch: '',
24+
defaultValue: 'master',
25+
quickFilterEnabled: false,
26+
selectedValue: 'DEFAULT',
27+
sortMode: 'ASCENDING_SMART',
28+
tagFilter: '*',
29+
type: 'PT_BRANCH'
30+
)
31+
string(
32+
name: 'BUILD_SOURCE',
33+
description: 'URL to APK or Jenkins build (pkg/*.apk). Required when BROWSERSTACK_APP_ID is empty.',
34+
defaultValue: ''
35+
)
36+
string(
37+
name: 'BROWSERSTACK_APP_ID',
38+
description: 'Existing BrowserStack app identifier (lt://...). Leave empty to upload from BUILD_SOURCE.',
39+
defaultValue: ''
40+
)
41+
string(
42+
name: 'DEVICE_ID',
43+
description: 'BrowserStack device id to run on (leave empty to use the environment default).',
44+
defaultValue: ''
45+
)
46+
string(
47+
name: 'PYTEST_ARGS',
48+
description: 'Pytest flags (e.g. "-m smoke" or "-m critical") and other args.',
49+
defaultValue: '-n=5 -m smoke tests'
50+
)
51+
}
52+
53+
options {
54+
timestamps()
55+
timeout(time: 120, unit: 'MINUTES')
56+
buildDiscarder(logRotator(
57+
daysToKeepStr: '30',
58+
numToKeepStr: '30',
59+
artifactNumToKeepStr: '30',
60+
))
61+
disableRestartFromStage()
62+
}
63+
64+
environment {
65+
VIRTUAL_ENV = "${env.WORKSPACE_TMP}/venv-appium"
66+
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}"
71+
}
72+
73+
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+
92+
stage('Prep') {
93+
steps {
94+
script {
95+
setBrowserStackProjectName()
96+
updateGitHubStatus()
97+
}
98+
}
99+
}
100+
101+
stage('Setup Python environment') {
102+
steps {
103+
dir('test/e2e_appium') {
104+
sh """
105+
set -euo pipefail
106+
python3 -m venv ${VIRTUAL_ENV}
107+
source ${VIRTUAL_ENV}/bin/activate
108+
pip install --upgrade pip
109+
pip install -r requirements.txt
110+
"""
111+
}
112+
}
113+
}
114+
115+
stage('Acquire APK') {
116+
steps {
117+
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+
}
146+
}
147+
}
148+
}
149+
150+
stage('Upload APK to BrowserStack') {
151+
when {
152+
expression { !env.BROWSERSTACK_APP_ID }
153+
}
154+
steps {
155+
script {
156+
withCredentials([
157+
usernamePassword(
158+
credentialsId: 'browserstack-status-desktop',
159+
usernameVariable: 'BROWSERSTACK_USERNAME',
160+
passwordVariable: 'BROWSERSTACK_ACCESS_KEY'
161+
)
162+
]) {
163+
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+
""",
175+
returnStdout: true
176+
).trim()
177+
def result = readJSON text: response
178+
def appUrl = result?.app_url
179+
if (!appUrl) {
180+
error("BrowserStack upload failed: ${response}")
181+
}
182+
env.BROWSERSTACK_APP_ID = appUrl
183+
env.BROWSERSTACK_BUILD_NAME = result?.custom_id
184+
env.BROWSERSTACK_BUILD_IDENTIFIER = env.BUILD_NUMBER
185+
echo "BrowserStack app uploaded: ${env.BROWSERSTACK_APP_ID}"
186+
}
187+
}
188+
}
189+
}
190+
191+
stage('Run pytest suite') {
192+
steps {
193+
script {
194+
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+
}
205+
206+
withCredentials([
207+
usernamePassword(
208+
credentialsId: 'browserstack-status-desktop',
209+
usernameVariable: 'BROWSERSTACK_USERNAME',
210+
passwordVariable: 'BROWSERSTACK_ACCESS_KEY'
211+
)
212+
]) {
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+
}
228+
}
229+
}
230+
}
231+
}
232+
}
233+
}
234+
235+
post {
236+
success {
237+
script {
238+
github.notifyPR(true)
239+
}
240+
}
241+
failure {
242+
script {
243+
github.notifyPR(false)
244+
}
245+
}
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+
}
254+
cleanWs(disableDeferredWipeout: true)
255+
}
256+
}
257+
}
258+
259+
def updateGitHubStatus() {
260+
if (params.BUILD_SOURCE ==~ /.*\/PR-[0-9]+\/?$/) {
261+
github.statusUpdate(
262+
context: 'jenkins/prs/tests/e2e-android',
263+
commit: jenkins.getJobCommitByPath(params.BUILD_SOURCE),
264+
repo_url: 'https://github.com/status-im/status-desktop'
265+
)
266+
}
267+
}
268+
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

Comments
 (0)