This sample app just shows how to execute JSF requests through Gatling stress tool against Primefaces showcase.
Running it
mvn gatling:execute
To chose a specific simulation use -D flag, example: -Dgatling.simulationClass=com.rmpestano.gatling.perf.DatatableSelectionSimulation otherwise all simulations will run. |
JSF Viewstate
When working with JSF you need to handle the ViewState which basically stores JSF component tree state and must be preserved across requests.
The way to extract information from http requests with Gatling is using http checks, here is how its done for JSF ViewState:
val jsfViewStateCheck = css("input[name='javax.faces.ViewState']", "value")
.saveAs("viewState") (1)
val jsfPartialViewStateCheck = xpath("//update[contains(@id,'ViewState')]")
.saveAs("viewState") (1)
1 | Saves the viewState in Gatling session. |
These checks needs to be called on each request so we can preserve the viewState. An idea is to put them in template requests like below:
def jsfGet(name: String, url: Expression[String]) =
http(name)
.get(url)
.check(jsfViewStateCheck)
def jsfPost(name: String, url: Expression[String]) = http(name)
.post(url)
.formParam("javax.faces.ViewState", "${viewState}")
.check(jsfViewStateCheck)
def jsfPartialPost(name: String, url: Expression[String]) = http(name)
.post(url)
.header("X-Requested-With", "XMLHttpRequest") (1)
.header("Faces-Request", "partial/ajax") (2)
.formParam("javax.faces.partial.ajax", "true") (2)
.formParam("javax.faces.ViewState", "${viewState}") (3)
.check(jsfPartialViewStateCheck)
1 | http ajax call format |
2 | JSF ajax call indication, JSF will return a xml in the response |
3 | already stored (in gatling session) view state |
So we basically have two kinds of request, a normal and a partial http request. Partial requests are mainly to deal with ajax calls (e.g p:ajax or f:ajax).
As you can see, in post requests the viewState already must be present in gatling session. |
CommandButton ajax
Here is an example of JSF ajax button call:
<p:commandButton value="Ajax Submit" id="ajax" update="growl" actionListener="#{buttonView.buttonAction}" styleClass="ui-priority-primary" />
This code is taken from Primefaces showcase: http://www.primefaces.org/showcase/ui/button/commandButton.xhtml |
And here is the correspondent Gatling call:
def ajaxButtonCall = jsfPartialPost("request_ajax_button", "/ui/button/commandButton.xhtml")
.formParam("javax.faces.source", "${btAjax}")
.formParam("javax.faces.partial.execute", "@all")
.formParam("javax.faces.partial.render","${growlId}")
.formParam("${btAjax}", "${btAjax}")
.formParam("${form}", "${form}")
.check(status.is(200),growlResponseCheck)
To find which params you need to pass in the request, firebug is your best friend:
The initial call
As stated before we need a previous JSF call to extract and store variables in Gatling session like button id in ${btAjax} variable, form id in ${form} and so on.
Here is the initial call:
jsfGet("saveState", "/ui/button/commandButton.xhtml")
.check(status.is(200), ajaxButtonIdCheck, formIdCheck,growlIdCheck)
and then the checks that stores the component’s id needed in the request:
val ajaxButtonIdCheck = css("button[id$='ajax']", "id") .saveAs("btAjax") val formIdCheck = css("form", "id") .saveAs("form") val growlIdCheck = css("span[id$='growl']", "id") .saveAs("growlId")
css selector $= denotes to 'endsWith' and is very helpful when working with JSF prepended id strategy. |
Check the response
As you’ve probably noticed, there is a growlResponseCheck after the request completes to assure that it has performed as the real application.
The response contains the growl component, use firebug tab response to see its format:
<partial-response id="j_id1"><changes><update id="j_idt87:growl"><![CDATA[<span id="j_idt87:growl" class="ui-growl-pl"
data-widget="widget_j_idt87_growl" data-summary="data-summary" data-severity="all,error" data-redisplay="true"></span>
<script id="j_idt87:growl_s" type="text/javascript">$(function(){PrimeFaces.cw('Growl','widget_j_idt87_growl',{id:'j_idt87:growl',
sticky:false,life:2000,escape:true,msgs:[{summary:"Welcome to Primefaces!!",detail:"",severity:'info'}]});});</script>]]></update>
<update id="j_id1:javax.faces.ViewState:0"><![CDATA[3557342521411359398:379028951273673786]]></update></changes></partial-response>
There are multiple ways to check that response, I’ll use xpath this time:
val growlResponseCheck = xpath("//*[contains(text(),'Welcome to Primefaces!!')]")
If you use wrong selector the request will fail, example: xpath("//*[contains(text(),'Welcome to Richfaces!!')]") will fail with: ---- Response Time Distribution ------------------------------------------------ > t < 800 ms 34 ( 61%) > 800 ms < t < 1200 ms 9 ( 16%) > t > 1200 ms 12 ( 21%) > failed 1 ( 2%) ---- Errors -------------------------------------------------------------------- > xpath(//*[contains(text(),'Welcome to Richfaces!!')]).find(0).exists 1 (100,0%) , found nothing =============================================================================== |
Complete CommmandButton simulation can be found here.
Ajax Behaviour event
For ajax behaviour event the approach is quite similar, here goes an example of keyup event on page http://www.primefaces.org/showcase/ui/ajax/event.xhtml:
val formIdCheck = css("form", "id")
.saveAs("form")
val inputIdCheck = css("input[id$='firstname']", "id")
.saveAs("inputId") //stores in gatling session id of input which id endswith 'firstname'
val outputIdCheck = css("span[id$='out1']", "id")
.saveAs("outputId")
//checks the partial response xml result
val outputValueCheck = xpath("//*[contains(@id,'out1') and contains(text(),'Gatling, JSF and Primefaces rules')]")
def ajaxEventRequest = jsfPartialPost("request_ajax_event", "/ui/ajax/event.xhtml")
.formParam("javax.faces.source", "${inputId}")
.formParam("javax.faces.partial.execute", "${inputId}")
.formParam("javax.faces.behavior.event", "keyup")
.formParam("javax.faces.partial.event", "keyup")
.formParam("javax.faces.partial.render","${outputId}")
.formParam("${inputId}", "Gatling, JSF and Primefaces rules")
.formParam("${form}", "${form}")
.check(status.is(200), outputValueCheck)
val AjaxEventScenario = scenario("ajaxEvent scenario")
.exec(
jsfGet("initialRequest", "/ui/ajax/event.xhtml")
.check(status.is(200), inputIdCheck,outputIdCheck, formIdCheck)
)
.pause(2)
.exec(ajaxEventRequest)
.pause(1)
here is Gatling response to confirm the partial event is fired:
<partial-response id="j_id1"><changes><update id="j_idt87:out1"><![CDATA[<span id="j_idt87:out1">Gatling, JSF and Primefaces rules</span>]]></update><update id="j_id1:javax.faces.ViewState:0"><![CDATA[5642006804874081440:6246997700145170162]]></update></changes></partial-response>
For debugging purposes just enable request and response logs in test/resources/logback.xml file: |
<logger name="io.gatling.http" level="TRACE" />
Complete ajaxEvent simulation can be found here.
Datatable row select event
Here is an example of row select event from this showcase page (third table).
First thing to do is to save row id (in the initial request) because its needed for row select event:
val tableRowCheck = css("tbody[id='form:eventsDT_data'] > tr[role='row'] > td[role='gridcell']")
.saveAs("rowId")
now we are ready to fire the event:
def datatableSelectCarRowEvent = jsfPartialPost("request_datatable_select_car", "/ui/data/datatable/selection.xhtml")
.formParam("javax.faces.source", "form:eventsDT")
.formParam("javax.faces.partial.execute", "form:eventsDT")
.formParam("javax.faces.partial.render", "form:msgs")
.formParam("form", "form")
.formParam("form:eventsDT_instantSelectedRowKey","${rowId}")
.formParam("javax.faces.behavior.event","rowSelect")
.formParam("javax.faces.partial.event","rowSelect")
.check(status.is(200), growlCheck)
growlCheck verifies the partial response which will be something like below:
<partial-response id="j_id1"><changes><update id="form:msgs">
<![CDATA[<span id="form:msgs" class="ui-growl-pl" data-widget="widget_form_msgs" data-summary="data-summary" data-detail="data-detail"
data-severity="all,error" data-redisplay="true"></span><script id="form:msgs_s" type="text/javascript">
$(function(){PrimeFaces.cw('Growl','widget_form_msgs',{id:'form:msgs',sticky:false,life:6000,escape:true,msgs:
[{summary:"Car Selected",detail:"77698f6d",severity:'info'}]});});</script>]]></update>
<update id="j_id1:javax.faces.ViewState:0"><![CDATA[-5013885715335809736:669939156470551654]]></update></changes></partial-response>
and here is the check:
val growlCheck = xpath("//*[contains(text(),'Car Selected')]")
.saveAs("growlValue") //save is just to confirm in printSession
Check complete datatable simulation here.
Test Result
the output should be something like this:
================================================================================ ---- Global Information -------------------------------------------------------- > request count 110 (OK=110 KO=0 ) > min response time 231 (OK=231 KO=- ) > max response time 1670 (OK=1670 KO=- ) > mean response time 383 (OK=383 KO=- ) > std deviation 287 (OK=287 KO=- ) > response time 50th percentile 244 (OK=244 KO=- ) > response time 75th percentile 266 (OK=266 KO=- ) > mean requests/sec 13.019 (OK=13.019 KO=- ) ---- Response Time Distribution ------------------------------------------------ > t < 800 ms 101 ( 92%) > 800 ms < t < 1200 ms 5 ( 5%) > t > 1200 ms 4 ( 4%) > failed 0 ( 0%) ================================================================================ Reports generated in 0s. Please open the following file: /home/pestano/projects/gatling-jsf-demo/target/gatling/results/ajaxeventsimulation-1431742082396/index.html Global: percentage of successful requests is greater than 99 : true [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 15.516s [INFO] Finished at: Fri May 15 23:08:12 BRT 2015 [INFO] Final Memory: 7M/150M [INFO] ------------------------------------------------------------------------
Also some detailed reports about the simulation are generated at target/gatling folder:
see full generated reports here: