Click here to Skip to main content
15,886,518 members
Articles / Web Development / HTML

Writing Integration Tests for ASP.NET with Selenium 2.0 – Part 2

Rate me:
Please Sign up or sign in to vote.
4.60/5 (4 votes)
17 Apr 2012CPOL2 min read 37.5K   15   3
This is the second in a series of posts on writing integration tests for ASP.NET using the Selenium 2.0 web application testing system.

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

In this post, I’ll go over how to write and run C# test-cases using Selenium 2.0. I’ve also provided a base-class that contains helper methods for repetitive stuff like typing inside input fields. The base-class also speeds up the tests by re-using the same driver and preserving logins across tests.

Getting Ready to Write Your First Test-case…

Make sure your test project references the following assemblies:

  • WebDriver.dll
  • WebDriver.Support.dll
  • Newtonsoft.Json.dll
  • Ionic.Zip.dll
  • Castle.Core.dll

Refer back to my first post on Selenium to learn about how to configure Selenium and where to grab these DLLs from.

Your First Test-case

For the purposes of writing our test case, we will assume that we have a web application that allows a user to login, fill out a form and submit it. Below are basic HTML elements that we care about:

Login.aspx
HTML
<input type="text" id="userId" />
<input type="password" id="password" />
<input type="submit" id="btnLogin" value="Log In" />
FillForm.aspx
HTML
<input type="text" id="firstName" />
<input type="text" id="lastName" />
<input type="text" id="address1" />
<input type="text" id="city" />
<input type="text" id="state" />
<input type="submit" name="btnSubmit" value="Submit" />

Here is what our test-class would look like:

C#
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;

namespace Web.UI.Tests
{
    [TestClass]
    public class FillFormIntegrationTest
    {
        public static string BaseUrl = "http://localhost:8080";

        // the max. time to wait before timing out.
        public const int TimeOut = 30;

        [TestMethod]
        public void CanFillAndSubmitFormAfterLogin()
        {
            var driver = new FirefoxDriver();

            // we need to setup implicit wait times so that selenium waits for some time and
            // re-checks if an element isn't found.
            // This is useful to ensure that a page 
            // gets fully loaded before selenium tries to look for stuff.
            driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(TimeOut));

            driver.Navigate().GoToUrl(BaseUrl + "/Login.aspx");

            driver.FindElement(By.Id("userId")).SendKeys("testUser");
            driver.FindElement(By.Id("password")).SendKeys("foobar");

            driver.FindElement(By.Id("btnLogin")).Click();

            Assert.AreEqual("Fill out form", driver.Title);

            driver.FindElement(By.Id("firstName")).SendKeys("User");
            driver.FindElement(By.Id("lastName")).SendKeys("One");
            driver.FindElement(By.Id("address1")).SendKeys("99 Test Street");
            driver.FindElement(By.Id("city")).SendKeys("Test City");
            driver.FindElement(By.Id("state")).SendKeys("TX");

            driver.FindElement(By.Id("btnSubmit")).Click();

            Assert.IsTrue(driver.FindElement(By.CssSelector("confirm-label")).Text.
                   Contains("Submission successful."));
        }
    }
}

And there it is! If you run this test case, you’ll see the Firefox browser come up and go through actions specified in the test case above.

Saving Our Fingers and Speeding Up Our Tests!

If you happen to find the Selenium API a little verbose, then you’re not alone. I feel the same way. Having to type ‘driver.FindElement(By.Name(“firstName”))’ for every single action starts getting tedious pretty quick – even with IntelliSense! The other issue that you’ll notice as soon as you have more than one test-case is that starting up the FirefoxDriver takes a bit of time. What we really want is a way to create the driver once and then simply re-use it across all our tests. Another pain point are logins. For most apps, a great majority of tests will require that a user be logged in. Furthermore, you’re likely to re-use the same login across most of your test-cases. Instead of having each of our tests log the user in every single time, why not skip the login process if the user is already logged in? Both these changes will make a significant impact in speeding up our tests. So let’s add them!

