I'm still pretty new to Gatling / Scala, so my apologies if I've misunderstood something obvious, but...
I have a scenario with a sequence of requests. One of them is along the lines of:
.exec (
http("Get IDs")
.post(<urlpath>)
.body(<body text>)
.headers(<headerinfo>)
.check(jsonPath("$[*].some.json.path").findAll.transform(_.map(_.replace("unwantedchars,""))).saveAs(myIds)
)
And that works fine, returning a vector of all matching json elements, with the unwanted chars removed. What I'm trying to do next is loop over the first 5 ids, and pass them into the next request. I've tried assorted variants of the following, but no amount of variation / googling has returned the actual solution:
.exec( session => {
val firstFive = session("myIds").as[Vector[String]].toArray.take(5)
for (inx <- 0 until 4){
exec(
http("get the item")
.get("/path/to/item/thing/" + firstFive(inx))
.headers(<etc etc>)
)
session
})
So I understand nested exec's aren't supported - but how can I create a block that combines the for-loop, and the actual HTTP requests?
Am I missing something obvious?
if you're looking to take 5, then you just need to put them in the session and then process them with a .foreach block.
so instead of taking the first five and trying to make exec calls (which, as you observed, won't work), save the list back to the session
.exec( session => {
val firstFive = session("myIds").as[Vector[String]].take(5)
session.set("firstFive", firstFive)
)
then use the DSL to loop through the collection
.foreach("${firstFive}", "id") {
exec(
http("get the item")
.get("/path/to/item/thing/${id}")
.headers(<etc etc>)
)
}
you could even add the selection of the first five to your transform step - so the whole thing could be...
.exec (
http("Get IDs")
.post(<urlpath>)
.body(<body text>)
.headers(<headerinfo>) .check(jsonPath("$[*].some.json.path").findAll.transform(_.map(_.replace("unwantedchars,"")).take(5).saveAs(myIds)
)
.foreach("${myIds}", "id") {
exec(
http("get the item")
.get("/path/to/item/thing/${id}")
.headers(<etc etc>)
)
}
Related
I'm trying to run a scenario which has 2 requests
The first one authenticates the users through an API and gets a token. I want each user to do the first request only once.
The second one executes a GET throught another API.
Here's the code of the scenario:
val slot: ScenarioBuilder = scenario("Get slot")
.doIf(_("connected").asOption[String].isEmpty) {
exec(
http("Get_token")
.post(my_endpoint)
.headers(specific_headers)
.formParam("username", "my_username")
.formParam("password", "my_password")
.check(regex("""id_token":"(.*?)"""").saveAs("my_token"))
)
.exec { session =>
println("Before: " + session("connected").as[String]) //prints nothing as expected
session
}
.exec(_.set("connected", "true"))
.exec { session =>
println("After: " + session("connected").as[String]) //prints "true" as expected
session
}
}
.feed(fsid)
.exec(
http("Get_slot")
.get("/slots")
.headers(other_specific_headers) //including "my_token" extraced above
.queryParam("specific_id", "${specific_id}") //from feeder fsid
.check(regex("""id":"(.*?)"""").findRandom.saveAs("slot_id"))
)
Here's the code of the simulation:
setUp(
slot.inject(
constantUsersPerSec(1).during(15.seconds)
).protocols(httpConfig)
)
When I execute the above simulation, I can see in the logs that each of the 2 requests are both executed 15 times. I'm expecting to have less "Get_token" executed than "Get_slot".
I've read some documentation avout Gatling's forever element, and I think it could do the work.
But I would like to understand why this does not behave as I expect.
If both requests are executed 15 times, does it mean that:
15 users are launched, and thus their session is empty at the beginning, so it's normal they do the get_token ?
My "Doif" is not well written ?
Something else I'm missing ?
Any help would be great, thank you in advance.
EDIT:
Just to give more informations, according to simpleApp's comment, I only see 2 actives users during the test (in average)
This is why I expect to see 4 "Get_token", and not more.
As I read right from the Gatling documentation, foreach loop expects ChainBuilder as an executable expression.
foreach(sequence, elementName){chain} The chain is repeated for every
element in the sequence and the current element is stored in the
Session under the elementName key
However, if I want to use it inside resource method, it expects HttpRequestBuilders. Here's my usecase:
.exec(
http("Some name.")
.get("/api/call1?viewType=REGULAR")
.check(
jsonPath("$..reviewers[*]").ofType[Seq[Any]].saveAs("reviewers")
)
.resources(
foreach(session => session("reviewers").as[Seq[Any]], "reviewer") {
http("Request resource.")
.get("/api/users/${reviewer}/photo")
.headers(resourceRequestHeaders)
}
)
)
That's not possible as of Gatling 3.3 (and upcoming 3.4). Number of requests in the resources can only be static.
I have two feeders in my test scenario
When i use one of them in first request it works alright, however when I use it in the next request block it doesn't work
I have tried changing the feed(feederName) position but still having the same problem
Here is a snippet of my test scenario with some comment to explain what's not working
//the Two feeders
val kmPerYearFeeder = Iterator.continually(
Map("kmPerYear" -> Random.shuffle(List("10000", "15000", "20000", "25000", "30000", "35000", "40000", "45000", "50000")).head)
)
val customerTypes = Iterator.continually(
Map("customerType" -> Random.shuffle(List("P","B")).head)
)
//here the customerTypes feeder is working
val homepage = feed(customerTypes)
.exec(http("homepage")
.get("/?customer_type=${customerType}"))
//this block is not really important but working alright
val pdp = exec(http("homepage")
....
// the feeder here doesn't work
val calculate_rate = feed(kmPerYearFeeder)
.exec(http("calculate_random_rate")
.get(session => session("random_pdp_link").as[String] + "?inquiry_type=&km_per_year=${kmPerYear}")
.check(status.is(200)))
val pdp_scenario = scenario("PDP").exec(homepage).exec(pdp).exec(calculate_rate)
setUp(
pdp_scenario.inject(
rampUsers(10) during (5 seconds),
).protocols(httpProtocol),
)
these are the get requests that are executed (got them from the logger)
GET ********?inquiry_type=&km_per_year=$%7BkmPerYear%7D
GET ********?inquiry_type=&km_per_year=$%7BkmPerYear%7D
GET ********?inquiry_type=&km_per_year=$%7BkmPerYear%7D
Your issue is that in where you attempt to use the Gatling EL to reference the kmPerYear session var in
.get(session => session("random_pdp_link").as[String] + "?inquiry_type=&km_per_year=${kmPerYear}")
this version of .get takes a session function (which you are using to get "random_pdp_link"), but the Gatling EL doesn't work in session functions.
You either need to get it manually using
session("kmPerYear").as[String]
or to reference "random_pdp_link" via the EL and not use the session function get. eg:
.get("${random_pdp_link}?inquiry_type=&km_per_year=${kmPerYear}")
I have to query an API with pagination. But because the API is weird I have to query it until the response is empty (contains no items).
Actually it is done in Java with a do-while :
do {
objects = doQuery(from);
from += 200
} while ( !objects.isEmpty() )
But I would like to convert it in scala. My first idea is to use a stream with a step and takeWhile :
Stream.iterate(0)(_+200)
.map( from => doQuery(from) )
.takeWhile( objects -> !objects.isEmpty )
But another changes make doQuery returns a Future. Thus I cannot do the test in takeWhile and have no best idea on how to do it (maybe a recursive call).
Hopefully this new code will be into an Akka actor that will tell another for each object (no need to return anything)
This gives you Stream[Future[(Boolean, T)]], where the first time will be false as soon as the rest will be empty. Not sure how to do the takeWhile without blocking.
Stream.iterate(0)(_+200)
.scanLeft(Future((true, List[Int]())))({case (prev, from) =>
prev.flatMap({case (cont, _) =>
if(cont) {
doQuery(from).map(res => (res.isEmpty, res.toList))
} else {
Future((false, List()))
}
})
})
I've come across the following code in a Gatling scenario (modified for brevity/privacy):
val scn = scenario("X")
.repeat(numberOfLoops, "loopName") {
exec((session : Session) => {
val loopCounter = session.getTypedAttribute[Int]("loopName")
session.setAttribute("xmlInput", createXml(loopCounter))
})
.exec(
http("X")
.post("/rest/url")
.headers(headers)
.body("${xmlInput}"))
)
}
It's naming the loop in the repeat block, getting that out of the session and using it to create a unique input XML. It then sticks that XML back into the session and extracts it again when posting it.
I would like to do away with the need to name the loop iterator and accessing the session.
Ideally I'd like to use a Stream to generate the XML.
But Gatling controls the looping and I can't recurse. Do I need to compromise, or can I use Gatling in a functional way (without vars or accessing the session)?
As I see it, neither numberOfLoops nor createXml seem to depend on anything user related that would have been stored in the session, so the loop could be resolved at build time, not at runtime.
import com.excilys.ebi.gatling.core.structure.ChainBuilder
def addXmlPost(chain: ChainBuilder, i: Int) =
chain.exec(
http("X")
.post("/rest/url")
.headers(headers)
.body(createXml(i))
)
def addXmlPostLoop(chain: ChainBuilder): ChainBuilder =
(0 until numberOfLoops).foldLeft(chain)(addXmlPost)
Cheers,
Stéphane
PS: The preferred way to ask something about Gatling is our Google Group: https://groups.google.com/forum/#!forum/gatling