Back to Blog

LoadRunner Citrix Scripting Tricks

Note: This article was originally posted on, and has been migrated to the Northway web site to maintain the content online.

Citrix applications represent the second most common protocol for LoadRunner engagements at Loadtester Incorporated Northway Solutions Group. Our white paper from 2006 on Citrix best practices continues to be a very popular download on the site. In 2008, we’ve presented this information to the Citrix iForum and at local user groups. There continues to be a lot of interest in testing Citrix applications, but there is not a whole lot of information beyond the basic tips and guides found in the LoadRunner manual or support documentation from HP.

Since we’ve already laid out most of the basics in our whitepapers, I won’t repeat them here. I have worked on a few Citrix engagements myself going back to the days before there was a Citrix protocol (let me get my walking cane and Geritol and I’ll tell you some stories). I wanted to share a few additional tips I have learned from some of my more recent engagements. These are based on code solutions that helped me in real situations, and I hope you can use these to your benefit. I am going to assume you know enough about C coding and using Vugen so that you can use this code as merely a starting point. You should know where to put this stuff to make the script work. If you do need help with understanding the code, I would suggest taking a LoadRunner class for VuGen. On other hand, if you are better at C than I am – and most people are – then send me a better way to do these functions if you see something that needs to be changed. I’ll post your code examples as well. In the real world, my functions would have a lot more error handling and messages being sent to the output log and the Controller depending on the level of debugging I need.

More Planning = Easier Scripting Experience

Before we start getting into code, let’s just refresh our minds with the most important aspect of the performance testing project: Planning. 70% of the time should be spent planning, especially when using Citrix, because it is all about setting up the application in a way that allows you to script against it without pulling your hair out. You should meet with the Citrix administrators, and the people who set up user access to the application, and those who control the data. Think about restrictions on access and map out the business processes directly to the data needed (and the resulting screens) depending on the profile of the person. I find that the technical team not understanding the amount and type data needed for the testing causes the biggest delay in the whole loadtesting process. If you cannot get the application set up so that the business process is almost the same for each user, be prepared for some long days scripting around it.

Citrix Agent – Friend or Foe?

The LoadRunner Citrix agent was designed to make scripting easier by recognizing objects. But what happens when you finally get permission to put the agent on the Citrix server, and VuGen no longer works? I had a problem recently where certain windows in the application would not even pop-up when doing a single mouse-click, and the only solution was to turn the Citrix agent off. If you get to certain screens and everything seems to run at a crawl, or it seems like your session is hanging, rename the LoadRunner Citrix agent on the server to turn it off. Then record a new script without it. Are things faster? It may be the agent causing the problem. As always, ensure the agent is the same version as the rest of your LR rig. Always update the Citrix agent when you update the LR software (even for service packs). Check the support site for the latest Citrix specific patches.

Don’t confuse the Vugen Code Generation recording option where you can uncheck the setting called “Use Citrix Agent input in code generation”. This is not the same thing. To keep the agent on the Citrix server but disable it, simply go the directory where CitrixAgent.exe is and rename it to something else.

Setting the Client Name

There may be times when you have to define specific client names to make them work in the controller. This is mainly for applications where each workstation is assigned security access, not just the user name. in those cases, you need to tell the server what client name to use. If you do not, Vugen will come up with one for you and it probably will not work. In order to set the client name in the code, I have used the host name of the machine, and appended the Vuser id number from the list of users in the Controller – meaning this will only work when executed in the Controller. You also have to keep in mind the way your Vusers are listed and how they are numbered in Groups to ensure you don’t have duplicates (if you require unique names). I then set that new name as a parameter and set that value as the client name with the ctrx_set_connect_opt function:

char * my_host;
char clientname[20];
my_host = lr_get_host_name( );
sprintf(clientname, "%s-%s", my_host, lr_eval_string("{pVuserID}"));
lr_save_string(clientname, "pClientName");
ctrx_set_connect_opt(CLIENT_NAME, "{pClientName}");
ctrx_set_connect_opt(ICAFILE, "myicafile.ica");