Below is a base-level class that contains the improvements pointed out above and also provides some helper methods to give our fingers a break!

C#
public class BaseIntegrationTest
{
    public const string BaseUrl = "http://localhost:8888";

    // specify if our web-app uses a virtual path   
    private const string VirtualPath = "";

    private const int TimeOut = 30;
    private static int _testClassesRunning;

    private static readonly IWebDriver StaticDriver = CreateDriverInstance();
    private static Login _currentlyLoggedInAs;

    static BaseIntegrationTest()
    {
        StaticDriver.Manage().Timeouts().ImplicitlyWait(
           TimeSpan.FromSeconds(TimeOut));
    }
 
    // Pass in null if want to run your test-case without logging in.
    public static void ClassInitialize(Login login = null)
    {
        _testClassesRunning++;
        if (login == null)
        {
            Logoff();
        }
        else if (!IsCurrentlyLoggedInAs(login))
        {
            Logon(login);
        }
    }
 
    public static void ClassCleanup()
    {
        try
        {
            _testClassesRunning--;
            if (_testClassesRunning == 0)
            {
                StaticDriver.Quit();
            }
        }
        catch (Exception)
        {
            // Ignore errors if unable to close the browser
        }
    }

    public IWebDriver Driver
    {
        get { return StaticDriver; }
    }

    public void Open(string url)
    {
        Driver.Navigate().GoToUrl(BaseUrl + VirtualPath + url.Trim('~'));
    }

    public void Click(string id)
    {
        Click(By.Id(id));
    }
 
    public void Click(By locator)
    {
        Driver.FindElement(locator).Click();
    }

    public void ClickAndWait(string id, string newUrl)
    {
        ClickAndWait(By.Id(id), newUrl); 
    }

    /// <summary>
    /// Use when you are navigating via a hyper-link and need for the page to fully load before 
    /// moving further.  
    /// </summary>
    public void ClickAndWait(By locator, string newUrl)
    {
        Driver.FindElement(locator).Click();
        WebDriverWait wait = new WebDriverWait(Driver, 
              TimeSpan.FromSeconds(TimeOut));
        wait.Until(d => d.Url.Contains(newUrl.Trim('~')));
    }

    public void AssertCurrentPage(string pageUrl)
    {
        var absoluteUrl = new Uri(new Uri(BaseUrl), VirtualPath + 
               pageUrl.Trim('~')).ToString();    
        Assert.AreEqual(absoluteUrl, Driver.Url);
    }

    public void AssertTextContains(string id, string text)
    {
        AssertTextContains(By.Id(id), text);
    }

    public void AssertTextContains(By locator, string text)
    {
        Assert.IsTrue(Driver.FindElement(locator).Text.Contains(text));
    }

    public void AssertTextEquals(string id, string text)
    {
        AssertTextEquals(By.Id(id), text);
    }

    public void AssertTextEquals(By locator, string text)
    {
        Assert.AreEqual(text, Driver.FindElement(locator).Text);
    }

    public void AssertValueContains(string id, string text)
    {
        AssertValueContains(By.Id(id), text);
    }

    public void AssertValueContains(By locator, string text)
    {
        Assert.IsTrue(GetValue(locator).Contains(text));
    }

    public void AssertValueEquals(string id, string text)
    {
        AssertValueEquals(By.Id(id), text);
    }

    public void AssertValueEquals(By locator, string text)
    {
        Assert.AreEqual(text, GetValue(locator));
    }

    public IWebElement GetElement(string id)
    {
        return Driver.FindElement(By.Id(id));
    }

    public string GetValue(By locator)
    {
        return Driver.FindElement(locator).GetAttribute("value");
    }

    public string GetText(By locator)
    {
        return Driver.FindElement(locator).Text;
    }

    public void Type(string id, string text)
    {
        var element = GetElement(id);
        element.Clear();
        element.SendKeys(text);
    }

    public void Uncheck(string id)
    {
        Uncheck(By.Id(id));
    }

