Common Scenarios for Creating Coded Web Tests with Team System 2008

Typical Web tests are designed to handle a wide variety of Web test scenarios. Data binding, extraction rules, plug-ins, and context parameters provide a lot of control over Web test execution, but sometimes even more control is needed. Coded Web tests provide the most control and extensibility by offering a .NET API you code against using Visual C# or Visual Basic.

This article is the final in a series of three articles related to creating and debugging Web tests in Team System 2008. This article highlights when and how to create custom Web tests using coded Web tests. For more information, see Techniques for Testing Web Site Performance with Team System 2008.

When to Generate Code, and Why

A coded Web test should only be generated after the limits of typical Web tests are reached. The most obvious limits in typical Web tests are looping, because you cannot run a subset of the requests multiple times, and branching, because you cannot conditionally execute a set of requests. Other reasons to generate code include fine-grained event handling and programmatically setting a parameter value.

It is easier and less error-prone to change a Web test using the graphical Web Test Editor than it is to change code directly. As a result, you should customize a Web test as much as you can before generating code. For example, you should add extraction and validation rules, setup data binding, create transactions around groups of requests, and adjust think times. The code that is generated for all these aspects of a Web test provides a solid foundation for additional customization of the coded Web test. For details on how to convert an existing Web test into a coded Web test, see How to: Create a Coded Web Test.

The following sections demonstrate some examples that require, or would be significantly simplified by, coded Web tests.

Branching

You can use coded Web tests when the Web test must conditionally issue a different set of requests. The following example demonstrates a coded Web test that logs into a Web site using data bound user credentials and creates a new user account if the user does not exist in the system.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TestTools.WebTesting;
using Microsoft.VisualStudio.TestTools.WebTesting.Rules;

[DataSource("DataSource1", "Provider=Microsoft.Jet.OLEDB.4.0;Data 
Source=\"test.mdb\"", DataBindingAccessMethod.Sequential, "Credentials")]
[DataBinding("DataSource1", "Credentials", "UserName", 
"DataSource1.Credentials.UserName")]
[DataBinding("DataSource1", "Credentials", "Password", 
"DataSource1.Credentials.Password")]
public class BranchingCoded : WebTest
{
  public BranchingCoded()
  {
  }

  public override IEnumerator<WebTestRequest> GetRequestEnumerator()
  {
    // Go to the home page of the Web application

    WebTestRequest request1 = new WebTestRequest("http://testserver/website");
    request1.ThinkTime = 4;
    yield return request1;

    // Go to the login page

    WebTestRequest request2 = new WebTestRequest("http://testserver/website/Login.aspx");
    request2.ThinkTime = 16;
    ExtractHiddenFields rule1 = new ExtractHiddenFields();
    rule1.ContextParameterName = "1";
    request2.ExtractValues += new EventHandler<ExtractionEventArgs>(rule1.Extract);
    yield return request2;

    // Attempt to login 

    WebTestRequest request3 = new WebTestRequest("http://testserver/website/Login.aspx");
    request3.ThinkTime = 6;
    request3.Method = "POST";
    FormPostHttpBody request3Body = new FormPostHttpBody();
    request3Body.FormPostParameters.Add("__VIEWSTATE", this.Context["$HIDDEN1.__VIEWSTATE"].ToString());
    request3Body.FormPostParameters.Add("username", 
this.Context["DataSource1.Credentials.UserName"].ToString());
    request3Body.FormPostParameters.Add("password", 
this.Context["DataSource1.Credentials.Password"].ToString());
    request3.Body = request3Body;
    yield return request3;

    // If the login failed, create a new user account

    if (LastResponse.StatusCode != System.Net.HttpStatusCode.OK)
    {
      WebTestRequest request4 = new WebTestRequest("http://testserver/website/register.aspx");
      request4.ThinkTime = 9;
      ExtractHiddenFields rule2 = new ExtractHiddenFields();
      rule2.ContextParameterName = "1";
      request4.ExtractValues += new EventHandler<ExtractionEventArgs>(rule2.Extract);
      yield return request4;

      WebTestRequest request5 = new WebTestRequest("http://testserver/website/register.aspx");
      request5.ThinkTime = 5;
      request5.Method = "POST";
      FormPostHttpBody request5Body = new FormPostHttpBody();
      request5Body.FormPostParameters.Add("__VIEWSTATE", 
this.Context["$HIDDEN1.__VIEWSTATE"].ToString());
      request3Body.FormPostParameters.Add("username", 
this.Context["DataSource1.Credentials.UserName"].ToString());
      request3Body.FormPostParameters.Add("password", 
this.Context["DataSource1.Credentials.Password"].ToString());
      request5Body.FormPostParameters.Add("confirmpassword", 
this.Context["DataSource1.Credentials.Password"].ToString());
      request5.Body = request5Body;
      yield return request5;
    }

    // View the user's profile now that the user is logged in

    WebTestRequest request6 = new WebTestRequest("http://testserver/website/userprofile.aspx");
    yield return request6;
  }
}

Looping

If the Web test must loop in order to issue several requests that are dynamically determined during the test, you can use coded Web tests. The following example demonstrates a Web test that performs a search and then follows every link in the search results.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TestTools.WebTesting;

public class LoopingCoded : WebTest
{
  public LoopingCoded()
  {
  }

  public override IEnumerator<WebTestRequest> GetRequestEnumerator()
  {
    // Issue a search for the term "Microsoft"

    WebTestRequest request7 = new WebTestRequest("http://testserver/website/Search.aspx");
    request7.ThinkTime = 20;
    request7.Method = "POST";
    FormPostHttpBody request7Body = new FormPostHttpBody();
    request7Body.FormPostParameters.Add("txtSearch", "Microsoft");
    request7.Body = request7Body;
    yield return request7;

    // Loop through each anchor tag in the search result and issue a request to each tag's target url (href)

    foreach (HtmlTag tag in this.LastResponse.HtmlDocument.GetFilteredHtmlTags("a"))
    {
      WebTestRequest loopRequest = new WebTestRequest(tag.GetAttributeValueAsString("href"));
      yield return loopRequest;
    }
  }
}

