Archive for the ‘Performance Testing’ Category

Related Pages:
performance testing interview questions, performance testing strategy, performance testing guidance for web applications, performance testing definition, performance testing metrics, performance testing plan, performance testing resume, performance testing council, performance testing software

How Selenium Works

Tuesday, October 20th, 2009

How Selenium Works
However, automation has specific advantages for improving the long-term efficiency of a software team’s testing processes. Test automation supports:

* Frequent regression testing
* Rapid feedback to developers during the development process
* Virtually unlimited iterations of test case execution
* Customized reporting of application defects
* Support for Agile and eXtreme development methodologies
* Disciplined documentation of test cases
* Finding defects missed by manual testing
how-it-works

Introducing Selenium Commands

Tuesday, October 20th, 2009

Introducing Selenium Commands
Script Syntax
Selenium Commands -- Selenese

Selenium provides a rich set of commands for fully testing your web-app in virtually any way you may imagine. This command set is often called selenese. These commands essentially create a testing language.

In selenese, certainly, one can test the existence of UI elements, based on their HTML tags, can test for specific content, can test for broken links, input fields, selection list, submitting forms, and table data. In addition Selenium commands support testing of window size (is true? must check the reference), mouse position, alerts, Ajax functionality, pop up windows, event handling, and many other features. The Command Reference (available at SeleniumHQ.org) lists all the available commands. (more...)

Getting Started with Selenium/ Selenium RC steps

Tuesday, October 20th, 2009

Step a) Record your test case using Selenium IDE as shown in earlier flash demo.

For intial recording- avoid using "cross domain" testing (eg www.abc.com, www.def.com) and
preferably use http site only.(till you figure out the workarounds)

Step b) Play your test case using selenium IDE itself to make sure
that your test case works..
Modify the recorded code if necessary(eg replace click by clickAndWait
or change timeout) as explained in Quirks section. (more...)

Monitoring Weblogic using JMX in SiteScope

Tuesday, October 20th, 2009

Using JMX (Java Management Extensions) it is possible to monitor Managed Beans published by MBean servers. Weblogic (WL) provides MBeans for both admin and managed servers.

You can find out more info about WebLogic MBeans and accessing them with JMX here …

Following are some augmented instructions for setting up JMX monitors within SiteScope. I used this excellent article as a starting point. (more...)

JMeter Non HTTP response message: Unconnected sockets not implemented

Tuesday, October 20th, 2009

If you’re testing HTTPS with JMeter 2.3.2 and a current version of Java greater than 1.5 e.g.

java -version
java version "1.6.0_11"
Java(TM) SE Runtime Environment (build 1.6.0_11-b03)
Java HotSpot(TM) Client VM (build 11.0-b16, mixed mode, sharing)

Then you are likely to encounter this error when using the standard HTTP Request sampler:
(more...)

Testing SAP Web Portals with JMeter

Tuesday, October 20th, 2009

Some people get confused with the additional protocols that LoadRunner sports for SAP, particularly the SAP GUI. But if your SAP portal uses plain old HTTP (or HTTPS) then JMeter can do the job.

There are a few gotchas which I will continue to update on this post, namely around correlation. In JMeter, correlation is particularly *easier* because you don’t have to worry about where to position a correlation rule. The equivalent function to use is a Post Processor -> Regular Expression Extractor.

I used the following rules to help correlate the SAP portal:

Response Field to check: Body
Reference Name: sap-ext-sid
Regular Expression: sap-ext-sid=”(.+?)”
Template: $1$
Match No.: 1
(more...)

Load balancing vusers without a load balancer

Tuesday, September 22nd, 2009
Recently I ran a test at a company which had a performance test environment with multiple web servers, but no load balancer. To spread my virtual users evenly across the web servers, I made a simple modification to my script.
First, I created a file-based parameter containing my web server names.
Next, I parameterised the server name part of the URLs in my web_url() and web_submit_data() function calls.
web_url("Login",
"URL=http://{ServerName}/login.do",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTML",
LAST);
So, when the script ran, the virtual user would would choose a web server at random on every iteration.
…and that was it. I didn’t say it was going to be hard. :)
Of course, my Test Summary Report was very clear that one of the limitations of the Performance Test cycle had been that there was no load balancer in the test environment, so the stability and performance characteristics of this component in the Production environment could not be predicted.

Recently I ran a test at a company which had a performance test environment with multiple web servers, but no load balancer. To spread my virtual users evenly across the web servers, I made a simple modification to my script.

First, I created a file-based parameter containing my web server names.

parameter-based-load-balancing

Next, I parameterised the server name part of the URLs in my web_url() and web_submit_data() function calls.

web_url("Login",

"URL=http://{ServerName}/login.do",

"TargetFrame=",

"Resource=0",

"RecContentType=text/html",

"Referer=",

"Snapshot=t1.inf",

"Mode=HTML",

LAST);

So, when the script ran, the virtual user would would choose a web server at random on every iteration.

…and that was it. I didn’t say it was going to be hard. :)

Of course, my Test Summary Report was very clear that one of the limitations of the Performance Test cycle had been that there was no load balancer in the test environment, so the stability and performance characteristics of this component in the Production environment could not be predicted.

VuGen scripting for BMC Remedy Action Request System 7.1

Tuesday, September 22nd, 2009

