Setup Selenium, NUnit, and Visual Studio

Update

This article is still relevant but a more comprehensive guide exists at http://timothycope.com/c-selenium-automation. This new guide also covers Page Object Design Pattern (POM) and Selenium Server (Grid).

Background

Selenium automates browsers. That’s it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) also be automated as well.
http://docs.seleniumhq.org/

NUnit is a unit-testing framework for all .Net languages. Initially ported from JUnit, the current production release, version 2.6, is the seventh major release of this xUnit based unit testing tool for Microsoft .NET. It is written entirely in C# and has been completely redesigned to take advantage of many .NET language features, for example custom attributes and other reflection related capabilities. NUnit brings xUnit to all .NET languages.
http://www.nunit.org/

Install NUnit

You will need NUnit to execute your automated tests. Download the win MSI file and run it. Select a complete installation when prompted.

Setup Visual Studio

  1. Open Visual Studio
  2. Click File, click New, and select Project…
  3. In the left navigation pane, expand Templates, then Visual C#, and then select Class Library in the center window pane.
  4. In Package Manager Console, type Install-Package Selenium.WebDriverBackedSelenium
  5. In Package Manager Console, type Install-Package Selenium.Support
  6. In Package Manager Console, type Install-Package NUnit

Creating Your First Test

I renamed Class1.cs to AutomatedTest01.cs and that is reflected in the code below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenQA.Selenium;
using OpenQA.Selenium.IE;
using NUnit.Framework;
using OpenQA.Selenium.Firefox;

namespace SeleniumClassLibrary
{
    [TestFixture]
    public class AutomatedTest01
    {

        IWebDriver driver;

        [TestFixtureSetUp] // http://www.nunit.org/index.php?p=testFixture&r=2.5
        public void TestSetUp()
        {
            // Set the IWebDriver to use FireFox
            driver = new FirefoxDriver();

            // Set the timeout to 30 seconds
            driver.Manage().Timeouts().ImplicitlyWait(new TimeSpan(0, 0, 30));

        }

        [Test] // http://www.nunit.org/index.php?p=test&r=2.5
        public void NavigateToGoogle()
        {
            // Navigate to URL
            driver.Navigate().GoToUrl("http://www.google.com");

            // Assert that the IWebDriver window's title is "Google"
            Assert.AreEqual("Google", driver.Title);
        }

        [TestFixtureTearDown] // http://www.nunit.org/index.php?p=fixtureTeardown&r=2.5
        public void FixtureTearDown()
        {
            // Closes all windows associated with the IWebDriver
            driver.Quit();
        }
    }
}

Save and build the project.

Run the Automated Test

  1. Open NUnit
  2. Click File and select Open Project…
  3. Find the .dll from your Visual Studio Project
  4. Click Run in the center window pane

Importing Code from Selenium IDE for Firefox

You can download the Firefox add-on that allows you to record-and-click basic webpage tasks.

  1. In Selenium IDE:
    1. Click File, select Export Test Case As…, and select C# / NUnit / WebDriver
  2. You can copy+paste the code into a new .cs file in your project or import the cs file
  3. Save and Build your project

Note: You may have to make some manual changes to the code to have it playback correctly in NUnit.

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Support.UI;

namespace SeleniumClassLibrary
{
    // Genereated by Selenuim IDE for Firefox
    [TestFixture]
    public class AutomatedTest02
    {

        private IWebDriver driver;
        private StringBuilder verificationErrors;
        private string baseURL;
        private bool acceptNextAlert = true;

        [SetUp]
        public void SetupTest()
        {
            driver = new FirefoxDriver();
            baseURL = "http://timothycope.com/";
            verificationErrors = new StringBuilder();
        }

        [TearDown]
        public void TeardownTest()
        {
            try
            {
                driver.Quit();
            }
            catch (Exception)
            {
                // Ignore errors if unable to close the browser
            }
            Assert.AreEqual("", verificationErrors.ToString());
        }

        [Test]
        public void The01Test()
        {
            driver.Navigate().GoToUrl(baseURL + "/");
            Thread.Sleep(2500);
            driver.FindElement(By.LinkText("ABOUT TIMOTHY COPE")).Click();
            Thread.Sleep(2500);
            for (int second = 0; ; second++)
            {
                if (second >= 60) Assert.Fail("timeout");
                try
                {
                    if (IsElementPresent(By.XPath("//img[@alt='logo']"))) break;
                }
                catch (Exception)
                { }
                Thread.Sleep(1000);
            }
        }
        private bool IsElementPresent(By by)
        {
            try
            {
                driver.FindElement(by);
                return true;
            }
            catch (NoSuchElementException)
            {
                return false;
            }
        }

        private bool IsAlertPresent()
        {
            try
            {
                driver.SwitchTo().Alert();
                return true;
            }
            catch (NoAlertPresentException)
            {
                return false;
            }
        }

        private string CloseAlertAndGetItsText()
        {
            try
            {
                IAlert alert = driver.SwitchTo().Alert();
                string alertText = alert.Text;
                if (acceptNextAlert)
                {
                    alert.Accept();
                }
                else
                {
                    alert.Dismiss();
                }
                return alertText;
            }
            finally
            {
                acceptNextAlert = true;
            }
        }
    }
}

Solution on GitHub

You can download the project I made from GitHub:
https://github.com/kirbycope/SeleniumClassLibrary

C# Assembly Reflection

The System.Reflection namespace is tool that allows you to reflect the properties of an assembly without importing the assembly to your project references. A good example is the RESTful application I host on GitHub.

Reflected Assembly

This code (below) will load an assembly from the specified file path in a WPF application.

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

using System.Reflection;

namespace RESTful
{
    class ReflectedAssembly
    {
        private static Assembly _ReflectedAssembly = null;
        public static Assembly Assembly
        {
            get
            {
                // Get the path from x:Name="AssemblyPath"
                string assemblyPath = ((MainWindow)System.Windows.Application.Current.MainWindow).AssemblyPath.Text;

                if ((_ReflectedAssembly == null) && (assemblyPath != null) && (assemblyPath != ""))
                {
                    // Load the imported assembly
                    _ReflectedAssembly = Assembly.LoadFrom(assemblyPath);
                }
                return _ReflectedAssembly;
            }
        }
    }

}

07072014_01

Reflected Assembly Properties

Now that the assembly is loaded, you can see it’s properties.

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

using System.Reflection;

namespace RESTful
{
    class ReflectedProperties
    {
        public static PropertyInfo[] _Properties = null;
        public static PropertyInfo[] Properties
        {
            get
            {
                if (ReflectedType.Type != null)
                {
                    // Get the properties for the selected type
                    _Properties = ReflectedType.Type.GetProperties();
                }

                return _Properties;
            }
        }
    }
}

07072014_02

Reflected Assembly Property Type

Now that there is a list of Properties, we can select one and view its data type.

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

using System.Reflection;

namespace RESTful
{
    class ReflectedType
    {
        public static Type _ReflectedType = null;
        public static Type Type
        {
            get
            {
                if (ReflectedAssembly.Assembly != null)
                {
                    // Get the selected value from x:Name="Types"
                    string selectedType = ((MainWindow)System.Windows.Application.Current.MainWindow).Types.SelectedValue.ToString();

                    // Get the type
                    _ReflectedType = ReflectedAssembly.Assembly.GetType(selectedType);
                }

                return _ReflectedType;
            }
        }

    }
}

07072014_03

Further Reading

MSDN

  • Microsoft’s documentation on the System.Reflection namespace

GitHub

  • My project (RESTful) that is used as examples in this blog post

.Net Reflector

  • An application that uses reflection (not open source) to give you some ideas

MVC Highlight Active Navigation Tab

The top navigation bar on most sites has some form of highlight to allow the user to quickly see what section of the site they are viewing. In MVC this can be accomplished with a simple HTML and CSS modification.

Modify the Controller(s)

You will need to pass a variable <code>@currentPage</code> to your view from the appropriate controller(s).

//
// GET: /Product/

public ActionResult Index()
{
	ViewBag.CurrentPage = "Products";

	return View();
}

Modify the Shared View

You will need to modify the <body> tag of _Layout.cshtml to include an id. In the code (below) I go the extra step of converting the string to lowercase to match the CSS no matter how a developer types the CurrentPage in the controller.

@{
    string currentPage = @ViewBag.CurrentPage.ToLower();
}
<body class="homepage" id="@currentPage">

Modify the CSS

If using a minified CSS this part can be a bit of a pain, but still possible. In your CSS file add code or modify an existing block, a:hover for example, to include the body tags.

body#home a#home,
body#blog a#blog,
body#apparel a#apparel,
body#comics a#comics,
body#gadgets a#gadgets,
body#games a#games,
body#movies a#movies
{
	background: #d52349;
	color: #fff !important;
	font-weight: 700;
}

Tying it All Together

Whenever a bodyID matches the CSS, the active section will be highlighted.

07072014_001

Further Reading

Original credit goes to Bhavin Surela – Highlighting current page in MVC 3 – Slick Trick.

POST a Model with an Image Attachment

MVC POST a Model with Image

When POSTing a model you may need to stream some file content; like an image. The template controller will need to be modified to accomplish this. For this example, we will look at the source code I used to add ‘products’ to theCosmics.com. You will notice the use of the [Authorize] data annotation, which means only a logged in user can submit the POST.

