Send your request Join Sii

We – the Java people – are doomed to the object-oriented paradigm. Don’t take it as a bad feature of the language, though. As the 3 Billion Devices Run Java installer shows (invariably since the beginning of this century), Java has entered into the programming canon and it’s very difficult to find a person in IT who hasn’t heard of this language.

It’s been over 25 years since the first release of Java, and although it’s still evolving, we can’t change one truth about it – Java wasn’t created with functional programming in mind. However, this statement has become less bitter after the Java 8 update, which introduced lambda expressions and streams into our code. 

Nowadays, the JVM universe isn’t limited to Java only. Many noteworthy programming languages have been created. Some of them are more focused on functional programming, which perfectly complements Java.  

In this article, I’d like to familiarize you with selected functionalities of the Kotlin language that will enrich and facilitate writing automatic UI tests, based on the example of web testing.

Kotlin – first steps

An introduction to Kotlin has already been described in the article by Rafał Hałasa. Also, the Jetbrains company prepared a very interesting introduction to the language. Those who are not familiar with Kotlin yet are welcome to have a closer look at both the article and the tutorial.

Using Kotlin in a project 

IntelliJ environment 

In the case of IntelliJ, most of the work with adding support to Kotlin will be done for us by IDE. The user has to add the Kotlin plugin in IDE and create a new Kotlin file (.kt) in the project structure. Then, IntelliJ will automatically detect the usage of Kotlin and ask if we want to configure the project. The essential changes in dependency (adding kotlin-stdlib) and plugin will be made automatically.  

Another environment 

If you use an IDE other than IntelliJ, check a guidebook to implementing Kotlin in Gradle and Maven projects. 

Fun facts 

The Kotlin language was created to be fully cooperative with Java. When searching for a solution for a problem with a code, we can search for sources both in Java and Kotlin languages. If we copy the Java code and paste it into the .kt file, IntelliJ will ask us whether we want to translate the pasted code to Kotlin: 

Sample translation of Java into Kotlin

Why does it work? 

Both Java and Kotlin compile to Java bytecode. The code snippet written in Kotlin may serve as an example: 

fun main() {
        val numbers = listOf(1, 2, 3, 4, 5).map { it * it }
    }

IntelliJ enables previewing the bytecode which generates this snippet. The option is located under the path:
Tools → Kotlin → Show Kotlin Bytecode

A part of the bytecode is presented below 

public final  static  main()V

L0

LINENUMBER 5 L0

ICONST_5

ANEWARRAY java/lang/Integer

DUP

ICONST_0

ICONST_1

INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;

…

We can now decompile this bytecode back into Java. The result is not satisfying, though. The computer has translated the instructions into Java, but the style of the code leaves much to be desired:  

public static  final  void  main() {

Iterable $this$map$iv = (Iterable)CollectionsKt.listOf(new  Integer[]{1, 2, 3, 4, 5});

int $i$f$map = false;

Collection destination$iv$iv = (Collection)(new  ArrayList(CollectionsKt.collectionSizeOrDefault($this$map$iv, 10)));

int $i$f$mapTo = false;

Iterator var6 = $this$map$iv.iterator();

while(var6.hasNext()) {

Object item$iv$iv = var6.next();

int  it = ((Number)item$iv$iv).intValue();

int var9 = false;

Integer var11 = it * it;

destination$iv$iv.add(var11);

}

List numbers = (List)destination$iv$iv;

}

Nevertheless, it is equivalent (from the JVM point of view) with the code written in Kotlin. 

The IntelliJ environment has a fine-tuned Java → Kotlin code swap, and a code created in this way is structurally acceptable in most cases. Java classes can be easily used in Kotlin code. This relation works both ways, which means Kotlin code can also be implemented in Java.  

Let’s take a sample class written in Kotlin: 

class Person(private val name: String, private val age: Int) {
    fun greet() = println("My name is $name and I am $age ${if (age > 1) "years" else "year"} old.")
}

We can now use it in the .java file: 

public class Foo {
    public static void main(String[] args) {
        new Person("Tom", 24).greet();
    }
}

Output:

My name is Tom and I am 24 years old.

Kotlin language functionalities in practice 

Let’s move on to the main body of the article. In it, I will present two approaches of the Kotlin language that will make writing and maintaining the code easier. 

Delegate by Locator

Wait until the fridge is visible and open it. Wait until you see the eggs and take them out.

When giving the instructions to be carried out, we often skip the obvious details we are certain that everyone will assume before performing the assigned task. A simple example can be clicking the elements on the screen. We simply expect them to be visible before we click on them. 

By using the ‘by’ delegators and the higher-order functions, we can build a nice mechanism for searching web elements and deciding when they should be interactive:  

private val usernameField by Id("username")
private val passwordField by Id("password")
private val loginButton by Xpath(".//button[@type='submit']") {
    //poczekaj aż pola username i password zostaną wypełnione
    it.isDisplayed && usernameField.getAttribute("value").isNotEmpty() && passwordField.getAttribute("value").isNotEmpty()
}
(…)

fun pressLoginButton() = loginButton.click()

Such an approach allows hiding the expectation of web elements availability and focusing exclusively on the logic of the test scenario.

What it looks like from the inside 

Preliminary assumptions 

The project uses the Selenium library. There is a reference to WebDriver in the project, which operates UI tests. We create a FluentWait object that we will use to wait for web elements. 

val wait: FluentWait<WebDriver> = FluentWait(driver)
    .withTimeout(Duration.ofSeconds(System.getProperty("wait.timeout")?.toLong() ?: 10L))
    .pollingEvery(Duration.ofMillis(200))
    .ignoring(NoSuchElementException::class.java)
ikona zarowki 1 - Using Kotlin language functionalities to enrich the UI tests written in Java


The wait object doesn’t have to be a field of any class. Defining it outside the body of classes means global access to this object.

We create an abstract Locator class, which expands the ReadOnlyProperty class. The Locator class will contain all the logic concerning operating on web elements. 

Description of class parameters: 

locator – the selected mechanism for finding elements (e.g., Id, Xpath, CssSelector, etc.)  

path – the value of the path that points to the searched web element 

accessRule – a function that takes a web element as a parameter and returns boolean (a condition for interaction with the web element).

abstract class Locator(
    private val locator: (String) -> By,
    private val path: String,
    private val accessRule: (WebElement) -> Boolean
) : ReadOnlyProperty<Any?, WebElement> {
    private val element: WebElement by lazy {
        wait.until { driver.findElement(locator(path)) }
    }
 
    override fun getValue(thisRef: Any?, property: KProperty<*>): WebElement {
        wait.until { accessRule(webElement) }
        return webElement
    }
}
ikona zarowki 1 - Using Kotlin language functionalities to enrich the UI tests written in Java


The By lazy delegator means lazy evaluation. The element field will not be calculated until it is first used.

ikona zarowki 1 - Using Kotlin language functionalities to enrich the UI tests written in Java


We can freely extend the getValue method with further instructions on accessing the web element. However, there are some cases when we don’t want to wait until the object is visible. Such objects can be marked with any annotation (e.g., @Hidden). We will get access to the delegated object through the property argument.  

All that’s left is creating the classes we need to extend Locator, according to our needs: 

class Xpath(
    path: String,
    // poczekaj aż będzie widoczny
    accessRule: (WebElement) -> Boolean = { it.isDisplayed }
) : Locator(By::xpath, path, accessRule)

class Id(
    id: String,
    //zwracaj zawsze
    accessRule: (WebElement) -> Boolean = { true }
) : Locator(By::id, id, accessRule)

And using them as in the example above. 

Integration with Java 

As I’ve already mentioned, we can use Kotlin code in a Java class. However, the Java language doesn’t have the function of expressing ‘by’ delegators. An idea for integration is defining web elements in the .kt class and expanding the .java class with it: 

class MyScreenPageObject extends MyScreenKotlinPageObject {
(…)
    public void clickLoginButton() {
        getLoginButton().click();
    }
}

Getters for the fields defined by delegators were automatically generated for operating in Java, but all logic for accessing and evaluating elements remained intact. 

ikona zarowki 1 - Using Kotlin language functionalities to enrich the UI tests written in Java


Bear in mind that in this case the access modifier in the .kt class may not be set to private.

ikona zarowki 1 - Using Kotlin language functionalities to enrich the UI tests written in Java


All Kotlin classes (except for the abstract ones) are ‘final’ by default, meaning that one cannot inherit from them. In this case, it is required to mark the .kt class with the word open (the inverse of the meaning of ‘final’). 

For comparison, this is the same code – Vanilla Java + Selenium (simplified): 

class MyScreen {
(…)
    public void clickLoginButton() {
        WebElement usernameField = getWait().until(ExpectedConditions.visibilityOfElementLocated(By.id("username")));
        WebElement passwordField = getWait().until(ExpectedConditions.visibilityOfElementLocated(By.id("password")));
        getWait().until(driver -> !usernameField.getAttribute("value").isEmpty()
                &&
                !passwordField.getAttribute("value").isEmpty());
        getWait().until(ExpectedConditions.visibilityOfElementLocated(By.xpath(".//button[@type='submit']"))).click();
    }
}

Robot pattern

On the login page, enter your username.

On the login page, enter your password.

On the login page, click the Login button.

When performing steps within one web app page, we know the context. Referring to the object each time to call a method can be onerous, especially when there are a dozen steps. 

