Writing an Appium test in Kotlin

Kotlin is a modern programming language that focuses on conciseness, clarity, and code safety. Google officially adopted Kotlin by adding support into Android Studio in 2017 and since then has announced it as the preferred language for Android developers at Google I/O 2019. Perhaps your Android app team has incrementally added Kotlin code to your project or have elected to start new Android projects in Kotlin moving forward. Having the app code and UI automation code in the same language helps engineering and QA teams level set on the critical user journeys being tested. I’d like to share the basics of getting started with writing an Appium test in Kotlin. I’ll be using the Google News Android app as the target for this example and test on a real Google Pixel 3 XL from the HeadSpin platform.

Kotlin is fully compatible with Java so we can fortunately leverage the Appium Java client and JUnit libraries in addition to writing our code in the IntelliJ IDE. IntelliJ has a free community version called IntelliJ IDEA CE, which can be downloaded from their website or via your package manager.

Getting started

In IntelliJ IDE, navigate to File > New Project. Select Gradle and under Additional Libraries and Frameworks select Kotlin/JVM

In the Project file view on the left, open the build.gradle file and add the following dependencies:

testCompile group: 'junit', name: 'junit', version: '4.13'
compile group: 'io.appium', name: 'java-client', version: '7.3.0'

We will add the Appium tests in the src/test/kotlin project directory.

Right click the kotlin subdirectory and select New > Kotlin File/Class. Create three new classes:

  • ProjectCapabilities
  • TestBase
  • GoogleNewsAndroidTest

We will structure the project in these three classes to make the organization clear. The ProjectCapabilities class will return the Desired Capabilities, TestBase will be a base class for our test for scaffolding the test setup and teardown, and lastly the GoogleNewsAndroidTest class will inherit from our TestBase, use the Desired Capabilities, and contain our test logic.

1. ProjectCapabilities Class

Let’s get started with the ProjectCapabilities class. This class will have a method to return the Desired Capabilities.

import org.openqa.selenium.remote.DesiredCapabilities

class ProjectCapabilities {
    companion object {
        fun AndroidBaseCapabilities(): DesiredCapabilities {
            val caps = DesiredCapabilities()
            caps.setCapability("autoAcceptAlerts", true)
            caps.setCapability("platformName", "Android")
            caps.setCapability("automationName", "UiAutomator2")
            caps.setCapability("deviceName", "8ABY0H6YG")
            caps.setCapability("udid", "8ABY0H6YG")
            caps.setCapability("appPackage", "com.google.android.apps.magazines")
            caps.setCapability("appActivity", "com.google.apps.dots.android.app.activity.CurrentsStartActivity")
            return caps
        }
    }
}

Functions and Methods in Kotlin are declared with the “fun” keyword. You can also indicate the function return type with a colon followed by the type. We can make this a static class method by declaring it in a “companion object.” You can create read-only variables with “val” and variables that can be reassigned with “var”.

In this case I am targeting a real Pixel 3 XL device in the HeadSpin platform. From the HeadSpin Web UI you can get the minimum Desired Capabilities for the device by clicking on the vertical dots menu and selecting Automation Configuration.

For the appPackage and appActivity I am specifying the associated values for the Google News Android app.

2. TestBase Class

Next, let’s work on our TestBase class. By default classes in Kotlin are implicitly final and can’t be inherited. To make a class inheritable, we’ll mark it with the “open” keyword. In this class we will create property variables for the: driver, capabilities, HeadSpin API Token, and WebDriver URL. We’ll provide visibility modifiers to our properties, in this case:

  • Protected: Only visible in the class or subclasses
  • Private: Only visible in the class

We’ll also mark our Desired Capabilities variable as “open” so it can be overridden from our GoogleNewsAndroidTest class which inherits from the TestBase class.

In addition you can indicate whether a variable can be null. The ability to declare this upfront is a great feature of the Kotlin language because it protects against unexpected NullPointerExceptions from our code. In this case we can indicate that the driver and caps can be null with “?” after the type and and set them to null. Kotlin also supports string interpolation by using the “$” character to interpolate a variable or “${}” to interpolate an expression. We’ll use this convenient language feature to pass our HeadSpin API Token to our WebDriver URL string.

Our TestBase will also have two JUnit annotations: @Before for our setup function and @After for our teardown to perform a driver quit. Our setup function will be straightforward and it will instantiate the driver object. In our teardown function we’ll check to see if the driver object is not null and if so quit our session. Otherwise we will throw an Exception. Kotlin provides a convenience operator for checking for a null condition with “?:”. This is called the Elvis Operator because if you turn your head sideways the question mark character looks like a swoosh of hair and the colon looks like eyes.

Do you see the resemblance now? Haha either way, the source code for the class can be found below and let’s carry on.

import io.appium.java_client.MobileElement
import io.appium.java_client.android.AndroidDriver
import org.junit.After
import org.junit.Before
import org.openqa.selenium.remote.DesiredCapabilities
import java.lang.Exception
import java.net.URL

open class TestBase {
    protected var driver: AndroidDriver<MobileElement>? = null
    protected open var caps: DesiredCapabilities? = null
    private val headSpinAPIToken: String = "your-api-token-here"
    private val webDriverURL: URL = URL("https://appium-dev.headspin.io/v0/$headSpinAPIToken/wd/hub")


    @Before
    fun setUp() {
        this.driver = AndroidDriver(webDriverURL, caps)
    }

    @After
    fun tearDown() {
        this.driver?.quit() ?: throw Exception("Driver instance was unable to quit.")
    }

}

3. GoogleNewsAndroidTest Class

Our last class, GoogleNewsAndroidTest will have our test logic. Here we have one test function annotated with the JUnit Test annotation. Our test will be simple and straightforward, we will tap on the Headlines tab button and then scroll down from the list of news articles. Since we will be performing driver actions we’ll need to first ensure the driver is not null. Kotlin provides a way to perform an operation only if the value is not null using the “?” (the safe call operator) together with “let.”

import io.appium.java_client.MobileBy
import org.junit.Test
import org.openqa.selenium.interactions.Interaction
import org.openqa.selenium.interactions.PointerInput
import org.openqa.selenium.interactions.Sequence
import org.openqa.selenium.remote.DesiredCapabilities
import org.openqa.selenium.support.ui.ExpectedConditions
import org.openqa.selenium.support.ui.WebDriverWait
import java.time.Duration

class GoogleNewsAndroidTest: TestBase() {
    override var caps: DesiredCapabilities? = ProjectCapabilities.AndroidBaseCapabilities()
    private val headlinesTabButton: String = "com.google.android.apps.magazines:id/tab_headlines"

    @Test
    fun headlinesScrollTest() {

        // Set an explicit wait of 10 seconds
        val wait = WebDriverWait(driver?.let { it }, 10)

        // Tap on the Headlines tab button
        wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.id(headlinesTabButton))).click()

        // Scroll Down
        val finger: PointerInput = PointerInput(PointerInput.Kind.TOUCH, "finger")
        val moveToStart: Interaction = finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), 726, 2452)
        val pressDown: Interaction = finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg());
        val moveToEnd: Interaction = finger.createPointerMove(Duration.ofMillis(1000), PointerInput.Origin.viewport(), 726, 660)
        val pressUp: Interaction = finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg())

        val swipe = Sequence(finger, 0)
        swipe.addAction(moveToStart)
        swipe.addAction(pressDown)
        swipe.addAction(moveToEnd)
        swipe.addAction(pressUp)

        driver?.let { it.perform(arrayListOf(swipe)) }

    }
}

Conclusion

Our work here is done! We can now execute our test and watch it in action.

Congratulations you now have a taste of what it’s like to write an Appium test in Kotlin! If this article has helped further your knowledge feel free to Like or Share it. You can download the full project source code here.