QueryString Correlation: WebTest Plug-in


 


using System;


using System.Collections.Generic;


using System.ComponentModel;


using System.IO;


using System.Text;


using System.Windows.Forms;


using System.Xml;


 


using Microsoft.VisualStudio.TestTools.WebTesting;


using Microsoft.VisualStudio.TestTools.WebTesting.Rules;


 


namespace WhidbeyCorrelation


{


    public class CorrelationPlugin : WebTestPlugin


    {


        public override void PreWebTest(object sender, PreWebTestEventArgs e)


        {


            webtest = new XmlDocument();


            webtest.Load(e.WebTest.Name + ".webtest");


 


            //Wire up a PreRequest handler here in the Test Plugin so we don't


            //need to use multiple plugins


            e.WebTest.PreRequest += new EventHandler<PreRequestEventArgs>(PreRequest);


        }


         


        void PreRequest(object sender, PreRequestEventArgs e)


        {


            if (!IsValidRequestForCorrelation(e))


            {


                curReqIndex++;


                return;


            }


 


            string RequestUrl = e.Request.Url.ToLower();


            RequestUrl = RequestUrl.Substring(RequestUrl.LastIndexOf('/') + 1);


                       


            //Used to store info on embedded querystring parameters


            //that are found during parsing


            Dictionary<string, Dictionary<string, string>> linkQsps =


                new Dictionary<string, Dictionary<string,string>>();


 


            //Search the text of the Last Response for the RequestUrl 


            //and try to parse any querystring parameters out.


            ParseLastResponse(e, linkQsps, RequestUrl);


           


            //Present each querystring parameter value in the current 


            //WebTestRequest that has a different value embedded in the


            //WebTest's LastResponse to the User and ask if they would


            //like to use the embedded value.


            QueryUserWithCorrelations(e, linkQsps, RequestUrl);


                       


            curReqIndex++;


        }


 


 


        public override void PostWebTest(object sender, PostWebTestEventArgs e)


        {


            if (userMadeChanges == true)


            {


                DialogResult res = MessageBox.Show("Would you like to update " +


                    "your Web Test to include extraction rules and bindings " +


                    "to automatically perform these correlations?  If you " +


                    "select yes a backup of this webtest and the updated " +


                    "version of the webtest will be created in " +


                    e.WebTest.Context["$TestDeploymentDir"].ToString(),


                    "Correlation Plugin", MessageBoxButtons.YesNo);


                if (res == DialogResult.Yes)


                {


                    //Remove this Plugin from the webtest


                    XmlNode TestCase = webtest.SelectSingleNode("//TestCase");


                    TestCase.Attributes["TestCaseCallbackClass"].Value = "";


                  


                    File.Copy(e.WebTest.Name + ".webtest", e.WebTest.Name +


                        "_bkp.webtest");


                    webtest.Save(e.WebTest.Name + ".webtest");


                }


            }


        }


 


 


        private bool IsValidRequestForCorrelation(PreRequestEventArgs e)


        {


            //Return if this is the first request


            if (e.WebTest.LastResponse == null)


            {


                return false;


            }


 


            //Handle url's like "http://abcCompany/site/"


            if (e.Request.Url.EndsWith("/"))


            {


                return false;


            }


 


            string RequestUrl = e.Request.Url.Substring(e.Request.Url.LastIndexOf('/') + 1);


 


            //Handle url's like "http://abcCompany/site"


            if (!RequestUrl.Contains("."))


            {


                return false;


            }


 


            return true;


        }


 


 


        private void ParseLastResponse(PreRequestEventArgs e, Dictionary<string,


            Dictionary<string, string>> linkQsps, string RequestUrl)


