Android Marshmallow:使用Espresso测试权限?


72

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

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



试试这个可能对您有帮助:-stackoverflow.com/a/41221852/5488468
Bipin Bharti

Answers:


102

随着新版本的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.更多信息。


是的,没有任何问题。
尼克拉斯,

1
尽管在清单中声明了权限,但我仍然遇到以下错误:12-28 14:09:35.063 7193-7215 / com.blaha.test E / GrantPermissionCallable:权限:android.permission.SET_TIME无法被授予!
Faux Pas

3
这应该被接受为正确答案,因为它使用了适当的框架并且可以与其他解决方案进行比较。
罗杰·阿里恩

1
不要忘记在Manifest上添加权限,您还可以在以下路径中创建一个新的AndroidManifest.xml文件:/ src / debug
Kunami

1
有什么方法可以在每个方法/测试级别执行此操作?我是否真的需要将针对应用设置类的测试分成两个测试类,一个用于许可前许可,一个用于许可后许可?
allofmex

47

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

看一下这个解决方案:

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在Estapress和UiAutomator上方的工具Barista中添加了绿色工具测试:https ://github.com/SchibstedSpain/Barista对其进行了检验,因为我们将对其进行维护逐个发布。


1
创造奇迹!也适用于使用不同语言环境的测试,这就是我的情况。
Sloy

1
太棒了!在大多数答案中,人们不会使用护理设备的语言。.所以,做得很好,谢谢!!
alxsimo 2013年

您也可以复制代码,但如果还要测试拒绝权限,则将索引更改为0。索引2是不再询问我的复选框。
伊桑(Ethan)2016年

@Ethan哭!刚刚更新了代码,避免了“不再询问我”复选框。它在测试环境中没有用。顺便说一句,感谢您的反馈!
Roc Boronat

1
我刚刚检查了咖啡师,我真的推荐这个图书馆。在最新版本中,使用ID代替可能更改的索引,现在的代码也位于Kotlin中。
米洛什·切尔尼洛夫斯基

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
请注意,这不适用于非EN语言环境,或者将来按钮文本会更改。如果制造商自定义对话框,则某些设备甚至可能会失败。
JoseAlcérreca'16

6
奇怪的是,这在装有Android 6的LG Nexus 5上不起作用。找不到文本“ ALLOW”。使用以下代码可以正常工作:new UiSelector()。clickable(true).checkable(false).index(1)
David

1
您如何进行浓缩咖啡测试?
巴尔加夫

@Bharga无法使用Espresso。但是您可以同时使用UIAutomation和Espresso。
Thanh Le

如果您要处理本地化(甚至可能由于制造商而可能更改字符串),则他们可能使用的字符串资源ID是android.R.string.allow,可在以下位置
David Liu

20

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

@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
我在Google测试示例代码中找到了类似的代码。它适用于该程序包中的呼叫电话示例应用程序,但是如果在应用程序启动后立即询问权限(例如摄像头权限),则该功能将不起作用。有人知道为什么吗?
fangmobile

您可以尝试@BeforeClass。
JoseAlcérreca'16

@ fangmobile.com也许是因为该活动也以@ Before规则开始?
nickmartens1980 '16

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无法处理与权限对话框的交互,因为Dialog是其他应用程序的实例-com.android.packageinstaller。
否认2015年

您是否尝试过使用UIAutomator接受弹出窗口?对我来说,似乎没有找到“允许”按钮。任何想法如何解决这个问题?
conca

1
@conca,您必须寻找“允许”文本,而不是“允许”。系统在运行时将字符串大写,但实际上可以将其保存为“允许”。
否认

@denys,在Nexus 6上它可以使用ALLOW运作,但在LG Nexus 5上则无法使用,这种差异确实令人烦恼。
大卫

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

浓缩咖啡更新

此单行代码立即授予在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/


是否有类似的方法来拒绝权限以在测试期间测试该案例?
莫里西奥·托格尼

2

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

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似乎不是,因为这通常需要重新启动应用才能应用!
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);

0

我知道答案已经被接受,但是,if除了一遍又一遍地提出的陈述之外,另一种更优雅的方法是在特定版本的OS的实际测试中执行以下操作:

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

如果assumeTrue使用表达式的结果为false调用该函数,则测试将暂停并被忽略,假设在SDK 23之前的设备上执行该测试,这就是您想要的。


0

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

现在还装在一个SDK,但主要的想法是重写的功能ContextWrapper.checkSelfPermissionActivityCompat.requestPermissions进行操作,并返回模拟结果,将应用诱骗到要测试的不同场景中,例如:权限被拒绝,因此应用请求它并以授予结束允许。即使该应用程序一直都具有许可,也会发生这种情况,但是其想法是,该方法受到了覆盖实现的模拟结果的欺骗。

此外,该实现还有一个TestRule被调用的PermissionRule类,可以在测试类中使用它来轻松模拟所有条件以无缝测试权限。也可以进行断言,例如确保应用已调用requestPermissions()


0

对于允许的权限,必要的时候,我觉得最简单的方法是用咖啡师PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.GET_ACCOUNTS)直接需要该权限的考验。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.