Writing Fiddler Web Test Plugins

My last blog post discussed changes made to fiddler which added enhanced support for saving web tests. This post will go into more detail about the changes and show you how to write your own custom code which can modify the way a web test is saved.

The main change we made for saving web tests was adding a plug-in architecture which is very similar to Web Test Plug-ins and Web Test Request plug-ins. The new architecture provides you with a hook into the web test save code so that you can modify what is written to the web test. Each of the new filter type features listed in my previous post were written by writing FiddlerWebTest plug-ins. For example, the FilterByExtension plugin loops through each request that would be written to the web test and flags certain requests to not be written based on certain extensions.

Let’s walk through the process of writing a new plugin. We will write a plugin which will automatically correlate dynamic fields associated with a SQL Server Reporting services site. This is the same reporting site I blogged about in this post: Debugging a Web Test. There are 2 dynamic fields which always need to be correlated for the reporting site that I am testing. They are the ExecutionID and the ControlID. By correlated, I mean that we need to the following:

1. Add an extraction rule to the first request to pull out the values for these fields for the current session

2. Then each time a subsequent request uses these parameters, bind them to the values extracted in the first step.

Since I know that I need to fix up every test I write, wouldn’t it be nice to write a plug-in which will automatically do this for me at save time. Let’s walk through this process.

1. First install the new version of fiddler if you have not done so already: https://www.fiddler2.com/dl/Fiddler2Setup.exe

2. Open Visual Studio and create a library project.

3. Add Fiddler.exe as a reference to the library project. This is typically installed in the following location: C:\Program Files\Fiddler2

4. Now we are ready to start writing our plugin. Add a class to the project and give it a name indicating what the plugin will do. I will call mine ReportingServicesPlugin.

5. Add a using statement to the plugin for Fiddler.WebTesting and Fiddler;

6. Have your class implement the IFiddlerWebTestPlugin interface

7. After making these changes you class should look like the following:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using Fiddler;

using Fiddler.WebTesting;

namespace FiddlerPluginExample

{

    public class ReportingServicesPlugin : IFiddlerWebTestPlugin

    {

        #region IFiddlerWebTestPlugin Members

        public void PreWebTestSave(object sender, PreWebTestSaveEventArgs e)

        {

           

        }

   #endregion

    }

}

As you can see the IFiddlerWebTestPlugin interface has one method that needs to be implemented, PreWebTestSave. This method will be called prior to the test being saved and the event args passed in has a reference to the fiddler sessions being saved.

8. Now we need to figure out what our plugin needs to do. This plugin needs to do the following:

a. Figure out which request has the ExecutionID and ControlID parameters.

b. Add extraction rules to this request for these parameters

c. Then scan remaining requests and replace the hard coded values with the extracted value.

9. So let’s record the script using fiddler and see what we have. After recording the script, Fiddler has the following:

 

 

10. As you can see from the highlighted section on the right, I found the 2 parameters that I am looking for. How does this translate to the plugin. We can simply scan the responses looking for these values in the response. Once we find the correct request, we can add extraction rules. After adding this code, our class would look like the following:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using Fiddler.WebTesting;

using Fiddler;

namespace FiddlerPluginExample

{

    public class ReportingServicesPlugin : IFiddlerWebTestPlugin

    {

        #region IFiddlerWebTestPlugin Members

        public void PreWebTestSave(object sender, PreWebTestSaveEventArgs e)

        {

            //loop through each request and try to find the ExecutionID

            //and ControlID parameters

            for (int i = 0; i < e.FiddlerWebTest.Sessions.Count; i++)

      {

                //Get the WebTestSession which is a wrapper around the

                //Fiddler Session Object

                WebTestSession session = e.FiddlerWebTest.Sessions[i];

                //If the fiddler session is not null and has a response

                //then let's search the response for ControlID and ExecutionID

                if (session.FiddlerSession != null

                    && session.FiddlerSession.responseBodyBytes != null)

                {

                    //get the response body

                    session.FiddlerSession.utilDecodeResponse();

                    string responseString = CONFIG.oHeaderEncoding.GetString(session.FiddlerSession.responseBodyBytes).Trim();

                    //Look for our 2 parameters

                    if (responseString.Contains("ExecutionID=")

                        && responseString.Contains("ControlID="))

                    {

                        //we have found the correct request,

                        //now let's add a ExtractionRule for each parameter

                    }

                }

            }

        #endregion

        }

    }

}

 

11. The next thing we need to do is add an extraction rule for each parameter. I am going to add an ExtractText rule. This code will go into the last if in the code above. This code looks like the following. You can see I call a method to create an instance of the rule and then add the rule to the extraction rule collection for the session.:

//first the ExecutionID                   

session.ExtractionRules.Add(CreateExtractionRule("ExecutionID"));

//Now the ControlID                       

session.ExtractionRules.Add(CreateExtractionRule("ControlID"));

