75

The new permissions scheme introduced by Android Marshmallow requires checking for specific permissions at runtime, which implies the need to provide different flows depending on whether the user denies or allows access.

As we use Espresso to run automated UI tests on our app, how can we mock or update the state of the permissions in order to test different scenarios?

argenkiwi
  • 2,134
  • 1
  • 23
  • 26
  • Possible duplicate of [How to manage Runtime permissions android marshmallow espresso tests](http://stackoverflow.com/questions/32787234/how-to-manage-runtime-permissions-android-marshmallow-espresso-tests) – Daniel Gomez Rico Jun 07 '16 at 16:57
  • Try this it may be help you:-http://stackoverflow.com/a/41221852/5488468 – Bipin Bharti Dec 20 '16 at 11:06

13 Answers13

114

With the new release of the Android Testing Support Library 1.0, there's a GrantPermissionRule that you can use in your tests to grant a permission before starting any tests.

@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);

Kotlin solution

@get:Rule var permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION)

@get:Rule must be used in order to avoid java.lang.Exception: The @Rule 'permissionRule' must be public. More info here.

Cristan
  • 12,083
  • 7
  • 65
  • 69
Niklas
  • 23,674
  • 33
  • 131
  • 170
  • Works like a charm! Check [this blog post](http://kotlindevelopment.com/runtime-permissions-espresso-done-right/) for further info. – Andras K Oct 12 '17 at 14:03
  • 2
    I keep encountering the following error despite having declared the permission in the Manifest: 12-28 14:09:35.063 7193-7215/com.blaha.test E/GrantPermissionCallable: Permission: android.permission.SET_TIME cannot be granted! – Faux Pas Dec 28 '17 at 19:09
  • 3
    This should be accepted as correct answer because it uses appropriate framework and actually work comparing to other solutions. – Roger Alien Jan 11 '18 at 23:43
  • @Faux Pas the GrantPermissionRule only works for runtime permissions the user can grant from within the app. – ataulm Feb 09 '18 at 07:03
  • 1
    Do not forget to add permission on Manifest, you could also create a new AndroidManifest.xml file in path: /src/debug – Kunami Mar 06 '18 at 11:15
  • If you want to grant multiple permissions in Java : @Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CAMERA); – learner Apr 17 '19 at 08:19
  • 1
    Is there any way to do this on per-Method/Test level? Do I really need to split my tests for app setup class in two test classes, one for pre-permission-granted and one for post-permission-granted?? – allofmex Apr 28 '20 at 07:28
  • Is there a way to do this where the permission is removed from the test package, since the permissions appear to be the same as the main module. – John Glen Jan 02 '23 at 04:03
52

The accepted answer doesn't actually test the permissions dialog; it just bypasses it. So, if the permissions dialog fails for some reason, your test will give a false green. I encourage actually clicking the "give permissions" button to test the whole app behaviour.

Have a look at this solution:

public static void allowPermissionsIfNeeded(String permissionNeeded) {
    try { 
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasNeededPermission(permissionNeeded)) {
        sleep(PERMISSIONS_DIALOG_DELAY);
        UiDevice device = UiDevice.getInstance(getInstrumentation());
        UiObject allowPermissions = device.findObject(new UiSelector()
          .clickable(true) 
          .checkable(false) 
          .index(GRANT_BUTTON_INDEX));
        if (allowPermissions.exists()) {
          allowPermissions.click();
        } 
      } 
    } catch (UiObjectNotFoundException e) {
      System.out.println("There is no permissions dialog to interact with");
    } 
  } 

Find the whole class here: https://gist.github.com/rocboronat/65b1187a9fca9eabfebb5121d818a3c4

By the way, as this answer has been a popular one, we added PermissionGranter to Barista, our tool above Espresso and UiAutomator to make instrumental tests green: https://github.com/SchibstedSpain/Barista check it out, because we will maintain it release by release.

