web-dev-qa-db-fra.com

Comment gérer les autorisations d'exécution des tests Android Marshmallow espresso

J'utilise l'espresso pour les tests, mais parfois, j'essaie d'obtenir une mémoire externe sous forme d'image. Avec Marshmallow, j'ai besoin d'une autorisation d'exécution. Dans le cas contraire, un crash se produira et le test échouera. 

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'

comment puis-je gérer ce droit? 

dois-je écrire un test pour les autorisations d'exécution ou un moyen de le désactiver pour les tests? 

devrais-je donner des autorisations avant que les tests ne se déroulent comme elle le dit ici? https://www.youtube.com/watch?list=PLWz5rJ2EKKc-lJo_RGGXL2Psr8vVCTWjM&v=C8lUdPVSzDk

25
Caipivara

Vous pouvez créer une tâche de dégradé Android pour accorder une autorisation:

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

Et voici la commande pour exécuter la tâche: gradle grantDebugPermissions

10
Luiz Augusto

METTRE À JOUR! Vous pouvez maintenant utiliser Rule depuis Android Testing Support Library

Il est plus approprié d'utiliser que des règles personnalisées.

Réponse obsolète:

Vous pouvez ajouter une règle de test pour réutiliser du code et ajouter plus de flexibilité:

/**
 * 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);
            }
        }
    }
}

Après cela, vous pouvez utiliser cette règle dans vos classes de test:

@Rule
public final PermissionsRule permissionsRule = new PermissionsRule(
new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS});

Garder en tete:

  1. La règle n’affecte pas les méthodes @Before car toutes les règles sont exécutées après
  2. executeShellCommand est asynchrone et si vous avez besoin d'autorisations acceptées juste après le début du test, pensez à ajouter un peu de retard
24
Denis Nek

Vous pouvez utiliser GrantPermissionRule . Cette règle accordera toutes les autorisations d'exécution demandées pour toutes les méthodes de test de cette classe de test.

@Rule 
public GrantPermissionRule mRuntimePermissionRule
            = GrantPermissionRule.grant(Manifest.permission.READ_PHONE_STATE);
10
vsvankhede

Vous pouvez y parvenir facilement en accordant une autorisation avant de commencer le test. Par exemple, si vous êtes censé utiliser l'appareil photo pendant l'exécution du test, vous pouvez accorder l'autorisation comme suit:

@Before
public void grantPhonePermission() {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        getInstrumentation().getUiAutomation().executeShellCommand(
                "pm grant " + getTargetContext().getPackageName()
                        + " Android.permission.CAMERA");
    }
}
9

Il y a GrantPermissionRule dans Android Testing Support Library , que vous pouvez utiliser dans vos tests pour accorder une autorisation avant de commencer des tests.

@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(Android.Manifest.permission.CAMERA);
7
NSK

Dans la configuration multi-saveurs, quelle que soit votre tâche d'instrumentation, disons connectedYourFlavorDebugAndroidTest, vous pouvez spécifier les autorisations que vous souhaitez attribuer avant l'exécution des tests sur tous les périphériques connectés:

gradlew grantYourFlavorDebugPermissions -Ppermissions=Android.permission.ACCESS_FINE_LOCATION,Android.permission.ACCESS_COARSE_LOCATION

Voir l'extrait de sfjava ci-dessous à copier dans build.gradle pour générer la tâche grantYourFlavorDebugPermissions

4
riwnodennyk

Quelques mises à jour mineures à l'extrait ci-dessus (accessoires de riwnodennyk) - ce qui a très bien fonctionné pour moi lorsque je compilais contre le SDK 24 et avec la 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);
    }
}
3
sfjava

J'ai implémenté une solution qui exploite les classes wrapper, redéfinit et construit les variantes de configuration. La solution est assez longue à expliquer et se trouve ici: https://github.com/ahasbini/AndroidTestMockPermissionUtils . Il n'a pas besoin de script pour être ajouté dans le système de construction ou exécuté avant d'exécuter les tests.

Il n’a pas encore été emballé dans un sdk mais l’idée principale est de remplacer les fonctionnalités de ContextWrapper.checkSelfPermission() et ActivityCompat.requestPermissions() à manipuler et à renvoyer les résultats fictifs capturant l’application dans les différents scénarios à tester, comme par exemple: l’autorisation a été refusée. avec la permission accordée. Ce scénario se produira même si l'application avait l'autorisation depuis le début, mais l'idée est qu'il a été trompé par les résultats simulés de l'implémentation prépondérante.

De plus, l'implémentation a une classe TestRule appelée PermissionRule qui peut être utilisée dans les classes de test pour simuler facilement toutes les conditions permettant de tester les autorisations de manière transparente. Des affirmations peuvent également être faites, comme s’assurer que l’application a appelé requestPermissions() par exemple.

2
ahasbini

Si vous utilisez la dernière librairie pour espresso 'com.Android.support.test.espresso: espresso-core: 3.0.1', vous pouvez utiliser une seule ligne de code. Tout ce que vous avez à faire est simplement d'ajouter une règle dans la classe Test et de continuer à ajouter les autorisations dont vous avez besoin en tant que paramètres de fonction pour accorder une fonction. Voir ci-dessous:

@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

2
Abhishek Dhotre
    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 ");
                }
            }
        }
    }
0
NickUnuchek