Running Android Tests in Docker

Running Android Tests in Docker

In this article, you'll learn how to run and test their automated Android mobile application tests in a Docker container.

In this article, you'll learn how to run and test their automated Android mobile application tests in a Docker container.

As part of the project I’m currently engaged on, my team is writing automated tests for an application which has a web interface, and also two mobile apps, one for Android, and one for iOS. As part of the project, we’ve built a test automation pipeline which runs our tests against our application to ensure changes we’re making don’t impact other tests. Yes, we’re testing our tests. One of the challenges we ran into was ensuring we could verify our Android and iOS tests still worked in a timely fashion. The solution we eventually found worked best was to create our own emulators inside of a Docker container to test on.

The Setup

If you’re not familiar with Docker, check out some of our many excellent posts on it. It also works great for testing. Essentially, instead of spinning up a web application inside a container, we wanted to spin up an Android emulator, running our app. We would then connect to the container, the same way we would a tethered physical device, a local emulator, or something in the cloud. This made it incredibly cheap (and fast) to parallelize our testing. All we needed to do was launch multiple Docker containers, and then connect using ADB to each one.

The Dockerfile

Unfortunately, building the Docker container wasn’t straightforward. We needed a Docker container with Android SDK installed, an Android VM created, and Appium. Because I had gotten everything running just fine locally (Ubuntu), I decided to start with a similar base image (maven:3.5.2-jdk-8). We then installed the ADK, set up the emulator, and installed Appium. Finally, we coped over the APK and test suite. We soon discovered that the base image didn’t have kvm set up, so we needed to enable this. Not an easy task, which unfortunately required a manual step. As a result, I decided to split this into two Docker files, a new base one with kvm setup, and one with all of the installs.

DockerfileKVM

This Docker container was relatively straightforward, however, not quite simple. I built it once, uploaded it to my local Docker repository, and then built on top of it for the Docker Android container. All I did was install kvm, and manually copy over the proper lib modules. The Dockerfile looked like:

FROM maven:3.5.2-jdk-8
#debian based

RUN apt-get update -qqy \
    && apt-get -qqy install libglu1 qemu-kvm libvirt-dev virtinst bridge-utils msr-tools kmod \
    && wget -q http://security.ubuntu.com/ubuntu/pool/main/c/cpu-checker/cpu-checker_0.7-0ubuntu7_amd64.deb \
    && dpkg -i cpu-checker_0.7-0ubuntu7_amd64.deb \
    && apt-get install -f \
    && kvm-ok


From there, I built and tagged the Docker file. I then ran it, logged in (-it), and copied over my machine’s lib modules (/lib/modules). Please note, your mileage might vary based on your machine’s kernel and version. Then I pushed this image into my local Docker repository.

DockerfileAndroid

This Docker container had a lot more steps, but ultimately wasn’t too much more complex. I set up my Android SK, loaded up my emulator, installed Appium, and copied over our APK and tests. The Dockerfile looked like this:

FROM kvm:maven-3.5.2-jdk-8#tag we gave to DockerfileKVM
# debian based

ENV UDIDS=""

#=====================
# Install android sdk
#=====================
ARG ANDROID_SDK_VERSION=4333796
ENV ANDROID_SDK_VERSION=$ANDROID_SDK_VERSION
ARG ANDROID_PLATFORM="android-25"
ARG BUILD_TOOLS="26.0.0"
ENV ANDROID_PLATFORM=$ANDROID_PLATFORM
ENV BUILD_TOOLS=$BUILD_TOOLS

# install adk
RUN mkdir -p /opt/adk \
    && wget -q https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_VERSION}.zip \
    && unzip sdk-tools-linux-${ANDROID_SDK_VERSION}.zip -d /opt/adk \
    && rm sdk-tools-linux-${ANDROID_SDK_VERSION}.zip \
    && wget -q https://dl.google.com/android/repository/platform-tools-latest-linux.zip \
    && unzip platform-tools-latest-linux.zip -d /opt/adk \
    && rm platform-tools-latest-linux.zip \
    && yes | /opt/adk/tools/bin/sdkmanager --licenses \
    && /opt/adk/tools/bin/sdkmanager "emulator" "build-tools;${BUILD_TOOLS}" "platforms;${ANDROID_PLATFORM}" "system-images;${ANDROID_PLATFORM};google_apis;armeabi-v7a" \
    && echo no | /opt/adk/tools/bin/avdmanager create avd -n "Android" -k "system-images;${ANDROID_PLATFORM};google_apis;armeabi-v7a" \
    && mkdir -p ${HOME}/.android/ \
    && ln -s /root/.android/avd ${HOME}/.android/avd \
    && ln -s /opt/adk/tools/emulator /usr/bin \
    && ln -s /opt/adk/platform-tools/adb /usr/bin
ENV ANDROID_HOME /opt/adk

