Gatling Login scenario with CSV feeder - scala

I have to write some tests with Gatling / Scala. In my concrete case I have to login to a website with username and password (additionally there is also keycloak security). There is a CSV file with a lot of user/password lines and my goal is to login with every single user/password out of this CSV file.
The problem is I do not know how to do that. I am able to login with username/password and the security token of the keycloak with just one user. That's fine so far, but not enough. Here is what I have done so far.
The first class:
class LasttestBestand extends Simulation {
val context = Context.ladeContext("de", "integ")
val userCredentials = TestdatenImport.ladeTestDaten("de")
val httpProtocol = http
.baseUrl(s"${context.protocol}://${context.host}")
.inferHtmlResources(
BlackList(""".*\.css""", """.*\.js""", """.*\.ico""", """.*\.woff"""),
WhiteList()
)
.acceptHeader("application/json, text/plain, */*")
.acceptEncodingHeader("gzip, deflate")
.acceptLanguageHeader("de,en-US;q=0.7,en;q=0.3")
.disableFollowRedirect
.userAgentHeader(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:63.0) Gecko/20100101 Firefox/63.0"
)
val scn = scenario("Lasttest Bestand")
.feed(userCredentials.csvFeeder)
.exec(Login.holeAccessToken("${Benutzername}", "${Passwort}", context))
.exec(Suche.ladeKundensucheResourcen(context))
setUp(
scn.inject(
atOnceUsers(1)
)
).protocols(httpProtocol)
}
The feeder class:
class TestdatenImport(val csvFeeder: BatchableFeederBuilder[String]) {}
object TestdatenImport {
def ladeTestDaten(land: String) = {
val csvFeeder = csv(s"data/eca-bb3-${land}-testdaten.csv").circular
new TestdatenImport(
csvFeeder
)
}
}
The Login:
object Login {
def holeAccessToken(
benutzer: String,
passwort: String,
context: Context
): ChainBuilder = {
val keycloakUri = s"${context.protocol}://${context.keycloakHost}"
val redirectUri =
s"${context.protocol}%3A%2F%2F${context.host}%2Fapp%2F%3Fredirect_fragment%3D%252Fsuche"
exec(
http("Login Page")
.get(
s"$keycloakUri/auth/realms/${context.realm}/protocol/openid-connect/auth"
)
.queryParam("client_id", "bestand-js")
.queryParam("redirect_uri", redirectUri)
.queryParam("state", UUID.randomUUID().toString())
.queryParam("nonce", UUID.randomUUID().toString())
.queryParam("response_mode", "fragment")
.queryParam("response_type", "code")
.queryParam("scope", "openid")
.header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
)
.header("Upgrade-Insecure-Requests", "1")
.check(status.is(200))
.check(
css("#kc-form-login")
.ofType[Node]
.transform(variable => {
variable.getAttribute("action")
})
.saveAs("loginUrl")
)
).exec(
http("Login")
.post("${loginUrl}")
.formParam("username", benutzer)
.formParam("password", passwort)
.header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
)
.header("Upgrade-Insecure-Requests", "1")
.check(status.is(302))
.check(
header("Location")
.transform(url => {
url.substring(url.indexOf("code=") + 5, url.length())
})
.saveAs("code")
)
.check(header("Location").saveAs("nextPage"))
)
.exec(
http("Fetch Token")
.post(
s"$keycloakUri/auth/realms/${context.realm}/protocol/openid-connect/token"
)
.header("Accept", "*/*")
.header("Origin", s"${context.protocol}://${context.host}")
.formParam("code", "${code}")
.formParam("grant_type", "authorization_code")
.formParam("client_id", "bestand-js")
.formParam("redirect_uri", redirectUri)
.check(status.is(200))
.check(
jsonPath("$..access_token")
.saveAs("accessToken")
)
)
}
}
As you can see I added a feeder to the scenario, but I do not know how to "repeat" the login the number of times there are user/password lines in the CSV file. What do I do wrong?

Inject as many users as you have entries in your CSV file, eg:
scn.inject(
rampUsers(numberOfEntries) during(10 minutes)
)

Related

Auth token handling for Gatling

