Tracking Selenium Commands Using C# and SQL Server 2008

When running a suite of Selenium tests, I wanted an easy way of looking at the pages visited as well as commands executed during each unit test. To solve this problem, I designed a solution built on top of the core Selenium classes that records all unit test data to a SQL Server 2008 database.

In this post, details of the following are covered:

  • Back End Design – Database schema overview.
  • Core logic – The class that drives tracking of the test.
  • Example Unit Test – Example of tracking a simple unit test.

Not all code required to communicate with the database is listed in this post. You can either download the source from the project on SourceForge by going to https://sourceforge.net/projects/seleniumtrack/ or browse the source in the project’s SVN repository.

The Back-End Design

The SQL Server database consists of 5 tables:

  • Test Suite – All test suites executed.
  • UnitTest – All unit tests for each test suite.
  • CommandLog – Commands executed for each unit test.
  • PageLog – Page or URL accessed by a command.
  • Cookies – Cookies values, if any, available when viewing a page.

tracking database schema

Tracking Database Schema


 

Core Logic

There’s one main class the directs the traffic, TrackDefaultSelenium. Inheriting from DefaultSelenium, the commands start, stop, open and click are recorded.

It’s here where test suites and unit test records are saved in the database. For each command targeted at the TrackDefaultSelenium class, records are saved which can be easily tied back to their respective unit tests.


    public class TrackDefaultSelenium : DefaultSelenium, ISelenium

    {

        private string _screenshotPath = @"c:\build\SeleniumTests\Screenshots\";

        private string _currentUrl;

        private int _testSuiteId;

        private int _unitTestId;

        private bool _captureScreenShots = false;

 

        private string _testSuiteName;

        private string _unitTestName;

        public TestStatus UnitTestStatus { get; set; }

 

        public enum TestStatus

        {

            Running,

            Completed,

            Failed

        }

Constructors – Kicks off the creation of a test suite.


        public TrackDefaultSelenium(ICommandProcessor processor, string testSuiteName, string unitTestName, bool CaptureScreenshots)

            : base(processor)

        {

            this._currentUrl = "";

            this._testSuiteName = testSuiteName;

            this._unitTestName = unitTestName;

            this._captureScreenShots = CaptureScreenshots;

            this._testSuiteId = Tracking.CreateTestSuite(this._unitSuiteName);

        }

 

        public TrackDefaultSelenium(string serverHost, int serverPort, string browserString, string browserURL)

            : base(serverHost, serverPort, browserString, browserURL)

        {

            this._currentUrl = "";

            this._testSuiteId = Tracking.CreateTestSuite(this._unitSuiteName);

        }

Command OverridesStart creates the unit test, whereas, click and open record page hits.


        void ISelenium.Start()

        {

            this.UnitTestStatus = TestStatus.Running;

            this.commandProcessor.Start();

            this._unitTestId = Tracking.CreateUnitTest(this._testSuiteId, this._unitTestName, GetLocation());           

        }

 

        void ISelenium.Click(string locator)

        {

            this.commandProcessor.DoCommand("click", new string[] { locator });

            int fileId = RecordPageHit("click", GetLocation());

 

            if (this._captureScreenShots)

                CaptureScreenShot(fileId);

        }

 

        void ISelenium.Open(string url)

        {

            this.commandProcessor.DoCommand("open", new string[] { url });

            RecordPageHit("open", GetLocation());

        }

 

        void ISelenium.Stop()

        {

            Tracking.UpdateUnitTestStatus(this._unitTestId, UnitTestStatus.ToString());

            this.commandProcessor.Stop();

        }

Tracking and Screenshot Capture Methods


        private void CaptureScreenShot(int fileId)

        {

            if (!System.IO.Directory.Exists(this._screenshotPath + this._testSuiteId))

                System.IO.Directory.CreateDirectory(this._screenshotPath + this._testSuiteId);

 

            if (!System.IO.Directory.Exists(this._screenshotPath + this._testSuiteId + @"\" + this._unitTestId))

                System.IO.Directory.CreateDirectory(this._screenshotPath + this._testSuiteId + @"\" + this._unitTestId);

 

            CaptureEntirePageScreenshot(this._screenshotPath + this._testSuiteId + @"\" + this._unitTestId + @"\" + fileId + @".jpg", "");

        }

 

        private int RecordPageHit(string command, string Url)

        {

            // record the command

            int CommandId = Tracking.CreateCommandLogEntry(this._unitTestId, command, "");

            int PageId = Tracking.CreatePageLogEntry(CommandId, Url);

 

            // attempt to obtain all cookie values without causing test to fail

            string ASPNetSessionId = "";

            try { ASPNetSessionId = GetCookieByName("ASP.NET_SessionId"); } catch { }

 

            // record all cookie values

            if (ASPNetSessionId != "")

                Tracking.CreateCookieEntry(PageId, "ASP.NET_SessionId", ASPNetSessionId);

 

            // save the URL

            this._currentUrl = Url;

 

            return CommandId;

        }

    }

}

Example Unit Test

In this example, we hit a w3school’s page, check the page title and record every command directed at the Selenium server.


using System;

using System.Text;

using System.Text.RegularExpressions;

using System.Threading;

using NUnit.Framework;

using Selenium;

using Web.Diagnostics.Selenium;

 

namespace Web.Diagnostics.UnitTests.TripPages

{

    [TestFixture]

    public class HeaderMastheadLinks : TestSuiteBase

    {

        private ISelenium selenium;

        private StringBuilder verificationErrors;

 

        [SetUp]

        public void SetupTest()

        {

            this.BaseUrl = "http://www.w3schools.com/";

 

            //target the Selenium RC Server

            selenium = new TrackDefaultSelenium(new HttpCommandProcessor("localhost", 4444,

                "*chrome", this.BaseUrl), "Example Test Suite", "Example Unit Test", true);

 

            // test with an anonymous (not logged in, not recognized) user

            selenium.DeleteAllVisibleCookies();

 

            selenium.Start();

            verificationErrors = new StringBuilder();

        }

 

        [TearDown]

        public void TeardownTest()

        {

            try

            {

                selenium.Stop();

            }

            catch (Exception)

            {

                // Ignore errors if unable to close the browser

            }

            Assert.AreEqual("", verificationErrors.ToString());

        }

 

        [Test]

        public void Header()

        {

            try

            {

                selenium.Open("http://www.w3schools.com/");

                selenium.Click("link=Learn HTML5");

                selenium.WaitForPageToLoad("30000");

                Assert.AreEqual("HTML5 Tutorial", selenium.GetTitle());

 

                (selenium as TrackDefaultSelenium).UnitTestStatus =

                    TrackDefaultSelenium.TestStatus.Completed;

            }

            catch (Exception e)

            {

                (selenium as TrackDefaultSelenium).UnitTestStatus =

                    TrackDefaultSelenium.TestStatus.Failed;

                selenium.Stop();

                throw (e);

            }

        }

    }

}

Notice how the script matches that of one generated by the Firefox plugin. The only difference here is how we’re instantiating the Selenium object. Instead of using the DefaultSelenium object, we’re using our custom Selenium tracking object TrackDefaultSelenium.

Looking at the data captured from the test above, we get the following:

UnitTestResults

Advertisements

Integrating Selenium Tests into CruiseControl.Net via NUnit

Getting Selenium and CruiseControl.Net to talk to each other requires some work and behind-the-scenes configuration. Before incorporating Selenium tests into CruiseControl.Net, make sure you:

To successfully get Selenium, NUnit and CruiseControl.Net talking to each other, you will have to accomplish the following:

  1. Configure Selenium RC to run as a service
  2. Edit your CruiseControl.Net config file to include a NUnit task
  3. Point NUnit to your Selenium Unit Test Library

The details of this article help to get you started in accomplishing the tasks above.

Running Selenium RC as a service

By default, Selenium RC runs as a stand-alone application capable of managing Selenium unit tests and directing traffic for different browser requests. If you want to incorporate your Selenium tests into CruiseControl.Net, you have to get Selenium RC running as a service.

I found a great resource that details how to do this. To summarize the article, follow these simple steps:

  1. Download a copy of the Windows Resource Kit.
  2. Register the new service “SeleniumRC”

    "C:\Program Files\Windows Resource Kits\Tools\instsrv.exe" SeleniumRC
    "C:\Program Files\Windows Resource Kits\Tools\srvany.exe" -a [myuser] -p [mypass]
    
  3. Edit your registry to include the path of your Selenium RC installation and executable:

    Windows Registry Editor Version 5.00
    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SeleniumRC\Parameters]
    "Application"="java.exe"
    "AppDirectory"="C:\\Program Files\\selenium-server-1.0.1"
    "AppParameters"="-Xrs -jar selenium-server.jar"
    
  4. Now that you have the Selenium RC service created, go to Administrative Tools > Services and start the service. Check the system event logs for errors to ensure everything is working properly.

    Wire-Up Selenium to CruiseControl.Net using NUnit

    With the Selenium RC service running, edit your CruseControl.Net config file to incorporate NUnit:

    
    

        <tasks>

          <exec>

            <executable>C:\Program Files (x86)\NUnit 2.5.5\bin\net-2.0\nunit-console.exe</executable>

            <buildArgs>"C:\Users\Dave\Documents\Visual Studio 2008\Projects\SeleniumTest\SeleniumTest\bin\Debug\SeleniumTest.dll"</buildArgs>

          </exec>

        </tasks>

    After a successful execution of your test plan using Selenium RC in CruiseControl.Net, you should see a response from NUnit in your CruiseControl.Net build log like the following:

    <buildresults>
      <message>NUnit version 2.5.5.10112</message>
      <message>Copyright (C) 2002-2009 Charlie Poole.</message>
      <message>Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.</message>
      <message>Copyright (C) 2000-2002 Philip Craig.</message>
      <message>All Rights Reserved.</message>
      <message>Runtime Environment - </message>
      <message>   OS Version: Microsoft Windows NT 6.1.7600.0</message>
      <message>  CLR Version: 2.0.50727.4927 ( Net 2.0 )</message>
      <message>ProcessModel: Default    DomainUsage: Single</message>
      <message>Execution Runtime: net-2.0</message>
      <message>.</message>
      <message>Tests run: 1, Errors: 0, Failures: 0, Inconclusive: 0, Time: 20.9481982 seconds</message>
      <message>  Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0</message>
    </buildresults>
    </build>
    </cruisecontrol>
    

Creating Selenium Unit Test Suites with NUnit

When looking for a way to test our websites by using web macros, directly interacting with the rendered presentation of our sites, I ran across quite a few test suites. Both Waitr and Selenium stood out as the more stable, extensible and easy to incorporate test suites out there. Selenium, however, seemed to offer easy implementation on a .Net platform. Once properly setup, Selenium unit tests are as easy to create as opening Firefox, browsing the site you want to test and record your unit test macro as you navigate from one page to another.

Selenium comes in with a few different installation components and programs:

  • Selenium IDE – Firefox plugin used to create the unit tests.
  • Selenium Core – Core libraries for .Net/C#.
  • Selenium Remote Control -Allows for managing Selenium unit tests with multiple broswers simultaneously (includes the Selenium Core libraries).
  • Selenium Grid -Load balance your tests across multiple servers.

Installing Selenium IDE

Download and install the Selenium IDE. This will add a “Selenium IDE” option under tools in your Firefox browser.

Installing Selenium Remote Control

Download the Selenium RC installation files and extract to a location on your test server. For this example, I extracted to c:\Selenium-Remote-Control\.

Starting the Selenium Server is as easy as running “java -jar selenium-server.jar” from the directory of your Selenium Server installation. Since this is a java-based application, you may be required to download the latest version of java prior to successfully starting the Selenium Server.

Starting selenium server

Starting the Selenium Server using Java v1.6.017

If your Selenium Server output resembles something like above, then you’re in business.

Installing NUnit

Installing Selenium Server is not enough. You must also install NUnit to drive the C# generated unit tests that target the Selenium server.

Download the NUnit installation files and install the software to your test server.

Creating Your First Selenium Unit Test

1. Open Firefox

2. Go to tools and select Selenium IDE

Starting Selenium IDE

Starting the Selenium IDE

3. Once the IDE opens, it defaults to recording every action you take on a given website. Select the output that the IDE will use to generate your unit test. For this example, we’re using C#.

Choose C#

Choose C# to output your test

4. While still within Firefox, click through the website for which you want to create a unit test. Every action you take will be recorded by the IDE, with your unit tests generated immediately.

5. When done generating your test, click on the record/stop button to end the IDE. You’ll see your complete unit test within the Source tab of the Selenium IDE window.

Selenium IDE Record Actions

Record your actions in C#

Example Unit Test Generated by the Selenium IDE

Clicking through w3school’s website generated the following code:

Selenium Test Code

Snapshot of Generated Code

Steps to generate the code above:

Clicked on Next Chapter

Clicked on "Next Chapter"

Clicked on Try it Yourself

Clicked on Try it Yourself

Edited the HTML

Edited the HTML

Running The Example Test

1. Take the C# code generated by the Selenium IDE and add it as a class to a new Visual Studio project.

2. Add references to your project for the following dll’s

  • C:\Selenium-Remote-Control\selenium-dotnet-client-driver-1.0.1\nmock.dll
  • C:\Selenium-Remote-Control\selenium-dotnet-client-driver-1.0.1\nunit.core.dll
  • C:\Selenium-Remote-Control\selenium-dotnet-client-driver-1.0.1\nunit.framework.dll
  • C:\Selenium-Remote-Control\selenium-dotnet-client-driver-1.0.1\ThoughtWorks.Selenium.Core.dll
  • C:\Selenium-Remote-Control\selenium-dotnet-client-driver-1.0.1\ThoughtWorks.Selenium.IntegrationTests.dll
  • C:\Selenium-Remote-Control\selenium-dotnet-client-driver-1.0.1\ThoughtWorks.Slenium.UnitTests.dll
Selenium Project References

Selenium Project References

3. Build the solution.

4. Open NUnit. Select File > Open Project… and select the your project’s compiled assembly.

5. Within NUnit, highlight your unit test class name and click on Run.

NUnit Run

Sit back and watch the show. NUnit will kick off the unit test, which runs the compiled code generated by the Selenium IDE. You’ll see the Selenium Server output the commands in the command prompt window. Selenium Remote Control Server then opens, which triggers the opening of a browser window to run the recorded Selenium unit test. NUnit will then report back the results of the test.

Selenium in Action

Selenium in Action