Skip to content
Open
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
4 changes: 4 additions & 0 deletions .github/actions/restore-dependencies/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ runs:
working-directory: CSF.Screenplay.JsonToHtmlReport.Template/src
run: npm ci
shell: bash
- name: Restore Sample project Node modules
working-directory: Samples/CSF.Screenplay.Samples.SimpleToDoApp
run: npm ci
shell: bash
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "Samples/CSF.Screenplay.Samples.SimpleToDoApp/todomvc"]
path = Samples/CSF.Screenplay.Samples.SimpleToDoApp/todomvc
url = https://github.com/tastejs/todomvc
21 changes: 17 additions & 4 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"command": "npm",
"args": ["run", "test"],
"problemMatcher": [],
"label": "npm: test (Javascript)",
"label": "npm: test (JsonToHtmlReport)",
"options": {
"cwd": "${workspaceFolder}/CSF.Screenplay.JsonToHtmlReport.Template/src"
}
Expand Down Expand Up @@ -88,7 +88,7 @@
"command": "npm",
"args": ["i"],
"problemMatcher": [],
"label": "npm: install (Javascript)",
"label": "npm: install (JsonToHtmlReport)",
"options": {
"cwd": "${workspaceFolder}/CSF.Screenplay.JsonToHtmlReport.Template/src"
}
Expand All @@ -101,7 +101,7 @@
"command": "npm",
"args": ["run", "dev"],
"problemMatcher": [],
"label": "npm: dev (Javascript)",
"label": "npm: dev (JsonToHtmlReport)",
"options": {
"cwd": "${workspaceFolder}/CSF.Screenplay.JsonToHtmlReport.Template/src"
}
Expand All @@ -114,10 +114,23 @@
"command": "npm",
"args": ["run", "build"],
"problemMatcher": [],
"label": "npm: build (Javascript)",
"label": "npm: build (JsonToHtmlReport)",
"options": {
"cwd": "${workspaceFolder}/CSF.Screenplay.JsonToHtmlReport.Template/src"
}
},
{
"group": {
"kind": "none"
},
"type": "shell",
"command": "npm",
"args": ["run", "serve"],
"problemMatcher": [],
"label": "npm: serve (TodoMVC)",
"options": {
"cwd": "${workspaceFolder}/Samples/CSF.Screenplay.Samples.SimpleToDoApp"
}
}
]
}
2 changes: 1 addition & 1 deletion CSF.Screenplay.JsonToHtmlReport.Template/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# HTML template source

This project contains a small node/Webpack project, used to build the HTML file template for **CSF.Screenplay.JsonToHtmlReport**.
This project contains a small node/Vite project, used to build the HTML file template for **CSF.Screenplay.JsonToHtmlReport**.

The HTML template is used as an embedded resource in that app/library.
This project contains the _source code_ for building the HTML template; the build output of this project is sent to `CSF.Screenplay.JsonToHtmlReport/template/template.html`, where it is consumed as an embedded resource.
Expand Down
1 change: 1 addition & 0 deletions CSF.Screenplay.Reqnroll/CSF.Screenplay.Reqnroll.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<ItemGroup>
<PackageReference Include="Reqnroll" Condition="'$(ReqnrollVersion)' == ''" Version="2.0.0" />
<PackageReference Include="Reqnroll" Condition="'$(ReqnrollVersion)' != ''" Version="$(ReqnrollVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions " Version="6.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
58 changes: 57 additions & 1 deletion CSF.Screenplay.Reqnroll/ServiceProviderAdapter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using Microsoft.Extensions.Logging;

#if SPECFLOW
using BoDi;
#else
Expand All @@ -16,7 +18,61 @@ public class ServiceProviderAdapter : IServiceProvider
readonly IObjectContainer wrapped;