        {


            string responseText = e.WebTest.LastResponse.BodyString;


            string[] responseLines = responseText.Split('\n');


           


            int urlInstance = 0;


            int position = responseText.ToLower().IndexOf(RequestUrl, 0);


           


            while (position >= 0)


            {


                if (responseText[position + RequestUrl.Length] == '?')


                {


                    position = position + RequestUrl.Length;


                    int startIndex = ++position;


 


                    while (!endOfQuerystring.Contains(responseText[position]))


                    {


                        position++;


                    }


 


                    string querystring = responseText.Substring(startIndex, position - startIndex);


                    string[] name_value = querystring.Split('&');


 


                    //If the querystring parameter names don't match up, continue to the next embedded url


                    bool skipThisUrl = false;


                    if (name_value.Length != e.Request.QueryStringParameters.Count)


                    {


                        skipThisUrl = true;


                    }


                    else


                    {


                        foreach (string n_v in name_value)


                        {


                            if (!e.Request.QueryStringParameters.Contains(n_v.Substring(0,


                                n_v.IndexOf('=')).Replace("amp;", "")))


                            {


                                skipThisUrl = true;


                                break;


                            }


                        }


                    }


 


                    if (skipThisUrl == true)


                    {


                        position = responseText.ToLower().IndexOf(RequestUrl, position);


                        urlInstance++;


                        continue;


                    }


 


 


                    //Continue processing this embedded url one parameter at a time


                    foreach (string n_v in name_value)


                    {


                        string[] tmp = n_v.Split('=');


 


                        if (tmp.Length != 2)


                            continue;


 


                        string name = tmp[0].Replace("amp;", "");


                        string value = tmp[1];


 


                        if (!linkQsps.ContainsKey(name))


                        {


                            linkQsps.Add(name, new Dictionary<string, string>());


                        }


 


                        if (!linkQsps[name].ContainsKey(value))


                        {


                            foreach (QueryStringParameter qsp in e.Request.QueryStringParameters)


                            {


                                if (qsp.Name.ToLower() == name.ToLower() && qsp.Value != value)


                                {


                                    //Figure out the line number and get the line of text in the response


                                    //in which this embedded querystring name/value appears so that it


                                    //can be presented to the user in the dialog later.


                                    string infoForUser = null;


                                    int responseIndex = 0;


 


                                    for (int i = 0; i < responseLines.Length; i++)


                                    {


                                        //If Param1=a and Param1=ab both were to appear in response then


                                        //using the call to Contains below could produce the wrong line


                                        //number and line text, preventing this from happening by checking


                                        //the index.


                                        responseIndex += responseLines[i].Length;


                                        if (responseIndex < startIndex)


                                        {


                                            continue;


                                        }


 


                                        if (responseLines[i].Contains(n_v))


                                        {


                                            //{0}, portion below will not be displayed to the user in the dialog


                                            //it is there to give the code that creates the extraction rules on the


                                            //webtest information it needs.


                                            infoForUser = String.Format("{0},Line {1}: {2}", urlInstance.ToString(),


                                                i.ToString(), responseLines[i]);


                                            break;


                                        }


 


                                    }


 


                                    linkQsps[name].Add(value, infoForUser);


                                    break;


                                }


                            }


                        }


                    }


 


 


                }


                position = responseText.ToLower().IndexOf(RequestUrl, position);


                urlInstance++;


            } //while(position > 0)


 


        }


 


 


        private void QueryUserWithCorrelations(PreRequestEventArgs e, Dictionary<string,


            Dictionary<string, string>> linkQsps, string RequestUrl)


