Automated browser testing

  • Post-deployment smoke testing
  • Automated regression testing
  • Client-side unit tests
  • Auto-replay

The technology stack

  • xUnit
  • Selenium WebDriver
  • PhantomJS

xUnit



Selenium WebDriver



PhantomJsDriver



GhostDriver



PhantomJs

drives

implemented by

binds to

implements

 

A simple example

  • Some class contains a test method decorated with the [Fact] attribute
  • The test method creates a PhantomJsDriver instance to simulate a running browser
  • During the simulated browser session, the test user navigates to a URL, examines the DOM, sends page events, and makes assertions about the state of the simulated client
[Fact]
public void CanConnectToGoogle()
{
    using (var driver = new PhantomJSDriver())
    {
        driver.Navigate()
              .GoToUrl("https://www.google.com");

        Assert.Contains("//www.google.com", driver.Url);
    }
}

[Fact]
public void CanRenderSearchButton()
{
    using (var driver = new PhantomJSDriver())
    {
        driver.Navigate()
              .GoToUrl("https://www.google.com");

        var wait = new WebDriverWait(
                    driver, 
                    TimeSpan.FromSeconds(60));

        var input = wait.Until(
                      ExpectedConditions.ElementExists(
                        By.CssSelector(
                         "[type=submit]")));

        input.Click();
        Assert.True(/*...*/);
    }
}

[Fact] vs. [Theory]

xUnit's TheoryAttribute allows parameterized tests

[Fact]
public void CanConnectToGoogle()
{
    using (var driver = new PhantomJSDriver())
    {
        driver.Navigate()
              .GoToUrl("https://www.google.com");

        Assert.Contains("//www.google.com", driver.Url);
    }
}

[Theory]
[InlineData("www.google.com")]
[InlineData("www.microsoft.com")]
[InlineData("www.google.co.uk")]
public void CanConnectTo(string url)
{
    using (var driver = new PhantomJSDriver())
    {
        driver.Navigate()
              .GoToUrl("http://" + url);

        Assert.Contains("//" + url, driver.Url);
    }
}

The patterns

  • Hiding the IWebDriver instance
  • Fixtures and Collections
  • Arrange/Act/Assert
    (aka Arrange/Act/Verify)

Hiding the driver instance

Touch the IWebDriver

Why encapsulate the driver instance?

  • For every running PhantomJsDriver instance, you need an instance of PhantomJS.exe
  • If the PhantomJsDriver object isn't disposed, the process keeps running (this is a nightmare on TeamCity)
  • A using block takes care of this, but not if you want session persistence

Why encapsulate the driver instance?

  • For every running PhantomJsDriver instance, you need an instance of PhantomJS.exe
  • If the PhantomJsDriver object isn't disposed, the process keeps running (this is a nightmare on TeamCity)
  • A using block takes care of this, but not if you want session persistence

Okay. Why do I want session persistence?

Why encapsulate the driver instance?

  • For every running PhantomJsDriver instance, you need an instance of PhantomJS.exe
  • If the PhantomJsDriver object isn't disposed, the process keeps running (this is a nightmare on TeamCity)
  • A using block takes care of this, but not if you want session persistence

Okay. Why do I want session persistence?

  • Experimentation has shown that logging in before every test adds a lot of overhead
  • More importantly, 
    PhantomJsDriver has problems with isolating sessions from each other
  • Logging in/out during one test may throw off other tests
  • This forces us to deal with persistent state; may as well make the best of it

Is it worth it?

Maybe, maybe not. Browser testing needs to find a balance between different goals:

BrowserTestBase, persistent login sessions, and driver encapsulation are evolving strategies to find the right balance. If they turn out to be more cumbersome than they're worth, we'll move beyond them.

  • Performance
  • Robustness
  • Simplicity
  • Valid tests
  • Reliable results

Browser Testing

By Justin Morgan

Browser Testing

  • 383