Monthly Archives: April 2023

Converting data to a Table in SpecFlow

The use case for this is, I have a step Set fields on view which takes a table like this

Scenario: With table
  Given Set fields on view 
    | A  | B  | C  |
    | a1 | b1 | c1 |
    | a2 | b2 | c2 |      
    | a3 | b3 | c3 |                            

The code for this step, currently just outputs the data set to it to the console or log file, so looks like this

[Given(@"Set fields on view")]
public void GivenFieldsOnView(Table table)
{
   table.Log();
}

Now in some cases, I want to set fields on a view using an Examples. So in the current case we’re sending multiple rows in one go, but maybe in some situations we want to set fields, one row at a time, so we uses Examples like this

Scenario Outline: Convert To Table
  Given Set fields on view A=<A>, B=<B>, C=<C>

  Examples:
    | A  | B  | C  |
    | a1 | b1 | c1 |
    | a2 | b2 | c2 |      
    | a3 | b3 | c3 |            

We’ll then need a new step that looks like this

[Given(@"Set fields on view (.*)")]
public void GivenFieldsOnView(string s)
{
}

Ofcourse we can now split the variable s by comma, then split by = to get our key values, just like a Table and there’s nothing wrong with this approach, but an alternative is to have this transformation as a StepArgumentTransformation. So our code above would change to

[Given(@"Set fields on view (.*)")]
public void GivenFieldsOnView2(Table table)
{
   table.Log();
}

and now in our hook class we’d have something like this

[Binding]
public class StepTransformer
{
  [StepArgumentTransformation]
  public Table TransformToTable(string input)
  {
    var inputs = input.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
    var d = inputs.Select(s =>
      s.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries))
        .ToDictionary(v => v[0], v => v[1]);

    // this only handles a single row 
    var table = new Table(d.Keys.ToArray());
    table.AddRow(d.Values.ToArray());
    return table;
  }
}

Note: This is just an example, with no error handling and will only convert a string to a single row, it’s just a demo at this point.

So, now what we’ve done is create a transformer which understands a string syntax such as K1=V1, K2=V2… and can convert to a table for us.

I know that you’re probably asking, why, we could we not just execute the same code in the public void GivenFieldsOnView(string s) method ourselves. Well you could ofcourse do that, but now you’ve got a generic sort of method for making such transformations for you.

What I really wanted to try to do is use a single step to handle this by changing the regular expression, i.e. we have one method for both situations. Sadly I’ve not yet found a way to achieve this, but at least we can reduce the code to just handle the data as tables.

Ordering of our SpecFlow hooks

In my post Running code when a feature or scenario starts in SpecFlow I showed we can use hooks to run before the feature and scenario. However, what if we have, for example, a lot of separate scenario hooks but the order that they run in matters. Maybe we need to have the logging of the scenario title and this should run first.

The BeforeScenario has a property Order which we can assign a number like this

[BeforeScenario(Order = 1)]
public static void BeforeScenario(ScenarioContext scenarioContext)
{
  Debug.WriteLine($"Scenario starting: {scenarioContext.ScenarioInfo.Title}");
}

This will run before other scenarios, including those with no Order property.

Beware, if you set the [AfterScenario(Order = 1)] it would also be run first. Which you might not want in a logging situation, then (the only solutions I’ve found thuis far is) you’ll have to actually have an Order property in all AfterScenario attributes, i.e. explicitly state the order or all such hooks.

Generic steps using regular expressions within SpecFlow

The use case I have is as follows.

Using SpecFlow for running UI automation tests, we have lots of views off of the main application Window, we might write out scenarios specific to each view, such as

Given Trader view, set fields
Given Middle Office view, set fields
Given Sales view, set fields

and these in turn are written as the following steps

[Given(@"Trader view, set fields")]
public void GivenTraderViewSetFields()
{
  // set the fields on the trader view
}

[Given(@"Middle Office view, set fields")]
public void GivenMiddleOfficeViewSetFields()
{
  // set the fields on the middle office view
}

[Given(@"Sales view, set fields")]
public void GivenSalesViewSetFields()
{
  // set the fields on the sales view
}

obviously this fine, but if all our views had the same automation steps to set fields, then the code within will be almost exactly the same, so we might prefer to rewrite the step code to be more generic

[Given(@"(.*) view, set fields")]
public void GivenViewSetFields(string viewName)
{
  // find the view and set the fields using same automation steps
}

This is great, we’ve reduced our step code, but the (.*) accepts any value which means that if we have a view which doesn’t support the same steps to set fields, then this might confuse the person writing the test code. So we can change the (.*) to restrict the view names like this

[Given(@"(Trader|Middle Office|Sales) view, set fields")]
public void GivenViewSetFields(string viewName)
{
  // find the view and set the fields using same automation steps
}

Now if you add a new view like the step below, your SpecFlow plugin will highlight it as not having a matching step and if you run the test you’ll get the “No matching step definition found for one or more steps.” error.

Given Admin view, set fields

We can ofcourse write a step like the following code, and now the test works

[Given(@"Admin view, set fields")]
public void GivenAdminViewSetFields()
{
}

But this looks different in the code highlighting via the SpecFlow extension to our IDE but also, what if Admin and Settings views both can use the same automation steps, then we’re again back to creating steps per view.

Yes, we could reuse the actual UI automation code, but I want to reduce the number of steps also to a minimum. SpecFlow allows, what we might think of as regular expression overrides, so let’s change the above to now look like this

[Given(@"(Admin|Settings) view, set fields")]
public void GivenAdminViewSetFields(string viewName)
{
}

Obviously we cannot have the same method name with the same arguments in the same class, but from the Feature/Scenario design perspective it now appears that we’re writing steps for the same method whereas in fact each step is routed to the method that understands how to automate that specific view.

This form of regular expression override also means we might have the method for Trader, Middle Office and Sales in one step definition file and the Admin, Settings method in another step definition file making the separation more obvious (and ofcourse allowing us to then use the same method name).

What’s also very cool about using this type of expression is the the SpecFlow IDE plugins, will show via autocomplete, that you have a “Admin view, set fields”, “Trader view, set fields” etc. steps .

Transforming inputs in SpecFlow

One really useful option in SpecFlow, which maybe most people will never need, is the ability to intercept input and potentially transform it. So the example I have is, we want to include tokens wrapped in { }. These braces will include a token which can then be replaced by a “real” value. Let’s assume {configuration.XXX} means get the value XXX from the configuration file (App.config).

With this use case in mind, let’s assume we have a feature that looks like this

Feature: Transformer Tests

  Scenario: Transform a number
    Given The value {configuration.someValue1}

  Scenario: Transform a table
    Given The values 
    | One                        | Two                        |
    | {configuration.someValue1} | {configuration.someValue2} |

We’re going to hard code our transformer to simple change configuration.someValue1 to Value1 and configuration.someValue2 to Value2, but ofcourse the real data could be read from the App.Config or generated in some more useful way.

Our step file for the above feature file, might look like this

[Given(@"The value (.*)")]
public void GivenTheValue(string s)
{
  s.Should().Be("Value1");
}

[Given(@"The values")]
public void GivenTheValues(Table table)
{
  foreach (var row in table.Rows)
  {
    var i = 1;
    foreach (var rowValue in row.Values)
    {
      rowValue.Should().Be($"Value{i}");
      i++;
    }
  }
}

We create a class, usually stored in the Hooks folder which looks like the following, where we include methods with the StepArgumentTransformation attribute, meaning these methods are call for the given type to do something with the step argument.

[Binding]
public class StepTransformer
{
  private string SimpleTransformer(string s)
  {
    switch (s)
    {
      case "{configuration.someValue1}":
        return "Value1";
      case "{configuration.someValue2}":
        return "Value2";
      default:
        return s;
    }
  }
        
  [StepArgumentTransformation]
  public string Transform(string input)
  {
    return SimpleTransformer(input);
  }
        
  [StepArgumentTransformation]
  public Table Transform(Table input)
  {
    foreach (var row in input.Rows)
    {
      foreach (var kv in row)
      {
        row[kv.Key] = SimpleTransformer(kv.Value);
      }
    }
    return input;
  }
}

