Friday, May 21, 2010

Goodbye multiple putty windows!

If you use PuTTY for your remote telnet or SSH needs, chances are you have had a situation where you had to open and manage PuTTY windows. I used to do that and it was sometimes confusing. But recently, just out of luck (my laptop had crashed and I was looking for ways to export/import PuTTY connection profiles), I found this: PuTTY Connection Manager. Its a tabbed version of PuTTY client and provides a solution for managing multiple PuTTY instances. I’ve tried and it works great!

Since I’m endorsing some good tools that I use everyday, here’s others:

- Agent Ransack: A file searching utility for Windows, it has great features for searching by regular expression, within files etc. I haven’t used Windows built-in Search since I found this.

- SketchPath: is an XPath tool that you can use to view XMLs and run XPath queries.

- Sysinternals: a collection of utilities to perform various troubleshooting tasks in Windows. It provides a lot of helpful utilities like Process Explorer (an advanced version of Task Manager), tcpview (shows all open connections with their processes) etc.

- WinMD5: an MD5 utility for Windows.

That’s it for now. I’ll update this post if I can remember more tools to recommend.

Saturday, May 15, 2010

LoadRunner Scripting Challenge – KodakGallery.com (AJAX, JSON, REST & XML)

If you are not familiar with KodakGallery.com, it is an online photo publishing and sharing site offered by Kodak. It offers features to store and share your photos online and to print them or order certain photo products, just as competing sites like flickr, Snapfish, Picasa etc. What they don’t offer is an API to interact with your account online (uploading/downloading pictures etc) without having to go through their website. Flickr (the site I use) has a published API that can be used.

Background:

Last year, KodakGallery changed their storage policy so that users would have to make a minimum purchase from the site based on their storage size in order to continue storing pictures. Even though the cost is minimal for the storage they offer, it was still considered worthwhile to explore other options. And the direct impact to me was that I was tasked with downloading all the pictures that have been stored there for last few years. Manual download was out of the question because of the number of pictures that had accumulated over that duration. And scripting it with Perl or LoadRunner (my weapon of choice) was very viable because of my experience with finding myself in these kind of situations and the thrill of being faced with a challenge and learning something new out of it.

Note: If you navigated here looking for an automated way to download all your pictures from KodakGallery.com, I’m planning to create a Perl script to automate that. You can safely ignore the rest of the post and write me a comment, which will provide me with the motivation to stay up extra hours in the night.

Challenge:

So here’s what we needed to do: Build a script that logs in to KodakGallery.com website and downloads all stored pictures to the local disk categorized by their album folders.

If you’re a LoadRunner user who has to use it in your daily life (atleast while at work) and want to give it a shot, please do so before going through the post. You’ll need a KodakGallery account and a few albums with pictures in them. I promise that it’ll be fun and challenging. I also have to state that scripting/automating interactions with a website has to be done with some caution. You may be using the website’s resources in a way they were not intended to be used. Sean Burke, author of Perl & LWP puts it in a very succinct and precise manner here:

…the data on the Web was put there with the assumption (sometimes implicit, sometimes explicit) that it would be looked at directly in a browser. When you write an LWP program that downloads that data, you are working against that assumption. The trick is to do this in as considerate a way as possible.

Scripting:

With that in mind, let’s get to it. Scripting a live website comes with its own unknowns. The fact that you have no idea about the underlying technologies used to transfer the data and present it to the user provides a great opportunity to learn not only about some new technologies but also about the tool that you use because you may have to use the tool (or some of its features) in ways that you’ve never done before.

The first step in LoadRunner scripting of course is to record the user interaction with the website. For the record, I only have availability to LoadRunner 8.0 that I’m using for scripting here. I’ve heard that newer versions have better support for new web technologies that have come up in recent years. But in last few years at my current role, there’s never been a time that I wasn’t able to deliver a script because of using an older version and I have never felt that I’m missing something.

I created a script using my preferred recording options:

Recording Mode: HTML-based script containing explicit URLs only
Not using correlation
Not recording headers
Not excluding content types
Do not record this content types as a resource:
text/html
text/xml
Record non-HTML elements in current HTML function

I put a comment in the script before every action so that when editing the script later on, I know clearly where each action starts and ends. But after recording, the script in this case was not very intuitive. Some of the actions didn’t correspond to my script comments. For example, in the script where I put the comment for login, I didn’t see any web request that would match a login request or any form-based submission with login parameters. Instead, there was a web_url request to “storagestatus.jsp”. When I put a comment for clicking on an album, there were no steps.

