Test Automation in Kotlin with Selenium, TestNG and Page Object Model

Carmelo Buelvas Comas
8 min readJul 13, 2017

Nowadays, automation testing has proved to be an important component within the cycle of software development and even more for the agile development of software. Being an automated process makes easier to perform repetitive tests, end to end business flows, covering many scenarios, which would be very difficult and costly to perform manually. As a tool it is of great help, since it gives a greater vision and allows to detect errors in early phases of the development, so much, that in cycles of continuous integration, with the use of this tool it is possible to detect errors before the changes are released in QA environments, this is favorable because in QA it would generate less reprocessing 🚀💣.

The considerable reduction of errors in QA and production environments is significant and therefore the cost of maintainability is much lower. Automation testing makes easier to perform regressions and integration tests very simply and repetitively. To go to the point, the following project is a basic and simple introduction to automation testing using Kotlin, TestNG, Selenium and Page Object Model; For this particular case we will use a small source — YouTube. We will make a search of the video of a song that I like a lot, after this, we will reproduce the video.

Kotlin as Programming Language

Our environment is diversified more and more everyday. Lately there are so many programming languages that allow you to do much more with less. Kotlin as language is interoperable, concise and secure. Being a programming language that runs on the virtual machine of Java (JVM); allows an open interaction with the Java code and therefore with the frameworks and libraries available for Java in an almost transparent way.

Kotlin takes the best of Java and Scala, the response times are similar as working with Java natively, which is a considerable advantage over Scala. Another favorable point is that Google adopted it as the official programming language for Android, this denotes the stability of the language.

Now let’s get to work…! 👐

Project requirements

  • Java JDK 1.8
  • IDE: IntelliJ IDEA or Eclipse
  • Maven
  • Kotlin Plugin: IntelliJ IDEA or Eclipse

Project Creation

We will start with the creation of our project, for this we have two ways, choose the one that you like:

Creation by Console

The most common and I mean the old school type, is by console, the following script will facilitate the way you do it:

$ mvn archetype:generate -DgroupId=com.cafekotlin -DartifactId=TAEYouTube -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Creation by IDE

In my case I chose IntelliJ IDEA because of the ease of use and the powerful features it has. Let’s start:

When opening IntelliJ IDEA Select> Create new Project.

Figure 1. New Project Creation

Now the following screen will be presented, at this point we choose the Maven project type and select the creation from an archetype> Create from archetype. The archetype we will select is> org.jetbrains.kotlin:kotlin-archetype-jvm and we continue to press the Next button.

Figure 2. Selection of Maven Archetype

In order not to make this article longer, we will move faster. The options that will be presented will be: Define group and archetype for the project, in this case it’s > GroupId: com.cafekotlin and ArtifactId: TAEYouTube.

Project Structure

Once our project has been created, we add the following content in the pom.xml file:

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.1.2-4</kotlin.version>
<testng.version>6.8.8</testng.version>
<selenium.version>3.1.0</selenium.version>
</properties>

<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>

<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
</dependency>

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-support</artifactId>
<version>${selenium.version}</version>
</dependency>
</dependencies>

<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>

<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Following this step we can start working with Kotlin, Selenium and TestNG. Now the next step will be to create directories for Kotlin, it should look something like the following image:

Figure 3. Project Structure

Finding Locators

On the main YouTube page we will start the search of the video, the header has a text box and the search button. By modeling Web pages as objects, it is necessary to find and identify the elements we will require to automate our test cases. Firefox has FireBath and FirePath extensions available for this purpose and which must be installed to be able to use them, they facilitate to locate all the web elements needed for automation.

When you do right-click on any item on a page, you can activate the “Inspect in FirePath” option in the context menu to get detailed information about the item.

From the FirePath tab of Firefox, we have the option to get our locators in different ways, which are: XPath, CSS, Sizzle and the component ID.

Figure 4. FirePath in Firefox

Working with Page Object Model

Now, it is necessary to represent each page that we require of the application in an object, for this it is necessary to create one class per page in order to obtain instances of classes and to be able to work with object-oriented programming.

Here are our Pages Objects:

HomePage

package com.cafekotlin.webpages

import org.openqa.selenium.WebDriver
import org.openqa.selenium.WebElement
import org.openqa.selenium.support.FindBy
import org.openqa.selenium.support.PageFactory

class HomePage(private val driver: WebDriver) {

@FindBy(id = "masthead-search-term")
private val searchBox: WebElement? = null

@FindBy(id = "search-btn")
private val searchButton: WebElement? = null

@FindBy(xpath = ".//*[@id='section-list-201801']/li[1]/div/div[1]/div/p")
private val numResult: WebElement? = null

init
{
PageFactory.initElements(driver, this)
}

fun searchVideo(video: String) {
searchBox?.sendKeys(video)
searchButton?.click()
}
}

ResultPage

package com.cafekotlin.webpages

import org.openqa.selenium.WebDriver
import org.openqa.selenium.WebElement
import org.openqa.selenium.support.FindBy
import org.openqa.selenium.support.PageFactory


