Skip to content

Commit af41fb6

Browse files
authored
Merge pull request #368 from csf-dev/craigfowler/issue365
Resolve #365 - Improve docs
2 parents 4bc44ce + 18b5073 commit af41fb6

8 files changed

Lines changed: 168 additions & 45 deletions

File tree

CSF.Screenplay.Docs/docs/dependencyInjection/index.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,22 @@ uid: DependencyInjectionMainArticle
55
# Dependency injection
66

77
The Screenplay design pattern is fundamentally compatible with and based upon [dependency injection], aka DI.
8-
You may add Screenplay to an existing [container] if you wish, via the [`AddScreenplay`] extension method.
9-
Alternatively you may create an instance of [`Screenplay`] which uses its own self-contained DI container with the static [`Screenplay.Create`] helper method.
8+
9+
* You may add Screenplay to an existing [container] via the [`AddScreenplay`] extension method
10+
* You may create a self-contained instance of [`Screenplay`] via the static [`Screenplay.Create`] helper method
11+
* Use this is you wish to integrate with an app or library which does not use DI itself
12+
* [Test integrations] have their own ways to integrate with DI
13+
* [DI integration with NUnit 3+]
14+
* [DI integration with Reqnroll/Specflow]
1015

1116
[dependency injection]: https://en.wikipedia.org/wiki/Dependency_injection
1217
[container]: xref:Microsoft.Extensions.DependencyInjection.IServiceCollection
1318
[`AddScreenplay`]: xref:CSF.Screenplay.ScreenplayServiceCollectionExtensions.AddScreenplay(Microsoft.Extensions.DependencyInjection.IServiceCollection)
1419
[`Screenplay`]: xref:CSF.Screenplay.Screenplay
1520
[`Screenplay.Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection})
21+
[Test integrations]: ../../glossary/Integration.md
22+
[DI integration with NUnit 3+]: ../gettingStarted/nunit3/index.md#step-3-write-a-screenplay-factory
23+
[DI integration with Reqnroll/Specflow]: ../gettingStarted/reqnroll/index.md#step-3-configuring-dependency-injection
1624

1725
## Learn more about DI in Screenplay
1826

CSF.Screenplay.Docs/docs/gettingStarted/nonTesting/index.md

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,63 @@ Screenplay may be added to any application or library, _via dependency injection
55
This is as simple as installing the **[CSF.Screenplay]** NuGet package and adding Screenplay to your service collection.
66
For more information, see the documentation for [`ScreenplayServiceCollectionExtensions`].
77

8+
Once Screenplay has been added to your DI, [you may resolve and use Screenplay-related services from dependency injection].
9+
To execute some logic in the scope of a [`Performance`], consider using the method [`Screenplay.ExecuteAsPerformanceAsync`].
10+
11+
[you may resolve and use Screenplay-related services from dependency injection]: ../../dependencyInjection/InjectingServices.md
12+
[`Performance`]: xref:CSF.Screenplay.IPerformance
13+
[`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)
14+
15+
## Example
16+
17+
The following example shows the core components for using Screenplay outside of a testing framework.
18+
19+
* DI registration which adds the Screenplay architecture
20+
* A start-point class which begins one or more performances
21+
* Each performance is coordinated from its own class which implements [`IHostsPerformance`]
22+
23+
_This is the recommended pattern_ for consuming Screenplay outside of a testing framework.
24+
See the documentation for the [`Screenplay`] and [`ScreenplayExtensions`] classes for other techniques.
25+
26+
If you wish to activate Screenplay extensions, be sure to add these to the DI container in your application startup.
27+
For example, to add [the Screenplay/Selenium extension], add a line like `services.AddSelenium()`.
28+
829
```csharp
930
using CSF.Screenplay;
1031

11-
// IServiceCollection services;
32+
// In your app startup, services is an instance of IServiceCollection, your
33+
// application's DI container.
1234
services.AddScreenplay();
35+
// optionally add further services to the container, as normal
1336
14-
// ... then, after the service collection is built:
15-
// IServiceProvider serviceProvider;
16-
var screenplay = serviceProvider.GetService<Screenplay>();
37+
// ... now in your regular app logic you may now constructor inject Screenplay:
38+
public class MyScreenplayConsumer(Screenplay screenplay)
39+
{
40+
public async Task StartScreenplay()
41+
{
42+
// As many performances as you want
43+
await screenplay.ExecuteAsPerformanceAsync<SamplePerformance>();
44+
}
45+
}
1746

18-
// Or you may constructor-inject an instance of Screenplay into
19-
// any type which was resolved from the service provider
47+
public class SamplePerformance(IStage stage) : IHostsPerformance
48+
{
49+
public Task<bool?> ExecutePerformanceAsync(CancellationToken token)
50+
{
51+
// Use the injected stage to get an actor and execute your Screenplay logic
52+
}
53+
}
2054
```
2155