This is a very simple example of such code, what we’d do in a real world is create some more advanced transformer code and inject that via the constructor but the basic Transform method would look much like they are here.

Running code when a feature or scenario starts in SpecFlow

One of the useful capabilities within SpecFlow is for us to define hooks which starts when the feature or scenario starts and ends.

An obvious use of these is to log information about the feature or scenario – we’ll just use Debug.WriteLine to demonstrate this.

All we need to do is create a binding and then supply static methods marked with the BeforeFeature and AfterFeature attributes, which might look like this

[Binding]
public class FeatureHook
{
  [BeforeFeature]
  public static void BeforeFeature(FeatureContext featureContext)
  {
    Debug.WriteLine($"Feature starting: {featureContext.FeatureInfo.Title}");
  }

  [AfterFeature]
  public static void AfterFeature(FeatureContext featureContext)
  {
    Debug.WriteLine($"Feature ending: {featureContext.FeatureInfo.Title}");
  }
}

as you can probably guess, we can do the same with the BeforeScenario and AfterScenario attributes like this

[Binding]
public class ScenarioHook
{
  [BeforeScenario]
  public static void BeforeScenario(ScenarioContext scenarioContext)
  {
    Debug.WriteLine($"Scenario starting: {scenarioContext.ScenarioInfo.Title}");
  }

  [AfterScenario]
  public static void AfterScenario(ScenarioContext scenarioContext)
  {
    Debug.WriteLine($"Scenario ending: {scenarioContext.ScenarioInfo.Title}");
  }
}

Creating a SpecFlow code generation plugin

Over the last few months I’ve been helping develop an Appium/WinAppDriver framework (or library if you prefer) to allow one of the application to look to move one of my client’s application away from CodedUI (support is being phased out for this).

One of the problem I had is, I wanted to be able to put text into the Windows Clipboard and paste it into an various edit controls. The pasting made things slightly quicker but also, more importantly, bypassed some control’s autocomplete which occasionally messes up using SendKeys.

The problem is that the clipboard uses OLE and NUnit complains that it needs to be running in an STA.

To fix this, SpecFlow classes are partial so we can create new classes within another file with the same classname (and mark as partial) and namespace and then add the NUnit require attribute as below

[NUnit.Framework.Apartment(System.Threading.ApartmentState.STA)]
public partial class CalculatorFeature
{
}

This is all well and good. It’s hardly a big deal, but we have hundreds of classes and it’s just more hassle, remembering each time to do this, or you run the tests and when it hits a Paste from the Clipboard, it fails, wasting a lot of time before you’re reminded you need to create the partial class.

SpecFlow has the ability to intercept parameters passed into our steps, it has tags and hooks which allow you to customize behaviour and it also comes with a way to interact with the code generation process which we can use to solve this issue and generate the required attributes in the generated code.

The easiest way to get started is follow the steps below, although if you prefer, the code I’m presenting here is available on my PutridParrot.SpecFlow.NUnitSta GitHub repo., so you can bypass this post and go straight the code for this article if you prefer.

  • Grab the zip or clone the repo to get the following GenerateOnlyPlugin
  • You may want to update SampleGeneratorPlugin.csproj to
    <TargetFrameworks>net472;netcoreapp3.1</TargetFrameworks>
    

    I would keep to these two frameworks to begin with, as we have a few other places to change and it’s better to get something working before we start messing with the rest of the build properties etc.

  • In the build folder edit the .targets file to reflect the Core and !Core frameworks, i.e.
    <_SampleGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp3.1</_SampleGeneratorPluginFramework>
    <_SampleGeneratorPluginFramework Condition=" '$(MSBuildRuntimeType)' != 'Core'">net472</_SampleGeneratorPluginFramework>
    

    Again I’ve left _SampleGeneratorPluginFramework for now as I just want to get things working.

  • Next go to the .nuspec file and change the file location at the bottom of this file, i.e.
    <file src="bin\$config$\net472\PutridParrot.SpecFlow.NUnitSta.*" target="build\net472"/>
    <file src="bin\$config$\netcoreapp3.1\PutridParrot.SpecFlow.NUnitSta.dll" target="build\netcoreapp3.1"/>
    <file src="bin\$config$\netcoreapp3.1\PutridParrot.SpecFlow.NUnitSta.pdb" target="build\netcoreapp3.1"/>
    
  • Now go and change the name of the solution, the project names and the assembly namespace if you’d like to. Ofcourse this also means changing the PutridParrot.SpecFlow.NUnitSta in the above to your assembly name.

