Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions CSF.Screenplay.Docs/docs/dependencyInjection/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
58 changes: 45 additions & 13 deletions CSF.Screenplay.Docs/docs/gettingStarted/nonTesting/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Screenplay>();
// ... 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<SamplePerformance>();
}
}

// 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<bool?> 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.
Expand Down
47 changes: 32 additions & 15 deletions CSF.Screenplay.Docs/docs/gettingStarted/nunit3/index.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
---
uid: NUnit3GettingStartedArticle
---

# Screenplay & NUnit tutorial

Begin writing NUnit tests using Screenplay by following these steps.
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

Expand All @@ -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;
Expand All @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/Specflow.md
Original file line number Diff line number Diff line change
@@ -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
44 changes: 35 additions & 9 deletions CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions CSF.Screenplay.Docs/docs/introduction/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 18 additions & 6 deletions CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,33 @@ namespace CSF.Screenplay
/// <para>
/// This attribute is the core of the NUnit3 <xref href="IntegrationGlossaryItem?text=test+framework+integration"/> 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.
/// </para>
/// <para>
/// This attribute has one mandatory parameter; that is the <see cref="Type"/> of a concrete implementation of
/// <see cref="IGetsScreenplay"/>. That type will be instantiated by the NUnit3 integration and will be used to
/// This attribute has one mandatory parameter; that is the <see cref="Type"/> of the Screenplay Factory which should be used in this test project.
/// A Screenplay Factory is a class which provides a concrete implementation of <see cref="IGetsScreenplay"/>.
/// An instance of the factory will be instantiated by the NUnit3 integration and will be used to
/// build and retrieve the <see cref="Screenplay"/> instance for running the Screenplay-based tests within the decorated
/// assembly. Each test method must additionally be decorated with the <see cref="ScreenplayAttribute"/> in order to make
/// it a Screenplay-based test.
/// assembly.
/// </para>
/// <para>
/// Each test method in the project must additionally be decorated with the
/// <see cref="ScreenplayAttribute"/> in order to activate the Screenplay architecture for that test.
/// It is possible to mix &amp; match Screenplay-based and non-Screenplay-based tests within a single test project.
/// </para>
/// <para>
/// All Screenplay-based tests within a test project/assembly will use the Screenplay Factory which is identified by this attribute.
/// </para>
/// </remarks>
/// <example>
/// <para>
/// Decorate your assembly with this attribute using the syntax <c>[assembly: CSF.Screenplay.ScreenplayAssembly]</c>. You may place this
/// Decorate your assembly with this attribute using the syntax <c>[assembly: CSF.Screenplay.ScreenplayAssembly(typeof(MyScreenplayFactory))]</c>. 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 <c>Properties</c> project directory.
/// </para>
/// <para>
/// There is a further example available in the <xref href="NUnit3GettingStartedArticle?text=getting+started+guide+for+Screenplay+and+NUnit3"/>.
/// </para>
/// </example>
/// <seealso cref="ScreenplayAttribute"/>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
Expand Down Expand Up @@ -68,7 +80,7 @@ public Screenplay GetScreenplay()
/// The <paramref name="factoryType"/> specified in this constructor must meet all of the following criteria:
/// </para>
/// <list type="bullet">
/// <item><description>It must be a non-<see langword="null" /> <see cref="Type"/> which derives from <see cref="IGetsScreenplay"/></description></item>
/// <item><description>It must be a non-<see langword="null" />, non-<see langword="abstract"/> <see cref="Type"/> which derives from <see cref="IGetsScreenplay"/></description></item>
/// <item><description>It must have a public parameterless constructor</description></item>
/// <item><description>It must return a non-<see langword="null" /> instance of <see cref="Screenplay"/> from its <see cref="IGetsScreenplay.GetScreenplay"/> method</description></item>
/// </list>
Expand Down
Loading
Loading