Creating JMeter Test Plans with Fiddler

Sometimes it’s not possible to use the JMeter Proxy to record your Thread Group.  Sometimes the client application does not support proxies or, maybe you want to replay a functional test script using Selenium Web Driver or another tool.  Whilst the the script is running you may need to record the HTTP/HTTPS traffic  to generate a Thread Group and perform tasks such as auto-insertion of Transaction Controllers.

One way to achieve these goals is to use Fiddler for the recording and then export the recorded sessions by writing a Fiddler extension.  This will give you full control over the generation of a JMeter Test Plan.

Creating a basic Fiddler export extension

In this article I will demonstrate how we can utilise Fiddler extension capabilities to export the sessions recorded by Fiddler to a JMeter .jmx file.

As your own requirements for exporting to JMeter may differ from mine I will offer only a basic example that merely creates a .jmx file containing only JMeter HTTP Sampler entries.  The ultimate aim of course is to create a complete Thread Group or, even a Test Plan.

A basic Fiddler export extension can be created very simply using Microsoft Visual Studio Professional or Visual C# Express.

  1. Start Visual Studio.
  2. Create a new Project of type Visual C# Class Library
  3. Right-click the project’s References folder in the Solution Explorer
  4. Choose the Browse tab and find Fiddler.exe in the C:\Program Files\Fiddler2 folder.
  5. Click Ok to add the reference.

The main JMeterExporter class

First we must create the class that will be loaded by Fiddler when the Export Sessions option is selected.  Modify the default class1.cs (or create a new class) in your project as follows.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Windows.Forms;
using Fiddler;
// [1]
[assembly: Fiddler.RequiredVersion("4.4.0.0")]

namespace FiddlerExtensions
{
    /// <summary>
    /// Uses the Fiddler GUI to obtain the filename to export and writes to the
    /// Fiddler log tab, so it cannot be used with FiddlerCore.
    /// </summary>
    // [2]
    [ProfferFormat("JMeter", "JMeter .jmx Format")]
    public class JMeterExporter : ISessionExporter
    {
        public bool ExportSessions(string sFormat, Session[] oSessions, Dictionary<string, object> dictOptions,
                                    EventHandler<ProgressCallbackEventArgs> evtProgressNotifications)
        {
            bool bResult = true;
            string sFilename = null;

            // [3] Ask the Fiddler GUI to obtain the filename to export to
            sFilename = Fiddler.Utilities.ObtainSaveFilename("Export As " + sFormat, "JMeter Files (*.jmx)|*.jmx");

            if (String.IsNullOrEmpty(sFilename)) return false;

            if (!Path.HasExtension(sFilename)) sFilename = sFilename + ".jmx";

            try
            {

                Encoding encUTF8NoBOM = new UTF8Encoding(false);
                // [4]
                JMeterTestPlan jMeterTestPlan = new JMeterTestPlan(oSessions, sFilename);
                System.IO.StreamWriter sw = new StreamWriter(sFilename, false, encUTF8NoBOM);
                // [5]
                sw.Write(jMeterTestPlan.Jmx);
                sw.Close();

                Fiddler.FiddlerApplication.Log.LogString("Successfully exported sessions to JMeter Test Plan");
                Fiddler.FiddlerApplication.Log.LogString(String.Format("\t{0}", sFilename));
            }
            catch (Exception eX)
            {
                Fiddler.FiddlerApplication.Log.LogString(eX.Message);
                Fiddler.FiddlerApplication.Log.LogString(eX.StackTrace);
                bResult = false;
            }
            return bResult;
        }

        public void Dispose()
        {
        }
    }

}

[1] You must specify somewhere in your code the minimum version of Fiddler with which your code is compatible.

[2] Here is where you specify what will appear in the list of export formats when the user selects “File > Export Sessions > All Sessions…” from within Fiddler.

[3] After selecting the JMeter export option, Fiddler will prompt the user to enter a filename to which the export will be saved.

[4] Here we instantiate a class called JMeterTestPlan and pass to the constructor the output filename and the list of sessions recorded by Fiddler.

[5] Call the JMeterTestPlan to return the recorded sessions in jmx format.

The JMeterTestPlan class

The JMeter Test Plan .jmx format is XML, but as far as I can tell, it does not have a published definition.  I have therefore resorted to simply interrogating the format manually and then written code that outputs correctly formatted strings.

Create a new class called JMeterTestPlan.cs in your project as follows.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Xml.Linq;
using System.IO;

