Extensions Edit on GitHub


Storyteller 4.2 added a new ability to apply additive, reusable extensions or add ons to a Storyteller specification project:

Using IExtension

Extensions are implementations of the IExtension interface shown below:


public interface IExtension : IDisposable
{
    /// <summary>
    /// Called immediately after ISystem.Warmup() to do any necessary actions
    /// to set up the 
    /// </summary>
    /// <returns></returns>
    Task Start();

    /// <summary>
    /// Called before each specification run to add services or state to the ISpecContext
    /// </summary>
    /// <param name="context"></param>
    void BeforeEach(ISpecContext context);

    /// <summary>
    /// Called immediately after each specification run for clean up and logging actions
    /// </summary>
    /// <param name="context"></param>
    void AfterEach(ISpecContext context);
}

As a concrete example, here's the very early (and naive) version of a simplistic extension to add Selenium support to a running Storyteller project that just manages the lifecycle of the Selenium IWebDriver object being used in the specifications:


public class SeleniumExtension : IExtension
{
    private readonly Func<IWebDriver> _source;
    private IWebDriver _driver;

    // You'd want the web driver construction done lazily
    // so that it can be parallelized with the system bootstrapping
    public SeleniumExtension(Func<IWebDriver> source)
    {
        _source = source;
    }

    // Clean up after your
    public void Dispose()
    {
        _driver?.Close();
        _driver?.Dispose();
    }

    // This is executed during the bootstrapping of the system
    // under test
    public Task Start()
    {
        return Task.Factory.StartNew(() =>
        {
            _driver = _source();
        });
    }

    // This gets executed right before each specification
    // run. In this case, it puts the current IWebDriver
    // where the related ScreenFixture can find it during
    // Specification runs
    public void BeforeEach(ISpecContext context)
    {
        context.State.Store(_driver);
    }

    public void AfterEach(ISpecContext context)
    {
        // Nothing
    }
}

The extension has some value to establish a pattern of how the rather expensive Selenium IWebDriver object is bootstrapped, applied to subsequent specifications, and a clean system under test shutdown -- which is absolutely vital for iterating on automated tests and frequently ignored.

The AfterEach(ISpecContext) method shown above doesn't do anything in this case, but in other extensions it's been a great place to add custom logging to the Storyteller results.

Now, to apply an IExtension, you just need to add it to the CellHandling.Extensions list as you create your ISystem for your Storyteller specification project.

Here's an example from Storyteller's internal sample project:


public class SeleniumSystem : SimpleSystem
{
    protected override void configureCellHandling(CellHandling handling)
    {
        handling.Extensions.Add(new SeleniumExtension(new ChromeDriver()));
    }
}