At this point you have a SpecFlow plugin which does nothing, but builds to a NuGet package and in your application you can set up your nuget.config with a local package pointing to the build from this plugin. Something like

<packageSources>
  <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
  <add key="Local" value="F:\Dev\PutridParrot.SpecFlow.NUnitSta\PutridParrot.SpecFlow.NUnitSta\bin\Debug"/>
</packageSources>

Obviously change the Local package repository value to the location of your package builds.

Now if you like, you can create a default SpecFlow library using the default SpecFlow templates. Ensure the SpecFlow.NUnit version is compatible with your plugin (so you may need to update the package from the template one’s). Finally just add the package you built and then build your SpecFlow test library.

If all went well, nothing will have happened, or more importantly, no errors will be displayed.

Back to our Plugin, thankfully I found some code to demonstrated adding global:: to the NUnit attributes on GitHub, so my thanks to joebuschmann.

  • Create yourself a class, mine’s NUnit3StaGeneratorProvider
  • This should implement the interface IUnitTestGeneratorProvider
  • The constructor should look like this (along with the readonly field)
    private readonly IUnitTestGeneratorProvider _unitTestGeneratorProvider;
    
    public NUnit3StaGeneratorProvider(CodeDomHelper codeDomHelper)
    {
       _unitTestGeneratorProvider = new NUnit3TestGeneratorProvider(codeDomHelper);
    }
    
  • We’re only interested in the SetTestClass, which looks like this
    public void SetTestClass(TestClassGenerationContext generationContext, string featureTitle, string featureDescription)
    {
       _unitTestGeneratorProvider.SetTestClass(generationContext, featureTitle, featureDescription);
    
       var codeFieldReference = new CodeFieldReferenceExpression(
          new CodeTypeReferenceExpression(typeof(ApartmentState)), "STA");
    
       var codeAttributeDeclaration =
          new CodeAttributeDeclaration("NUnit.Framework.Apartment", new CodeAttributeArgument(codeFieldReference));
             generationContext.TestClass.CustomAttributes.Add(codeAttributeDeclaration);
    }
    

    All other methods will just have calls to the _unitTestGeneratorProvider method that matches their method name.

  • We cannot actually use this until we register out new class with the generator plugins, so in your Plugin class, mine’s NUnitStaPlugin, change the Initialize to look like this
    public void Initialize(GeneratorPluginEvents generatorPluginEvents, 
       GeneratorPluginParameters generatorPluginParameters,
       UnitTestProviderConfiguration unitTestProviderConfiguration)
    {
       generatorPluginEvents.CustomizeDependencies += (sender, args) =>
       {
          args.ObjectContainer
             .RegisterTypeAs<NUnit3StaGeneratorProvider, IUnitTestGeneratorProvider>();
       };
    }
    
  • If you changed the name of your plugin class ensure the assembly:GeneratorPlugin reflects this new name, the solution will fail to build if you haven’t updated this anyway.

Once all these things are completed, you can build your NuGet package again. It might be worth incrementing the version and then update in your SpecFlow test class library. Rebuild that and the generated .feature.cs file should have classes with the [NUnit.Framework.Apartment(System.Threading.ApartmentState.STA)] attribute now. No more writing partial classes by hand.

This plugin is very simple, all we’re really doing is creating a really minimal implementation of IUnitTestGeneratorProvider which just adds an attribute to a TestClass and we registered this with the GeneratorPluginEvents, there’s a lot more why could potentially do.

Whilst this was very simple in the end. The main issues I had previously with this are that we need to either copy/clone or handle these build props and targets as well as use .nuspec to get our project packaged in a way that works with SpecFlow.