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"));
        }