Notice that I have two ctrx_set_connect_opt functions. It matters the order that you put these lines of code. In a recent project if I pulled from the ICA file first and then set the client name, it was ignored and the client name was not set – thus the script failed. When I set the client name first, it worked fine. HP support was unable to recreate this in their lab, but I could recreate it at will on my servers, so keep this in mind.

Troubleshooting with this Citrix tool

Citrix has a small ATL-based application called ATLico that can be used as a debugging tool. It was originally intended to do simple record and replays of an ICA session, and it has been around for several years. It’s small and very simple to use. You can launch and record a Citrix session with this and then do the same steps within Vugen. This is good for sharing the business process recording without making a script because output files are small and could be sent via email to someone else who have the product installed as well. The other reason you might want to do this is to isolate an issue in a Citrix recording to Vugen or within the ICA session itself.  It’s also good for troubleshooting tickets with HP support. To find out more about this Citrix application, check with Citrix support. You can also read more about this little application in these PDF’s online:’s%20LoadRunner%20for%20Citrix.pdf

Inquiring Minds Want to Know

This is my favorite code when I am lost in a Citrix error where expected windows are not appearing where they are supposed to, or they are not the right ones. Many times this will help you expose just what the window and X/y coordinates are that are causing a problem. At the top of the script I initialize the variables, and when I get to a portion of the code where a problematic window has been stopping the script, I will place the ctrx_get_window_name and ctrx_get_window_position right before it. If I want to sync on it and possibly do something, I’ll put additional code, like the ctrx_sync_on_window you see below:

char window_name[100];
long xpos, ypos, width, height;
ctrx_get_window_position(window_name, &xpos, &ypos, &width, &height);
lr_output_message("Window = %s. x= %ld, y= %ld, width= %ld, height = %ld", window_name, xpos, ypos, width, height);
ctrx_sync_on_window(window_name, ACTIVATE, xpos, ypos, width, height, "snapshot134", CTRX_LAST);

Make sure you have your logging set properly when you run this in Vugen.

Exception Handling

There are times when you are going through the work flow of a business process and depending on the user, the profile, the access, etc, the process may have different window title’s that could pop up. I am not talking about using wild cards (*) in a window title. I mean altogether new widows or forms that you have to deal with. For example, I had a script where the process needed to click past an alert message by clicking on the OK button, but the alert only popped up under specific conditions. In this case, I check for the window name. If it matches, I hit the ENTER key to move beyond the form (which is always focused on the OK button). I also double check my window by syncing on it and looking at the output. This might be overkill, but it worked every time. I always make sure send an output message to the execution log to know I triggered the alert. In a real script I would have a lot more detail in that message so I would know the time, the Vuser, the login used, the iteration, and more for debugging purposes.  I would also put a little think time in (3 seconds or so) before I hit the ENTER key. If the alert does not pop-up, none of this runs because a different window name will be in the title and the script should run fine.