Doing performance testing of API using Gatling.
Scenario:
Login (authToken will be generated in header)
For GET, POST, PUT request, need to pass that generated authToken in header
Here's my code snippet:
package apitest
import scala.concurrent.duration.*
import io.gatling.core.Predef.*
import io.gatling.http.Predef.*
import io.gatling.jdbc.Predef.*
import scala.language.postfixOps
class TestEnv4trial extends Simulation {
var e1: String = "https://testenv1-dev.net"
var e2: String = "https://testenv2-dev.net"
var BaseUrl: String = e1
var pwd: String = "pass123"
// Users
var admin: String = "admin123"
val httpProtocol = http
.baseUrl(BaseUrl)
.inferHtmlResources()
val login_headers = Map(
"Accept" -> """*/*""",
"Connection" -> "keep-alive",
"Content-Type" -> "application/x-www-form-urlencoded;charset=UTF-8"
)
val scn1 = scenario("Admin Login")
.exec(http("Login Admin")
.post({BaseUrl} + "/api/user/login")
.formParam("username", admin123)
.formParam("password", pass123)
.check(jsonPath("$.authToken").saveAs("tokenId")))
.exec { session => println(session("tokenId").as[String]); session } //authToken getting printed
val common_headers = Map(
"Accept" -> """*/*""",
"Accept-Encoding" -> "gzip, deflate, br",
"Accept-Language" -> "en-GB,en;q=0.9,en-US;q=0.8",
"Authorization" -> "Bearer " + $tokenId, //With hardcoded authToken works. Need to pass generated authToken in prev scenario here.
"Connection" -> "keep-alive",
)
val scn2 = scenario("All Employees")
.exec(http("All Employees")
.post("/api/employee/lists/")
.headers(common_headers)
.body(RawFileBody("test/TestEnv4trial/employees_request.json")).asJson)
setUp(
scn1.inject(atOnceUsers(1)).protocols(httpProtocol),
scn2.inject(atOnceUsers(1)).protocols(httpProtocol))
}
When I hardcode authToken generated in scn1 in common_headers, scn2 works.
But when I use tokenId, its not able to identify tokenId.
How do I pass saved key tokenId in common_headers?
Thanks.
"Authorization" -> "Bearer " + $tokenId
This doesn't compile.
Currently, you're using Session attributes, meaning tokenId is scoped for the single user executing scn1.
There's no way for a user executing scn2 to be able to reach it as is.

How can I parameterise information in Gatling scenarios