22-
Once Screenplay has been added to your DI, [you may resolve and use Screenplay-related services from dependency injection].
23-
To execute some logic in the scope of a [`Performance`], consider using the method [`Screenplay.ExecuteAsPerformanceAsync`].
24-
2556
[as a tool for automating software tests]: ../../bestPractice/SuitabilityAsATestingTool.md
2657
[CSF.Screenplay]: https://www.nuget.org/packages/CSF.Screenplay
2758
[`ScreenplayServiceCollectionExtensions`]: xref:CSF.Screenplay.ScreenplayServiceCollectionExtensions
28-
[you may resolve and use Screenplay-related services from dependency injection]: ../../dependencyInjection/InjectingServices.md
29-
[`Performance`]: xref:CSF.Screenplay.IPerformance
30-
[`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)
59+
[`IHostsPerformance`]: xref:CSF.Screenplay.IHostsPerformance
60+
[`Screenplay`]: xref:CSF.Screenplay.Screenplay
61+
[`ScreenplayExtensions`]: xref:CSF.Screenplay.ScreenplayExtensions
62+
[the Screenplay/Selenium extension]: ../../extensions/selenium/index.md
3163

32-
## Abstractions package
64+
## The Abstractions package
3365

3466
If your solution is separated into multiple projects/assemblies then only your entry-point project needs the full CSF.Screenplay NuGet package.
3567
Once Screenplay has been added to DI, other projects in the solution may consume its logic, only requiring the [CSF.Screenplay.Abstractions] package.

CSF.Screenplay.Docs/docs/gettingStarted/nunit3/index.md

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
---
2+
uid: NUnit3GettingStartedArticle
3+
---
4+
15
# Screenplay & NUnit tutorial
26

37
Begin writing NUnit tests using Screenplay by following these steps.
48
Further detail is provided below.
59

610
1. Ensure that your test project uses [NUnit version 3.6.0] or higher
711
1. Install the NuGet package **[CSF.Screenplay.NUnit]** to your test project
8-
1. Write a class which implements [`IGetsScreenplay`]
9-
1. Decorate your test assembly with [`ScreenplayAssemblyAttribute`], referencing your implementation of `IGetsScreenplay`
12+
1. Write a Screenplay Factory class, implementing [`IGetsScreenplay`]
13+
1. Decorate your test assembly with [`ScreenplayAssemblyAttribute`], referencing that Screenplay Factory
1014
1. Write your tests, decorating each test method with [`ScreenplayAttribute`]
1115
1. Add parameters to your test methods to access the Screenplay architecture
1216

@@ -20,20 +24,18 @@ Further detail is provided below.
2024
[`ScreenplayAttribute`]:xref:CSF.Screenplay.ScreenplayAttribute
2125
[these best practices for writing tests which use Screenplay]: ../../bestPractice/WritingTests.md
2226

23-
## Decorating your test assembly with `[ScreenplayAssembly]`
27+
## Step 3: Write a Screenplay factory
2428

25-
So that your tests may make use of a [`Screenplay`], you must install the Screenplay extension to the NUnit testing framework.
26-
This is **steps 3 & 4** in the list above.
27-
This is achieved using the [`ScreenplayAssemblyAttribute`].
28-
Place a line of code somewhere in your test project, outside of any type definition like this:
29+
NUnit requires a factory class which implements [`IGetsScreenplay`].
30+
This tells the underlying framework how to create and configure the `Screenplay` for your tests.
31+
This factory class is user-customisable, because it is where you would configure **[dependency injection]** for your own Screenplay.
2932

30-
```csharp
31-
[assembly: CSF.Screenplay.ScreenplayAssembly(typeof(MyScreenplayFactory))]
32-
```
33+
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.
34+
There's no need to use `.AddScreenplay()` here, _the integration does that already_.
35+
For example, to add [the Screenplay/Selenium extension], add `services.AddSelenium()` here.
3336

34-
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.
35-
A screenplay factory is a class which must implement [`IGetsScreenplay`].
36-
Consider the example below as a starting point for writing your own.
37+
> [!IMPORTANT]
38+
> The Screenplay Factory class _must have a parameterless constructor_.
3739
3840
```csharp
3941
using CSF.Screenplay;
@@ -50,12 +52,27 @@ public class MyScreenplayFactory : IGetsScreenplay
5052
}
5153
```
5254

55+
[dependency injection]: ../../dependencyInjection/index.md
56+
[the Screenplay/Selenium extension]: ../../extensions/selenium/index.md
57+
58+
## Step 4: Decorate your test assembly with `[ScreenplayAssembly]`
59+
60+
One line of boilerplate code is required per test assembly (project).
61+
Its purpose is to signpost the Screenplay Factory which you wrote in step 3 (above) to the NUnit/Screenplay integration.
62+
63+
Place a line of code like the following in your test project.
64+
This should be outside of any type; it may go into its own source file if you wish.
65+
Be sure to replace `MyScreenplayFactory` with the factory class your wrote in step 3.
66+
See the docs for [`ScreenplayAssemblyAttribute`] for more info.
67+
68+
```csharp
69+
[assembly: CSF.Screenplay.ScreenplayAssembly(typeof(MyScreenplayFactory))]
70+
```
71+
5372
> [!IMPORTANT]
5473
> 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`.
5574
> 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.
5675
57-
[`Screenplay`]: xref:CSF.Screenplay.Screenplay
58-
5976
## Writing test methods
6077