loginPage.fillCredentialsForUser("blogersii001);
loginPage.pressLoginButton();
loginPage.verifyUserIsSuccessfullyLoggedIn();

An idea for improvement idea is to use high-order functions again and create a pleasant mechanism for creating scopes where the context is already known. 

@Test
fun `user can access website and log in`() {
    mainPage {
        verifyHeaderTitleIsEqualTo("<bloger_sii/>")
        clickOnSignInButton()
    }
 
    loginPage {
        fillCredentialsForUser("blogersii001")
        pressLoginButton()
        verifyUserIsSuccessfullyLoggedIn()
    }
}
ikona zarowki 1 - Using Kotlin language functionalities to enrich the UI tests written in Java


Functions in Kotlin can be named by standard camelCase or by any other string of characters marked between the ‘ ’ (back quote) characters. 

What it looks like from the inside 

Above I showed that the test stages are divided into scopes, called ‘robots.’ Each robot has a reference to the PageObject object for which a set of functions is executed. The only available functions in each robot are those that are defined in the PageObject class that the robot was created for.  

The secret of this approach is a ‘robot’ function that takes the methods of one particular page (defined in PageObject) and executes them in the order they are given: 

fun loginPage(func: LoginPage.() -> Unit) {
    LoginPage().func()
}

The argument of the loginPage function is a function that exists in the LoginPage class and returns a Unit value (equivalent to void in Java). The loginPage function creates a new object of the LoginPage class and applies all the methods given as arguments to it. 

Similarly, a robot for the MainPage class will be created from the example above.  

ikona zarowki 1 - Using Kotlin language functionalities to enrich the UI tests written in Java


Robots can be accessed globally. Define them in a separate file (e.g., robots.kt) for project cleanliness. 

Integration with Java 

Java doesn’t support the robot mechanism as high-order functions. So what we can do in this situation is to create robot-functions for the already existing PageObject classes written in Java. 

public class AddBlogPage {
(...)

    public void addBlogTitle(String blogTitle) {
        (...)
    }

    public void submitBlogArticle() {
        (...)
    }
}

We create a robot-function for the Java class: 

fun addBlogPage(func: AddBlogPage.() -> Unit) {
    AddBlogPage().func()
}

And use it in tests written in Kotlin: 

@Test
fun `Integrate Java class in Kotlin robot test`() {
    addBlogPage {
        addBlogTitle("Blogersii - Kotlin")
        submitBlogArticle()
    }
}

One may ask: 

Ok, but why to do that when we know ‘Builder design pattern’ and ‘method chaining’ that are available in regular Java?

Creating a robot-like structure is possible in Java, but may cause problems. 

To chain methods, they must return a reference to the object from which they are called. Each method we want to chain must have the returned type changed from void to its own class. In the case of Kotlin, code refactoring is not required. The improvement is added without changes to the body of the PageObject classes. 

An example of chaining methods – Vanilla Java: 

public class AddBlogPage {
	(...)

    public AddBlogPage addBlogTitle(String blogTitle) {
        (...)
        return this;
    }

    public AddBlogPage submitBlogArticle() {
        (...)
        return this;
    }
}

Usage in tests:

@Test
void javaTest(){
	new AddBlogPage()
	.addBlogTitle("Blogersii - Kotlin")
	.submitBlogArticle();
}

The second approach, less known and applied to achieve a similar result in Java is initializing objects using double brace initialization. In this case, we don’t have to change the return types in the AddBlogPage class, since the reference to the class inside the braces in executed through the ‘this’ value. 

Usage in tests:  

@Test
void javaTest(){
    new AddBlogPage(){{
        addBlogTitle("Blogersii - Kotlin");
        submitBlogArticle();
    }};
}

This approach carries risks. This way of initialization generates an additional anonymous class inside each time, which extends after the PageObject class. Although using this type of initialization in trace amounts will not hinder the work of the Garbage Collector, initializing objects repeatedly in this way may lead to memory leaks. 

Summary 

Kotlin is still developing. Presenting all the fun facts and improvements it offers is surely far beyond the content of one article. 

I hope this article will strengthen the readers’ interest in Kotlin, and the proposed solutions (perhaps after creative modifications) will be useful in your projects. 

The article is based on the UI-testing framework I am developing as my hobby.  

Source code and examples are located in the public repository

Also, check play.kotlinlang.org to try out Kotlin online without prior installation. 

5/5 ( votes: 5)
Rating:
5/5 ( votes: 5)
Author
Avatar
Tomasz Siemieniuk

Test and analysis engineer at Sii since 2020. Additionally, he develops in Java, Kotlin and Selenium technologies. In his spare time he practices physical and mental sports.

Leave a comment

Your email address will not be published. Required fields are marked *

You might also like

More articles

Don't miss out

Subscribe to our blog and receive information about the latest posts.

Get an offer

If you have any questions or would like to learn more about our offer, feel free to contact us.

Send your request Send your request

Natalia Competency Center Director

Get an offer

Join Sii

Find the job that's right for you. Check out open positions and apply.

Apply Apply

Paweł Process Owner

Join Sii

SUBMIT

Ta treść jest dostępna tylko w jednej wersji językowej.
Nastąpi przekierowanie do strony głównej.

Czy chcesz opuścić tę stronę?