ctrx_get_window_name(window_name, CTRX_LAST);
if ( strcmp(window_name, "Special Alert") == 0 ) {
    rc = ctrx_sync_on_window("Special Alert", ACTIVATE, 322, 252, 381, 241, "snapshot77", CTRX_LAST);
    if (rc == 0) {
        lr_output_message(">>INFO: Alert triggered. Clicking OK to alert.");
        ctrx_key("ENTER_KEY", 0, "snapshot78", CTRX_LAST);

As an additional exception to the rule, there might be times when there were no records displayed and no processing needed to occur. In this case, I would look for a bitmap that would only exist if there were no records, and handle accordingly with another if statement.

Looking For A Sign

There are times when you have a visual cue that may move around a bit. If you only have a couple of places the cue could be, you can use Tim’s trick of putting more than one hash value by separating them with the pipe character “|” as a delimiter (see his white paper for this information). This way you can use a few within the same bitmap sync. However, what if it is almost in a different place every time and there are dozens of places it could be? When the hash pipe isn’t getting it for you…(cue the Weezer song), there might be another way. I recently had a small graphic that was presented dynamically on the screen depending on how much text was above it. There was no way to know where on the Y-axis it would show up. However, I did know that the X-axis was always at 351 pixels from the ICA frame. I also knew the size of the icon was always the same width and height. So, really all I had to do was adjust my Y-axis value and match the bitmap hash value. To do this I created a variable for the Y-axis instead of the hard coded number, but I initialized it to the pixel value near where I thought it might appear, but at the highest most boundary of where it could possibly appear, which happened to be around 175 pixels south of the border of the bitmap. Then I created a loop to scan down pixel-by-pixel and check the bitmap hash value. At some point it should match.

int rc = 0;
int i = 0;
char window_name[20];
char buffer[50];
long y=175;
for (i = 1; i < 125; i++) {
    rc = 1;
    ctrx_get_bitmap_value(351, y, 1, 1, buffer, CONTINUE_ON_ERROR, CTRX_LAST);
    if(strcmp(buffer, "83f4ebd2a2fc94dc5314a798543a9428") != 0){
        lr_output_message(">>INFO: Bitmap buffer for the confirmation icon is %s", buffer);
        ctrx_sync_on_bitmap(351, y, 1, 1, "83f4ebd2a2fc94dc5314a798543a9428", CTRX_LAST);
        lr_output_message(">>INFO: Found icon. y is currently at %ld", y);
        rc = 0;
if (rc != 0) {
    // if you cannot find the window (if rc has not been set to zero) then log and leave...
    lr_output_message(">>INFO: Icon NOT FOUND. Exit current window");

To find out exactly where something needs to be, you can use the Microsoft Paint application that comes with all the latest MS operating systems. If you navigate in windows explorer to the directory where the snapshots are for the iteration (for example c:\scripts\scriptname\result1\Iteration1\snapshots”), can right-click and select edit to open up the specific PNG file in Paint. As you hold the mouse pointer over a place in the image, the bottom right corner of the window will display the x/y coordinates. You can use zoom to get precise measurements of where something is.  Perhaps some of you could create some code that looks at both x and y coordinates based on a radius. Here is a challenge: Publish the Solitaire application and have some code that checks areas on the cards that can determine if it is a heart, spade, clover, or diamond by using similar code.

Escape From It All

The ctrx_execute_on_window is a great way to handle unexpected screens that might appear anywhere in your script. If you know there is a chance you will get hung on an unexpected screen, you can always code a separate function to use some known key sequence that can be used to get out safely (in my example below I use the ENTER key). I created a function called enterkey_form_handler and put it into vuser_init section of the script in the VERY TOP before the vuser_init() is made:

int enterkey_form_handler(char win_title[]) {
    ctrx_key("ENTER_KEY", 0, "snapshot130", CTRX_LAST);
    return 0;

Then, I added this line of code in the action section just under my variable declaration:

ctrx_execute_on_window(“Window Title One”, enterkey_form_handler);

If you know what the window titles are for the majority of your application, and you have the time, you can begin to “object orient” your code by creating a header file with a bunch of your own custom functions to handle the window forms with their various key strokes and mouse clicks. You can then include this header and refer to it with every new script. Then your Citrix scripts begin to look like this (in the action section):

ctrx_execute_on_window("Screen1: ", generic_form_handler);
ctrx_execute_on_window("*Text Entry", nongeneric_textform_handler);
ctrx_execute_on_window("Standard Peripheral IV*", ecapekey_form_handler);
ctrx_execute_on_window("IV Assessment Abnormals ", generic_form_handler);
ctrx_execute_on_window("Standard Behavior appropriate*", onekey_form_handler);
ctrx_execute_on_window("Screen4", twokey_form_handler);
ctrx_execute_on_window("Standard maintenance*", generic_form_handler);

That might be the entire action section of business process steps. Notice the use of wild cards when needed. This takes a little getting used to, but could make more maintainable scripts for long term projects when things are still changing. You would only need to modify the handler functions for a specific form without having to redo the entire script.

Until Next Time…

I hope these tips have been helpful. If you have some tips or tricks of your own, please add a comment below. I will add more to this blog as I come across them.

Back to Blog