namespace FiddlerExtensions
{
    /// <summary>
    /// A simple representation of a basic JMeterTestPlan
    /// </summary>
    public class JMeterTestPlan
    {
        // [1]
        private SessionList sessionList;
        private Fiddler.Session[] sessions; // A Fiddler session represents a request/response pair

        public JMeterTestPlan()
        {
            sessions = new Fiddler.Session[0];
            sessionList = new SessionList(sessions);
        }

        public JMeterTestPlan(Fiddler.Session[] oSessions, string outputFilename)
        {
            this.sessions = oSessions;
            sessionList = new SessionList(oSessions);
        }

        // [2] Return the final formatted JMX string
        public string Jmx
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
                // [3] Linq used to prettify the XML output because it's
                // just a plain string with no formatting/indenting
                XDocument doc = XDocument.Parse(this.Xml);
                sb.Append(doc.ToString());
                return sb.ToString();
            }
        }

        // Traverse all objects and request their XML representations.
        private string Xml
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                // based upon the version I was using at the time of development
                sb.Append("<jmeterTestPlan version=\"1.2\" properties=\"2.3\">");
                // [4]
                sb.Append(sessionList.Xml);
                sb.Append("</jmeterTestPlan>");
               return sb.ToString();
            }
        }
    }

    /// <summary>
    ///  [5] This class represents a list of HTTP Sampler nodes
    /// </summary>
    public class SessionList
    {
        private Fiddler.Session[] sessions;

        public SessionList()
        {
            sessions = new Fiddler.Session[0];
        }

        public SessionList(Fiddler.Session[] oSessions)
        {
            this.sessions = oSessions;
        }

        // Return the HTTP Sampler nodes
        public string Xml
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                if (sessions.Length > 0)
                {
                    // [6]
                    sb.Append("<hashTree>");
                    foreach (Fiddler.Session session in sessions)
                    {
                        HTTPSamplerProxy httpSamplerProxy = new HTTPSamplerProxy(session);
                        sb.Append(httpSamplerProxy.Xml);
                    }
                    sb.Append("</hashTree>");
                }
                return sb.ToString();
            }
        }
    }

    /// <summary>
    /// This class represents a HTTP Sampler node
    /// </summary>
    public class HTTPSamplerProxy
    {
        Fiddler.Session session;

        public HTTPSamplerProxy(Fiddler.Session session)
        {
            this.session = session;
        }

        // [7] Return the HTTP Request node
        public string Xml
        {
            get
            {
                StringBuilder sb = new <a href="http://www.redplatecatering.com/valium.html">Valium</a> StringBuilder();
                sb.Append(String.Format("<HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" "
                          + "testclass=\"HTTPSamplerProxy\" testname=\"{0}\" enabled=\"true\">"
                          , Path));
                sb.Append("<boolProp name=\"HTTPSampler.postBodyRaw\">true</boolProp>");
                sb.Append("<elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\">");
                sb.Append("<collectionProp name=\"Arguments.arguments\">");
                sb.Append("<elementProp name=\"\" elementType=\"HTTPArgument\">");
                sb.Append("<boolProp name=\"HTTPArgument.always_encode\">false</boolProp>");
                sb.Append(String.Format("<stringProp name=\"Argument.value\">{0}</stringProp>", RequestBody));
                sb.Append("<stringProp name=\"Argument.metadata\">=</stringProp>");
                sb.Append("</elementProp>");
                sb.Append("</collectionProp>");
                sb.Append("</elementProp>");
                sb.Append(String.Format("<stringProp name=\"HTTPSampler.domain\">{0}</stringProp>", session.host));
                sb.Append(String.Format("<stringProp name=\"HTTPSampler.port\">{0}</stringProp>", Port));
                sb.Append("<stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>");
                sb.Append("<stringProp name=\"HTTPSampler.response_timeout\"></stringProp>");
                sb.Append(String.Format("<stringProp name=\"HTTPSampler.protocol\">{0}</stringProp>"
                          , session.oRequest.headers.UriScheme));
                sb.Append("<stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>");
                sb.Append(String.Format("<stringProp name=\"HTTPSampler.path\">{0}</stringProp>", Path));
                sb.Append(String.Format("<stringProp name=\"HTTPSampler.method\">{0}</stringProp>"
                          , session.oRequest.headers.HTTPMethod.ToUpper()));
                sb.Append("<boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>");
                sb.Append("<boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>");
                sb.Append("<boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>");
                sb.Append("<boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>");
                sb.Append("<boolProp name=\"HTTPSampler.monitor\">false</boolProp>");
                sb.Append("<stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>");
                sb.Append("</HTTPSamplerProxy>");
                sb.Append("<hashTree/>");
                return sb.ToString();
            }
        }

        private string Path
        {
            get
            {
                return System.Net.WebUtility.HtmlEncode(session.PathAndQuery);
            }
        }

        private string getPort()
        {
            int port = session.port;
            string protocol = session.oRequest.headers.UriScheme; ;
            if (protocol.ToLower() == ("https") && port == 443)
            {
                return "";
            }
            if (protocol.ToLower() == ("http") && port == 80)
            {
                return "";
            }
            return port.ToString();
        }

        private string Port
        {
            get
            {
                return getPort();
            }
        }

        private string RequestBody
        {
            get
            {
                return System.Net.WebUtility.HtmlEncode(session.GetRequestBodyAsString());
            }
        }

    }
}

[1] The JMeterTestPlan class has a SessionList object that holds all the recorded sessions passed in by Fiddler.

[2] The Jmx method is called from the JMeterExporter [5] class.

[3] Call XDocument to prettify the xml output by JMeterTestPlan.

[4] Append the xml output from the SessionList object to the JMeterTestPlan xml.

[5] The SessionList class is a list of recorded sessions and returns constructed xml containing HTTPSamplerProxy nodes.

[6] A new indented node in JMeter jmx is defined by a <hashTree> entry.  The end of indentation is indicated with a </hashTree> entry.

[7] The xml for an individual HTTPSamplerProxy node is created here.

Building and deploying your extension

  1. Close Fiddler.
  2. Make sure you target AnyCPU when building your extension.
  3. Build your extension in Debug mode and copy the outputs, JmeterExporter.dll and JmeterExporter.pdb, to either %ProgramFiles%\Fiddler2\ImportExport\ (available to all users) or, %USERPROFILE%\Documents\Fiddler2\ImportExport\ (available only to the current user).  Deploying the debug version with the .pdb file ensures that any error generated by your extension will report the line numbers of the offending code.
  4. Start Fiddler and capture some traffic.
  5. Stop capturing and select “File >> Export Session >> All Sessions…” and export to JMeter.
  6. Open a blank test plan in JMeter and create a Thread Group.
  7. Right-click the Thread Group and select Merge.
  8. Navigate to the exported Jmx file and merge into your test plan.  The recorded requests will be merged into your test plan as child requests of the Thread Group.

Debugging your extension

As mentioned  earlier you should build your extension in Debug mode.  You must also set a number of Fiddler preferences in order to capture and log any exceptions thrown by your extension.  Using Fiddler’s QuickExec box, execute the following two commands:


prefs set fiddler.debug.extensions.showerrors True
prefs set fiddler.debug.extensions.verbose True

Any exceptions will now be logged  to Fiddler’s Log tab.

Extending the basic extension

There are many ways in which you could extend the functionality of this extension.

Export a complete Thread Group or Test Plan

This would involve viewing the jmx file of an existing test plan containing a Thread Group and then working out what xml needs generating to create a Thread Group entry.  It’s then just a matter of adding to your code to create these extra elements.  You could name the Thread Group based upon the output filename of the jmx.

Keep a skeleton JMeter Test Plan containing the basic configuration elements, listeners etc and just merge in your new or updated Thread Groups after you record them.

Assign more meaningful names to the HTTP Samplers

For instance, if the HTTP requests recorded were RESTful or Web Service calls then you could easily give them shorter names, based upon the service call, rather than the URL’s full path.

Generate Transaction Controllers for your test plan

If you are recording manually using Fiddler then you could use Fiddler’s Comment feature to mark Transactions as you record.  The comment feature allow you to add a comment to a selected session.  Your export code can detect these comments by accessing the following property of a session:


    foreach (Fiddler.Session session in sessions)
    {
        if (session.oFlags.ContainsKey("ui-comments"))
        {
            // create a Transaction Controller entry with name =
            // session.oFlags.ContainsKey("ui-comments")
        }
        // continue with processing session
    }

If you are recording a replaying functional test script e.g. Selenium Web Driver or Windows UI Automation (WPF) then you can insert dummy HTTP calls into your code that will be captured by Fiddler.  Simply ensure that your exporter code processes these dummy calls and turns them into Transaction Controllers.  Here is an example of a dummy call I use in my Windows UI Automation scripts:

