如何管理运行时权限 android marshmallow espresso 测试
How to manage Runtime permissions android marshmallow espresso tests
我正在使用 espresso 进行测试,但有时我会尝试从外部存储获取图像,而使用 marshmallow 我需要运行时权限,否则会出现异常崩溃并且测试将失败。
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'
我怎样才能做到这一点?
我应该为运行时权限编写测试还是有办法禁用它进行测试?
我应该像她在这里说的那样在测试前授予权限 运行 吗? https://www.youtube.com/watch?list=PLWz5rJ2EKKc-lJo_RGGXL2Psr8vVCTWjM&v=C8lUdPVSzDk
您可以使用以下方式授予和撤销权限:
adb shell pm grant com.package.myapp android.permission.<PERMISSION>
adb shell pm revoke com.package.myapp android.permission.<PERMISSION>
要使用 Java 个仪器测试,请从 Google 个样本中调用此方法:
https://github.com/googlesamples/android-testing/blob/ed62c450e43f859333b3113d44dd59f75971b529/ui/espresso/IntentsBasicSample/app/src/androidTest/java/com/example/android/testing/espresso/BasicSample/DialerActivityTest.java#L94
在多风格设置中,无论您的检测任务是什么,比方说 connectedYourFlavorDebugAndroidTest
,您可以指定在所有连接的设备上 运行 测试之前要授予的权限:
gradlew grantYourFlavorDebugPermissions -Ppermissions=android.permission.ACCESS_FINE_LOCATION,android.permission.ACCESS_COARSE_LOCATION
见复制到build.gradle
生成grantYourFlavorDebugPermissions
任务
只是对上面的代码片段进行了一些小的更新(riwnodenyk 的道具)——在针对 SDK 24 和工具版本 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);
}
}
您可以创建一个 Android gradle 任务来授予权限:
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()
}
}
}
}
这是 运行 任务的命令:
gradle grantDebugPermissions
更新! 现在您可以使用 Rule from Android Testing Support Library
比自定义规则更适合使用
过时的答案:
您可以添加测试规则以重用代码并增加灵活性:
/**
* 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);
}
}
}
}
之后你可以在你的测试中使用这个规则类:
@Rule
public final PermissionsRule permissionsRule = new PermissionsRule(
new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS});
切记:
- 规则不影响@Before 方法,因为所有规则都是
之后执行
- executeShellCommand 是异步的,如果您需要在测试开始后立即接受权限,请考虑添加一些延迟
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 ");
}
}
}
}
您可以通过在开始测试前授予权限来轻松实现此目的。例如,如果您要在测试期间使用摄像头运行,您可以按如下方式授予权限
@Before
public void grantPhonePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand(
"pm grant " + getTargetContext().getPackageName()
+ " android.permission.CAMERA");
}
}
您可以使用 GrantPermissionRule。此规则将为该测试中的所有测试方法授予所有请求的运行时权限 class.
@Rule
public GrantPermissionRule mRuntimePermissionRule
= GrantPermissionRule.grant(Manifest.permission.READ_PHONE_STATE);
如果您使用最新的 'com.android.support.test.espresso:espresso-core:3.0.1' 浓缩咖啡库,只需一行代码即可完成。你需要做的只是在Test class中添加一条规则,并不断添加你需要的权限作为函数参数来授予函数。见下文:
@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
我已经实现了一个利用包装器 classes 的解决方案,覆盖并构建变体配置。解决方案解释起来很长,可在此处找到:https://github.com/ahasbini/AndroidTestMockPermissionUtils。它不需要在构建系统中添加任何脚本或在 运行 测试之前执行。
它还没有打包在 sdk 中,但主要思想是覆盖 ContextWrapper.checkSelfPermission()
和 ActivityCompat.requestPermissions()
的功能以进行操作,并且 return 模拟结果欺骗应用程序进入要测试的不同场景如下:权限被拒绝因此应用程序请求它并以授予权限结束。即使应用程序一直具有权限,这种情况也会发生,但想法是它被覆盖实现的模拟结果所欺骗。
此外,该实现还有一个名为 PermissionRule
class 的 TestRule
,可用于测试 classes 以轻松模拟所有条件以测试权限无缝地。也可以做出断言,例如确保应用程序调用了 requestPermissions()
。
Android 测试支持库 中有 GrantPermissionRule,您可以在测试中使用它在开始任何测试之前授予权限。
@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.CAMERA);
adb install -g package.apk
我在自动化测试中遇到了类似的问题:启动应用程序时弹出权限对话框。 adb shell pm grant …
命令没有帮助,但是 adb install
的 -g
选项可以在安装时授予所有权限。这解决了我的问题。
来自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
[…]
我正在使用 espresso 进行测试,但有时我会尝试从外部存储获取图像,而使用 marshmallow 我需要运行时权限,否则会出现异常崩溃并且测试将失败。
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'
我怎样才能做到这一点?
我应该为运行时权限编写测试还是有办法禁用它进行测试?
我应该像她在这里说的那样在测试前授予权限 运行 吗? https://www.youtube.com/watch?list=PLWz5rJ2EKKc-lJo_RGGXL2Psr8vVCTWjM&v=C8lUdPVSzDk
您可以使用以下方式授予和撤销权限:
adb shell pm grant com.package.myapp android.permission.<PERMISSION>
adb shell pm revoke com.package.myapp android.permission.<PERMISSION>
要使用 Java 个仪器测试,请从 Google 个样本中调用此方法: https://github.com/googlesamples/android-testing/blob/ed62c450e43f859333b3113d44dd59f75971b529/ui/espresso/IntentsBasicSample/app/src/androidTest/java/com/example/android/testing/espresso/BasicSample/DialerActivityTest.java#L94
在多风格设置中,无论您的检测任务是什么,比方说 connectedYourFlavorDebugAndroidTest
,您可以指定在所有连接的设备上 运行 测试之前要授予的权限:
gradlew grantYourFlavorDebugPermissions -Ppermissions=android.permission.ACCESS_FINE_LOCATION,android.permission.ACCESS_COARSE_LOCATION
见build.gradle
生成grantYourFlavorDebugPermissions
任务
只是对上面的代码片段进行了一些小的更新(riwnodenyk 的道具)——在针对 SDK 24 和工具版本 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);
}
}
您可以创建一个 Android gradle 任务来授予权限:
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()
}
}
}
}
这是 运行 任务的命令:
gradle grantDebugPermissions
更新! 现在您可以使用 Rule from Android Testing Support Library
比自定义规则更适合使用
过时的答案:
您可以添加测试规则以重用代码并增加灵活性:
/**
* 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);
}
}
}
}
之后你可以在你的测试中使用这个规则类:
@Rule
public final PermissionsRule permissionsRule = new PermissionsRule(
new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS});
切记:
- 规则不影响@Before 方法,因为所有规则都是 之后执行
- executeShellCommand 是异步的,如果您需要在测试开始后立即接受权限,请考虑添加一些延迟
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 ");
}
}
}
}
您可以通过在开始测试前授予权限来轻松实现此目的。例如,如果您要在测试期间使用摄像头运行,您可以按如下方式授予权限
@Before
public void grantPhonePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand(
"pm grant " + getTargetContext().getPackageName()
+ " android.permission.CAMERA");
}
}
您可以使用 GrantPermissionRule。此规则将为该测试中的所有测试方法授予所有请求的运行时权限 class.
@Rule
public GrantPermissionRule mRuntimePermissionRule
= GrantPermissionRule.grant(Manifest.permission.READ_PHONE_STATE);
如果您使用最新的 'com.android.support.test.espresso:espresso-core:3.0.1' 浓缩咖啡库,只需一行代码即可完成。你需要做的只是在Test class中添加一条规则,并不断添加你需要的权限作为函数参数来授予函数。见下文:
@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
我已经实现了一个利用包装器 classes 的解决方案,覆盖并构建变体配置。解决方案解释起来很长,可在此处找到:https://github.com/ahasbini/AndroidTestMockPermissionUtils。它不需要在构建系统中添加任何脚本或在 运行 测试之前执行。
它还没有打包在 sdk 中,但主要思想是覆盖 ContextWrapper.checkSelfPermission()
和 ActivityCompat.requestPermissions()
的功能以进行操作,并且 return 模拟结果欺骗应用程序进入要测试的不同场景如下:权限被拒绝因此应用程序请求它并以授予权限结束。即使应用程序一直具有权限,这种情况也会发生,但想法是它被覆盖实现的模拟结果所欺骗。
此外,该实现还有一个名为 PermissionRule
class 的 TestRule
,可用于测试 classes 以轻松模拟所有条件以测试权限无缝地。也可以做出断言,例如确保应用程序调用了 requestPermissions()
。
Android 测试支持库 中有 GrantPermissionRule,您可以在测试中使用它在开始任何测试之前授予权限。
@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.CAMERA);
adb install -g package.apk
我在自动化测试中遇到了类似的问题:启动应用程序时弹出权限对话框。 adb shell pm grant …
命令没有帮助,但是 adb install
的 -g
选项可以在安装时授予所有权限。这解决了我的问题。
来自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 […]