I need to send specific parameters to a scenario that is being reused multiple times with different payloads depending on the workflows. The following is the code that is to be reused:
var reqName = ""
var payloadName = ""
lazy val sendInfo: ScenarioBuilder = scenario("Send info")
.exec(session => {
reqName = session("localReqName").as[String]
payloadName = session("localPayloadName").as[String]
session}
)
.exec(jms(s"$reqName")
.send
.queue(simQueue)
.textMessage(ElFileBody(s"$payloadName.json"))
)
.exec(session => {
val filePath = s"$payloadName"
val body = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource(filePath).toURI)))
logger.info("timestamp value: " + session("timestamp").as[String])
logger.debug("Template body:\n " + body)
session
})
I know that you can chain scenarios in Scala/Gatling but how can I pass in information like reqName and payloadName down the chain, where reqName is a parameter to indicate the name of the request where the info is being sent and payloadName is the name of the actual JSON payload for the related request:
lazy val randomInfoSend: ScenarioBuilder = scenario("Send random info payloads")
.feed(csv("IDfile.csv").circular)
.randomSwitch(
(randomInfoType1prob) -> exec(
feed(timeFeeder)
.exec(session => {
payloadName = "Info1payload.json"
reqName ="Info1Send"
session.set("localReqName", "Info1Send")
session.set("localPayloadName", "Info1payload.json")
session
})
.exec(sendInfo)
),
(100.0 - randomInfoType1prob) -> exec(
feed(timeFeeder)
.exec(session => {
payloadName = "Info2Payload.json"
reqName ="Info2Send"
session.set("localReqName", "Info2Send")
session.set("localPayloadName", "Info2Payload.json")
session
})
.exec(sendInfo)
)
I attempted the above but the values of that 2 specific parameters were not passed through correctly. (The IDs and timestamps were fed through correctly though) Any suggestions?
Please properly read the Session API documentation. Session is immutable so Session#set returns a new instance.

Gatling Error when running multiple users - 'httpRequest-2' failed to execute: No attribute named 'access_token' is defined

im new to Gatling and have been trying to setup a test where my users login, get an access token, then perform some simple get requests using that token. Having 1-2 users works fine, however once i start ramping up the users i start getting spammed with this error:
[ERROR] i.g.h.a.HttpRequestAction - 'httpRequest-2' failed to execute: No attribute named 'access_token' is defined
Im thinking it could have something to do with the way I am saving and using the access token ?
class GatlingTest extends Simulation {
val httpProtocol = http
.baseUrl("https://myurl.com/api/v1")
.inferHtmlResources(BlackList(""".*\.js""", """.*\.css""", """.*\.gif""", """.*\.jpeg""", """.*\.jpg""", """.*\.ico""", """.*\.woff""", """.*\.woff2""", """.*\.(t|o)tf""", """.*\.png""", """.*detectportal\.firefox\.com.*"""), WhiteList())
.acceptLanguageHeader("en-GB,en;q=0.5")
.upgradeInsecureRequestsHeader("1")
object GetUserData {
val userData = exec(http("Get_User_Data")
.get("/user")
.header("Authorization", "Bearer ${access_token}"))
.pause(1)
}
object GetUserInfo {
val userInfo = exec(http("Get_User_Info")
.get("/userInfo")
.header("Authorization", "Bearer ${access_token}")
.header("Accept", "application/json"))
.pause(1)
}
object Login {
val sentHeaders = Map("api_key" -> "nnxzv336wt2374h6zw5x24qd", "Content-Type" -> "application/x-www-form-urlencoded", "Accept" -> "application/json")
val login = exec(http("Login_User")
.post("/login")
.basicAuth("username", "password")
.headers(sentHeaders)
.body(StringBody("grant_type=password&username=username#username.local&password=12345"))
.check(jsonPath("$.access_token").saveAs("access_token"))
)
}
val user = scenario("User").exec(Login.login).exec(GetUserData.userData, GetUserInfo.userInfo)
setUp(
user.inject(
rampUsers(5).during(2.seconds),
).protocols(httpProtocol)
)
}
I have added Authorization Bearer to the get requests, like i mentioned it does work, but as soon as 3+ users are involved i get the error.
It means the login request failed and hence, the user wasn't able to capture the access_token there.

Gatling Sequential scenarios

I'm trying to use gatling and I have a problem.
1- I have one scenario that exec POST request for getting a list of tokens and save all tokens in csv
2- I create another scenario that exec GET request but I need a token for auth each request
My problem is before executing my first scenario my file doesn't exist and I have this following error:
Could not locate feeder file: Resource user-files/resources/token.csv not found
My code :
Scenario 1 :
val auth_app = scenario("App authentication")
.exec(http("App Authentication")
.post("/token")
.header("Content-Type", "application/x-www-form-urlencoded")
.formParamSeq(Seq(("grant_type", "password"), ("client_id", clientID), ("client_secret", clientSecret)))
.check(jsonPath("$.token").saveAs("token")))
.exec(session => {
val token_data = new File(token_file_path)
if(token_data.exists()){
val writer = new PrintWriter(new FileOutputStream(new File(token_file_path), true))
writer.write(session("access_token").as[String].trim)
writer.write("\n")
writer.close()
}
else {
val writer = new PrintWriter(new FileOutputStream(new File(token_file_path), true))
writer.println("AccessToken")
writer.write(session("access_token").as[String].trim)
writer.write("\n")
writer.close()
}
session
})
Scenario 2 :
val load_catalog = scenario("Load catalog")
.exec(http("Load catalog")
.get("/list")
.headers(Map("Content-Type" -> "application/json", "Authorization Bearer" -> "${AccessToken}")))
.feed(csv(token_file_path).random)
My setup :
setUp(
auth_app.inject(atOnceUsers(10)).protocols(httpProtocol),
load_catalog.inject(nothingFor(120 seconds), atOnceUsers(10)).protocols(httpProtocol)
)
Is it possible to have a dynamic feeder with gatling ?

How to send access_token which is generated in saveAs("access_token") to be sent to ElFileBody of Gatlin Soap Request

I have this code for gatling where i wish to pass auth_token to soap request body.
Soap request body is written in a txt file and that txt file is called in ElFilebody
All i need is to send access_token which is generated in saveAs("access_token") to be sent to ElFileBody("Soap_request_CancelEvent.txt")
Content of Soap_request_CancelEvent.txt is below
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns="http://www.example.com/Service/2013-03">
<soapenv:Header/>
<soapenv:Body>
<ns:CancelEvent>
<!--Optional:-->
<ns:request>
<ns:AuthToken>${access_token}</ns:AuthToken>
<ns:SanctionNumber>?</ns:SanctionNumber>
</ns:request>
</ns:CancelEvent>
</soapenv:Body>
</soapenv:Envelope>
Error in Gatling console is
Session(Test Cancel Event Soap Request,23,Map(caee65d1-d380-48e9-8566-
9d8efb16b5fa -> 0, 6014555f-5487-44bc-9384-864dece96fe0 ->
5),1557435685640,-125,KO,List(ExitOnCompleteLoopBlock(6014555f-5487-44bc-
9384-864dece96fe0), ExitOnCompleteLoopBlock(caee65d1-d380-48e9-8566-
6b5fa)),io.gatling.core.protocol.ProtocolComponentsRegistry$$Lambda$37
2/1791589252#2be8b240)
14:01:30.520 [ERROR] i.g.h.a.s.HttpRequestAction - 'httpRequest-17' failed
to execute: No attribute named 'access_token' is defined
Code is below
package simulations
import scala.io.Source
class ERBSJson extends BaseSimulation {
val httpProtocol = http
.baseURL("http://services.online.local:4059/Business/")
val uri03 = "https://api.platform.com"
)
val header_cancel = Map(
"POST" -> "http://services.online.local:4059/Business/ HTTP/1.1",
"Accept-Encoding" -> "gzip,deflate",
"Content-Type" -> "text/xml;charset=UTF-8",
"SOAPAction" -> "http://www.example.org/Service/2013-
03/IEventReporterEndpoint/CancelEvent",
"Content-Length" -> "429",
"Host" -> "r20services.onlinegaming.local:4059",
"Connection" -> "Keep-Alive",
"User-Agent" -> "Apache-HttpClient/4.1.1 (java 1.5)"
)
val headers_10 = Map("Content-Type" -> "application/json","Authorization"
-> "${token_type} + ${access_token}" )
val source: String = Source.fromFile("C:/Gatling2/resources/data/input-
ERBS.json").getLines.mkString
def userCount: Int = JsonPath.parse(source).read("$.[0].user")
def testDuration: Int = JsonPath.parse(source).read("$.[0].testDuration")
def rampDuration: Int = JsonPath.parse(source).read("$.[0].rampDuration")
before {
println(s"Running test with ${userCount} users")
println(s"Ramping users over ${rampDuration} seconds")
println(s"Total Test duration: ${testDuration} seconds")
}
def CancelEvent()={
repeat(990000000){
exec(flushHttpCache)
exec(flushHttpCache)
.exec(http("Cancel Event")
.post("EventReporterV2")
.headers(header_cancel)
.body(ElFileBody("Soap_request_CancelEvent.txt"))
.check(status.in(200,201)))//checkforaspecificstatus
.exec{session=>println(session);session}//parameterfortheorgIdgoeshere
.pause(1)
}
}
val scenario1 = scenario("Test Cancel Event Soap Request ")
.exec(http("Event-Reservations-Web-Image-Login")
.get("https://api.origin.cloud/dev/event-reservations-
/loading.dfbfd678.svg")
.headers(headers_1)
.resources(http("Http Header Token Authentication Url")
.options(uri03 + "/auth/oauth/token")
.headers(headers_7),
http("Token Generation Url For Post")
.post(uri03 + "/auth/oauth/token")
.headers(headers_8)
.formParam("grant_type", "password")
.formParam("username", "zyz#abc.com")
.formParam("password", "fJC2RuVmHB")
.basicAuth("ikrwugh3883gh","NbnEEqmDLSfno315o87ghFGYr3jybtzbi76sr")
.check(jsonPath("$.access_token").exists.saveAs("access_token"))
.check(jsonPath("$.token_type").exists.saveAs("token_type"))
))
.forever() { // add in the forever() method - users now loop forever
exec(CancelEvent())
}
setUp(
scenario1.inject(
nothingFor(5 seconds),
rampUsers(userCount) over ( rampDuration ))
.protocols(httpProtocol))
.maxDuration(testDuration)
}
can you try with removing the '.exists' command? It's not necessary if you're saving anyway