CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Jeremy D. Miller -- The Shade Tree Developer

Under the hood and working with .Net, TDD, Software Design, and Agile Stuff

Automated Web Testing with Selenium Driven by .Net

The Agile development community has struggled for years with an array of solutions for automated testing solutions for web development.  NUnitASP is a good way to unit test server side ASP.Net code, especially now that it doesn't require XHTML compliant pages, but it can't handle client side scripting and AJAX is exploding in popularity.  Several tools have used COM (must die) to drive Internet Explorer (IE) with varying degrees of success.  My personal experience is that the IE COM API is too byzantine and flat out flaky.  Besides the flakiness, Firefox and other browsers are gaining in popularity so the IE only testing might not cut it anymore.

Enter the Selenium project.  The developers of Selenium had the brilliant, but in retrospect painfully obvious, idea to use Javascript inside a browser to drive the web testing.  Presto, automated testing for web applications that can test client side Javascript and multiple browser engines.  In its original incarnation Selenium ran FIT style test tables inside a web browser.  Recently, Selenium Remote Control has been released to that allow the core Javascript engine to be driven through API's for .Net, Java, Ruby, or Python.  The FIT style table runner is perfectly functional, but for us it's very convenient to use the Selenium RC .Net wrapper.  So far I'm pleasantly surprised by how easy it is to use the .Net wrapper.  

Sample Test

The API libraries communicate with the Selenium Server, a Java executable that can start and stop any supported browser and send testing commands to the browser.  The .Net callable wrapper works by sending HTTP messages to the Selenium Server that controls a browser.  The first step is to start up the Selenium Server from a command prompt.

java -jar server\selenium-server.jar -interactive

The next step was to create a simple HTML page with a textbox that changes values when a button is clicked:

<html>
<head>
<script language=javascript src=prototype-1.4.0.js></script>
<title>Selenium Target Page</title>
<script id=clientEventHandlersJS language=javascript>
<!--
 
function button1_onclick() {
    $('text1').value = "Goodbye"; // Using the Prototype library
}
 
//-->
</script>
</head>
<body>
 
<form name="form1">
    <input id="button1" type=button onclick="return button1_onclick()"
 value="click me"/>
    <input id="text1" type=text value="Hello"/>
    <select testid="select1" >
        <option value="1">North</option>
        <option value="2" selected=true>West</option>
        <option value="3">South</option>
        <option value="4">East</option>
    </select>
</form>
 
</body>
</html>

Next I wrote a little NUnit test fixture class to run tests against the web page. The key object is the DefaultSelenium class that is created in the SetUp() method:

/// <param name="serverHost">the host name on which the 
/// Selenium Server resides</param>
/// <param name="serverPort">the port on which the 
/// Selenium Server is listening</param>
/// <param name="browserString">the command string used 
/// to launch the browser, e.g. "*firefox", "*iexplore"
/// or "c:\\program files\\internet explorer\\iexplore.exe"</param>
/// <param name="browserURL">the starting URL including 
/// just a domain name.  We'll start the browser pointing at
/// the Selenium resources on this URL,
/// e.g. "http://www.google.com" would send the browser to 
/// "http://www.google.com/selenium-server/SeleneseRunner.html"</param>
public DefaultSelenium(String serverHost, int serverPort, 
String browserString, String browserURL)
{
  this.commandProcessor = new HttpCommandProcessor(serverHost, 
serverPort, browserString, browserURL);
}


using System;
using NUnit.Framework;
using Selenium;
 
namespace SeleniumTarget
{
    [TestFixture]
    public class WebPageTester
    {
        DefaultSelenium selenium;
 
        [SetUp]
        public void SetUp()
        {
            // 4444 is the default port for the Selenium Server
            selenium = new DefaultSelenium("localhost", 4444, "*iexplore", "http://localhost");
            selenium.Start();
        }
 
        [TearDown]
        public void TearDown()
        {
            // Make sure the Selenium environment is cleaned up after each test
            selenium.Stop();
        }
 
 
        [Test]
        public void CheckTheTitle()
        {
            selenium.Open("http://localhost/SeleniumTarget/TestPage1.htm");
 
            Assert.AreEqual("Selenium Target Page",
                            selenium.GetTitle(),
                            "Check the title of the browser");
        }
 
 
        [Test]
        public void ClickButton1ChangesText1FromHelloToGoodbye()
        {
            selenium.Open("http://localhost/SeleniumTarget/TestPage1.htm");
            Assert.AreEqual("Hello",
                            selenium.GetValue("text1"),
                            "Initial Value");
 
            selenium.Click("button1");
 
            Assert.AreEqual("Goodbye",
                            selenium.GetValue("text1"),
                            "Value after clicking button1");
        }
 
        /// <summary>
        /// Check that the options of a <select></select> element are
        /// as expected.  Finds the <select> element by using an xpath expression
        /// </summary>
        [Test]
        public void Select1Values()
        {
            selenium.Open("http://localhost/SeleniumTarget/TestPage1.htm");
            string locator = "xpath=//select[@testid='select1']";
 
            string[] options = selenium.GetSelectOptions(locator);
 
            Assert.AreEqual(new string[] {"North", "West", "South", "East"},
                            options,
                            "Values in the select1 dropdown");
        }
    }
}

It's a trivial example, but it's a start. 

What I don't know -

  • What's the best way to handle the Selenium Server process?  How do you guarantee it's up when you start your tests?  I'm thinking some kind of Windows service wrapper
  • How do you integrate Selenium with CruiseControl.Net to get it into your Continuous Integration strategy?  I can't find much on the web about this yet.  You can run Selenium from NUnit for developer testing, but that isn't a very desirable answer from a tester's perspective.  For a couple of reasons, our thinking is to wrap the Selenium manipulation inside a FitNesse DoFixture.  We already know how to integrate FitNesse tests within a CC.Net build and it's convenient to run all the tests together.  We're also hoping that the DoFixture tests will be easier to understand and that we can hide some of the web page details behind the fixture.

Other Tools