Roc Boronat
  • 11,395
  • 5
  • 47
  • 59
  • 1
    Works wonders! Also for testing with different locales, which is my case. – Sloy Jun 08 '16 at 10:37
  • 1
    Awesome! In most answers people don't take in care device's language.. So, well done and thanks!! – alxsimo Jun 09 '16 at 09:46
  • You can duplicate the code but change index to 0 if you want to test denying permission as well. Index 2 is the checkbox for never ask me again. – Ethan Jul 14 '16 at 19:56
  • @Ethan wep! Just updated the code to avoid the "never ask me again" checkbox. It is not useful on a test environment. BTW, thanks for the feedback! – Roc Boronat Jul 21 '16 at 15:56
  • Awesome, how would you test this if you have multiple test methods and all of them need a permission. Can you handle this in @BeforeTest or do you have to check at the beginning of each method? – Elias Nov 16 '16 at 17:17
  • @Elias you have to call this code everytime you know that the permission dialog may appear. So, @ BeforeTest is not a great place to put it, but inside your @ Test – Roc Boronat Dec 14 '16 at 12:34
  • What if my app requests two permissions at once? The OS will then show a dialog where you need to click allow twice. I'm guessing it would work if you call your allowPermissionsIfNeeded twice, but only if you call it in the right order. If you reverse the order and check the second permission first, it will click allow on the first permission's dialog, and then the first permission check would be like "I already have the permission, no need to click allow" and just stop at the dialog. – knezmilos Mar 10 '17 at 10:03
  • So I've just checked, and if using multiple permissions at the same time, the indexes get shifted - 0 is nothing (false), 1 is deny and 2 is allow – knezmilos Mar 10 '17 at 10:49
  • Thanks, @knezmilos ! Just opening an issue at Barista to reproduce and fix it :·) – Roc Boronat Apr 18 '17 at 12:33
  • 1
    I've just checked the Barista and I really recommend this library. In the latest version IDs are used instead of indices which are subject to change, also the code is in Kotlin now. – Miloš Černilovský Nov 19 '20 at 09:38
  • I spent some time with Barista, it will generate false positives for certain permission edge cases. GrantPermissionRule is the way to go unless you have a specific reason to need to check for the permission dialog. – Bob Liberatore Sep 11 '22 at 14:55
  • Yey @BobLiberatore! May you open a issue on Barista's GitHub? Just to know that edge cases if you have them fresh. Thanks! https://github.com/AdevintaSpain/Barista – Roc Boronat Sep 12 '22 at 15:04
  • @RocBoronat I've previously done that. It was not productive. – Bob Liberatore Sep 16 '22 at 17:00
  • GRANT_BUTTON_INDEX is unresolved and I am getting nothing from Google. – John Glen Jan 02 '23 at 20:58
  • 1
    thanks @RocBoronat . With Barista this was solved with just one line! `PermissionGranter.allowPermissionsIfNeeded(PERMISSION_1_CONTACTS)` – Jorge Casariego Feb 01 '23 at 13:49
30

Give a try with such static method when your phone is on English locale:

private static void allowPermissionsIfNeeded() {
    if (Build.VERSION.SDK_INT >= 23) {
        UiDevice device = UiDevice.getInstance(getInstrumentation());
        UiObject allowPermissions = device.findObject(new UiSelector().text("Allow"));
        if (allowPermissions.exists()) {
            try {
                allowPermissions.click();
            } catch (UiObjectNotFoundException e) {
                Timber.e(e, "There is no permissions dialog to interact with ");
            }
        }
    }
}

I found it here

riwnodennyk
  • 8,140
  • 4
  • 35
  • 37
  • 3
    Note that this won't work with a non EN locale or if the button text changes in the future. It might even fail with some devices if the manufacturers customise the dialog. – Jose Alcérreca Aug 03 '16 at 11:09
  • 6
    Strangely this not work on LG Nexus 5 with Android 6. the text "ALLOW" is not found. Using following it works: new UiSelector().clickable(true).checkable(false).index(1) – David Sep 22 '16 at 11:26
  • 1
    How do you do this for espresso tests? – Bhargav Oct 21 '16 at 06:32
  • @Bharga there is no way to do it with Espresso. But you can use both UIAutomation and Espresso together. – Thanh Le Jan 12 '17 at 08:12
  • If you want to deal with localization (or even possible string changes due to manufacturers), the string resource id they're probably using is android.R.string.allow, found here: https://raw.githubusercontent.com/android/platform_frameworks_base/master/core/res/res/values/strings.xml – David Liu Jun 12 '17 at 19:07
21

You can grant permissions before the test is run with something like:

@Before
public void grantPhonePermission() {
    // In M+, trying to call a number will trigger a runtime dialog. Make sure
    // the permission is granted before running this test.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        getInstrumentation().getUiAutomation().executeShellCommand(
                "pm grant " + getTargetContext().getPackageName()
                        + " android.permission.CALL_PHONE");
    }
}

But you can't revoke. If you try pm reset-permissions or pm revoke... the process is killed.

