Task:
Let connected Android tests work well on Android M.
Question:
How to enable read/write contacts permission when run connected Android test?
Problem:
I know pm command could enable the apk's permission.
adb shell pm grant <PACKAGE_NAME> <PERMISSION_NAME>
I want to run the tests which could run on both real apis and mock apis. If I fail to trigger pm command in gradle DSL, test code is not able to touch real api for security reason.
I try to add the step as first of connectedAndroidTest (connectedInstrumentTest) task. It doesn't work for the target apk has not been install yet. The command lines are called with error code.
android.testVariants.all { variant ->
variant.connectedInstrumentTest.doFirst {
def adb = android.getAdbExe().toString()
exec {
commandLine 'echo', "hello, world testVariants"
}
exec {
commandLine adb, 'shell', 'pm', 'grant', variant.testedVariant.applicationId, 'android.permission.READ_ACCOUNTS'
}
}
}
I try to add the step as last step of install task. It isn't called when I start connectedAndroidTest.
android.applicationVariants.all { variant ->
if (variant.getBuildType().name == "debug") {
variant.install.doLast {
def adb = android.getAdbExe().toString()
exec {
commandLine 'echo', "hello, world applicationVariants"
}
exec {
commandLine adb, 'shell', 'pm', 'grant', variant.applicationId, 'android.permission.READ_ACCOUNTS'
}
}
}
}
My plan is to enable permissions before tests are launched. I don't know which task is proper one. It looks like connectedVariantAndroidTest doesn't depend on installVariant, though they both call adb install.
I try to run the pm grant from test cases. It fails as expected.
I will accept other solutions to run the android tests well.
I think that you need to create your own task depending on installDebug and then make connectedDebugAndroidTest depend on your task.
People does it to disable animations and works, you force the app installation and grant your specific permission before the android tests are executed like this:
def adb = android.getAdbExe().toString()
task nameofyourtask(type: Exec, dependsOn: 'installDebug') { // or install{productFlavour}{buildType}
group = 'nameofyourtaskgroup'
description = 'Describe your task here.'
def mypermission = 'android.permission.READ_ACCOUNTS'
commandLine "$adb shell pm grant ${variant.applicationId} $mypermission".split(' ')
}
tasks.whenTaskAdded { task ->
if (task.name.startsWith('connectedDebugAndroidTest')) { // or connected{productFlavour}{buildType}AndroidTest
task.dependsOn nameofyourtask
}
}
You can add this code to a new yourtask.gradle file and add the next line at the bottom of the build.gradle file:
apply from: "yourtask.gradle"
And declare your permission in the proper manifest
<uses-permission android:name="android.permission.READ_ACCOUNTS" />
Update:
Fixed commandLine command like you did on your version for multiple variants, thanks.
android.applicationVariants.all { variant ->
if (variant.getBuildType().name == "debug") {
task "configDevice${variant.name.capitalize()}" (type: Exec){
dependsOn variant.install
group = 'nameofyourtaskgroup'
description = 'Describe your task here.'
def adb = android.getAdbExe().toString()
def mypermission = 'android.permission.READ_ACCOUNTS'
commandLine "$adb shell pm grant ${variant.applicationId} $mypermission".split(' ')
}
variant.testVariant.connectedInstrumentTest.dependsOn "configDevice${variant.name.capitalize()}"
}
}
Related
I want to run a bash script from a task (in build.gradle) before the Instrumentation tests starts. That script should run a docker container that contains a ruby bases mock server.
I don't know why I can't get it but this is all I have for now (placed in my build.gradle):
task startMock(type:Exec) {
println("Executing myScript")
def proc = "cd ../..".execute()
proc.waitForProcessOutput(System.out, System.err)
proc = "../scripts/_mock.sh -a start -p ${projectDir}/../../ -m deps/mock-config".execute()
proc.waitForProcessOutput(System.out, System.err)
}
gradle.projectsEvaluated {
connectedDebugAndroidTest.dependsOn startMock
}
The Problem is that the Task runs always, not only on calling connectedDebugAndroidTest (or connectCheck) ...
I'm confused and appreciate any help :) Maybe someone can give me a hint on how to solve this.
OK, I finally got it hoooray :)
I added the following parts to my build.gradle(app) and now the script is called before and after connectCheckhas been triggered:
task('mockStart', type: Exec){
doFirst {
println "MOCK: Start server ..."
}
executable "../../scripts/_mock.sh"
args '-a', 'start', '-p', "${projectDir}/../../", '-m', 'deps/mock-config'
}
task('mockStop', type: Exec){
doFirst {
println "MOCK: Stop Server ..."
}
executable "../../scripts/_mock.sh"
args '-a', 'stop', '-p', "${projectDir}/../../", '-m', 'deps/mock-config'
}
gradle.projectsEvaluated {
connectedDebugAndroidTest.dependsOn mockStart
connectedDebugAndroidTest.finalizedBy mockStop
}
Maybe this will help someone that has some error like I had :)
Good luck & stay tuned!
I have a few unit tests that are being ran by the command './gradlew test'. When this completes, it generates 'index.html' in the build/reports folder.
Is there a way to have that automatically open when finished?
Thanks
Edit: I can use the command
./gradlew test && start C:\\<PACKAGE>\\build\\reports\\tests\\testDebugUnitTest\\debug\\index.html
This works. However. Is there a way to just include that entire line in my gradle file so it does it automatically?
In your project build.gradle you can add this task:
task testAndOpen(type: Exec) {
//execute test task first
dependsOn 'test'
//set the base dir
workingDir './build/reports/tests/testDebugUnitTest/debug'
//launch cmd and open the file with the default associated program
commandLine 'cmd', '/c', 'start index.html'
}
then from terminal you can do .\gradlew testAndOpen.
If you are not on Windows you can do a similar thing with a bit different commands. This is the "dirty and fast" way, because the path folder must be changed manually for different flavors but it's a good starting point.
the task will open all test reports from folder MyProject/mymodule/build/reports/tests
just add the code to mymodule/build.gradle
and execute in terminal:
./gradlew app:testDebugUnitTest --tests com.examplepackage.* testOpenTestReport
task testOpenTestReport(group: "verification") {
doLast{
File reportsDir = new File("${project.buildDir.path}/reports/tests")
if (!reportsDir.exists() || !reportsDir.isDirectory()) {
println "reportsDir ${reportsDir.absolutePath} not exist"
return null
}
fileTree(reportsDir)
.filter { it.isFile() }
.files
.findAll { (it.name == "index.html") }
.forEach{file->
println "${file.absolutePath}"
//launch cmd and open the file with the default associated program
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
println "start ${file.absolutePath}".execute().text.trim()
} else {
println "sensible-browser ${file.absolutePath}".execute().text.trim()
}
}
}
}
I have a build.gradle for my Android app and then some helper.gradle files for various sub tasks. Some of these helper tasks invoke shell scripts. Because this app has been in development on exclusively Linux and Mac machines, the shell scripts run fine.
I've recently added a Windows development machine into the mix and the gradle build configuration is failing at calling the shell script.
task helperTask1 << {
exec {
workingDir rootProject.getProjectDir()
commandLine 'sh', './scripts/helperScript1.sh'
}
}
At some point I'll get Cygwin up and running on the Windows machine, but what are my options for getting my app compiling?
Is there a way to effectively have a helperTask1_Linux and helperTask1_Windows tasks that get called on their respective platforms?
Is there a more "gradle" way of calling shell scripts so that my gradle files are defining steps at a higher level?
Here is how I'm doing this:
define a property 'ant.condition.os'. The value of the property depends of the OS.
use the value that property in a switch to call a windows or linux script.
ant.condition(property: "os", value: "windows") { os(family: "windows") }
ant.condition(property: "os", value: "unix" ) { os(family: "unix") }
task myTask() {
switch (ant.properties.os) {
case 'windows':
commandLine 'cmd', '/c', 'myscript.cmd'
break;
case 'unix':
commandLine './myscript.sh'
break;
}
}
I try to run an Instrumentation Test for a certain flavor of my android app and always get this:
Test running failed: Unable to find instrumentation info for: ComponentInfo{<packackename>/android.support.test.runner.AndroidJUnitRunner}
Empty test suite.
This happens only in a certain productFlavor where I had to change the packagename in the build.gradle "manually" with:
applicationVariants.all { variant ->
def flavorName = variant.getVariantData().getVariantConfiguration().getFlavorName()
def mergedFlavour = variant.getVariantData().getVariantConfiguration().getMergedFlavor();
if (flavorName.toLowerCase().contains("foobar")) {
mergedFlavour.setApplicationId(mergedFlavour.getApplicationId() + ".foobar")
}
}
I already tried to do the same thing in the android-testing.gradle but actually all packagenames are looking fine:
Installing de.test.foobar.debug
DEVICE SHELL COMMAND: pm install -r "/data/local/tmp/de.test.foobar.debug"
pkg: /data/local/tmp/de.test.foobar.debug
Success
Installing APK:
Uploading file to: /data/local/tmp/de.test.foobar.debug.test
Installing de.test.foobar.debug.test
DEVICE SHELL COMMAND: pm install -r "/data/local/tmp/de.test.foobar.debug.test"
pkg: /data/local/tmp/de.test.foobar.debug.test
Success
If I remove the "manual" change of the packagename of this specific productflavor, all tests are executed fine.
I already tried to change the instrumentationRunner and made sure that it is the same in the configuration of the test execution - but unfortunately without luck...
Is there maybe a possibility to override the changed applicationId if the test is executed?
Thanks for every input!
if you recently upgraded to Android Studio 2.x and have enabled MultiDex (instant run need it at some point), you need to add the following in your gradle.build
defaultConfig {
...
multiDexEnabled true // <- if you have this you need the following
testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
...
}
The testApplicationId needs to match the applicationId for each flavor you modify so the Test Runner can find the tests. You can do this by using testVariants along with applicationVariants.
Something like this in your case:
testVariants.all { variant ->
def flavorName = variant.getVariantData().getVariantConfiguration().getFlavorName()
def mergedFlavour = variant.getVariantData().getVariantConfiguration().getMergedFlavor();
if (flavorName.toLowerCase().contains("foobar")) {
mergedFlavour.setApplicationId(mergedFlavour.getApplicationId() + ".foobar")
}
}
I`m moving my project to Gradle build system. After APK build I need to sign it with manufacturer certificate.
How to execute .bat file by Gradle after APK was built?
task runSign(type:Exec) {
println "Sign apk..."
commandLine = ['cmd','/c','sign.bat']
}
I know just how to run .bat before build (but I need after):
preBuild.doLast {
runSign.execute()
}
I've found the solution.
Go to Run -> Edit Configurations...
Choose module where you want to run task after APK build. Add new configuration after "Gradle-aware Make".
Click on icon at picture below to choose module where task is implemented and write name of it.
After this steps your custom Gradle task will be executed after APK build.
I needed to perform something similar, but additionally to that I needed to know with which product favour was built, and with what configuration.
I've ended up adding following line into build.gradle:
android {
applicationVariants.all { variant -> variant.assemble.doLast { signAndInstall.execute() } }
...
And with following helper function:
//
// Returns array for CommandLine, path, variant (arm7), configuration (debug / release)
//
def getCommandLine(path)
{
String taskReqStr = getGradle().getStartParameter().getTaskRequests().toString()
Pattern pattern = Pattern.compile("(assemble|generate)(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher(taskReqStr)
if (!matcher.find())
return [ path ]
String flavor = matcher.group(2).toLowerCase() + " " + matcher.group(3).toLowerCase()
return [ path, matcher.group(2).toLowerCase(), matcher.group(3).toLowerCase() ]
}
task signAndInstall(type: Exec) {
def batch = projectDir.toString() + '\\postbuild.bat'
commandLine = getCommandLine(batch)
}
With following postbuild.bat:
#echo off
rem echo %0 %*
if %1. == . exit /b 0
if %2. == . exit /b 0
set InPath=%~dp0build\outputs\apk\%1\%2\app-%1-%2.apk
set OutPath=%~dp0build\outputs\apk\app-%1-%2.apk
copy /y %InPath% %OutPath% 1>NUL
You can of course configure this batch to perform anything what you like, %1 receives your product favour (e.g. arm7, arm8, fat...), and %2 receives 'debug' or 'release' as configuration.