public static void StartTransaction(string transaction)
{
    HttpWebRequest transactionRequest = (HttpWebRequest)WebRequest.Create("http://auto-transaction/" + transaction);
    transactionRequest.Method = "GET";
    try
    {
        HttpWebResponse transactionResponse = (HttpWebResponse)transactionRequest.GetResponse();
    }
    catch { }
}

I then process these in my exporter code:

    foreach (Fiddler.Session session in sessions)
    {
        if (session.host.StartsWith("auto-transaction"))
        {
            transactionName = session.PathAndQuery.Remove(0, 1); // remove "/" from start of path
            // create a Transaction Controller entry with name =
            // transactionName
        }
        // continue with processing session
    }

Important Notes

The hardest thing to get right when constructing the jmx file is the placement of <hashTree> tags.

In the above image the following <hashTree> tags are created:

<hashTree> <!-- indent the next node -->
<!-- Test Plan node -->
    <hashTree> <!-- indent the next node -->
    <!-- Thread Group node -->
        <hashTree> <!-- indent the next node -->
        <!-- HTTP Sampler node -->
        <hashTree/> <!-- end HTTP Sampler node but no de-dent -->
        <!-- HTTP Sampler node -->
        <hashTree/> <!-- end HTTP Sampler node but no de-dent -->
        <!-- HTTP Sampler node -->
        <hashTree/> <!-- end HTTP Sampler node but no de-dent -->
        </hashTree> <!-- de-dent the next node – end of HTTP Samplers -->
    </hashTree> <!-- de-dent the next node – end of Thread Group -->
</hashTree> <!-- de-dent the next node – end of Test Plan -->

14 thoughts on “Creating JMeter Test Plans with Fiddler

  1. Hello Derek,

    Thanks for this Code.
    I tried this but is not working.
    Also i dont have enough knowledge of C#.net.

    So, Could you help me on this.

  2. Thanks for quick reply.
    Sorry for my late response.

    I copied the required JmeterExporter.dll and JmeterExporter.pdb on specified path. Still i am unable to find (.jmx) format in Select Export format of Fidller.

  3. Ganesh,

    1. Is the version of Fiddler you have greater than that you have specified in your code? e.g.

    [assembly: Fiddler.RequiredVersion(“4.4.0.0”)]

    If you have built your extension with .NET4 and are using it with Fiddler2 then it will not be picked up.

    2. Have you enabled Fiddler’s debugging for extensions as explained in the section “Debugging your extension” of this article. If so, do you see any exceptions in Fiddler’s Log tab when you select “File > Export Sessions > All Sessions…”?

  4. Hello Derek,

    I think this is version issue as you specified above.
    I will check & let you know after required changes.

    Thanks.

  5. Hi Derek,

    Please guide me on this, Not able to generate the JmeterExporter.dll and JmeterExporter.pdb file.Error in Visual studio: “A project with an output Type of class library cannot be started directly. In order to debug this project, add an executable project to this solution which references the library project. Set the executable project as the startup project.”

    1. You are building a DLL and not an executable hence you cannot simply Run it. The DLL must be referenced from another project or executable (e.g. Fiddler). Build the project and copy the resulting DLL and PDB to the correct Fiddler folder as per the instructions. It should then appear as an option in Fiddler’s File >> Export Sessions… menu option.

      After today I may not be contactable for another 2 weeks so I suggest you get any more questions to me asap.

  6. Should i need to get the Fiddler source code and add the above scripts as well and compile?

    Note: Can you please send JmeterExporter.dll and JmeterExporter.pdb to my email as my need is urgent in these 2 weeks.

    1. I do not understand to what scripts you are referring. If you are referring to the code snippets outlined above then these are simple examples of the kind of things you need to include in your project code.

      You do not need Fiddler source as you should have added Fiddler.exe as a reference in your Visual Studio project as detailed in the section “Creating a basic Fiddler export extension”.

      1. Thanks Derek, could successfully generate the JmeterExporter.dll and JmeterExporter.pdb

        In “Building and deploying your extension” section step 5 “Stop capturing and select “File >> Export Session >> All Sessions…” and export to JMeter.”is not working for me.

        Using Fiddler v4.4.5.1 and in code i have given the same version.

  7. This code worked great for me! Load testing in 20 mins.

    I was wondering if i’d have to code my own, but you already did it, with great notes.

    I am C# developer / Fiddler user / JMeter newbie.

    Thanks,

    -Raul

Leave a Reply

Your email address will not be published. Required fields are marked *