I recently created some BPM scripts for the BMC Remedy Action Request System 7.1 web client. This Tech Tip contains some of the things that I learnt.
My favourite part of this exercise was proving that the person from BMC who said “we have already tried, and found that it is impossible to script ARS with VuGen” was totally wrong.
Note that the information should be equally relevant whether you are creating VuGen scripts for LoadRunner, or for use as BPMs for BAC.
bmc-remedy-action-request-system-login-page
Recording and Script Generation
Remedy ARS has both a Win32 client and a web-based client. The Win32 client communicates with the server using a proprietary protocol that is not supported by VuGen. Traffic can be recorded using the Windows Sockets vuser type, but the data is in an unintelligible binary format (just because you can record an application with Winsock doesn’t mean you should try to create a script using it).
Note that it might be possible to configure VuGen to record ARS by using the VuGen protocol development kit, and referring to the ARS API documentation, but time limitations prevented me from experimenting with this.
The Web (Click and Script) vuser type does not successfully record ARS, but it is possible to create a working script using the Web (HTTP/HTML) vuser type.
When recording, you must either use URL mode (so that each HTTP request is a separate function call), or change your recording settings to treat the MIME type “text/plain” as a non-resource (Recording Options > HTTP Properties > Advanced > Non-Resources). This will ensure that the calls to BackChannel are not recorded as EXTRARES.
VuGen 9.5 has an odd behaviour when generating code for the BackChannel requests. These appear as a web_custom_request with “Method=GET”, but still have a Body parameter with the same value as the arguments passed in the URL (obviously GET requests should not have a body). As long as the HTTP method is GET, then this body may be safely removed.
Backchannel Requests
You may have already guessed that BackChannel requests are kind of important for ARS. These requests are sent behind the scenes to fetch data to display in the GUI and to update data on the server. This means that you will be scripting “blind” as VuGen will not show you what any pages look like in either the Tree View or the Test Results window.
Here is an example of a BackChannel request:

// Lookup of Incident # INC00000126798

web_custom_request("BackChannel_15",

"URL=https://remedy-ars.example.com/arsys/BackChannel/?param=416%2FGetEntryList%2F22%2Fremedy-ars.example.com31%2FHPD%3AIncident%20Management%20Console22%2Fremedy-ars.example.com13%2FHPD%3AHelp%20Desk0%2F30%2F4%5C1%5C1%5C1000000161%5C99%5C302085700%

5C13%2F1%2F9%2F30208570020%2F1%2F15%2FINC0000012679875%2F1%2F1%2F41%2F21%2F3212%2F17%2F9%2F240001005

10%2F100000000110%2F10000057919%2

F20000000510%2F100000000010%2F100000005610%2F100000016410%2F100000015110%2F100000008010%2F10000002179

%2F20000000310%2F100000056010%2F10000057859%2

F2000000049%2F24000100310%2F10000000999%2F240001002",

"Method=GET",

"TargetFrame=",

"Resource=0",

"RecContentType=text/plain",

"Referer=https://remedy-ars.example.com/arsys/forms/remedy-ars.example.com/HPD%3AIncident+Management+Console/Default+User+View+%28Support%29/?cacheid=70246b58",

"Snapshot=t22.inf",

"Mode=HTML",

"EncType=text/plain; charset=UTF-8",

LAST);