6178
When writing test methods, the test methods must be decorated with [`ScreenplayAttribute`], which activates Screenplay for that particular test method.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Using CSF.Screenplay with legacy SpecFlow
2+
3+
For now, _CSF.Screenplay supports SpecFlow_.
4+
It is strongly recommended to [upgrade to Reqnroll at your earliest opportunity].
5+
No promises can be made about future support, particularly as Reqnroll diverges from SpecFlow's original architecture.
6+
7+
## Adapting instructions for SpecFlow
8+
9+
Users of SpecFlow may use [the Getting Started guide for Reqnroll], with the following adaptations.
10+
11+
* At step 1, install SpecFlow version 3.4.3 or higher instead
12+
* At step 2, install the package [CSF.Screenplay.SpecFlow] instead
13+
* When using the framework, replace any usage of the `Reqnroll` namespace with `TechTalk.SpecFlow`
14+
15+
The remainder of the guide holds true for both Reqnroll or SpecFlow.
16+
The same techniques & syntax mentioned in the guide are supported in both frameworks, with only the namespace difference noted above.
17+
18+
[upgrade to Reqnroll at your earliest opportunity]: https://docs.reqnroll.net/latest/guides/migrating-from-specflow.html
19+
[the Getting Started guide for Reqnroll]: ./index.md
20+
[CSF.Screenplay.SpecFlow]: https://www.nuget.org/packages/CSF.Screenplay.SpecFlow

