By now everyone has heard about the Marshmallow permission model. You havent? Where have you been? Go and read Requesting Permissions at Run Time and come back when you've finished. It's ok. I can wait...

So it's all well and good except when you're trying to run tests. When you're using Espresso there's just no way to tap on the allow permission dialog.

It's possible to deny and grant permissions using adb (as you know because you read the Requesting Permissions link didn't you? Didn't you!? But how to work that into an automated test?

Let's just take the simple case; grant all permissions. Yes I know that a proper test should test when permissions are denied as well but you have to give your QAs something to do.

I came across this post which details how to add a task into your build.gradle to grant permissions. It's a great post and I suggest you read it. However it has some shortcomings.

Firstly it doesn't really explain that it only really works if the app is already installed on your device. The grantPermissions task is made to be a dependency of assembleDebugAndroidTest. However at this point the app hasn't been pushed onto the test device. So unless the app is already on there from a previous test, the granting of permissions doesn't work.

However the bigger problem for me is that it doesn't work if you have flavoured builds. (Yes, I'm British. I put a 'u' in flavour. Deal with it!)

The app I've been working on has a dev flavour, a qa flavour, a staging flavour and the production flavour. (For more on flavours, see the section "Define product flavors in the build file" over at Configuring Gradle Builds.)

When you use flavours, the names of your tasks change to incorporate them. So assembleDebugAndroidTest becomes assembleDevDebugAndroidTest (note the addition of Dev in there) for your dev flavour.

So to get around this, here's the fragment from my build.gradle that adds in a permissions grant task for each flavour.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
android.productFlavors.all { flavour ->
    def applicationId = flavour.applicationId
    def adb = android.getAdbExe().toString()

    def grantPermissionsTask = tasks.create("grant${flavour.name.capitalize()}Permissions") << {
        "${adb} shell pm grant ${applicationId} android.permission.ACCESS_FINE_LOCATION".execute()
        "${adb} shell pm grant ${applicationId} android.permission.ACCESS_COARSE_LOCATION".execute()
    }
    grantPermissionsTask.description = "Grants permissions for Marshmallow"

    tasks.whenTaskAdded { theTask ->
        def assemblePattern = ~"assemble${flavour.name.capitalize()}DebugAndroidTest"
        if (assemblePattern.matcher(theTask.name).matches()) {
            theTask.dependsOn grantPermissionsTask.name
        }
    }
}

This chuck of code loops around over all the build flavours (line 1). It creates a new task for each flavour called grantFlavourPermissions (line 5) where Flavour is the name of your flavour. So you'd get grantDevPermissions, grantQaPermissions, etc. (By the way, in the gradle task names the flavours are capitalised, so if your flavour is called "lemon" the task is "grantLemonPemissions".)

The comnands in the block (lines 6 and 7) are the grant commands. You can have as many as you like in here.

The def applicationId on line 2 is there because, in our app, each flavour has a different applicationId so they can all be installed on one device at the same time. The grant sub-command of pm requires an applicationId to know what package to grant permissions to. The def adb on line 3 is to get the full path to the execuable adb.

Line 9 just sets a description for the task and lines 11 to 16 add the make the equivalently flavoured assemble task depend on the new grant task. More on this below.

In his post Josh uses a shell script to run all his grants, and he makes it intelligent to loop over all connected devices. I'm lazier and assume that I'm only running against one device. I also like to have the grants in the build.gradle rather than a shell script, but that's just me.

Whilst I still make my grant...Permission task a dependency for assemble...DebugAndroidTest, as mentioned above, if you run a test and the app isn't already installed, this won't work; the permissions grant has to be done after the app is installed. To get around this we do

./gradlew uninstallAll installDevDebug grantDevPermissions spoonDevDebugAndroidTest

I like to uninstall any previous builds and tests before running a new test suite, hence why I need to redo the grant each time. As always, replace Dev with the name of your flavour in the above commands.

(We use Spoon from Square and the Stanfy gradle plugin to run our instrumentation tests. You can probably replace spoonDevDebugAndroidTest with connectedDevDebugAndroidTest if you like.)

Just to tie up a loose end; both spoonDevDebugAndroidTest or connectedDevDebugAndroidTest seemed to install the app themselves so there was no way to create a gradle dependency that would run the grantDevPermissions task at the right time. As Josh also mentions as a comment in his code, Android Studio also only seems to run the assemble task if you run your tests from within the IDE. So using the assemble tasks for dependency seems to work, with the caveat that I've said twice already; this only works once the app has been installed once. After that the permissions grants are remembered (until you uninstall).

Oh, and before I forget, I haven't tested any of this on un-flavoured builds. If it doesn't work for you - and it probably won't - Josh's solution likely will.

Darren @ Æ


Comments

comments powered by Disqus