Jose Alcérreca
  • 1,809
  • 17
  • 20
  • 2
    I found similar code in google test sample code. It works for the call phone sample app in that package, but it did not work if the permissions are asked as soon as the app starts, such as the camera permission. Anybody has any idea why? – fangmobile Oct 24 '16 at 23:51
  • You could try @BeforeClass. – Jose Alcérreca Oct 30 '16 at 11:56
  • @fangmobile.com perhaps because the activity is also started in an @ Before rule? – nickmartens1980 Nov 29 '16 at 18:14
  • 1
    You should be able to revoke permissions using the same method so long as you do it with the `@org.junit.AfterClass` or `@org.junit.BeforeClass` annotations – Nathan Reline Apr 28 '17 at 16:32
  • Sadly this does not work very well. It works for permission checks itself, so WRITE_EXTERNAL_STORAGE is reported as granted afterwards. But permission is not use-able, /sdcard is still not writeable. It would require a app restart after `pm grand` to work, which is impossible within a test.. – allofmex Apr 28 '20 at 07:33
11

Actually there are 2 ways of doing this I know so far:

  1. Grant the permission using adb command before test starts (documentation):

adb shell pm grant "com.your.package" android.permission.your_permission

  1. You can click on permission dialog and set the permission using UIAutomator (documentation). If your tests are written with Espresso for android you can combine Espresso and UIAutomator steps in one test easily.
denys
  • 6,834
  • 3
  • 37
  • 36
  • I don't see how we could use the `adb shell` command along with our test suite. And about clicking the dialog, Espresso should be able to handle that itself. The problem is, after you run the test and the permission is enabled, the next time you run the test it will fail because the setting is persisted and the dialog won't show up again. – argenkiwi Nov 26 '15 at 21:36
  • 1
    Espresso can't handle interactions with the permission dialog since dialog is an instance of other application - com.android.packageinstaller. – denys Nov 26 '15 at 22:28
  • Have you tried to accept the popup with UIAutomator? For me it doesn't seem to find the "ALLOW" button. Any ideas how to solve this? – conca Dec 18 '15 at 22:53
  • 1
    @conca you have to look for "Allow" text, I guess, not "ALLOW". The system makes string upper case at runtime but actually it can be saved as "Allow". – denys Jan 04 '16 at 15:16
  • @denys, on Nexus 6 it works using ALLOW, but on LG Nexus 5 this won't work, this type of disparities are really anoying. – David Sep 22 '16 at 11:32
7

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 grantPermission() {
    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
  • This works for me! I had to look in the manifest to find out exactly which permissions to grant using this technique. – jerimiah797 Mar 21 '17 at 23:50
4

ESPRESSO UPDATE

This single line of code grants every permission listed as parameter in the grant method with immediate effect. In other words, the app will be treated like if the permissions were already granted - no more dialogs

@Rule @JvmField
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION)

and gradle

dependencies {
  ...
  testImplementation "junit:junit:4.12"
  androidTestImplementation "com.android.support.test:runner:1.0.0"
  androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.0"
  ...
}

reference: https://www.kotlindevelopment.com/runtime-permissions-espresso-done-right/

murt
  • 3,790
  • 4
  • 37
  • 48
2

If you need to set a permission for a single test or during runtime rather than a rule you can use this:

PermissionRequester().apply {
    addPermissions(android.Manifest.permission.RECORD_AUDIO)
    requestPermissions()
}

e.g.

@Test
fun openWithGrantedPermission_NavigatesHome() {
    launchFragmentInContainer<PermissionsFragment>().onFragment {
        setViewNavController(it.requireView(), mockNavController)
        PermissionRequester().apply {
            addPermissions(android.Manifest.permission.RECORD_AUDIO)
            requestPermissions()
        }
    }

    verify {
        mockNavController.navigate(R.id.action_permissionsFragment_to_homeFragment)
    }
}
CampbellMG
  • 2,090
  • 1
  • 18
  • 27
  • Is this still working? At least for WRITE_EXTERNAL_STORAGE it seems not, since this usually need the app to be restarted to apply!? – allofmex Apr 28 '20 at 07:17
1

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, android.Manifest.permission.ACCESS_FINE_LOCATION);
NSK
  • 251
  • 3
  • 10
1

Thank you @niklas for the solution. In case anyone looking to grant multiple permissions in Java:

 @Rule
public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.CAMERA);
learner
  • 786
  • 7
  • 17
0

I know an answer has been accepted, however, instead of the if statement that has been suggested over and over again, another more elegant approach would be to do the following in the actual test you want for a specific version of OS:

@Test
fun yourTestFunction() {
    Assume.assumeTrue(Build.VERSION.SDK_INT >= 23)
    // the remaining assertions...
}

If the assumeTrue function is called with an expression evaluating to false, the test will halt and be ignored, which I am assuming is what you want in case the test is being executed on a device pre SDK 23.

Edison Spencer
  • 491
  • 1
  • 9
  • 24
0

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 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
0

For allowing the permission, when necessary, I think the easiest way is to use Barista's PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.GET_ACCOUNTS) directly in the test which requires this permission.

lomza
  • 9,412
  • 15
  • 70
  • 85