Fine-Grained Event Handling

You can use coded Web tests when you want to apply PreRequest and PostRequest event handlers only to specific requests. Declarative Web tests apply WebTestRequestPlugins, which handles both events, to all requests in the Web test. The following example demonstrates a Web test that logs the response body of two requests to disk for debugging or baselining purposes.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.VisualStudio.TestTools.WebTesting;


public class EventHandlingCoded : WebTest
{
  public EventHandlingCoded()
  {
  }

  public override IEnumerator<WebTestRequest> GetRequestEnumerator()
  {
    WebTestRequest request1 = new WebTestRequest("http://testserver/website");
    request1.ThinkTime = 8;
    yield return request1;

    // Log this response out to a file

    WebTestRequest request2 = new WebTestRequest("http://testserver/website/products.aspx");
    request2.ThinkTime = 2;
    request2.QueryStringParameters.Add("CategoryID", "14", false, false);
    request2.PostRequest += new EventHandler<PostRequestEventArgs>(request2_PostRequest);
    yield return request2;

    WebTestRequest request3 = new WebTestRequest("http://testserver/website/products.aspx");
    request3.ThinkTime = 2;
    request3.QueryStringParameters.Add("CategoryID", "15", false, false);
    yield return request3;

    // Log this response out to a file, too

    WebTestRequest request4 = new WebTestRequest("http://testserver/website/products.aspx");
    request4.ThinkTime = 1;
    request4.QueryStringParameters.Add("CategoryID", "20", false, false);
    request4.PostRequest += new EventHandler<PostRequestEventArgs>(request4_PostRequest);
    yield return request4;
  }

  void request2_PostRequest(object sender, PostRequestEventArgs e)
  {
    File.WriteAllBytes("c:\\request2.html", e.Response.BodyBytes);
  }

  void request4_PostRequest(object sender, PostRequestEventArgs e)
  {
    File.WriteAllBytes("c:\\request4.html", e.Response.BodyBytes);
  }
}

Simulating JavaScript with Coded Web Tests

As described earlier, the Web test engine works at the HTTP layer and does not run JavaScript. Web sites that depend on JavaScript in a way that affects the HTTP layer can used coded Web tests to simulate the logic ordinarily performed by JavaScript.

The following example demonstrates a simple Web page that collects a telephone number and uses JavaScript to strip out some non-numeric characters when the form is submitted.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html  >
<head>
  <title>Untitled Page</title>
  <script type="text/javascript">
    //remove some common non-digit characters
    // in the phone number string
    function FixPhoneNumber() {
      var phoneNumberElement = document.getElementById('phoneNumber');
      var number = phoneNumberElement.value;
      number = number.replace('-', '');
      number = number.replace('(', '');
      number = number.replace(')', '');
      number = number.replace(' ', '');
      
      phoneNumberElement.value = number;
      
      return true;
    }
  </script>
</head>
<body>
  <form action="http://testserver/testwebsite/showparameters.aspx" method="post">
  <div>
    <input name="phoneNumber" id="phoneNumber" type="text"/>
    <input name="submit" type="submit" value="Submit" onclick="FixPhoneNumber()" />
  </div>
  </form>
</body>
</html>

Problems can occur when testing this kind of page when you use data binding with the phoneNumber field. If the data source does not contain telephone numbers in the correct format, unexpected data can be submitted to the server, which causes the Web test to fail. Ideally, the server validates and fixes the data in case JavaScript is disabled, but Web tests might still be necessary to simulate the client-side script.

In most cases, a coded Web test can easily simulate the JavaScript that affects an HTTP request. The following example demonstrates simulating the JavaScript included in the previous Web page.

    public override IEnumerator<WebTestRequest> GetRequestEnumerator()
    {
      WebTestRequest request1 = new WebTestRequest("http://testserver/testwebsite/default.aspx");
      yield return request1;

      WebTestRequest request2 = new WebTestRequest("http://testserver/testwebsite/showparameters.aspx");
      request2.Method = "POST";
      FormPostHttpBody request2Body = new FormPostHttpBody();
      
      //get the databound phone number from the context and
      // remove certain characters to simulate JavaScript
      string phoneNumber = this.Context["PhoneNumber"].ToString();
      phoneNumber = phoneNumber.Replace("-", "");
      phoneNumber = phoneNumber.Replace("(", "");
      phoneNumber = phoneNumber.Replace(")", "");
      phoneNumber = phoneNumber.Replace(" ", "");

      request2Body.FormPostParameters.Add("phoneNumber", phoneNumber);
      request2Body.FormPostParameters.Add("submit", "Submit");
      request2.Body = request2Body;
      yield return request2;
    }

Running and Debugging Coded Web Tests

You must build the coded Web test before you can run or debug it. Once the build is complete, select the test in the Test View window or Test Manager window and then click Run. For more information on the Test view and Test Manager windows, see How to : Customize Test Views.

When a coded Web test starts to run, you can double-click in the Test Results window to display the Web Test Viewer. You can verify that the coded Web tests are correct using the same features that are available for declarative Web tests.

Because Web testing is integrated with Visual Studio, you can debug a coded Web test by selecting Debug instead of Run. Breakpoints can be set anywhere in a coded Web test and all standard debugging features are available.

See Also

Concepts

Techniques for Testing Web Site Performance with Team System 2008

Other Resources

How to: Create a Coded Web Test