28

I'm using espresso for testing but sometimes I try to get an image form external storage and with marshmallow I need a Runtime permission otherwise there will be an Exception crash and the test will fail.

androidTestCompile 'com.android.support.test:runner:0.4'
androidTestCompile 'com.android.support.test:rules:0.4'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1'
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.1') {
    // this library uses the newest app compat v22 but the espresso contrib still v21.
    // you have to specifically exclude the older versions of the contrib library or
    // there will be some conflicts
    exclude group: 'com.android.support', module: 'appcompat'
    exclude group: 'com.android.support', module: 'support-v4'
    exclude module: 'recyclerview-v7'
}
androidTestCompile 'junit:junit:4.12'
androidTestCompile 'com.squareup.retrofit:retrofit-mock:1.9.0'
androidTestCompile 'com.squareup.assertj:assertj-android:1.1.0'
androidTestCompile 'com.squareup.spoon:spoon-client:1.2.0'

how can I manage that right?

should I write test for Runtime permissions or there's a way to disable it for testing?

should I give permissions before the tests run like she says here? https://www.youtube.com/watch?list=PLWz5rJ2EKKc-lJo_RGGXL2Psr8vVCTWjM&v=C8lUdPVSzDk

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Daniel Gomez Rico
  • 15,026
  • 20
  • 92
  • 162
  • 1
    Possible duplicate of [Android Marshmallow: Test permissions with Espresso?](http://stackoverflow.com/questions/33929937/android-marshmallow-test-permissions-with-espresso) – Roc Boronat Jun 07 '16 at 16:06
  • @RocBoronat in that case that question should be the duplicated one, this one is older – Daniel Gomez Rico Jun 07 '16 at 16:56
  • totally agree, but it seems that the other one has more commitment by the community. The answers are really constructive. In addition, the accepted answer of this question does not fix the issue... so, for me, is not terms of "who arrived first", but "what question and answers are more useful for the community". – Roc Boronat Jun 08 '16 at 10:21

12 Answers12

26

UPDATE! Now you can use Rule from Android Testing Support Library

It is more proper to use than custom rules.

Outdated answer:

You can add test rule to reuse code and add more flexibility:

/**
 * This rule adds selected permissions to test app
 */

public class PermissionsRule implements TestRule {

    private final String[] permissions;

    public PermissionsRule(String[] permissions) {
        this.permissions = permissions;
    }

    @Override
    public Statement apply(final Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {

                allowPermissions();

                base.evaluate();

                revokePermissions();
            }
        };
    }

    private void allowPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            for (String permission : permissions) {
                InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
                        "pm grant " + InstrumentationRegistry.getTargetContext().getPackageName()
                                + " " + permission);
            }
        }
    }

    private void revokePermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            for (String permission : permissions) {
                InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
                        "pm revoke " + InstrumentationRegistry.getTargetContext().getPackageName()
                                + " " + permission);
            }
        }
    }
}

After that you can use this rule in your test classes:

@Rule
public final PermissionsRule permissionsRule = new PermissionsRule(
new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS});

Keep in mind:

  1. Rule does not affect on @Before methods because all rules are executed after that
  2. executeShellCommand is asynchronous and if you need accepted permissions right after test started consider adding some delay