CSF.Screenplay.Docs/docs/gettingStarted/reqnroll/index.md

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,58 @@
22

33
> [!TIP]
44
> Are you using the legacy **SpecFlow**?
5-
> **Reqnroll** is the maintained fork of SpecFlow, so it's recommended you upgrade your projects ASAP.
6-
>
7-
> For now, CSF.Screenplay continues to support SpecFlow.
8-
> Use the [CSF.Screenplay.SpecFlow] package and SpecFlow v3.4.3 or higher instead.
9-
> The remainder of the instructions below work for either Reqnroll or SpecFlow.
5+
> [Reqnroll is the maintained fork of SpecFlow], so it's recommended you upgrade your projects ASAP.
6+
> See [the documentation on using CSF.Screenplay with SpecFlow] for more information.
107
118
Begin writing Reqnroll tests using Screenplay by following these steps.
129
Further detail is provided below.
1310

1411
1. Ensure that your test project uses [Reqnroll version 2.0.0] or higher
1512
1. Install the NuGet package **[CSF.Screenplay.Reqnroll]** to the project which will contain your `.feature` files
16-
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.
13+
1. Configure dependency injection with any services you intend to use in your tests
1714
1. Write step binding classes which dependency-inject and use Screenplay's architecture
1815

16+
[Reqnroll is the maintained fork of SpecFlow]: https://reqnroll.net/news/2025/01/specflow-end-of-life-has-been-announced/
17+
[the documentation on using CSF.Screenplay with SpecFlow]: Specflow.md
1918
[Reqnroll version 2.0.0]: https://www.nuget.org/packages/Reqnroll/2.0.0
2019
[CSF.Screenplay.Reqnroll]: https://www.nuget.org/packages/CSF.Screenplay.Reqnroll
21-
[CSF.Screenplay.SpecFlow]: https://www.nuget.org/packages/CSF.Screenplay.SpecFlow
2220
[Abilities]: ../../../glossary/Ability.md
23-
[use Reqnroll context injection & hooks]:https://docs.reqnroll.net/latest/automation/context-injection.html#advanced-options
2421

