Android Marshmallow:使用 Espresso 测试权限?

Posted

技术标签:

【中文标题】Android Marshmallow:使用 Espresso 测试权限?【英文标题】:Android Marshmallow: Test permissions with Espresso? 【发布时间】:2016-02-29 00:51:27 【问题描述】:

android Marshmallow 引入的新权限方案需要在运行时检查特定权限,这意味着需要根据用户是拒绝还是允许访问来提供不同的流程。

当我们使用 Espresso 在我们的应用上运行自动化 UI 测试时,我们如何模拟或更新权限状态以测试不同的场景?

【问题讨论】:

How to manage Runtime permissions android marshmallow espresso tests的可能重复 试试这个可能对你有帮助:-***.com/a/41221852/5488468 【参考方案1】:

随着Android Testing Support Library 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. 更多信息here。

【讨论】:

像魅力一样工作!检查this blog post了解更多信息。 尽管在 Manifest 中声明了权限,但我仍然遇到以下错误:12-28 14:09:35.063 7193-7215/com.blaha.test E/GrantPermissionCallable: Permission: android.permission。无法授予 SET_TIME! 这应该被接受为正确答案,因为它使用了适当的框架并且与其他解决方案相比实际上可以工作。 不要忘记在Manifest上添加权限,您也可以在路径:/src/debug中创建一个新的AndroidManifest.xml文件 有没有办法在每个方法/测试级别上做到这一点?我真的需要将应用程序设置类的测试拆分为两个测试类,一个用于授予预许可,一个用于授予许可后??【参考方案2】:

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

看看这个解决方案:

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

顺便说一句,由于这个答案很受欢迎,我们将 PermissionGranter 添加到 Barista 中,这是我们在 Espresso 和 UiAutomator 之上的工具,可以使仪器测试变得绿色:https://github.com/SchibstedSpain/Barista 看看,因为我们将逐个发布维护它。

【讨论】:

创造奇迹!也用于测试不同的语言环境,这是我的情况。 太棒了!在大多数答案中,人们不关心设备的语言。所以,做得好,谢谢!! 如果您还想测试拒绝权限,您可以复制代码但将索引更改为 0。索引 2 是不再问我的复选框。 @Ethan 哭泣!刚刚更新了代码以避免“不再问我”复选框。它在测试环境中没有用。顺便说一句,感谢您的反馈! 我刚刚检查了咖啡师,我真的推荐这个库。在最新版本中,使用 ID 而不是可能会更改的索引,代码现在也在 Kotlin 中。【参考方案3】:

当您的手机处于英文区域时,请尝试使用这种静态方法:

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

我找到了here

【讨论】:

请注意,这不适用于非 EN 语言环境或按钮文本将来发生变化。如果制造商自定义对话框,它甚至可能会在某些设备上失败。 奇怪的是,这不适用于搭载 Android 6 的 LG Nexus 5。找不到文本“ALLOW”。使用以下它可以工作: new UiSelector().clickable(true).checkable(false).index(1) 浓缩咖啡测试如何做到这一点? @Bharga 没有办法用浓缩咖啡来做到这一点。但是你可以同时使用 UIAutomation 和 Espresso。 如果你想处理本地化(或者甚至可能由于制造商导致的字符串更改),他们可能使用的字符串资源 id 是 android.R.string.allow,在这里找到:raw.githubusercontent.com/android/platform_frameworks_base/… 【参考方案4】:

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

@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...,进程就会被杀死。

【讨论】:

我在google测试示例代码中发现了类似的代码。它适用于该包中的通话电话示例应用程序,但如果在应用程序启动后立即询问权限,例如相机权限,则它不起作用。有人知道为什么吗? 你可以试试@BeforeClass。 @fangmobile.com 可能是因为活动也是在 @Before 规则中开始的? 只要使用@org.junit.AfterClass@org.junit.BeforeClass 注释,您应该能够使用相同的方法撤销权限 遗憾的是,这不是很好。它本身适用于权限检查,因此 WRITE_EXTERNAL_STORAGE 之后报告为已授予。但是权限不可用,/sdcard 仍然不可写。它需要在pm grand 之后重新启动应用程序才能工作,这在测试中是不可能的..【参考方案5】:

到目前为止,实际上我知道有两种方法:

    在测试开始前使用 adb 命令授予权限 (documentation):

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

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

【讨论】:

我不明白我们如何将adb shell 命令与我们的测试套件一起使用。关于单击对话框,Espresso 应该能够自行处理。问题是,运行测试并启用权限后,下次运行测试会失败,因为设置保持不变,对话框不会再次出现。 Espresso 无法处理与权限对话框的交互,因为对话框是其他应用程序的实例 - com.android.packageinstaller。 您是否尝试过使用 UIAutomator 接受弹出窗口?对我来说,它似乎没有找到“允许”按钮。任何想法如何解决这个问题? @conca 你必须寻找“允许”文本,我猜,而不是“允许”。系统在运行时使字符串大写,但实际上可以保存为“允许”。 @denys,在 Nexus 6 上它使用 ALLOW 可以工作,但在 LG Nexus 5 上这不起作用,这种类型的差异真的很烦人。【参考方案6】:

您可以通过在开始测试之前授予权限来轻松实现此目的。例如,如果您应该在测试运行期间使用相机,您可以按如下方式授予权限

@Before
public void grantPermission() 
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 
        getInstrumentation().getUiAutomation().executeShellCommand(
                "pm grant " + getTargetContext().getPackageName()
                        + " android.permission.CAMERA");
    

【讨论】:

这对我有用!我必须查看清单以找出使用此技术授予的确切权限。【参考方案7】:

浓缩咖啡更新

这一行代码授予作为参数列出的每个权限 即刻生效。换句话说,应用程序 将被视为已授予权限 - 不再 对话框

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

和分级

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/

【讨论】:

是否有类似的方法来拒绝权限以便在测试期间测试该案例? 在 GrantPermissionRule.grant() 中没有提及它会导致这一点【参考方案8】:

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

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

例如

@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 似乎不是,因为这通常需要重新启动应用程序才能应用!?【参考方案9】:

Android 测试支持库中有 GrantPermissionRule,您可以在测试中使用它在开始任何测试之前授予权限。

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

【讨论】:

【参考方案10】:

感谢@niklas 提供的解决方案。如果有人希望在 Java 中授予多个权限:

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

【讨论】:

【参考方案11】:

我知道答案已被接受,但是,不是一再建议的 if 声明,另一种更优雅的方法是在您想要的特定版本的实际测试中执行以下操作操作系统:

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

如果调用 assumeTrue 函数时表达式评估为 false,则测试将停止并被忽略,我假设这是您想要的,以防测试在 SDK 23 之前的设备上执行。

【讨论】:

【参考方案12】:

我已经实现了一个利用包装类、覆盖和构建变体配置的解决方案。该解决方案解释起来很长,可以在这里找到:https://github.com/ahasbini/AndroidTestMockPermissionUtils。

它尚未打包在 sdk 中,但主要思想是覆盖要操作的 ContextWrapper.checkSelfPermissionActivityCompat.requestPermissions 的功能并返回模拟结果,诱使应用程序进入要测试的不同场景,例如:权限被拒绝因此应用程序请求它并以授予的权限结束。即使应用程序一直拥有权限,这种情况也会发生,但想法是它被覆盖实现的模拟结果欺骗了。

此外,该实现有一个名为PermissionRuleTestRule 类,可以在测试类中使用它来轻松模拟所有条件以无缝测试权限。也可以做出断言,例如确保应用调用了requestPermissions()

【讨论】:

【参考方案13】:

为了允许权限,在需要的时候,我认为最简单的方法是在需要这个权限的测试中直接使用Barista的PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.GET_ACCOUNTS)

【讨论】:

以上是关于Android Marshmallow:使用 Espresso 测试权限?的主要内容,如果未能解决你的问题,请参考以下文章

Android Marshmallow:使用 Espresso 测试权限?

模拟坐标 Android Marshmallow

在 Android Marshmallow 检查权限

如何使用默认裁剪意图裁剪 android marshmallow 中的图像?

Android 6.0 Marshmallow BLE 连接问题

Android 6编译环境搭建 (Marshmallow)