Denis Nek
  • 1,874
  • 3
  • 19
  • 21
  • 3
    I like this solution and it works well for me, after fixing that the `executeShellCommand` in `allowPermissions` requires synchronisation (reading the returned file descriptor/stream until the end). Otherwise the test may start before the permissions have actually been applied. And in general the file descriptors returned by `executeShellCommand` should be closed. – masc3d Feb 25 '17 at 22:41
  • 2
    plus the `revokePermissions()` should be in a `finally` block to be sure. – masc3d Feb 25 '17 at 22:44
  • 2
    I like this solution, though it has one problem for me. Revoking permissions on a running app (at least, on the 6.0.1 marshmallow I'm using) results in termination. (This is sensible: if you manually revoked a permission on a running app, there is no notification mechanism in Android to tell that app that the permission it already requested and was granted was suddenly revoked. Termination here prevents crashes.) – David Pisoni May 24 '17 at 22:56
  • @masc3d is that possible to have the synchronization part in java? I am not quite understand the kotlin code( file descriptor part) – bj4947 Jun 13 '17 at 16:43
  • @IHC_Applroid added a comment to gist. – masc3d Jun 17 '17 at 17:30
  • @DavidPisoni in this case, is there a way still to revoke a permission for tests? – bj4947 Jun 20 '17 at 21:40
  • I don't think so... don't know for certain though. – David Pisoni Jun 26 '17 at 20:49
  • This solution stopped working when I upgraded to test Android 12. Getting the following error - permission grant or revoke changed gids – Jaleel Ahmed Aug 24 '21 at 16:46
24

You can grant and revoke permissions using:

adb shell pm grant com.package.myapp android.permission.<PERMISSION>
adb shell pm revoke com.package.myapp android.permission.<PERMISSION>

To use from Java instrumentation tests call this method from Google samples: https://github.com/googlesamples/android-testing/blob/ed62c450e43f859333b3113d44dd59f75971b529/ui/espresso/IntentsBasicSample/app/src/androidTest/java/com/example/android/testing/espresso/BasicSample/DialerActivityTest.java#L94

Sebas LG
  • 1,715
  • 1
  • 18
  • 31
  • but I should not test how the code react to that new "manage runtime permissions" code? – Daniel Gomez Rico Sep 26 '15 at 15:02
  • 1
    You should separate the test cases, all the storage functionality should be tested with permissions granted and the disabled+granted/rejected permissions should be tested in isolated scenarios. The easiest way I can think of testing those permissions scenarios is with unit tests and mocking the calls to request permissions methods (the system calls could be wrapped inside your own methods) and you can test the whole flow with Robolectric, not even Espresso, unless you really care about the pixels drawn in this flow. – Sebas LG Sep 27 '15 at 10:36
  • so.. why is onView(withText("ALLOW")).perform(click()); not working in the case of asking Espresso to allow the permission? – Jeroen Nov 25 '15 at 14:37
  • Because Espresso has access only to your activities and the "Allow" text on the dialog belongs to Android (afaik) so your activity and therefore Espresso don't have permission to touch it. – Sebas LG Nov 26 '15 at 16:12
  • Would this work for external storage permission on Marshmallow? – IgorGanapolsky Mar 24 '17 at 20:48
  • 2
    @IgorGanapolsky its working fine to me, the only thing is that the command seems to be aynchronously and i had to wait for seconds. – bj4947 Jun 13 '17 at 05:31
  • revoking permission like this in runtime might kill the process so all tests after that will fail.. no? – Ewoks Jul 11 '17 at 07:57
14

You can use GrantPermissionRule. This rule will grant all the requested runtime permissions for all test methods in that test class.

@Rule 
public GrantPermissionRule mRuntimePermissionRule
            = GrantPermissionRule.grant(Manifest.permission.READ_PHONE_STATE);
Vijay Vankhede
  • 3,018
  • 1
  • 26
  • 46
  • 2
    Thanks, other way is https://github.com/stanfy/spoon-gradle-plugin to run the tests and `spoon { grantAllPermissions = true }` to enable all by default – Daniel Gomez Rico Oct 10 '17 at 20:22
  • Does this have to be used for all tests? What if in one of my tests I want to test that the app shows a permission dialog for a certain rule but in another test I want it granted? – Matt Wolfe Apr 17 '20 at 19:11
10

You can create an Android gradle task to grant permission:

android.applicationVariants.all { variant ->
    def applicationId = variant.applicationId
    def adb = android.getAdbExe().toString()
    def variantName = variant.name.capitalize()
    def grantPermissionTask = tasks.create("grant${variantName}Permissions") << {
        "${adb} devices".execute().text.eachLine {
            if (it.endsWith("device")){
                def device = it.split()[0]
                println "Granting permissions on devices ${device}"
                "${adb} -s ${device} shell pm grant ${applicationId} android.permission.CAMERA".execute()
                "${adb} -s ${device} shell pm grant ${applicationId} android.permission.ACCESS_FINE_LOCATION".execute()
            }
        }
    }
}

And this is the command to run the task: gradle grantDebugPermissions

LissF
  • 93
  • 1
  • 7
Luiz Augusto
  • 827
  • 10
  • 8
  • for me this gives something looking like a permission problem … is the task allowed to perform such actions per default, or possible the operating system prohibiting this? like: * What went wrong: Execution failed for task ':Remote:grantAllDebugPermissions'. > A problem occurred starting process 'command 'java.lang.UNIXProcess@6081a6a2'' any ideas? – cV2 Feb 10 '17 at 15:34
  • 2
    Now it is better to use https://developer.android.com/reference/android/support/test/rule/GrantPermissionRule.html – Denis Nek Jul 26 '17 at 02:21
  • @DenisNek `GrantPermissionRule` is not supported on pre-marshmallow devices :/ – ahasbini Jan 05 '18 at 07:54
10

There is GrantPermissionRule in Android Testing Support Library, that you can use in your tests to grant a permission before starting any tests.

@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.CAMERA);
NSK
  • 251
  • 3
  • 10