/// <inheritdoc/>
public object GetService(Type serviceType) => wrapped.Resolve(serviceType);
/// <remarks>
/// <para>
/// The design of BoDi means that the <c>ObjectContainer.IsRegistered</c> method (and variants) are unsafe to use here.
/// This means that we cannot reliably check whether a service type is already available in the container in a side-effect free manner.
/// This is because when using <c>IsRegistered</c> or its other overloads, can return <see langword="false"/> for types which BoDi was capable
/// of resolving, due to its <em>registration-free resolution</em> functionality. That is - when resolving concrete types which are not registered, it
/// will do its best to create an instance regardless.
/// Thus, whilst <c>IsRegistered</c> is correctly named, it is not what we want here. What we wanted is "Can you resolve type X?"
/// </para>
/// <para>
/// The alternative, used here, is to attempt to resolve the type, but contained within a <c>try/catch</c> which catched BoDi's
/// <see cref="ObjectContainerException"/>, which is thrown if BoDi cannot resolve the specified type. At that point, if it can't be resolved,
/// then we use the <c>catch</c> to revert to the <see cref="IServiceProvider"/> behaviour, which is to return <see langword="null"/> for a service
/// which cannot be resolved.
/// </para>
/// <para>
/// <b>Beware when debugging</b> that this comes with an unfortunate trade-off! If BoDi encounters an error whilst resolving something, then we can't tell at this
/// point whether that was an expected or unexpected error. An example of an expected error would be that we're trying to resolve an interface which
/// is not registered. An unexpected error might be an exception encountered within the constructor of something we are resolving.
/// Because the contract of <see cref="IServiceProvider"/> is to return null when attempting to resolve things which are not registered, then we must not
/// throw for those expected errors. The dilemma this offers us (because we can't tell expected and unexpected apart) is that we either have to throw for
/// every problem, or never throw.
/// I have picked the "never throw" route, but the trade-off is that this logic masks unexpected errors. That will make debugging more difficult, because unexpected
/// exceptions from BoDi will be squashed and replaced with null returns. What I have done to make that less painful is to integrate with MS Logging Abstractions here.
/// If you install and register a logging abstraction within DI, then an implementation of <see cref="ILogger{T}"/> will be resolved and the original error
/// recorded in the log.
/// </para>
/// </remarks>
public object GetService(Type serviceType)
{
try
{
return wrapped.Resolve(serviceType);
}
catch(ObjectContainerException ex)
{
if(TryGetLogger(out var logger))
logger.LogError(ex, "An error occured within BoDi's DI resolution. As we cannot differentiate between expected and unexpected errors, the service provider adapter logs it and returns null.");
return null;
}
}

bool TryGetLogger(out ILogger<ServiceProviderAdapter> logger)
{
try
{
logger = wrapped.Resolve<ILogger<ServiceProviderAdapter>>();
return true;
}
catch(ObjectContainerException)
{
logger = null;
return false;
}
}

/// <summary>
/// Initialises an instance of <see cref="ServiceProviderAdapter"/>.
Expand Down
2 changes: 1 addition & 1 deletion CSF.Screenplay.Selenium/CSF.Screenplay.Selenium.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.0.0" Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == '$(DotNetFrameworkLegacy)'" />
<PackageReference Include="Selenium.WebDriver" Version="4.0.0" />
<PackageReference Include="Selenium.Support" Version="4.0.0" />
<PackageReference Include="CSF.Extensions.WebDriver" Version="2.0.0-7.beta" />
<PackageReference Include="CSF.Extensions.WebDriver" Version="2.0.0-9.beta" />
<PackageReference Include="CSF.Specifications" Version="2.0.0" />
</ItemGroup>

Expand Down
3 changes: 3 additions & 0 deletions CSF.Screenplay.Selenium/Tasks/TakeAndSaveScreenshot.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using CSF.Screenplay.Abilities;
using OpenQA.Selenium;
using static CSF.Screenplay.Selenium.PerformableBuilder;

