Getting Started
Write your first test with locator delegates
In this tutorial, we’ll cover the basics of Kolibrium and explore several project configurations to help you get started quickly.
We'll be writing tests for the login functionality on the Sauce Labs demo e-commerce website: https://www.saucedemo.com.
All the examples can be found in the kolibrium-demo project.
1. Add the Selenium module to your project
- Gradle
- Maven
To get started, add the following dependency and configure JUnit in your Gradle project build file (build.gradle.kts):
dependencies {
implementation("dev.kolibrium:kolibrium-selenium:0.4.0")
// other dependencies
}
tasks.test {
useJUnitPlatform()
}
View the full file here
To get started, add the following dependency to the dependencies section:
<dependency>
<groupId>dev.kolibrium</groupId>
<artifactId>kolibrium-selenium</artifactId>
<version>0.4.0</version>
</dependency>
2. Use locator delegate functions from the Selenium module
Create a test class, such as a JUnit test class, and use the locator delegate functions from the selenium
module to locate elements:
@Test
fun loginTest() {
with(driver) {
val username by name("user-name")
val password by id("password")
val button by name("login-button")
username.sendKeys("standard_user")
password.sendKeys("secret_sauce")
button.click()
val shoppingCart by className("shopping_cart_link")
shoppingCart.isDisplayed shouldBe true
}
}
View the full file here
Note:
shouldBe
is an assertion function from kotest, but you may use any assertion library you prefer.
Kolibrium utilizes the Delegation pattern, natively supported in Kotlin, to locate elements lazily with locator strategies.
For instance, the following code locates the WebElement
with the "user-name"
attribute when it’s accessed (in this case, when the sendKeys
command is issued):
val username by name("user-name")
username.sendKeys("standard_user")
3. Implement Page Object models
To introduce a layer of abstraction, let's create a Page Object class for the login page:
class LoginPage(driver: WebDriver) {
private val username: WebElement by driver.name("user-name")
private val password: WebElement by driver.idOrName("password")
private val button: WebElement by driver.name("login-button")
fun login(username: String, password: String) {
this.username.sendKeys(username)
this.password.sendKeys(password)
button.click()
}
}
Similarly, for the inventory page:
class InventoryPage(driver: WebDriver) {
private val shoppingCart by driver.className("shopping_cart_link")
fun isShoppingCartDisplayed() = shoppingCart.isDisplayed
}
View the full files here
Now, let's utilize these Page Objects in our test:
@Test
fun loginTest() {
LoginPage(driver).login(
username = "standard_user",
password = "secret_sauce"
)
InventoryPage(driver).isShoppingCartDisplayed() shouldBe true
}
View the full file here
4. Use Context Receivers to inject the driver instance into the Page Objects
Context receivers offer a streamlined way to provide context (such as a WebDriver
instance) to functions without passing it explicitly as an argument. In this example, we'll use context receivers to simplify access to the WebDriver
instance in our Page Objects.
⚠️ Note: Context receivers, also known as context parameters, are still an experimental feature in Kotlin and are not enabled by default.
To enable them, add the following configuration to your build.gradle.kts
file:
tasks.withType<KotlinCompile> {
compilerOptions.freeCompilerArgs = listOf(
"-Xcontext-receivers",
)
}
View the full file here
After reloading the Gradle configuration, we can add context(WebDriver)
to our Page Objects and remove the constructor and driver instance when calling the delegate functions:
context(WebDriver)
class LoginPage {
private val username: WebElement by name("user-name")
private val password: WebElement by idOrName("password")
private val button: WebElement by name("login-button")
fun login(username: String, password: String) {
this.username.sendKeys(username)
this.password.sendKeys(password)
button.click()
}
}
context(WebDriver)
class InventoryPage {
private val shoppingCart by className("shopping_cart_link")
fun isShoppingCartDisplayed() = shoppingCart.isDisplayed
}
View the full files here
To make the test compile after introducing context receivers, we need to provide a driver context by using the with
scope function:
@Test
fun loginTest() {
with(driver) {
LoginPage().login(
username = "standard_user",
password = "secret_sauce"
)
InventoryPage().isShoppingCartDisplayed() shouldBe true
}
}
View the full file here
Generate repository classes for locators with Kolibrium code generation
In this section, we will begin using the ksp
module to generate part of our Page Objects.
1. Add KSP module to the build file
First, update your Gradle project build file by adding the following configuration:
plugins {
kotlin("jvm") version "2.0.21"
id("com.google.devtools.ksp") version "2.0.21-1.0.26"
}
dependencies {
implementation("dev.kolibrium:kolibrium-annotations:0.4.0")
implementation("dev.kolibrium:kolibrium-selenium:0.4.0")
ksp("dev.kolibrium:kolibrium-ksp:0.4.0")
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
// other dependencies
}
View the full file here
2. Store locators in a single file
Create a file named Locators.kt
and add the following enum classes to the dev.kolibrium.demo.ksp._01.locators
package:
package dev.kolibrium.demo.ksp._01.locators
@Locators
enum class LoginPageLocators {
@Id("user-name")
username,
password,
@Name("login-button")
loginButton
}
@Locators
enum class InventoryPageLocators {
@ClassName("shopping_cart_link")
shoppingCart
}
View the full file here
3. Generate files
Now, build the project and navigate to the build/generated/ksp/main/kotlin/dev/kolibrium/demo/ksp/_01/locators/generated/
directory. You will notice that two files have been created:
LoginPageLocators.kt
:
package dev.kolibrium.demo.ksp._01.locators.generated
context(WebDriver)
public class LoginPageLocators {
public val username: WebElement by id("user-name")
public val password: WebElement by idOrName("password")
public val loginButton: WebElement by name("login-button")
}
InventoryPageLocators
:
package dev.kolibrium.demo.ksp._01.locators.generated
context(WebDriver)
public class InventoryPageLocators {
public val shoppingCart: WebElement by className("shopping_cart_link")
}
4. Create Page Objects and use locators from the generated files
Now, let's create Page Object classes that incorporate a locators
property.
LoginPage.kt
:
context(WebDriver)
class LoginPage {
private val locators = LoginPageLocators()
fun login(username: String = "standard_user", password: String = "secret_sauce") = with(locators) {
this.username.sendKeys(username)
this.password.sendKeys(password)
loginButton.submit()
}
}
InventoryPage
:
context(WebDriver)
class InventoryPage {
private val locators = InventoryPageLocators()
fun isShoppingCartDisplayed() = locators.shoppingCart.isDisplayed
}
View the full files here
Next, update the test accordingly:
@Test
fun loginTest() {
with(driver) {
LoginPage().login()
InventoryPage().isShoppingCartDisplayed() shouldBe true
}
}
View the full file here
Use DSL functions for creating and configuring WebDriver
instances
In this section, we will leverage DSLs to create WebDriver instances in our tests.
1. Add the DSL module to the build file
As always, begin by updating your Gradle configuration:
dependencies {
implementation("dev.kolibrium:kolibrium-dsl:0.4.0")
// other dependencies
}
View the full file here
2. Create driver instance using the DSL module
Let's create a Chrome driver instance with the following configuration:
@BeforeEach
fun setUp() {
driver = chromeDriver {
driverService {
logFile = "chrome.log"
appendLog = true
readableTimestamp = true
}
options {
arguments {
+incognito
windowSize {
width = 1920
height = 1080
}
}
}
}.apply {
get("https://www.saucedemo.com")
}
}
View the full file here
While it's intuitive to understand what the chromeDriver
function call does, let's break it down further:
- It creates a
DriverService
(specifically, aChromeDriverService
) withappendLog
andreadableTimestamp
enabled. - It sets up an
Options
object (in this case, aChromeOptions
) withincognito
mode enabled and a window size of 1920 x 1080 pixels. - Finally, it utilizes both objects to instantiate the actual driver.
Inject driver instances into your tests with the JUnit module
It's time to unlock the full potential of Kolibrium by enabling it to inject drivers into your JUnit 5 tests.
1. Add the JUnit module to the build file
As you may expect, we begin with the Gradle configurations:
plugins {
kotlin("jvm") version "2.0.21"
id("com.google.devtools.ksp") version "2.0.21-1.0.26"
}
dependencies {
implementation("dev.kolibrium:kolibrium-junit:0.4.0")
implementation("dev.kolibrium:kolibrium-selenium:0.4.0")
testImplementation("io.kotest:kotest-assertions-core-jvm:5.9.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.3")
}
View the full file here
2. Annotate your test with @Kolibrium
Next, let's enhance the test class by adding context(WebDriver)
and the @Kolibrium
annotation at the top. This allows us to simplify the test code:
context(WebDriver)
@Kolibrium
class JUnitTest {
@BeforeEach
fun setUp() {
this@WebDriver["https://www.saucedemo.com"]
}
@Test
fun loginTest() {
LoginPage().login(
username = "standard_user",
password = "secret_sauce"
)
InventoryPage().isShoppingCartDisplayed() shouldBe true
}
}
View the full file here
3. Customize the injected driver
You can tailor the injected driver by creating a custom Kolibrium configuration.
First, add the AutoService dependency:
dependencies {
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
// other dependencies
}
View the full file here
Next, create a project level configuration by extending AbstractJUnitProjectConfiguration
and overriding the baseUrl
and chromeDriver
properties:
@AutoService(AbstractJUnitProjectConfiguration::class)
object JUnitConfiguration : AbstractJUnitProjectConfiguration() {
override val baseUrl = "https://www.saucedemo.com"
override val chromeDriver = {
chromeDriver {
options {
arguments {
+incognito
+start_maximized
}
experimentalOptions {
excludeSwitches {
+enable_automation
}
localState {
browserEnabledLabsExperiments {
+same_site_by_default_cookies
+cookies_without_same_site_must_be_secure
}
}
}
}
}
}
}
View the full file here
After that, remove the @BeforeEach
block from the test:
context(WebDriver)
@Kolibrium
class JUnitTest {
@Test
fun loginTest() {
LoginPage().login(
username = "standard_user",
password = "secret_sauce"
)
InventoryPage().isShoppingCartDisplayed() shouldBe true
}
}
View the full file here
With this configuration, the @Kolibrium
annotation will instruct JUnit to inject a ChromeDriver
with the specified experimental options into the tests. It will also navigate to https://www.saucedemo.com before executing the tests.