#====================================
# Install latest nodejs, npm, appium
#====================================
ARG NODE_VERSION=v8.11.3
ENV NODE_VERSION=$NODE_VERSION
ARG APPIUM_VERSION=1.9.1
ENV APPIUM_VERSION=$APPIUM_VERSION

# install appium
RUN wget -q https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.xz \
    && tar -xJf node-${NODE_VERSION}-linux-x64.tar.xz -C /opt/ \
    && ln -s /opt/node-${NODE_VERSION}-linux-x64/bin/npm /usr/bin/ \
    && ln -s /opt/node-${NODE_VERSION}-linux-x64/bin/node /usr/bin/ \
    && ln -s /opt/node-${NODE_VERSION}-linux-x64/bin/npx /usr/bin/ \
    && npm install -g [email protected]${APPIUM_VERSION} --allow-root --unsafe-perm=true \
    && ln -s /opt/node-${NODE_VERSION}-linux-x64/bin/appium /usr/bin/

EXPOSE [4723,2251,5555]
CMD ["docker-entrypoint.sh"]


What you’ll notice is there was one last piece to this puzzle, the docker-entrypoint, which we used to launch the emulator, and get everything connected with Appium. This luckily was pretty easy, once you knew what to do.

docker-entrypoint.sh

#!/bin/bash

# launch the emulator
exec /opt/adk/tools/emulator -avd Android -no-audio -no-window &

# setup appium
while [ -z $udid ]; do
    udid=`adb devices | grep emulator | cut -f 1`
done
exec appium -p 4723 -bp 2251 --default-capabilities '{"udid":"'${udid}'"}' &


And that was it, all we needed to do, was launch our tests.

Test Execution

Without getting into the specifics of the project, we could do that either locally, or through another Docker container, but the simplest way was definitely locally, using Maven. We could do this by simply specifying the Docker IP, the same way you would if you have the emulator running locally, or the device tethered. Swapping ports is simple enough as well.

Final Thoughts

We did run into one last issue, which was trying to run this all in AWS. We’ve been trying to keep our pipelines as fast moving as possible, and a large portion of that means dynamically provisioned machines in AWS when we need them, it greatly increased our throughput. Unfortunately, AWS machines don’t support nested KVM (yes, I’m aware of using metal, but we wanted to avoid that cost increase). Unfortunately, that meant a large portion of this couldn’t be used in this fashion. Stay tuned for the next blog post, in which I get into the work around to solve this issue.

Find this useful? Got stuck? As always, please leave some comments below.

Learn More

An illustrated guide to Kubernetes Networking

AWS DevOps: Introduction to DevOps on AWS

Getting started with Flutter

Android Studio for beginners

Building a mobile chat app with Nest.js and Ionic 4

Creating an iOS app with user presence using Node.js and Swift

Let’s Develop a Mobile App in Flutter

Docker Tutorial for Beginners

Docker Basics: Docker Compose

Docker and Kubernetes: The Complete Guide

Docker Mastery: The Complete Toolset From a Docker Captain

Docker for the Absolute Beginner - Hands On - DevOps

Learn DevOps: The Complete Kubernetes Course

WordPress in Docker. Part 1: Dockerization

WordPress in Docker. Part 1: Dockerization

This entry-level guide will tell you why and how to Dockerize your WordPress projects.

This entry-level guide will tell you why and how to Dockerize your WordPress projects.

Kotlin Coroutines on Android - How to use Coroutines on Android

Kotlin Coroutines on Android - How to use Coroutines on Android

Coroutines are a Kotlin feature that convert async callbacks for long-running tasks, such as database or network access, into sequential code. This Kotlin Coroutines tutorial will show you how to use coroutines on Android, and how the new androidx-concurrent library makes it easy to use them to get things off the main thread. You'll also learn how the new library helps coroutines work with Architecture Components. This session also covers coroutine patterns, best practices, and even how to test coroutines!

Kotlin Coroutines on Android - How to use Coroutines on Android.

Coroutines are a feature of Kotlin that help convert callback-based code into sequential code, making code easier to read, write, and understand. This session will show you how to use coroutines on Android, and how the new androidx-concurrent library makes it easy to use them to get things off the main thread. You'll also learn how the new library helps coroutines work with Architecture Components. This session also covers coroutine patterns, best practices, and even how to test coroutines!

What is Docker | Docker Tutorial for Beginners

What is Docker | Docker Tutorial for Beginners

This DevOps Docker Tutorial on what is docker will help you understand how to use Docker Hub, Docker Images, Docker Container & Docker Compose. This tutorial explains Docker's working Architecture and Docker Engine in detail.

This Docker tutorial also includes a Hands-On session around Docker by the end of which you will learn to pull a centos Docker Image and spin your own Docker Container. You will also see how to launch multiple docker containers using Docker Compose. Finally, it will also tell you the role Docker plays in the DevOps life-cycle.

The Hands-On session is performed on an Ubuntu-64bit machine in which Docker is installed.