TwitterFacebookGoogle

Writing integration tests for ASP .NET with Selenium 2.0 – Part 1

This is the first in a series of posts on writing integration tests for ASP .NET using the Selenium 2.0 web application testing system.

UPDATE: This post was updated on April 15, 2012 to explain how to setup and configure Selenium 2.0.

In this post, I explain what Selenium is, why use it and how to setup and configure it. In the next subsequent posts, I’ll do a code walk-thru and also share some advanced scenarios.

What is Selenium?
The best description of Selenium is probably the one mentioned on their website:

“Selenium automates browsers. That’s it. What you do with that power is entirely up to you. Primarily it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) also be automated as well.”

Why use Selenium?
There are other integration testing web frameworks out there such as WaitN or the Lightweight Test Automation Framework for testing ASP .NET web apps. So, why use Selenium? Here are the main reasons why picked it:

  1. Selenium allows you to record test scenarios manually (by browsing to specific pages and clicking buttons, etc.) as well as programmatically (by coding up a test case). Thus the tool can be used by both developers and QA’s. You can even use a combination of recorded test scenarios and C# test cases to integration test your application.
  2. One feature that I really liked is that you can export a recorded test scenario as a C# test-case! This technique can be used to drastically speed up the Selenium learning curve since it spits out code that you can use inside C# test-case. For instance, when you’re unsure how to code up a specific scenario you can manually record it and then export it to see what the code needs to look like.
  3. The recorded scripts can be used as a tool to speed up manual testing. For example, I never manually login into the web application that I am currently implementing anymore. Instead I run a test scenario that I had previously recorded, conveniently named Login, that logs me in. No more manually typing in username/password! You can use this technique to directly land on specific pages and/or fill-in and submit forms as well. This adds up to save you boat loads of time during your manual testing.
  4. Finally, the test recorder is a small Firefox plugin and integrates real nice with Firefox.

Convinced? Awesome! Let’s move on to setting up Selenium…

Initial Setup
There are two parts to setting up Selenium:

  1. Installing the Selenium IDE: A Firefox plugin that allows you to manually record test scenarios. Find and download the Selenium IDE plugin from the Selenium Downloads section. After it downloads, fire up the Firefox browser -> Go to Add Ons from the Main Menu -> Click the Settings icon -> Select the Install Add-On From File option -> Click Install Add-On.
  2. Installing the Selenium 2.0 WebDriver for .NET: Accepts commands from your C# test cases and automates the browser. Using the NuGet Package Manager Console, run the below commands to install the Selenium packages. Make sure you select your test project as the “Default project” inside the Package Manager Console before running the commands. This way the DLLs will get added to the correct project.

    Install-Package Selenium.WebDriver
    Install-Package Selenium.Support

    Okay, at this point, we should be all set with Selenium. Verify the following:

    - You are able to record a test scenario: Fire up Firefox. On the Main Menu, under Web Developer, you should now see a Selenium IDE option. Or you can use the Ctrl+Alt+S shortcut key to bring it up. Click the record button on the top right, browse to goggle.com, and search for something. Click the record button again to stop the recording. Now try running the script by clicking the play button and see what happens. Make sure you are already on the google.com page when you click the Play button. Hopefully, it worked. If not, call me. I’ll help you out and I only charge a $150/hr. Just kidding. If it doesn’t work then try re-installing the plugin, I guess. :)

    - You are able to write and run C# integration test-cases using Selenium – This is what I go over in my next post on Selenium.

    Twitter Email Linkedin Digg Stumbleupon Subscribe

Integrating TypeMock with ASP .NET Unit tests

When it comes to writing unit tests for your ASP .NET pages, there isn’t much help out there. I experimented with a few open source testing tools and found some major limitations.

Both NUnitAsp and WaitN, for instance, are “client-side” tools. In other words, you have to write your tests against the actual HTML output. For example, to get the value of a textbox, you have to specify the actual HTML id of the textbox. That’s painful! Especially, since ASP .NET ends up assigning long and complicated ID’s to your controls. Plus NUnitAsp is no longer being maintained or supported.

