UI testing with Selenium and Coypu

Selenium is a great product for browser automation. It allows me to build great suites of interface tests. The only issue is the code to implement Selenium in the C# world isn't very clean. With a single page application, the developers end up with a lot of Thread sleeps, complex selection queries, etc. Enter Coypu.

Coypu is a C# wrapper around Selenium with an API inspired by Capybara. This means coding to Coypu is very straightforward. It supports automatic retries and sits nicely with unit tests.

Testing from the browser

I create a factory so I don't duplicate session instantiation. I'm not creating an actual factory abstraction or anything just stopping code duplication:

using Coypu;
using Coypu.Drivers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace App.Tests
{
    public class BrowserSessionFactory
    {
        private static BrowserSession browser;

        public static BrowserSession GetBrowser()
        {
            if (browser != null)
                return browser;

            var sessionConfiguration = new SessionConfiguration()
            {
                Browser = Browser.Chrome,
                AppHost = Config.AppUrl, // whatever url you want
                Timeout = TimeSpan.FromSeconds(5)
            };
            browser = new BrowserSession(sessionConfiguration);
            browser.MaximiseWindow();

            return browser;
        }
    }
}

Next, create some tests. It is very simple to work with:

using Coypu;
using Coypu.Drivers;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace App.Tests
{
    [TestFixture]
    public class HomeTests
    {
        BrowserSession browser;

        [OneTimeSetUp]
        public void TestFixtureSetUp()
        {
            browser = BrowserSessionFactory.GetBrowser();
        }

        [OneTimeTearDown]
        public void TestFixtureTearDown() { }

        [Test, Order(1)]
        public void Logout_Success()
        {
            browser.Visit(Config.DotcomUrl + "/account/logout");

            Assert.IsTrue(browser.HasContent("Sign In"));
        }

        [Test, Order(2)]
        public void Login_Success()
        {
            browser.Visit(Config.DotcomUrl + "/account/login");
            browser.FillIn("Input_UserName").With(Config.TestEmail);
            browser.FillIn("Input_Password").With(Config.TestPassword);
            browser.Check("Input_RememberMe");
            browser.ClickButton("Log In");

            Assert.IsTrue(browser.HasContent("Something on the welcome page."));
        }
    }
}

A few cool things are happening here. When the Log In button is clicked Coypu checks to see if the user lands on the next page. The .HasContent method continuously checks and rechecks until it hits the timeout period. That means that this code works regardless of whether or not it is a single page application or there are AJAX requests happening. Also, note the check for a button with "Log In" as the text. Not crazy query selections needed.

Screenshots

One thing I like to do when running automated interface tests is automatically taking screenshots. This allows the company to always have an up-to-date slide deck of the site. I just threw together a simple class that can take a screenshot with Coypu.

using Coypu;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace App.Tests
{
    public class Screenshot
    {
        public void Save(BrowserSession browser, string name)
        {
            if (!Config.SaveScreenshots)
            {
                return;
            }

            if (!Directory.Exists(Config.ScreenshotDirectory))
            {
                Directory.CreateDirectory(Config.ScreenshotDirectory);
            }

            browser.SaveScreenshot(Path.Combine(Config.ScreenshotDirectory, name + ".jpg"),
                System.Drawing.Imaging.ImageFormat.Jpeg);
        }

        public void SaveMobile(BrowserSession browser, string name)
        {
            if (!Config.SaveScreenshots)
            {
                return;
            }

            if (!Directory.Exists(Config.ScreenshotDirectory))
            {
                Directory.CreateDirectory(Config.ScreenshotDirectory);
            }

            browser.ResizeTo(320, 568);
            browser.SaveScreenshot(Path.Combine(Config.ScreenshotDirectory, name + ".jpg"),
                System.Drawing.Imaging.ImageFormat.Jpeg);
            browser.MaximiseWindow();
        }
    }
}

Then I just throw it into the tests or put it in [TearDown] attribute.

[Test, Order(2)]
public void Login_Success()
{
    browser.Visit(Config.DotcomUrl + "/account/login");
    browser.FillIn("Input_UserName").With(Config.TestEmail);
    browser.FillIn("Input_Password").With(Config.TestPassword);
    browser.Check("Input_RememberMe");
    browser.ClickButton("Log In");

    Assert.IsTrue(browser.HasContent("Something on the welcome page."));

    new Screenshot().Save(browser, "Login_Success");
}

Hope this helps.