2017. július 28.

Automatizált képernyőképek Androidon

10 perc olvasási idő

Automatizált képernyőképek Androidon

ConfCat appjainkat évente újra megrendezésre kerülő konferenciákon használják. Ezekhez minden egyes alkalommal új képernyőképeket kell készíteni a konferenciák előtt a Play Store-hoz. 5 képernyőről, 2 nyelven, 3 méretben. Összesen 30 képet. Ezt az egészet pár perc alatt le lehetne rendezni, ha automatizáljuk. Akár a felületelemek különböző nyelveken való tesztelését is megkönnyíthetjük ezzel, mert csak a képeket kell átnézni. Így gyorsabban észrevehetőek a hibák.

Kezdjünk bele

Képernyőképeket tesztelés közben kell készíteni. Ehhez a készüléken vagy emulátoron futtatott tesztek szükségesek. Az ilyen teszteket instrumented teszteknek nevezik.

Most azzal nem fogok foglalkozni, hogy kell ilyen teszteket írni. Ha nem tudod, hogyan kezdj ilyenbe, akkor az Espresso nevű könyvtárat tudom ajánlani, amivel az Android Studioba épített Espresso Test Recorder segítségével, emulátoron keresztül rögzítheted tesztjeidet.
A képernyőképeket az UIAutomator és a Spoon könyvtár segítségével készítjük.

Spoon használata

A Square Spoon nevű könyvtárát használjuk képernyőképek lementésére és megjelenítésére. Továbbá ez futtatja az összes tesztet az elérhető vagy az általunk beállított készülékeken.

A project szintű build.gradle fájlban hozzáadjuk:


buildscript {
    dependencies {
        classpath 'com.stanfy.spoon:spoon-gradle-plugin:1.2.2'
        classpath 'com.squareup.spoon:spoon-runner:1.7.1'
    }
}

Majd az app build.gradle fájlban hozzáadjuk és konfigurálhatjuk:


apply plugin: 'spoon'

dependencies {
  androidTestCompile 'com.squareup.spoon:spoon-client:1.7.1'
}

// ez a rész opcionális
spoon {
    // debug kimenethez
    debug = true
    // teszt osztály megadása
    className = 'com.confcat.test.ScreenShotSuite'
    // készülékenkénti szekvenciális teszteléshez
    sequential = true
    // minden jog megadása (android M és felette)
    grantAllPermissions = true
}

Egy sor kóddal képernyőképet lehet készíteni, amit aztán egy webes felületen meg lehet tekinteni:


Spoon.screenshot(activity, fileName);

Mindegyik képernyőről tudunk készíteni képet, csak rá kell lépni a teszt során.

A webes felületet az app/build/spoon könyvtár alatt található. Itt fog megjelenni az összes egy futás során készített kép.

Spoon web interface

Sajnos OpenGL-t alkalmazó elemekről nem lehet készíteni képet. Ilyen például a Google Maps. Ehelyett az UIAutomator könyvtárat kell alkalmazni a Spoonnal karöltve. A Spoontól megkapjuk a fájlt, ahova a képet menti, majd az UIAutomator segítségével megcsináljuk a képet:


File screenshot = Spoon.screenshot(activity, fileName);
UiDevice.getInstance(getInstrumentation()).takeScreenshot(screenshot);

Ha nem sikerült valamilyen hiba miatt képet készíteni, annak oka is megtalálható a webes felületen, naplóval együtt.

Nyelvváltás

Mivel több nyelven is elérhető az alkalmazás, ezért valahogy nyelvet kell váltani és újra futtatni a teszteket a másik nyelvvel. Ezt parametrizált teszt segítségével lehet megvalósítani:


@RunWith(Parameterized.class)
@LargeTest
public class MainActivityTest {

  public MainActivityTest(Locale locale) {
    this.locale = locale;
  }

  @Parameterized.Parameters
  public static Object[] data() {}
}

Ekkor a data() metódusban vissza kell adni az összes nyelvet, amivel futnia kell a teszteknek.

Minden teszt előtt újraindul az Activity. Még az Activity indulása előtt be kell állítani a nyelvet. Ha máskor állítod be, nem fog sikerülni. Ehhez először az szükséges, hogy az Activity ne induljon el automatikusan:


@Rule
public ActivityTestRule activityRule = new ActivityTestRule<>(MainActivity.class, true, false);

Az ActivtyTestRule konstrukturának harmadik paramétere hamis, ekkor nem indul el automatikusan. Minden teszt indulása előtt beállítjuk a nyelvet és elindítjuk az Activityt:


@Before
public void setUp() throws Exception {
    LocaleUtil.setLocale(InstrumentationRegistry.getTargetContext(), locale);
    activityRule.launchActivity(null);
}

A LocaleUtil.setLocale() metódusa, ami a nyelvet váltja:


public static void setLocale(Context context, Locale locale) {
    Locale.setDefault(locale);
    Resources resources = context.getResources();
    Configuration configuration = resources.getConfiguration();
    if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.JELLY_BEAN) {
      configuration.setLocale(locale);
  } else {
      configuration.locale = locale;
  }
    resources.updateConfiguration(
      configuration,
      resources.getDisplayMetrics()
    );
}