Expand Down Expand Up @@ -51,6 +52,8 @@ public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment form
/// <inheritdoc/>
public async ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken = default)
{
if(!actor.HasAbility<GetAssetFilePaths>() && !throwIfUnsupported) return;

var screenshot = await actor.PerformAsync(GetTakeAScreenshotPerformable(), cancellationToken);
if(screenshot == null) return;
await actor.PerformAsync(SaveTheScreenshot(screenshot).WithTheName(name).WithTheFormat(format), cancellationToken);
Expand Down
1 change: 1 addition & 0 deletions CSF.Screenplay.SpecFlow/CSF.Screenplay.SpecFlow.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<ItemGroup>
<PackageReference Include="SpecFlow" Version="3.4.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions " Version="6.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 11 additions & 0 deletions CSF.Screenplay.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSF.Screenplay.Reqnroll", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSF.Screenplay.Reqnroll.Tests", "Tests\CSF.Screenplay.Reqnroll.Tests\CSF.Screenplay.Reqnroll.Tests.csproj", "{A3D505D9-FE84-4032-8356-B90A30A8E4C4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{9C0561D5-F438-4A32-973A-979B5378E5A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSF.Screenplay.Samples.SimpleToDoAppTests", "Samples\CSF.Screenplay.Samples.SimpleToDoAppTests\CSF.Screenplay.Samples.SimpleToDoAppTests.csproj", "{7C44F380-B414-40D7-8B30-EA9908AD70E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -138,6 +142,12 @@ Global
{A3D505D9-FE84-4032-8356-B90A30A8E4C4}.Release|Any CPU.Build.0 = Release|Any CPU
{A3D505D9-FE84-4032-8356-B90A30A8E4C4}.Docs|Any CPU.ActiveCfg = Debug|Any CPU
{A3D505D9-FE84-4032-8356-B90A30A8E4C4}.Docs|Any CPU.Build.0 = Debug|Any CPU
{7C44F380-B414-40D7-8B30-EA9908AD70E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C44F380-B414-40D7-8B30-EA9908AD70E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C44F380-B414-40D7-8B30-EA9908AD70E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C44F380-B414-40D7-8B30-EA9908AD70E1}.Release|Any CPU.Build.0 = Release|Any CPU
{7C44F380-B414-40D7-8B30-EA9908AD70E1}.Docs|Any CPU.ActiveCfg = Debug|Any CPU
{7C44F380-B414-40D7-8B30-EA9908AD70E1}.Docs|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{7369BCFA-DA98-4C05-9708-7949AF3E485B} = {2C3ABE29-B5F7-404E-B20A-E3679E2ECEC5}
Expand All @@ -146,5 +156,6 @@ Global
{AEF2B39D-5129-41DA-AD51-02C8617A344F} = {2C3ABE29-B5F7-404E-B20A-E3679E2ECEC5}
{B5C9CDE0-9EAB-4381-8C79-D3F2ED1A09D2} = {2C3ABE29-B5F7-404E-B20A-E3679E2ECEC5}
{A3D505D9-FE84-4032-8356-B90A30A8E4C4} = {2C3ABE29-B5F7-404E-B20A-E3679E2ECEC5}
{7C44F380-B414-40D7-8B30-EA9908AD70E1} = {9C0561D5-F438-4A32-973A-979B5378E5A1}
EndGlobalSection
EndGlobal
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,18 @@ There is nothing hand-editable inside.

[![.NET CI](https://github.com/csf-dev/CSF.Screenplay/actions/workflows/dotnetCi.yml/badge.svg)](https://github.com/csf-dev/CSF.Screenplay/actions/workflows/dotnetCi.yml)
[![Test coverage](https://sonarcloud.io/api/project_badges/measure?project=csf-dev_CSF.Screenplay&metric=coverage)](https://sonarcloud.io/summary/new_code?id=csf-dev_CSF.Screenplay)

### This repository uses git submodules

Note that - for the sample application - this repository makes use of [git submodules].
When cloning this repository, you should either use the [`--recurse-submodules`] option, or after cloning use:

```txt
git submodule update --init --recursive
```

Failure to do so will leave you without [the sample web app] files.

[git submodules]: https://git-scm.com/docs/gitsubmodules
[`--recurse-submodules`]: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---recurse-submodulespathspec
[the sample web app]: ./Samples/CSF.Screenplay.Samples.SimpleToDoApp/README.md
Binary file not shown.
34 changes: 34 additions & 0 deletions Samples/CSF.Screenplay.Samples.SimpleToDoApp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Simple TODO app

This project contains a [git submodule] which points to [the TodoMVC project].
TodoMVC is a small, self-contained web application for which we may write tests using Screenplay.
The sample app uses **version 2.0.0** of TodoMVC.

[git submodule]: https://git-scm.com/docs/gitsubmodules
[the TodoMVC project]: https://github.com/tastejs/todomvc

## If the `todomvc` directory is empty

If the directory is empty in your working copy then most likely you did not initialise the submodules, as directed [in the instructions for cloning the repo].
You can fix this by running the command:

```txt
git submodule update --init --recursive
```

[in the instructions for cloning the repo]: ../../README.md

## Running the sample app

To run the sample app, use the following commands, from this directory.

```txt
npm ci
npm run serve
```

The app will be available at `http://localhost:5173`.
You may change the port if desired from `package.json`.

Note: The test project `CSF.Screenplay.Samples.SimpleToDoAppTests` performs these steps automatically when it runs.
You only need run it manually if you'd like to see it for yourself.
Loading
Loading