25-
## Writing step bindings
22+
## Step 3: Configuring dependency injection
23+
24+
Reqnroll has a built-in mechanism which offers an opportunity to add services to its DI container; these are [the event hook attributes].
25+
This is a viable technique because [Reqnroll's internal DI container "BoDi"] is unusual in that it permits retrospective addition of services.
26+
27+
Use this technique by writing a small binding class like the following, replacing the comment with your DI registrations.
28+
There's no need to use `.AddScreenplay()` here, _the integration does that already_.
29+
For example, to add [the Screenplay/Selenium extension], add a line like `.AddSelenium()`.
30+
31+
```csharp
32+
[Binding]
33+
public class DependenciesSetup(IObjectContainer reqnrollContainer)
34+
{
35+
// Or [BeforeTestRun] or [BeforeFeature]
36+
[BeforeScenario]
37+
public void AddExtraDependencies()
38+
{
39+
reqnrollContainer.ToServiceCollection()
40+
// Add your own dependency injection service descriptors to the service collection here
41+
// For example, services which will be used by Screenplay Abilities.
42+
;
43+
}
44+
}
45+
```
2646

2747
> [!IMPORTANT]
2848
> 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`.
2949
> 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.
3050
51+
[the event hook attributes]: https://docs.reqnroll.net/latest/automation/context-injection.html#advanced-options
52+
[Reqnroll's internal DI container "BoDi"]: https://github.com/reqnroll/Reqnroll/tree/main/Reqnroll/BoDi
53+
[the Screenplay/Selenium extension]: ../../extensions/selenium/index.md
54+
55+
## Step 4: Writing step bindings
56+
3157
When using Screenplay with Reqnroll, `.feature` files are written as normal.
3258
The only difference in writing your tests is that **Step Binding** classes should inject Screenplay architecture and use it within the bindings.
3359

CSF.Screenplay.Docs/docs/introduction/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ It is particularly useful when many of those steps have a lot in common, making
55
This has seen Screenplay become popular for writing the logic of [Behavior-driven development tests](https://en.wikipedia.org/wiki/Behavior-driven_development).
66
Despite this, _Screenplay is not limited to just testing logic_.
77

8+
In its early days _Screenplay was better-known as the "Journey" pattern_.
9+
810
## How Screenplay fits into an architecture
911

1012
This diagram provides a very high-level look at where Screenplay lies within the architecture of your software.

CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,33 @@ namespace CSF.Screenplay
1111
/// <para>
1212
/// This attribute is the core of the NUnit3 <xref href="IntegrationGlossaryItem?text=test+framework+integration"/> with Screenplay.
1313
/// In order to run tests with Screenplay, the assembly must be decorated with this attribute.
14+
/// Its purpose is to signpost the Screenplay Factory (see below) to the NUnit/Screenplay integration.
1415
/// </para>
1516
/// <para>
16-
/// This attribute has one mandatory parameter; that is the <see cref="Type"/> of a concrete implementation of
17-
/// <see cref="IGetsScreenplay"/>. That type will be instantiated by the NUnit3 integration and will be used to
17+
/// This attribute has one mandatory parameter; that is the <see cref="Type"/> of the Screenplay Factory which should be used in this test project.
18+
/// A Screenplay Factory is a class which provides a concrete implementation of <see cref="IGetsScreenplay"/>.
19+
/// An instance of the factory will be instantiated by the NUnit3 integration and will be used to
1820
/// build and retrieve the <see cref="Screenplay"/> instance for running the Screenplay-based tests within the decorated
19-
/// assembly. Each test method must additionally be decorated with the <see cref="ScreenplayAttribute"/> in order to make
20-
/// it a Screenplay-based test.
21+
/// assembly.
22+
/// </para>
23+
/// <para>
24+
/// Each test method in the project must additionally be decorated with the
25+
/// <see cref="ScreenplayAttribute"/> in order to activate the Screenplay architecture for that test.
26+
/// It is possible to mix &amp; match Screenplay-based and non-Screenplay-based tests within a single test project.
27+
/// </para>
28+
/// <para>
29+
/// All Screenplay-based tests within a test project/assembly will use the Screenplay Factory which is identified by this attribute.
2130
/// </para>
2231
/// </remarks>
2332
/// <example>
2433
/// <para>
25-
/// Decorate your assembly with this attribute using the syntax <c>[assembly: CSF.Screenplay.ScreenplayAssembly]</c>. You may place this
34+
/// Decorate your assembly with this attribute using the syntax <c>[assembly: CSF.Screenplay.ScreenplayAssembly(typeof(MyScreenplayFactory))]</c>. You may place this
2635
/// into any source file, outside of any type declaration. By convention it would be put into a dedicated source file
2736
/// within the <c>Properties</c> project directory.
2837
/// </para>
38+
/// <para>
39+
/// There is a further example available in the <xref href="NUnit3GettingStartedArticle?text=getting+started+guide+for+Screenplay+and+NUnit3"/>.
40+
/// </para>
2941
/// </example>
3042
/// <seealso cref="ScreenplayAttribute"/>
3143
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
@@ -68,7 +80,7 @@ public Screenplay GetScreenplay()
6880
/// The <paramref name="factoryType"/> specified in this constructor must meet all of the following criteria:
6981
/// </para>
7082
/// <list type="bullet">
71-
/// <item><description>It must be a non-<see langword="null" /> <see cref="Type"/> which derives from <see cref="IGetsScreenplay"/></description></item>
83+
/// <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>
7284
/// <item><description>It must have a public parameterless constructor</description></item>
7385
/// <item><description>It must return a non-<see langword="null" /> instance of <see cref="Screenplay"/> from its <see cref="IGetsScreenplay.GetScreenplay"/> method</description></item>
7486
/// </list>

0 commit comments

Comments
 (0)