Sajnos itt van egy bökkenő. A Spoon elromlik, mert rosszak lesznek az elnevezések. Indexelni kell a nyelveket 0-tól és működni fog minden. Ekkor már a nyelv és az index is paraméterként kerül a konstruktorba:


public MainActivityTest(Locale locale, int localeIndex) {
      this.locale = locale;
      this.localeIndex= localeIndex;
}

Ezt az indexet kell megadni a Spoonnak, amikor képet készítünk:


File screenshot = Spoon.screenshot(
              activityRule.getActivity(),
              fileName,
              getClass().getCanonicalName(),
              String.format("%s[%s]", testName, localeIndex)
);

Demo mód integrálása

Nekünk Play Store-hoz kellenek a képek, ezért nem szeretnénk, ha felesleges dolgok lennének a státusz baron. Szerencsére 23-as API-tól elérhető a demo mód, amivel testre lehet azt szabni.

Demo mode

Ezt minden futás előtt engedélyezni szeretnénk, majd utána kikapcsolni. Hogy ez telesüljön egy Gradle taskot kell lefuttatni a Spoon Gradle taskja előtt, majd egyet utána. Gradle taskhoz létrehozunk egy buildSrc/src/main/groovy könyvtárszerezetet a project gyökeréből kiindulva. A groovy almappában létre lehet hozni egy .groovy kiterjesztésű fájt, amit a task futtat majd.

A buildSrc könyvtárban található build.gradle fájlnak az alábbiakat kell tartalmaznia:


apply plugin: 'groovy'

repositories {
  mavenCentral()
  jcenter()
}

dependencies {
  compile 'com.squareup.spoon:spoon-runner:1.7.1'
  compile 'com.android.tools.ddms:ddmlib:25.3.0'
  compile 'com.android.tools.build:gradle:2.3.3'
  compile gradleApi()
  compile localGroovy()
}

Itt annyi történik, hogy a groovy plugint használjuk groovy fordításához. Spoon függőségen keresztül inicializáljuk az ADB-t, ddmlib-et az ADB használatához és Android Gradle plugint az SDK könyvtár megtalálásához.

A task inicializálja az ADB-t. Megkérdezi milyen eszközök vannak. Ha be kell kapcsolni a prezentációs módot végigmegy az összesen és bekapcsolja azt, ha ki kell kapcsolni, akkor kikapcsolja mindegyiken.


class DemoModeTask extends DefaultTask {

    Duration DEFAULT_ADB_TIMEOUT = Duration.ofMinutes(10)

    boolean isDemoModeEnabled;

    @TaskAction
    public void run() {
        //init adb
        BaseExtension android = project.android
        AndroidDebugBridge adb = SpoonUtils.initAdb(
                android.sdkDirectory,
                DEFAULT_ADB_TIMEOUT.toMillis()
        )
        //get all devices
        IDevice[] devices = adb.getDevices()
        CollectingOutputReceiver grantOutputReceiver = new CollectingOutputReceiver()
        if (isDemoModeEnabled) {
            for (IDevice device : devices) {
                //turn demo mode on
                device.executeShellCommand("settings put global sysui_demo_allowed 1", grantOutputReceiver)
                device.executeShellCommand("am broadcast -a com.android.systemui.demo -e command enter", grantOutputReceiver)
                //set clock to 12:00
                device.executeShellCommand("am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1200", grantOutputReceiver)
                //hide notification icon
                device.executeShellCommand("am broadcast -a com.android.systemui.demo -e command notifications -e visible false", grantOutputReceiver)
                //change network bars to full with no lte 3g or other logo
                device.executeShellCommand("am broadcast -a com.android.systemui.demo -e command network -e mobile show -e datatype none -e level 4", grantOutputReceiver)
            }
        } else {
            for (IDevice device : devices) {
                //turn off demo mode
                device.executeShellCommand("am broadcast -a com.android.systemui.demo -e command exit", grantOutputReceiver)
            }
        }
    }
}

Miért van minden készüléken beállítva a prezentációs mód? Alternatív esetben szükséges lenne az összes készülékhez generálni külön taskot, mivel egy task csak egyszer futhat le. A jelenlegi egy egyszerűbb, gyorsabb megoldás.

Amint ez megvan, az app build.gradle-ben, beállítjuk:


task enableDemoMode(type: DemoModeTask) {
    isDemoModeEnabled = true
}
task disableDemoMode(type: DemoModeTask) {
    isDemoModeEnabled = false
}

tasks.whenTaskAdded() { theTask ->
    if (theTask.name.contains("spoon")) {
        theTask.dependsOn(enableDemoMode)
        theTask.finalizedBy(disableDemoMode)
    }
}

A isDemoModeEnabled szabályozza, hogy ki vagy bekapcsoljuk a demó módot. Ehhez 2 taskot kell létrehozni. Egyet, ami a Spoon előtt fut le és egyet, ami utána.

Eredmény

Ezek után pár perc alatt megvannak a képek, amit lehet böngészni. Egy másik Gradle taskban akár automatikusan is fel lehetne tölteni a Play Store-ba, ugyanis van hozzá API. De ezt majd máskor…

Vaszil Ádám

Már sok-sok éve szeretne programozó lenni, de mivel ennyi nyelvet nehéz megtanulni, ezért inkább csak a Javara koncentrál.

Vaszil Ádám

Hozzászólások