Unlike NUnitAsp and WaitN, VS Studio ASP .NET Unit testing let’s you examine the actual HttpRequest object. What this means is that you can call methods on your Page and get access to the controls within the page. VS Studio ASP .NET is a pretty decent tool and maybe the answer for you. IF you don’t need to use TypeMock that is. But if you do, then tough luck because VS Studio ASP .NET unit tests don’t work with TypeMock. If you give it try, you’ll get the following exception:

Test method AzAsh.WebApp.Tests.DefaultTest.LoginNotRequiredTest threw exception:  TypeMock.TypeMockException: 
*** Typemock Isolator is not currently enabled. 
To enable do one of the following:

* To run Typemock Isolator as part of an automated process you can:
   - run tests via TMockRunner.exe command line tool
   - use 'TypeMockStart' tasks for MSBuild or NAnt

* To work with Typemock Isolator inside Visual Studio.NET:
        set Tools->Enable Typemock Isolator from within Visual Studio

For more information consult the documentation (see 'Running' topic).

Check the enable property as they have suggested and you’ll notice that Typemock is enabled! So, what gives? I have no idea. But I do know that Ivonna – a ASP .NET testing tool that is being developed in partnership with TypeMock WILL let you work in conjunction with TypeMock. Like VS Studio, it allows you to examine the intrinsic objects, such as the Page object. In addition, it’s got another neat feature that let’s you inject setup code and assertions into your page’s lifecycle event handlers – very handy especially during type mocking. The only drawback is that it’s a little slow. The unit tests take a while to run.

So if you’ve been scratching your head trying to figure how to develop ASP .NET tests that can work with TypeMock, Ivonna is probably the tool you’ve been waiting for!

Twitter Email Linkedin Digg Stumbleupon Subscribe

Update Panels dont play well with Validators in Chrome

If your page uses ASP .NET custom validators and an UpdatePanel, you will notice that your UpdatePanels will not work in Chrome. In order to fix this, you will have disable client script on the custom validators by setting EnableClientScript=false.

Hopefully this tip will save others the headache that I went through!

Happy Coding!

Twitter Email Linkedin Digg Stumbleupon Subscribe

Making ASP .NET Gridview a little less painful

Isn’t it pain in the neck when you have to write the following for every input control whose value you want to extract from a Gridview:

public static void myGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
   string firstname = ((TextBox)myGridview.Rows[e.RowIndex].
                                            FindControl("txtFirstname")).Text;
   string lastname = ((TextBox)myGridview.Rows[e.RowIndex].
                                            FindControl("txtLastname")).Text;
 
   //....and so on.. 
}

Well, you can make it a little easier by implementing the following method either in a class that extends GridView or by writing a extension method. In this example, I’ve written it as an extension method.

public static T FindControl<T>(this GridView gridView, string cntrlName, 
                                          int rowIndex)
      where T : Control
{
   return (T)(gridView.Rows[rowIndex].FindControl(cntrlName));
}

Now the code inside my page is little cleaner and easier to write:

public static void myGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
   string firstname = gridView.FindControl<TextBox>("txtFirstname", e.RowIndex).Text;
   string lastname = gridView.FindControl<TextBox>("txtLastname", e.RowIndex).Text;
 
     //....and so on.. 
}

I admit it’s not something huge but it definitely is a big help when you have to type in that statement for every single control in your Gridview!

I hope that helps some!

Twitter Email Linkedin Digg Stumbleupon Subscribe

An easier way to write HTML input forms

Consider the block of code below:

<table cellpadding="3">
  <tr>
    <td valign="top">
      <b>First Name</b>
    </td>
    <td>
      <asp:TextBox ID="txtFirstName" runat="server" />
      <div>
        Please enter your first name.</div>
    </td>
  </tr>
  <tr>
    <td valign="top">
      <b>Last name</b>
    </td>
    <td>
      <asp:TextBox ID="txtLastName" runat="server" />
      <div>
        Please enter your last name.</div>
    </td>
  </tr>
</table>

The rendered output looks something like this:
HTML Input Form
You will notice that we have to repeat the
and the tags, along with any style information that these contain, for every row of our input form. Ouch! That's a lot of typing. Furthermore, if, in the future, we have make layout and/or style related changes, we'd have to modify all the pages in our application that contain these HTML input forms! Fortunately, there is an easier way.

