When considering using JMeter for JDeveloper 11g ADF applications there are additional JMeter configuration steps required beyond that detailed in the original post. As per the original post ADF passes around cookies to store the JSESSIONID, and you can use JMeter's HTTP Cookie Manager to correctly handle the cookie. However ADF also makes use of a number of parameters passed as HTTP POST parameters and URL parameters. On running an ADF application you'll often see URL parameters like _adf.ctrl-state, _afrLoop, and embedded within the HTTP bodies variables such as javax.faces.ViewState, used by ADF to coordinate state with the browser session. In order for JMeter to simulate multiple user sessions it needs to be correctly configured to handle these too, capturing and carrying the unique values for each session in the load test.
The following post lists the additional configurations required for JMeter beyond those described in the original post. As noted in the original post the easiest way to setup your JMeter Test Plan is to record an existing ADF session through JMeter's HTTP Proxy Server and I haven't bothered to reiterate those instructions here. In turn I've taken no effort to explain the JMeter features we're utilizing to do this; if you want to learn about the relevant JMeter features consult the JMeter User Manual.
Caveat for ADF 10g users: You'll note above I specifically stated JDeveloper 11g ADF, not 10g. I haven't tested any of the following settings for JDev 10g and I don't believe they'll work, as if memory serves me correctly the POST and URL parameters mechanisms changed between versions. However the following notes may give you enough information to dig around in a running 10g ADF application, looking at the HTTP Requests to work out what parameters to work with.
JMeter ADF Required Configurations
The following lists 5 additional objects and their settings. You need only create one of each for each Thread Group. The end result will look something like this:
a) HTTP Cookie Manager
Create via Test Plan -> Thread Group -> Right Click -> Add -> Config Element -> HTTP Cookie Manager
Name: | HTTP Cookie Manager |
Clear cookies each iteration: | Selected |
b) User Defined Variables
Create via Test Plan -> Thread Group -> Right Click -> Add -> Config Element -> User Defined Variables
Name: | ADF Variables |
Variable Name: | afrLoop |
Variable Name: | afrWindowId |
Variable Name: | jsessionId |
Variable Name: | adf.ctrl-state |
Variable Name: | javax.faces.ViewState |
No initial values.
c) Regular Expression Extractor #1
Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor
Name: | adf.ctrl-state Extractor |
Response Field to Check: | Body |
Reference Name: | adf.ctrl-state |
Regular Expression: | _adf.ctrl-state=([-_0-9A-Za-z!]{10,13}) |
Template: | $1$ |
Match No.: | 1 |
Default Value: | n/a |
Take notice that the variable name does not include an underscore in it's name unlike the actual parameter _adf.ctrl-state.
d) Regular Expression Extractor #2
Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor
Name: | afrLoop Extractor |
Response Field to Check: | Body |
Reference Name: | afrLoop |
Regular Expression: | _afrLoop=([-_0-9A-Za-z]{13,16}) |
Template: | $1$ |
Match No.: | 1 |
Default Value: | n/a |
Take notice that the variable name does not include an underscore in it's name unlike the actual parameter _afrLoop.
d) Regular Expression Extractor #3
Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor
Name: | afrWindowId Extractor |
Response Field to Check: | Body |
Reference Name: | afrWindowId |
Regular Expression: | window.name='([-_0-9A-Za-z!]{10,13})' |
Template: | $1$ |
Match No.: | 1 |
Default Value: | n/a |
e) Regular Expression Extractor #4
Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor
Name: | javax.faces.ViewState Extractor |
Response Field to Check: | Body |
Reference Name: | javax.faces.ViewState |
Regular Expression: | <input type="hidden" name="javax\.faces\.ViewState" value="!(.+?)"> |
Template: | $1$ |
Match No.: | 1 |
Default Value: | n/a |
f) Regular Expression Extractor #5
Create via Test Plan -> Thread Group -> Right Click -> Add -> Post Processors -> Regular Expression Extractor
Name: | jsesssionId Extractor |
Response Field to Check: | Body |
Reference Name: | jsessionId |
Regular Expression: | ;jsessionid=([-_0-9A-Za-z!]{63}) |
Template: | $1$ |
Match No.: | 1 |
Default Value: | n/a |
Recorded HTTP Request Changes
After recording an ADF session in JMeter, and setting up the variables above, you'll note in the recorded HTTP Requests numerous references to the same named variables as HTTP parameters. Yet the recorded HTTP Requests include the parameters' values for the recorded session, which simply won't work when we replay the session as the values will have changed.
It is therefore necessary to identify all locations where the parameters exist in the recorded session HTTP Requests and substitute references to your variables.
a) HTTP Request – Path – any path that makes reference to jsessionId must be changed.
ie: /SageApp/faces/ViewEvents;jsessionId=${jsessionId}
b) HTTP Request – Path – ditto any path that makes reference to _adf.ctrl-state must be changed.
ie. /SageApp/faces/ViewEvents?_adf.ctrl-state=${adf.ctrl-state}
You can also change this in the name if you want but it wont make a different to the overall result, the name is for show only.
c) HTTP Request – Parameters – for any of the following parameters:
_adf.ctrl-state
_afrLoop
_afrWindowId (* see exception at the end of this step)
...for their values substitute the relevant variable. The following example shows the values for the afrLoop and adf.ctrl-state replaced:
However there is one exception with _afrWindowId. In one of the initial requests its value will be the literal string "null" (see the picture in step (a) for an example). Do not replace this, leave it as null.
d) HTTP Request – Parameters – for the "javax.faces.ViewState" parameter, ditto replace its values. Note it is necessary to put a not (negate) symbol in front of it:
ie. !${javax.faces.ViewState}
In addition by default the "Encode?" parameter will be on for each javax.faces.ViewState. Instead turn it off for each:
e) HTTP Request – Parameters – for the "unique" parameter, occurring in the Path and request parameters replace it with the following notation:
${__javaScript(new Date().getTime(),DUMMY)}
Note there is no JMeter user variable for this, we just write it straight into the unique value.
f) HTTP Header Manager Referer – if the HTTP session has been recorded via JMeter's HTTP Proxy, each HTTP Request will include a sub-element HTTP Header Manager that includes an element called Referer. You'll note it will have hardcoded many of the parameters discussed above. However I don't think ADF requires the Referer element and you can just ignore it or delete it from every HTTP Header Manager.
Assertions
Once you've completed the above setup and want to run your load test via JMeter, it is important to see that the test is working correctly. One way to do this is to create a View Results Tree (Test Plan -> Thread Group -> Right Click -> Add -> Listener -> View Results Tree) that allows you to inspect the response of each HTTP Request. If you then see HTTP Request responses in red this indicates a HTTP error code response (!= 200). However it's also necessary to inspect each response even if they are green to see if there's a valid response returned carrying an error message. There is definitely a small knack to differentiating between the different type of failures in an ADF application, which you'll become familiar with on some experience.
In order to save you having to look for the same problems for each test-run, you can create a Response Assertion (Test Plan -> Thread Group -> Add -> Assertions -> Response Assertion) to look for specific errors in the responses. From my own ADF experience you want to setup a Response Assertion with the following values:
Which Samples to Test: | Main Sample Only |
Response Field to Test: | Text Response |
Pattern Matching Rules: | Contains |
Not: | Selected |
Patterns to Test #1: | The following exception occurred |
Patterns to Test #2: | Server Exception during PPR |
Patterns to Test #3: | ADFC-12000 |
Patterns to Test #4: | java.net.SocketException |
Patterns to Test #5: | java.io.IOException |
This isn't a definitive list, and in turn there may be specific errors that you may want to pick up for your application.
Sample JMeter file
Readers can download a sample JMeter JMX file for ADF here with the configurations already setup.
Finishing Comment
I haven't had the chance to test this in every ADF scenario or application. It's possible in your application, dependent on the components you're using or some other trick of ADF, that I've missed one of the state values it wants the browser to carry. With the post above hopefully you'll get an idea of the majority and be able to identify more. I'd appreciate it if anybody finds more that we need to cater for please post a comment with your findings to assist other readers.