12. I added a method called CreateExtractionRule which looks like the following. :

        private static ExtractionRule CreateExtractionRule(string Param)

        {

            //create the rule

            ExtractionRule rule = new ExtractionRule(ExtractionRule.ExtractTextClassName);

            rule.ContextParameterName = Param;

            //add the rule properties

            rule.Properties.Add(new RuleProperty(ExtractionRule.StartsWith, Param + "="));

            rule.Properties.Add(new RuleProperty(ExtractionRule.EndsWith, "&"));

            rule.Properties.Add(new RuleProperty(ExtractionRule.IgnoreCase, "True"));

            rule.Properties.Add(new RuleProperty(ExtractionRule.UseRegularExpression, "False"));

            rule.Properties.Add(new RuleProperty(ExtractionRule.Required, "True"));

            rule.Properties.Add(new RuleProperty(ExtractionRule.Index, "0"));

            return rule;

        }

13. Now that we have added the rule, we need to loop through the remaining requests looking for query string whose names are ExecutionID or ControlID. If we find one, then we need to replace the hard coded value with the value from the extraction rule. This looks like the following:

if (foundParameters && session.FiddlerSession != null)

{

    foreach (QueryStringParameter qsp in session.RequestQueryParams)

    {

        if (qsp.Name.Equals("ExecutionID"))

       {

          qsp.Value = "{{ExecutionID}}";

       }

        else if (qsp.Name.Equals("ControlID"))

        {

  qsp.Value = "{{ControlID}}";

        }

  }

 }

14. Now compile and if it builds, then the plug-in is ready for use.

15. Fiddler looks in the following location for classes that implement the IFiddlerWebTestPlugin interface: c:\My Documents\Fiddler2\Scripts\vswebtest

16. Copy your dll to this location.

17. Now we are ready to save the test. First let’s save the test without using this new plug-in. When we do that, the output is the following. Notice there are no extraction rules and the parameters are still hard coded:

 

 

18. Now let’s use the new plug-in. The first thing to notice is that the new plug-in appears in the Select Plugin dialog:

 

 

19. Open the saved test in VS and it will look like the following. Notice that the test already has the correct extraction rules and that the query string parameters are set to pull from the extracted value instead of being hardcoded. This is test is ready to run without any modifications:

 

 

 

 

Hopefully this example shows you how you can modify the way a web test is saved so that you can avoid having to do the same fix up tasks for all of the web tests for your site.

 

Here is the full code example:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using Fiddler.WebTesting;

using Fiddler;

namespace FiddlerPluginExample

{

    public class ReportingServicesPlugin : IFiddlerWebTestPlugin

    {

        #region IFiddlerWebTestPlugin Members

        public void PreWebTestSave(object sender, PreWebTestSaveEventArgs e)

        {

            //loop through each request and try to find the ExecutionID

            //and ControlID parameters

            bool foundParameters = false;

            for (int i = 0; i < e.FiddlerWebTest.Sessions.Count; i++)

            {

                //Get the WebTestSession which is a wrapper around the

                //Fiddler Session Object

                WebTestSession session = e.FiddlerWebTest.Sessions[i];

                //If the fiddler session is not null and has a response

                //then let's search the response for ControlID and ExecutionID

                if (!foundParameters

                    && session.FiddlerSession != null

                    && session.FiddlerSession.responseBodyBytes != null)

                {

                    //get the response body

                    session.FiddlerSession.utilDecodeResponse();

                    string responseString = CONFIG.oHeaderEncoding.GetString(session.FiddlerSession.responseBodyBytes).Trim();

                    //Look for our 2 parameters

                    if (responseString.Contains("ExecutionID=")

                        && responseString.Contains("ControlID="))

                    {

                        //we have found the correct request,

                        //now let's add a ExtractionRule for each parameter

                        foundParameters = true;

                        //first the ExecutionID

                        session.ExtractionRules.Add(CreateExtractionRule("ExecutionID"));

                        //Now the ControlID

                        session.ExtractionRules.Add(CreateExtractionRule("ControlID"));

                    }

                }

                //if we already found the request we needed to add the rule to

                //lets look to see if any parameters need to be bound to the value

                //from the rule;

                if (foundParameters

                    && session.FiddlerSession != null)

                {

                    foreach (QueryStringParameter qsp in session.RequestQueryParams)

                    {

  if (qsp.Name.Equals("ExecutionID"))

                        {

                            qsp.Value = "{{ExecutionID}}";

                        }

                        else if (qsp.Name.Equals("ControlID"))

                        {

                            qsp.Value = "{{ControlID}}";

                        }

                    }

                }

            }

        #endregion

        }

        private static ExtractionRule CreateExtractionRule(string Param)

        {

            //create the rule

            ExtractionRule rule = new ExtractionRule(ExtractionRule.ExtractTextClassName);

            rule.ContextParameterName = Param;

            //add the rule properties

            rule.Properties.Add(new RuleProperty(ExtractionRule.StartsWith, Param + "="));

            rule.Properties.Add(new RuleProperty(ExtractionRule.EndsWith, "&"));

            rule.Properties.Add(new RuleProperty(ExtractionRule.IgnoreCase, "True"));

            rule.Properties.Add(new RuleProperty(ExtractionRule.UseRegularExpression, "False"));

            rule.Properties.Add(new RuleProperty(ExtractionRule.Required, "True"));

            rule.Properties.Add(new RuleProperty(ExtractionRule.Index, "0"));

            return rule;

        }

    }

}