开发者

How do I test multiple browsers with selenium and a single NUnit suite and keep it DRY?

I'm looking for a way to reuse one NUnit test suite without duplicating the entire suite for each browser. It seems like I would need a new fixture for each browser. Can I send some sort of environment variable or configuration setting from the NUnit gui to switch the browser? see below:

[TestFixture]
public c开发者_如何学Class User
{
    private ISelenium selenium;
    private StringBuilder verificationErrors;

    [SetUp]
    public void SetupTest()
    {
        // TheBrowser = How do I populate this variable from the NUnit gui? 
        selenium = new DefaultSelenium("localhost", 4444, **TheBrowser**, "http://localhost:52251/");
        selenium.Start();
        verificationErrors = new StringBuilder();
    }

    [TearDown]
    public void TeardownTest()
    {
      ...
    }

    [Test]
    public void SearchUser()
    {
       ... 
    }

}


NUnit 2.5+ supports Generic Test Fixtures which make testing in multiple browsers very straightforward. http://www.nunit.org/index.php?p=testFixture&r=2.5

Building the following will create two "GoogleTest" NUnit tests, one for Firefox and one for IE.

using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
using System.Threading;

namespace SeleniumTests 
{
    [TestFixture(typeof(FirefoxDriver))]
    [TestFixture(typeof(InternetExplorerDriver))]
    public class TestWithMultipleBrowsers<TWebDriver> where TWebDriver : IWebDriver, new()
    {
        private IWebDriver driver;

        [SetUp]
        public void CreateDriver () {
            this.driver = new TWebDriver();
        }

        [Test]
        public void GoogleTest() {
            driver.Navigate().GoToUrl("http://www.google.com/");
            IWebElement query = driver.FindElement(By.Name("q"));
            query.SendKeys("Bread" + Keys.Enter);

            Thread.Sleep(2000);

            Assert.AreEqual("bread - Google Search", driver.Title);
            driver.Quit();
        }
    }
}


Good question, plenty of people run into this issue. I'm a fan of injecting my browser into my test case using an IoC container. That lets me put all my browser configuration in a injection 'mudule'

I use the Java bindings and Guice as my IoC Container, but the principals are the same in .Net. You want a DefaultSelnium field in your class that gets injected. Your tests then use this object and dispose it when they're done. You may find you can inject it right away, or you may need to do the object creation in a setup method. A few things you should watch out for, depending on your unit testing framework:

  • Are your test classes created new for each test? JUnit creates a new instance of the test class for each test to be run. TestNG famously did away with this an reuses test class objects for each contained test. The problem with reuse is your injected DefaultSelenium instance is caried along for the ride, which could lead to problems if your tests are run in parallel, or change browser state.
  • Lazy Load your browser object If your Unit testing tool loads all the test classes right off the bat, it will try to create the browser objects up front, which is pretty resource intensive.

I'm sure you can Google for yourself better than I can, but these are some DI and NUnit links I thought looked promising.

NUnit integration tests and dependency injection

http://buildstarted.com/2010/08/24/dependency-injection-with-ninject-moq-and-unit-testing/

If you don't like DI I've heard of people using factory methods to generate their browser based on some external setup.


Here is an example unit test using a custom XUnit DataAttribute to provide the Driver to the test

using OpenQA.Selenium;
using SeleniumPageObjectsPatternExample.Attributes;
using SeleniumPageObjectsPatternExample.PageObjects;
using Xunit;
using Xunit.Extensions;

public class HomepageTests 
{
    [Theory]
    [Browser(Type.Firefox)]
    [Browser(Type.GoogleChrome)]
    public void HomepageLinksToBlogPage(IWebDriver webDriver)
    {
        // arrange 
        var expected = "some expected value";

        // act
        var homepage = new HomePage(webDriver, true);

        // assert
        Assert.True(homepage.BlogLink.Displayed);
        Assert.Equal(expected, homepage.Header.Text);
    }
}

Here is the custom DataAttribute

using System.Reflection;
using OpenQA.Selenium;
using SeleniumPageObjectsPatternExample.WebDriver;
using Xunit.Extensions;

public class BrowserAttribute : DataAttribute
{
    private IWebDriver WebDriver { get; set; }

    public BrowserAttribute(Type browser)
    {
        this.WebDriver = WebDriverFactory.Create(browser);
    }

    public override IEnumerable<object[]> GetData(MethodInfo methodUnderTest, System.Type[] parameterTypes)
    {
        return new[] { new[] { this.WebDriver } };
    }
}

Using this WebDriverFactory

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;

using Type = SeleniumPageObjectsPatternExample.Attributes.Type;

public class WebDriverFactory
{
    public static IWebDriver Create(Type browser)
    {
        IWebDriver webDriver;

        switch (browser)
        {
            case Type.Firefox:
                webDriver = new FirefoxDriver();
                break;
            case Type.GoogleChrome:
                webDriver = new ChromeDriver();
                break;
            default:
                webDriver = new ChromeDriver();
                break;
        }

        webDriver.Manage().Window.Maximize();

        return webDriver;
    }
}

And the browser type enum

public enum Type
{
    Firefox,
    GoogleChrome
}

I would advise you change the name of the enum from Type to something else...


I recommend an important variation on Lucifer's solution. Have the attribute supply a factory, not a driver, to Fact/Theory methods. Why? Well, when you see all those browser windows pop-up (n x m, where n = number of tests, m = browsers per test), you'll decide you only want them created when the test is actually going to run.

So with updates from prior solution, including some name changes...

using OpenQA.Selenium;
using SeleniumPageObjectsPatternExample.Attributes;
using SeleniumPageObjectsPatternExample.PageObjects;
using Xunit;
using Xunit.Extensions;

namespace SeleniumHelpers
{    
    public class HomepageTests 
    {
        [Theory]
        [WebDriver(DriverType.Firefox)]
        [WebDriver(DriverType.GoogleChrome)]
        public void HomepageLinksToBlogPage(WebDriverFactory factory)
        {
            // arrange 
            IWebDriver webDriver = factory.Create(); // Browser starts here.
            var expected = "some expected value";

            // act
            var homepage = new HomePage(webDriver, true);

            // assert
            Assert.True(homepage.BlogLink.Displayed);
            Assert.Equal(expected, homepage.Header.Text);

            // Optional cleanup (better wrapped in try/finally for entire method).
            webDriver.Quit();
            webDriver.Dispose();
        }
    }
}

Updated DataAttribute... (Note, I corrected the signature of the GetData() method to work with the latest xUnit.)

using System.Reflection;
using OpenQA.Selenium;
using Xunit.Extensions;

namespace SeleniumHelpers
{    
    public class WebDriverAttribute : DataAttribute
    {
        private WebDriverFactory WebDriverFactory { get; set; }

        public WebDriverAttribute(Type browser)
        {
            this.WebDriver = WebDriverFactory.Create(browser);
        }

        public override IEnumerable<object[]> GetData(MethodInfo methodUnderTest)
        {
            return new[] { new[] { this.WebDriverFactory } };
        }
    }
}

The new WebDriverFactory

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;

namespace SeleniumHelpers
{    
    public enum DriverType
    {
        Firefox,
        GoogleChrome
    }

    public class WebDriverFactory
    {
        private DriverType _driverType;
        public WebDriverFactory(DriverType driverType)
        {
            _driverType = driverType;
        }

        public static IWebDriver Create(Type browser)
        {
            IWebDriver webDriver;

            switch (browser)
            {
                case Type.Firefox:
                    webDriver = new FirefoxDriver();
                    break;
                case Type.GoogleChrome:
                    webDriver = new ChromeDriver();
                    break;
                default:
                    webDriver = new ChromeDriver();
                    break;
            }

            webDriver.Manage().Window.Maximize();

            return webDriver;
        }
    }
}


[SetUp]
    public void CreateDriver()
    {


        //driver = new TWebDriver();
        if (typeof(TWebDriver).Name == "ChromeDriver")
        {
            ChromeOptions options = new ChromeOptions();
            options.AddArguments("--incognito");
            driver = new ChromeDriver(options);
        }
        else if (typeof(TWebDriver).Name == "FirefoxDriver")
        {
            FirefoxOptions options = new FirefoxOptions();
            options.UseLegacyImplementation = false;
            options.SetPreference("browser.private.browsing.autostart", true);
            options.AddArgument("-private");
            driver = new FirefoxDriver(options);
        }
        else if (typeof(TWebDriver).Name == "InternetExplorerDriver")
        {
            InternetExplorerOptions options = new InternetExplorerOptions();
            options.BrowserCommandLineArguments = "-private";
            options.EnsureCleanSession = true;
            options.IgnoreZoomLevel = true;
            options.EnablePersistentHover = true;

            driver = new InternetExplorerDriver(options);

        } else
            driver = new TWebDriver();
    }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