9

You can achieve this easily by granting permission before starting the test. For example if you are supposed to use camera during the test run, you can grant permission as follows

@Before
public void grantPhonePermission() {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        getInstrumentation().getUiAutomation().executeShellCommand(
                "pm grant " + getTargetContext().getPackageName()
                        + " android.permission.CAMERA");
    }
}
Sachini Samarasinghe
  • 1,081
  • 16
  • 18
4

In the multi-flavor setup, whatever your instrumentation task is, let's say connectedYourFlavorDebugAndroidTest, you may specify the permissions you want to have granted before the tests are run on all the connected devices:

gradlew grantYourFlavorDebugPermissions -Ppermissions=android.permission.ACCESS_FINE_LOCATION,android.permission.ACCESS_COARSE_LOCATION

See sfjava's snippet below to copy into build.gradle to generate grantYourFlavorDebugPermissions task

Community
  • 1
  • 1
riwnodennyk
  • 8,140
  • 4
  • 35
  • 37
  • The above solution works great... thx! I just have a few updates (below) to the snippet you posted; as a few things were missing, or may have simply changed slightly, e.g. in the IDevice interface for, say, getting the device's "apiLevel". BTW haven't used Square's "Spoon" yet (i.e. to run UI-tests on a bunch of devices, concurrently), but it looks like there's also some good hints/snippets in that project as well to leverage similar to what you posted -- e.g. in their SpoonUtils (and SpoonRunner) classes -- among others I'm sure. Thx for all the clues! – sfjava Jun 30 '16 at 22:45
3

Just a few minor UPDATEs to the above snippet ( props to riwnodennyk ) -- which worked great for me when building against SDK 24 and with tools version 24.0.0:

import com.android.ddmlib.AndroidDebugBridge
import com.android.ddmlib.IShellOutputReceiver
import com.android.ddmlib.IDevice

import java.util.concurrent.TimeUnit

android.applicationVariants.all { variant ->
    def applicationId = [variant.mergedFlavor.applicationId, variant.buildType.applicationIdSuffix].findAll().join()
    def grantPermissionsTask = tasks.create("grant${variant.name.capitalize()}Permissions") << {
        if (!project.hasProperty('permissions')) {
            throw new GradleException("Please add the comma-separated command line parameter, for example -Ppermissions=android.permission.WRITE_EXTERNAL_STORAGE")
        }
        AndroidDebugBridge adb = initAdb(android.getAdbExe().toString())
        grantPermissionsOnAllConnectedDevice(adb, applicationId, project.properties['permissions'].split(','))
    }
    grantPermissionsTask.description = "Grants permissions for ${variant.name.capitalize()}."
    grantPermissionsTask.dependsOn "install${variant.name.capitalize()}"
}

public static Object grantPermissionsOnAllConnectedDevice(AndroidDebugBridge adb, String applicationId, String[] permissionNames) {
    return adb.getDevices().each {
        device ->
            int apiLevel = Integer.parseInt(device.getProperty(IDevice.PROP_BUILD_API_LEVEL))
            if (0 <  apiLevel && apiLevel < 23) {
                println "\nSkipping granting permissions for " + device.serialNumber + " because has API level " + device.apiLevel + " < 23"
                return
            }

            println "\nGranting permissions for " + applicationId + " on " + device.serialNumber

            permissionNames.each {
                permissionName ->
                    def shellGrantCommand = "pm grant " + applicationId + " " + permissionName
                    println(shellGrantCommand)
                    device.executeShellCommand(shellGrantCommand, new IShellOutputReceiver() {
                        @Override
                        void addOutput(byte[] data, int offset, int length) {
                            println new String(data[offset..(offset + length - 1)] as byte[])
                        }

                        @Override
                        void flush() {

                        }

                        @Override
                        boolean isCancelled() {
                            return false
                        }
                    })
            }
    }
}

public static AndroidDebugBridge initAdb(String path) {
    AndroidDebugBridge.initIfNeeded(false)
    AndroidDebugBridge adb = AndroidDebugBridge.createBridge(path, false)
    waitForAdb(adb, 15000)
    return adb
}

private static void waitForAdb(AndroidDebugBridge adb, long timeOutMs) {
    long sleepTimeMs = TimeUnit.SECONDS.toMillis(1);
    while (!adb.hasInitialDeviceList() && timeOutMs > 0) {
        try {
            Thread.sleep(sleepTimeMs);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        timeOutMs -= sleepTimeMs;
    }
    if (timeOutMs <= 0 && !adb.hasInitialDeviceList()) {
        throw new RuntimeException("Timeout getting device list.", null);
    }
}
sfjava
  • 216
  • 3
  • 5
2

If you are using the latest 'com.android.support.test.espresso:espresso-core:3.0.1' library for espresso this can be done in a single line of code. All you need to do is just add a rule in in Test class and keep adding the permissions you need as function parameters to grant function. See below:

@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule .grant(Manifest.permission.READ_PHONE_STATE, Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.BLUETOOTH,Manifest.permission.RECORD_AUDIO);

https://developer.android.com/reference/android/support/test/rule/GrantPermissionRule.html

Abhishek Dhotre
  • 481
  • 4
  • 17
2

I've implemented a solution which leverages wrapper classes, overriding and build variant configuration. The solution is quite long to explain and is found over here: https://github.com/ahasbini/AndroidTestMockPermissionUtils. It doesn't need any script to be added in the build system or executed before running the tests.

It is not yet packed in an sdk but the main idea is to override the functionalities of ContextWrapper.checkSelfPermission() and ActivityCompat.requestPermissions() to be manipulated and return mocked results tricking the app into the different scenarios to be tested like: permission was denied hence the app requested it and ended with granted permission. This scenario will occur even if the app had the permission all along but the idea is that it was tricked by the mocked results from the overriding implementation.

Furthermore the implementation has a TestRule called PermissionRule class which can be used in the test classes to easily simulate all of the conditions to test the permissions seamlessly. Also assertions can be made like ensuring the app has called requestPermissions() for example.

ahasbini
  • 6,761
  • 2
  • 29
  • 45
1
    android.support.test.uiautomator.UiDevice mDevice;

 @Before
    public void setUp() throws Exception {
        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    }

@Test
    public void testMainActivityScreenshots() {
               allowPermissionsIfNeeded();//allowPermissions on Activity
}

   private void allowPermissionsIfNeeded()  {
        if (Build.VERSION.SDK_INT >= 23) {
            UiObject allowPermissions = mDevice.findObject(
                    new UiSelector().className("android.widget.Button")
                    .resourceId("com.android.packageinstaller:id/permission_allow_button"));// get allow_button Button by id , because on another device languages it is not "Allow"
            if (allowPermissions.exists()) {
                try {
                    allowPermissions.click();
                    allowPermissionsIfNeeded();//allow second Permission
                } catch (UiObjectNotFoundException e) {
                    Timber.e(e, "There is no permissions dialog to interact with ");
                }
            }
        }
    }
NickUnuchek
  • 11,794
  • 12
  • 98
  • 138
0

adb install -g package.apk

I had a similar issue in automated testing: when launching the app the permission dialog popped up. The adb shell pm grant … command did not help but there is the -g option of adb install to grant all permissions upon install. This solved the problem for me.

From adb --help:

Android Debug Bridge version 1.0.41
Version 30.0.5-6877874
[…]
app installation (see also `adb shell cmd package help`):
 install [-lrtsdg] [--instant] PACKAGE
     push a single package to the device and install it
[…]
    -g: grant all runtime permissions
[…]
Julien
  • 2,139
  • 1
  • 19
  • 32