安卓棉花糖:使用Espresso测试权限?

75

Android Marshmallow引入了新的权限管理机制,需要在运行时检查特定的权限,这意味着需要根据用户是否拒绝或允许访问提供不同的流程。

由于我们使用Espresso在应用程序上运行自动化UI测试,因此如何模拟或更新权限状态以测试不同情景呢?


可能是如何管理运行时权限Android Marshmallow Espresso测试的重复问题。 - Daniel Gomez Rico
试试这个,它可能会对你有所帮助:-https://dev59.com/J1wX5IYBdhLWcg3w-Thv#41221852 - Bipin Bharti
13个回答

115

随着Android测试支持库1.0的新版本发布,现在你可以在测试中使用GrantPermissionRule来在开始任何测试之前授予权限。

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

Kotlin 解决方案

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

@get:Rule必须使用,以避免java.lang.Exception: The @Rule 'permissionRule' must be public.更多信息在此处


2
尽管在清单文件中声明了权限,但我仍然遇到以下错误:12-28 14:09:35.063 7193-7215/com.blaha.test E/GrantPermissionCallable: Permission: android.permission.SET_TIME 无法被授予! - Faux Pas
3
这个答案应该被接受为正确答案,因为它使用了适当的框架并且与其他解决方案相比实际上是有效的。 - Roger Alien
@Faux Pas 的 GrantPermissionRule 只适用于用户可以在应用程序内授予的运行时权限。 - ataulm
1
不要忘记在清单文件中添加权限,你也可以在路径 /src/debug 中创建一个新的 AndroidManifest.xml 文件。 - Kunami
1
有没有办法在每个方法/测试级别上做到这一点?我真的需要将应用程序设置类的测试拆分为两个测试类吗,一个用于预授权,另一个用于后授权? - allofmex
显示剩余3条评论

52

被接受的答案实际上并没有测试权限对话框,它只是绕过了它。因此,如果权限对话框由于某种原因失败,你的测试将会出现错误的绿色结果。我鼓励实际点击“授予权限”按钮来测试整个应用程序的行为。

看看这个解决方案:

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");
    } 
  } 

在此处找到整个班级:https://gist.github.com/rocboronat/65b1187a9fca9eabfebb5121d818a3c4

顺便说一句,由于这个答案很受欢迎,我们在Barista上添加了PermissionGranter,它是我们的工具,比Espresso和UiAutomator更好用。请查看https://github.com/SchibstedSpain/Barista,因为我们将会每次发布都维护它。


1
神奇的作用!对于测试不同的语言环境也非常有用,而这正是我的情况。 - Sloy
1
太棒了!在大多数答案中,人们并没有考虑设备的语言。所以,做得很好,谢谢! - alxsimo
所以我刚刚检查了一下,如果同时使用多个权限,索引会被移动 - 0 表示无权限(假),1 表示拒绝,2 表示允许。 - knezmilos
1
我刚刚检查了Barista库,我真的很推荐它。在最新版本中,使用ID代替了可能会更改的索引,而且现在的代码是Kotlin编写的。 - Miloš Černilovský
1
感谢 @RocBoronat。使用 Barista,仅需一行代码就可以解决此问题!PermissionGranter.allowPermissionsIfNeeded(PERMISSION_1_CONTACTS) - Jorge Casariego
显示剩余10条评论

30

当您的手机处于英语环境时,请尝试使用此静态方法:

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 ");
            }
        }
    }
}

我在 这里找到了它。


3
请注意,如果本地语言不是英语或是按钮文本在未来发生更改,此方法将无法使用。如果厂商对对话框进行了自定义,甚至可能会在某些设备上失败。 - Jose Alcérreca
6
奇怪的是,在安卓6系统的LG Nexus 5上,这个不起作用。文本“ALLOW”未被找到。使用以下代码可以解决:new UiSelector().clickable(true).checkable(false).index(1) - David
1
你如何在 Espresso 测试中执行此操作? - Bhargav
@Bharga,使用Espresso没有办法做到这一点。但是你可以同时使用UIAutomation和Espresso。 - Thanh Le
如果您想处理本地化(甚至由于制造商可能发生的字符串更改),他们可能正在使用的字符串资源ID是android.R.string.allow,可以在此处找到:https://raw.githubusercontent.com/android/platform_frameworks_base/master/core/res/res/values/strings.xml - David Liu

21

您可以在运行测试之前授予权限,例如:

@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");
    }
}

但你无法撤销。如果尝试执行pm reset-permissionspm revoke...,进程将被终止。


2
我在谷歌测试样例代码中找到了类似的代码。它适用于该包中的呼叫电话示例应用程序,但如果在应用程序启动时要求权限(例如相机权限),则不起作用。有人知道为什么吗? - fangmobile
你可以尝试使用 @BeforeClass。 - Jose Alcérreca
@fangmobile.com 可能是因为该活动也在 @ Before 规则中启动了? - nickmartens1980
1
只要您使用 @org.junit.AfterClass@org.junit.BeforeClass 注释,并正确执行操作,就应该能够撤消权限。 - Nathan Reline
很遗憾,这并不是很有效。它可以用于权限检查本身,因此WRITE_EXTERNAL_STORAGE在之后被报告为已授权。但是权限无法使用,/sdcard仍然无法写入。它需要在pm grand之后重新启动应用程序才能工作,但这在测试中是不可能的。 - allofmex

11

实际上,目前我知道有两种方法可以做到这一点:

  1. 在测试开始之前使用adb命令授予权限(文档):

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

  1. 您可以单击权限对话框,并使用UIAutomator设置权限(文档)。如果您的测试是使用Espresso for Android编写的,则可以轻松将Espresso和UIAutomator步骤合并到一个测试中。

我不明白我们如何在测试套件中使用 adb shell 命令。至于点击对话框,Espresso 应该能够自行处理。问题是,在你运行测试并允许权限之后,下一次运行测试将会失败,因为设置已经持久化,对话框不会再次显示。 - argenkiwi
1
Espresso 无法处理与权限对话框的交互,因为该对话框是其他应用程序 com.android.packageinstaller 的实例。 - denys
你尝试过使用UIAutomator接受弹出窗口吗?对我来说,它似乎找不到“允许”按钮。有什么解决办法吗? - conca
1
@conca,你需要查找“Allow”文本,我猜不是“ALLOW”。系统在运行时将字符串转换为大写,但实际上它可以保存为“Allow”。 - denys
@denys,在Nexus 6上使用ALLOW可以工作,但在LG Nexus 5上无法工作,这种差异真的很烦人。 - David

7
你可以在开始测试之前轻松地授予权限来实现这一点。例如,如果你需要在测试运行期间使用相机,可以按照以下方式授予权限。
@Before
public void grantPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        getInstrumentation().getUiAutomation().executeShellCommand(
                "pm grant " + getTargetContext().getPackageName()
                        + " android.permission.CAMERA");
    }
}

这对我很有效!我不得不查看清单以找出使用此技术授予哪些权限。 - jerimiah797

4

ESPRESSO更新

这一行代码立即授予grant方法中列出的所有权限。换句话说,该应用程序将被视为已经授予了这些权限 - 不再需要弹出对话框。

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

以及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"
  ...
}

参考资料:https://www.kotlindevelopment.com/runtime-permissions-espresso-done-right/

该文章介绍了如何在Android应用程序中正确地使用Espresso进行运行时权限测试。首先,需要确保在代码中启用权限请求。其次,需要编写一个自定义规则来允许必需的权限。最后,建议将Espresso测试分解成小型单元测试,以使测试更加可靠。


在测试期间,是否有类似的拒绝权限的方法来测试该情况? - Mauricio Togneri
在GrantPermissionRule.grant()中不提及它会导致这个问题。 - Evgenii Vorobei

2

如果您需要为单个测试或运行时设置权限而不是规则,则可以使用以下方法:

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)
    }
}

这还能用吗?至少对于 WRITE_EXTERNAL_STORAGE 来说似乎不行,因为这通常需要重新启动应用程序才能生效!? - allofmex

1
Android测试支持库中有GrantPermissionRule,您可以在测试中使用它,在开始任何测试之前授予权限。
@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.CAMERA, android.Manifest.permission.ACCESS_FINE_LOCATION);

1
感谢@niklas提供的解决方案。如果有人想在Java中授予多个权限:
 @Rule
public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.CAMERA);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接