Monday, November 23, 2009

User Extensions with the Selenium Toolkit for .NET

As I mentioned in my last post, new to version 0.81 the Selenium Toolkit for .NET now provides a simple mechanism to add user-defined extensions to Selenium.

Some background

A user-extension for Selenium is a JavaScript file that becomes embedded into Selenium’s Test Runner.  Typically, the script extends Selenium’s core JavaScript to add new commands, but it also can be used to extend existing functionality, or add custom locator strategies.  Both Selenium IDE and RC allow extensions to be added.

For the purposes of this discussion, here’s a crude example of a selenium extension: 
Selenium.prototype.getMyValue = function(locator, text) {
    return locator;
};

The toolkit takes a straight forward approach to including extensions: it combines the files listed in the configuration file (in order of appearance) into a single file and then configures the Selenium RC to use it. 

Incidentally, if you ever need to verify that your extensions were loaded, you can simply view the JavaScript file at this url: http://localhost:4444/selenium-server/core/scripts/user-extensions.js

The ISelenium interface of the Selenium API does not directly expose a mechanism to execute custom commands.  Instead, we need to send our custom command using a command processor (ICommandProcessor), which isn’t exposed by default so a custom implementation of the ISelenium interface is required.  The Selenium documentation provides a pretty good overview of how to do this, and the remainder of this post demonstrates how to integrate this approach with the toolkit.

Create a customized ISelenium

One of the new features added to the toolkit in this release is the ability to supply your own mechanism for instantiating the ISelenium instance. The factory is a basic class with a Create method.  Simply implement the ISeleniumFactoryProvider interface and then wire it up into the configuration settings (see below).  If no setting is provided in the config, a default factory is used.

This example below shows a custom ISeleniumFactoryProvider that creates a customized ISelenium instance that exposes the ICommandProcessor:

namespace Custom
{
    using Selenium;
    using SeleniumToolkit.Core;

    public class SeleniumFactory : ISeleniumFactoryProvider
    {
        public ISelenium Create(string host,
                                int port,
                                string browserProfile,
                                string baseUrl)
         {
             ICommandProcessor processor = new HttpCommandProcessor(host, port, browserProfile, baseUrl);
             return new CustomSelenium(processor);       
         }
    }

    public class CustomSelenium : DefaultSelenium
    {
        public CustomSelenium(ICommandProcessor processor) 
            : base(processor)
        {
            CommandProcessor = processor
        }

        public ICommandProcessor CommmandProcessor
        {
            get;
            protected set;
        }
    }
}

Tweak your Settings

After you’ve compiled your custom factory, you’ll need to reference your JavaScript file in the userExtensions element and specify your custom factory using the factoryType attribute of the Selenium Node.

<Selenium
    factoryType="Custom.SeleniumFactory, CustomProject"
    >
   <userExtensions>
       <add name="example"
            path="myextension.js" />
   </userExtensions>
</Selenium>

Putting it all together

With the configuration settings in place, now all we need to do is cast the Browser.Current instance to our custom type.

namespace Custom.Test
{
    using NUnit.Framework;
    using Selenium;
    using SeleniumToolkit;

    [WebFixture]
    public class Example
    {
        [WebTest]
        public void ShowUsage()
        {
            CustomSelenium browser = Browser.Current as CustomSelenium;

            string[] inputArgs = { "Hello World" };
            string result = browser.CommandProcessor.DoCommand("getMyValue", inputArgs);
            Assert.AreEqual("Hello World", result);
        }
    }
}

Happy coding.

submit to reddit

0 comments: