diff --git a/CSF.Screenplay.Docs/docs/dependencyInjection/index.md b/CSF.Screenplay.Docs/docs/dependencyInjection/index.md index 63fe0551..d17fc9d9 100644 --- a/CSF.Screenplay.Docs/docs/dependencyInjection/index.md +++ b/CSF.Screenplay.Docs/docs/dependencyInjection/index.md @@ -5,14 +5,22 @@ uid: DependencyInjectionMainArticle # Dependency injection The Screenplay design pattern is fundamentally compatible with and based upon [dependency injection], aka DI. -You may add Screenplay to an existing [container] if you wish, via the [`AddScreenplay`] extension method. -Alternatively you may create an instance of [`Screenplay`] which uses its own self-contained DI container with the static [`Screenplay.Create`] helper method. + +* You may add Screenplay to an existing [container] via the [`AddScreenplay`] extension method +* You may create a self-contained instance of [`Screenplay`] via the static [`Screenplay.Create`] helper method + * Use this is you wish to integrate with an app or library which does not use DI itself +* [Test integrations] have their own ways to integrate with DI + * [DI integration with NUnit 3+] + * [DI integration with Reqnroll/Specflow] [dependency injection]: https://en.wikipedia.org/wiki/Dependency_injection [container]: xref:Microsoft.Extensions.DependencyInjection.IServiceCollection [`AddScreenplay`]: xref:CSF.Screenplay.ScreenplayServiceCollectionExtensions.AddScreenplay(Microsoft.Extensions.DependencyInjection.IServiceCollection) [`Screenplay`]: xref:CSF.Screenplay.Screenplay [`Screenplay.Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection}) +[Test integrations]: ../../glossary/Integration.md +[DI integration with NUnit 3+]: ../gettingStarted/nunit3/index.md#step-3-write-a-screenplay-factory +[DI integration with Reqnroll/Specflow]: ../gettingStarted/reqnroll/index.md#step-3-configuring-dependency-injection ## Learn more about DI in Screenplay diff --git a/CSF.Screenplay.Docs/docs/gettingStarted/nonTesting/index.md b/CSF.Screenplay.Docs/docs/gettingStarted/nonTesting/index.md index 0910c40d..0acb7aa7 100644 --- a/CSF.Screenplay.Docs/docs/gettingStarted/nonTesting/index.md +++ b/CSF.Screenplay.Docs/docs/gettingStarted/nonTesting/index.md @@ -5,31 +5,63 @@ Screenplay may be added to any application or library, _via dependency injection This is as simple as installing the **[CSF.Screenplay]** NuGet package and adding Screenplay to your service collection. For more information, see the documentation for [`ScreenplayServiceCollectionExtensions`]. +Once Screenplay has been added to your DI, [you may resolve and use Screenplay-related services from dependency injection]. +To execute some logic in the scope of a [`Performance`], consider using the method [`Screenplay.ExecuteAsPerformanceAsync`]. + +[you may resolve and use Screenplay-related services from dependency injection]: ../../dependencyInjection/InjectingServices.md +[`Performance`]: xref:CSF.Screenplay.IPerformance +[`Screenplay.ExecuteAsPerformanceAsync`]: xref:CSF.Screenplay.Screenplay.ExecuteAsPerformanceAsync(System.Func{System.IServiceProvider,System.Threading.CancellationToken,System.Threading.Tasks.Task{System.Nullable{System.Boolean}}},System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken) + +## Example + +The following example shows the core components for using Screenplay outside of a testing framework. + +* DI registration which adds the Screenplay architecture +* A start-point class which begins one or more performances +* Each performance is coordinated from its own class which implements [`IHostsPerformance`] + +_This is the recommended pattern_ for consuming Screenplay outside of a testing framework. +See the documentation for the [`Screenplay`] and [`ScreenplayExtensions`] classes for other techniques. + +If you wish to activate Screenplay extensions, be sure to add these to the DI container in your application startup. +For example, to add [the Screenplay/Selenium extension], add a line like `services.AddSelenium()`. + ```csharp using CSF.Screenplay; -// IServiceCollection services; +// In your app startup, services is an instance of IServiceCollection, your +// application's DI container. services.AddScreenplay(); +// optionally add further services to the container, as normal -// ... then, after the service collection is built: -// IServiceProvider serviceProvider; -var screenplay = serviceProvider.GetService(); +// ... now in your regular app logic you may now constructor inject Screenplay: +public class MyScreenplayConsumer(Screenplay screenplay) +{ + public async Task StartScreenplay() + { + // As many performances as you want + await screenplay.ExecuteAsPerformanceAsync(); + } +} -// Or you may constructor-inject an instance of Screenplay into -// any type which was resolved from the service provider +public class SamplePerformance(IStage stage) : IHostsPerformance +{ + public Task ExecutePerformanceAsync(CancellationToken token) + { + // Use the injected stage to get an actor and execute your Screenplay logic + } +} ``` -Once Screenplay has been added to your DI, [you may resolve and use Screenplay-related services from dependency injection]. -To execute some logic in the scope of a [`Performance`], consider using the method [`Screenplay.ExecuteAsPerformanceAsync`]. - [as a tool for automating software tests]: ../../bestPractice/SuitabilityAsATestingTool.md [CSF.Screenplay]: https://www.nuget.org/packages/CSF.Screenplay [`ScreenplayServiceCollectionExtensions`]: xref:CSF.Screenplay.ScreenplayServiceCollectionExtensions -[you may resolve and use Screenplay-related services from dependency injection]: ../../dependencyInjection/InjectingServices.md -[`Performance`]: xref:CSF.Screenplay.IPerformance -[`Screenplay.ExecuteAsPerformanceAsync`]: xref:CSF.Screenplay.Screenplay.ExecuteAsPerformanceAsync(System.Func{System.IServiceProvider,System.Threading.CancellationToken,System.Threading.Tasks.Task{System.Nullable{System.Boolean}}},System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken) +[`IHostsPerformance`]: xref:CSF.Screenplay.IHostsPerformance +[`Screenplay`]: xref:CSF.Screenplay.Screenplay +[`ScreenplayExtensions`]: xref:CSF.Screenplay.ScreenplayExtensions +[the Screenplay/Selenium extension]: ../../extensions/selenium/index.md -## Abstractions package +## The Abstractions package If your solution is separated into multiple projects/assemblies then only your entry-point project needs the full CSF.Screenplay NuGet package. Once Screenplay has been added to DI, other projects in the solution may consume its logic, only requiring the [CSF.Screenplay.Abstractions] package. diff --git a/CSF.Screenplay.Docs/docs/gettingStarted/nunit3/index.md b/CSF.Screenplay.Docs/docs/gettingStarted/nunit3/index.md index fdcd43ec..2247d43b 100644 --- a/CSF.Screenplay.Docs/docs/gettingStarted/nunit3/index.md +++ b/CSF.Screenplay.Docs/docs/gettingStarted/nunit3/index.md @@ -1,3 +1,7 @@ +--- +uid: NUnit3GettingStartedArticle +--- + # Screenplay & NUnit tutorial Begin writing NUnit tests using Screenplay by following these steps. @@ -5,8 +9,8 @@ Further detail is provided below. 1. Ensure that your test project uses [NUnit version 3.6.0] or higher 1. Install the NuGet package **[CSF.Screenplay.NUnit]** to your test project -1. Write a class which implements [`IGetsScreenplay`] -1. Decorate your test assembly with [`ScreenplayAssemblyAttribute`], referencing your implementation of `IGetsScreenplay` +1. Write a Screenplay Factory class, implementing [`IGetsScreenplay`] +1. Decorate your test assembly with [`ScreenplayAssemblyAttribute`], referencing that Screenplay Factory 1. Write your tests, decorating each test method with [`ScreenplayAttribute`] 1. Add parameters to your test methods to access the Screenplay architecture @@ -20,20 +24,18 @@ Further detail is provided below. [`ScreenplayAttribute`]:xref:CSF.Screenplay.ScreenplayAttribute [these best practices for writing tests which use Screenplay]: ../../bestPractice/WritingTests.md -## Decorating your test assembly with `[ScreenplayAssembly]` +## Step 3: Write a Screenplay factory -So that your tests may make use of a [`Screenplay`], you must install the Screenplay extension to the NUnit testing framework. -This is **steps 3 & 4** in the list above. -This is achieved using the [`ScreenplayAssemblyAttribute`]. -Place a line of code somewhere in your test project, outside of any type definition like this: +NUnit requires a factory class which implements [`IGetsScreenplay`]. +This tells the underlying framework how to create and configure the `Screenplay` for your tests. +This factory class is user-customisable, because it is where you would configure **[dependency injection]** for your own Screenplay. -```csharp -[assembly: CSF.Screenplay.ScreenplayAssembly(typeof(MyScreenplayFactory))] -``` +The example below shows a minimal template for a factory; replace the commment with DI service registrations for anything that you would like to be available to Screenplay. +There's no need to use `.AddScreenplay()` here, _the integration does that already_. +For example, to add [the Screenplay/Selenium extension], add `services.AddSelenium()` here. -There is one other thing you must do, and that is to write a screenplay factory class, which configures how the `Screenplay` should be created for your tests. -A screenplay factory is a class which must implement [`IGetsScreenplay`]. -Consider the example below as a starting point for writing your own. +> [!IMPORTANT] +> The Screenplay Factory class _must have a parameterless constructor_. ```csharp using CSF.Screenplay; @@ -50,12 +52,27 @@ public class MyScreenplayFactory : IGetsScreenplay } ``` +[dependency injection]: ../../dependencyInjection/index.md +[the Screenplay/Selenium extension]: ../../extensions/selenium/index.md + +## Step 4: Decorate your test assembly with `[ScreenplayAssembly]` + +One line of boilerplate code is required per test assembly (project). +Its purpose is to signpost the Screenplay Factory which you wrote in step 3 (above) to the NUnit/Screenplay integration. + +Place a line of code like the following in your test project. +This should be outside of any type; it may go into its own source file if you wish. +Be sure to replace `MyScreenplayFactory` with the factory class your wrote in step 3. +See the docs for [`ScreenplayAssemblyAttribute`] for more info. + +```csharp +[assembly: CSF.Screenplay.ScreenplayAssembly(typeof(MyScreenplayFactory))] +``` + > [!IMPORTANT] > When using NUnit with Screenplay, every Screenplay-using test within a test assembly (thus, within a .NET project) must share the same instance of `Screenplay`. > This is not expected to be problematic, as all the `Screenplay` object does is set-up the Screenplay architecture and dependency injection for the tests. -[`Screenplay`]: xref:CSF.Screenplay.Screenplay - ## Writing test methods When writing test methods, the test methods must be decorated with [`ScreenplayAttribute`], which activates Screenplay for that particular test method. diff --git a/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/Specflow.md b/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/Specflow.md new file mode 100644 index 00000000..6e48f711 --- /dev/null +++ b/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/Specflow.md @@ -0,0 +1,20 @@ +# Using CSF.Screenplay with legacy SpecFlow + +For now, _CSF.Screenplay supports SpecFlow_. +It is strongly recommended to [upgrade to Reqnroll at your earliest opportunity]. +No promises can be made about future support, particularly as Reqnroll diverges from SpecFlow's original architecture. + +## Adapting instructions for SpecFlow + +Users of SpecFlow may use [the Getting Started guide for Reqnroll], with the following adaptations. + +* At step 1, install SpecFlow version 3.4.3 or higher instead +* At step 2, install the package [CSF.Screenplay.SpecFlow] instead +* When using the framework, replace any usage of the `Reqnroll` namespace with `TechTalk.SpecFlow` + +The remainder of the guide holds true for both Reqnroll or SpecFlow. +The same techniques & syntax mentioned in the guide are supported in both frameworks, with only the namespace difference noted above. + +[upgrade to Reqnroll at your earliest opportunity]: https://docs.reqnroll.net/latest/guides/migrating-from-specflow.html +[the Getting Started guide for Reqnroll]: ./index.md +[CSF.Screenplay.SpecFlow]: https://www.nuget.org/packages/CSF.Screenplay.SpecFlow diff --git a/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md b/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md index db49ee81..b53793c4 100644 --- a/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md +++ b/CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md @@ -2,32 +2,58 @@ > [!TIP] > Are you using the legacy **SpecFlow**? -> **Reqnroll** is the maintained fork of SpecFlow, so it's recommended you upgrade your projects ASAP. -> -> For now, CSF.Screenplay continues to support SpecFlow. -> Use the [CSF.Screenplay.SpecFlow] package and SpecFlow v3.4.3 or higher instead. -> The remainder of the instructions below work for either Reqnroll or SpecFlow. +> [Reqnroll is the maintained fork of SpecFlow], so it's recommended you upgrade your projects ASAP. +> See [the documentation on using CSF.Screenplay with SpecFlow] for more information. Begin writing Reqnroll tests using Screenplay by following these steps. Further detail is provided below. 1. Ensure that your test project uses [Reqnroll version 2.0.0] or higher 1. Install the NuGet package **[CSF.Screenplay.Reqnroll]** to the project which will contain your `.feature` files -1. _Optional:_ Add services to dependency injection which will be required by the [Abilities] you intend to use. If required, [use Reqnroll context injection & hooks] to add these to the DI container. +1. Configure dependency injection with any services you intend to use in your tests 1. Write step binding classes which dependency-inject and use Screenplay's architecture +[Reqnroll is the maintained fork of SpecFlow]: https://reqnroll.net/news/2025/01/specflow-end-of-life-has-been-announced/ +[the documentation on using CSF.Screenplay with SpecFlow]: Specflow.md [Reqnroll version 2.0.0]: https://www.nuget.org/packages/Reqnroll/2.0.0 [CSF.Screenplay.Reqnroll]: https://www.nuget.org/packages/CSF.Screenplay.Reqnroll -[CSF.Screenplay.SpecFlow]: https://www.nuget.org/packages/CSF.Screenplay.SpecFlow [Abilities]: ../../../glossary/Ability.md -[use Reqnroll context injection & hooks]:https://docs.reqnroll.net/latest/automation/context-injection.html#advanced-options -## Writing step bindings +## Step 3: Configuring dependency injection + +Reqnroll has a built-in mechanism which offers an opportunity to add services to its DI container; these are [the event hook attributes]. +This is a viable technique because [Reqnroll's internal DI container "BoDi"] is unusual in that it permits retrospective addition of services. + +Use this technique by writing a small binding class like the following, replacing the comment with your DI registrations. +There's no need to use `.AddScreenplay()` here, _the integration does that already_. +For example, to add [the Screenplay/Selenium extension], add a line like `.AddSelenium()`. + +```csharp +[Binding] +public class DependenciesSetup(IObjectContainer reqnrollContainer) +{ + // Or [BeforeTestRun] or [BeforeFeature] + [BeforeScenario] + public void AddExtraDependencies() + { + reqnrollContainer.ToServiceCollection() + // Add your own dependency injection service descriptors to the service collection here + // For example, services which will be used by Screenplay Abilities. + ; + } +} +``` > [!IMPORTANT] > When using Reqnroll with Screenplay, every Screenplay-using test within a test assembly (thus, within a .NET project) must share the same instance of `Screenplay`. > This is not expected to be problematic, as all the [`Screenplay`] object does is set-up the Screenplay architecture and dependency injection for the tests. +[the event hook attributes]: https://docs.reqnroll.net/latest/automation/context-injection.html#advanced-options +[Reqnroll's internal DI container "BoDi"]: https://github.com/reqnroll/Reqnroll/tree/main/Reqnroll/BoDi +[the Screenplay/Selenium extension]: ../../extensions/selenium/index.md + +## Step 4: Writing step bindings + When using Screenplay with Reqnroll, `.feature` files are written as normal. The only difference in writing your tests is that **Step Binding** classes should inject Screenplay architecture and use it within the bindings. diff --git a/CSF.Screenplay.Docs/docs/introduction/index.md b/CSF.Screenplay.Docs/docs/introduction/index.md index ccf08bd2..28f259f1 100644 --- a/CSF.Screenplay.Docs/docs/introduction/index.md +++ b/CSF.Screenplay.Docs/docs/introduction/index.md @@ -5,6 +5,8 @@ It is particularly useful when many of those steps have a lot in common, making This has seen Screenplay become popular for writing the logic of [Behavior-driven development tests](https://en.wikipedia.org/wiki/Behavior-driven_development). Despite this, _Screenplay is not limited to just testing logic_. +In its early days _Screenplay was better-known as the "Journey" pattern_. + ## How Screenplay fits into an architecture This diagram provides a very high-level look at where Screenplay lies within the architecture of your software. diff --git a/CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs b/CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs index 0a219685..0b92ec62 100644 --- a/CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs +++ b/CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs @@ -11,21 +11,33 @@ namespace CSF.Screenplay /// /// This attribute is the core of the NUnit3 with Screenplay. /// In order to run tests with Screenplay, the assembly must be decorated with this attribute. + /// Its purpose is to signpost the Screenplay Factory (see below) to the NUnit/Screenplay integration. /// /// - /// This attribute has one mandatory parameter; that is the of a concrete implementation of - /// . That type will be instantiated by the NUnit3 integration and will be used to + /// This attribute has one mandatory parameter; that is the of the Screenplay Factory which should be used in this test project. + /// A Screenplay Factory is a class which provides a concrete implementation of . + /// An instance of the factory will be instantiated by the NUnit3 integration and will be used to /// build and retrieve the instance for running the Screenplay-based tests within the decorated - /// assembly. Each test method must additionally be decorated with the in order to make - /// it a Screenplay-based test. + /// assembly. + /// + /// + /// Each test method in the project must additionally be decorated with the + /// in order to activate the Screenplay architecture for that test. + /// It is possible to mix & match Screenplay-based and non-Screenplay-based tests within a single test project. + /// + /// + /// All Screenplay-based tests within a test project/assembly will use the Screenplay Factory which is identified by this attribute. /// /// /// /// - /// Decorate your assembly with this attribute using the syntax [assembly: CSF.Screenplay.ScreenplayAssembly]. You may place this + /// Decorate your assembly with this attribute using the syntax [assembly: CSF.Screenplay.ScreenplayAssembly(typeof(MyScreenplayFactory))]. You may place this /// into any source file, outside of any type declaration. By convention it would be put into a dedicated source file /// within the Properties project directory. /// + /// + /// There is a further example available in the . + /// /// /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] @@ -68,7 +80,7 @@ public Screenplay GetScreenplay() /// The specified in this constructor must meet all of the following criteria: /// /// - /// It must be a non- which derives from + /// It must be a non-, non- which derives from /// It must have a public parameterless constructor /// It must return a non- instance of from its method /// diff --git a/CSF.Screenplay.NUnit/ScreenplayAttribute.cs b/CSF.Screenplay.NUnit/ScreenplayAttribute.cs index 25d19306..3689f4ec 100644 --- a/CSF.Screenplay.NUnit/ScreenplayAttribute.cs +++ b/CSF.Screenplay.NUnit/ScreenplayAttribute.cs @@ -22,6 +22,12 @@ namespace CSF.Screenplay /// /// Remember that for this attribute to be effective, the which contains the test method must be decorated with /// . If it is not, then the test will fail with an exception. + /// As noted on the documentation for the Screenplay Assembly attribute, all Screenplay-based tests in the test project/assembly must + /// use the same Screenplay Factory, identified by that attribute. + /// + /// + /// It is possible and supported to mix & match Screenplay-based tests (with this attribute) and non-Screenplay-based tests (without it) in the + /// same test project/assembly. /// /// ///