class ResultPage(private val driver: WebDriver) {

@FindBy(xpath = ".//*/li[1]/div/div[1]/div/p")
private val numResult: WebElement? = null

@FindBy(xpath = ".//*[@id='results']/ol/li/ol/li/div/div/div[2]/h3/a")
private val videos: List<WebElement>? = null

@FindBy(xpath = ".//*[@id='eow-title']")
private val titleVideo: WebElement? = null

@FindBy(xpath = ".//*[@id='watch7-user-header']/div/a")
private val channel: WebElement? = null

init
{
PageFactory.initElements(driver, this)
}

fun isPageOpened(): Boolean{
return numResult?.text.toString().contains("About")
}

fun selectVideo(selectVideo: String){
for (webElement in videos!!) {
if (webElement.getText().contains(selectVideo)) {
webElement.click()
break
}
}
}

fun playingVideo(titleVideo: String, channel: String): Boolean{
return titleVideo?.equals(titleVideo) && channel.equals(channel)
}
}

Developing a Simple Test

Once we have finished with our Page Objects we will proceed with the development of the tests. For this I have included an additional change, I’ve used the design pattern called Abstract Method, this will facilitate the reuse of code and each test class will extend from an abstract parent class called TestBase. This further enhances the readability of code and the centralization of processes, where each child class has the sole responsibility of carrying out the concrete implementation of each test.

TestBase -> Abstract Class

package com.cafekotlin.test


import com.cafekotlin.UtilResources
import org.openqa.selenium.WebDriver
import org.openqa.selenium.chrome.ChromeDriver
import org.testng.annotations.AfterTest
import org.testng.annotations.BeforeTest
import java.net.URI
import java.util.concurrent.TimeUnit

abstract class TestBase {

lateinit var driver: WebDriver
private set

@BeforeTest
fun setup() {
System.setProperty(UtilResources.getProperties("nameDriver"),
UtilResources.getProperties("pathDriver") + UtilResources.getProperties("exeDriver"))
driver = ChromeDriver()
driver?.manage()?.timeouts()?.implicitlyWait(10, TimeUnit.SECONDS)
driver?.manage()?.window()?.maximize()
driver?.get(URI(UtilResources.getProperties("pageURL")).toString())
}

@AfterTest
fun driverClose() {
driver?.close();
}
}

SearchVideoTest -> Child Class

package com.cafekotlin.test

import com.cafekotlin.UtilResources
import com.cafekotlin.webpages.HomePage
import com.cafekotlin.webpages.ResultPage
import org.testng.Assert
import org.testng.annotations.Test

class SearchVideoTest() : TestBase() {

@Test
fun searchVideo() {
val homePage = HomePage(driver!!)
homePage.searchVideo(UtilResources.getProperties("nameVideo"))

val resultPage = ResultPage(driver!!)
Assert.assertTrue(resultPage.isPageOpened())

resultPage.selectVideo(UtilResources.getProperties("selectVideo"))

Assert.assertTrue(resultPage.playingVideo(UtilResources.getProperties("selectVideo"),
UtilResources.getProperties("channel")))
}

}

Utility

From my personal appreciation, I consider that the organization and responsibility of each component within the software is determinant for the maintenance of the same. I added an improvement to the code and not have hard-code 🔥, so it will be easier to change the parameters of the search and not have to go directly modify the source code.

If you have detailed the classes prepared for the tests, you will be curious about the utility that appears in some lines. The implementation of UtilResources.getProperties () will allow you to obtain values from a properties file:

package com.cafekotlin

import java.io.FileInputStream
import java.io.IOException
import java.util.*

object UtilResources {

private var properties: Properties? = null

fun
loadProperties(): Unit {
try {
properties = Properties()
properties?.load(FileInputStream("config.properties"))
} catch (e: IOException) {
e.printStackTrace()
}
}

fun getProperties(properties: String): String {
loadProperties()
return UtilResources.properties?.getProperty(properties).toString()
}
}

File config.properties:

pathDriver = C:/Driver/
nameDriver
= webdriver.chrome.driver
exeDriver
= chromedriver.exe

pageURL
= https://www.youtube.com/

nameVideo
= one metallica
selectVideo
= Metallica - One (Video)
channel
= Warner Bros. Records

Running the Test

It is the time to observe if everything we have done until now is successful or not.

Final project structure and test execution:

Figure 5. Final project structure and test execution.

During the test run:

Figure 6. Execution of the test.

Execution in the browser:

Figure 7. Execution of the test in the browser.

At the end of the test that was executed we have in the IDE the result:

Figure 8. Test result.

CONCLUSIONS

The implementation of Selenium, the addition of Page Object Model in combination with Page Factory, is a great way to model our automated tests and includes a very good practice for automation testing. Within the projects and their teams, it facilitates the life of both developers and quality analysts -> QAs, in the same way it helps agile development of software to a great extent.

A correct implementation of a framework for automation testing is key and can give coverage to all the suite of tests of the project that we want. A clear abstraction of the business model and well-presented test cases can allow the maintainability of the framework and can bring on the adaptability of this to the changing requirements at a lower cost.

I hope this little introduction to automation testing will be useful in your day-to-day work and will be easily understood. I used Kotlin for the versatility of the language, apart from the similarity with Scala and the fact that I can work with the paradigm of Object Oriented Programming seems great.

--

--