问题
I'm using espresso for testing but sometimes I try to get an image form external storage and with marshmallow I need a Runtime permission otherwise there will be an Exception crash and the test will fail.
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'
how can I manage that right?
should I write test for Runtime permissions or there's a way to disable it for testing?
should I give permissions before the tests run like she says here? https://www.youtube.com/watch?list=PLWz5rJ2EKKc-lJo_RGGXL2Psr8vVCTWjM&v=C8lUdPVSzDk
回答1:
You can create an Android gradle task to grant permission:
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()
}
}
}
}
And this is the command to run the task:
gradle grantDebugPermissions
回答2:
UPDATE! Now you can use Rule from Android Testing Support Library
It is more proper to use than custom rules.
Outdated answer:
You can add test rule to reuse code and add more flexibility:
/**
* 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);
}
}
}
}
After that you can use this rule in your test classes:
@Rule
public final PermissionsRule permissionsRule = new PermissionsRule(
new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS});
Keep in mind:
- Rule does not affect on @Before methods because all rules are executed after that
- executeShellCommand is asynchronous and if you need accepted permissions right after test started consider adding some delay
回答3:
You can grant and revoke permissions using:
adb shell pm grant com.package.myapp android.permission.<PERMISSION>
adb shell pm revoke com.package.myapp android.permission.<PERMISSION>
To use from Java instrumentation tests call this method from Google samples: https://github.com/googlesamples/android-testing/blob/ed62c450e43f859333b3113d44dd59f75971b529/ui/espresso/IntentsBasicSample/app/src/androidTest/java/com/example/android/testing/espresso/BasicSample/DialerActivityTest.java#L94
回答4:
You can use GrantPermissionRule. This rule will grant all the requested runtime permissions for all test methods in that test class.
@Rule
public GrantPermissionRule mRuntimePermissionRule
= GrantPermissionRule.grant(Manifest.permission.READ_PHONE_STATE);
回答5:
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 grantPhonePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand(
"pm grant " + getTargetContext().getPackageName()
+ " android.permission.CAMERA");
}
}
回答6:
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);
回答7:
In the multi-flavor setup, whatever your instrumentation task is, let's say connectedYourFlavorDebugAndroidTest
, you may specify the permissions you want to have granted before the tests are run on all the connected devices:
gradlew grantYourFlavorDebugPermissions -Ppermissions=android.permission.ACCESS_FINE_LOCATION,android.permission.ACCESS_COARSE_LOCATION
See sfjava's snippet below to copy into build.gradle
to generate grantYourFlavorDebugPermissions
task
回答8:
Just a few minor UPDATEs to the above snippet ( props to riwnodennyk ) -- which worked great for me when building against SDK 24 and with tools version 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);
}
}
回答9:
If you are using the latest 'com.android.support.test.espresso:espresso-core:3.0.1' library for espresso this can be done in a single line of code. All you need to do is just add a rule in in Test class and keep adding the permissions you need as function parameters to grant function. See below:
@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
回答10:
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 doesn't need any script to be added in the build system or executed before running the tests.
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.
回答11:
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 ");
}
}
}
}
来源:https://stackoverflow.com/questions/32787234/how-to-manage-runtime-permissions-android-marshmallow-espresso-tests