        {


            foreach (QueryStringParameter qsp in e.Request.QueryStringParameters)


            {


                if (linkQsps.ContainsKey(qsp.Name) && linkQsps[qsp.Name].Count > 0)


                {


                    //In cases where there are multiple values embedded in the last response that


                    //differ from the value currently in use by the web test a dialog will be displayed


                    //for each one.  This isn't ideal as far as user interface goes but the reason it is


                    //done this way is that to bring up anything more sophisticated than a MessageBox


                    //would require a multithreaded approach (this code does not run in a STA thread)


                    //and that is beyond the scope of this sample plugin.


                    foreach (string embeddedValue in linkQsps[qsp.Name].Keys)


                    {


 


                        #region Dialog Message Text


                        string msgTxt = "The value of a querystring parameter on the request " +


                            "that is about to execute differs from a querystring parameter that " +


                            "is embedded in the last response text recieved during this web test. " +


                            "Would you like the Correlation Plugin to automatically update this for " +


                            "you?\r\n\r\n";


 


                        string info = linkQsps[qsp.Name][embeddedValue];


 


                        //extract instance from info string


                        int instance = Int32.Parse(info.Substring(0, info.IndexOf(',')));


 


                        //remove instance from info string for presentation to user


                        info = info.Substring(info.IndexOf(',') + 1, info.Length - info.IndexOf(',') - 2);


 


                        msgTxt += String.Format("Url: {0}\r\n\r\nQuerystring Parameter Name: {1}\r\n\r\n" +


                            "Current Value in WebTest: {2}\r\n\r\nValue found in last response from server " +


                            "during this execution: {3}\r\n\r\nWhere Found: {4}\r\n\r\n\r\n",


                            e.Request.Url, qsp.Name, qsp.Value, embeddedValue, info);


 


                        //Additional information is needed in the dialog if there are more than one instances of


                        //the url and querystring parameter with different values from the one currently used


                        if (linkQsps[qsp.Name].Count > 1)


                        {


                            msgTxt += "Note: Multiple instances of this querystring parameter were found with " +


                                "different values in the response text, only select Yes to this dialog if this " +


                                "is the instance you want your test to extract and bind to.  The current instance " +


                                "is the one listed next to \"Value found in last response:\" above and also " +


                                "surrounded by *'s in the list of all instances found.  Next to each instance " +


                                "listed below is the line of code where it was found.\r\n\r\nAll Instances Found:\r\n";


 


                            foreach (string val in linkQsps[qsp.Name].Keys)


                            {


                                //extract the instance data from the entry


                                info = linkQsps[qsp.Name][val];


                                info = linkQsps[qsp.Name][val].Substring(info.IndexOf(','), info.Length - info.IndexOf(','));


                                msgTxt = String.Concat((val == embeddedValue ? "*" + val + "*" : val) + " --> " + info, "\r\n");


                            }


 


                            msgTxt += "\r\n";


                        }


                        #endregion


 


                        DialogResult res = MessageBox.Show(msgTxt, "Correlation Plugin", MessageBoxButtons.YesNo);


 


                        if (res == DialogResult.Yes)


                        {


                            userMadeChanges = true;


 


                            //update value in current execution


                            qsp.Value = embeddedValue;


 


                            //update webtest object with extraction and binding to save off


                            //later if user decides to when the test has completed


                            #region Add the extraction rule


                            //plugin always uses the RawResponseText version of the extraction rule


 


                            XmlElement ExtractionRules = webtest.CreateElement("ExtractionRules");


 


                            XmlElement ExtractionRule = webtest.CreateElement("ExtractionRule");


                            ExtractionRule.Attributes.Append(webtest.CreateAttribute("Classname"));


                            ExtractionRule.Attributes["Classname"].Value =


                                "WhidbeyCorrelation.DynamicQueryStringExtraction_RawResponseText, " +


                                "WhidbeyCorrelation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";


                            ExtractionRule.Attributes.Append(webtest.CreateAttribute("VariableName"));


                            ExtractionRule.Attributes["VariableName"].Value = "DynamicQuerystringExtraction_" +


                                extractionRuleInstance.ToString();


 


                            XmlElement RuleParameters = webtest.CreateElement("RuleParameters");


 


                            XmlElement RuleParameter0 = webtest.CreateElement("RuleParameter");


                            RuleParameter0.Attributes.Append(webtest.CreateAttribute("Name"));


                            RuleParameter0.Attributes["Name"].Value = "Url";


                            RuleParameter0.Attributes.Append(webtest.CreateAttribute("Value"));


                            RuleParameter0.Attributes["Value"].Value = RequestUrl;


 


                            XmlElement RuleParameter1 = webtest.CreateElement("RuleParameter");


                            RuleParameter1.Attributes.Append(webtest.CreateAttribute("Name"));


                            RuleParameter1.Attributes["Name"].Value = "ParameterName";


                            RuleParameter1.Attributes.Append(webtest.CreateAttribute("Value"));


                            RuleParameter1.Attributes["Value"].Value = qsp.Name;


 


                            XmlElement RuleParameter2 = webtest.CreateElement("RuleParameter");


                            RuleParameter2.Attributes.Append(webtest.CreateAttribute("Name"));


                            RuleParameter2.Attributes["Name"].Value = "Instance";


                            RuleParameter2.Attributes.Append(webtest.CreateAttribute("Value"));


                            RuleParameter2.Attributes["Value"].Value = instance.ToString();


 


                            XmlElement RuleParameter3 = webtest.CreateElement("RuleParameter");


                            RuleParameter3.Attributes.Append(webtest.CreateAttribute("Name"));


                            RuleParameter3.Attributes["Name"].Value = "AutomaticCorrelation";


                            RuleParameter3.Attributes.Append(webtest.CreateAttribute("Value"));


                            RuleParameter3.Attributes["Value"].Value = "False";


 


                            XmlElement RuleParameter4 = webtest.CreateElement("RuleParameter");


                            RuleParameter4.Attributes.Append(webtest.CreateAttribute("Name"));


                            RuleParameter4.Attributes["Name"].Value = "CorrelateNextRequestOnly";


                            RuleParameter4.Attributes.Append(webtest.CreateAttribute("Value"));


                            RuleParameter4.Attributes["Value"].Value = "False";


 


                            RuleParameters.AppendChild(RuleParameter0);


                            RuleParameters.AppendChild(RuleParameter1);


                            RuleParameters.AppendChild(RuleParameter2);


                            RuleParameters.AppendChild(RuleParameter3);


                            RuleParameters.AppendChild(RuleParameter4);


                            ExtractionRule.AppendChild(RuleParameters);


                            ExtractionRules.AppendChild(ExtractionRule);


 


                            //Determine if this request has existing extraction rules already, if it does then add our


                            //ExtractionRule element to the existing ExtractionRules element, otherwise add the ExtractionRules


                            //element to the request


                            if (webtest.SelectNodes("//TestCase/Items/Request[" +


                                curReqIndex.ToString() + "]/ExtractionRules").Count > 0)


                            {


                                //Note: xpath uses 1-based indexing so using curReqIndex


                                //actually grabs the last request not the current one


                                XmlNode n = webtest.SelectSingleNode("//TestCase/Items/Request[" +


                                    curReqIndex.ToString() + "]/ExtractionRules");


                                n.AppendChild(ExtractionRule);


                            }


                            else


                            {


                                //Note: xpath uses 1-based indexing so using curReqIndex


                                //actually grabs the last request not the current one


                                XmlNode n = webtest.SelectSingleNode("//TestCase/Items/Request[" +


                                    curReqIndex.ToString() + "]");


                                n.AppendChild(ExtractionRules);


                            }


                            #endregion


 


                            #region Add the querystring binding


                            int curReqXpath = curReqIndex + 1;


                            webtest.SelectSingleNode("//TestCase/Items/Request[" +


                                curReqXpath.ToString() + "]/QueryStringParameters/QueryStringParameter[@Name='" +


                                qsp.Name + "']").Attributes["Value"].Value = "{{DynamicQuerystringExtraction_" +


                                extractionRuleInstance.ToString() + "}}";


 


                            #endregion


                           


                            extractionRuleInstance++;


                            break;


                        }


                    }


                }


            }


        }


       


        XmlDocument webtest;


        int extractionRuleInstance = 0;


        int curReqIndex = 0;


        bool userMadeChanges = false;


 


        //List of characters that would indicate a querystring has ended


        List<char> endOfQuerystring = new List<char>(new char[] { ' ', '\r', '\n', '\'', '\"', '\t' });


    }


}


 

Comments (1)

  1. Overview: While creating WebTests for your site one problem you may encounter is in dealing with dynamic

Skip to main content