Showing posts with label Selenium. Show all posts
Showing posts with label Selenium. Show all posts

Thursday, August 1, 2013

PhantomJS, the Headless Browser for your .NET WebDriver Tests

Did you know that Selenium already supports PhantomJS?

WebDriver is a specification for controlling the behavior of a web browser. PhantomJS is a headless WebKit scriptable with a JavaScript API. Ghost Driver is a WebDriver implementation that uses PhantomJS for its back-end. Selenium is a software testing framework for web applications. Selenium WebDriver is the successor to Selenium RC. The Selenium WebDriver NuGet Package is a .NET client for for Selenium WebDriver that includes support for PhantomJs via GhostDriver.

NuGet Packages

You need only install two NuGet packages in order to use PhantomJS with WebDriver. You will probably also want which ever Unit Testing framework you prefer. As always, I suggest xUnit.

  1. Selenium.WebDriver
  2. phantomjs.exe

PhantomJSDriver

After installing those, using the PhantomJSDriver is as easy as any other WebDriver!

const string PhantomDirectory =
    @"..\..\..\packages\phantomjs.exe.1.8.1\tools\phantomjs";
 
[Fact]
public void GoogleTitle()
{
    using (IWebDriver phantomDriver = new PhantomJSDriver(PhantomDirectory))
    {
        phantomDriver.Url = "http://www.google.com/";
        Assert.Contains("Google", phantomDriver.Title);
    }
}
Shout it

Enjoy,
Tom

Sunday, January 13, 2013

WebElement Form Extensions for WebDriver

I really like Selenium 2.0!

I've been working a lot on a side project involving WebDriver, and thus I have been creating a few extension methods for working with form data that I thought others might find helpful:

  • bool IsSelected(this IWebElement element)
  • bool IsChecked(this IWebElement element)
  • void SetChecked(this IWebElement element)
  • void SetUnchecked(this IWebElement element)
  • IWebElement GetOptionByValue(this IWebElement element, string value)
  • IWebElement GetOptionByText(this IWebElement element, string text)

Extension Methods

public static class WebElementExtensions
{
    private static readonly StringComparer DefaultComparer =
        StringComparer.InvariantCultureIgnoreCase;
 
    private static readonly string[] Selected = new[] { "true", "selected" };
 
    public static bool IsSelected(this IWebElement element)
    {
        var attribute = element.GetAttribute("selected");
        return Selected.Contains(attribute, DefaultComparer);
    }
 
    private static readonly string[] Checked = new[] { "true", "checked" };
 
    public static bool IsChecked(this IWebElement element)
    {
        var attribute = element.GetAttribute("checked");
        return Checked.Contains(attribute, DefaultComparer);
    }
 
    public static void SetChecked(this IWebElement element)
    {
        if (!element.IsChecked())
            element.Click();
    }
 
    public static void SetUnchecked(this IWebElement element)
    {
        if (element.IsChecked())
            element.Click();
    }
 
    private const StringComparison DefaultComparison = 
        StringComparison.InvariantCultureIgnoreCase;
 
    public static IWebElement GetOptionByValue(
        this IWebElement element, 
        string value)
    {
        return element.GetOption(
            o => value.Equals(o.GetAttribute("value"), DefaultComparison));
    }
 
    public static IWebElement GetOptionByText(
        this IWebElement element, 
        string text)
    {
        return element.GetOption(o => text.Equals(o.Text, DefaultComparison));
    }
 
    private static IWebElement GetOption(
        this IWebElement element, 
        Func<IWebElement, bool> predicate)
    {
        return element
            .FindElements(By.CssSelector("option"))
            .FirstOrDefault(predicate);
    }
}

Unit Tests

public class WebElementExtensionTests : IDisposable
{
    public IWebDriver Driver { get; private set; }
 
    public WebElementExtensionTests()
    {
        Driver = new ChromeDriver("C:/Code/Drivers");
        Driver.Url = "file:///C:/Code/WebDriverTests/TestPage.htm";
    }
 
    public void Dispose()
    {
        Driver.Dispose();
    }
 
    [Fact]
    public void RadioTests()
    {
        var e1 = Driver.FindElement(By.Id("r1"));
        var e2 = Driver.FindElement(By.Id("r2"));
 
        IsNotSelected(e1);
        IsSelected(e2);
 
        e1.Click();
        IsSelected(e1);
 
        e2.Click();
        IsNotSelected(e1);
    }
 
    protected void IsSelected(IWebElement element)
    {
        Assert.True(element.IsSelected());
    }
 
    protected void IsNotSelected(IWebElement element)
    {
        Assert.False(element.IsSelected());
    }
 
    [Fact]
    public void CheckboxTests()
    {
        var e1 = Driver.FindElement(By.Id("c1"));
        var e2 = Driver.FindElement(By.Id("c2"));
 
        IsNotChecked(e1);
        IsChecked(e2);
 
        e1.Click();
        IsChecked(e1);
 
        e1.Click();
        IsNotChecked(e1);
    }
 
    protected void IsChecked(IWebElement element)
    {
        Assert.True(element.IsChecked());
    }
 
    protected void IsNotChecked(IWebElement element)
    {
        Assert.False(element.IsChecked());
    }
 
    [Fact]
    public void OptionTests()
    {
        var e1 = Driver.FindElement(By.Id("s1"));
 
        var option1 = e1.GetOptionByValue("a");
        var option2 = e1.GetOptionByValue("b");
 
        IsNotSelected(option1);
        IsSelected(option2);
 
        option1.Click();
 
        IsSelected(option1);
        IsNotSelected(option2);
    }
 
    [Fact]
    public void ToggleCheckedTests()
    {
        var e1 = Driver.FindElement(By.Id("c1"));
        IsNotChecked(e1);
 
        e1.SetChecked();
        IsChecked(e1);
 
        e1.SetChecked();
        IsChecked(e1);
 
        e1.SetUnchecked();
        IsNotChecked(e1);
 
        e1.SetUnchecked();
        IsNotChecked(e1);
    }
}

TestPage.htm

<html> <body> <p> <select id="s1"> <option value="a">Ant</option> <option value="b" selected="selected">Bird</option> </select> </p> <p> <input id="r1" type="radio" name="r" value="c" />Cat<br> <input id="r2" type="radio" name="r" value="d" checked="checked" />Dog </p> <p> <input id="c1" type="checkbox" name="vehicle" value="e" />Elephant<br> <input id="c2" type="checkbox" name="vehicle" value="f" checked="checked" />Fox </p> </body> </html>
Shout it

Enjoy,
Tom

Monday, February 20, 2012

Browser Specific TestFixtures Sharing Tests

I will get to WebDrivers running in parallel next week, for now something even more fun: How to share Tests across browser specific TestFixtures with WebDriver.

When writing a unit test to test a webpage, you will want to duplicate that test against multiple browsers. How do we do accomplish this feat? Inheritance!

Step 1: SetUpFixture

First we need to create a SetUpFixture that will manage our WebDrivers. This will be largest and most complex class of our demonstration.

[SetUpFixture]
public class SetUpFixture
{
    private static IWebDriver _internetExplorerDriver;
    private static IWebDriver _chromeDriver;
 
    public static IWebDriver InternetExplorerDriver
    {
        get
        {
            if (_internetExplorerDriver == null)
            {
                DisposeDrivers();
                _internetExplorerDriver = new InternetExplorerDriver();
            }
            return _internetExplorerDriver;
        }
    }
 
    public static IWebDriver ChromeDriver
    {
        get
        {
            if (_chromeDriver == null)
            {
                DisposeDrivers();
                var dir = ConfigurationManager.AppSettings["chrome"];
                _chromeDriver = new ChromeDriver(dir);
            }
            return _chromeDriver;
        }
    }
 
    private static void DisposeDrivers()
    {
        if (_internetExplorerDriver != null)
        {
            _internetExplorerDriver.Close();
            _internetExplorerDriver.Dispose();
            _internetExplorerDriver = null;
        }
        if (_chromeDriver != null)
        {
            _chromeDriver.Close();
            _chromeDriver.Dispose();
            _chromeDriver= null;
        }
    }
 
    [TearDown]
    public void TearDown()
    {
        DisposeDrivers();
    }
}

Step 2: Base Class

Second we create an abstract base class that will go select our WebDriver from the SetUpFixture via an abstract property.

[TestFixture]
public abstract class TestFixtureBase
{
    public abstract IWebDriver WebDriver { get; }
 
    [SetUp]
    public void SetUp()
    {
        WebDriver.Url = "about:blank";
    }
}

Step 3: Tests

Write your tests in abstract classes that inherit from the base class.

public abstract class TestFixtureA : TestFixtureBase
{
    [Test]
    public void Test1()
    {
        WebDriver.Url = "http://www.phandroid.com/";
        Assert.IsTrue(WebDriver.Title.StartsWith("Android Phone"));
    }
 
    [Test]
    public void Test2()
    {
        WebDriver.Url = "http://www.reddit.com/";
        Assert.IsTrue(WebDriver.Title.StartsWith("reddit"));
    }
}

Step 4: Driver Specific TestFixtures

Create a test fixture for each permutation of browser and test class.

public class ChromeTestFixtureA : TestFixtureA
{
    public override IWebDriver WebDriver
    {
        get { return SetUpFixture.ChromeDriver; }
    }
}
 
public class InternetExplorerTestFixtureA : TestFixtureA
{
    public override IWebDriver WebDriver
    {
        get { return SetUpFixture.InternetExplorerDriver; }
    }
}

Step 5: Run!

Okay, next week I'll actually talk about running WebDrivers in parallel. :)

kick it on DotNetKicks.com

Enjoy,
Tom

Saturday, February 11, 2012

Sharing a WebDriver across TestFixtures

I absolutely love WebDriver.

WebDriver (also known as Selenium 2.0) is a web testing tool that is both useful and easy, which is a very rare find. If you are doing web development with ASP.NET, you need to take 30 minutes of your time and go try out WebDriver. That is all the time it will take to get you hooked.

WebDriver and NUnit

To launch a browser you need only new up a Driver object for that browser. I used to create a new Driver in my TestFixtureSetup, and then close and dispose of that in the testFixtureTearDown. However now that Firefox does not persist my windows login credentials it can be very frustrating to have to log back in for every test fixture.

A solution to this problem is simply to share a single WebDriver across multiple TestFixtures. Fortunately NUnit's SetUpFixture makes this very easy to do.

Step 1: Create a SetUpFixture

This class's SetUp method will be called once before any TestFixtures run, and then it's TearDown will be called once after all the TestFixtures have completed.

[SetUpFixture]
public class SetUpFixture
{
    public const string ProfileKey = "firefoxprofile";
 
    public static IWebDriver WebDriver { get; private set; }
 
    [SetUp]
    public void SetUp()
    {
        var profileDir = ConfigurationManager.AppSettings[ProfileKey];
        if (String.IsNullOrWhiteSpace(profileDir))
        {
            WebDriver = new FirefoxDriver();
        }
        else
        {
            var profile = new FirefoxProfile(profileDir);
            WebDriver = new FirefoxDriver(profile);
        }
    }
 
    [TearDown]
    public void TearDown()
    {
        if (WebDriver == null)
            return;
 
        WebDriver.Close();
        WebDriver.Dispose();
    }
}

Step 2: Create an abstract base class

This abstract class will use the TestFixtureSetUp to copy the static WebDriver that was initialized by the SetUpFixture. I also like to have a SetUp that will clear the browser, just to make sure you are navigation to a new page from a blank one.

public abstract class TestFixtureBase
{
    public IWebDriver WebDriver { get; private set; }
 
    [TestFixtureSetUp]
    public void TestFixtureSetUp()
    {
        WebDriver = SetUpFixture.WebDriver;
    }
 
    [SetUp]
    public void SetUp()
    {
        WebDriver.Url = "about:blank";
    }
}

Step 3: Make your TestFixtures inherit the base class

These TestFixtures will now share the static WebDriver.

[TestFixture]
public class TestFixtureA : TestFixtureBase
{
    [Test]
    public void Test1()
    {
        WebDriver.Url = "http://www.phandroid.com/";
        Assert.IsTrue(WebDriver.Title.StartsWith("Android Phone"));
    }
}
 
[TestFixture]
public class TestFixtureB : TestFixtureBase
{
    [Test]
    public void Test1()
    {
        WebDriver.Url = "http://www.reddit.com/";
        Assert.IsTrue(WebDriver.Title.StartsWith("reddit"));
    }
}

Step 4: Run those tests!

If you love .NET 4.0 then stay tuned, because in my next post I'll explore running WebDrivers in PARALLEL! See you next week. :)

kick it on DotNetKicks.com

Enjoy,
Tom

Monday, October 17, 2011

Using the InternetExplorerDriver for WebDriver

Are you getting this error when trying to use the InternetExplorerDriver for WebDriver (Selenium 2.0)?

System.InvalidOperationException

"Unexpected error launching Internet Explorer. Protected Mode must be set to the same value (enabled or disabled) for all zones. (NoSuchDriver)"

Don't worry, it's easier to fix than it sounds: Simply go into your Internet Options of Internet Explorer, select the Security tab, and all four zones to have the same Protected Mode value (either all on or all off). That's it!

Sample Code

using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.IE;
 
namespace WebDriverTests
{
    public abstract class WebDriverTestBase
    {
        public IWebDriver WebDriver { get; set; }
 
        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {
            WebDriver = new InternetExplorerDriver();
        }
 
        [TestFixtureTearDown]
        public void TestFixtureTearDown()
        {
            if (WebDriver != null)
            {
                WebDriver.Close();
                WebDriver.Dispose();
            }
        }
 
        [SetUp]
        public void SetUp()
        {
            WebDriver.Url = "about:blank";
        }
    }
 
    [TestFixture]
    public class GoogleTests : WebDriverTestBase
    {
        [Test]
        public void SearchForTom()
        {
            WebDriver.Url = "http://www.google.com/";
 
            IWebElement searchBox = WebDriver
                .FindElement(By.Id("lst-ib"));
 
            searchBox.SendKeys("Tom DuPont");
            searchBox.Submit();
 
            IWebElement firstResult = WebDriver
                .FindElement(By.CssSelector("#search cite"));
 
            Assert.AreEqual("www.tomdupont.net/", firstResult.Text);
        }
    }
}
Shout it

Enjoy,
Tom

Friday, June 11, 2010

Selenium 2 and WebDriver Extensions for .NET

If you have not had a change to play around with the Selenium 2 and WebDriver, let me assure you that it is worth checking out! We have been using WebDriver to help create unit tests for our new product, CodeSmith Insight, which is build using ASP.NET MVC2 for the back end and the ExtJS framework for our AJAX client.

Despite already being infinitely better than Selenium 1, it is still under development and in need of some polish. The good news is that by simply implementing a few extension methods, it can become a bit more versatile and a lot easier to work with.

Below are just definitions for the extension methods, please download the code from here.

LINQ Driver Extensions

The Find methods for WebDriver work with different selectors, but none of which support using LINQ expressions to search the resulting elements.

public static IWebElement FindElement(this IWebDriver driver, By by, Func<IWebElement, bool> predicate)
public static IEnumerable<IWebElement> FindElements(this IWebDriver driver, By by, Func<IWebElement, bool> predicate)

// Example
IWebElement el = driver.FindElement(By.CssSelector("input"), e => e.Value == "Save");

WaitFor Driver Extensions

WebDriver is great when working with elements that already exist on the page, but it is not so good at working with dynamic page elements. This is a slightly hacky solution, but I created added Wait method to the driver itself that just sleeps the current thread, and then I added WaitFor methods that sleep while waiting for elements to appear. This makes it much easier to work with elements that result from AJAX calls.

public static void Wait(this IWebDriver driver, int seconds = DefaultTimeout)
public static IWebElement WaitForElement(this IWebDriver driver, By by, Func<IWebElement, bool> predicate = null, int seconds = DefaultTimeout)
public static IEnumerable<IWebElement> WaitForElements(this IWebDriver driver, By by, Func<IWebElement, bool> predicate = null, int seconds = DefaultTimeout)

// Example
IWebElement el = driver.WaitForElement(By.CssSelector("div.success"), e => e.Text == "Cart saved!");

FrameSwitcher

When switching between frames on a webpage, WebDriver actually changes the target of your Driver object. I created the FrameSwitcher class so that I could use a disposable pattern and reference my frame calls inside of a using block.

public static FrameSwitcher SwitchToFrame(this IWebDriver driver, By by)

// Example
using (driver.SwitchToFrame(By.Id("email_iframe")))
{
    driver.FindElement(By.CssSelector("input.to")).SendKeys("tom@codesmithtools.com");
    driver.FindElement(By.CssSelector("button.send")).Click();
}

IJavaScriptExecutor

I did not feel that their documentation made it readily apparent, but you can still execute JavaScript against the web page. This is done by casting your WebDriver as IJavaScriptExecutor.

public static IJavaScriptExecutor GetJavaScriptExecutor(this IWebDriver driver)

// Example
var js = driver.GetJavaScriptExecutor();
js.ExecuteScript("Insight.getMainViewport().viewTreePanel.setClick(true);");

Tuesday, December 16, 2008

Automated Testing w/ Selenium!

We here at CodeSmith recently started using Selenium to test our webapps.

If you haven't heard of Selenium, or you have been thinking about trying to create tests for any of your websites or web apps but didn't know where to start, go check it out! (Some links can be found at the bottom of the post.)

While Selenium is a great tool, and the guys at ThoughtWorks did a great job, Selenium definitely lacking in the same department as most all of it's open source brethren: documentation. Thus, this is my attempt to try and help out!

Our Automation Goals