The Product Model

Note that the model has properties for ImageData and ImageMimeType. If you need to update your models, be sure to enable data migrations.

namespace theCosmics.Models
{
    public class Product
    {
        // Product Details
        public int ID { get; set; }
        [Required]
        public string Category { get; set; }
        [Required]
        public string ProductName { get; set; }
        [Required]
        public decimal Price { get; set; }
        [Required]
        public string ProductHeader { get; set; }
        [Required]
        [DataType(DataType.MultilineText)]
        public string LongDescription { get; set; }
        [Required]
        public string TrackingURL { get; set; }

        // Image
        public byte[] ImageData { get; set; }
        public string ImageMimeType { get; set; }

        // Page Hit Tracking
        public int Hits { get; set; }

        // Featured Flag
        public bool Featured { get; set; }
    }
}

The Create View

You will need to modify the default template’s Html form to include the multipart/form-data. Right now my ListItems are hardcoded so please forgive that bit of rough code.

@model theCosmics.Models.Product

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm("Create", "Product", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Product</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Category)
        </div>
        @{
            // TODO: Make this a method to dynamically get/post the data
            List<SelectListItem> listItems = new List<SelectListItem>();
            listItems.Add(new SelectListItem
                {
                    Text = "Apparel",
                    Value = "Apparel"
                });
            listItems.Add(new SelectListItem
            {
                Text = "Comics",
                Value = "Comics"
            });
            listItems.Add(new SelectListItem
            {
                Text = "Gadgets",
                Value = "Gadgets"
            });
            listItems.Add(new SelectListItem
            {
                Text = "Games",
                Value = "Games"
            });
            listItems.Add(new SelectListItem
            {
                Text = "Movies",
                Value = "Movies"
            });
        }

        <div class="editor-field">
            @Html.DropDownListFor(model => model.Category, listItems, "-- Category --")
            @Html.ValidationMessageFor(model => model.Category)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ProductName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ProductName)
            @Html.ValidationMessageFor(model => model.ProductName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Price)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Price)
            @Html.ValidationMessageFor(model => model.Price)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ProductHeader)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ProductHeader)
            @Html.ValidationMessageFor(model => model.ProductHeader)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.LongDescription)
        </div>
        <div class="editor-field">
            @Html.TextAreaFor(model => model.LongDescription, new {style = "width: 50%; height: 200px;"})
            @Html.ValidationMessageFor(model => model.LongDescription)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.TrackingURL)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.TrackingURL)
            @Html.ValidationMessageFor(model => model.TrackingURL)
        </div>
        <div>
            <label>Image</label>
            <p>Upload new image: <input type="file" name="Image" /></p>
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Product", "Admin", new { Category = @ViewBag.Category } , null)
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Modifying the Edit Method for the Product Controller

//
// POST: /Product/Create
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product, HttpPostedFileBase image)
{
	if (ModelState.IsValid)
	{
		if (image != null)
		{
			product.ImageMimeType = image.ContentType;
			product.ImageData = new byte[image.ContentLength];
			image.InputStream.Read(product.ImageData, 0, image.ContentLength);
		}

		product.Hits = 0;
		db.Products.Add(product);
		db.SaveChanges();

		// Send user back to the appropriate admin
		return RedirectToAction("Product", "Admin", new { Category = product.Category });
	}

	return View(product);
}

In the code (above) I have modified the parameters of the Create method to include a second parameter
HttpPostedFileBase. As long as the image is not null, it will save two columns of data to the database; the first column is the MIME type (jpg, gif, png, etc.) and the second column is the byte data of the image. NOTE: You will need to modify your EDIT controller method, too.

Using the Image

The webapp can use the SQL data to reassemble the data into a usable image using a new controller.

//
// GET: /Product/GetImage/5

public FileContentResult GetImage(int id)
{
	Product product = db.Products.Find(id);
	if((product.ImageData != null) && (product.ImageMimeType != null))
	{
		return File(product.ImageData, product.ImageMimeType);
	}
	else
	{
		return null;
	}
}

This controller will accept the model’s ID and return the image for that product.

Further Reading

Pro ASP.NET MVC 2 Framework

  • Much of the code (above) is derived from this book.

API Testing with Telerik Test Studio

Background

Telerik Test Studio is an application I have used in Software QA Automation. One project I had was api testing a REST web api for an MVC solution. This code can be used in almost any testing suite that allows you to import .NET 4.5.

API Testing Documentation

I am not going to recreate the post for this site just yet. You can find my original post; http://www.telerik.com/support/code-library/testing-rest-api-using-test-studio

C# Primer

About C#