By implementing a couple of simple ASP .NET server controls, we will save ourselves the trouble of typing the above shown HTML and will be able to simply type the following:

<cc:Form runat="server">
  <cc:FormField runat="server" Name="First Name" 
                      Description="Please enter the first name.">
    <asp:TextBox ID="txtFirstName" runat="server" />
  </cc:FormField>
  <cc:FormField runat="server" Name="Last Name" 
                     Description="Please enter the last name.">
    <asp:TextBox ID="txtLastName" runat="server" />
  </cc:FormField>
</cc:Form>

Now isn't that soo much more pleasant to the eyes and easier on your fingers? So how is this accomplished? As follows:

First, create a ASP .NET Server Control name Form.cs. This simple control will simply render the

tags. Below is the code for it:

public class Form : WebControl
{
  public override void RenderBeginTag(HtmlTextWriter writer)
  {
    writer.Write("<table cellpadding='3'>");
  }
  public override void RenderEndTag(HtmlTextWriter writer)
  {
    writer.Write("</table>");
  }
}

Notice the use of [ParseChildren(false)] attribute. This attributes ensures that 1) we will be able to nest additional controls inside this control and 2) The nested control won't actually become a child control this control. The second point is important and will explained in greater detail shortly.

Now we move on to the more interesting control: the FormField.cs control. This control renders the

's, tag
  • The column that contains the input field label. For example:
    <td><b>First Name</b></td>
  • The opening
  • tag for the column that contains the actual input field.

  • The
    tag containing the input field description. For example:

    <div>Please enter the first name</div>
  • The closing
  • tag

    Well, that all sounds well and good. But wait! Where is the FormField control rendering the actual input field (the asp:TextBox)? The answer is that it isn’t. The use of the [ParseChildren(false)] attribute prevents the parsing of any embedded controls that FormField may have. Thus, in our example, when the Form and the FormField control's rendering has finished, the page control tree looks something like this:

    
    - Page
      - Form
      - FormField
      - LiteralControl("
    's, the input field label, and the input field description. Below is the code for it:

    [ToolboxData("<{0}:FormField runat=server></{0}:FormField>"), ParseChildren(false)]
    public class FormField : WebControl
    {
      public string Name
      {
        get { return ViewState.GetAsString("Name"); }
        set { ViewState["Name"] = value; }
      }
      public string Description
      {
        get { return ViewState.GetAsString("Description"); }
        set { ViewState["Description"] = value; }
      }
      public string NameAlign
      {
        get { return ViewState.GetAsString("NameAlign"); }
        set { ViewState["NameAlign"] = value; }
      }
      public string ControlAlign
      {
        get { return ViewState.GetAsString("ControlAlign"); }
        set { ViewState["ControlAlign"] = value; }
      }
      public override void RenderBeginTag(HtmlTextWriter writer)
      {
        string beginHtml = @"<tr><td align={0}><b>{1}</b></td><td align={2}>";
        writer.Write(string.Format(beginHtml, NameAlign, Name, ControlAlign));
      }
      public override void RenderEndTag(HtmlTextWriter writer)
      {
        string endHtml = @"<div>{0}</div></td></tr>";
        writer.Write(endHtml, Description);
      }
    }

    The interesting parts of the class are the RenderBeginTag() and the RenderEndTag() methods. If you inspect these methods, you’ll see that the RenderBeginTag() is responsible for rendering:

    • The opening
    tag for the column that contains the actual input field (like the , for example)

    And the RenderEndTag() method is responsible for rendering:

    • The closing
    ") - LiteralControl("
    ") - TextBox - LiteralControl("
    Please enter first name
    ") .....

    Notice that the TextBox (txtFirstName) is a child of the Page instead of being a child of the FormField control. What this means is that instead of having to type something convoluted like:

    string firstName = ((TextBox)this.formField1.FindControl("txtFirstName")).Text;

    your page can directly reference the TextBox like so:

    string firstName = txtFirstname.Text

    I hope you’ve found this post helpful and informative. Please take some time to leave your feedback and/or comments to help me improve.

    Twitter Email Linkedin Digg Stumbleupon Subscribe
    CyberChimps