Tuesday, September 15, 2009

Using Selenium Tests and MasterPages

It was recently noted to me that running Selenium UnitTests can be a problem when the developer is using MasterPages and UserControls. This is due to the fact that when recording the test on one PC, the ControlID used my change depending on the server used.

A normal Selenium Test will produce code such as this :

    [TestFixture]
    public class WebTest
    {
        … code sniped as not needed for demo …

        [Test]
        public void MySeleniumTest()
        {

            selenium.Open("/somesite/default.aspx");
            selenium.Click("ctl02_ctl00_ButtonLaunch");
            selenium.WaitForPageToLoad("30000");
            selenium.Click("ctl02_ctl00_ButtonSubmit");
        }

    }

The ControlID to click is found using the code "ctl02_ctl00_ButtonLaunch"; however on another server it may be generated as  “ctl002_ctl00_ButtonLaunch” or “ctl02_ct0100_ButtonLaunch”, which would cause the test to fail.

To address this you add the following code to the test class or to a base selenium test class, if you have a number of tests running;

//******************************************************************/
//
//                  Helper Methods
//
//******************************************************************/

/// <summary>
/// Simple function to pre-append masterpage text to a control
/// </summary>
/// <param name="control">text name of the control</param>
/// <returns>control name with additional text</returns>
private string ControlName(string control)
{
    return ControlName(control, 0);
}

/// <summary>
/// Simple function to pre-append masterpage text to a control
/// </summary>
/// <param name="control">text name of the control</param>
/// <param name="iteration">number to look for</param>
/// <returns>control name with additional text</returns>

private string ControlName(string control, int iteration)
{
    int counter = 0;
    string pattern = String.Format(@"\bid=.[^ ]*{0}\b", control);   //@"id=.*?{0}"
    string reference = String.Empty; // the correct reference
    MatchCollection matches = Regex.Matches(selenium.GetHtmlSource(), pattern, RegexOptions.IgnoreCase);
    foreach (Match match in matches)
    {
        // work out the control name from the ref:master:control format
        control = match.Value.Replace("id=", "");
        if (counter >= iteration) break;
        counter++;
    }
    return control;
}

This simply uses the HTML to search for the correct ControlID using regular expressions. I’ve added an Overload in case you use the same UserControl on the same page a number of times so you can target a specific version.  If excluded it will simply return the first.

Now all you need to do is edit your test code to the following:

        [Test]
        public void MySeleniumTest()
        {

            selenium.Open("/somesite/default.aspx");
            selenium.Click(ControlName("ButtonLaunch"));
            selenium.WaitForPageToLoad("30000");
            selenium.Click(ControlName("ButtonSubmit"));
        }

Friday, September 11, 2009

Using ASP.NET Ajax controls

Here is a simple tutorial of the simplest of the Ajax functions called the “Partial Page Update”, this shows how to use the built in Visual Studio 2008 controls.  You will see from this that there is no need to write any of that old JavaScript plumbing and how simple it is to implement. 

Setting up your project

Start a new Visual Studio Project called AjaxDemo.

image

Add two text boxes to your default.aspx page called TextBox1 and TextBox2. then add a button called “Button1” with the text “Press Me”, the code is shown below:

<form id="form1" runat="server">
<div>
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><p />
    <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox><p />
    <asp:Button ID="Button1" runat="server" Text="Press Me" />
</div>
</form>

Next on the code behind (default.aspx.cs) add the following code to the Page_Load function which populates the text boxes with the current time.

protected void Page_Load(object sender, EventArgs e)
{
    TextBox1.Text = DateTime.Now.ToLongTimeString();
    TextBox2.Text = DateTime.Now.ToLongTimeString();
}

Run the application and it should look like this:

image

Pressing the button will cause a refresh of the page and both fields to be updated.

Implementing the Partial Page Update Ajax commands

To allow for Ajax on your page you need to add a ScriptManager Control.  this should be placed on the page before any other controls are rendered.  I find putting in on the Master Page works best, that way you don’t have to worry about it later.

1) Drag the ScriptManager from the tools in Visual Studio onto the design or code surface.

image

2) Next you need to add an UpdatePanel around the controls you want to take part in the partial update.  In this case I’m going to wrap TextBox2 and Button1.  Again simply drag the item from the tool box to your design or code surface.  move your controls between the UpdatePanel tags and add a <ContentTemplate> tag around the controls.

image

The code should look something like this:

<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<div>
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><p />
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
    <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox><p />
    <asp:Button ID="Button1" runat="server" Text="Press Me" />
    </ContentTemplate>
    </asp:UpdatePanel>
</div>
</form>

Now press Play on your solution to see it in action.

 image

Pressing the button now will cause the second textbox to become updated but not the first as it is outside the update panel.  Also there will be no full Post Back event for the page refresh.