Wednesday 20 April 2011

Custom Extraction Rules for Visual Studio Load Tests - Dealing with Dynamic Control Identifiers

I was recently commissioned to create and fix up several Visual Studio 2008 Load Tests for one of my clients. There were some questions raised about the validity and accuracy of the load test results - so I was brought in to do a "Load Test Audit".

I started by creating some data validation scripts which confirmed that data was being updated correctly. Unexpectedly, even though the tests were running without failure, the number of records affected in the database was completely different to the anticipated results.

Upon investigation, I found several issues which caused the Load tests to intermittently or "silently" fail (i.e. tests passed, but exceptions were logged in the backend of the custom application). There were several problems I found. I cover two of them below:

PROBLEM 1:
One of the problems is that one of the buttons in the recorded webtest would sometimes not be correctly triggered. Consequently all subsequent web test requests in that Load Test scenario would fail.

Turns out, there was a link button in one of the ASP.NET Web Parts rendered as part of a GridView - and the id of the control would actually change based on the number of records included in the grid.

Consequently, in most cases there was 1 record in the grid so the "lnkAdd" button was "lnkAdd1". This matched what was recorded in the Visual Studio Webtest. However, the load test would fail when there were a larger number of records as Visual Studio couldn't find the "lnkAddX" button.

To resolve this, the simplest way to dynamically determine the id of the dynamic control was to use a custom extraction rule that sets a context value for use in subsequent requests. Like so:
public class GetAddButtonId : ExtractionRule
    {
        /// 
        /// Add button name. TODO: Add defensive code
        /// 
        /// /// public override void Extract(object sender, ExtractionEventArgs e)
        {
            e.Success = true;
            if (!string.IsNullOrEmpty(e.Response.BodyString))
            {
                Match value = Regex.Match(e.Response.BodyString, @"ctl00\$ContentPlaceHolder1.[^>]*?grdScheduleChangeResults\$.[^>]*?\$lnkAdd");
                if (value.Success)
                {
                    this.ContextParameterName = "AddButtonId";
                    if (!e.WebTest.Context.ContainsKey("AddButtonId"))
                    {
                        e.WebTest.Context.Add("AddButtonId", value.Groups[0].Value);
                    }
                    else
                    {
                        e.WebTest.Context["AddButtonId"] = value.Groups[0].Value;
                    }
                }
            }
        }
    }

I added this Custom Extraction Rule as part of the web test, updated subsequent requests to use the Context value for the control name - and the issues were resolved.

PROBLEM 2:
There were also issues with the Validation rules in the Load Test - as the tests were running without failure, but they were actually just hitting the CustomError.aspx page (this occurs when there's an exception). I had to add a new Web Test Validation rules as below:

public override IEnumerator GetRequestEnumerator()
        {
            if ((this.Context.ValidationLevel >= Microsoft.VisualStudio.TestTools.WebTesting.ValidationLevel.High))
            {
                if (!Context.ContainsKey("IgnoreErrors") || (Context["IgnoreErrors"].ToString() != null && bool.Parse(Context["IgnoreErrors"].ToString())))
                {
                    ValidationRuleFindText validationRule2 = new ValidationRuleFindText();
                    validationRule2.FindText = "Unable to perform operation";
                    validationRule2.IgnoreCase = true;
                    validationRule2.UseRegularExpression = false;
                    validationRule2.PassIfTextFound = false;
                    this.ValidateResponse += new EventHandler(validationRule2.Validate);

                    
                    ValidationRuleFindText ErrorSummary_Validation = new ValidationRuleFindText();
                    ErrorSummary_Validation.FindText = "ErrorSummary";
                    ErrorSummary_Validation.IgnoreCase = true;
                    ErrorSummary_Validation.UseRegularExpression = false;
                    ErrorSummary_Validation.PassIfTextFound = false;
                    this.ValidateResponse += new EventHandler(ErrorSummary_Validation.Validate);

                    //'An open change request already exists

                    //Wasn't detecting custom error page as issue in test.
                    ValidationRuleFindText customError_Validation = new ValidationRuleFindText();
                    customError_Validation.FindText = "CustomError.aspx";
                    customError_Validation.IgnoreCase = true;
                    customError_Validation.UseRegularExpression = false;
                    customError_Validation.PassIfTextFound = false;
                    this.ValidateResponse += new EventHandler(customError_Validation.Validate);

                    ValidationRuleFindText callback = new ValidationRuleFindText();
                    callback.FindText = "Invalid postback or callback argument";
                    callback.IgnoreCase = true;
                    callback.UseRegularExpression = false;
                    callback.PassIfTextFound = false;
                    this.ValidateResponse += new EventHandler(callback.Validate);

                    this.StopOnError = true;
                }
            }
            return null;
        }
Hope this helps someone in the future when they are troubleshooting their Visual Studio Web Tests or Load Tests!

DDK

No comments: