Sitecore Analytics Data Loss: Max Size of Insert Queue Reached

For high traffic websites using Sitecore Analytics, you may run into instances where you find the following warning in your Sitecore logs:

Analystics: Max size of insert queue reached. Dropped 2680.

While this may seem like just a warning, the truth is, you’re losing data.

The warning message tells you exactly how many database writes were attempted to be queued, but dropped in the current session.

Further Analysis

Sitecore Analytics runs as a multi-threaded background process collecting thread requests to write data to the Analytics database. When the number of requests queued is greater than the threshold set in your Analytics.Config file, warnings are written for every 25 failures discovered and those additional requests are discarded with no way to recover the data.

Your only solution is to increase your MaxQueueSize attribute in the Analytics.Config file. Just make sure your servers can handle the load.

Using reflector, the method used to queue requests to be written to the database shows the vulnerability. Keep in mind this method is used in

  • Creating campaign events
  • Updating visitor identity
  • All HTTP and Media request handling

The public static method Enqueue, exposed in the DatabaseSubmitter class of Sitecore.Analytics.Data places tracking in queue to be updated in the Analytics database.

private static readonly int maxSize = Settings.GetIntSetting("Analytics.MaxQueueSize", 500);

...
public static void Enqueue(IDatabaseSubmittable databaseSubmittable)
{
    Assert.ArgumentNotNull(databaseSubmittable, "databaseSubmittable");
         lock (syncQueue)
    {
        if (queue.Count > maxSize)
        {
            AnalyticsManager.SetStatusFailed(AnalyticsManager.STATUS_QUEUE_FULL);
            
            failed++;
            if ((failed % 0x19) == 1)
            {
                Log.Warn( string.Format("Analystics: Max size of insert queue reached. Dropped {0}.", failed), typeof(DatabaseSubmitter));
            }
        }
        else
        {
            queue.Add(databaseSubmittable);
        }
    }
}

Lets hope future versions of Sitecore Analytics will provide better ways of dealing with excessive load than dropping data without having a way to recover it.

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

Partial Sitecore Cache Clearing by Item

Sometimes you just want to clear part of the Sitecore cache and not the entire thing. Taking a page out of the Staging module philosophy, you can easily clear cache by specific items.

First, make sure you understand the basics of Sitecore cache and how requests are handled.

The example method below, clears an enumerated list of items by GUID given a database context.


public static void ClearDataItemCache(Database database, IEnumerable ids)

{

    Cache prefetchCache = GetPrefetchCache(database);

    foreach (ID id in ids)

    {

        if (!ID.IsNullOrEmpty(id))

        {

            database.Caches.ItemCache.RemoveItem(id);

            database.Caches.DataCache.RemoveItemInformation(id);

            database.Caches.StandardValuesCache.RemoveKeysContaining(id.ToString());

            if (prefetchCache != null)

            {

                prefetchCache.Remove(id);

            }

        }

    }

}

Abandoning the ASP.NET User Session in Sitecore with OMS

Be careful when clearing out a user’s ASP.NET session, if you’re using Sitecore OMS. If programmatically clearing one’s session using code similar to one or all of the following:


Context.Session.Abandon();

HttpContext.Current.Request.Cookies.Remove("ASP.NET_SessionId");

Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", string.Empty));

You may notice that in your Sitecore logs, you’ll have entries that match:

    Violation of PRIMARY KEY constraint ‘PK_Session_1’. Cannot insert duplicate key in object ‘dbo.Session’.

The primary key violation is an indicator that you’ve cleared out the users ASP.NET session, but neglected to also clear the Analytics Session as well. Since both sessions are so tightly bound in the Analytics data (just take a look at the table schema for the table Sessions), breaking this relationship will cause loss of data.

All recording of page events via OMS produce errors for the given session after hitting the piece of code to abandon the ASP.NET session. You lose all Analytics tracking due to this error, which means any pages a user visits after abandoning the ASP.NET session will be lost.

The Fix

In addition to clearing the ASP.NET session, also clear the Analytics session:


HttpContext.Current.Request.Cookies.Remove("SC_ANALYTICS_SESSION_COOKIE");

Response.Cookies.Add(new HttpCookie("SC_ANALYTICS_SESSION_COOKIE", string.Empty));

Thanks to Sitecore Support for providing the analysis and recommended approach.

Running Remote Desktop Console from Command Line

I always forget the syntax for using Remote Desktop from the command line.

mstsc.exe /v:MyPCName /console /f

Troubleshooting SMTP Virtual Server Settings in Windows Server 2008

One of our development servers today needed to be configured to send emails from our web site to users and staff within the organization. We knew that the application was coded properly, as the email piece was functioning on other servers using the same code base.

Within Windows Server 2008, you must first add the IIS 6.0 Manager Components. We had this piece installed and configured using the IP of the web server and the default port of 25. Everything looked fine, but for some reason email wasn’t sending.

What I really needed was a way to troubleshoot the problem. I couldn’t step through the code, so I needed a way to determine what about the SMTP virtual server message delivery settings were incorrect. I then found a helpful KBA on troubleshooting via telnet.

By using telnet and following the steps in the KBA referenced above, I was able to see at which step during the email process the failure occurred. Making an EHLO command, the failure showed a domain different than what I was expecting. Instead of “oattravel.com”, I saw the name of the server. Going back into the SMTP virtual server properties, I quickly realized where the problem was.

The SMTP Advanced Delivery Options required domains for both:

  • Masquerade domain
  • Fully-qualified domain name

After changing the settings above, then retracing the steps to telnet back into the server and send an email, I then got a successful “Queued message for delivery” status.

Telnet test SMTP settings blur

One last note… make sure your SMTP Service is set to start up automatically. Ours wasn’t. For every IISRESET, the SMTP virtual server would stop and remain stopped until manually started again.

SMTP Service Automatic Startup

Set the SMTP Service to automatic startup

Why Can’t I Copy Assemblies from the GAC?

Today, I needed to obtain a copy of an assembly referenced in a web.config of one of our applications. The assembly referenced resembled the following:

<assemblies>

  <add assembly="Oracle.DataAccess, Version=10.1.0.200, Culture=neutral, PublicKeyToken=89B483F429C47342"/>

</assemblies>

I tried making a copy of an assembly from the GAC to my file system. Simple task, right? That’s what I thought until I right-clicked on a file only to find a few options, none of which had “copy”.

cannot copy file from explorer

After googling around, I discovered that I had a few options. I opted to copy from the command line:

copy assembly from the command line

By going into the GAC assembly directory from the command line and drilling deeper into the directory tree, you will eventually find the DLL you’re looking for.

Sitecore OMS Error: Overwhelming Change Notification

Today, our websites generated a stack trace error with an “Out of Memory Exception” message for the whole world to see.

Immediately looking at the IIS logs, we discovered 500 internal server errors over a 3-minute time span. Using the time stamp generated by IIS, I then compared against the error logs created by Sitecore (typically in [website path]\data\logs).

Looking at the Sitecore logs, sandwiched between two Analytics errors, I see the following:

4304 14:29:31 ERROR Failed to insert Analytics data
Exception: System.Data.SqlClient.SqlException
...

4276 14:30:18 INFO  **************************************************
4276 14:30:18 WARN  Sitecore shutting down
4276 14:30:18 WARN  Shutdown message: Overwhelming Change Notification in C:\inetpub\wwwroot
Overwhelming Change Notification in C:\inetpub\wwwroot
Change in GLOBAL.ASAX
HostingEnvironment initiated shutdown
CONFIG change

4304 14:30:26 ERROR Failed to insert Analytics data
Exception: System.Data.SqlClient.SqlException
...

Having no idea what “Overwhelming change notification” meant of what it was referring to, our team contacted Sitecore directly. Turns out that this error is a result of a bug in OMS 1.0.1 (running under Sitecore 6.1). An unhandled exception appeared in the Sitecore OMS module due to improper handling of the AnalyticsLogger class. Their support team were able to reproduce the issue and generate the following error message related to the one triggered above:

ERROR Unhandled exception detected. The ASP.NET worker process will be terminated.
Exception: System.IndexOutOfRangeException
Message: Index was outside the bounds of the array.
Source: mscorlib
   at System.Collections.Generic.List`1.Add(T item)
. . .

Currently, the only work around for this is to disable error logging for Sitecore OMS by following the steps below:

  1. Open the \Website\App_Config\Include\Sitecore.Analytics.config file.
  2. In Sitecore.Analytics.config file, comment the following pipeline’s processor:

    <!– <processor type=”Sitecore.Analytics.

    Pipelines.HttpRequest.StartDiagnostics, Sitecore.Analytics” patch:after=”processor[@type=’Sitecore.Pipelines.HttpRequest.StartMeasurements, Sitecore.Kernel’]” /> –>

Tough choice here…. Keep your OMS error logging or take your chances with random OMS exceptions?

Getting Started with the Facebook C# SDK

Facebook recently announced the release of the Facebook C# SDK. The SDK allows .NET developers to create Facebook applications by directly calling their API. To get started with the SDK, follow these steps:

  1. Download the source and build the FacebookAPI project.
  2. Create your Facebook application from the Developer Section of your Facebook profile’s Application Settings.
  3. Coding your application to use the FacebookAPI.

In this example, I’ll demonstrate the creation of a simple .NET application that communicates with Facebook to pull my profile and friends list.

Building the FacebookAPI Project

First, download the source of the Facebook API project. Open the solution FacebookAPI.sln in Visual Studio and build the project. You will later use Facebook API assembly to interact with Facebook via .NET.

Creating the Application on Facebook

In order to successfully make calls to Facebook, you have to first register your application with Facebook and obtain authentication keys to be used with OAuth 2.0.

1. Go to http://developers.facebook.com/setup/ to begin registering your application. Make sure you use Internet Explorer. I ran into problems when attempting to register using Firefox (application would register, but a blank page was displayed).

2. Register the application using a site name and URL of the path relative to your authenticating logic. The redirect_url parameter you provide to the Facebook Graph API needs to match the path used to register the application.

create facebook application

In this example, I’ve registered the application as:

    Site Name: Dave Test
    Site URL: http://localhost/Facebook/oauth/

3. Once registered, you can view your application’s configuration settings and authentication keys. These details will be referenced in the example code to make requests to Facebook.

my application overview

Coding the Application

To accomplish the task of pulling my Facebook profile and friends list, I need to do the following:

  • Redirect from my local web application to Facebook with my application id and URL of my redirect handler
  • Construct the redirect URL handler to accept the access token provided by Facebook
  • Instantiate the FacebookAPI object with the access token above
  • Access my Profile via the FacebookAPI

1. Redirecting to Facebook

We need to send Facebook our application id and URL of the handler for Facebook’s redirect, containing our access token.


protected void btnAuthenticate_Click(object sender, EventArgs e)

{

    string clientId = "117342178314989";

    string redirectUrl = "http://localhost/Facebook/oauth/oauth-redirect.aspx";

 

    Response.Redirect(string.Format("https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri={1}", clientId, redirectUrl));

}

Notice that the variable clientId matches the field Application Id in my Facebook Application configuration settings and the relative path in the variable redirectUrl matches the path defined in the field Connect URL.

After redirecting, a response is sent back to http://localhost/Facebook/oauth/oauth-redirect.aspx containing a code in the query string parameters of the URL. Successfully making a call, results in the following URL:

http://localhost/Facebook/oauth/oauth-redirect.aspx?code=2.UwNcNB5FfO69d_l5S1j76Q__.3600.1280984400-1427490881%7CGE2JRQaeMDwAZHwZMkk0NUiMQD4.

Notice the parameter code. This value will be used to request an access token from Facebook’s Graph API.

2. Building the Handler for Facebook’s Redirect

In step 1, we’ve created the request to Facebook. In step 2, we need to build the handler to accept the access token provided by Facebook to successfully make API calls.

Currently, there’s nothing built into the API that requests the access token, so I had to build one. The code below calls the Facebook Graph API, requesting an access token.


private Dictionary<string, string> GetOauthTokens(string code)

{

    Dictionary<string, string> tokens = new Dictionary<string, string>();

 

    string clientId = "117342178314989";

    string redirectUrl = "http://localhost/Facebook/oauth/oauth-redirect.aspx";

    string clientSecret = "bc7996cfc4f0c66d0417b54eea73f4e7";

    string scope = "read_friendlists,user_status";

 

    string url = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&client_secret={2}&code={3}&scope={4}",

                    clientId, redirectUrl, clientSecret, code, scope);

 

    HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;

    using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)

    {

        StreamReader reader = new StreamReader(response.GetResponseStream());

        string retVal = reader.ReadToEnd();

 

        foreach (string token in retVal.Split('&'))

        {

            tokens.Add(token.Substring(0, token.IndexOf("=")),

                token.Substring(token.IndexOf("=") + 1, token.Length - token.IndexOf("=") - 1));

        }

    }

 

    return tokens;

}

Variables clientId and clientSecret should match fields Application Id and Application Secret, respectively, in the Facebook Application Settings page.

Scope defines the scope of the request. These values are considered Extended Permissions which means requesting access to data not marked as public to everyone in a user’s Facebook profile.

3. Instantiate FacebookAPI with an Access Token

The method GetOauthTokens accepts a parameter code. We’ll pass in the code value obtained in the query string param of the response in step 1 and cache the response for the time defined by the expiration value in Facebook’s Graph API response.


protected void Page_Load(object sender, EventArgs e)

{

    if (Request.Params["code"] != null)

    {

        Facebook.FacebookAPI api = new Facebook.FacebookAPI(GetAccessToken());

        ...

    }

}

 

private string GetAccessToken()

{

    if (HttpRuntime.Cache["access_token"] == null)

    {

        Dictionary<string, string> args = GetOauthTokens(Request.Params["code"]);

        HttpRuntime.Cache.Insert("access_token", args["access_token"], null, DateTime.Now.AddMinutes(Convert.ToDouble(args["expires"])), TimeSpan.Zero);

    }

 

    return HttpRuntime.Cache["access_token"].ToString();

}

4. Access My Facebook Profile

Now that we have an active connection with Facebook, we can use the API in step 3 to request my profile information. Doing so is as easy as a few lines of code:


JSONObject me = api.Get("/me");

JSONObject meFriends = api.Get("/me/friends");

making Get requests to Facebook via the API returns JSON containing profile information. The first requests my profile while the second obtains my friends list.

Placing a watch on these objects gives us
a watch on me

a watch of variable meFriends

As you can see, the variable me contains all of my public profile attributes. The variable meFriends has all 188 of my friends in an array of dictionary items. Each friend is stored in an id/name combo. If we wanted, we could take this another level deeper and get all friends profiles by requesting the id via the Graph API like so (1427490881 is my Facebook id):


JSONObject me = api.Get("/1427490881");

Full Source

/Default.aspx.cs

using System;

using System.Collections;

using System.Configuration;

using System.Data;

using System.Linq;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Linq;

 

namespace Facebook

{

    public partial class _Default : System.Web.UI.Page

    {

        protected void btnAuthenticate_Click(object sender, EventArgs e)

        {

            string clientId = "117342178314989";

            string redirectUrl = "http://localhost/Facebook/oauth/oauth-redirect.aspx";

 

            Response.Redirect(string.Format("https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri={1}", clientId, redirectUrl));

        }

    }

}

/oauth/oauth-redirect.aspx.cs


using System;

using System.Collections;

using System.Configuration;

using System.Data;

using System.Linq;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Linq;

using Facebook;

using System.IO;

using System.Net;

using System.Collections.Generic;

 

namespace Facebook

{

    public partial class oauth_redirect : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            if (Request.Params["code"] != null)

            {

                Facebook.FacebookAPI api = new Facebook.FacebookAPI(GetAccessToken());

 

                JSONObject me = api.Get("/me");

                JSONObject meFriends = api.Get("/me/friends");

            }

        }

 

        private string GetAccessToken()

        {

            if (HttpRuntime.Cache["access_token"] == null)

            {

                Dictionary<string, string> args = GetOauthTokens(Request.Params["code"]);

                HttpRuntime.Cache.Insert("access_token", args["access_token"], null, DateTime.Now.AddMinutes(Convert.ToDouble(args["expires"])), TimeSpan.Zero);

            }

 

            return HttpRuntime.Cache["access_token"].ToString();

        }

 

        private Dictionary<string, string> GetOauthTokens(string code)

        {

            Dictionary<string, string> tokens = new Dictionary<string, string>();

 

            string clientId = "117342178314989";

            string redirectUrl = "http://localhost/Facebook/oauth/oauth-redirect.aspx";

            string clientSecret = "bc7996cfc4f0c66d0417b54eea73f4e7";

            string scope = "read_friendlists,user_status";

 

            string url = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&client_secret={2}&code={3}&scope={4}",

                            clientId, redirectUrl, clientSecret, code, scope);

 

            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;

            using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)

            {

                StreamReader reader = new StreamReader(response.GetResponseStream());

                string retVal = reader.ReadToEnd();

 

                foreach (string token in retVal.Split('&'))

                {

                    tokens.Add(token.Substring(0, token.IndexOf("=")),

                        token.Substring(token.IndexOf("=") + 1, token.Length - token.IndexOf("=") - 1));

                }

            }

 

            return tokens;

        }

    }

}

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>