    public void Uncheck(By locator)
    {
        var element = Driver.FindElement(locator);
        if(element.Selected)
            element.Click();
    }

    // Selects an element from a drop-down list.
    public void Select(string id, string valueToBeSelected)
    {
        var options = GetElement(id).FindElements(By.TagName("option"));
        foreach (var option in options)
        {
            if(valueToBeSelected == option.Text)
                option.Click();
        }
    }

    private static IWebDriver CreateDriverInstance(string baseUrl = BaseUrl)
    {
        return new FirefoxDriver();
    }       

    private static bool IsCurrentlyLoggedInAs(Login login)
    {
        return _currentlyLoggedInAs != null && 
               _currentlyLoggedInAs.Equals(login);
    }

    private static void Logon(Login login)
    {
        StaticDriver.Navigate().GoToUrl(BaseUrl + VirtualPath + "/Logon.aspx");

        StaticDriver.FindElement(By.Id("userId")).SendKeys(login.Username);
        StaticDriver.FindElement(By.Id("password")).SendKeys(login.Password);
        StaticDriver.FindElement(By.Id("btnLogin")).Click();

        _currentlyLoggedInAs = login;
    }

    private static void Logoff()
    {
        StaticDriver.Navigate().GoToUrl(
            VirtualPath + RedirectLinks.SignOff.Trim('~'));
        _currentlyLoggedInAs = null;
    }
}
 
public class Login
{
    public Login(string username, string password)
    {
        Username = username;
        Password = password;
    }

    public string Username { get; private set; }

    public string Password { get; private set; }

    public override bool Equals(object obj)
    {
        Login compareTo = obj as Login;
        if (compareTo == null)
            return false;

        return compareTo.Username == Username && 
               compareTo.Password == Password;
    }
}

public class Logins
{
    public static Login UserOne
    {
        get
        {
            return new Login("user_1", "foobar1");
        }
    }

    public static Login UserTwo
    {
        get
        {
            return new Login("user_2", "foobar2");
        }
    }
}

Upon inheriting from BaseIntegrationTest class, our test-case will look like the following:

C#
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;

namespace Web.UI.Tests
{
    [TestClass]
    public class FillFormIntegrationTest : BaseIntegrationTest
    {
        [ClassInitialize]
        public static void ClassInitialize(TestContext context)
        {
           ClassInitialize(Logins.UserOne);
        }

        [ClassCleanup]
        public static void ClassCleanup()
        {
            BaseIntegrationTest.ClassCleanup();
        }

        [TestMethod]
        public void CanFillAndSubmitFormAfterLogin()
        {
            Open("~/FillOutForm.aspx");

            Assert.AreEqual("Fill out form", driver.Title);

            Type("firstName", "User");
            Type("lastName", "One");
            Type("address1", "99 Test Street");
            Type("city", "Test City");
            Type("state", "TX");

            Click("btnSubmit");

            AssertTextContains(By.CssSelector("confirm-label"), "Submission successful.");
        }
    }
}

Much better, don’t you think? :)

In my next and final post on Selenium, I’ll go over the use of cookies to simulate and test various scenarios when integrating with 3rd party software.

Cheers!

This article was originally posted at http://nizarnoorani.com?p=551

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) NCube, Inc.
United States United States
Nizar Noorani is an independent software consultant. He provides services in the areas of web applications and systems architecture, analysis, design and development. Although proficient in a variety of technologies, he specializes in the .NET technology. He can be reached at nizar.noorani@gmail.com.

Comments and Discussions

 
QuestionFormatting Issues Pin
Sridhar Patnayak17-Apr-12 0:59
professionalSridhar Patnayak17-Apr-12 0:59 
No proper formatThumbs Down | :thumbsdown:
AnswerRe: Formatting Issues Pin
Nizar Noorani17-Apr-12 2:10
Nizar Noorani17-Apr-12 2:10 
SuggestionFormatting Pin
Wendelius16-Apr-12 18:15
mentorWendelius16-Apr-12 18:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.