C# is one of the programming languages designed for the Common Language Infrastructure. C# is built on the syntax and semantics of C++, allowing C programmers to take advantage of .NET and the common language runtime.
http://en.wikipedia.org/wiki/C_Sharp_(programming_language)

Download C#

The fastest way to start developing applications for Windows 8.1, Windows Phone, the web or the cloud. Visual Studio Express products provide a free development environment to develop applications for the latest platforms. Since it is Visual Studio, applications created in Express can be supported in other versions of Visual Studio as your needs grow.
http://www.visualstudio.com/en-us/products/visual-studio-express-vs.aspx

Starting C#

Learn how easy it is to set up Visual Studio and connect to Visual Studio Online. Once you’re set up, we’ll show you how to create and organize your backlog, share your code with your team, and configure your app for continuous builds and deployment. Choose any of the sections below to get started.
http://www.visualstudio.com/get-started/overview-of-get-started-tasks-vs

Hello World!

The following procedure creates a C# version of the traditional “Hello World!” program. The program displays the string Hello World!
http://msdn.microsoft.com/en-us/library/k1sx6ed2.aspx

To create and run a console application

  1. Start Visual Studio.
  2. On the menu bar, choose File, New, Project.
  3. The New Project dialog box opens.
  4. Expand Installed, expand Templates, expand Visual C#, and then choose Console Application.
  5. In the Name box, specify a name for your project, and then choose the OK button.
  6. The new project appears in Solution Explorer.
  7. If Program.cs isn’t open in the Code Editor, open the shortcut menu for Program.cs in Solution Explorer, and then choose View Code.
  8. Replace the contents of Program.cs with the following code:
// A Hello World! program in C#.
using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}
"Hello World!"

Variables

The C# typing system contains the following categories: Value types, Reference types, and Pointer types.
Variables that are value types store data, and those that are reference types store references to the actual data. Reference types are also referred to as objects. Pointer types can be used only in unsafe mode.
It is possible to convert a value type to a reference type, and back again to a value type, by using boxing and unboxing. With the exception of a boxed value type, you cannot convert a reference type to a value type.
This section also introduces void.
Value types are also nullable, which means they can store an additional non-value state. For more information, see Nullable Types.
http://msdn.microsoft.com/en-us/library/3ewxz6et.aspx

// A Hello World! program in C#.
using System;
namespace HelloWorld
{
    class Hello
    {
	string message = "Hello World!";
        static void Main()
        {
            Console.WriteLine(message);

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}
"Hello World!"

Functions

A method is a code block that contains a series of statements. A program causes the statements to be executed by calling the method and specifying any required method arguments. In C#, every executed instruction is performed in the context of a method. The Main method is the entry point for every C# application and it is called by the common language runtime (CLR) when the program is started.
http://msdn.microsoft.com/en-us/library/ms173114.aspx

using System;
namespace HelloWorld
{
	class Hello
	{
		string message = "Hello World!";
		static void Main()
        	{
			// Call the method
			WriteMessage()

			// Keep the console window open in debug mode.
			Console.WriteLine("Press any key to exit.");
			Console.ReadKey();
        	}

		static void WriteMessage()
		{
			Console.WriteLine(message);
		}
	}
}
"Hello World!"

Passing Variables to Functions

Previously, message was accessible because its accessibility level is private (default). Meaning it is accessible from within the Hello class. In this example, we will pass a string value to a method as a parameter.

using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            // Call the method
            WriteMessage("Hello World!")

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

		static void WriteMessage(string message)
		{
			Console.WriteLine(message);
		}
    }
}
"Hello World!"

Error Handling

A common usage of catch and finally together is to obtain and use resources in a try block, deal with exceptional circumstances in a catch block, and release the resources in the finally block.
http://msdn.microsoft.com/en-us/library/dszsf989.aspx

using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            try
			{
				// Call the method
				WriteMessage("Hello World!");
			}
			catch (Exception e)
			{
				// Write exception to console
                Console.WriteLine(e.Message);
			}
            finally
			{
				// Keep the console window open in debug mode.
				Console.WriteLine("Press any key to exit.");
				Console.ReadKey();
			}
        }

        static void WriteMessage(string message)
        {
            Console.WriteLine(message);
        }
    }
}
"Hello World!"

Further Reading

C# Programming Guide

  • Most of this section assumes that you already know something about C# and general programming concepts. If you are a complete beginner with programming or with C#, you might want to visit the C# Developer Center, where you can find many tutorials, samples and videos to help you get started.

.Net Pearls

  • Documentation intended to be as simple as possible. Shows examples and performance(speed, memory, etc.).

Related Reading

StackOverflow

  • If you have a question, then chances are someone else did to. Chances are if you search you question on Goggle, you will get a StackOverflow result.