So after scanning through the recorded script and the recording log, I realized that the login and other actions were being submitted through JavaScript and the content-type for these requests was non-HTML. My current recording settings specified that the requests that do not have a content-type of “text/html” or “text/xml” are not to be recorded as a resource, and so those were considered a part of current step and included after the EXTRARES parameter or the original request. Here’s the initial request. The login request is included in the original step (I later found that it has the Content-Type=text/javascript) and the authentication itself is handled through submitting the username and password in an HTTP cookie called “ssoCookies”:


web_add_cookie("ssoCookies=%7B%22email%22%3A%22abc%40example.com%22%2C%22password%22%3A%22test%22%7D; DOMAIN=www.kodakgallery.com");
web_url("welcome.jsp",
"URL=http://www.kodakgallery.com/gallery/welcome.jsp",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTML",
EXTRARES,
... //lots of images
"Url=https://www.kodakgallery.com/gallery/account/login.jsp?&uid=791701608",
ENDITEM,
LAST);

So with that information, I saved the current script and recorded another script with different recording options. I used URL-based script that records all the content (including non-HTML, like css, js, gif etc) in a separate web_url function. That meant the script was longer and a little harder to navigate through but atleast I could scan through the script and figure out what requests are being submitted by their corresponding actions.

Having recorded the actions (login, navigate to an album, download a full-resolution image etc), scanning through the script multiple times, using WebScarab (related post) to scan through the HTTP traffic, I found a lot of interesting things:

  1. Login is handled through JavaScript. A lot of JavaScript. They use MooTools JavaScript libraries for a lot of functionality but for login, it creates the cookie “ssoCookies” and sends a request to “login.jsp:” which (after successful authentication) returns some user information (ssId: probably some sort of unique user identifier, firstname: firstname of the user) in a script (Content-Type: text/javascript) which executes another JavaScript function (“callSignInComplete”), which sends the user to “/gallery/storagestatus.jsp”, which then ultimately redirects (HTTP 302) to “/gallery/creativeapps/photoPicker/albums.jsp”.
    >>Here’s some interesting information on how this lazy JavaScript loading works: http://ajaxpatterns.org/On-Demand_Javascript
  2. The site uses JavaScript and AJAX extensively to request resources and to present the response. For example, the album list (the images it uses to show the links to individual albums) and the pictures within the albums (once you click on an album) are retrieved asynchronously using XMLHttpRequest object.
  3. Complimentary to AJAX, it uses JSON to exchange the data. For example, the request for the list of albums returns a JSON response with the album list and the details. Here’s the HTTP request headers for the album list and as you can see (x-request and x-requested-with are custom HTTP headers used by the app):
    "GET /site/rest/v1.0/albumList HTTP/1.1\r\n"
    "Accept-Language:
    en-us\r\n"
    "Accept-Encoding: gzip, deflate\r\n"
    "Connection:
    Keep-Alive\r\n"
    "Accept: application/json\r\n"
    "x-request:
    JSON\r\n"
    "x-requested-with: XMLHttpRequest\r\n"


    And here’s a part of json response:

    {"AlbumList":{"TOS":{"storageSize":"11111111","storageStartDate":
    "2010-05-05T08:54:54.676-07:00","tosTotalTransactionsAmt":"0",
    "tosStatus":"4","tosComplianceDate":"2010-08-27T00:00:00.000-07:00",
    "warningZone":"false"},"Album":[{"id":"12355"...

  4. It implements the services using REST architecture. Representational State Transfer (REST) is an architectural style to expose services on the web and you can read more about it online…but to a LoadRunner scripter, XML services exposed RESTfully are no different than any other XML based service over HTTP. Some more information about REST:
  5. It includes some tracing cookies and requests to third-party sites that can be safely ignored and commented out.

So with all that information, it was fairly easy to visualize the script’s high-level steps:

  1. Navigate to home page & login.
  2. Send a REST-style request to get the list of Albums (name, URI etc)
  3. For each Album in the list
    1. Get the name of the Album and create a corresponding folder on the local disk
    2. Send a request to get the list of all photos in the album
    3. For each photo
      1. Send a request to download the image file
      2. save the image file in the album folder
  4. Logout
Step 1:

The 1st step is to navigate to the “welcome.jsp” page which returns the session cookies to be used throughout the session. I deleted all the extra requests in the script for images, css files etc. Next step is to login and to do that, we need to send a request to login.jsp with a random 9-digit number and also the username and password in the “ssoCookies” cookie. All other session cookies are automatically handled by LR.


web_url("welcome.jsp",
"URL=http://www.kodakgallery.com/gallery/welcome.jsp",
...
LAST);

web_add_cookie("ssoCookies=%7B%22email%22%3A%22abc%40example.com%22%2C%22password%22%3A%22test%22%7D; DOMAIN=www.kodakgallery.com");

web_url("login.jsp",
"URL=https://www.kodakgallery.com/gallery/account/login.jsp?&uid={randnum}",
...
LAST);


Step 2:

Once login is successful, we need to get a list of albums that the user has. This is done by sending a GET request to this REST-style URL: “http://www.kodakgallery.com/site/rest/v1.0/albumList” directly. We’ll also need to save the response body in a parameter that we’ll have to parse to get the album details.

web_reg_save_param("albumList", "LB=", "RB=", "Search=Body", LAST);
web_url("albumList",
"URL=http://www.kodakgallery.com/site/rest/v1.0/albumList",
...
LAST);

Now here’s the beauty of how the service has been implemented. When you visit the website through a browser, the actual response to this request is returned in JSON formatted string. But we can actually send the request in such a way that it returns the album list in XML rather than JSON. All we have to do is to not include the “Accept: application/json” header, instead just send “Accept: */*”. And since it’s easier to use LR’s built-in XML functions to parse XML strings, we do exactly that. LR’s web_url() function sends “Accept: */*” by default so we get an XML response with the album list.

Step 3:

So once we have the Album List in XML, I use lr_xml_get_values() to get the id of all the albums in the list.

numAlbums = lr_xml_get_values("Xml={albumList}", "Query=/AlbumList/Album/id","SelectAll=yes", "ValueParam=albumId", LAST); 

It returns the number of matches that match the XPath Query, which is the number of albums the user has. This parameter “albumId” holds all these ids and will be used to get the list of all the photos in an album.

Step 3.1:

Now for each of these albums, we get the id from the parameter “albumId” and then get the name of the album using lr_xml_get_values again and using the id in the XPath. Then we go ahead and create the directory as specified.

    for(j=1;j<=numAlbums;j++){
sprintf(sfx,"{albumId_%d}", j);
lr_save_string(lr_eval_string(sfx), "aid");

lr_xml_get_values("Xml={albumList}", "Query=/AlbumList/Album[id='{aid}']/name","ValueParam=albumName","NotFound=Continue",LAST);

sprintf(dname,"%s\\%s",baseDir,lr_eval_string("{albumName}"));
if (mkdir(dname)) { //works, but need better error handling
lr_output_message("Create directory %s failed", dname);

return -1; }

Step 3.2:

With the album Id, we send another GET request to the URL: “http://www.kodakgallery.com/site/rest/v1.0/album/{aid}”. Just like we did above for the album list, we save the response body which is a list of all the photos in this album.


        //---get album details
web_reg_save_param("albumDetails", "LB=", "RB=", "Search=Body", LAST);
web_url("albumDetails",
"URL=http://www.kodakgallery.com/site/rest/v1.0/album/{aid}",
...
LAST);

numPics = lr_xml_get_values("Xml={albumDetails}", "Query=/Album/pictures/photoUriFullResJpeg","SelectAll=yes", "ValueParam=fullResURI", LAST);

And again, just like we did above, we user lr_xml_get_values() to get the URIs to the full resolution picture. It returns the number of pictures and the URIs in a parameter.

Step 3.3:

Now for each of the pictures, we get the URI to full-resolution image from the parameter (lines 3-4 below). We need to get the filename which is returned in the “Content-Disposition” HTTP header (line 7) and also, we have to save the whole body (binary image data) in a parameter (line 9) that we can later use to store on the local disk.

   1:         //get all photos in the album
   2:         for (i=1;i<=numPics;i++){
   3:             sprintf(sfx,"{fullResURI_%d}", i);
   4:             lr_save_string(lr_eval_string(sfx), "uri");
   5:  
   6:             //save the file name that's part of Content-Disposition header
   7:             web_reg_save_param("filename", "LB=Content-Disposition: attachment;filename=", "RB=\r\n", "Search=Headers", LAST);
   8:             //save the whole HTTP body of request
   9:             web_reg_save_param("body", "LB=", "RB=", "Search=Body", LAST);
  10:  
  11:             web_url("FS",
  12:                 "URL={uri}",
  13:                 "TargetFrame=",
  14:                 "Resource=1",
  15:                 "RecContentType=image/jpeg",
  16:                 "Referer=",
  17:             LAST);
  18:  
  19:             lr_eval_string_ext("{body}",strlen("{body}"), &buf, &prmLen, 0, 0, -1);


Then we send a GET request to the URI which returns us the full-resolution image. But since this is a dynamically generated response (“Transfer-Encoding: chunked”), the server doesn’t return the size of the file in the Content-Length HTTP header which we could have used to write the contents in a file. Instead, we have to use lr_eval_string_ext() (line 19) to save the value in a buffer and to get the length of the buffer.

Now we have everything to save the final file: name, size and its contents. We use standard C file handling functions for that and finally free the memory by using lr_eval_string_ext_free().
            sprintf(fname, "%s\\%s", dname, lr_eval_string("{filename}"));
if ((file = fopen(fname, "wb" )) == NULL) {
lr_output_message("Unable to create %s", fname);
return -1;
}
fwrite(buf,prmLen, 1, file);
fclose(file);
lr_eval_string_ext_free(&buf);


The code then loops to save each image in each album locally.

Step 4:

The last step is to logout which is just another request to logout.jsp with a random number. It goes through similar steps as the login and finally redirects back to the home page.

Epilogue:

This was a great challenge and if you actually made it this far, I hope you enjoyed reading it and trying it yourself. I got to learn a lot from this and I hope you do too. Specifically, I learned about REST, JSON and about JavaScript lazy loading as well as HTTP chunked transfer. I also learned a little bit more about looking into LoadRunner recording logs, saving the HTTP response in a file and the XML functions in LR.

As I noted earlier, if you went through all this just looking for an automated way to download your pictures from KodakGallery, please leave me a comment and I’ll work on a Perl script to automate that.

Wednesday, March 3, 2010

LoadRunner, Memory Violations, lr_eval_string_ext and Pointers (ANSI C Style)

I think it’ll be quite accurate to say that an average programmer like me is daunted when first faced with the concept of pointers and memory management in C. During the initial programming years (much of which inevitably had to be in C), I tried my best to avoid using pointers in my code. Whether it be using character arrays with pre-defined size (who cares if it takes much more memory than is needed) or some other “nifty” trick… I thought I could get away as long as I can compile and run just “this” program. But I had to face it during a networking class in school when I worked on a peer-to-peer file sharing project and one of my classmates convinced me to “do it right” and pay heed to the requirement of the program being able to work with other students’ code.

So after much head-scratching and soul-searching, I begrudgingly revisited the concepts and began (or so I thought) to grasp the idea of address spaces. l-values vs. r-values and pointers. It all seemed to make sense and fit in place like a jigsaw puzzle finally coming together. And not before long, with some encouragement and confidence building, I rewrote the program using pointers instead of pre-allocated char arrays and making it as compatible as possible. But if it was supposed to make me happy and content, it didn’t last long and whatever feeling of competence I felt was shattered to bits when I compiled the program for the first time and got compile time errors that didn’t make any sense at all. It was even worse and torturous when after a few cycles of modifying and debugging, it finally compiled beautifully (oh the elation…) and then the first time I ran it, it threw a slew of memory violation errors and crashed as beautifully as it compiled.

Years have passed since then…some of which were spent in writing code in other languages (it doesn’t have pointers? I’ll take 10). And some others in trying to come to terms with how even after understanding the concepts if I now have to write a C program and I decide to (or have to) use pointers, the error messages still baffle the heck out of me. Brief moments of competence have existed…when after writing, re-writing, debugging, debugging again…debugging a few more times, I was finally able to turn out a decent piece of code that could accomplish what it was supposed to in a relatively efficient manner.

Background:

So how does all this relate to the subject of this post? A few days ago, I was writing a LoadRunner script for a web application which had an inquiry page that submitted a request. Depending on the data submitted, the request returned either a response page with the final result or a set of intermediate questions that needed to be answered. After submitting the answers, it again returned either another set of questions or the final result. Once the answers were submitted the second time, it returned the final result page. The number of questions returned was not constant, however 8 questions was the maximum. So a part of scenario logic was this:

a. Submit the initial inquiry
b. If Question Set A is returned, determine the number of questions, construct an answer string and submit
c. If Question Set B is returned, determine the number of questions, construct an answer string and submit
d. Final response

Problem:

Since the questions returned were in a select box, it was easy to find the left and right boundaries and use web_reg_save_param with "Ord=All". In this case, since the questions were in the form:

<SELECT NAME="Answer2" SIZE="5">
  <OPTION value="0"> ABC</OPTION><br>
  <OPTION value="1"> DEF</OPTION><br>
  <OPTION value="2"> MTG</OPTION><br>
  <OPTION value="3"> SVG</OPTION><br>
  <OPTION value="4">NONE</OPTION><br>
</SELECT>

it will be:


web_reg_save_param ("suffix", "LB=<SELECT NAME=\"Answer", "RB=\" SIZE", 
"Ord=All","NOTFOUND=Warning", LAST);

However, it wasn't as easy as determining the number of matches from {suffix_count} and looping to create a custom answer string. Each question had a relative order from 1 – 8 like the one above (the numeral after “Answer”) and the answer string had to be constructed based on that. The challenge was that the number of questions was variable and the order didn’t always start with 1 and go 1, 2, 3…and so on. So if 4 questions were returned, they could be labeled Answer2, Answer3, Answer5, Answer6 and based on this, the answer string would be answer2=2&answer3=2&answer5=2&answer6=2 (it didn’t matter if the questions were answered correctly, so a constant 2 would do). If I submitted answer1=2… here, it would return an error saying “Invalid Response” or something like that.

Solution:

So what I had to do was to save that suffix number in a temp variable and construct the answer string by concatenating it together. So, something like:


c = atoi(lr_eval_string("{suffix_count}"));
if(c>0){
  for(i=1;i<=c;i++){
    strcat(answerString, "Answer");
    sprintf(sfx, "{suffix_%d}", i);
    strcat(answerString, lr_eval_string(sfx));
    strcat(answerString, "=2&");
  }
  lr_save_string(answerString, "aString");
  ...

And then I would create a web_custom_request and submit it:


web_custom_request("AnswerSetA",
  …
  …
  …
  "Body={aString}submit1.x=61&submit1.y=27",
"LAST");

My first instinct, as I’ve mentioned earlier was to use character arrays for both answerString and sfx.


char answerString[256], sfx[10];

I figured that the chances of the string being longer than 256 chars was remote so I was safe. And it worked fine when I ran it in VuGen. But when I ran the load scenario, all the users belonging to this script’s group failed exactly after 4th or 5th iteration. The error was something I hadn’t seen before:

Action.c(162): Error (-17991): Failed to add item to mfifo data structure.

on the line with lr_eval_string. I searched online and came across this link (http://www.sqaforums.com/showflat.php?Number=420958) which suggested using lr_eval_string_ext instead of lr_eval_string to free memory earlier. The help on lr_eval_string also mentions:

Note: lr_eval_string allocates memory internally. The memory is freed at the end of each iteration. If you evaluate a parameter or parameters in a loop, conserve memory by not using lr_eval_string . Instead, use lr_eval_string_ext and free the memory in each loop iteration with lr_eval_string_ext_free.

I changed the code to:


c = atoi(lr_eval_string("{suffix_count}"));
if(c>0){
  for(i=1;i<=c;i++){
    strcat(answerString, "Answer");
    sprintf(sfx, "{suffix_%d}", i);
    lr_eval_string_ext(sfx,strlen(sfx), &sfx1, &prmLen, 0, 0, -1);
    strcat(answerString, sfx1);
    strcat(answerString, "=2&");
    lr_eval_string_ext_free(&sfx1);
  }
  lr_save_string(answerString, "aString");
  ...

but to my disappointment, it still threw an error when executing in Controller after 4th iteration. The good part was that the error message was familiar, the bad part was that it was a memory violation exception:

Action.c(168): Error: C interpreter run time error: Action.c (168): Error -- memory violation : Exception ACCESS_VIOLATION

For some reason, I felt that all the years of avoiding or trying to avoid using pointers obligated me to get to the root of the issue this time and fix it instead of coming up with a workaround. The actual issue however turned out to be something else and I’ll come back to it later. First order of the day…use char pointers instead of static arrays to manage the strings.

To start:


char *answerString; //instead of char answerString[256]
char sfx[10]; //this still can be an array

Next step was to figure out how much memory will I need to allocate based on the number of questions returned and then allocate it. This was done using malloc:


//we need AnswerX=2& times the number of questions, + 1 for '\0' 
if((answerString = (char *)malloc(c * 10 * sizeof(char) + 1)) == NULL){
  lr_output_message("Insufficient Memory!!");
  return -1;
}

Also, we have to initialize it because there may be some garbage in the allocated space that may hinder the proper functioning of strcat:


*answerString = '\0';

Now we have something very similar to a brand new char array, but only of the exact size that we need. Next, we create the string exactly as above, and I used lr_eval_string instead of lr_eval_string_ext because I honestly didn’t think that was the issue. After creating the string, I null-terminated it.


for(i=1;i<=c;i++){ 
  strcat(answerString, "Answer"); 
  sprintf(sfx, "{suffix_%d}", i); 
  strcat(answerString, lr_eval_string(sfx)); 
  strcat(answerString, "=2&"); 
} 
answerString[c * 10 * sizeof(char)] = '\0'; 
lr_save_string(answerString, "aString"); 
free(answerString); 

And the best part, after saving the string in a parameter, I free the associated memory and relish my guilt-free existence (at least in terms of this script). The script worked like a charm, not only through VuGen but multiple iterations through the scenario in Controller.

So what was the issue with using character array: the issue was not that LR agents were running out of memory because I had used 256 bytes when I actually only needed less than that. The issue was that I was not emptying the array before using it. I had declared it within the Action itself:


Action()
{
  int i,c;
  char answerString[256], sfx[10];

and I wrongly assumed that LoadRunner throws away variables from previous iteration and initializes brand new variables in every new iteration. Instead, what happened as in this case when I used strcat was that it was concatenating the new answer string from this iteration to whatever was left from the previous iteration. So after a few iterations, it ran out of the pre-allocated 256 bytes of space and threw the memory violation exception. I could’ve continued to use char array (and I’m glad I didn’t) by just re-initializing it in every iteration.

So lesson learnt. Hopefully all this helps somebody not make the same mistakes I made. I certainly won’t and I will also be less hesitant in using pointers. Even though I’m pretty sure this is not the last I’ve seen of memory violation exceptions, I can say that I’ll be ready to learn something new next time that happens.

By the way…that peer-to-peer file sharing application, I finally was able to compile it and make it work using a mix of pointers and character arrays. It worked great and I felt satisfied when I completed it. But of course when I was demonstrating it to the TA, it didn’t function as expected and I later found out that it was because I had forgotten to null-terminate a string.

Thursday, January 14, 2010

Parrot AR.Drone – iPhone controlled flying experience

 

A colleague of mine forwarded me this and I was immediately impressed: AR.Drone is a iPhone/iPod Touch controlled (via wi-fi) helicopter that you can not only just fly around but also play augmented reality games with.

Check out the video below and others on YouTube.

Wednesday, January 6, 2010

2 new blogs to follow

There are 2 new technology/technical blogs that I’m following.

- The Daily WTF (http://thedailywtf.com/): contains some really funny real-life situations, some of them too ridiculous to not make you go wtf?

- Digital Inspiration (http://www.labnol.org/): informative posts about new and interesting technologies/applications etc.

Friday, September 25, 2009

QTP Automation: A Generic Function to perform mouse click

For all the automation testing I’ve been doing in recent months, I’ve built a set of function libraries in QTP that contain functions doing a lot of different things from clicking on a button, validating properties of different web objects to parsing data in excel files. One of these libraries contains all the WebBrowser related functions and one of those is BrowserButtonClick. It takes the title of the browser and name/index of the button as input and does the obvious – clicks on the button within the specified browser. It does all that through descriptive programming, not needing to have any objects in the GUI repository and gives me the advantage of reusing this library framework in different projects/applications. This has allowed me to automate functional tests of different applications very quickly.

But this post is not about that function or about the framework. I’ll write about that later - how it all fits together to provide an automation framework that can speed up functional automation. In this post, I want to share another QTP VBScript function that I wrote to perform a mouse click on any web object. This function forms the basis of descriptive programming that I’ve used in my libraries and provides greater flexibility instead of tying it down to a specific object type.

Like I described, the BrowserButtonClick takes the name of the button as parameter. This works fine as long as the button object has the name property defined (which most of them do) and I know it when creating the tests. For one of the application page however, there were 2 buttons with no name specified but with “html id” property. This led me to come up with another function that will take the property names (“html id” or “name” etc) and property values as input parameters, search for a button object with specified properties and perform the action (left button click) on that. But thinking about it a little more, since the class of the object (“micclass”) is also another property of the object, it doesn’t have to be restricted to a “WebButton”. So the end result was this generic function that takes the list of appropriately delimited properties and values, finds the object and performs a left-click on the object. The input parameter is in the format “propName1=propValue1;propName2=propValue2…”. For example, “micclass=WebButton;html id=Search”. This way, it can be used to perform a click on any object and similar functions can be used to perform any action on any GUI object.

Here’s the function.

Part 1: Function Definition
' Function BrowserObjectClick
' ------------------
' Perform a mouse click on an object within the specified Browser
' Parameter: browserTitle - Title of the browser
' Parameter: props - a list of name-value pairs of properties and their values, semi-colon delimted. ("propName1=propValue1;propName2=propValue2…”)
' Parameter: objIndex - the index (0-based) of the object that needs to be clicked (if multiple objects match)
'@Description Perform a mouse click on the object that matches specified properties within the specified Browser. Returns 0 is successful, -1 if no object is found or the object is disabled
'@Documentation Perform a mouse click on object that matches <props> properties within <browserTitle> browser
Public Function BrowserObjectClick(ByVal browserTitle, ByVal props, ByVal objIndex)

As I already explained above, the function performs a mouse click on any object that matches the specified properties within the specified browser. The browser title needs to be provided as input parameter and so do the properties, which are name-value pairs delimited by semi-colon. For example, to find a WebButton object with name “Search”, the input will be “micclass=WebButton;name=Search”. For a link with outertext property “Log Off, the input will be “micclass=Link;outertext=Log Off”. If multiple objects match the identification properties, objIndex (0-based) will specify which one of those objects is clicked.

Part 2: Getting the browser objects
    On Error Resume Next
If (props <> "") Then
indx = CInt(objIndex)
If (Err <> 0 Or indx < 0)Then
indx = 0
Err.Clear
End If

'Get the browser objects
Set desc = Description.Create()
desc("micclass").Value = "Browser"
Set bObjs = Desktop.ChildObjects(desc)

For i = 0 To bObjs.Count -1
If Instr(1, bObjs(i).GetROProperty("title"), browserTitle, 1) > 0 Then

First we make sure that the properties are not blank in the input parameters. If they are blank, there’s not much we can do so we just exit out of the function. Next thing to do is to make sure the objIndex is an integer >=0. In case it isn’t, we take the default value of 0, which means that the first object matching the properties will be clicked. Then we get all existing browser objects and iterate through them until we find the one with matching title. If none are found, we’ll exit out with appropriate return values. If multiple browsers are found, we’ll use the 1st one that matches the title. In the applications I’ve automated so far, I haven’t come across a case where multiple browsers match the title…but if needed (in case the application pops up multiple windows, for example), the function can be easily modified to handle that.

Part 3: Getting the Object and performing the Action

                desc("micclass").Value = "Page"
Set pObjs = bObjs(i).ChildObjects(desc)
'Make sure you got one
If (pObjs.Count > 0) Then
'load the properties in an array
Dim propArray : propArray = Split(props,";",-1,vbTextCompare)
Dim prop
Dim propsDict 'As Scripting.Dictionary
Set propsDict = CreateObject("Scripting.Dictionary")
For Each prop in propArray
propsDict.Add Split(prop,"=",-1,vbTextCompare)(0), Split(prop,"=",-1,vbTextCompare)(1)
Next

'Get the Object
Dim j, pKeys : pKeys = propsDict.Keys
For j = 0 to propsDict.Count-1
desc(pKeys(j)).Value = propsDict.Item(pKeys(j))
Next
Set propsDict = Nothing
Set objs = pObjs(0).ChildObjects(desc)

So once we find the browser with specified title, we get its “Page” child objects since all other objects are child objects of the page object. We split the properties that we got as input by the semi-colon “;” and load it in an array. At this point, the array is populated with the name value pairs of the properties delimted by an equals sign “=”. I’m not doing much error handling here because I trust the input provided will be appropriately delimited.

Next, we split each of the array values by an equals sign “=” and load it in a “Scripting.Dictionary” object as key-value pairs, where the key is the name of the property. We create a Description object, load all the properties in it and find all matching child objects.

Part 4: Performing action on the object

                    If (objs.Count > indx) Then
'Execute the event
If (objs(indx).GetROProperty("disabled")) Then
BrowserObjectClick = -1
Else
objs(indx).Click
BrowserObjectClick = 0
End If
'Free up the objects
Set desc = Nothing
Set bObjs = Nothing
Set pObjs = Nothing
Set objs = Nothing
Exit Function
End If

Now that we have a collection of all objects that match the properties, we’ll make sure that the index is not more than the number of objects returned, that it is not disabled and then perform the click action. If it is disabled, we’ll return a –1 so that caller can handle it appropriately. If not, we perform the mouse click by calling the click method of the object and return a 0. Finally, we do some cleanup and exit function.

PostScript

This function provides a generic method to implement a mouse-click on any object using descriptive programming and is particularly useful where the standard properties of an object (name) are not available. It can be further extended to implement any action, not just clicks and provide other functionality as well.

If you have any questions or suggestions to improve or simplify the function, let me know.

Friday, August 14, 2009

Real Life Testing Scenario – Bank ATMs

I think a lot of real life defects you come across would have to do with Bank ATMs. Maybe because of complexity of coding the human-machine interface logic, the plethora of usage scenarios and/or real-time nature of the transactions that it is really hard to make a defect free (or almost) ATM machine.

Yesterday I faced one such scenario when trying to deposit some cash in an ATM machine. This was one of those BoFA ATMs that don’t need an envelope to deposit. You just put the cash in a slot in the ATM and it does the rest. It scans and counts the bill and tells you the total amount deposited.

So as I put all the cash in the slot, it closed the slot door and scanned and counted all the bills. But after it was done, It gave me a message on the screen that some of the bills couldn’t be accepted and opened the slot door for me to take the bills out. There was only 1 bill there and all others were accepted. I took out the one bill it couldn’t accept and it closed the slot door and showed the amount that it had accepted correctly. Pretty neat…so far.

On the same screen, it asked whether I wanted to add more money to the deposit. I pressed “Cash” and it opened the slot door again. After pressing and straightening the bill it had rejected previously. I put that in the slot. It didn’t accept the bill this time either. It gave me the error message and opened the slot door. I took the bill out and it closed the door. But…the error screen didn’t go away. It continued beeping and complaining that it couldn’t accept the bill even after it had closed the slot door. There were no options available…cancel the transaction, return the card…nothing. Just the error message and no buttons to select. At this point, my card was inside the ATM and so was the cash and I had no proof of the deposit! I didn’t have the customer service number because it is printed on the back of the card.

Well, after fruitlessly pressing random buttons including the cancel button and including keeping the cancel button pressed for sometime, I gave up. I approached the lady at next ATM slowly lest she thought I’m going to mug her. But she probably saw the ordeal I was going through and gave me the customer service number from the back of her card. And after spending about 40 minutes on the phone during which I faced numerous call transfers and 1 disconnect and had to talk to the amazingly annoying automated voice response system, I got to talk to a real person who seemed to understand what I was going through and gave me a temporary credit pending research. The previous card was rejected and a new card will be sent.

So…here are the steps:
1. Deposit Card in the ATM slot and go through entering the PIN etc.
2. Select “Deposit”…and “Cash”
3. Once the cash slot door opens, enter several bills at least 1 of which will definitely be rejected.
4. After it complains about the bills, take the bill(s) out.
5. Once the slot door closes and it counts and shows the amount, press the option to add more money to the current deposit.
6. After the slot door opens, add the rejected bill(s)
7. After it shows the error screen again and opens the slot door, take out the rejected bill.

The expected result at this point is for the machine to detect that I have taken the rejected bill out, close the slot door and continue with the amount that was previously determined. It should show me the options again that were shown at Step 5 and let me choose whether I want to try adding more cash again or just go ahead with whatever amount that has been added already. No matter how many times I add previously rejected bills, it should either accept the bills or reject them. This seems to be a normal usage scenario to me, which should’ve been tested thoroughly. This kind of scenario is very likely to happen and it’s not something a user would have to try real hard to go through. I honestly doubt that this issue can be replicated again by following the exact same steps. There has to be some other variable which caused the machine to malfunction.

If I had to test the scenario, there are several things I would want to know:
Firstly, how does the bill scanning logic works. How is it decided whether a bill should be accepted or rejected. Once I know that, I can test with several combinations of different amounts, with different combinations of good and bad bills and different degree of bad quality bills. Maybe also with junk (coins? fake bills? other paper? leaves? lint?)instead of bills!
Secondly, how does it detect whether the bills have been added or removed from the slot to close the slot door. Based on this, I can test with different number of bills to validate if it detects the change.
There are a lot of other exception scenarios also that come to mind…for example, what if somebody props up the slot door open by jamming something through the door? What if somebody continues to add rejected bills 5 times? 10 times? 100 times? (of course, not all the scenarios are realistic so the likelihood of the scenario would have to be weighed against its criticality).

It seems that in this case, it did recognize that I had taken the rejected bills out because it closed the slot door. This is assuming that it closes the slot door only after it detects that the bills have been removed. If it closes the door after a fixed time (I hope not), then this statement is invalid. Anyways, assuming it detected the change, it seems to have failed to continue to the next step which is to carry on with the transaction and provide the options to the user to choose what to do next. This is exactly where I would look to identify the problem by running various scenarios or a combination of scenarios.