Page Object Model for Performance Testing With Gatling
Gatling is a powerful open-source tool for performance/load testing; we take you through the POM design model to create and test a Gradle repository.
Join the DZone community and get the full member experience.
Join For FreePerformance Testing
It is one of the Non-Function testing techniques that test if and how SUT [System/Software Under Test] will perform under a load in terms of responsiveness, stability, scalability, reliability, and resource usage.
Many factors, like network, code quality, traffic spike, etc., could potentially hamper a system's performance. Perf testing helps to uncover these bottlenecks.
UI and API Performance Testing
There are 2 ways to load test the application. They are UI and API load tests.
UI load testing involves testing everything visible to users or the client-side. They evaluate the translation of data from the API to the UI and ensure that everything is happening quickly and accurately. These tests provide the best insight into the actual loading times experienced by users.
In API load tests, as the name suggests, we put the load on the application by hitting APIs directly and then checking how APIs are responding under various load conditions.
Gatling: Performance Testing Tool
There are many performance testing tools available. I have used Gatling for this article.
It's is a quite famous and powerful open-source tool for performance/load testing and provides integration with CICD tools like Jenkins.
It offers great reporting and an easy to use recorder and script generator.
It provides default integration with build tools like Maven and Gradle.
Page Object Model for Performance Testing
We have used the POM [Page Object Model] with UI automation tools; however, the same pattern we can very well use in Gatling.
Consider an example of https://katalon-demo-cura.herokuapp.com/. Here we have a journey, i.e.,
- Landing on the home page,
- Logging in,
- Booking an appointment,
- Navigating to the History page to confirm if the appointment has been successfully made.
- Logging out.
In the above example, I have shown just 1 journey, which traverses through multiple pages. On every page, there was a call to some API. We might need to use the same API differently to perform various other journeys, like with a different set of request parameters.
If we are using these same APIs in different journeys, then it leads to a lot of duplicate code.
An easy but effective way is the usage of POM in the above use case.
With POM, we will be extracting out the APIs page vise, and then these APIs can be called in various journeys or tests.
In this article, I have used IntelliJ as an editor and Gradle as a build tool.
Documentation for Gatling's Gradle plugin can be found here: https://gatling.io/docs/current/extensions/gradle_plugin/
Let's start with writing the code.
Step 1: Download Gatling: https://gatling.io/open-source/
Step 2: Set up the Scala plugin in IntelliJ: http://allaboutscala.com/tutorials/chapter-1-getting-familiar-intellij-ide/scala-environment-setup-install-scala-plugin-intellij/
Step 3: Create a new Gradle repository in IntelliJ as follows:
Input GroupId and ArtifactId as shown below and then Finish:
NOTE: We need to have Gradle already setup in our machines to get this set-up ready smoothly. I have used sdkman to get Gradle 6.3 on my machine.
Step 4: After the successful creation of the Gradle repository, open the build.gradle file and insert the below code in it:
xxxxxxxxxx
plugins {
// The following line allows to load io.gatling.gradle plugin and directly apply it
id 'io.gatling.gradle' version '3.4.2'
}
gatling {
// WARNING: options below only work when logback config file isn't provided
logLevel = 'WARN' // logback root level
logHttp = 'NONE' // set to 'ALL' for all HTTP traffic in TRACE, 'FAILURES' for failed HTTP traffic in DEBUG
}
Step 5: From the terminal window, run Gradle clean build to download all dependencies.
It will take some time to download the dependencies. After a successful Gradle build, our repository will look something like this:
With Gatling's Gradle plugin, we get the folder structure displayed above, with some sample files available.
- All the request payloads, CSV files for feeders, etc., can be kept inside the resources folder.
- All test simulations, any helpers, and Constant data files can be kept inside the scala folder.
Step 6: Let's start creating page object classes.
- Create 3 packages inside the Scala directory as
com.journeys
com.pageObjects
com.helper
- Inside
com.helper
package create a scala class asConstants.scala
wherein we will keep all our common URLs, username and common password, and common header information:
x
package com.helper
object ConstantsData{
val BASE_URL = "https://katalon-demo-cura.herokuapp.com/"
val ACCEPT_HEADER = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
val ACCEPT_ENCODING_HEADER = "gzip, deflate, br"
val ACCEPT_LANGUAGE_HEADER = "en-GB,en-US;q=0.9,en;q=0.8"
val USER_AGENT_HEADER = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/87.0.4280.67 Safari/537.36"
object URLS {
val PROFILE = s"${BASE_URL}profile.php"
val LOGIN = s"${BASE_URL}authenticate.php"
val LOGOUT = s"${BASE_URL}authenticate.php?logout"
val BOOK_APPOINTMENT = s"${BASE_URL}appointment.php"
val HISTORY = s"${BASE_URL}history.php"
}
object SECRETS {
val USER_NAME = "John Doe"
val PASSWORD = "XXXXXXXXXXX"
}
}
Code Explanation
BASE_URL
is the main URL of our application.- In the
URLS
object, we have kept all the API ends points which start fromBASE_URL
. - In
SECRETS
I am keeping usernames and passwords so that they need to be in code. As an enhancement, we can take these secrets from a vault. - Our application under test has many headers. These various headers are stored in these variables.
- Create 5 .scala files in
com.pageObjects
as:BasePage
for all the common code like header information.LandingPage.scala
AuthenticatePage.scala
AppointmentPage.scala
HistoryPage.scala
BasePage.scala
for common header information.
LandingPage
x
package com.pageObjects
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import com.helper.ConstantsData._
object LandingPage {
val getlandingPage = http("Get Landing Page")
.get(BASE_URL)
.check(status.is(200))
}
Code Explanation
- In LandingPage, we are landing onto the application's main page using BASE_URL and asserting that API should give a status code as 200.
AuthenticatePage
x
package com.pageObjects
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import com.helper.ConstantsData._
object AuthenticatePage {
val userName = SECRETS.USER_NAME
val password = SECRETS.PASSWORD
val getProfile = http("Get Profile")
.get(URLS.PROFILE)
.check(status.is(200))
val login = http("Do Login")
.post(URLS.LOGIN)
.formParamSeq(
Seq(
("username", userName),
("password", password)
))
.check(status.is(200))
val logout = http("Do Logout")
.get(URLS.LOGOUT)
.check(status.is(200))
}
Code Explanation
- We have created 3 methods here in AuthenticatePage: to get the User's profile, to Login, and to Logout.
- Since login call is a post-call that accepts form parameters in its request payload, I have used Gatling's method
formParamSeq()
. username
andpassword
we are importing fromConstantsData
where the secrets are.- In all these 3 methods, respective URLs also we are getting from ConstantsData.
AppointmentPage
x
package com.pageObjects
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import com.helper.ConstantsData._
object AppointmentPage {
val bookAppointment = http("Book an appointment")
.post(URLS.BOOK_APPOINTMENT)
.formParamSeq(
Seq(
("facility", "Tokyo+CURA+Healthcare+Center"),
("hospital_readmission", "Yes"),
("programs", "Medicaid"),
("visit_date", "21%2F01%2F2021"),
("comment", "My+new+appointment")
))
.check(status.is(200))
}
Code Explanation
- We have created 1 method here in AppointmentPage: to book an appointment.
- Since this call again is a post-call that accepts form parameters in its request payload, I have used Gatling's method
formParamSeq()
. - In
Seq()
we need to pass the fields and their values.
HistoryPage
xxxxxxxxxx
package com.pageObjects
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import com.helper.ConstantsData._
object history {
val checkHistory = http("Check History")
.get(URLS.HISTORY)
}
BasePage
xxxxxxxxxx
package com.pageObjects
import com.helper.ConstantsData._
import io.gatling.core.Predef._
import io.gatling.http.Predef._
object basePage {
val httpProtocol = http
.baseUrl(BASE_URL)
.warmUp(BASE_URL)
.acceptHeader(ACCEPT_HEADER)
.acceptEncodingHeader(ACCEPT_ENCODING_HEADER)
.acceptLanguageHeader(ACCEPT_LANGUAGE_HEADER)
.userAgentHeader(USER_AGENT_HEADER)
}
Code Explanation
- We have declared a variable as
httpProtocol
here which declares thebaseUrl
,warmUp
url and all the headers. - Under different header methods, we are passing respective headers, which we are importing again from ConstantsData.
Step 7: Let's write a journey test using these pages inside com.journeys
.
- Every test in the Gatling world is known as a Simulation, and it has to extend a Gatling's built-in class known as Simulation.
- Our test will look like this:
xxxxxxxxxx
package com.journeys
import io.gatling.core.Predef._
import scala.concurrent.duration._
import com.helper.ConstantsData._
import com.pageObjects._
class MakeAnAppointment extends Simulation {
object appointment {
val myAppointment =
exec(LandingPage.getlandingPage)
.exec(AuthenticatePage.getProfile)
.exec(AuthenticatePage.login)
.exec(AppointmentPage.bookAppointment)
.exec(HistoryPage.checkHistory)
.exec(AuthenticatePage.logout)
}
val bookAnAppointment = scenario("Test appointment booking")
.exec(appointment.myAppointment)
setUp(
bookAnAppointment.inject(
rampUsers(50) during (50 seconds)
)
)
.assertions(
global.responseTime.max.lte(2000),
forAll.failedRequests.count.lt(1)
)
.protocols(basePage.httpProtocol)
}
Code Explanation
- There are 3 parts in the test
- object
appointment
which contains API calling in a sequence. - a variable as
bookAnAppointment
which executes a scenario. This scenario name can be any user-relevant value as it gets displayed in the report. - a
setUp
that does user injection followed by assertions.
- object
- I have kept the number of users as 50 with a ramp-up time of 50 seconds, i.e., in every 1 second, Gatling will create 1 virtual user.
- Depending upon whether AUT is an Open Or Closed Model, we can consider the load injection behavior. Here since this application is an Open model, I have taken this load injection method:
rampUsers(10) during (5 seconds)
Step 8: Running the tests.
- We need to specify the path of the test from the 'simulations' folder.
x
./gradlew gatlingRun-com.journeys.MakeAnAppointment
Step 9: Checking Reports.
- After successful execution, we can check out the reports inside the build/reports/Gatling folder by checking out the index.html file.
- index.html file looks something like this:
In this report, we can check various parameters like:
- The total number of users for every API. Every OK in Gatling is a success, and every KO means there is some failure.
- How much time the APIs took to respond.
- The number of requests per second.
- The number of responses per second.
I hope all of you readers find this article useful!
Coming next: Post for running Gatling performance tests in Jenkins and Dockerised setup.
Opinions expressed by DZone contributors are their own.
Comments