Once we got our unit tests working, we wanted a quick and easy way to automate them; the goal was to be able to open N-Unit, run tests, and not have to worry about configuration and server connections. Also, this worked out well for running our unit tests with our build for continuous integration!

Disclaimer: For large scale or enterprise testing, there are better ways of accomplishing this (such as leaving the server running on a dedicated testing box), but we were just looking for a quick and easy way to run our tests!

Our Automation Implementation 

We are just running the Selenium Server out of the Test Fixture Setup; we launch it as a process, run our tests, then close it in the Test Fixture Tear Down.

First, we created a class to pull my settings from the App.Config...

public static class SeleniumAppSettings
{
    public static string ServerHost
    {
        get { return ConfigurationManager.AppSettings["SeleniumServerHost"] ?? "localhost"; } 
    }

...etc, etc, I only mention this so you know where I'm getting the values later!

Second, we create an abstract class for our Tests to inherit from...

public abstract class SeleniumTestBase
{
    private Process _process;
    protected ISelenium selenium;

    [TestFixtureSetUp]
    public void TestFixtureSetUp()
    {
        string arguments = String.Format("-jar \"{0}\" -firefoxProfileTemplate \"{1}\"",
            SeleniumAppSettings.ServerJar,
            SeleniumAppSettings.FirefoxProfile);
        _process = Process.Start("java", arguments);

        selenium = new DefaultSelenium(
            SeleniumAppSettings.ServerHost,
            SeleniumAppSettings.ServerPort,
            SeleniumAppSettings.Browser,
            SeleniumAppSettings.URL);
        selenium.Start();
    }

    [TestFixtureTearDown]
    public void TestFixtureTearDown()
    {
        if (selenium != null)
        {
            try
            {
                // Close Browser
                selenium.Stop();
                // Close Server
                selenium.ShutDownSeleniumServer();
            }
            catch { }
        }

        // Confirm server is closed.
        try
        {
            if (!_process.HasExited)
                _process.CloseMainWindow();
            if (!_process.HasExited)
                _process.Kill();
            _process.Close();
        }
        catch { }
    }
}

Third, we just created a class for whatever we wanted to test...

[TestFixture]
public class WebsiteTest : SeleniumTestBase
{
    [Test]
    public void MainPageTest()
    {
        selenium.Open("/");
        selenium.WaitForPageToLoad(SeleniumAppSettings.TimeOut);
        Assert.IsTrue(selenium.GetTitle().Equals("My Website"));
        Assert.IsTrue(selenium.IsTextPresent("Hello world!"));
    }
}

As long as it inherits the base it will take care of managing the server!

Firefox 3.0

A great feature with Selenium is that you can run your tests against multiple browsers just by customing your start up parameters. (...yes, my example is hard coded to use Firefox, see my next section for why!) So, many are disappointed that Selenium does not natively support Firefox 3.0...

However! There is a very simple, and very reliable work around! Here's what you need to do...

  1. Open selenium-server.jar (You can do this by using Winrar.)
  2. Recursively traverse the customProfileDirCUSTFFCHROME and customProfileDirCUSTFF directorys...
  3. ... and edit all the install.rdf files...
  4. ...update the <em:maxVersion> values from 2.0.0.* to 4.0.0.*

...this will allow Selenium to run the latest Firefox!

(My thanks to Mikhail Koryak for blogging about this fix!)

Persisting Custom SSH Certificates

When testing one of our websites we kept running into a problem: our test domains did not have legitimate SSH certificates, so we were unable to test our https pages!

Why/how was this a problem? When Selenium launches it creates a clean browser profile, this way no cookies plugins or other thing will interferer with the tests. However, this also means that no temporary certificates were being persisted from test to test!

The Solution

Create a custom Firefox profile, and tell Selenium to use that when it launches.

Close your Firefox browser and Run "firefox.exe -profileManager". This will allow you to create another Firefox profile (I named mine "Selenium"). Then launch that profile, go to the website you want to test, and store a permanent SSH certificate for it.

Above you may have noticed that I used the "-firefoxProfileTemplate" argument when launching the Selenium server, this tells the server to load the specified profile rather than creating a new one.

Note: The changes made to your profile while Selenium is running will not be saved! This is good because no cookies or other such things will be left behind to intefear with future tests. However, it has the slight draw back (for us at least) of cause Selenium to reinstall its Firefox plugins every time it runs. ...meh!

(Thanks to Chris Pitts for blogging about something very similar to this!)

Helpful Links

Selenium Homepage
Selenium IDE Homepage
Selenium Video Example/Tutorial
Selenium on Wikipedia

Real Time Web Analytics