We're looking primarily at Selenium, but we're also considering WATIR (Ruby based tool) and Sahi (I don't know much about it, but it looks strong).  One of our colleagues is experimenting with a Ruby based DSL for testing another web product using WATIR as the core that looks promising.  I've also been playing with the Ruby driver for Selenium with an eye towards creating a testing DSL for our application.   



Comments

David Kemp said:

This post is displaying funny:
http://flickr.com/photos/b3ardman/146770254/
Firefox 1.5.0.3 on Windows XP SP2
# May 15, 2006 3:43 AM

David Kemp said:

Also, and perhaps more relevantly, be interested to hear how you get on with Sahi.
I'm currently working with some ('classic') ASP apps, and any tools I can find to help with any automated testing at all would be wonderful.
# May 15, 2006 4:16 AM

Jason said:

I have used HtmlUnit in the Java world where NUnitASP is used in .NET. HtmlUnit has excellent JavaScript support and allowed us to perfrom intergation level testing in our web application. It was not perfect but is definitely worth a look.
# May 15, 2006 11:31 AM

Josh said:

Crikey Jeremy, you're not having a lot of luck keeping these posts viewable! Do you work on a billion pixel monitor?

:D

It's a shame because I'm just not going to read this post now.

Josh
# May 16, 2006 4:25 AM

Sachin said:

You could perhaps use the <exec> NAnt task to run this.  Dunno how you would explicitly stop this process, though.  Then again, why would you want to?
# May 18, 2006 7:36 AM

Jeremy D. Miller said:

Sachin,

It does seems to hang occasionally.  My concern would be cleaning up dangling instances of the Selenium exececutable
# May 18, 2006 10:00 AM

Sachin said:

Just tried opening up another instance at the same port.  Jetty handles this quite gracefully and shuts down, which is nice.
# May 18, 2006 6:17 PM

Sachin said:

Jeremy,

I don't quite see how you will have dangling instances of selenium server, since it won't let you open up another instance at the same port.

Of course we still need a test for whether the server has hanged.

I was working on another idea; didn't quite finish it yesterday.  You could have a NUnit base class which sends a web request to
http://[host]:[port]/selenium-server/SeleneseRunner.html

If it comes back with a web response, then we can be sure there is a listener on that port and it is servicing requests.  If not, it will come up with a web exception or will time out, in which case the test should fail with an appropriate error.

Would have been good if there was a way to do this declaratively in CC.NET.
# May 18, 2006 6:26 PM

Garrett said:

In Java 5, pass a collection and compare against a selenium.getSelectOptions(locator);

   /**
    * Checks equivalence of a collection and a String[].
    * The String[] is from selenium.getSelectOptions().
    * Usage: Test a dataset against an option list.
    *
    * @param titles
    * @param options
    */

public void assertOptionsPresent(ArrayList<String> titles, String[] options) {

assertTrue(
              titles.size() == options.length &&
              titles.containsAll(Arrays.asList(options))
             );
}



# June 5, 2006 8:19 PM

Allan With said:

Hi Jeremy

Thanks for showing how painlessly simple and powerful Selenium .NET testing can be! I have been looking for something like this for a long time.

I saw this description on how to integrate Selenium with CruiseControl on OpenQA's website:
http://wiki.openqa.org/display/SEL/Integrating+Selenium+And+CruiseControl.Net

I haven't actually tried it, but it might just do what you are looking for.
# June 8, 2006 4:51 PM

Jeremy D. Miller said:

Thanks for the link Allan, I'll definitely give it a long look.
# June 8, 2006 8:37 PM

Jeremy D. Miller -- The Shade Tree Developer said:

My team uses FitNesse extensively to create automated acceptance tests for our systems.&amp;nbsp; Recently...
# July 15, 2006 1:31 PM

Ruslan Trifonov's blog said:

I&#39;m digging lately with my team into our new product. We are using a full stack of buzz words in

# September 21, 2006 5:39 PM

Alex said:

You can also check SWExplorerAutomation (SWEA) from <a href='http://webiussoft.com'>http://webiussoft.com</a>. SWEA records, replays and generates test script code in C# or VB.NET. SWEA also supports automation of complex Web applications developed with AJAX.

# February 24, 2007 4:32 PM

Frederic Torres said:

Jeremy,

As you mentioned "Several tools have used COM to drive Internet Explorer (IE) with varying degrees of success" and the 'varying degrees of success' may be part of the problem.

Selenium RC provides a solution to implement cross browsers testing.

But does not give you a direct access to the DOM (because of the Java proxy).

I am wondering if you can automate a drag and drop with Selenium or handling the Save As dialog.

If you are not a Microsoft hater and are .NET friendly, you could have a look at InCisif.net.

See how I implemented a drag and drop with InCisif.net at my post:

http://blog.incisif.net/2007/04/02/watin-incisifnet-and-drag-and-drop.aspx

To finish where you started:

The Agile development community is still struggling on how to automated testing solutions for web development.

I really like to see a kind FIT/FITNESSE for Web User Interfaces.

Let us try to find some good solutions without starting another war of religion.

# April 8, 2007 6:06 PM

khaled said:

hello jeremy.

i have a probleme with selenium.

how to make To recover a String in the thead of a table and not tbody with xpath

thank you for your help(exuse my english...i know is very bad)

# June 28, 2007 8:36 AM

Jeremy D. Miller said:

@Khaled,

I think all you need to do is specify "tHead" in your xpath expression.  There shouldn't be anything special to do.

# June 29, 2007 7:01 AM

ak said:

The last piece of code was the one I have been looking for a long time. - Thank You.

# August 8, 2007 4:17 PM

Monal said:

Hi ,

This is Monal, am currently working on Selenium RC and I came across a problem.

The Issue is "How do we make selenium recognize pop ups?" Do we have any spl addins?

Plz let me know some details on the same.

Thanks in Advance

Monal

# October 12, 2007 3:38 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About Jeremy D. Miller

Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy previously worked as a systems architect building mission critical supply chain software for a Fortune 100 company and learned agile development practices as a .Net consultant at ThoughtWorks, one of the pioneers of agile development. Jeremy is the author of the open source StructureMap (http://structuremap.sourceforge.net) tool for Dependency Injection with .Net and the forthcoming StoryTeller (http://storyteller.tigris.org) tool for supercharged FIT testing in .Net. Jeremy's thoughts on just about everything software related can be found on his weblog "The Shade Tree Developer" at http://codebetter.com/blogs/jeremy.miller, part of the popular CodeBetter site. Jeremy is a Microsoft MVP for C#. Check out Devlicio.us!

This Blog

Syndication

News

All opinions expressed here constitute my (Jeremy D. Miller's) personal opinion, and do not necessarily represent the opinion of any other organization or person, including (but not limited to) my fellow employees, my employer, its clients or their agents.

About Me

"Best Of" Compendium

StructureMap (Dependency Injection for .Net)

StoryTeller (Supercharged Fit)

Build your own Cab

TestDriven

MVP