/*

URL decoded request:

https://remedy-ars.example.com/arsys/BackChannel/?param=416/GetEntryList/22/remedy-ars.example.com31/HPD:Incident Management Console22/remedy-ars.example.com13/HPD:Help Desk0/30/4\1\1\1000000161\99\302085700\13/1/9/30208570020/1

/15/INC0000012679875/1/1/41/21/3212/17/9/24000100510/100000000110

/10000057919/20000000510

/100000000010/100000005610/100000016410/100000015110/1

00000008010/10000002179/20000000310/100000056010/10

000057859/2000000049/24000100310/10000000999/240001002

JSON response:

this.result={n:1,f:[{t:0},{t:4,v:"JDS"},{t:4,v:"PPL000000132801"},{t:4,v:"Network Management Systems"},{t:4,v:"Test Environment: Please free swap space on following Production servers\r\n"},{t:4,v:"(03) 9663 4573"},{t:6,v:"2 Medium"},{t:4,v:"Test Environment: Please free swap space on following Production servers\r\n\r\jdsweb01.jds.net.au                             \r\jdsweb02.jds.net.au  \r\jdsapp01.jds.net.au                             \r\jdsapp02.jds.net.au                            "},{t:4,v:"PPL000000132801"},{t:4,v:"JDS Remedy Test Support"},{t:4,v:"Services"},{t:7,v:"1248074292"},{t:4,v:"(03) 9663 4573"},{t:4,v:"Systems"},{t:4,v:"JDS"},{t:6,v:"1 User Service Request"},{t:4,v:"ARS"},{ts:1249950456}]};

The data in the URL “param=” argument becomes a little clearer once you URL decode it.

The first part of the string (416/) is the length of the string, not including this first length parameter and the delimiter.

The second part of the string (GetEntryList) is the action that is being called on the server. Other possible values include:

GetURLForForm

GetEntry

GetEntryList

GetTableEntryList

GetCurrencyExchangeRates

SetEntry

SetEntryList

SetOverride

SaveSearch

ExpandMenu (used to populate a drop-down list)

CompileExternalQualification

ServerRunProcess

Each value after the action has a field length argument immediately before it e.g. “22/remedy-ars.example.com31/HPD:Incident Management Console”. Note that 0 is a valid length, and a length value may be specified that includes muliple “/” characters (like the “212/” value in the request above).

Creating a BackChannel request that does not conform to this format may result in a “Network protocol/data error” response but, then again, it may not - the BackChannel requests don’t always seem to be validated on the server.

Error Messages and Verification

A successful BackChannel request will always return “this.result=”, so it is a good first step to put a basic web_reg_find function before each BackChannel request to verify that this string appears in the HTTP response.

web_reg_find("Text=this.result", LAST);

Other possible responses start with “CurWFC.status” or “retry=this.Override”. Some examples are:

CurWFC.status([{cId:1,t:2,m:"Network protocol/data error when performing data operation. Please contact administrator.",n:9350,a:""}]);

CurWFC.status([{t:2,m:"",n:1291039,a:"The incident location information is invalid. Use the menus on the Region, Site Group, and Site fields or the type ahead return function on the Site field to select this information."}]);

CurWFC.status([{cId:1,t:2,m:"Session is invalid or has timed out. Please reload page to log in again.",n:9201,a:""}]);

CurWFC.status([{cId:1,t:2,m:"Message not found",n:-1,a:""}]);

CurWFC.status([{cId:1,t:2,m:"User is currently connected from another machine",n:9084,a:""}]);

retry=this.Override([{cId:1,t:2,m:"User is currently connected from another machine",n:9093,a:""}]);

The difference between the last two messages is that the last message can be bypassed by acknowledging the message and continuing, while the second-last message cannot be bypassed and will prevent a user from opening the requested screen/module inside ARS.

When BackChannel returns an empty recordset (like from GetEntryList), it will look like “this.result={n:0};”

Save and Set functions may indicate success by returning “this.result=1? or “this.result=true”.

But most of the time BackChannel will return data in a format with 1 elements (n=1), with the element being an array of values (f:[{t:0},{t:4,v:"JDS"},{t:4,v:"PPL000000132801"}]).

Here are some for known error messages:

// Known error messages

web_global_verification("Text=Authentication failed", "ID=AuthenticationFailed", LAST);

web_global_verification("Text=The incident location information is invalid", "ID=IncidentLocationInformationIsInvalid", LAST);

web_global_verification("Text=Network protocol/data error when performing data operation", "ID=NetworkProtocolDataError", LAST);

web_global_verification("Text=Please contact administrator", "ID=PleaseContactAdministrator", LAST);

web_global_verification("Text=Session is invalid", "ID=SessionIsInvalidOrHasTimedOut", LAST);

web_global_verification("Text=Message not found", "ID=MessageNotFound", LAST);

web_global_verification("Text=User is currently connected from another machine", "ID=UserIsCurrentlyConnectedFromAnotherMachine", LAST);

Code Snippets

For some reason ARS will create a hash of the password before sending it to the server. While it is possible to reinvent their hashing function, there is no point. When you parameterise the password, just put the hashed value in your parameter file, and ensure that all accounts have the same password.

web_submit_data("LoginServlet",

"Action=https://{ServerName}/arsys/servlet/LoginServlet",

"Method=POST",

"TargetFrame=",

"RecContentType=text/html",

"Referer=https://{ServerName}/arsys/shared/login.jsp",

"Snapshot=t2.inf",

"Mode=HTML",

ITEMDATA,

"Name=username", "Value=bpm01", ENDITEM,

"Name=pwd", "Value=pthkhphshjhkhk", ENDITEM, // welcome4

"Name=auth", "Value=", ENDITEM,

"Name=timezone", "Value=AET", ENDITEM,

"Name=encpwd", "Value=1", ENDITEM,

"Name=goto", "Value=", ENDITEM,

"Name=server", "Value=", ENDITEM,

"Name=ipoverride", "Value=0", ENDITEM,

"Name=initialState", "Value=0", ENDITEM,

"Name=returnBack", "Value=null", ENDITEM,

LAST);

Timestamps are used in many requests. These are either in seconds or milliseconds since 1/Jan/1970. Be careful not to confuse a timestamp in seconds with a length argument after it for a timestamp in milliseconds. This will cause a “Network protocol/data error when performing data operation” error.

// Example in C

// Timestamp in seconds

lr_save_int(time(NULL), "pTimestamp_1"); // 1249950809

// Timestamp in milliseconds

web_save_timestamp_param("pTimestamp_2"); // 1249950434718

// Example in Java

// Timestamp in seconds

lr.save_int((int) (System.currentTimeMillis()/1000), "pTimestamp_1"); // 1249950809

// Timestamp in milliseconds

lr.save_string(System.currentTimeMillis() + "", "pTimestamp_2"); // 1249950434718

The following code example deals with the situation where the account is already logged onto the system. It will confirm the dialog window (SetOverride) and retry the request.

// Handle case where dialog window pops up saying "User is currently connected from another machine".

web_reg_find("Text=this.result", "SaveCount=TextCheckCount", LAST); // This happens normally.

web_reg_find("Text=User is currently connected from another machine", "SaveCount=LoggedInCount", LAST); // This happens when the account is already logged on.

web_custom_request("BackChannel_2",

// https://{ServerName}/arsys/BackChannel/?param=170/GetEntryList/22/{ServerName}31/HPD:Incident Management Console22/{ServerName}15/AST:AppSettings0/16/4\1\2\2\1\2\2\1\2/0/2/0/2/0/1/21/313/1/9/400127400"

"URL=https://{ServerName}/arsys/BackChannel/?param=170%2FGetEntryList%2F22%2F{ServerName}31%2FHPD%3AIncident%20Management%20Console22%2F{ServerName}15%2FAST%3AAppSettings0

%2F16%2F4%5C1%5C2%5C2%5C1%5C2%5C2%5C1%5C2%2F0%2F2%2F0%2F2%2F0%2F1%2F21%2F313%2F1%2F9%2F400127400",

"Method=GET",

"TargetFrame=",

"Resource=0",

"RecContentType=text/plain",

"Referer=https://{ServerName}/arsys/forms/{ServerName}/HPD%3AIncident+Management+Console/Default+User+View+%28Support%29/?cacheid={pCacheID_2}",

"Snapshot=t9.inf",

"Mode=HTML",

"EncType=text/plain; charset=UTF-8",

LAST);

// RETURNS: retry=this.Override([{cId:1,t:2,m:"User is currently connected from another machine",n:9093,a:""}]);

// RETURNS: CurWFC.status([{cId:1,t:2,m:"User is currently connected from another machine",n:9084,a:""}]); // if this response is received, there does not seem to be a way to bypass it.

// RETURNS: this.result={n:1,f:[{t:4,v:"BMC.ASSET"},{ts:1249950451}]};

if (strcmp(lr_eval_string("{LoggedInCount}"), "1") == 0) {

// User is already logged in.

// Confirm dialog window and continue.

lr_output_message("Account is in use by another session. Script will confirm dialog and continue. LoggedInCount: %s, TextCheckCount: %s", lr_eval_string("{LoggedInCount}"), lr_eval_string("{TextCheckCount}"));

web_reg_find("Text=this.result=true", LAST);

web_custom_request("BackChannel_2b",

// https://{ServerName}/arsys/BackChannel/?param=15/SetOverride/1/1"

"URL=https://{ServerName}/arsys/BackChannel/?param=15%2FSetOverride%2F1%2F1",

"Method=GET",

"TargetFrame=",

"Resource=0",

"RecContentType=text/plain",

"Referer=https://{ServerName}/arsys/forms/{ServerName}/HPD%3AIncident+Management+Console/Default+User+View+%28Support%29/?cacheid={pCacheID_2}",

"Snapshot=t9.inf",

"Mode=HTML",

"EncType=text/plain; charset=UTF-8",

LAST);

// RETURNS: this.result=true;

web_reg_find("Text=this.result", LAST);

web_reg_find("Text=User is currently connected from another machine", "Fail=Found", LAST);

web_custom_request("BackChannel_2c",

// https://{ServerName}/arsys/BackChannel/?param=170/GetEntryList/22/{ServerName}31/HPD:Incident Management Console22/{ServerName}15/AST:AppSettings0/16/4\1\2\2\1\2\2\1\2/0/2/0/2/0/1/21/313/1/9/400127400"

"URL=https://{ServerName}/arsys/BackChannel/?param=170%2FGetEntryList%2F22%2F{ServerName}31%2FHPD%3AIncident%20Management%20Console22%2F{ServerName}15%2FAST%3AAppSettings0

%2F16%2F4%5C1%5C2%5C2%5C1%5C2%5C2%5C1%5C2%2F0%2F2%2F0%2F2%2F0%2F1%2F21%2F313%2F1%2F9%2F400127400",

"Method=GET",

"TargetFrame=",

"Resource=0",

"RecContentType=text/plain",

"Referer=https://{ServerName}/arsys/forms/{ServerName}/HPD%3AIncident+Management+Console/Default+User+View+%28Support%29/?cacheid={pCacheID_2}",

"Snapshot=t9.inf",

"Mode=HTML",

"EncType=text/plain; charset=UTF-8",

LAST);

// RETURNS: this.result={n:1,f:[{t:4,v:"BMC.ASSET"},{ts:1249950451}]};

// RETURNS: CurWFC.status([{cId:1,t:2,m:"User is currently connected from another machine",n:9084,a:""}]); <-- this is returned if the dialog confirm did not work.

} else if (strcmp(lr_eval_string("{TextCheckCount}"), "1") == 0) {

// Everything is working as expected. Continue script execution.

lr_output_message("Account is not in use by another session, and everything is working as expected. LoggedInCount: %s, TextCheckCount: %s", lr_eval_string("{LoggedInCount}"), lr_eval_string("{TextCheckCount}"));

} else {

// Did not find expected text

lr_error_message("Error. Did not get \"user is currently connected\", but also did not get expected text. Something is wrong with Remedy ARS.");

lr_exit(LR_EXIT_VUSER, LR_FAIL);

}

Correlation

Correlation is quite difficult for Remedy ARS. My approach was very manual:

Record the business process twice with the same input data, then WDiff to find request values that change between iterations.

Record the business process twice with different input data (different username, different “incident” data etc), then WDiff to find other request values that change.

VuGen will break long strings (like the BackChannel URL) into multiple lines. This will be a problem for you if you are trying to do a search and replace for values you are correlating/parameterising. I put all long strings onto a single line.

The BackChannel URLs are hard to read in their URL encoded form. I put the URL decoded value above in a comment above the URL.

I found that I had a clearer idea of what the application was doing during the business process when I cut and pasted the JSON response below each BackChannel request.

If you find that you have to do difficult string processing or use someone else’s JSON library, you may find it easier to convert your C-based script into a Java-based script. Note that there are some bugs with HP’s sed script that does the conversion. It does not add “new String[]{” to the end of the web_custom_request “URL” argument, when the URL agument spans multiple lines. It will also change any occurances of LASTID into LAST}}ID and LASTCOUNT into LAST}}COUNT.

Finally, you will be amazed at the values that look like they should be correlated that actually never change. Remember that the only way to be sure is to use WDiff (Tools > Compare with Script). Just don’t expect these values to stay the same if the version of Remedy ARS changes, or you user profiles change.

// Lookup of Incident # INC00000126798
web_custom_request("BackChannel_15",
"URL=https://remedy-ars.example.com/arsys/BackChannel/?param=416%2FGetEntryList%2F22%2Fremedy-ars.example.com31%2FHPD%3AIncident%20Management%20Console22%2Fremedy-ars.example.com13%2FHPD%3AHelp%20Desk0%2F30%2F4%5C1%5C1%5C1000000161%5C99%5C302085700%5C13%2F1%2F9%2F30208570020%2F1%2F15%2FINC0000012679875%2F1%2F1%2F41%2F21%2F3212%2F17%2F9%2F24000100510%2F100000000110%2F10000057919%2F20000000510%2F100000000010%2F100000005610%2F100000016410%2F100000015110%2F100000008010%2F10000002179%2F20000000310%2F100000056010%2F10000057859%2F2000000049%2F24000100310%2F10000000999%2F240001002",
"Method=GET",
"TargetFrame=",
"Resource=0",
"RecContentType=text/plain",
"Referer=https://remedy-ars.example.com/arsys/forms/remedy-ars.example.com/HPD%3AIncident+Management+Console/Default+User+View+%28Support%29/?cacheid=70246b58",
"Snapshot=t22.inf",
"Mode=HTML",
"EncType=text/plain; charset=UTF-8",
LAST);
/*
URL decoded request:
https://remedy-ars.example.com/arsys/BackChannel/?param=416/GetEntryList/22/remedy-ars.example.com31/HPD:Incident Management Console22/remedy-ars.example.com13/HPD:Help Desk0/30/4\1\1\1000000161\99\302085700\13/1/9/30208570020/1/15/INC0000012679875/1/1/41/21/3212/17/9/24000100510/100000000110/10000057919/20000000510/100000000010/100000005610/100000016410/100000015110/100000008010/10000002179/20000000310/100000056010/10000057859/2000000049/24000100310/10000000999/240001002
JSON response:
this.result={n:1,f:[{t:0},{t:4,v:"JDS"},{t:4,v:"PPL000000132801"},{t:4,v:"Network Management Systems"},{t:4,v:"Test Environment: Please free swap space on following Production servers\r\n"},{t:4,v:"(03) 9663 4573"},{t:6,v:"2 Medium"},{t:4,v:"Test Environment: Please free swap space on following Production servers\r\n\r\jdsweb01.jds.net.au                             \r\jdsweb02.jds.net.au  \r\jdsapp01.jds.net.au                             \r\jdsapp02.jds.net.au                            "},{t:4,v:"PPL000000132801"},{t:4,v:"JDS Remedy Test Support"},{t:4,v:"Services"},{t:7,v:"1248074292"},{t:4,v:"(03) 9663 4573"},{t:4,v:"Systems"},{t:4,v:"JDS"},{t:6,v:"1 User Service Request"},{t:4,v:"ARS"},{ts:1249950456}]};
The data in the URL “param=” argument becomes a little clearer once you URL decode it.
The first part of the string (416/) is the length of the string, not including this first length parameter and the delimiter.
The second part of the string (GetEntryList) is the action that is being called on the server. Other possible values include:
GetURLForForm
GetEntry
GetEntryList
GetTableEntryList
GetCurrencyExchangeRates
SetEntry
SetEntryList
SetOverride
SaveSearch
ExpandMenu (used to populate a drop-down list)
CompileExternalQualification
ServerRunProcess
Each value after the action has a field length argument immediately before it e.g. “22/remedy-ars.example.com31/HPD:Incident Management Console”. Note that 0 is a valid length, and a length value may be specified that includes muliple “/” characters (like the “212/” value in the request above).
Creating a BackChannel request that does not conform to this format may result in a “Network protocol/data error” response but, then again, it may not - the BackChannel requests don’t always seem to be validated on the server.
Error Messages and Verification
A successful BackChannel request will always return “this.result=”, so it is a good first step to put a basic web_reg_find function before each BackChannel request to verify that this string appears in the HTTP response.
web_reg_find("Text=this.result", LAST);
Other possible responses start with “CurWFC.status” or “retry=this.Override”. Some examples are:
CurWFC.status([{cId:1,t:2,m:"Network protocol/data error when performing data operation. Please contact administrator.",n:9350,a:""}]);
CurWFC.status([{t:2,m:"",n:1291039,a:"The incident location information is invalid. Use the menus on the Region, Site Group, and Site fields or the type ahead return function on the Site field to select this information."}]);
CurWFC.status([{cId:1,t:2,m:"Session is invalid or has timed out. Please reload page to log in again.",n:9201,a:""}]);
CurWFC.status([{cId:1,t:2,m:"Message not found",n:-1,a:""}]);
CurWFC.status([{cId:1,t:2,m:"User is currently connected from another machine",n:9084,a:""}]);
retry=this.Override([{cId:1,t:2,m:"User is currently connected from another machine",n:9093,a:""}]);
The difference between the last two messages is that the last message can be bypassed by acknowledging the message and continuing, while the second-last message cannot be bypassed and will prevent a user from opening the requested screen/module inside ARS.
When BackChannel returns an empty recordset (like from GetEntryList), it will look like “this.result={n:0};”
Save and Set functions may indicate success by returning “this.result=1? or “this.result=true”.
But most of the time BackChannel will return data in a format with 1 elements (n=1), with the element being an array of values (f:[{t:0},{t:4,v:"JDS"},{t:4,v:"PPL000000132801"}]).
Here are some for known error messages:
// Known error messages
web_global_verification("Text=Authentication failed", "ID=AuthenticationFailed", LAST);
web_global_verification("Text=The incident location information is invalid", "ID=IncidentLocationInformationIsInvalid", LAST);
web_global_verification("Text=Network protocol/data error when performing data operation", "ID=NetworkProtocolDataError", LAST);
web_global_verification("Text=Please contact administrator", "ID=PleaseContactAdministrator", LAST);
web_global_verification("Text=Session is invalid", "ID=SessionIsInvalidOrHasTimedOut", LAST);
web_global_verification("Text=Message not found", "ID=MessageNotFound", LAST);
web_global_verification("Text=User is currently connected from another machine", "ID=UserIsCurrentlyConnectedFromAnotherMachine", LAST);
Code Snippets
For some reason ARS will create a hash of the password before sending it to the server. While it is possible to reinvent their hashing function, there is no point. When you parameterise the password, just put the hashed value in your parameter file, and ensure that all accounts have the same password.
web_submit_data("LoginServlet",
"Action=https://{ServerName}/arsys/servlet/LoginServlet",
"Method=POST",
"TargetFrame=",
"RecContentType=text/html",
"Referer=https://{ServerName}/arsys/shared/login.jsp",
"Snapshot=t2.inf",
"Mode=HTML",
ITEMDATA,
"Name=username", "Value=bpm01", ENDITEM,
"Name=pwd", "Value=pthkhphshjhkhk", ENDITEM, // welcome4
"Name=auth", "Value=", ENDITEM,
"Name=timezone", "Value=AET", ENDITEM,
"Name=encpwd", "Value=1", ENDITEM,
"Name=goto", "Value=", ENDITEM,
"Name=server", "Value=", ENDITEM,
"Name=ipoverride", "Value=0", ENDITEM,
"Name=initialState", "Value=0", ENDITEM,
"Name=returnBack", "Value=null", ENDITEM,
LAST);
Timestamps are used in many requests. These are either in seconds or milliseconds since 1/Jan/1970. Be careful not to confuse a timestamp in seconds with a length argument after it for a timestamp in milliseconds. This will cause a “Network protocol/data error when performing data operation” error.
// Example in C
// Timestamp in seconds
lr_save_int(time(NULL), "pTimestamp_1"); // 1249950809
// Timestamp in milliseconds
web_save_timestamp_param("pTimestamp_2"); // 1249950434718
// Example in Java
// Timestamp in seconds
lr.save_int((int) (System.currentTimeMillis()/1000), "pTimestamp_1"); // 1249950809
// Timestamp in milliseconds
lr.save_string(System.currentTimeMillis() + "", "pTimestamp_2"); // 1249950434718
The following code example deals with the situation where the account is already logged onto the system. It will confirm the dialog window (SetOverride) and retry the request.
// Handle case where dialog window pops up saying "User is currently connected from another machine".
web_reg_find("Text=this.result", "SaveCount=TextCheckCount", LAST); // This happens normally.
web_reg_find("Text=User is currently connected from another machine", "SaveCount=LoggedInCount", LAST); // This happens when the account is already logged on.
web_custom_request("BackChannel_2",
// https://{ServerName}/arsys/BackChannel/?param=170/GetEntryList/22/{ServerName}31/HPD:Incident Management Console22/{ServerName}15/AST:AppSettings0/16/4\1\2\2\1\2\2\1\2/0/2/0/2/0/1/21/313/1/9/400127400"
"URL=https://{ServerName}/arsys/BackChannel/?param=170%2FGetEntryList%2F22%2F{ServerName}31%2FHPD%3AIncident%20Management%20Console22%2F{ServerName}15%2FAST%3AAppSettings0%2F16%2F4%5C1%5C2%5C2%5C1%5C2%5C2%5C1%5C2%2F0%2F2%2F0%2F2%2F0%2F1%2F21%2F313%2F1%2F9%2F400127400",
"Method=GET",
"TargetFrame=",
"Resource=0",
"RecContentType=text/plain",
"Referer=https://{ServerName}/arsys/forms/{ServerName}/HPD%3AIncident+Management+Console/Default+User+View+%28Support%29/?cacheid={pCacheID_2}",
"Snapshot=t9.inf",
"Mode=HTML",
"EncType=text/plain; charset=UTF-8",
LAST);
// RETURNS: retry=this.Override([{cId:1,t:2,m:"User is currently connected from another machine",n:9093,a:""}]);
// RETURNS: CurWFC.status([{cId:1,t:2,m:"User is currently connected from another machine",n:9084,a:""}]); // if this response is received, there does not seem to be a way to bypass it.
// RETURNS: this.result={n:1,f:[{t:4,v:"BMC.ASSET"},{ts:1249950451}]};
if (strcmp(lr_eval_string("{LoggedInCount}"), "1") == 0) {
// User is already logged in.
// Confirm dialog window and continue.
lr_output_message("Account is in use by another session. Script will confirm dialog and continue. LoggedInCount: %s, TextCheckCount: %s", lr_eval_string("{LoggedInCount}"), lr_eval_string("{TextCheckCount}"));
web_reg_find("Text=this.result=true", LAST);
web_custom_request("BackChannel_2b",
// https://{ServerName}/arsys/BackChannel/?param=15/SetOverride/1/1"
"URL=https://{ServerName}/arsys/BackChannel/?param=15%2FSetOverride%2F1%2F1",
"Method=GET",
"TargetFrame=",
"Resource=0",
"RecContentType=text/plain",
"Referer=https://{ServerName}/arsys/forms/{ServerName}/HPD%3AIncident+Management+Console/Default+User+View+%28Support%29/?cacheid={pCacheID_2}",
"Snapshot=t9.inf",
"Mode=HTML",
"EncType=text/plain; charset=UTF-8",
LAST);
// RETURNS: this.result=true;
web_reg_find("Text=this.result", LAST);
web_reg_find("Text=User is currently connected from another machine", "Fail=Found", LAST);
web_custom_request("BackChannel_2c",
// https://{ServerName}/arsys/BackChannel/?param=170/GetEntryList/22/{ServerName}31/HPD:Incident Management Console22/{ServerName}15/AST:AppSettings0/16/4\1\2\2\1\2\2\1\2/0/2/0/2/0/1/21/313/1/9/400127400"
"URL=https://{ServerName}/arsys/BackChannel/?param=170%2FGetEntryList%2F22%2F{ServerName}31%2FHPD%3AIncident%20Management%20Console22%2F{ServerName}15%2FAST%3AAppSettings0%2F16%2F4%5C1%5C2%5C2%5C1%5C2%5C2%5C1%5C2%2F0%2F2%2F0%2F2%2F0%2F1%2F21%2F313%2F1%2F9%2F400127400",
"Method=GET",
"TargetFrame=",
"Resource=0",
"RecContentType=text/plain",
"Referer=https://{ServerName}/arsys/forms/{ServerName}/HPD%3AIncident+Management+Console/Default+User+View+%28Support%29/?cacheid={pCacheID_2}",
"Snapshot=t9.inf",
"Mode=HTML",
"EncType=text/plain; charset=UTF-8",
LAST);
// RETURNS: this.result={n:1,f:[{t:4,v:"BMC.ASSET"},{ts:1249950451}]};
// RETURNS: CurWFC.status([{cId:1,t:2,m:"User is currently connected from another machine",n:9084,a:""}]); <-- this is returned if the dialog confirm did not work.
} else if (strcmp(lr_eval_string("{TextCheckCount}"), "1") == 0) {
// Everything is working as expected. Continue script execution.
lr_output_message("Account is not in use by another session, and everything is working as expected. LoggedInCount: %s, TextCheckCount: %s", lr_eval_string("{LoggedInCount}"), lr_eval_string("{TextCheckCount}"));
} else {
// Did not find expected text
lr_error_message("Error. Did not get \"user is currently connected\", but also did not get expected text. Something is wrong with Remedy ARS.");
lr_exit(LR_EXIT_VUSER, LR_FAIL);
}
Correlation
Correlation is quite difficult for Remedy ARS. My approach was very manual:
Record the business process twice with the same input data, then WDiff to find request values that change between iterations.
Record the business process twice with different input data (different username, different “incident” data etc), then WDiff to find other request values that change.
VuGen will break long strings (like the BackChannel URL) into multiple lines. This will be a problem for you if you are trying to do a search and replace for values you are correlating/parameterising. I put all long strings onto a single line.
The BackChannel URLs are hard to read in their URL encoded form. I put the URL decoded value above in a comment above the URL.
I found that I had a clearer idea of what the application was doing during the business process when I cut and pasted the JSON response below each BackChannel request.
If you find that you have to do difficult string processing or use someone else’s JSON library, you may find it easier to convert your C-based script into a Java-based script. Note that there are some bugs with HP’s sed script that does the conversion. It does not add “new String[]{” to the end of the web_custom_request “URL” argument, when the URL agument spans multiple lines. It will also change any occurances of LASTID into LAST}}ID and LASTCOUNT into LAST}}COUNT.
Finally, you will be amazed at the values that look like they should be correlated that actually never change. Remember that the only way to be sure is to use WDiff (Tools > Compare with Script). Just don’t expect these values to stay the same if the version of Remedy ARS changes, or you user profiles change.

Testing Web Services With a Standard Web Vuser

Tuesday, September 22nd, 2009

It is possible to test web services using the standard Web (HTTP/HTML) virtual user type instead of the Web Services vuser type. The main disadvantage of this is that you cannot generate your SOAP body from the WSDL file using the VuGen wizard. But if you know what your XML request should look like, then you shouldn’t have any real problems.
Here are my tips:
Send your SOAP payload using lr_custom_request().
Add a SOAPAction HTTP header using web_add_header().
Remove unnecessary HTTP headers (that are generated automatically by VuGen) with web_remove_auto_header().
Don’t forget to verify that you get a valid response. Use web_reg_find() for a simple check. For better verification of the SOAP response use lr_xml_find().
To extract values from the response body, use lr_xml_get_values(). Brush up on your XPath qeries beforehand though.
It may be necessary to HTML-encode some characters in your XML/SOAP message (e.g. convert “&” to “&”). Unfortunately VuGen does not provide this functionality (but HP could easily add it to the web_convert_param function), so you will have to either write (or find) a function to do it, or convert all the entries in your data table before running the script.
As an example, here is a simple script that makes use of a web service that will look up the source of a Shakespeare quote for you. The WSDL is available from http://www.xmlme.com/WSShakespeare.asmx?wsdl.

Action()
{
// ContentCheck Rules for known error messages
web_global_verification("Text=Speech not found", "ID=SpeechNotFound", LAST);

lr_start_transaction ("Search For Shakespeare Quote");

// By default, VuGen sends a user-agent header.
// Let's remove this as an example of removing automatically generated headers.
web_remove_auto_header("User-Agent", "ImplicitGen=No", LAST);

// Add a SOAPAction HTTP header
web_add_header("SOAPAction", "http://xmlme.com/WebServices/GetSpeech");

// Save entire body from the HTTP response for later checking with lr_xml_find.
web_reg_save_param("ResponseBody",
"LB=",
"RB=",
"Search=Body",
"IgnoreRedirections=Yes",
LAST);

// Note that the text to search for would normally be replaced with a parameter,
// and so would the element of the below SOAP message.
web_reg_find("Text=TWELFTH NIGHT", LAST);

web_custom_request("Search Shakespeare",
"URL=http://www.xmlme.com/WSShakespeare.asmx",
"Method=POST",
"Resource=0",
"Referer=",
"Snapshot=t1.inf",
"Mode=URL",
"EncType=text/xml; charset=utf-8",
"Body=" // As it is SOAP, you are unlikely to have to use BodyBinary, unless your request has CDATA.
"< ?xml version=\"1.0\" encoding=\"utf-8\"?>"
""
"
"
""
"Be not afraid of greatness"
"
"
"
"
"",
LAST);

// The response from the web service looks like this:
/*
< ?xml version="1.0" encoding="utf-8"?>




<SPEECH>
<PLAY>TWELFTH NIGHT</PLAY>
<SPEAKER>MALVOLIO</SPEAKER>
'Be not afraid of greatness:' 'twas well writ.</SPEECH>


*/

// An example of extracting the a value from a SOAP reponse.
// This saves the element into {OutputParameter}.
// The same syntax could be used with lr_xml_find to check the response.
lr_xml_extract("XML={ResponseBody}",
"XMLFragmentParam=OutputParameter",
"Query=/soap:Envelope/soap:Body/GetSpeechResponse/GetSpeechResult", LAST);
lr_output_message("Source of Shakespeare quote: %s", lr_eval_string("{OutputParameter}"));

lr_end_transaction ("Search For Shakespeare Quote", LR_AUTO);

return 0;
}

What’s New in LoadRunner 9.50?

Tuesday, September 22nd, 2009

LoadRunner 9.5 was released today and, as mentioned by the LoadRunner Product Manager, the focus has been on refining current functionality rather than adding completely new features.
This is not meant to be an exhaustive list (or a replication of the readme file), but it covers the features that I think are significant, and also my impressions after a day of using the tool.
For those who want the executive summary, LoadRunner now works on Vista, and has an agent for the RDP vuser type. The biggest new feature is the protocol detection feature in VuGen. For those who want a more detailed analysis, read on…
VuGen
VuGen is not just important to load testers, it is also significant to people who do application monitoring with BAC, as it is used to create BPM scripts. Unfortunately, the current version of BPM (7.52) will not support VuGen 9.50, so BPM scripts should still be written using Loadrunner 9.10.
Protocol Advisor - HP must have been getting lots of feedback that junior load testers were having trouble figuring out what virtual user type to record their application with. The Protocol Advisor records your application and then gives you some suggestions on which vuser type to use, based on the network traffic that it has recorded. This probably beats the previous technique of just trying all of them and seeing which ones work. Note that the protocol detection only seems to work for some of the supported vuser types/protocols.
HP Service Test integration - If you do automated functional testing of web services, you may have used HP Service Test, and you may have noticed that it looked almost exactly like VuGen. As they were basically the same program, it was not possible to have both Service Test and VuGen instaled on the same computer. Now Service Test has been integrated with VuGen, so there are no sociability problems between the two. Activating some of the features of Service Test requires a license key.
Improved Test Results report in VuGen - There have been some changes to the Test Results report (View > Test Results), which I guess is good now that Service Test is integrated. The report can be exported to HTML, and defects can be raised in Quality Center directly from the report page (if you have set up a connection to QC).
Runs on Vista - Previously only the load generator software ran on Vista, now all LoadRunner components will happily run on Microsoft’s latest desktop operating system.
VuGen look and feel is identical to the previous version, except for the Start Page, which now looks a little more “Vista-like”.
The most exciting developments are in the VuGen protocols…
Citrix Agent for 64-bit Windows - The Citrix vuser type can be kind of painful without the Citrix Agent, and over the last couple of years it has been more and more common to find Citrix servers running on 64-bit Windows. I am very happy that HP has now provided a Citrix Agent for 64-bit Windows.
RDP Agent for Microsoft Terminal Server - Hopefully it will now be practical to use either the Citrix or RDP vuser type to create scripts for protocols that are otherwise unsupported by VuGen.
Support for the RTMP protocol - Flash objects can use the Real-Time Messaging Protocol to transfer audio, video and data. HP must have made a special deal with Adobe to get access to the details of this protocol, because Adobe only just announced that they plan to open the spec sometime in the first half of 2009.
Click and Script improvements - This vuser type now supports dojo. There aren’t any new functions though.
New functions in VuGen:
Web
web_cross_step_download
Flex
flex_rtmp_connect
flex_rtmp_send
flex_rtmp_receive
flex_rtmp_disconnect
RDP
rdp_sync_on_window
rdp_sync_on_agent
rdp_get_window_position
rdp_get_active_window_title
rdp_get_object_info
rdp_sync_on_object_info
rdp_sync_object_mouse_click
rdp_sync_object_mouse_double_click
rdp_get_text
rdp_sync_on_text
Oracle NCA
nca_list_click_select_item
nca_configurator_start
nca_configurator_set_ui_data
nca_configurator_parameterize_url
nca_configurator_parameterize_data
LoadRunner Analysis
LoadRunner Analysis has had a couple of tweaks, but it has one new feature that I’m excited about…
Analysis API - An API is now available that can be used for extracting load test data from LoadRunner Analysis. Being able to extract measurements from multiple Analysis sessions should make it easier to create your own trend report. This feature would have saved me lots of cut and paste work with Excel when tracking performance of daily software builds on past projects. In fact, this process could be completely automated, as the LoadRunner Controller can now automatically launch a program (like an Analysis data extraction tool) at the end of a test.
LoadRunner Controller
WAN Emulation - Integration with the Shunra WAN Emulator is back. It is possible to emulate the network bandwidth and latency that real users at remote network locations would encounter. This delay is introduced by Shunra’s VE Desktop software on the load generators. Note that VE Desktop must be licensed separately from Shunra. Related metrics from the Shunra software can now be shown in the LoadRunner Controller.
Load Generators
Secure Load Generators - Did you ever think that anyone could connect to your load generators, upload arbitary code (inside a VuGen script), and run it? Well now they can’t because the connection between the Controller and the load generators can be (note, “can be” not “must be”) secured with a password and an encrypted connection. Previously this was only possible if you were also using the MI Listener, which is normally used to monitor servers and control load generators over a firewall.
WAN Emulation - See description in the LoadRunner Controller section.
More efficient Click & Script - The Click & Script vuser type is supposed to use less system resources now (I assume this mostly means a smaller memory footprint).
Performance Center
Performance Center is still squarely pitched at the Enterprise-level customers, but for the first time, HP has included a feature that is not available in the standard LoadRunner product - trend reports. This suggests that HP might be pitching Performance Center as a “premium” product, not just a product that helps you to run manage multiple performance testing projects at once.
Trend Reports - This is the only new Performance Center feature that anyone will get excited about. Trend reports allow you to easily compare key metrics across multiple tests. Previously it was only possible to compare two tests by using the Cross with Results option in LoadRunner Analysis.
User Interface Improvements - I haven’t used Performance Center for a long time, but the user interface seems cleaner, and there are now Ajax calls to update some parts of the page.
Host License Breakdown - Performance Center admins will like the improvements to the licensing interface, which now provides details for each license that is installed, and gives overall license usage metrics (as pretty graphs).
Scenario Creation - Creating a load test scenario has been made a little more straightforward when it comes to scheduling (ramp up rates, groups etc).
Update:
The LoadRunner 9.51 patch/feature pack has now been released.