diff --git a/.appveyor.yml b/.appveyor.yml
index fa45fc94..5e17168e 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -21,14 +21,18 @@ install:
cd CSF.Screenplay.JsonToHtmlReport.Template\src
npm ci
cd ..\..
+ # This was taken from https://stackoverflow.com/questions/60304251/unable-to-open-x-display-when-trying-to-run-google-chrome-on-centos-rhel-7-5
+ # It's the minimum dependencies for running Chrome in a headless environment on Linux
+ - sh: |
+ sudo apt-get update
+ sudo apt install -y xorg xvfb gtk2-engines-pixbuf dbus-x11 xfonts-base xfonts-100dpi xfonts-75dpi xfonts-cyrillic xfonts-scalable
before_build:
- dotnet --version
- dotnet restore --verbosity m
- dotnet clean
- - cmd: >
- IF NOT DEFINED APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH (SET BranchName=%APPVEYOR_REPO_BRANCH%)
- ELSE (SET BranchName=%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%)
+ - cmd: Tools\appveyor-setup-sonarscanner.bat
+ - cmd: Tools\appveyor-setup-selenium.bat
- cmd: >
dotnet-sonarscanner begin
/k:"csf-dev_CSF.Screenplay"
@@ -36,9 +40,14 @@ before_build:
/o:craigfowler-github
/d:sonar.host.url=https://sonarcloud.io
/d:sonar.token=%SONARCLOUD_SECRET_KEY%
- /d:sonar.branch.name=%BranchName%
+ /d:%BranchParam%=%BranchName%
+ %PRParam%
/d:sonar.javascript.lcov.reportPaths=%APPVEYOR_BUILD_FOLDER%\CSF.Screenplay.JsonToHtmlReport.Template\src\TestResults\lcov.info
/s:%APPVEYOR_BUILD_FOLDER%\.sonarqube-analysisproperties.xml
+ # Activate Xvfb and export a display so that Chrome can run in Linux
+ - sh: |
+ Xvfb -ac :99 -screen 0 1280x1024x16 &
+ export DISPLAY=:99
build_script:
- dotnet build --no-incremental
@@ -61,3 +70,7 @@ after_test:
- ps: if ($isWindows) { Tools\appveyor-upload-test-results.ps1 }
- cmd: dotnet build -c Docs
- ps: if ($isWindows) { Tools\appveyor_publish_docs.ps1 }
+
+artifacts:
+ - path: Tests\**\ScreenplayReport_*.json
+ name: Screenplay report
diff --git a/.gitignore b/.gitignore
index c3b716f9..51db7c89 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,8 @@ obj/
TestResults/
Tests/**/*.feature.cs
node_modules/
+*.orig
/CSF.Screenplay.JsonToHtmlReport/template/
/CSF.Screenplay.JsonToHtmlReport.Template/src/output/
+**/ScreenplayReport*
+**/TestResult.xml
diff --git a/.sonarqube-analysisproperties.xml b/.sonarqube-analysisproperties.xml
index f923c073..61a28780 100644
--- a/.sonarqube-analysisproperties.xml
+++ b/.sonarqube-analysisproperties.xml
@@ -2,9 +2,12 @@
- Tests/**/*,**/*Exception.cs,*_old/**/*,**/*.spec.js,**/*.config.js
- Tests/**/*,*_old/**/*,**/*.spec.js
+ Tests\**\*,**\*Exception.cs,**\*.spec.js,**\*.config.js
+ docs\**\*,*_old\**\*
+ Tests\**\*,**\*.spec.jsTests\**\TestResults.xmlTestResults\*.opencover.xmlfalse
+ Tests\**\*,**\*.spec.js
+ true
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..074847c3
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "dotnet",
+ "projectPath": "${workspaceFolder}/Tests/CSF.Screenplay.Selenium.TestWebapp/CSF.Screenplay.Selenium.TestWebapp.csproj",
+ "name": "Selenium testing web app",
+ "request": "launch",
+
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CSF.Screenplay.Abstractions/Abilities/GetAssetFilePaths.cs b/CSF.Screenplay.Abstractions/Abilities/GetAssetFilePaths.cs
new file mode 100644
index 00000000..4ab345a6
--- /dev/null
+++ b/CSF.Screenplay.Abstractions/Abilities/GetAssetFilePaths.cs
@@ -0,0 +1,39 @@
+using CSF.Screenplay.Reporting;
+
+namespace CSF.Screenplay.Abilities
+{
+ ///
+ /// Screenplay ability which gets the file system path for asset files generated by actors participating in the current performance.
+ ///
+ public class GetAssetFilePaths
+ {
+ readonly IGetsAssetFilePath pathProvider;
+
+ ///
+ /// Gets the file system path for the specified asset file.
+ ///
+ ///
+ ///
+ /// The returned file system path is an absolute path to which the asset file should be written. The path is determined by the
+ /// logic of the service . This means that the final filename will not be identical to the
+ /// but will include that base name within it.
+ ///
+ ///
+ /// If this method returns then the asset file should not be written to the file system.
+ ///
+ ///
+ /// A short descriptive file name fragment for the asset file, including the file extension.
+ /// The asset file path.
+ ///
+ public string GetAssetFilePath(string baseName) => pathProvider.GetAssetFilePath(baseName);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The path provider used to get asset file paths.
+ public GetAssetFilePaths(IGetsAssetFilePath pathProvider)
+ {
+ this.pathProvider = pathProvider ?? throw new System.ArgumentNullException(nameof(pathProvider));
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSF.Screenplay.Abstractions/Abilities/UseAStopwatch.cs b/CSF.Screenplay.Abstractions/Abilities/UseAStopwatch.cs
index 04184af2..8fc6fd69 100644
--- a/CSF.Screenplay.Abstractions/Abilities/UseAStopwatch.cs
+++ b/CSF.Screenplay.Abstractions/Abilities/UseAStopwatch.cs
@@ -22,7 +22,7 @@ public class UseAStopwatch : ICanReport
public System.Diagnostics.Stopwatch Stopwatch { get; } = new System.Diagnostics.Stopwatch();
///
- public ReportFragment GetReportFragment(IHasName actor, IFormatsReportFragment formatter)
+ public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter)
=> formatter.Format(AbilityReportStrings.UseAStopwatchFormat, actor);
}
}
diff --git a/CSF.Screenplay.Abstractions/Actor.cs b/CSF.Screenplay.Abstractions/Actor.cs
index 4cec7968..e1c27a8a 100644
--- a/CSF.Screenplay.Abstractions/Actor.cs
+++ b/CSF.Screenplay.Abstractions/Actor.cs
@@ -58,6 +58,9 @@ public partial class Actor : IHasName, IHasPerformanceIdentity
Guid IHasPerformanceIdentity.PerformanceIdentity => PerformanceIdentity;
+ ///
+ public override string ToString() => $"[Actor '{Name}' in Performance {PerformanceIdentity}]";
+
/// Initialises a new instance of
///
///
diff --git a/CSF.Screenplay.Abstractions/Actor.performer.cs b/CSF.Screenplay.Abstractions/Actor.performer.cs
index 8b95e54a..1726e2ee 100644
--- a/CSF.Screenplay.Abstractions/Actor.performer.cs
+++ b/CSF.Screenplay.Abstractions/Actor.performer.cs
@@ -79,7 +79,8 @@ protected virtual ValueTask PerformAsync(IPerformableWithResult perform
PerformableException GetPerformableException(object performable, Exception ex)
{
- return new PerformableException($"{Name} encountered an unexpected exception whilst performing a performable of type {performable.GetType().FullName}", ex)
+
+ return new PerformableException($"{Name} encountered an unexpected exception whilst executing the performable logic of {performable.GetType().FullName}", ex)
{
Performable = performable,
};
diff --git a/CSF.Screenplay.Abstractions/ActorExtensions.abilities.cs b/CSF.Screenplay.Abstractions/ActorExtensions.abilities.cs
index 8c2ac7e3..315e6262 100644
--- a/CSF.Screenplay.Abstractions/ActorExtensions.abilities.cs
+++ b/CSF.Screenplay.Abstractions/ActorExtensions.abilities.cs
@@ -80,6 +80,42 @@ public static object GetAbility(this ICanPerform actor, Type abilityType)
?? throw new InvalidOperationException($"{((IHasName) actor).Name} must have an ability of type {abilityType.FullName}");
}
+ /// Tries to get the first ability which the actor has of the specified type
+ /// The actor from whom to get the ability
+ /// If this method returns then this exposes the strongly-typed ability; if not then this value is undefined
+ /// The type of ability desired
+ /// if the actor has an ability of the specified type; if not.
+ /// If the is
+ /// If the actor does not implement
+ public static bool TryGetAbility(this ICanPerform actor, out T ability)
+ {
+ ability = default;
+ if (!TryGetAbility(actor, typeof(T), out var untypedAbility)) return false;
+ ability = (T) untypedAbility;
+ return true;
+ }
+
+ /// Gets the first ability which the actor has of the specified type
+ /// The actor from whom to get the ability
+ /// The type of ability desired
+ /// If this method returns then this exposes the strongly-typed ability; if not then this value is undefined
+ /// if the actor has an ability of the specified type; if not.
+ /// If any parameter is
+ public static bool TryGetAbility(this ICanPerform actor, Type abilityType, out object ability)
+ {
+ if(actor is null) throw new ArgumentNullException(nameof(actor));
+ if(abilityType is null) throw new ArgumentNullException(nameof(abilityType));
+
+ if(!actor.HasAbility(abilityType))
+ {
+ ability = default;
+ return false;
+ }
+
+ ability = actor.GetAbility(abilityType);
+ return true;
+ }
+
/// Adds an ability to the specified actor
/// The actor from whom to get the ability
/// The ability to add to the actor
diff --git a/CSF.Screenplay.Abstractions/ICanReport.cs b/CSF.Screenplay.Abstractions/ICanReport.cs
index b7b28e0d..8c3f3499 100644
--- a/CSF.Screenplay.Abstractions/ICanReport.cs
+++ b/CSF.Screenplay.Abstractions/ICanReport.cs
@@ -69,6 +69,6 @@ public interface ICanReport
/// A human-readable report fragment.
/// An actor for whom to write the report fragment
/// A report-formatting service
- ReportFragment GetReportFragment(IHasName actor, IFormatsReportFragment formatter);
+ ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter);
}
}
diff --git a/CSF.Screenplay.Abstractions/ICast.cs b/CSF.Screenplay.Abstractions/ICast.cs
index 08857b8f..4e19813f 100644
--- a/CSF.Screenplay.Abstractions/ICast.cs
+++ b/CSF.Screenplay.Abstractions/ICast.cs
@@ -1,3 +1,5 @@
+using System.Collections.Generic;
+
namespace CSF.Screenplay
{
/// A combined registry and factory for instances, useful when coordinating multiple
@@ -31,6 +33,25 @@ namespace CSF.Screenplay
///
public interface ICast : IHasServiceProvider, IHasPerformanceIdentity
{
+ ///
+ /// Gets a collection of string names, indicating the collection of actors which this cast object knows about.
+ ///
+ ///
+ ///
+ /// Because a cast instance serves as both a registry and a factory, whenever it is used to get an for the first
+ /// time, that actor instance is cached within the cast which created it. This method may be used to get a collection of the names
+ /// of actors which are cached within the current cast instance.
+ ///
+ ///
+ /// Note that the collection returned by this method is a snapshot in time, correct as-at the time when this method is executed.
+ /// The collection does not update automatically as new actors are added to the cast. You must execute this method again to get an
+ /// updated collection.
+ ///
+ ///
+ /// A collection of strings, corresponding to the actors which have been created in the lifetime
+ /// of the current cast instance.
+ IReadOnlyCollection GetCastList();
+
///
/// Gets a single by their name, creating them if they do not already exist in the cast.
///
diff --git a/CSF.Screenplay.Abstractions/IPerformance.cs b/CSF.Screenplay.Abstractions/IPerformance.cs
index 5c3c09dc..6d37a2b1 100644
--- a/CSF.Screenplay.Abstractions/IPerformance.cs
+++ b/CSF.Screenplay.Abstractions/IPerformance.cs
@@ -67,15 +67,6 @@ public interface IPerformance : IHasPerformanceIdentity,
/// Alternatively, for a BDD-style testing framework, it could be named based upon human-readable
/// feature & scenario names.
///
- ///
- /// Ideally this property would be immutable after a Performance is created.
- /// Unfortunately, some testing frameworks do not expose relevant naming information about a test until after the point
- /// of execution where the Performance must be created.
- /// Thus, this property is mutable, so that it is possible to 'backfill' missing naming information after the performance has
- /// been created.
- /// Wherever possible, it is recommended to avoid updating this list of identifier/names and to only set them up when creating the
- /// performance, via .
- ///
///
///
///
@@ -84,7 +75,7 @@ public interface IPerformance : IHasPerformanceIdentity,
/// and the second will be named Joe can take out the Trash.
///
///
- List NamingHierarchy { get; }
+ IReadOnlyList NamingHierarchy { get; }
/// Gets a value which indicates the state of the current performance.
///
diff --git a/CSF.Screenplay.Abstractions/Performables/ReadTheStopwatch.cs b/CSF.Screenplay.Abstractions/Performables/ReadTheStopwatch.cs
index 0edfae42..905de004 100644
--- a/CSF.Screenplay.Abstractions/Performables/ReadTheStopwatch.cs
+++ b/CSF.Screenplay.Abstractions/Performables/ReadTheStopwatch.cs
@@ -18,7 +18,7 @@ namespace CSF.Screenplay.Performables
public class ReadTheStopwatch : IPerformableWithResult, ICanReport
{
///
- public ReportFragment GetReportFragment(IHasName actor, IFormatsReportFragment formatter)
+ public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter)
=> formatter.Format(PerformableReportStrings.ReadTheStopwatchFormat, actor);
///
diff --git a/CSF.Screenplay.Abstractions/Performables/ResetTheStopwatch.cs b/CSF.Screenplay.Abstractions/Performables/ResetTheStopwatch.cs
index d86845f0..a99f133e 100644
--- a/CSF.Screenplay.Abstractions/Performables/ResetTheStopwatch.cs
+++ b/CSF.Screenplay.Abstractions/Performables/ResetTheStopwatch.cs
@@ -17,7 +17,7 @@ namespace CSF.Screenplay.Performables
public class ResetTheStopwatch : IPerformable, ICanReport
{
///
- public ReportFragment GetReportFragment(IHasName actor, IFormatsReportFragment formatter)
+ public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter)
=> formatter.Format(PerformableReportStrings.ResetTheStopwatchFormat, actor);
///
diff --git a/CSF.Screenplay.Abstractions/Performables/StartTheStopwatch.cs b/CSF.Screenplay.Abstractions/Performables/StartTheStopwatch.cs
index 30f54f0a..6158f911 100644
--- a/CSF.Screenplay.Abstractions/Performables/StartTheStopwatch.cs
+++ b/CSF.Screenplay.Abstractions/Performables/StartTheStopwatch.cs
@@ -17,7 +17,7 @@ namespace CSF.Screenplay.Performables
public class StartTheStopwatch : IPerformable, ICanReport
{
///
- public ReportFragment GetReportFragment(IHasName actor, IFormatsReportFragment formatter)
+ public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter)
=> formatter.Format(PerformableReportStrings.StartTheStopwatchFormat, actor);
///
diff --git a/CSF.Screenplay.Abstractions/Performables/StopTheStopwatch.cs b/CSF.Screenplay.Abstractions/Performables/StopTheStopwatch.cs
index 20ecad96..00ecc6d4 100644
--- a/CSF.Screenplay.Abstractions/Performables/StopTheStopwatch.cs
+++ b/CSF.Screenplay.Abstractions/Performables/StopTheStopwatch.cs
@@ -17,7 +17,7 @@ namespace CSF.Screenplay.Performables
public class StopTheStopwatch : IPerformable, ICanReport
{
///
- public ReportFragment GetReportFragment(IHasName actor, IFormatsReportFragment formatter)
+ public ReportFragment GetReportFragment(Actor actor, IFormatsReportFragment formatter)
=> formatter.Format(PerformableReportStrings.StopTheStopwatchFormat, actor);
///
diff --git a/CSF.Screenplay.Abstractions/Performances/ICreatesPerformance.cs b/CSF.Screenplay.Abstractions/Performances/ICreatesPerformance.cs
deleted file mode 100644
index f3e5a7e4..00000000
--- a/CSF.Screenplay.Abstractions/Performances/ICreatesPerformance.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace CSF.Screenplay.Performances
-{
- /// An object which creates instances of ; a factory service.
- public interface ICreatesPerformance
- {
- /// Creates a new performance instance.
- /// A new performance instance
- IPerformance CreatePerformance();
- }
-}
\ No newline at end of file
diff --git a/CSF.Screenplay.Abstractions/Performances/IRelaysPerformanceEvents.cs b/CSF.Screenplay.Abstractions/Performances/IRelaysPerformanceEvents.cs
index cd92421a..82e9a4e7 100644
--- a/CSF.Screenplay.Abstractions/Performances/IRelaysPerformanceEvents.cs
+++ b/CSF.Screenplay.Abstractions/Performances/IRelaysPerformanceEvents.cs
@@ -53,6 +53,9 @@ public interface IRelaysPerformanceEvents
///
///
/// Use this method when ending a performance, as a convenience to unsubscribe from all of its actors at once.
+ /// Note that this method might not result in unsubscribing any actors. If the subscribed actors are also managed
+ /// by an implementation of then the disposal of the cast will automatically unsubscribe these actors
+ /// on its own. This method is provided & used as a backup technique, in case the actors are not managed by a cast.
///
///
/// The identity of a performance.
@@ -103,17 +106,15 @@ public interface IRelaysPerformanceEvents
///
/// Invokes an event indicating that a has begun.
///
- /// The performance identity
- /// The performance's hierarchical name
- void InvokePerformanceBegun(Guid performanceIdentity, IList namingHierarchy);
+ /// The performance
+ void InvokePerformanceBegun(IPerformance performance);
///
/// Invokes an event indicating that a has finished.
///
- /// The performance identity
- /// The performance's hierarchical name
+ /// The performance
/// A value indicating whether or not the performance was a success
- void InvokePerformanceFinished(Guid performanceIdentity, IList namingHierarchy, bool? success);
+ void InvokePerformanceFinished(IPerformance performance, bool? success);
#endregion
diff --git a/CSF.Screenplay.Abstractions/Performances/PerformanceCompleteEventArgs.cs b/CSF.Screenplay.Abstractions/Performances/PerformanceCompleteEventArgs.cs
index e3b1d6db..40eacacd 100644
--- a/CSF.Screenplay.Abstractions/Performances/PerformanceCompleteEventArgs.cs
+++ b/CSF.Screenplay.Abstractions/Performances/PerformanceCompleteEventArgs.cs
@@ -34,13 +34,10 @@ public class PerformanceFinishedEventArgs : PerformanceEventArgs
public bool? Success { get; }
/// Initialises a new instance of
- /// The performance identity
- /// The scenario hierarchy
+ /// The performance.
/// A value indicating whether or not the scenario completed with a succeess result
/// If the scenario hierarchy is
- public PerformanceFinishedEventArgs(Guid performanceIdentity,
- IReadOnlyList namingHierarchy,
- bool? success) : base(performanceIdentity, namingHierarchy)
+ public PerformanceFinishedEventArgs(IPerformance performance, bool? success) : base(performance)
{
Success = success;
}
diff --git a/CSF.Screenplay.Abstractions/Performances/PerformanceEventArgs.cs b/CSF.Screenplay.Abstractions/Performances/PerformanceEventArgs.cs
index 6806b4a9..efe6e465 100644
--- a/CSF.Screenplay.Abstractions/Performances/PerformanceEventArgs.cs
+++ b/CSF.Screenplay.Abstractions/Performances/PerformanceEventArgs.cs
@@ -10,6 +10,11 @@ namespace CSF.Screenplay.Performances
///
public class PerformanceEventArgs : PerformanceScopeEventArgs
{
+ ///
+ /// Gets the to which this event relates.
+ ///
+ public IPerformance Performance { get; }
+
/// Gets an ordered list of identifiers which indicate the 's name within an organisational hierarchy.
///
///
@@ -18,15 +23,14 @@ public class PerformanceEventArgs : PerformanceScopeEventArgs
///
///
///
- public IReadOnlyList NamingHierarchy { get; }
+ public IReadOnlyList NamingHierarchy => Performance.NamingHierarchy;
/// Initialises a new instance of
- /// The performance identity
- /// The screenplay naming hierarchy
+ /// The performance
/// If the scenario hierarchy is
- public PerformanceEventArgs(Guid performanceIdentity, IReadOnlyList namingHierarchy) : base(performanceIdentity)
+ public PerformanceEventArgs(IPerformance performance) : base(performance.PerformanceIdentity)
{
- NamingHierarchy = namingHierarchy ?? throw new ArgumentNullException(nameof(namingHierarchy));
+ Performance = performance;
}
}
}
\ No newline at end of file
diff --git a/CSF.Screenplay.Abstractions/Reporting/IGetsAssetFilePath.cs b/CSF.Screenplay.Abstractions/Reporting/IGetsAssetFilePath.cs
new file mode 100644
index 00000000..c783354a
--- /dev/null
+++ b/CSF.Screenplay.Abstractions/Reporting/IGetsAssetFilePath.cs
@@ -0,0 +1,29 @@
+namespace CSF.Screenplay.Reporting
+{
+ ///
+ /// A service which gets a filesystem path to which Screenplay asset files should be written, if they are to be written at all.
+ ///
+ public interface IGetsAssetFilePath
+ {
+ ///
+ /// Gets the filesystem path to which an asset file should be written.
+ ///
+ ///
+ ///
+ /// If reporting is disabled, for the same reasons as would return ,
+ /// then this method will also return .
+ /// In that case, reporting is disabled and no asset files should be written to the file system.
+ ///
+ ///
+ /// If reporting is enabled, then this method should return an absolute file system path to which an asset file should be written,
+ /// where the asset has the specified 'base name'. That base name should be a short filename fragment which describes the asset.
+ /// This file name will be embellished with other information by this method, such as to ensure that the file name is unique within
+ /// the current Screenplay run.
+ ///
+ ///
+ /// A short & descriptive filename fragment, which includes the file extension but no path information
+ /// An absolute file system path at which the asset file should be saved, or a reference indicating that
+ /// the asset file should not be saved.
+ string GetAssetFilePath(string baseName);
+ }
+}
\ No newline at end of file
diff --git a/CSF.Screenplay.Abstractions/Reporting/IGetsReportPath.cs b/CSF.Screenplay.Abstractions/Reporting/IGetsReportPath.cs
new file mode 100644
index 00000000..6ee81c59
--- /dev/null
+++ b/CSF.Screenplay.Abstractions/Reporting/IGetsReportPath.cs
@@ -0,0 +1,26 @@
+namespace CSF.Screenplay.Reporting
+{
+ ///
+ /// A service which gets the path to which the Screenplay report should be written.
+ ///
+ public interface IGetsReportPath
+ {
+ ///
+ /// Gets the path to which the report should be written.
+ ///
+ ///
+ ///
+ /// If the returned path is then Screenplay's reporting functionality should be disabled and no report should be written.
+ /// Otherwise, implementations of this interface should return an absolute file system path to which the report should be written.
+ /// This path must be writable by the executing process.
+ ///
+ ///
+ /// Reporting could be disabled if either the Screenplay Options report path is or a whitespace-only string, or if the path
+ /// indicated by those options is not writable.
+ ///
+ ///
+ /// The report path.
+ string GetReportPath();
+ }
+}
+
diff --git a/CSF.Screenplay.Docs/docfx.json b/CSF.Screenplay.Docs/docfx.json
index d4bc2031..144d8cd7 100644
--- a/CSF.Screenplay.Docs/docfx.json
+++ b/CSF.Screenplay.Docs/docfx.json
@@ -9,8 +9,9 @@
"docs/**",
"**/bin/**",
"**/obj/**",
- "Tests_old/**",
- "Tests/**"
+ "Tests/**",
+ "*_old/**",
+ "CSF.Screenplay.JsonToHtmlReport.Template/**"
]
}
],
diff --git a/CSF.Screenplay.Docs/docs/dependencyInjection/InjectableServices.md b/CSF.Screenplay.Docs/docs/dependencyInjection/InjectableServices.md
index a2ef5b5b..2d3ff741 100644
--- a/CSF.Screenplay.Docs/docs/dependencyInjection/InjectableServices.md
+++ b/CSF.Screenplay.Docs/docs/dependencyInjection/InjectableServices.md
@@ -1,5 +1,40 @@
+---
+uid: InjectableServicesArticle
+---
+
# Injectable services
-TODO: Write this docco
+Screenplay explicitly supports dependency-injection of the following services from the Screenplay architecture into your [performance] logic.
+These are _in addition to_ any services you may have configured yourself within DI.
+
+Whilst it may be possible to inject other services from Screenplay's architecture, these are explicitly supported throughout all [integrations].
+
+[performance]: xref:CSF.Screenplay.IPerformance
+[integrations]: ../../glossary/Integration.md
+
+## The Stage
+
+Inject the [`IStage`] into your [performance] logic to get and work with [Actors].
+This is _the recommended way_ to manage any actors involved in a performance.
+
+The stage provides all the functionality of the Cast (below) as well as control of the [spotlight], should you wish to use it.
+
+[`IStage`]: xref:CSF.Screenplay.IStage
+[Actors]: xref:CSF.Screenplay.Actor
+[spotlight]: ../../glossary/Spotlight.md
+
+## The Cast
+
+The [`ICast`] is available for dependency injection into your [performance] logic as an alternative mechanism by which to work with [Actors].
+It is recommended to _inject the Stage (above) instead of the Cast_.
+The Cast is reachable from the Stage should you need it, via the [`Cast`] property.
+
+[`ICast`]: xref:CSF.Screenplay.ICast
+[`Cast`]: xref:CSF.Screenplay.IStage.Cast
+
+## The Performance
+
+You may inject an instance of [`IPerformance`] to gain direct access to the current [performance] object, from within your performance logic.
+Whilst supported, _it is usually not required (or recommended)_ to inject this object into your performance logic.
-It should be a list of services which Screenplay makes available for DI, such as the cast & stage etc.
+[`IPerformance`]: xref:CSF.Screenplay.IPerformance
diff --git a/CSF.Screenplay.Docs/glossary/Integration.md b/CSF.Screenplay.Docs/glossary/Integration.md
index 1edfa1cd..72536b08 100644
--- a/CSF.Screenplay.Docs/glossary/Integration.md
+++ b/CSF.Screenplay.Docs/glossary/Integration.md
@@ -6,9 +6,10 @@ uid: IntegrationGlossaryItem
An **Integration** refers to an integration library between the Screenplay library and a framework for performing automated tests.
-The integration library performs the necessary scaffolding to make the Screenplay types available for dependency injection.
+The integration library performs the necessary scaffolding to ensure that [supported Screenplay types are available for dependency injection].
It also deals with the association of **[Scenarios]** with **[Performances]** and the lifetime of the whole **[Screenplay]**, culminating with the production of the **[Report]**.
+[supported Screenplay types are available for dependency injection]: ../docs/dependencyInjection/InjectableServices.md
[Scenarios]: Scenario.md
[Performances]: xref:CSF.Screenplay.IPerformance
[Screenplay]: xref:CSF.Screenplay.Screenplay
diff --git a/CSF.Screenplay.Docs/glossary/index.md b/CSF.Screenplay.Docs/glossary/index.md
index 7b2b4ea2..8d98c293 100644
--- a/CSF.Screenplay.Docs/glossary/index.md
+++ b/CSF.Screenplay.Docs/glossary/index.md
@@ -4,24 +4,24 @@ Following is a glossary of Screenplay terminology; each term is a link to its ow
Many of these terms are implemented directly as .NET types in the Screenplay architecture.
Where applicable, the glossary item links directly to the relevant type within the [API documentation].
-| Term | Summary |
-| ---- | ------- |
-| [Screenplay] | A complete execution of the Screenplay software |
-| [Performance] | A single end-to-end script of performables |
-| [Performable] | A Screenplay verb; something that an actor can do |
-| [Action] | A kind of peformable; the lowest-level interaction that changes the state of the application |
-| [Question] | A kind of peformable; the lowest-level interrogation that reads application state |
-| [Task] | A composition of actions, questions and/or other tasks to create higher-level performables |
-| [Actor] | Typically a human user of the application, directs the use of performables |
-| [Ability] | Something that an actor is able to do or has; provides the dependencies for actions/questions |
-| [Persona] | A factory or template for consistently creating reusable, well-known actors |
-| [Cast] | A factory & registry for actors which facilitates managing multiple actors in a performance |
-| [Stage] | Provides situational context; a concept of 'the currently active actor' |
-| [Spotlight] | The currently active actor, facilitated by the stage |
-| [Report] | An output which details every performance/scenario and the outcomes of theie performables |
-| [Scenario] | Typically similar to a performance, this is a single test within a testing framework |
-| [Feature] | A logical group of related scenarios, this is a test class or test fixture in some testing frameworks |
-| [Integration] | A consumer of the Screenplay framework, such as a testing framework |
+| Term | Summary |
+| ---- | ------- |
+| [Ability] | Something that an actor is able to do or has; provides the dependencies for actions/questions |
+| [Action] | A kind of peformable; the lowest-level interaction that changes the state of the application |
+| [Actor] | Typically a human user of the application, directs the use of performables |
+| [Cast] | A factory & registry for actors which facilitates managing multiple actors in a performance |
+| [Feature] | A logical group of related scenarios, this is a test class or test fixture in some testing frameworks |
+| [Integration] | A consumer of the Screenplay framework, such as a testing framework |
+| [Performable] | A Screenplay verb; something that an actor can do |
+| [Performance] | A single end-to-end script of performables |
+| [Persona] | A factory or template for consistently creating reusable, well-known actors |
+| [Question] | A kind of peformable; the lowest-level interrogation that reads application state |
+| [Report] | An output which details every performance/scenario and the outcomes of theie performables |
+| [Scenario] | Typically similar to a performance, this is a single test within a testing framework |
+| [Screenplay] | A complete execution of the Screenplay software |
+| [Spotlight] | The currently active actor, facilitated by the stage |
+| [Stage] | Provides situational context; a concept of 'the currently active actor' |
+| [Task] | A composition of actions, questions and/or other tasks to create higher-level performables |
[API documentation]: xref:CSF.Screenplay
[Screenplay]: xref:CSF.Screenplay.Screenplay
diff --git a/CSF.Screenplay.Docs/glossary/toc.yml b/CSF.Screenplay.Docs/glossary/toc.yml
index f5cd997f..d49ee9bd 100644
--- a/CSF.Screenplay.Docs/glossary/toc.yml
+++ b/CSF.Screenplay.Docs/glossary/toc.yml
@@ -15,7 +15,7 @@
- name: Performable
href: Performable.md
- name: Performance
- uid: CSF.Screenplay.Performance
+ uid: CSF.Screenplay.IPerformance
- name: Persona
href: Persona.md
- name: Question
@@ -26,6 +26,8 @@
href: Scenario.md
- name: Screenplay
uid: CSF.Screenplay.Screenplay
+- name: Spotlight
+ href: Spotlight.md
- name: Stage
uid: CSF.Screenplay.IStage
- name: Task
diff --git a/CSF.Screenplay.JsonToHtmlReport.Template/src/js/ReportLoader.js b/CSF.Screenplay.JsonToHtmlReport.Template/src/js/ReportLoader.js
index 893c6f2a..8b33e496 100644
--- a/CSF.Screenplay.JsonToHtmlReport.Template/src/js/ReportLoader.js
+++ b/CSF.Screenplay.JsonToHtmlReport.Template/src/js/ReportLoader.js
@@ -19,8 +19,7 @@ export class ReportLoader {
const jsonData = JSON.parse(scriptElement.textContent);
return jsonData;
} catch (error) {
- console.error(error);
- throw new Error('Failed to parse JSON content');
+ throw new Error('Failed to parse JSON content whilst loading a Screenplay report', { cause: error });
}
}
}
diff --git a/CSF.Screenplay.JsonToHtmlReport.Template/src/package-lock.json b/CSF.Screenplay.JsonToHtmlReport.Template/src/package-lock.json
index 42daa487..a01c093a 100644
--- a/CSF.Screenplay.JsonToHtmlReport.Template/src/package-lock.json
+++ b/CSF.Screenplay.JsonToHtmlReport.Template/src/package-lock.json
@@ -39,15 +39,15 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
- "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
@@ -386,9 +386,9 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -396,9 +396,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -431,27 +431,27 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
- "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.0"
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
- "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.26.0"
+ "@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1778,28 +1778,25 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
- "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "regenerator-runtime": "^0.14.0"
- },
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
- "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.25.9",
- "@babel/parser": "^7.25.9",
- "@babel/types": "^7.25.9"
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1825,14 +1822,14 @@
}
},
"node_modules/@babel/types": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
- "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -3061,9 +3058,9 @@
"license": "ISC"
},
"node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3134,6 +3131,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -3179,9 +3190,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001676",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz",
- "integrity": "sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==",
+ "version": "1.0.30001762",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz",
+ "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==",
"dev": true,
"funding": [
{
@@ -3441,9 +3452,9 @@
}
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3953,6 +3964,21 @@
"tslib": "^2.0.3"
}
},
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.50",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.50.tgz",
@@ -4027,6 +4053,26 @@
"is-arrayish": "^0.2.1"
}
},
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-module-lexer": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
@@ -4034,6 +4080,35 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -4296,14 +4371,16 @@
}
},
"node_modules/form-data": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
- "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -4362,6 +4439,31 @@
"node": "6.* || 8.* || >= 10.*"
}
},
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
@@ -4372,6 +4474,20 @@
"node": ">=8.0.0"
}
},
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -4424,6 +4540,19 @@
"node": ">=4"
}
},
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -4441,6 +4570,35 @@
"node": ">=8"
}
},
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -5592,9 +5750,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5838,6 +5996,16 @@
"tmpl": "1.0.5"
}
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@@ -5941,9 +6109,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
@@ -6211,9 +6379,9 @@
"license": "MIT"
},
"node_modules/picocolors": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
- "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
@@ -6948,13 +7116,6 @@
"node": ">=4"
}
},
- "node_modules/regenerator-runtime": {
- "version": "0.14.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/regenerator-transform": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
diff --git a/CSF.Screenplay.JsonToHtmlReport/CSF.Screenplay.JsonToHtmlReport.csproj b/CSF.Screenplay.JsonToHtmlReport/CSF.Screenplay.JsonToHtmlReport.csproj
index 198c1413..b5b533fb 100644
--- a/CSF.Screenplay.JsonToHtmlReport/CSF.Screenplay.JsonToHtmlReport.csproj
+++ b/CSF.Screenplay.JsonToHtmlReport/CSF.Screenplay.JsonToHtmlReport.csproj
@@ -7,6 +7,9 @@
NU1903,NU1902CSF.Screenplay.JsonToHtmlReport$(MSBuildProjectDirectory)\bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
+
+ false
diff --git a/CSF.Screenplay.NUnit/CastAdapter.cs b/CSF.Screenplay.NUnit/CastAdapter.cs
new file mode 100644
index 00000000..f940f9f3
--- /dev/null
+++ b/CSF.Screenplay.NUnit/CastAdapter.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace CSF.Screenplay
+{
+ ///
+ /// An adapter which enables the use of within an NUnit3 test, without needing to parameter-inject the instance
+ /// as Lazy<ICast>.
+ ///
+ ///
+ ///
+ /// Due to NUnit architectural limitations, injectable parameters cannot be resolved from DI at the point the test method is built.
+ /// If we were to attempt this, then the parameter value would not be associated with the correct Screenplay/DI scope (and thus Event Bus).
+ /// This is due to the two-process model which NUnit uses; one process for building the test methods and another process for running the
+ /// tests. By using an adapter with Lazy resolution of the real implementation, we ensure that DI resolution is deferred into the test-run
+ /// process and not the test-building process.
+ ///
+ ///
+ public sealed class CastAdapter : ICast
+ {
+ readonly Lazy wrappedCast;
+
+ ///
+ public IServiceProvider ServiceProvider => wrappedCast.Value.ServiceProvider;
+
+ ///
+ public Guid PerformanceIdentity => wrappedCast.Value.PerformanceIdentity;
+
+ ///
+ public Actor GetActor(string name) => wrappedCast.Value.GetActor(name);
+
+ ///
+ public Actor GetActor(IPersona persona) => wrappedCast.Value.GetActor(persona);
+
+ ///
+ public IReadOnlyCollection GetCastList() => wrappedCast.Value.GetCastList();
+
+ ///
+ /// Creates a new instance of for the specified performance identity.
+ ///
+ /// A performance identity, corresponding to .
+ public CastAdapter(Guid performanceIdentity)
+ {
+ wrappedCast = new Lazy(() => ScreenplayLocator.GetScopedPerformance(performanceIdentity).Scope.ServiceProvider.GetRequiredService());
+ }
+ }
+}
+
diff --git a/CSF.Screenplay.NUnit/PerformanceAdapter.cs b/CSF.Screenplay.NUnit/PerformanceAdapter.cs
new file mode 100644
index 00000000..c4aa8499
--- /dev/null
+++ b/CSF.Screenplay.NUnit/PerformanceAdapter.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using CSF.Screenplay.Performances;
+
+namespace CSF.Screenplay
+{
+ ///
+ /// An adapter which enables the use of within an NUnit3 test, without needing to parameter-inject the instance
+ /// as Lazy<IPerformance>.
+ ///
+ ///
+ ///
+ /// Due to NUnit architectural limitations, injectable parameters cannot be resolved from DI at the point the test method is built.
+ /// If we were to attempt this, then the parameter value would not be associated with the correct Screenplay/DI scope (and thus Event Bus).
+ /// This is due to the two-process model which NUnit uses; one process for building the test methods and another process for running the
+ /// tests. By using an adapter with Lazy resolution of the real implementation, we ensure that DI resolution is deferred into the test-run
+ /// process and not the test-building process.
+ ///
+ ///
+ public sealed class PerformanceAdapter : IPerformance
+ {
+ readonly Lazy wrappedPerformance;
+
+ ///
+ public IReadOnlyList NamingHierarchy => wrappedPerformance.Value.NamingHierarchy;
+
+ ///
+ public PerformanceState PerformanceState => wrappedPerformance.Value.PerformanceState;
+
+ ///
+ public Guid PerformanceIdentity => wrappedPerformance.Value.PerformanceIdentity;
+
+ ///
+ public IServiceProvider ServiceProvider => wrappedPerformance.Value.ServiceProvider;
+
+ ///
+ public void BeginPerformance() => wrappedPerformance.Value.BeginPerformance();
+
+ ///
+ public void Dispose() => wrappedPerformance.Value.Dispose();
+
+ ///
+ public void FinishPerformance(bool? success) => wrappedPerformance.Value.FinishPerformance(success);
+
+ ///
+ /// Creates a new instance of for the specified performance identity.
+ ///
+ /// A performance identity, corresponding to .
+ public PerformanceAdapter(Guid performanceIdentity)
+ {
+ wrappedPerformance = new Lazy(() => ScreenplayLocator.GetScopedPerformance(performanceIdentity).Performance);
+ }
+ }
+}
+
diff --git a/CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs b/CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs
index 69984e03..0a219685 100644
--- a/CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs
+++ b/CSF.Screenplay.NUnit/ScreenplayAssemblyAttribute.cs
@@ -55,10 +55,10 @@ public Screenplay GetScreenplay()
public override ActionTargets Targets => ActionTargets.Suite;
///
- public override void AfterTest(ITest test) => GetScreenplay().CompleteScreenplay();
+ public override void AfterTest(ITest test) => ScreenplayLocator.GetScreenplay(test).CompleteScreenplay();
///
- public override void BeforeTest(ITest test) => GetScreenplay().BeginScreenplay();
+ public override void BeforeTest(ITest test) => ScreenplayLocator.GetScreenplay(test).BeginScreenplay();
///
/// Initializes a new instance of .
diff --git a/CSF.Screenplay.NUnit/ScreenplayAttribute.cs b/CSF.Screenplay.NUnit/ScreenplayAttribute.cs
index 48f66ad0..25d19306 100644
--- a/CSF.Screenplay.NUnit/ScreenplayAttribute.cs
+++ b/CSF.Screenplay.NUnit/ScreenplayAttribute.cs
@@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
-using CSF.Screenplay.Performances;
-using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
-using NUnit.Framework.Internal.Builders;
-using static CSF.Screenplay.ScreenplayLocator;
namespace CSF.Screenplay
{
@@ -34,25 +29,16 @@ namespace CSF.Screenplay
public class ScreenplayAttribute : Attribute, ITestAction, ITestBuilder
{
///
- /// The name of an NUnit3 Property for the suite or test description.
- ///
- internal const string DescriptionPropertyName = "Description";
-
- ///
- /// A key for an NUnit3 Property which will hold the current .
- ///
- internal const string CurrentPerformanceKey = "Current Screenplay Performance";
-
- ///
- /// A key for an NUnit3 Property which will hold the of the current .
+ /// A key for an NUnit3 Property which will hold the
+ /// of the current .
///
+ ///
+ ///
+ /// This property value may be used with
+ ///
+ ///
internal const string CurrentPerformanceIdentityKey = "Current Screenplay Performance identity";
- ///
- /// A key for an NUnit3 Property which will hold the current Dependency Injection scope.
- ///
- internal const string CurrentDiScopeKey = "Current Screenplay DI scope";
-
///
/// Gets the targets for the attribute (when performing before/after test actions).
///
@@ -63,77 +49,21 @@ public class ScreenplayAttribute : Attribute, ITestAction, ITestBuilder
///
public void BeforeTest(ITest test)
{
- var performance = GetPerformance(test);
- BackfillPerformanceNamingHierarchy(performance, test);
- performance.BeginPerformance();
+ var performance = ScreenplayLocator.GetScopedPerformance(test);
+ performance.Performance.BeginPerformance();
}
///
public void AfterTest(ITest test)
{
- var performance = GetPerformance(test);
- performance.FinishPerformance(GetOutcome());
-
- var diScope = test.Properties.Get(CurrentDiScopeKey) as IServiceScope;
+ var scopeAndPerformance = ScreenplayLocator.GetScopedPerformance(test);
+ scopeAndPerformance.Performance.FinishPerformance(GetOutcome());
+ var diScope = scopeAndPerformance.Scope;
diScope?.Dispose();
}
///
- public IEnumerable BuildFrom(IMethodInfo method, Test suite)
- {
- if (method is null)
- throw new ArgumentNullException(nameof(method));
- if (suite is null)
- throw new ArgumentNullException(nameof(suite));
-
- var screenplay = GetScreenplay(method);
- var scopeAndPerformance = screenplay.CreateScopedPerformance();
- var testMethod = GetTestMethod(scopeAndPerformance, method, suite);
- return new[] { testMethod };
- }
-
- static IPerformance GetPerformance(ITest test)
- {
- return (IPerformance)test.Properties.Get(CurrentPerformanceKey)
- ?? throw new ArgumentException($"The specified test must contain a property '{CurrentPerformanceKey}' containing an {nameof(IPerformance)}", nameof(test));
- }
-
- static TestMethod GetTestMethod(ScopeAndPerformance scopeAndPerformance, IMethodInfo method, Test suite)
- {
- var builder = new NUnitTestCaseBuilder();
- var resolvedTestMethodParameters = (from parameter in method.GetParameters()
- select scopeAndPerformance.Performance.ServiceProvider.GetService(parameter.ParameterType))
- .ToArray();
- var testCaseParameters = new TestCaseParameters(resolvedTestMethodParameters);
-
- var testMethod = builder.BuildTestMethod(method, suite, testCaseParameters);
- testMethod.Properties.Add(CurrentPerformanceKey, scopeAndPerformance.Performance);
- testMethod.Properties.Add(CurrentPerformanceIdentityKey, scopeAndPerformance.Performance.PerformanceIdentity);
- testMethod.Properties.Add(CurrentDiScopeKey, scopeAndPerformance.Scope);
- return testMethod;
- }
-
- static void BackfillPerformanceNamingHierarchy(IPerformance performance, ITest test)
- {
- var namingHierarchy = GetReverseOrderNamingHierarchy(test).ToList();
-
- // Reverse it to get it in the correct order
- namingHierarchy.Reverse();
- performance.NamingHierarchy.Clear();
- performance.NamingHierarchy.AddRange(namingHierarchy);
- }
-
- static IEnumerable GetReverseOrderNamingHierarchy(ITest suite)
- {
- for (var currentSuite = suite;
- currentSuite != null;
- currentSuite = currentSuite.Parent)
- {
- if (!currentSuite.IsSuite || (currentSuite.Method is null && currentSuite.Fixture is null))
- continue;
- yield return new IdentifierAndName(currentSuite.FullName, currentSuite.Properties.Get(DescriptionPropertyName)?.ToString());
- }
- }
+ public IEnumerable BuildFrom(IMethodInfo method, Test suite) => TestMethodBuilder.BuildFrom(method, suite);
static bool? GetOutcome()
{
diff --git a/CSF.Screenplay.NUnit/ScreenplayLocator.cs b/CSF.Screenplay.NUnit/ScreenplayLocator.cs
index 713eab14..a78e648e 100644
--- a/CSF.Screenplay.NUnit/ScreenplayLocator.cs
+++ b/CSF.Screenplay.NUnit/ScreenplayLocator.cs
@@ -1,7 +1,11 @@
using System;
using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
+using CSF.Screenplay.Performances;
using NUnit.Framework.Interfaces;
+using NUnit.Framework.Internal;
namespace CSF.Screenplay
{
@@ -18,7 +22,18 @@ namespace CSF.Screenplay
///
public static class ScreenplayLocator
{
+ ///
+ /// The name of an NUnit3 Property for the suite or test description.
+ ///
+ ///
+ ///
+ /// This is a built-in NUnit property. It is set automatically by the framework if/when a test is decorated with appropriate attributes.
+ ///
+ ///
+ internal const string DescriptionPropertyName = "Description";
+
static readonly ConcurrentDictionary screenplayCache = new ConcurrentDictionary();
+ static readonly ConcurrentDictionary performanceCache = new ConcurrentDictionary();
///
/// Gets a instance from the specified .
@@ -86,6 +101,8 @@ public static Screenplay GetScreenplay(IMethodInfo method)
public static Screenplay GetScreenplay(ITest test)
{
if(test is null) throw new ArgumentNullException(nameof(test));
+ if(test is TestAssembly testAssembly)
+ return GetScreenplay(testAssembly.Assembly);
return GetScreenplay(test.Method);
}
@@ -95,5 +112,75 @@ static Screenplay GetScreenplayFromAssembly(Assembly assembly)
?? throw new ArgumentException($"The assembly {assembly.FullName} must be decorated with {nameof(ScreenplayAssemblyAttribute)}.", nameof(assembly));
return assemblyAttrib.GetScreenplay();
}
+
+ ///
+ /// Gets a DI scope and for the specified test.
+ ///
+ ///
+ ///
+ /// This method will return a cached if one exists for the specified test.
+ /// If one does not yet exist then a new scope will be created, with an associated performance, and added to the cache.
+ ///
+ ///
+ /// An NUnit3 test object.
+ /// A DI scope and performance.
+ public static ScopeAndPerformance GetScopedPerformance(ITest test)
+ {
+ return performanceCache.GetOrAdd(GetPerformanceIdentity(test), _ => CreateScopedPerformance(test));
+ }
+
+ ///
+ /// Gets a DI scope and matching the specified performance identity.
+ ///
+ ///
+ ///
+ /// Unlike the other overload of this method, this overload will not create a scoped performance if one does not yet exist.
+ /// If this method is used with a performance identity which is not yet cached, then an exception will be raised.
+ ///
+ ///
+ /// A GUID performance identity, corresponding to .
+ /// A DI scope and performance.
+ /// If no scope & performance exists in the cache, matching the specified identity
+ public static ScopeAndPerformance GetScopedPerformance(Guid identity)
+ {
+ if (!performanceCache.TryGetValue(identity, out var result))
+ throw new ArgumentException($"There must be a cached performance with the identity {identity}", nameof(identity));
+ return result;
+ }
+
+ static ScopeAndPerformance CreateScopedPerformance(ITest test)
+ {
+ var screenplay = GetScreenplay(test);
+ return screenplay.CreateScopedPerformance(GetNamingHierarchy(test), GetPerformanceIdentity(test));
+ }
+
+ static Guid GetPerformanceIdentity(ITest test)
+ {
+ if (test is null) throw new ArgumentNullException(nameof(test));
+ if(!test.Properties.ContainsKey(ScreenplayAttribute.CurrentPerformanceIdentityKey))
+ throw new ArgumentException($"The test must contain a property by the name of '{ScreenplayAttribute.CurrentPerformanceIdentityKey}', " +
+ "containing a Guid performance identity",
+ nameof(test));
+ return (Guid) test.Properties.Get(ScreenplayAttribute.CurrentPerformanceIdentityKey);
+ }
+
+ static IList GetNamingHierarchy(ITest test)
+ {
+ var namingHierarchy = GetReverseOrderNamingHierarchy(test).ToList();
+ namingHierarchy.Reverse();
+ return namingHierarchy;
+ }
+
+ static IEnumerable GetReverseOrderNamingHierarchy(ITest suite)
+ {
+ for (var currentSuite = suite;
+ currentSuite != null;
+ currentSuite = currentSuite.Parent)
+ {
+ if (!currentSuite.IsSuite || (currentSuite.Method is null && currentSuite.Fixture is null))
+ continue;
+ yield return new IdentifierAndName(currentSuite.FullName, currentSuite.Properties.Get(DescriptionPropertyName)?.ToString());
+ }
+ }
}
}
diff --git a/CSF.Screenplay.NUnit/StageAdapter.cs b/CSF.Screenplay.NUnit/StageAdapter.cs
new file mode 100644
index 00000000..8081853c
--- /dev/null
+++ b/CSF.Screenplay.NUnit/StageAdapter.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using CSF.Screenplay.Performances;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace CSF.Screenplay
+{
+ ///
+ /// An adapter which enables the use of within an NUnit3 test, without needing to parameter-inject the instance
+ /// as Lazy<IStage>.
+ ///
+ ///
+ ///
+ /// Due to NUnit architectural limitations, injectable parameters cannot be resolved from DI at the point the test method is built.
+ /// If we were to attempt this, then the parameter value would not be associated with the correct Screenplay/DI scope (and thus Event Bus).
+ /// This is due to the two-process model which NUnit uses; one process for building the test methods and another process for running the
+ /// tests. By using an adapter with Lazy resolution of the real implementation, we ensure that DI resolution is deferred into the test-run
+ /// process and not the test-building process.
+ ///
+ ///
+ public sealed class StageAdapter : IStage
+ {
+ readonly Lazy wrappedStage;
+
+ ///
+ public ICast Cast => wrappedStage.Value.Cast;
+
+ ///
+ public Actor GetSpotlitActor() => wrappedStage.Value.GetSpotlitActor();
+
+ ///
+ public void Spotlight(Actor actor) => wrappedStage.Value.Spotlight(actor);
+
+ ///
+ public Actor Spotlight(IPersona persona) => wrappedStage.Value.Spotlight(persona);
+
+ ///
+ public Actor TurnSpotlightOff() => wrappedStage.Value.TurnSpotlightOff();
+
+ ///
+ /// Creates a new instance of for the specified performance identity.
+ ///
+ /// A performance identity, corresponding to .
+ public StageAdapter(Guid performanceIdentity)
+ {
+ wrappedStage = new Lazy(() => ScreenplayLocator.GetScopedPerformance(performanceIdentity).Scope.ServiceProvider.GetRequiredService());
+ }
+ }
+}
+
diff --git a/CSF.Screenplay.NUnit/TestMethodBuilder.cs b/CSF.Screenplay.NUnit/TestMethodBuilder.cs
new file mode 100644
index 00000000..6375d7cb
--- /dev/null
+++ b/CSF.Screenplay.NUnit/TestMethodBuilder.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework.Interfaces;
+using NUnit.Framework.Internal;
+using NUnit.Framework.Internal.Builders;
+
+namespace CSF.Screenplay
+{
+ ///
+ /// Builder class which is used to create NUnit3 test method instances for a Screenplay-based test.
+ ///
+ public static class TestMethodBuilder
+ {
+ static readonly Dictionary> supportedInjectableTypes = new Dictionary>
+ {
+ { typeof(IStage), id => new StageAdapter(id) },
+ { typeof(ICast), id => new CastAdapter(id) },
+ { typeof(IPerformance), id => new PerformanceAdapter(id) },
+ };
+
+ ///
+ /// Gets a collection of NUnit3 TestMethod instances for the specified method and test.
+ ///
+ ///
+ ///
+ /// This method handles the resolution of the , which may be used in tests by
+ /// adding them as parameters to the NUnit test method.
+ ///
+ ///
+ /// The NUnit method object for a test
+ /// The NUnit test suite object
+ /// A collection of NUnit test method instances
+ /// If any parameter is
+ public static IEnumerable BuildFrom(IMethodInfo method, Test suite)
+ {
+ if (method is null) throw new ArgumentNullException(nameof(method));
+ if (suite is null) throw new ArgumentNullException(nameof(suite));
+
+ var builder = new NUnitTestCaseBuilder();
+
+ var performanceId = Guid.NewGuid();
+ var testCaseParameters = GetResolvedParameters(method, performanceId);
+
+ var testMethod = builder.BuildTestMethod(method, suite, testCaseParameters);
+ testMethod.Properties.Add(ScreenplayAttribute.CurrentPerformanceIdentityKey, performanceId);
+
+ return new [] { testMethod };
+ }
+
+ static TestCaseParameters GetResolvedParameters(IMethodInfo method, Guid performanceId)
+ {
+ var requestedParameters = method.GetParameters();
+ if(requestedParameters.Any(param => !supportedInjectableTypes.Keys.Contains(param.ParameterType)))
+ throw new InvalidOperationException("The test method must not contain any parameters except those of supported injectable types:\n" +
+ string.Join(", ", supportedInjectableTypes.Keys.Select(x => x.Name)) +
+ "\nUnfortunately, NUnit's architecture makes it troublesome to support arbitrary injectable parameters.");
+ var resolvedParameterValues = requestedParameters.Select(param => supportedInjectableTypes[param.ParameterType].Invoke(performanceId)).ToArray();
+ return new TestCaseParameters(resolvedParameterValues);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/AutoMoqDataAttribute.cs b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/AutoMoqDataAttribute.cs
new file mode 100644
index 00000000..63115422
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/AutoMoqDataAttribute.cs
@@ -0,0 +1,14 @@
+using System;
+using Ploeh.AutoFixture;
+using Ploeh.AutoFixture.AutoMoq;
+using Ploeh.AutoFixture.NUnit3;
+
+namespace CSF.Screenplay.Selenium.Tests
+{
+ public class AutoMoqDataAttribute : AutoDataAttribute
+ {
+ public AutoMoqDataAttribute() : base(new Fixture().Customize(new AutoMoqCustomization()))
+ {
+ }
+ }
+}
diff --git a/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/CSF.Screenplay.Selenium.BrowserFlags.Tests.csproj b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/CSF.Screenplay.Selenium.BrowserFlags.Tests.csproj
new file mode 100644
index 00000000..f1d2c1b4
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/CSF.Screenplay.Selenium.BrowserFlags.Tests.csproj
@@ -0,0 +1,83 @@
+
+
+
+ Debug
+ AnyCPU
+ {87454F03-FBB3-4506-9055-551297445891}
+ Library
+ CSF.Screenplay.Selenium.Tests
+ CSF.Screenplay.Selenium.BrowserFlags.Tests
+ v4.5
+ 1.0.0
+
+
+ true
+ full
+ false
+ bin\Debug
+ DEBUG;
+ prompt
+ 4
+
+
+ true
+ bin\Release
+ prompt
+ 4
+
+
+
+
+ ..\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll
+
+
+ ..\packages\AutoFixture.3.50.3\lib\net40\Ploeh.AutoFixture.dll
+
+
+ ..\packages\AutoFixture.AutoMoq.3.50.3\lib\net40\Ploeh.AutoFixture.AutoMoq.dll
+
+
+ ..\packages\AutoFixture.NUnit3.3.50.3\lib\net40\Ploeh.AutoFixture.NUnit3.dll
+
+
+ ..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll
+
+
+ ..\packages\Moq.4.7.25\lib\net45\Moq.dll
+
+
+ ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
+
+
+ ..\packages\Selenium.WebDriver.3.4.0\lib\net40\WebDriver.dll
+
+
+
+ ..\packages\CSF.Configuration.1.1.2\lib\net45\CSF.Configuration.dll
+
+
+ ..\packages\CSF.WebDriverExtras.1.0.3\lib\net45\CSF.WebDriverExtras.dll
+
+
+
+
+
+
+
+
+
+ FooBrowser.flags.json
+
+
+
+
+
+
+
+
+ {0665F99E-DB05-4208-BCF1-137EF914CBF5}
+ CSF.Screenplay.Selenium.BrowserFlags
+
+
+
+
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/FooBrowser.flags.json b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/FooBrowser.flags.json
new file mode 100644
index 00000000..d6c1fd8c
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/FooBrowser.flags.json
@@ -0,0 +1,8 @@
+[
+ {
+ "browserName": "FooBrowser",
+ "flags": [
+ "SampleFlag1",
+ ],
+ },
+]
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/GetBrowserFlagsDefinitionsTests.cs b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/GetBrowserFlagsDefinitionsTests.cs
new file mode 100644
index 00000000..cd2fb6ca
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/GetBrowserFlagsDefinitionsTests.cs
@@ -0,0 +1,89 @@
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using CSF.WebDriverExtras.Flags;
+using Moq;
+using NUnit.Framework;
+
+namespace CSF.Screenplay.Selenium.Tests
+{
+ [TestFixture,Parallelizable(ParallelScope.All)]
+ public class GetBrowserFlagsDefinitionsTests
+ {
+ [Test,AutoMoqData]
+ public void GetDefinitions_from_test_assembly_returns_one_definition(IReadsFlagsDefinitions definitionReader,
+ FlagsDefinition def)
+ {
+ // Arrange
+ var testAssembly = Assembly.GetExecutingAssembly();
+ Mock.Get(definitionReader)
+ .Setup(x => x.GetFlagsDefinitions(It.IsAny()))
+ .Returns(new [] { def });
+ var sut = new GetBrowserFlagsDefinitions(definitionReader);
+
+ // Act
+ var result = sut.GetDefinitions(testAssembly);
+
+ // Assert
+ Assert.That(result, Has.Length.EqualTo(1));
+ }
+
+ [Test,AutoMoqData]
+ public void GetDefinitions_from_test_assembly_uses_definition_reader(IReadsFlagsDefinitions definitionReader,
+ FlagsDefinition def)
+ {
+ // Arrange
+ var testAssembly = Assembly.GetExecutingAssembly();
+ Mock.Get(definitionReader)
+ .Setup(x => x.GetFlagsDefinitions(It.IsAny()))
+ .Returns(new [] { def });
+ var sut = new GetBrowserFlagsDefinitions(definitionReader);
+
+ // Act
+ var result = sut.GetDefinitions(testAssembly);
+
+ // Assert
+ Mock.Get(definitionReader)
+ .Verify(x => x.GetFlagsDefinitions(It.IsAny()), Times.Once);
+ }
+
+ [Test,Category("Integration")]
+ public void GetDefinitions_integration_test_only_finds_one_definition()
+ {
+ // Arrange
+ var testAssembly = Assembly.GetExecutingAssembly();
+ var sut = new GetBrowserFlagsDefinitions();
+
+ // Act
+ var result = sut.GetDefinitions(testAssembly);
+
+ // Assert
+ Assert.That(result, Has.Length.EqualTo(1));
+ }
+
+ [Test,Category("Integration")]
+ public void GetDefinitions_integration_test_finds_definition_for_FooBrowser()
+ {
+ // Arrange
+ var testAssembly = Assembly.GetExecutingAssembly();
+ var sut = new GetBrowserFlagsDefinitions();
+
+ // Act
+ var result = sut.GetDefinitions(testAssembly).Single();
+
+ // Assert
+ Assert.That(result.BrowserNames.First(), Is.EqualTo("FooBrowser"));
+ }
+
+ [Test,Category("Integration")]
+ public void FromDefinitionsAssembly_integration_test_returns_more_than_two_flags_definitions()
+ {
+ // Act
+ var result = GetBrowserFlagsDefinitions.FromDefinitionsAssembly();
+
+ // Assert
+ Assert.That(result, Is.Not.Null);
+ Assert.That(result, Has.Length.GreaterThan(2));
+ }
+ }
+}
diff --git a/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/InvalidFlagsDefinition.json b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/InvalidFlagsDefinition.json
new file mode 100644
index 00000000..c4b1955f
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/InvalidFlagsDefinition.json
@@ -0,0 +1,8 @@
+[
+ {
+ "browserName": "BarBrowser",
+ "flags": [
+ "SampleFlag1",
+ ],
+ },
+]
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/app.config b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/app.config
new file mode 100644
index 00000000..437c6629
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/app.config
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/packages.config b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/packages.config
new file mode 100644
index 00000000..3ab15217
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags.Tests_old/packages.config
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/CSF.Screenplay.Selenium.BrowserFlags.csproj b/CSF.Screenplay.Selenium.BrowserFlags_old/CSF.Screenplay.Selenium.BrowserFlags.csproj
new file mode 100644
index 00000000..4a22cc42
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/CSF.Screenplay.Selenium.BrowserFlags.csproj
@@ -0,0 +1,80 @@
+
+
+
+ Debug
+ AnyCPU
+ {0665F99E-DB05-4208-BCF1-137EF914CBF5}
+ Library
+ CSF.Screenplay.Selenium
+ CSF.Screenplay.Selenium.BrowserFlags
+ v4.5
+ 1.0.0
+
+
+ true
+ full
+ false
+ bin\Debug
+ DEBUG;
+ prompt
+ 4
+ bin\Debug\CSF.Screenplay.Selenium.BrowserFlags.xml
+ false
+
+
+ true
+ bin\Release
+ prompt
+ 4
+ bin\Release\CSF.Screenplay.Selenium.BrowserFlags.xml
+ false
+
+
+
+
+ ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
+
+
+ ..\packages\Selenium.WebDriver.3.4.0\lib\net40\WebDriver.dll
+
+
+
+ ..\packages\CSF.Configuration.1.1.2\lib\net45\CSF.Configuration.dll
+
+
+ ..\packages\CSF.WebDriverExtras.1.0.3\lib\net45\CSF.WebDriverExtras.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AppleSafari.flags.json
+
+
+ GoogleChrome.flags.json
+
+
+ InternetExplorer.flags.json
+
+
+ MicrosoftEdge.flags.json
+
+
+ MozillaFirefox.flags.json
+
+
+
+
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/CSF.Screenplay.Selenium.BrowserFlags.nuspec b/CSF.Screenplay.Selenium.BrowserFlags_old/CSF.Screenplay.Selenium.BrowserFlags.nuspec
new file mode 100644
index 00000000..7e2b9106
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/CSF.Screenplay.Selenium.BrowserFlags.nuspec
@@ -0,0 +1,21 @@
+
+
+
+ CSF.Screenplay.Selenium.BrowserFlags
+ 1.0.0
+ CSF.Screenplay.Selenium.BrowserFlags
+ CSF Software Ltd
+ MIT
+ https://github.com/csf-dev/CSF.Screenplay.Selenium
+ false
+ An assembly containing 'browser flags definitions' for CSF.Screenplay.Selenium. This indicates the quirks and behaviours of various popular web browsers which are compatible with Selenium WebDriver.
+ Copyright 2018
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/AppleSafari.flags.json b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/AppleSafari.flags.json
new file mode 100644
index 00000000..286888f1
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/AppleSafari.flags.json
@@ -0,0 +1,15 @@
+[
+ {
+ "browserName": "Safari",
+ "flags": [
+ ],
+ },
+ {
+ "browserName": "Safari",
+ "minVersion": "11",
+ "flags": [
+ "HtmlElements.Select.RequiresUpdatesViaJavaScriptWorkaround",
+ ],
+ },
+]
+
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/GoogleChrome.flags.json b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/GoogleChrome.flags.json
new file mode 100644
index 00000000..b62dc273
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/GoogleChrome.flags.json
@@ -0,0 +1,8 @@
+[
+ {
+ "browserName": "Chrome",
+ "flags": [
+ "HtmlElements.InputTypeDate.RequiresEntryUsingLocaleFormat",
+ ],
+ },
+]
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/InternetExplorer.flags.json b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/InternetExplorer.flags.json
new file mode 100644
index 00000000..f92a921c
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/InternetExplorer.flags.json
@@ -0,0 +1,7 @@
+[
+ {
+ "browserName": [ "InternetExplorer", "Internet Explorer" ],
+ "flags": [
+ ],
+ },
+]
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/MicrosoftEdge.flags.json b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/MicrosoftEdge.flags.json
new file mode 100644
index 00000000..d9fd8749
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/MicrosoftEdge.flags.json
@@ -0,0 +1,10 @@
+[
+ {
+ "browserName": "MicrosoftEdge",
+ "flags": [
+ "Browser.CannotClearDomainCookies",
+ "HtmlElements.SelectMultiple.RequiresCtrlClickToToggleOptionSelection",
+ "HtmlElements.InputTypeDate.RequiresInputViaJavaScriptWorkaround",
+ ],
+ },
+]
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/MozillaFirefox.flags.json b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/MozillaFirefox.flags.json
new file mode 100644
index 00000000..c8e38046
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/Definitions/MozillaFirefox.flags.json
@@ -0,0 +1,8 @@
+[
+ {
+ "browserName": "Firefox",
+ "flags": [
+ "HtmlElements.InputTypeDate.RequiresInputViaJavaScriptWorkaround"
+ ],
+ },
+]
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/Flags.Browser.cs b/CSF.Screenplay.Selenium.BrowserFlags_old/Flags.Browser.cs
new file mode 100644
index 00000000..b1cef97f
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/Flags.Browser.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace CSF.Screenplay.Selenium
+{
+ public static partial class Flags
+ {
+ ///
+ /// Flags relating to the web browser itself, such as management of cookies.
+ ///
+ public static class Browser
+ {
+ ///
+ /// Indicates that the web driver is incapable of clearing the cookies for the current domain.
+ ///
+ ///
+ ///
+ /// The current domain is the domain for the page upon which the web driver is currently viewing.
+ ///
+ ///
+ public static readonly string CannotClearDomainCookies = "Browser.CannotClearDomainCookies";
+ }
+ }
+}
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/Flags.HtmlElements.cs b/CSF.Screenplay.Selenium.BrowserFlags_old/Flags.HtmlElements.cs
new file mode 100644
index 00000000..3be266d8
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/Flags.HtmlElements.cs
@@ -0,0 +1,55 @@
+using System;
+namespace CSF.Screenplay.Selenium
+{
+ public static partial class Flags
+ {
+ ///
+ /// Flags relating to specific HTML elements.
+ ///
+ public static class HtmlElements
+ {
+ ///
+ /// Flags relating to HTML <input type="date"> elements.
+ ///
+ public static class InputTypeDate
+ {
+ ///
+ /// Indicates that the web driver may be used to enter a date using a format that conforms to the web browser's
+ /// current locale setting.
+ ///
+ public static readonly string RequiresEntryUsingLocaleFormat = "HtmlElements.InputTypeDate.RequiresEntryUsingLocaleFormat";
+
+ ///
+ /// Indicates that the web driver must use a JavaScript workaround to set the date, because it is impossible to do so
+ /// by typing keys.
+ ///
+ public static readonly string RequiresInputViaJavaScriptWorkaround = "HtmlElements.InputTypeDate.RequiresInputViaJavaScriptWorkaround";
+ }
+
+ ///
+ /// Flags relating to HTML <select multiple> elements.
+ ///
+ public static class SelectMultiple
+ {
+ ///
+ /// Indicates that the web driver must send Ctrl+Click in order to toggle the selection of a single
+ /// option within the select element. Without Ctrl, the click is interpreted as "change entire selection to just
+ /// the one option clicked".
+ ///
+ public static readonly string RequiresCtrlClickToToggleOptionSelection = "HtmlElements.SelectMultiple.RequiresCtrlClickToToggleOptionSelection";
+ }
+
+ ///
+ /// Flags relating to HTML <select> elements.
+ ///
+ public static class Select
+ {
+ ///
+ /// Indicates that the browser requires a JavaScript workaround in order to change the selection state
+ /// of an HTML <select> element.
+ ///
+ public static readonly string RequiresUpdatesViaJavaScriptWorkaround = "HtmlElements.Select.RequiresUpdatesViaJavaScriptWorkaround";
+ }
+ }
+ }
+}
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/Flags.cs b/CSF.Screenplay.Selenium.BrowserFlags_old/Flags.cs
new file mode 100644
index 00000000..2a0f2e1c
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/Flags.cs
@@ -0,0 +1,10 @@
+using System;
+namespace CSF.Screenplay.Selenium
+{
+ ///
+ /// A catalogue of the the various browser flags of which this library is aware.
+ ///
+ public static partial class Flags
+ {
+ }
+}
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/GetBrowserFlagsDefinitions.cs b/CSF.Screenplay.Selenium.BrowserFlags_old/GetBrowserFlagsDefinitions.cs
new file mode 100644
index 00000000..d0a30191
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/GetBrowserFlagsDefinitions.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using CSF.WebDriverExtras.Flags;
+
+namespace CSF.Screenplay.Selenium
+{
+ ///
+ /// Helper type which gets browser flags definitions stored inside an assembly as embedded resources,
+ /// using the resource suffix .flags.json.
+ ///
+ public sealed class GetBrowserFlagsDefinitions
+ {
+ #region constants
+
+ const string FlagsResourceSuffix = ".flags.json";
+
+ #endregion
+
+ #region fields
+
+ readonly IReadsFlagsDefinitions definitionReader;
+ readonly object syncRoot;
+ IReadOnlyCollection cachedDefinitions;
+
+ #endregion
+
+ #region properties
+
+ Assembly ThisAssembly => Assembly.GetExecutingAssembly();
+
+ #endregion
+
+ #region public methods
+
+ ///
+ /// Gets the browser flags definitions contained within the CSF.Screenplay.Selenium.BrowserFlags assembly.
+ ///
+ /// The browser flags definitions.
+ public IReadOnlyCollection GetDefinitions() => GetDefinitions(ThisAssembly);
+
+ ///
+ /// Gets the browser flags definitions contained within the given assembly.
+ ///
+ /// The browser flags definitions.
+ /// An assembly to search for flags definitions.
+ public IReadOnlyCollection GetDefinitions(Assembly assembly)
+ {
+ if(assembly == null)
+ throw new ArgumentNullException(nameof(assembly));
+
+ if(assembly != ThisAssembly)
+ {
+ return GetDefinitionsFromAssembly(assembly);
+ }
+
+ lock(syncRoot)
+ {
+ if(cachedDefinitions != null)
+ return cachedDefinitions;
+
+ cachedDefinitions = GetDefinitionsFromAssembly(assembly);
+ return cachedDefinitions;
+ }
+ }
+
+ #endregion
+
+ #region methods
+
+ IReadOnlyCollection GetDefinitionsFromAssembly(Assembly assembly)
+ {
+ var resourceNames = GetDefinitionsResourceNames(assembly);
+ return resourceNames
+ .SelectMany(x => GetDefinitions(x, assembly))
+ .ToArray();
+ }
+
+ IEnumerable GetDefinitionsResourceNames(Assembly assembly)
+ => assembly
+ .GetManifestResourceNames()
+ .Where(x => x.EndsWith(FlagsResourceSuffix, StringComparison.InvariantCulture));
+
+ IReadOnlyCollection GetDefinitions(string resourceName, Assembly assembly)
+ {
+ using(var stream = assembly.GetManifestResourceStream(resourceName))
+ return definitionReader.GetFlagsDefinitions(stream);
+ }
+
+ #endregion
+
+ #region constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GetBrowserFlagsDefinitions() : this(null) {}
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Definition reader.
+ public GetBrowserFlagsDefinitions(IReadsFlagsDefinitions definitionReader)
+ {
+ syncRoot = new object();
+ this.definitionReader = definitionReader ?? new DefinitionReader();
+ }
+
+ #endregion
+
+ #region static methods
+
+ ///
+ /// Helper method which gets the flags definitions from the CSF.Screenplay.Selenium.BrowserFlags assembly.
+ ///
+ /// The dlags definitions.
+ public static IReadOnlyCollection FromDefinitionsAssembly()
+ => new GetBrowserFlagsDefinitions().GetDefinitions();
+
+ #endregion
+ }
+}
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/Properties/AssemblyInfo.cs b/CSF.Screenplay.Selenium.BrowserFlags_old/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..d14392e2
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/Properties/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("CSF.Screenplay.Selenium.BrowserFlags")]
+[assembly: AssemblyDescription("A repository of pre-defined Browser Flags for CSF.Screenplay.Selenium")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("CSF Software Limited")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("Craig Fowler")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.0.0")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/ScreenplayLocator.cs b/CSF.Screenplay.Selenium.BrowserFlags_old/ScreenplayLocator.cs
new file mode 100644
index 00000000..713eab14
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/ScreenplayLocator.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Concurrent;
+using System.Reflection;
+using NUnit.Framework.Interfaces;
+
+namespace CSF.Screenplay
+{
+ ///
+ /// A small static service locator of sorts, dedicated to getting an appropriate instance of for
+ /// a specified test object.
+ ///
+ ///
+ ///
+ /// This type uses reflection to find the which decorates the assembly in which the
+ /// specified object (a test, a test method or the assembly itself) resides.
+ /// It additionally caches the results in-memory to avoid repetitive reflection, only to retrieve the same results.
+ ///
+ ///
+ public static class ScreenplayLocator
+ {
+ static readonly ConcurrentDictionary screenplayCache = new ConcurrentDictionary();
+
+ ///
+ /// Gets a instance from the specified .
+ ///
+ ///
+ ///
+ /// This method makes use of the which decorates the assembly to get a
+ /// Screenplay object instance for that assembly.
+ /// If the specified assembly is not decorated with the Screenplay assembly attribute then this method will raise
+ /// an exception.
+ ///
+ ///
+ /// The test assembly for which to get a Screenplay object.
+ /// The Screenplay object for the specified assembly.
+ /// If is .
+ /// If the is not decorated with .
+ public static Screenplay GetScreenplay(Assembly assembly)
+ {
+ if (assembly is null) throw new ArgumentNullException(nameof(assembly));
+ return screenplayCache.GetOrAdd(assembly, GetScreenplayFromAssembly);
+ }
+
+ ///
+ /// Gets a instance from the specified test method.
+ ///
+ ///
+ ///
+ /// This method makes use of the which decorates the assembly in which the
+ /// specified method was declared, to get a Screenplay object instance applicable to the test method.
+ /// If the method's assembly is not decorated with the Screenplay assembly attribute then this method will raise
+ /// an exception.
+ ///
+ ///
+ /// The test method for which to get a Screenplay object.
+ /// The Screenplay object for the specified test method.
+ /// If is .
+ /// If the 's assembly is or
+ /// is not decorated with .
+ public static Screenplay GetScreenplay(IMethodInfo method)
+ {
+ if (method is null) throw new ArgumentNullException(nameof(method));
+
+ var assembly = method.MethodInfo?.DeclaringType?.Assembly;
+ if (assembly is null)
+ throw new ArgumentException($"The test method must have an associated {nameof(Assembly)}.", nameof(method));
+ return GetScreenplay(assembly);
+ }
+
+ ///
+ /// Gets a instance from the specified test.
+ ///
+ ///
+ ///
+ /// This method makes use of the which decorates the assembly in which the
+ /// specified test's method was declared, to get a Screenplay object instance applicable to the test method.
+ /// If the test's method's assembly is not decorated with the Screenplay assembly attribute then this method will raise
+ /// an exception.
+ ///
+ ///
+ /// The test for which to get a Screenplay object.
+ /// The Screenplay object for the specified test.
+ /// If is .
+ /// If the 's method's assembly is or
+ /// is not decorated with .
+ public static Screenplay GetScreenplay(ITest test)
+ {
+ if(test is null) throw new ArgumentNullException(nameof(test));
+ return GetScreenplay(test.Method);
+ }
+
+ static Screenplay GetScreenplayFromAssembly(Assembly assembly)
+ {
+ var assemblyAttrib = assembly.GetCustomAttribute()
+ ?? throw new ArgumentException($"The assembly {assembly.FullName} must be decorated with {nameof(ScreenplayAssemblyAttribute)}.", nameof(assembly));
+ return assemblyAttrib.GetScreenplay();
+ }
+ }
+}
diff --git a/CSF.Screenplay.Selenium.BrowserFlags_old/packages.config b/CSF.Screenplay.Selenium.BrowserFlags_old/packages.config
new file mode 100644
index 00000000..51dabf90
--- /dev/null
+++ b/CSF.Screenplay.Selenium.BrowserFlags_old/packages.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/CSF.Screenplay.Selenium.JavaScriptWorkarounds.csproj b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/CSF.Screenplay.Selenium.JavaScriptWorkarounds.csproj
new file mode 100644
index 00000000..fb372e78
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/CSF.Screenplay.Selenium.JavaScriptWorkarounds.csproj
@@ -0,0 +1,76 @@
+
+
+
+ Debug
+ AnyCPU
+ {CC20F88D-C4B9-4FDB-B5B1-7349547430FA}
+ Library
+ CSF.Screenplay.Selenium
+ CSF.Screenplay.Selenium.JavaScriptWorkarounds
+ v4.5
+ 1.0.0
+
+
+ true
+ full
+ false
+ bin\Debug
+ DEBUG;
+ prompt
+ 4
+ bin\Debug\CSF.Screenplay.Selenium.JavaScriptWorkarounds.xml
+ false
+ true
+
+
+ true
+ bin\Release
+ prompt
+ 4
+ bin\Release\CSF.Screenplay.Selenium.JavaScriptWorkarounds.xml
+ false
+
+
+
+
+ ..\packages\Selenium.WebDriver.3.4.0\lib\net40\WebDriver.dll
+
+
+
+ ..\packages\CSF.Reflection.1.0.3\lib\net45\CSF.Reflection.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/Properties/AssemblyInfo.cs b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..13b84a46
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/Properties/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("CSF.Screenplay.Selenium.JavaScriptWorkarounds")]
+[assembly: AssemblyDescription("Workarounds, using JavaScript, for browser-specific issues relating to Selenium actions")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("CSF Software Limited")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("Craig Fowler")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.0.0")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/ArgumentsArrayValidator.cs b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/ArgumentsArrayValidator.cs
new file mode 100644
index 00000000..06e95245
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/ArgumentsArrayValidator.cs
@@ -0,0 +1,57 @@
+//
+// ArgumentsArrayValidator.cs
+//
+// Author:
+// Craig Fowler
+//
+// Copyright (c) 2018 Craig Fowler
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using CSF.Screenplay.Selenium.StoredScripts;
+
+namespace CSF.Screenplay.Selenium.ScriptResources
+{
+ ///
+ /// A stored JavaScript which validates a collection of arguments passed as an array.
+ ///
+ public class ArgumentsArrayValidator : ScriptResource
+ {
+ const string EntryPointNameConst = "validateArgumentsArray";
+
+ ///
+ /// Gets the name of the entry point for this script (which differs from the default).
+ ///
+ /// The name of the entry point.
+ public static string EntryPointName => EntryPointNameConst;
+
+ ///
+ /// Gets the name of this script.
+ ///
+ /// The name.
+ public override string Name => "a JavaScript which validates function arguments arrays";
+
+ ///
+ /// Gets the name of the entry point to the script - this is the function exposed by
+ /// .
+ ///
+ /// The name of the entry point function.
+ public override string GetEntryPointName() => EntryPointName;
+ }
+}
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/ArgumentsArrayValidator.js b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/ArgumentsArrayValidator.js
new file mode 100644
index 00000000..6b653445
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/ArgumentsArrayValidator.js
@@ -0,0 +1,87 @@
+var argumentsArrayValidator = function()
+{
+ 'use strict';
+
+ function getLengthParams(min, max)
+ {
+ if(typeof min === 'number' && max === undefined)
+ {
+ return {
+ count: min,
+ validateCount: true,
+ };
+ }
+
+ var minCount, validateMinCount = false, maxCount, validateMaxCount = false;
+
+ if(typeof min === 'number')
+ {
+ minCount = min;
+ validateMinCount = true;
+ }
+
+ if(typeof max === 'number')
+ {
+ maxCount = max;
+ validateMaxCount = true;
+ }
+
+ return {
+ min: minCount,
+ validateMin: validateMinCount,
+ max: maxCount,
+ validateMax: validateMaxCount,
+ validateCount: false,
+ };
+ }
+
+ function validateType(argsArray)
+ {
+ if(!argsArray) throw new Error('Arguments must be a non-null array.');
+ if(!(argsArray instanceof Array)) throw new Error('Arguments must be an array.');
+ }
+
+ function validateLength(argsArray, lengthParams)
+ {
+ if(lengthParams.validateCount && argsArray.length !== lengthParams.count)
+ throw new Error('Arguments array must contain precisely ' + lengthParams.count + ' item(s).');
+
+ if(lengthParams.validateMin && argsArray.length < lengthParams.min)
+ throw new Error('Arguments array must contain at least ' + lengthParams.min + ' item(s).');
+
+ if(lengthParams.validateMax && argsArray.length > lengthParams.max)
+ throw new Error('Arguments array must contain no more than ' + lengthParams.max + ' item(s).');
+ }
+
+ function validate(argsArray, min, max)
+ {
+ validateType(argsArray);
+ var lengthParams = getLengthParams(min, max);
+ validateLength(argsArray, lengthParams);
+
+ return true;
+ }
+
+ function selfValidate(argsArray, min, max)
+ {
+ try
+ {
+ return validate(argsArray, min, max);
+ }
+ catch(e) { throw new Error('The call to \'validateArgumentsArray\' raised an error: ' + e.message); }
+ }
+
+ return {
+ validate: validate,
+ selfValidate: selfValidate,
+ };
+}();
+
+function validateArgumentsArray(argsArray)
+{
+ 'use strict';
+
+ validator.selfValidate(argsArray, 1, 3);
+ var arrayToValidate = argsArray[0], min = argsArray[1], max = argsArray[2];
+ return validator.validate(arrayToValidate, min, max);
+}
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetALocalisedDate.cs b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetALocalisedDate.cs
new file mode 100644
index 00000000..8cb0206f
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetALocalisedDate.cs
@@ -0,0 +1,64 @@
+//
+// GetALocalisedDate.cs
+//
+// Author:
+// Craig Fowler
+//
+// Copyright (c) 2018 Craig Fowler
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using CSF.Screenplay.Selenium.StoredScripts;
+
+namespace CSF.Screenplay.Selenium.ScriptResources
+{
+ ///
+ /// Script resource for getting a localised date string
+ ///
+ public class GetALocalisedDate : ScriptResource
+ {
+ readonly ScriptResource argsValidator;
+
+ ///
+ /// Gets the name of this script.
+ ///
+ /// The name.
+ public override string Name => "a JavaScript which converts a date to a locale-formatted string";
+
+ ///
+ /// Gets a collection of scripts which the current script instance depends upon.
+ ///
+ /// The dependencies.
+ protected override ScriptResource[] GetDependencies() => new [] { argsValidator };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GetALocalisedDate() : this(null) {}
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Arguments validator.
+ public GetALocalisedDate(ScriptResource argsValidator)
+ {
+ this.argsValidator = argsValidator ?? new ArgumentsArrayValidator();
+ }
+ }
+}
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetALocalisedDate.js b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetALocalisedDate.js
new file mode 100644
index 00000000..9456b68c
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetALocalisedDate.js
@@ -0,0 +1,17 @@
+function executeScript(argsArray) {
+ 'use strict';
+
+ argumentsArrayValidator.validate(argsArray, 3);
+
+ var
+ year = argsArray[0],
+ month = argsArray[1],
+ day = argsArray[2],
+ theDate;
+
+ if(typeof year !== 'number' || typeof month !== 'number' || typeof day !== 'number')
+ throw new Error('The supplied year, month and day must all be numbers.');
+
+ theDate = new Date(year, month, day);
+ return theDate.toLocaleDateString();
+}
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetDocumentReadyState.cs b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetDocumentReadyState.cs
new file mode 100644
index 00000000..bff50835
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetDocumentReadyState.cs
@@ -0,0 +1,42 @@
+//
+// GetDocumentReadyState.cs
+//
+// Author:
+// Craig Fowler
+//
+// Copyright (c) 2018 Craig Fowler
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using CSF.Screenplay.Selenium.StoredScripts;
+
+namespace CSF.Screenplay.Selenium.ScriptResources
+{
+ ///
+ /// Script resource for getting the document ready state.
+ ///
+ public class GetDocumentReadyState : ScriptResource
+ {
+ ///
+ /// Gets the name of this script.
+ ///
+ /// The name.
+ public override string Name => "a JavaScript which gets the current web page's ready-state";
+ }
+}
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetDocumentReadyState.js b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetDocumentReadyState.js
new file mode 100644
index 00000000..b8953856
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/GetDocumentReadyState.js
@@ -0,0 +1,4 @@
+function executeScript(argsArray) {
+ 'use strict';
+ return document.readyState;
+}
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementAttribute.cs b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementAttribute.cs
new file mode 100644
index 00000000..93126cf8
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementAttribute.cs
@@ -0,0 +1,48 @@
+//
+// SetAnElementAttribute.cs
+//
+// Author:
+// Craig Fowler
+//
+// Copyright (c) 2018 Craig Fowler
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using CSF.Screenplay.Selenium.StoredScripts;
+
+namespace CSF.Screenplay.Selenium.ScriptResources
+{
+ ///
+ /// Script resource for setting an attribute upon an element.
+ ///
+ public class SetAnElementAttribute : ScriptResource
+ {
+ ///
+ /// Gets the name of this script.
+ ///
+ /// The name.
+ public override string Name => "a JavaScript which sets an attribute upon an element";
+
+ ///
+ /// Gets a collection of scripts which the current script instance depends upon.
+ ///
+ /// The dependencies.
+ protected override ScriptResource[] GetDependencies() => new [] { new ArgumentsArrayValidator() };
+ }
+}
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementAttribute.js b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementAttribute.js
new file mode 100644
index 00000000..79c5f7e9
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementAttribute.js
@@ -0,0 +1,24 @@
+function executeScript(argsArray)
+{
+ 'use strict';
+
+ argumentsArrayValidator.validate(argsArray, 3);
+
+ var htmlElement = argsArray[0], attributeName = argsArray[1], newValue = argsArray[2];
+
+ function validateElement(element)
+ {
+ if(element === null || element === undefined)
+ throw new Error('You must provide an HTML element object.');
+ if(!(element instanceof HTMLElement))
+ throw new Error('The element must be an HTML element.');
+ }
+
+ validateElement(htmlElement);
+ if(newValue === null)
+ htmlElement.removeAttribute(attributeName);
+ else
+ htmlElement.setAttribute(attributeName, newValue);
+
+ return true;
+}
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementValue.cs b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementValue.cs
new file mode 100644
index 00000000..3b9279da
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementValue.cs
@@ -0,0 +1,64 @@
+//
+// SetAnElementValue.cs
+//
+// Author:
+// Craig Fowler
+//
+// Copyright (c) 2018 Craig Fowler
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using CSF.Screenplay.Selenium.StoredScripts;
+
+namespace CSF.Screenplay.Selenium.ScriptResources
+{
+ ///
+ /// A stored JavaScript which sets the value of an HTML element and then triggers the 'change' event for that element.
+ ///
+ public class SetAnElementValue : ScriptResource
+ {
+ readonly ScriptResource argsValidator;
+
+ ///
+ /// Gets the name of this script.
+ ///
+ /// The name.
+ public override string Name => "a JavaScript to set the value of an HTML element";
+
+ ///
+ /// Gets a collection of scripts which the current script instance depends upon.
+ ///
+ /// The dependencies.
+ protected override ScriptResource[] GetDependencies() => new [] { argsValidator };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SetAnElementValue() : this(null) {}
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Arguments validator.
+ public SetAnElementValue(ScriptResource argsValidator)
+ {
+ this.argsValidator = argsValidator ?? new ArgumentsArrayValidator();
+ }
+ }
+}
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementValue.js b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementValue.js
new file mode 100644
index 00000000..d93f5b4f
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/SetAnElementValue.js
@@ -0,0 +1,36 @@
+function executeScript(argsArray)
+{
+ 'use strict';
+
+ argumentsArrayValidator.validate(argsArray, 2);
+
+ var htmlElement = argsArray[0], newValue = argsArray[1];
+
+ function validateElement(element)
+ {
+ if(element === null || element === undefined)
+ throw new Error('You must provide an HTML element object.');
+ if(!(element instanceof HTMLElement))
+ throw new Error('The element must be an HTML element.');
+ if(element.value === undefined)
+ throw new Error('The element must have a \'value\' property.');
+ }
+
+ function setValue(element, val)
+ {
+ element.value = val;
+ }
+
+ function triggerChangeEvent(element)
+ {
+ var ev = document.createEvent('Event');
+ ev.initEvent('change', true, true);
+ element.dispatchEvent(ev);
+ }
+
+ validateElement(htmlElement);
+ setValue(htmlElement, newValue);
+ triggerChangeEvent(htmlElement);
+
+ return true;
+}
diff --git a/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/UpdateSelectElementSelection.cs b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/UpdateSelectElementSelection.cs
new file mode 100644
index 00000000..ef9a59a6
--- /dev/null
+++ b/CSF.Screenplay.Selenium.JavaScriptWorkarounds_old/ScriptResources/UpdateSelectElementSelection.cs
@@ -0,0 +1,83 @@
+//
+// UpdateSelectElementSelection.cs
+//
+// Author:
+// Craig Fowler
+//
+// Copyright (c) 2018 Craig Fowler
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using CSF.Screenplay.Selenium.StoredScripts;
+
+namespace CSF.Screenplay.Selenium.ScriptResources
+{
+ ///
+ /// A stored JavaScript which sets the selected option(s) for an HTML <select> element.
+ ///
+ public class UpdateSelectElementSelection : ScriptResource
+ {
+ ///
+ /// Gets the action name for deselecting every option.
+ ///
+ public static readonly string DeselectAllActionName = "deselectAll";
+
+ ///
+ /// Gets the action name for selecting an option by its zero-based index.
+ ///
+ public static readonly string SelectByIndexActionName = "selectByIndex";
+
+ ///
+ /// Gets the action name for selecting an option by its value.
+ ///
+ public static readonly string SelectByValueActionName = "selectByValue";
+
+ ///
+ /// Gets the action name for selecting an option by its displyed text.
+ ///
+ public static readonly string SelectByTextActionName = "selectByText";
+
+ ///
+ /// Gets the action name for deselecting an option by its zero-based index.
+ ///
+ public static readonly string DeselectByIndexActionName = "deselectByIndex";
+
+ ///
+ /// Gets the action name for deselecting an option by its value.
+ ///
+ public static readonly string DeselectByValueActionName = "deselectByValue";
+
+ ///
+ /// Gets the action name for deselecting an option by its displayed text.
+ ///
+ public static readonly string DeselectByTextActionName = "deselectByText";
+
+ ///
+ /// Gets the name of this script.
+ ///
+ /// The name.
+ public override string Name => "a JavaScript to set the select options of an HTML