Android / Gitlab ci - sample setup files to setup your own local gitlab runner with real physical android devices. Check https://github.com/caipivara/awesome-android-scripts
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
/*
* Print test results on terminal.
* From: http://stackoverflow.com/a/36199263/273119
*/
tasks.withType(Test) {
testLogging {
events TestLogEvent.FAILED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.STANDARD_ERROR,
TestLogEvent.STANDARD_OUT
exceptionFormat TestExceptionFormat.FULL
showCauses true
showExceptions true
showStackTraces true
}
}
// Merge of
// https://github.com/mgouline/android-samples/blob/master/jacoco/app/build.gradle
// and https://github.com/pushtorefresh/storio/blob/master/gradle/jacoco-android.gradle
// Requires Jacoco plugin in build classpath.
apply plugin: 'jacoco'
// Enables code coverage for JVM tests.
// Android Gradle Plugin out of the box supports only code coverage for instrumentation tests.
project.afterEvaluate {
// Grab all build types and product flavors
def buildTypes = android.buildTypes.collect { type -> type.name }
def productFlavors = android.productFlavors.collect { flavor -> flavor.name }
// When no product flavors defined, use empty
if (!productFlavors) productFlavors.add('')
productFlavors.each { productFlavorName ->
buildTypes.each { buildTypeName ->
def sourceName, sourcePath
if (!productFlavorName) {
sourceName = sourcePath = "${buildTypeName}"
} else {
sourceName = "${productFlavorName}${buildTypeName.capitalize()}"
sourcePath = "${productFlavorName}/${buildTypeName}"
}
def testTaskName = "test${sourceName.capitalize()}UnitTest"
def coverageTaskName = "${testTaskName}Coverage"
// Create coverage task of form 'testFlavorTypeCoverage' depending on 'testFlavorTypeUnitTest'
task "${coverageTaskName}"(type: JacocoReport, dependsOn: "$testTaskName") {
group = 'Reporting'
description = "Generate Jacoco coverage reports for the ${sourceName.capitalize()} build."
classDirectories = fileTree(
dir: "${project.buildDir}/intermediates/classes/${sourcePath}",
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*$Lambda$*.*', // Jacoco can not handle several "$" in class name.
'**/*$inlined$*.*', // Kotlin specific, Jacoco can not handle several "$" in class name.
'**/*Module.*', // Modules for Dagger.
'**/*Dagger*.*', // Dagger auto-generated code.
'**/*MembersInjector*.*', // Dagger auto-generated code.
'**/*_Provide*Factory*.*'] // Dagger auto-generated code.
)
def coverageSourceDirs = [
'src/main/java',
"src/$productFlavorName/java",
"src/$buildTypeName/java"
]
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec")
reports {
xml.enabled = true
html.enabled = true
}
}
build.dependsOn "${coverageTaskName}"
}
}
}
#!/usr/bin/env bash
#
# Install required dependencies
# sdkmanager can be found in $ANDROID_HOME/tools/bin/sdkmanager
#
# Accept licences
# src http://vgaidarji.me/blog/2017/05/31/automatically-accept-android-sdkmanager-licenses/
/usr/bin/expect -c '
set timeout -1;
spawn '"${ANDROID_HOME}"'/tools/bin/sdkmanager --licenses;
expect {
"y/N" { exp_send "y\r" ; exp_continue }
eof
}
'
for I in "platforms;android-26" \
"platforms;android-25" \
"platforms;android-23" \
"platforms;android-21" \
"build-tools;26.0.1" \
"tools" \
"platform-tools" \
"extras;google;m2repository" \
"extras;android;m2repository" \
"extras;google;google_play_services"; do
echo "Trying to update with tools/bin/sdkmanager: " $I
sdkmanager $I
done
sdkmanager --update
#!/usr/bin/env bash
#
# Copy env variables to app module gradle properties file
#
PROPERTIES_FILE_PATH=gradle.properties
set +x // dont print the next lines on run script
printenv | tr ' ' '\n' > $PROPERTIES_FILE_PATH
set -x
//...
apply plugin: 'spoon'
apply from: "../test-setup.gradle"
apply from: '../jacoco.gradle'
android {
//...
defaultConfig {
//...
testApplicationId "${appId}.tests"
}
buildTypes {
debug {
//...
testCoverageEnabled true
}
}
}
spoon {
// debug = true
grantAllPermissions = true
shard = true
codeCoverage = true
// ignoreFailures = true
// failIfNoDeviceConnected = true
}
#!/usr/bin/env bash
#
# Run a command on each adb connected device (adb fail with INSTALL_FAILED_UPDATE_INCOMPATIBLE if multiple devices are connected)
#
# Sample:
# To uninstall an apk on every device:
# $ sh adb-all.sh uninstall my.apk
adb devices | while read line
do
if [ ! "$line" = "" ] && [ `echo $line | awk '{print $2}'` = "device" ]
then
device=`echo $line | awk '{print $1}'`
echo "$device $@ ..."
adb -s $device $@
fi
done
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".base.MyApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<uses-library android:name="android.test.runner" />
<instrumentation
android:name="com.myapp.utils.EspressoRxJUnitRunner"
android:targetPackage="com.myapp.tests" />
</application>
</manifest>
stages:
- build
- test
- deploy
variables:
GIT_STRATEGY: clone
cache:
key: ${CI_PROJECT_ID}
paths:
- .gradle/
before_script:
- sh scripts/cp-env-to-properties.sh
build:
type: build
script:
# Delete spoon screenshots on sdcard before tests to avoid crash
- sh scripts/adb-all.sh shell rm -r sdcard/app_spoon-screenshots
- sh scripts/install-android-dependencies.sh
- ./gradlew clean --stacktrace -PnoDevTools -PpreDexEnable=false
- ./gradlew assemble --stacktrace -scan -PnoDevTools -PpreDexEnable=false
# to avoid INSTALL_FAILED_UPDATE_INCOMPATIBLE
- sh scripts/adb-all.sh uninstall com.medanswers
- sh scripts/adb-all.sh uninstall com.medanswers.tests
unit_tests:
type: test
script:
- ./gradlew testDebugUnitTestCoverage --stacktrace -PnoDevTools -PpreDexEnable=false
# Print on terminal just the total of the coverage
- grep -Eo "Total.*?([0–9]{1,3})%" app/build/reports/jacoco/testDebugUnitTestCoverage/html/index.html
artifacts:
when: always
paths:
- app/build/reports
ui_tests:
type: test
script:
# Wake up screen
- sh scripts/adb-all.sh shell input keyevent 26 # KEYCODE_POWER
- sh scripts/adb-all.sh shell input keyevent 3 # HOME
# Wait for each device
- sh scripts/adb-all.sh wait-for-device
- sh scripts/adb-all.sh shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82'
- ./gradlew spoonDebugAndroidTest --stacktrace -PnoDevTools -PpreDexEnable=false
- ./gradlew createDebugCoverageReport --stacktrace -PnoDevTools -PpreDexEnable=false
# Print on terminal just the total of the coverage
- grep -Eo "Total.*?([0–9]{1,3})%" app/build/reports/coverage/debug/index.html
artifacts:
when: always
paths:
- app/build/outputs
- app/build/spoon
- app/build/reports
deploy-fabric:
type: deploy
environment: staging
script:
- fastlane android deploy_staging
only:
- /^release.*$/
cache:
paths:
- .gradle/wrapper
key: "$CI_BUILD_REF_NAME-medcache"