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:
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)
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
}
}
The By lazy delegator means lazy evaluation. The element field will not be calculated until it is first used.
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.
Bear in mind that in this case the access modifier in the .kt class may not be set to private.
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()
}
}
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.
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.
Leave a comment