Test Automation in Kotlin with Selenium, TestNG and Page Object Model
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.
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.
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:
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.
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:
During the test run:
Execution in the browser:
At the end of the test that was executed we have in the IDE the 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.