Combining CodedUI Code first, SpecFlow and Visual Studio 2012

Today we tried to combine a number of different test technologies on the latest version of Visual Studio and ran into a few small issues. We found a work around for all of them. So if you want to use the BDD style of Specflow to test the UI of your applications, hold tight.

Make sure you have a version of Visual Studio that supports CodedUI tests (Premium or Ultimate) and install the following add-ons to enable SpecFlow:

Create a new test project (we're using an MsTest based test project, since CodedUI requires that) and use the package manager or the package manager console to add the following NuGet packages:

Now you'll need to update a few references to make Visual Studio load the 2012 version of the referenced assemblies. Check the assemblies in the references and update any reference from the 2010 to the 2012 version.

  • Microsoft.VisualStudio.TestTools.UITest.Extension

Edit the app.config of your test project (or add one) and add a number of binding redirects to explain the test runner you want to load the 2012 version of these assemblies:


<configuration>
<runtime>
<assemblybinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentassembly>
<assemblyidentity culture="neutral" name="Microsoft.VisualStudio.QualityTools.CodedUITestFramework" publickeytoken="b03f5f7f11d50a3a">
<bindingredirect newversion="11.0.0.0" oldversion="10.0.0.0"></bindingredirect>
</assemblyidentity>
</dependentassembly>
<dependentassembly>
<assemblyidentity culture="neutral" name="Microsoft.VisualStudio.TestTools.UITest.Common" publickeytoken="b03f5f7f11d50a3a">
<bindingredirect newversion="11.0.0.0" oldversion="10.0.0.0"></bindingredirect>
</assemblyidentity>
</dependentassembly>
<dependentassembly>
<assemblyidentity culture="neutral" name="Microsoft.VisualStudio.TestTools.UITest.Extension" publickeytoken="b03f5f7f11d50a3a">
<bindingredirect newversion="11.0.0.0" oldversion="10.0.0.0"></bindingredirect>
</assemblyidentity>
</dependentassembly>
<dependentassembly>
<assemblyidentity culture="neutral" name="Microsoft.VisualStudio.TestTools.UITesting" publickeytoken="b03f5f7f11d50a3a">
<bindingredirect newversion="11.0.0.0" oldversion="10.0.0.0"></bindingredirect>
</assemblyidentity>
</dependentassembly>
</assemblybinding>
</runtime>
</configuration>

This should allow you to build and run any tests. Now configure SpecFlow to use MsTest as the test framework:


<configuration>
<configsections>
<section name="specFlow" type="TechTalk.SpecFlow.Configuration.ConfigurationSectionHandler, TechTalk.SpecFlow">
</configSection>
</section>
<specflow>
<unittestprovider name="MsTest" />
</specflow>
</configuration>

Follow the steps laid out in the CodedUI Code First tutorials to setup your Homepage class and to write methods to find data on the page. When you add a new Unit Test, remove the [TestClass] attribute from the class and replace it with [CodedUITest].

Setup SpecFlow to generate CodedUI test by creating a custom Generator provider and setting configure it in the app.config of the test project.

Don't forget the following important note all the way at the bottom of the custom generator provider post:

Ensuring the SpecFlow hooks can use the CodedUI API

If you want to use any of the SpecFlow Hooks as steps such as [BeforeTestRun],[BeforeFeature], [BeforeScenario], [AfterTestRun], [AfterFeature] or [AfterScenario], unless you add specific code you will receive an error: Microsoft.VisualStudio.TestTools.UITest.Extension.TechnologyNotSupportedException: The browser is currently not supported

This is resolved by adding a Playback.Initialize(); call in your [BeforeTestRun] step, and a Playback.Cleanup(); in your [AfterTestRun] step.

We've created a base class derived off the PageTest class to handle this for us (updated to not launch all browser windows at once):


using Microsoft.Services.TestTools.UITesting.Html;
using System;
using Microsoft.Services.TestTools.UITesting.Html;
using Microsoft.VisualStudio.TestTools.UITesting;
using TechTalk.SpecFlow;
namespace Specflow.CodedUI.Configuration
{
    [Binding]
    public static class SpecflowCodedUI
    {
        [BeforeScenario("CodedUI")]
        public static void SpecflowBeforeTestRun()
        {
            Playback.Initialize();
        }
        [AfterScenario("CodedUI")]
        public static void SpecflowAfterTestRun()
        {
            Playback.Cleanup();
        }
    }
    public class SpecflowPageTest<T> : PageTest<T> where T : Page, new()
    {
        protected new T TestedPage 
        { 
            get
            {
                if (base.TestedPage == null)
                {
                    Initialize();
                }
                return base.TestedPage;
            }
            set
            {
                base.TestedPage = value;
            }
        }
        [AfterScenario("CodedUI")]
        public virtual void SpecflowCleanUp()
        {
            if (base.TestedPage != null)
            {
                this.Cleanup();
                base.TestedPage = null;
            }
        }
    }
}

With all of this work done, you will be able to run your CodedUI Code First tests from SpecFlow inside Visual Studio 2012. The only other issue we found was that Code Analysis would still complain that it could not find the proper assembly version. Instead of adding the Binding Redirects to the fxcopcmd.exe.config as well, you can set the following item in that same config file:


...
<appsettings>
<!-- 
Indicates the mode to use when matching references to assemblies.
None                       Do not match strong names at all, any assembly with the same
file name is considered a match.
StrongName                 Strong names including assembly name, version, culture and 
public key token must exactly match.
StrongNameIgnoringVersion  Strong names including assembly name, culture and public
key token must exactly match. Assemblies with an equal or
greater version are considered a match.                      
-->
<!--<add key="AssemblyReferenceResolveMode" value="StrongName" />-->
<add key="AssemblyReferenceResolveMode" value="StrongNameIgnoringVersion"/>
</appsettings>