diff --git a/CSF.Screenplay.Abstractions/Performables/IProvidesTimeSpan.cs b/CSF.Screenplay.Abstractions/Performables/IProvidesTimeSpan.cs deleted file mode 100644 index 2830aad0..00000000 --- a/CSF.Screenplay.Abstractions/Performables/IProvidesTimeSpan.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace CSF.Screenplay.Performables -{ - /// - /// A type which may provide a . - /// - /// - /// - /// Many performables make use of time; this interface provides a common abstraction for - /// objects that provide time spans. - /// - /// - /// - /// - public interface IProvidesTimeSpan - { - /// - /// Gets the which is exposed by the current instance. - /// - /// The time span - TimeSpan GetTimeSpan(); - } -} diff --git a/CSF.Screenplay.Abstractions/Performables/TimeSpanBuilder.cs b/CSF.Screenplay.Abstractions/Performables/TimeSpanBuilder.cs deleted file mode 100644 index 7002687b..00000000 --- a/CSF.Screenplay.Abstractions/Performables/TimeSpanBuilder.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace CSF.Screenplay.Performables -{ - /// - /// Static helper class for creating instances of . - /// - /// - /// - /// See the documentation for for more information - /// about how this class is to be used. - /// - /// - public static class TimeSpanBuilder - { - /// - /// Creates and returns a which can hold time span information - /// and then continue the building process associated with the other builder. - /// - /// - /// - /// See the documentation for for more information - /// about how this method is to be used. - /// - /// - /// An instance of another performable builder - /// The absolute time span value, without any units - /// The type of the other performable builder - public static TimeSpanBuilder Create(TOtherBuilder otherBuilder, int value) where TOtherBuilder : class - => new TimeSpanBuilder(otherBuilder, value); - } -} diff --git a/CSF.Screenplay.Abstractions/Performables/TimeSpanBuilder.generic.cs b/CSF.Screenplay.Abstractions/Performables/TimeSpanBuilder.generic.cs deleted file mode 100644 index 7a165610..00000000 --- a/CSF.Screenplay.Abstractions/Performables/TimeSpanBuilder.generic.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; - -namespace CSF.Screenplay.Performables -{ - /// - /// A supplementary builder type which enables the collection of instances. - /// - /// - /// - /// When consuming it is recommended to use - /// to create them. - /// A commonly-used 'parameter' which may be specified in builders is 'an amount of time', IE a . - /// - /// - /// This builder is intended to supplement another builder, for the purpose of specifying an amount of time. - /// The 'other' builder is passed as a constructor parameter to this builder, along with an absolute amount. - /// The consumer should then execute one of the methods of this type, which selects the unit of time and thus - /// determines the value. - /// The method which determines the units then returns that other builder instance, allowing the building process - /// to continue with that other builder. - /// - /// - /// Whilst it is possible to create instances of this type via its public constructor, it is often easier to create - /// instances using the static class. - /// - /// - /// - /// - /// The example below shows how the time span builder is intended to be used. It is consumed from within another builder, - /// which needs to include a developer-configurable time span. - /// See for more information - /// about the makeup of the EatLunchPerformableBuilder. - /// - /// - /// public class EatLunchPerformableBuilder : IGetsPerformable - /// { - /// IProvidesTimeSpan? timeSpanBuilder; - /// - /// protected string? FoodName { get; init; } - /// - /// IPerformable IGetsPerformable.GetPerformable() - /// => new EatLunch(FoodName, timeSpanBuilder?.GetTimeSpan() ?? TimeSpan.Zero); - /// - /// public TimeSpanBuilder<EatLunchPerformableBuilder> For(int howMany) - /// { - /// var builder = TimeSpanBuilder.Create(this, howMany); - /// timeSpanBuilder = builder; - /// return builder; - /// } - /// - /// public static EatLunchPerformableBuilder Eat(string foodName) => new EatLunchPerformableBuilder() { FoodName = foodName }; - /// } - /// - /// - /// The sample builder above would be used to build an instance of a (fictitious) EachLunch performable, which derives from - /// . The fictitious performable requires two parameters; the name of the food being eaten for lunch - /// and how long the lunch break lasts. The time span builder is used for that second parameter. - /// A consumer which uses this builder in an , or another , - /// might consume it as follows. - /// - /// - /// using static EatLunchPerformableBuilder; - /// - /// // ... - /// - /// actor.PerformAsync(Eat("Sandwiches").For(30).Minutes(), cancellationToken); - /// - /// - /// A note for developers with access to the source code for this library. - /// There is a small integration test which sets up and exercises the example above; it is named TimeSpanBuilderTests. - /// - /// - /// The builder type for which this builder will supplement - /// - public class TimeSpanBuilder : IProvidesTimeSpan where TOtherBuilder : class - { - readonly TOtherBuilder otherBuilder; - readonly int value; - TimeSpan timespan; - - TimeSpan IProvidesTimeSpan.GetTimeSpan() => timespan; - - /// - /// Configures the contained time span to be measured in milliseconds, then returns the contained builder. - /// - public TOtherBuilder Milliseconds() - { - timespan = TimeSpan.FromMilliseconds(value); - return otherBuilder; - } - - /// - /// Configures the contained time span to be measured in seconds, then returns the contained builder. - /// - public TOtherBuilder Seconds() - { - timespan = TimeSpan.FromSeconds(value); - return otherBuilder; - } - - /// - /// Configures the contained time span to be measured in minutes, then returns the contained builder. - /// - public TOtherBuilder Minutes() - { - timespan = TimeSpan.FromMinutes(value); - return otherBuilder; - } - - /// - /// Configures the contained time span to be measured in hours, then returns the contained builder. - /// - public TOtherBuilder Hours() - { - timespan = TimeSpan.FromHours(value); - return otherBuilder; - } - - /// - /// Configures the contained time span to be measured in days, then returns the contained builder. - /// - public TOtherBuilder Days() - { - timespan = TimeSpan.FromDays(value); - return otherBuilder; - } - - /// - /// Initializes a new instance of . - /// - /// The absolute value of time, but without units - /// The other builder which shall be supplemented by this - /// If is . - /// If is less than zero. - public TimeSpanBuilder(TOtherBuilder otherBuilder, int value) - { - if (otherBuilder == null) - throw new ArgumentNullException(nameof(otherBuilder)); - if (value < 0) - throw new ArgumentOutOfRangeException(nameof(value), value, "Value must not be negative"); - - this.value = value; - this.otherBuilder = otherBuilder; - } - } -} diff --git a/CSF.Screenplay.Docs/docs/extendingScreenplay/ScreenplayExtensions.md b/CSF.Screenplay.Docs/docs/extendingScreenplay/ScreenplayExtensions.md index 5079a1a5..47ede299 100644 --- a/CSF.Screenplay.Docs/docs/extendingScreenplay/ScreenplayExtensions.md +++ b/CSF.Screenplay.Docs/docs/extendingScreenplay/ScreenplayExtensions.md @@ -3,18 +3,18 @@ Screenplay ships [with a small number of Abilities and performables] but it is designed to be extended with new ones. New [Abilities], [Actions] and [Questions] extend [Screenplay] by allowing [Actors] to interact with new APIs, services and libraries. -Broadly-speaking to extend Screenplay in this way you must: +Broadly-speaking, to extend Screenplay in this way you must: * Write one or more new ability types which provide access to the API of the service or library with which you'd like to interact * Write one or more Action and/or Question [Performables] which make use of that ability -[with a small number of Abilities and performables]: ../performables/index.md +[with a small number of Abilities and performables]: ../extensions/index.md#built-in-abilities-and-performables [Screenplay]: xref:CSF.Screenplay.Screenplay ## Writing abilities Recall that [Abilities] represent capabilities & dependencies granted to or associated with [Actors]. -It is normal for developers to want to write new Ability classes in order to provide capabilities/dependencies which are not yet catered-for. +It is normal for developers to want to write new Ability classes in order to provide capabilities/dependencies which are not yet catered-for. Ability classes do not _need to derive_ from any particular base type, although it is strongly recommended that they implement [`ICanReport`]. Ability classes [may constructor-inject dependencies] and should declare whatever API is appropriate. diff --git a/CSF.Screenplay.Docs/docs/extensions/index.md b/CSF.Screenplay.Docs/docs/extensions/index.md index 8b9f69b1..d3d3c19f 100644 --- a/CSF.Screenplay.Docs/docs/extensions/index.md +++ b/CSF.Screenplay.Docs/docs/extensions/index.md @@ -1,8 +1,30 @@ # Screenplay extensions -This page lists the officially-supported **[Screenplay Extensions]**. +Most of the useful functionality of Screenplay comes from **[Screenplay Extensions]**. +Extensions typically provide [Abilities], [Performables] and [Builders] which offer functionality relevant to that extension's technology. + +[Screenplay Extensions]: ../../glossary/Extension.md +[Abilities]: ../../glossary/Ability.md +[Performables]: ../../glossary/Performable.md +[Builders]: ../builderPattern/index.md + +## Officially-supported extensions + +These extensions are authored and maintained alongside the main Screenplay library. +Developers are encouraged and welcomed to create their own extensions, too. * **[Selenium](selenium/index.md)**: _remote control Web Browsers using Selenium Web Driver_ * **[Web APIs](webApis/index.md)**: _communicate with Web APIs with an HTTP Client_ -[Screenplay Extensions]: ../../glossary/Extension.md +## Built-in abilities and performables + +Screenplay offers a very small number of abilities and performables within the base library; these do not require the installation of an extension. + +### Using a Stopwatch + +When an actor needs to keep precise track of time, you may give them the [`UseAStopwatch`] ability. +Actors with this ability may use Actions and Questions which relate to use of the stopwatch. +These are all accessible from the builder class [`StopwatchBuilder`]. + +[`UseAStopwatch`]: xref:CSF.Screenplay.Abilities.UseAStopwatch +[`StopwatchBuilder`]: xref:CSF.Screenplay.Performables.StopwatchBuilder diff --git a/CSF.Screenplay.Docs/docs/performables/index.md b/CSF.Screenplay.Docs/docs/performables/index.md deleted file mode 100644 index 1a8734d5..00000000 --- a/CSF.Screenplay.Docs/docs/performables/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# Performables - -Screenplay comes with a few pre-created [Abilities], [Performables] and [Builders], for common tasks. -Some of these require the installation of additional **NuGet packages**. - -[Abilities]: ../../glossary/Ability.md -[Performables]: ../../glossary/Performable.md -[Builders]: ../builderPattern/index.md - -## Using a Stopwatch - -When an actor needs to keep precise track of time, you may give them the [`UseAStopwatch`] ability. -Actors with this ability may use Actions and Questions which relate to use of the stopwatch. -These are all accessible from the builder class [`StopwatchBuilder`]. - -[`UseAStopwatch`]: xref:CSF.Screenplay.Abilities.UseAStopwatch -[`StopwatchBuilder`]: xref:CSF.Screenplay.Performables.StopwatchBuilder - -## TimeSpan builder - -The [`TimeSpanBuilder`] is not a complete performable builder; it is intended to supplement other builders such as those of your own design. -It handles a commonly-used aspect of building performables in a reusable manner. - -[`TimeSpanBuilder`]: xref:CSF.Screenplay.Performables.TimeSpanBuilder`1 \ No newline at end of file diff --git a/CSF.Screenplay.Docs/docs/performables/toc.yml b/CSF.Screenplay.Docs/docs/performables/toc.yml deleted file mode 100644 index b34ca748..00000000 --- a/CSF.Screenplay.Docs/docs/performables/toc.yml +++ /dev/null @@ -1,6 +0,0 @@ -- name: Introduction - href: index.md -- name: Stopwatch - uid: CSF.Screenplay.Performables.StopwatchBuilder -- name: TimeSpan builder - uid: CSF.Screenplay.Performables.TimeSpanBuilder`1 \ No newline at end of file diff --git a/CSF.Screenplay.Docs/docs/toc.yml b/CSF.Screenplay.Docs/docs/toc.yml index 0d090c7b..200ba62a 100644 --- a/CSF.Screenplay.Docs/docs/toc.yml +++ b/CSF.Screenplay.Docs/docs/toc.yml @@ -66,8 +66,6 @@ items: - name: Assets href: Assets.md - - name: Performables - href: performables/toc.yml - name: Best practices items: - name: When to use for tests diff --git a/CSF.Screenplay.Docs/glossary/Extension.md b/CSF.Screenplay.Docs/glossary/Extension.md index dd5b892c..644c6062 100644 --- a/CSF.Screenplay.Docs/glossary/Extension.md +++ b/CSF.Screenplay.Docs/glossary/Extension.md @@ -4,14 +4,15 @@ The Screenplay library doesn't do much on its own. The packages CSF.Screenplay.Abstractions and CSF.Screenplay offer very little in the way of [Abilities], [Actions] or [Questions]. Since these are the building blocks for writing [Tasks] and [Performances], developers won't get very far without installing one or more extensions. -There are [a few extensions available] which are maintained by the authors of CSF.Screenplay. +A few extensions are authored and maintained alongside the CSF.Screenplay library. +These are listed on **[the extensions documentation page]**. [Abilities]: Ability.md [Actions]: Action.md [Questions]: Question.md [Tasks]: Task.md [Performances]: xref:CSF.Screenplay.IPerformance -[a few extensions available]: ../docs/extensions/index.md +[the extensions documentation page]: ../docs/extensions/index.md ## Can't see what you need? diff --git a/Tests/CSF.Screenplay.NUnit.Tests/TestWithDescription.cs b/Tests/CSF.Screenplay.NUnit.Tests/TestWithDescription.cs index 1e31d875..1ab677e2 100644 --- a/Tests/CSF.Screenplay.NUnit.Tests/TestWithDescription.cs +++ b/Tests/CSF.Screenplay.NUnit.Tests/TestWithDescription.cs @@ -29,10 +29,8 @@ public void SecondTest(IPerformance performance) static void AssertThatPerformanceHasCorrectState(IPerformance performance, IdentifierAndName[] expectedNamingHierarchy) { - Assert.Multiple(() => - { - Assert.That(performance, Is.Not.Null, "Performance must not be null"); - Assert.That(performance.NamingHierarchy, Is.EqualTo(expectedNamingHierarchy), "Performance naming hierarchy is correct"); - }); + using var scope = Assert.EnterMultipleScope(); + Assert.That(performance, Is.Not.Null, "Performance must not be null"); + Assert.That(performance.NamingHierarchy, Is.EqualTo(expectedNamingHierarchy), "Performance naming hierarchy is correct"); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.NUnit.Tests/TestWithoutDescription.cs b/Tests/CSF.Screenplay.NUnit.Tests/TestWithoutDescription.cs index 475afb64..1c4c1b8b 100644 --- a/Tests/CSF.Screenplay.NUnit.Tests/TestWithoutDescription.cs +++ b/Tests/CSF.Screenplay.NUnit.Tests/TestWithoutDescription.cs @@ -29,10 +29,8 @@ public void SecondTest(IPerformance performance) static void AssertThatPerformanceHasCorrectState(IPerformance performance, IdentifierAndName[] expectedNamingHierarchy) { - Assert.Multiple(() => - { - Assert.That(performance, Is.Not.Null, "Performance must not be null"); - Assert.That(performance.NamingHierarchy, Is.EqualTo(expectedNamingHierarchy), "Performance naming hierarchy is correct"); - }); + using var scope = Assert.EnterMultipleScope(); + Assert.That(performance, Is.Not.Null, "Performance must not be null"); + Assert.That(performance.NamingHierarchy, Is.EqualTo(expectedNamingHierarchy), "Performance naming hierarchy is correct"); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Reqnroll.Tests/ServiceCollectionAdapterTests.cs b/Tests/CSF.Screenplay.Reqnroll.Tests/ServiceCollectionAdapterTests.cs index a28e5202..b21287b4 100644 --- a/Tests/CSF.Screenplay.Reqnroll.Tests/ServiceCollectionAdapterTests.cs +++ b/Tests/CSF.Screenplay.Reqnroll.Tests/ServiceCollectionAdapterTests.cs @@ -14,20 +14,18 @@ public void UnsupportedFunctionalityShouldThrowNotSupportedException() var container = new ObjectContainer(); var sut = new ServiceCollectionAdapter(container); - Assert.Multiple(() => - { - Assert.That(() => sut[0], Throws.InstanceOf(), "Indexer get"); - Assert.That(() => sut[0] = null, Throws.InstanceOf(), "Indexer set"); - Assert.That(() => sut.Clear(), Throws.InstanceOf(), nameof(IServiceCollection.Clear)); - Assert.That(() => sut.Contains(null), Throws.InstanceOf(), nameof(IServiceCollection.Contains)); - Assert.That(() => sut.CopyTo(null, default), Throws.InstanceOf(), nameof(IServiceCollection.CopyTo)); - Assert.That(() => sut.GetEnumerator(), Throws.InstanceOf(), nameof(IServiceCollection.GetEnumerator)); - Assert.That(() => ((IEnumerable) sut).GetEnumerator(), Throws.InstanceOf(), nameof(IServiceCollection.GetEnumerator) + ": " + nameof(IEnumerable)); - Assert.That(() => sut.IndexOf(default), Throws.InstanceOf(), nameof(IServiceCollection.IndexOf)); - Assert.That(() => sut.Insert(default, null), Throws.InstanceOf(), nameof(IServiceCollection.Insert)); - Assert.That(() => sut.Remove(null), Throws.InstanceOf(), nameof(IServiceCollection.Remove)); - Assert.That(() => sut.RemoveAt(default), Throws.InstanceOf(), nameof(IServiceCollection.RemoveAt)); - }); + using var scope = Assert.EnterMultipleScope(); + Assert.That(() => sut[0], Throws.InstanceOf(), "Indexer get"); + Assert.That(() => sut[0] = null, Throws.InstanceOf(), "Indexer set"); + Assert.That(() => sut.Clear(), Throws.InstanceOf(), nameof(IServiceCollection.Clear)); + Assert.That(() => sut.Contains(null), Throws.InstanceOf(), nameof(IServiceCollection.Contains)); + Assert.That(() => sut.CopyTo(null, default), Throws.InstanceOf(), nameof(IServiceCollection.CopyTo)); + Assert.That(() => sut.GetEnumerator(), Throws.InstanceOf(), nameof(IServiceCollection.GetEnumerator)); + Assert.That(() => ((IEnumerable) sut).GetEnumerator(), Throws.InstanceOf(), nameof(IServiceCollection.GetEnumerator) + ": " + nameof(IEnumerable)); + Assert.That(() => sut.IndexOf(default), Throws.InstanceOf(), nameof(IServiceCollection.IndexOf)); + Assert.That(() => sut.Insert(default, null), Throws.InstanceOf(), nameof(IServiceCollection.Insert)); + Assert.That(() => sut.Remove(null), Throws.InstanceOf(), nameof(IServiceCollection.Remove)); + Assert.That(() => sut.RemoveAt(default), Throws.InstanceOf(), nameof(IServiceCollection.RemoveAt)); } [Test] diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Builders/QueryPredicatePrototypeBuilderTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Builders/QueryPredicatePrototypeBuilderTests.cs index 5ff3d6b1..23da53d7 100644 --- a/Tests/CSF.Screenplay.Selenium.Tests/Builders/QueryPredicatePrototypeBuilderTests.cs +++ b/Tests/CSF.Screenplay.Selenium.Tests/Builders/QueryPredicatePrototypeBuilderTests.cs @@ -24,11 +24,9 @@ public void AttributeValueShouldCreateASpecificationThatUsesGetAttribute(QueryPr var sut = builder.AttributeValue("data-test", v => v.StartsWith("foo")).GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -45,11 +43,9 @@ public void AttributeValueWithPlainValueShouldCreateASpecificationThatUsesGetAtt var sut = builder.AttributeValue("data-test", "foobar").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -66,11 +62,9 @@ public void AttributeShouldCreateASpecificationThatUsesGetAttribute(QueryPredica var sut = builder.Attribute("data-test").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -87,11 +81,9 @@ public void ClassShouldCreateASpecificationThatUsesGetAttribute(QueryPredicatePr var sut = builder.Class("foobar").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -108,11 +100,9 @@ public void NotClassShouldCreateASpecificationThatUsesGetAttribute(QueryPredicat var sut = builder.NotClass("qux").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -129,11 +119,9 @@ public void AllClassesShouldCreateASpecificationThatUsesGetAttribute(QueryPredic var sut = builder.AllClasses("foobar", "baz").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -154,13 +142,11 @@ public void ClickableShouldCreateASpecificationThatTestsVisibilityAndEnabledStat var sut = builder.Clickable().GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement1), Is.False, "Non-matching element 1 should not match"); Assert.That(sut.Matches(nonMatchingElement2), Is.False, "Non-matching element 2 should not match"); Assert.That(sut.Matches(nonMatchingElement3), Is.False, "Non-matching element 3 should not match"); - }); } [Test, AutoMoqData] @@ -177,11 +163,9 @@ public void CssPropertyShouldCreateASpecificationThatUsesGetCssValue(QueryPredic var sut = builder.CssProperty("color", v => v == Colors.RED).GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -198,11 +182,9 @@ public void CssPropertyWithValueShouldCreateASpecificationThatUsesGetCssValue(Qu var sut = builder.CssProperty("color", Colors.RED.ToRgbaString()).GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -215,11 +197,9 @@ public void LocationShouldCreateASpecificationThatUsesLocationQuery(QueryPredica var sut = builder.Location(new Point(100, 200)).GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -232,11 +212,9 @@ public void SizeShouldCreateASpecificationThatUsesSizeQuery(QueryPredicateProtot var sut = builder.Size(new Size(100, 200)).GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -249,11 +227,9 @@ public void TextShouldCreateASpecificationThatUsesTextQuery(QueryPredicateProtot var sut = builder.Text("Hello World").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -266,11 +242,9 @@ public void ValueShouldCreateASpecificationThatUsesValueQuery(QueryPredicateProt var sut = builder.Value("Hello World").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -283,11 +257,9 @@ public void VisibleShouldCreateASpecificationThatUsesVisibilityQuery(QueryPredic var sut = builder.Visible().GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -312,11 +284,9 @@ public void SelectedOptionsWithTextShouldCreateASpecificationThatUsesOptionsQuer var sut = builder.SelectedOptionsWithText("Option 1", "Option 3").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -341,11 +311,9 @@ public void SelectedOptionsWithValueShouldCreateASpecificationThatUsesOptionsQue var sut = builder.SelectedOptionsWithValue("1", "3").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -370,11 +338,9 @@ public void UnselectedOptionsWithTextShouldCreateASpecificationThatUsesOptionsQu var sut = builder.UnselectedOptionsWithText("Option 2").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -399,11 +365,9 @@ public void UnselectedOptionsWithValueShouldCreateASpecificationThatUsesOptionsQ var sut = builder.UnselectedOptionsWithValue("2").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -427,11 +391,9 @@ public void OptionsWithTextShouldCreateASpecificationThatUsesOptionsQuery(QueryP var sut = builder.OptionsWithText("Option 1", "Option 2", "Option 3").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } [Test, AutoMoqData] @@ -455,10 +417,8 @@ public void OptionsWithValueShouldCreateASpecificationThatUsesOptionsQuery(Query var sut = builder.OptionsWithValue("1", "2", "3").GetElementSpecification(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(sut.Matches(matchingElement), Is.True, "Matching element should match"); Assert.That(sut.Matches(nonMatchingElement), Is.False, "Non-matching element should not match"); - }); } } diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Builders/UnnamedWaitBuilderTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Builders/UnnamedWaitBuilderTests.cs index 2c48b1b0..af1dfde6 100644 --- a/Tests/CSF.Screenplay.Selenium.Tests/Builders/UnnamedWaitBuilderTests.cs +++ b/Tests/CSF.Screenplay.Selenium.Tests/Builders/UnnamedWaitBuilderTests.cs @@ -19,11 +19,10 @@ public void GetPerformableShouldGetAnObjectWithTheCorrectConfiguration() var performable = (Wait) ((IGetsPerformable) sut).GetPerformable(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); + Assert.That(performable, Has.PrivateFieldEqualTo("timeout", TimeSpan.FromSeconds(10))); Assert.That(performable, Has.PrivateFieldEqualTo("pollingInterval", TimeSpan.FromMilliseconds(500))); Assert.That(performable, Has.PrivateFieldEqualTo("ignoredExceptionTypes", new [] { typeof(InvalidOperationException), typeof(ArgumentException) })); - }); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Selenium.Tests/ColorTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/ColorTests.cs index 8684fe32..7246fd19 100644 --- a/Tests/CSF.Screenplay.Selenium.Tests/ColorTests.cs +++ b/Tests/CSF.Screenplay.Selenium.Tests/ColorTests.cs @@ -28,11 +28,9 @@ public class ColorTests public void TryParseShouldReturnTrueAndExposeACorrespondingColorForAValidString(string colorString, byte red, byte green, byte blue, double alpha) { var result = Color.TryParse(colorString, out var color); - Assert.Multiple(() => - { - Assert.That(result, Is.True, "Parsing success"); - Assert.That(color, Is.EqualTo(new Color(red, green, blue, alpha)), "Expected color matches"); - }); + using var scope = Assert.EnterMultipleScope(); + Assert.That(result, Is.True, "Parsing success"); + Assert.That(color, Is.EqualTo(new Color(red, green, blue, alpha)), "Expected color matches"); } [TestCase("rgb(1, 2, 3)", 1, 2, 3, 1)] diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Tasks/EnterTheDateTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Tasks/EnterTheDateTests.cs index 1be82a05..e31c707d 100644 --- a/Tests/CSF.Screenplay.Selenium.Tests/Tasks/EnterTheDateTests.cs +++ b/Tests/CSF.Screenplay.Selenium.Tests/Tasks/EnterTheDateTests.cs @@ -58,10 +58,8 @@ public async Task EnteringADateInAnUnusualCultureShouldYieldIncorrectResults(ISt await When(webster).AttemptsTo(EnterTheDate(new DateTime(2025, 11, 12)).Into(inputArea).ForTheCultureNamed("ja-JP")); var result = await Then(webster).Should(ReadFromTheElement(displayText).TheText()); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(result, Is.Not.EqualTo(string.Empty), "The date shouldn't be empty"); Assert.That(result, Is.Not.EqualTo("2025-11-12"), "The date shouldn't be the value which was entered either, because of the culture/format difference"); - }); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Tasks/OpenUrlRespectingBaseTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Tasks/OpenUrlRespectingBaseTests.cs index e5d7cdb2..94a4ce84 100644 --- a/Tests/CSF.Screenplay.Selenium.Tests/Tasks/OpenUrlRespectingBaseTests.cs +++ b/Tests/CSF.Screenplay.Selenium.Tests/Tasks/OpenUrlRespectingBaseTests.cs @@ -36,13 +36,11 @@ public async Task TheActionCreatedByThisTaskShouldContainTheCorrectReport(IWebDr { await sut.PerformAsAsync(actor); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(performable, Is.InstanceOf(), "Performable is correct type"); Assert.That(((OpenUrl) performable!).GetReportFragment(actor, formatter).ToString(), Is.EqualTo("Anthony opens their browser at the test page: https://example.com/test.html"), "The report is correct"); - }); } finally { @@ -71,13 +69,11 @@ public async Task TheActionCreatedByThisTaskShouldNotUpdateAnAlreadyAbsoluteUrl( { await sut.PerformAsAsync(actor); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(performable, Is.InstanceOf(), "Performable is correct type"); Assert.That(((OpenUrl) performable!).GetReportFragment(actor, formatter).ToString(), Is.EqualTo("Anthony opens their browser at the test page: https://contoso.com/test.html"), "The report is correct"); - }); } finally { diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Tasks/TakeAndSaveAScreenshotTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Tasks/TakeAndSaveAScreenshotTests.cs index d73ab8b6..513a52b7 100644 --- a/Tests/CSF.Screenplay.Selenium.Tests/Tasks/TakeAndSaveAScreenshotTests.cs +++ b/Tests/CSF.Screenplay.Selenium.Tests/Tasks/TakeAndSaveAScreenshotTests.cs @@ -28,11 +28,9 @@ public async Task TakeAndSaveAScreenshotShouldSaveAFile(IStage stage) webster.RecordsAsset -= OnRecordsAsset; } - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(screenshotPath, Is.Not.Null); Assert.That(screenshotPath, Does.Exist); - }); } void OnRecordsAsset(object? sender, PerformableAssetEventArgs e) diff --git a/Tests/CSF.Screenplay.SpecFlow.Tests/ServiceCollectionAdapterTests.cs b/Tests/CSF.Screenplay.SpecFlow.Tests/ServiceCollectionAdapterTests.cs index abf78ef1..77b92a83 100644 --- a/Tests/CSF.Screenplay.SpecFlow.Tests/ServiceCollectionAdapterTests.cs +++ b/Tests/CSF.Screenplay.SpecFlow.Tests/ServiceCollectionAdapterTests.cs @@ -14,8 +14,7 @@ public void UnsupportedFunctionalityShouldThrowNotSupportedException() var container = new ObjectContainer(); var sut = new ServiceCollectionAdapter(container); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(() => sut[0], Throws.InstanceOf(), "Indexer get"); Assert.That(() => sut[0] = null, Throws.InstanceOf(), "Indexer set"); Assert.That(() => sut.Clear(), Throws.InstanceOf(), nameof(IServiceCollection.Clear)); @@ -27,7 +26,6 @@ public void UnsupportedFunctionalityShouldThrowNotSupportedException() Assert.That(() => sut.Insert(default, null), Throws.InstanceOf(), nameof(IServiceCollection.Insert)); Assert.That(() => sut.Remove(null), Throws.InstanceOf(), nameof(IServiceCollection.Remove)); Assert.That(() => sut.RemoveAt(default), Throws.InstanceOf(), nameof(IServiceCollection.RemoveAt)); - }); } [Test] diff --git a/Tests/CSF.Screenplay.Tests/ActorTests.cs b/Tests/CSF.Screenplay.Tests/ActorTests.cs index f931c26d..5f487c60 100644 --- a/Tests/CSF.Screenplay.Tests/ActorTests.cs +++ b/Tests/CSF.Screenplay.Tests/ActorTests.cs @@ -63,11 +63,9 @@ public void DisposeShouldDisposeDisposableAbilities(Mock abi sut.Dispose(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); ability1.Verify(x => x.Dispose(), Times.Once, "Ability 1 has been disposed"); ability2.Verify(x => x.Dispose(), Times.Once, "Ability 2 has been disposed"); - }); } [Test,AutoMoqData] @@ -150,11 +148,9 @@ public async Task PerformAsyncWithoutResultShouldRaiseTwoEvents(Actor sut, IPerf sut.BeginPerformable -= OnBeginPerformable; sut.EndPerformable -= OnEndPerformable; - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(beginPerformable, Is.True, "BeginPerformable was triggered"); Assert.That(endPerformable, Is.True, "EndPerformable was triggered"); - }); } [Test,AutoMoqData] @@ -166,12 +162,10 @@ public void PerformAsyncWithoutResultShouldRaisePerformableFailedEventIfPerforma Mock.Get(performable).Setup(x => x.PerformAsAsync(sut, It.IsAny())).Throws(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(async () => await ((ICanPerform)sut).PerformAsync(performable), Throws.InstanceOf(), "PerformAsync throws an exception"); sut.PerformableFailed -= OnPerformableFailed; Assert.That(exceptionCaught, Is.InstanceOf(), "The correct exception was caught"); - }); } [Test,AutoMoqData] @@ -211,12 +205,10 @@ public async Task PerformAsyncWithNongenericResultShouldRaiseThreeEvents(Actor s sut.PerformableResult -= OnPerformableResult; sut.EndPerformable -= OnEndPerformable; - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(beginPerformable, Is.True, "BeginPerformable was triggered"); Assert.That(result, Is.SameAs(expectedResult), "PerformableResult was triggered"); Assert.That(endPerformable, Is.True, "EndPerformable was triggered"); - }); } [Test,AutoMoqData] @@ -228,12 +220,10 @@ public void PerformAsyncWithNongenericResultShouldRaisePerformableFailedEventIfP Mock.Get(performable).Setup(x => x.PerformAsAsync(sut, It.IsAny())).Throws(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(async () => await ((ICanPerform)sut).PerformAsync(performable), Throws.InstanceOf(), "PerformAsync throws an exception"); sut.PerformableFailed -= OnPerformableFailed; Assert.That(exceptionCaught, Is.InstanceOf(), "The correct exception was caught"); - }); } [Test,AutoMoqData] @@ -273,12 +263,10 @@ public async Task PerformAsyncWithGenericResultShouldRaiseThreeEvents(Actor sut, sut.PerformableResult -= OnPerformableResult; sut.EndPerformable -= OnEndPerformable; - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(beginPerformable, Is.True, "BeginPerformable was triggered"); Assert.That(result, Is.SameAs(expectedResult), "PerformableResult was triggered"); Assert.That(endPerformable, Is.True, "EndPerformable was triggered"); - }); } [Test,AutoMoqData] @@ -290,12 +278,10 @@ public void PerformAsyncWithGenericResultShouldRaisePerformableFailedEventIfPerf Mock.Get(performable).Setup(x => x.PerformAsAsync(sut, It.IsAny())).Throws(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(async () => await ((ICanPerform)sut).PerformAsync(performable), Throws.InstanceOf(), "PerformAsync throws an exception"); sut.PerformableFailed -= OnPerformableFailed; Assert.That(exceptionCaught, Is.InstanceOf(), "The correct exception was caught"); - }); } [Test,AutoMoqData] diff --git a/Tests/CSF.Screenplay.Tests/Actors/CastTests.cs b/Tests/CSF.Screenplay.Tests/Actors/CastTests.cs index a3444412..c4ba846f 100644 --- a/Tests/CSF.Screenplay.Tests/Actors/CastTests.cs +++ b/Tests/CSF.Screenplay.Tests/Actors/CastTests.cs @@ -27,11 +27,9 @@ public void GetActorByNameShouldReturnTheSameActorIfUsedTwice(string name, var actor1 = sut.GetActor(name); var actor2 = sut.GetActor(name); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(actor1, Is.Not.Null, "Actors aren't null"); Assert.That(actor1, Is.SameAs(actor2), "Same actor for second usage of the method"); - }); } [Test, AutoMoqData] diff --git a/Tests/CSF.Screenplay.Tests/Integration/EventBusIntegrationTests.cs b/Tests/CSF.Screenplay.Tests/Integration/EventBusIntegrationTests.cs index 70b6062b..9f1fba3e 100644 --- a/Tests/CSF.Screenplay.Tests/Integration/EventBusIntegrationTests.cs +++ b/Tests/CSF.Screenplay.Tests/Integration/EventBusIntegrationTests.cs @@ -54,15 +54,13 @@ await sut.ExecuteAsPerformanceAsync(async (s, c) => eventPublisher.EndPerformable -= OnEndPerformable; eventPublisher.PerformableResult -= OnPerformableResult; - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(performanceBegun, Is.True, $"{nameof(OnPerformanceBegun)} was triggered"); Assert.That(performanceFinished, Is.True, $"{nameof(OnPerformanceFinished)} was triggered"); Assert.That(createdActor, Has.Property(nameof(IHasName.Name)).EqualTo("Joe"), $"{nameof(OnActorCreated)} was triggered"); Assert.That(performablesBegun, Is.EqualTo(new object[] { sampleAction, sampleQuestion }), $"{nameof(OnBeginPerformable)} was triggered with the right performables"); Assert.That(performablesEnded, Is.EqualTo(new object[] { sampleAction, sampleQuestion }), $"{nameof(OnEndPerformable)} was triggered with the right performables"); Assert.That(performableResults, Is.EqualTo(new object[] { "Joe" }), $"{nameof(OnPerformableResult)} was triggered with the right performables"); - }); } [Test,AutoMoqData] @@ -91,11 +89,9 @@ await sut.ExecuteAsPerformanceAsync((s, c) => eventPublisher.ActorSpotlit -= OnActorSpotlit; eventPublisher.SpotlightTurnedOff -= OnSpotlightTurnedOff; - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(spotlitActor, Has.Property(nameof(IHasName.Name)).EqualTo("Joe"), $"{nameof(OnActorSpotlit)} was triggered"); Assert.That(spotlightOff, Is.True, $"{nameof(OnSpotlightTurnedOff)} was triggered"); - }); } [Test,AutoMoqData] @@ -149,13 +145,11 @@ await sut.ExecuteAsPerformanceAsync(async (s, c) => eventPublisher.PerformableFailed -= OnPerformableFailed; eventPublisher.PerformanceFinished -= OnPerformanceFinished; - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(exceptionCaught, Is.InstanceOf().And.Property(nameof(Exception.Message)).EqualTo(ThrowingAction.Message), $"{nameof(OnPerformableFailed)} was triggered"); Assert.That(result, Is.False, $"{nameof(OnPerformanceFinished)} was triggered"); - }); } [Test,AutoMoqData] @@ -230,10 +224,8 @@ await sut.ExecuteAsPerformanceAsync((s, c) => eventPublisher.RecordAsset -= OnRecordsAsset; - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(filePath, Is.EqualTo(expectedPath), "File path"); Assert.That(fileSummary, Is.EqualTo(expectedSummary), "File summary"); - }); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Tests/Integration/PerformanceIntegrationTests.cs b/Tests/CSF.Screenplay.Tests/Integration/PerformanceIntegrationTests.cs index 2d134fb7..65a529f7 100644 --- a/Tests/CSF.Screenplay.Tests/Integration/PerformanceIntegrationTests.cs +++ b/Tests/CSF.Screenplay.Tests/Integration/PerformanceIntegrationTests.cs @@ -25,10 +25,9 @@ await screenplay.ExecuteAsPerformanceAsync(async (s, c) => { return true; }); - Assert.Multiple(() => { + using var scope = Assert.EnterMultipleScope(); Assert.That(sampleAction.ActorName, Is.EqualTo("Joe"), "Action result"); Assert.That(question1Result, Is.EqualTo(5), "Question 1 result"); Assert.That(question2Result, Is.EqualTo("Joe"), "Question 2 result"); - }); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/AssetEmbedderTests.cs b/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/AssetEmbedderTests.cs index 4e9d2e74..69de2b11 100644 --- a/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/AssetEmbedderTests.cs +++ b/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/AssetEmbedderTests.cs @@ -25,15 +25,13 @@ public async Task EmbedReportAssetsAsyncShouldNotEmbedAnAssetOfAnUnsupportedType await sut.EmbedReportAssetsAsync(report, new () { EmbeddedFileExtensions = "jpeg", EmbeddedFileSizeThresholdKb = 50 }); var performable = report.Performances.First().Reportables.OfType().First(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(performable.Assets, Has.One.Matches(a => a is { FileName: SampleAssetsCustomization.Asset2Filename, FileData: not null }), "JPEG asset has been embedded"); Assert.That(performable.Assets, Has.One.Matches(a => a is { FileName: SampleAssetsCustomization.Asset1Filename, FileData: null }), "PNG asset has not been embedded"); - }); } [Test, AutoMoqData] @@ -50,15 +48,13 @@ public async Task EmbedReportAssetsAsyncShouldNotEmbedAnAssetWhichIsTooLarge(Ass await sut.EmbedReportAssetsAsync(report, new () { EmbeddedFileExtensions = "png,jpeg", EmbeddedFileSizeThresholdKb = 10 }); var performable = report.Performances.First().Reportables.OfType().First(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(performable.Assets, Has.One.Matches(a => a is { FileName: SampleAssetsCustomization.Asset2Filename, FileData: null }), "JPEG asset has not been embedded"); Assert.That(performable.Assets, Has.One.Matches(a => a is { FileName: SampleAssetsCustomization.Asset1Filename, FileData: not null }), "PNG asset has been embedded"); - }); } [Test, AutoMoqData] @@ -67,14 +63,12 @@ public async Task EmbedReportAssetsAsyncShouldUseCorrectBase64(AssetEmbedder sut await sut.EmbedReportAssetsAsync(report, new () { EmbeddedFileExtensions = "png,jpeg", EmbeddedFileSizeThresholdKb = 50 }); var performable = report.Performances.First().Reportables.OfType().First(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); var asset1Data = performable.Assets.FirstOrDefault(x => x.FileName == SampleAssetsCustomization.Asset1Filename)?.FileData; Assert.That(asset1Data, Is.EqualTo(asset1Base64), "PNG asset encoded correctly"); var asset2Data = performable.Assets.FirstOrDefault(x => x.FileName == SampleAssetsCustomization.Asset2Filename)?.FileData; Assert.That(asset2Data, Is.EqualTo(asset2Base64), "JPEG asset encoded correctly"); - }); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/ReportConverterTests.cs b/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/ReportConverterTests.cs index a30ea220..721eeafd 100644 --- a/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/ReportConverterTests.cs +++ b/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/ReportConverterTests.cs @@ -34,12 +34,10 @@ public async Task ConvertAsyncShouldWriteTheReportUsingATemplate([NoAutoProperti await sut.ConvertAsync(options); - Assert.Multiple(async () => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(options.ReportPath, Does.Exist, "Report exists"); using var reader = new StreamReader(File.Open(options.OutputPath, FileMode.Open)); var reportContent = await reader.ReadToEndAsync(); Assert.That(reportContent, Is.EqualTo(""), "Report has correct content"); - }); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/TemplateReaderTests.cs b/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/TemplateReaderTests.cs index 866f217e..ceb0093e 100644 --- a/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/TemplateReaderTests.cs +++ b/Tests/CSF.Screenplay.Tests/JsonToHtmlReport/TemplateReaderTests.cs @@ -8,11 +8,9 @@ public async Task ReadTemplateShouldReturnAString(TemplateReader sut) { var result = await sut.ReadTemplate(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(result, Is.Not.Null, "Not null"); Assert.That(result, Is.Not.Empty, "Not empty"); - }); } } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Tests/Performables/TimeSpanBuilderTests.cs b/Tests/CSF.Screenplay.Tests/Performables/TimeSpanBuilderTests.cs deleted file mode 100644 index ed5ce104..00000000 --- a/Tests/CSF.Screenplay.Tests/Performables/TimeSpanBuilderTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -using static CSF.Screenplay.PerformanceStarter; -using static CSF.Screenplay.Performables.TimeSpanBuilderTests.EatLunchPerformableBuilder; -using CSF.Screenplay.Actors; - -namespace CSF.Screenplay.Performables; - -[TestFixture,Parallelizable] -public class TimeSpanBuilderTests -{ - [Test,AutoMoqData] - public async Task GetTimeSpanShouldReturnACorrectTimeSpanInTheContextOfALargerBuilderIntegrationTest(Actor actor, string foodName) - { - object? performable = null; - void OnBeginPerformable(object? sender, PerformableEventArgs ev) => performable = ev.Performable; - - actor.BeginPerformable += OnBeginPerformable; - await When(actor).AttemptsTo(Eat(foodName).For(10).Minutes()); - actor.BeginPerformable -= OnBeginPerformable; - - Assert.Multiple(() => - { - Assert.That(performable, Is.Not.Null, "Performable must not be null"); - Assert.That(performable, Is.InstanceOf(), $"Performable must not be an instance of {nameof(EatLunch)}"); - Assert.That(performable, Has.Property(nameof(EatLunch.FoodName)).EqualTo(foodName), $"The performable must have the correct {nameof(EatLunch.FoodName)}"); - Assert.That(performable, Has.Property(nameof(EatLunch.HowLong)).EqualTo(TimeSpan.FromMinutes(10)), $"The performable must have the correct {nameof(EatLunch.HowLong)}"); - }); - } - - [Test,AutoMoqData] - public void MillisecondsShouldCreateAnAmountInMilliseconds(object otherBuilder) - { - var sut = TimeSpanBuilder.Create(otherBuilder, 20); - sut.Milliseconds(); - Assert.That(((IProvidesTimeSpan)sut).GetTimeSpan(), Is.EqualTo(TimeSpan.FromMilliseconds(20))); - } - - [Test,AutoMoqData] - public void SecondsShouldCreateAnAmountInSeconds(object otherBuilder) - { - var sut = TimeSpanBuilder.Create(otherBuilder, 20); - sut.Seconds(); - Assert.That(((IProvidesTimeSpan)sut).GetTimeSpan(), Is.EqualTo(TimeSpan.FromSeconds(20))); - } - - [Test,AutoMoqData] - public void MinutesShouldCreateAnAmountInMinutes(object otherBuilder) - { - var sut = TimeSpanBuilder.Create(otherBuilder, 20); - sut.Minutes(); - Assert.That(((IProvidesTimeSpan)sut).GetTimeSpan(), Is.EqualTo(TimeSpan.FromMinutes(20))); - } - - [Test,AutoMoqData] - public void HoursShouldCreateAnAmountInHours(object otherBuilder) - { - var sut = TimeSpanBuilder.Create(otherBuilder, 20); - sut.Hours(); - Assert.That(((IProvidesTimeSpan)sut).GetTimeSpan(), Is.EqualTo(TimeSpan.FromHours(20))); - } - - [Test,AutoMoqData] - public void DaysShouldCreateAnAmountInDays(object otherBuilder) - { - var sut = TimeSpanBuilder.Create(otherBuilder, 20); - sut.Days(); - Assert.That(((IProvidesTimeSpan)sut).GetTimeSpan(), Is.EqualTo(TimeSpan.FromDays(20))); - } - - // Note that this class is identical to the example in the docco comments for TimeSpanBuilder - public class EatLunchPerformableBuilder : IGetsPerformable - { - IProvidesTimeSpan? timeSpanBuilder; - - protected string? FoodName { get; init; } - - IPerformable IGetsPerformable.GetPerformable() - => new EatLunch(FoodName, timeSpanBuilder?.GetTimeSpan() ?? TimeSpan.Zero); - - public TimeSpanBuilder For(int howMany) - { - var builder = TimeSpanBuilder.Create(this, howMany); - timeSpanBuilder = builder; - return builder; - } - - public static EatLunchPerformableBuilder Eat(string foodName) => new EatLunchPerformableBuilder() { FoodName = foodName }; - } - - public class EatLunch(string? foodName, TimeSpan howLong) : IPerformable - { - public string? FoodName { get; } = foodName; - public TimeSpan HowLong { get; } = howLong; - - // Intentional no-op, this is just a testing class. - public ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken = default) - => ValueTask.CompletedTask; - } -} \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Tests/ReportFragmentFormatterTests.cs b/Tests/CSF.Screenplay.Tests/ReportFragmentFormatterTests.cs index c68aebeb..ab10ac39 100644 --- a/Tests/CSF.Screenplay.Tests/ReportFragmentFormatterTests.cs +++ b/Tests/CSF.Screenplay.Tests/ReportFragmentFormatterTests.cs @@ -22,13 +22,11 @@ public void FormatShouldReturnCorrectReportFragment([Frozen] IGetsReportFormat f var result = sut.Format(template, values); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(result.OriginalTemplate, Is.EqualTo(template), "Original template is correct"); Assert.That(result.FormattedFragment, Is.EqualTo("Joe washes 5 dishes"), "Formatted fragment is correct"); Assert.That(result.PlaceholderValues, Has.Count.EqualTo(2), "Count of placeholder items"); Assert.That(result.PlaceholderValues, Has.One.Matches(x => x.Name == "Actor" && Equals(x.Value, "Joe")), "First placeholder item"); Assert.That(result.PlaceholderValues, Has.One.Matches(x => x.Name == "Count" && Equals(x.Value, 5)), "Second placeholder item"); - }); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Tests/Reporting/JsonScreenplayReporterTests.cs b/Tests/CSF.Screenplay.Tests/Reporting/JsonScreenplayReporterTests.cs index 6dfaa2a4..416ccf6e 100644 --- a/Tests/CSF.Screenplay.Tests/Reporting/JsonScreenplayReporterTests.cs +++ b/Tests/CSF.Screenplay.Tests/Reporting/JsonScreenplayReporterTests.cs @@ -13,8 +13,7 @@ public void SubscribeTo_ShouldSubscribeToEvents([Frozen] Mock - { + using var scope = Assert.EnterMultipleScope(); mockEvents.VerifyAdd(e => e.ScreenplayStarted += It.IsAny(), Times.Once); mockEvents.VerifyAdd(e => e.ScreenplayEnded += It.IsAny(), Times.Once); mockEvents.VerifyAdd(e => e.PerformanceBegun += It.IsAny>(), Times.Once); @@ -28,7 +27,6 @@ public void SubscribeTo_ShouldSubscribeToEvents([Frozen] Mock e.GainedAbility += It.IsAny>(), Times.Once); mockEvents.VerifyAdd(e => e.ActorSpotlit += It.IsAny>(), Times.Once); mockEvents.VerifyAdd(e => e.SpotlightTurnedOff += It.IsAny>(), Times.Once); - }); } [Test, AutoMoqData] @@ -39,8 +37,7 @@ public void UnsubscribeFrom_ShouldUnsubscribeFromEvents([Frozen] Mock - { + using var scope = Assert.EnterMultipleScope(); mockEvents.VerifyRemove(e => e.ScreenplayStarted -= It.IsAny(), Times.Once); mockEvents.VerifyRemove(e => e.ScreenplayEnded -= It.IsAny(), Times.Once); mockEvents.VerifyRemove(e => e.PerformanceBegun -= It.IsAny>(), Times.Once); @@ -54,7 +51,6 @@ public void UnsubscribeFrom_ShouldUnsubscribeFromEvents([Frozen] Mock e.GainedAbility -= It.IsAny>(), Times.Once); mockEvents.VerifyRemove(e => e.ActorSpotlit -= It.IsAny>(), Times.Once); mockEvents.VerifyRemove(e => e.SpotlightTurnedOff -= It.IsAny>(), Times.Once); - }); } [Test, AutoMoqData] diff --git a/Tests/CSF.Screenplay.Tests/Reporting/PerformanceReportBuilderTests.cs b/Tests/CSF.Screenplay.Tests/Reporting/PerformanceReportBuilderTests.cs index fba9d7d7..2a2ea5fe 100644 --- a/Tests/CSF.Screenplay.Tests/Reporting/PerformanceReportBuilderTests.cs +++ b/Tests/CSF.Screenplay.Tests/Reporting/PerformanceReportBuilderTests.cs @@ -123,8 +123,7 @@ public void BeginAndEndPerformableShouldBeAbleToCreateAHierarchyOfPerformableRep sut.EndPerformable(performable1, actor); var report = sut.GetReport(outcome); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(report.Reportables, Has.Count.EqualTo(1), "Only one reportable should be present at root level"); var taskReport = report.Reportables.OfType().Single(); @@ -132,7 +131,6 @@ public void BeginAndEndPerformableShouldBeAbleToCreateAHierarchyOfPerformableRep Assert.That(taskReport.Reportables.OfType().Select(x => x.PerformableType), Is.EqualTo(new[] { "CSF.Screenplay.Performables.StartTheStopwatch", "CSF.Screenplay.Performables.StopTheStopwatch" }), "The child reports should be of the correct performable types"); - }); } diff --git a/Tests/CSF.Screenplay.Tests/Reporting/ReportFormatCreatorTests.cs b/Tests/CSF.Screenplay.Tests/Reporting/ReportFormatCreatorTests.cs index 24dbbd3d..a98c6095 100644 --- a/Tests/CSF.Screenplay.Tests/Reporting/ReportFormatCreatorTests.cs +++ b/Tests/CSF.Screenplay.Tests/Reporting/ReportFormatCreatorTests.cs @@ -21,11 +21,9 @@ public void GetReportFormatShouldReturnTheCorrectFormattedTemplate(string origin var sut = new ReportFormatCreator(); var actual = sut.GetReportFormat(original, Array.Empty()); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(actual?.OriginalTemplate, Is.EqualTo(original), "Original value is unchanged"); Assert.That(actual?.FormatTemplate, Is.EqualTo(expected), "Expected value is correct"); - }); } [TestCase("", "", "", "")] @@ -53,13 +51,11 @@ public void GetReportFormatShouldReturnTheCorrectObjectsWithTheCorrectNames(Repo { var template = "{foo} content {bar} content {baz}"; var actual = sut.GetReportFormat(template, [value1, value2, value3]); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(actual.Values, Has.Count.EqualTo(3), "Count"); Assert.That(actual.Values, Has.One.Matches(x => x.Name == "foo" && x.Value == value1), "Value 1"); Assert.That(actual.Values, Has.One.Matches(x => x.Name == "bar" && x.Value == value2), "Value 2"); Assert.That(actual.Values, Has.One.Matches(x => x.Name == "baz" && x.Value == value3), "Value 3"); - }); } [Test,AutoMoqData] @@ -68,12 +64,10 @@ public void GetReportFormatShouldReturnTheCorrectObjectsWithTheCorrectNamesIfMor var template = "{foo} content {bar} content"; var actual = sut.GetReportFormat(template, [value1, value2, value3]); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(actual.Values, Has.Count.EqualTo(2), "Count"); Assert.That(actual.Values, Has.One.Matches(x => x.Name == "foo" && x.Value == value1), "Value 1"); Assert.That(actual.Values, Has.One.Matches(x => x.Name == "bar" && x.Value == value2), "Value 2"); - }); } [Test,AutoMoqData] @@ -82,12 +76,10 @@ public void GetReportFormatShouldReturnTheCorrectObjectsWithTheCorrectNamesIfMor var template = "{foo} content {bar} content {baz}"; var actual = sut.GetReportFormat(template, [value1, value2]); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(actual.Values, Has.Count.EqualTo(3), "Count"); Assert.That(actual.Values, Has.One.Matches(x => x.Name == "foo" && x.Value == value1), "Value 1"); Assert.That(actual.Values, Has.One.Matches(x => x.Name == "bar" && x.Value == value2), "Value 2"); Assert.That(actual.Values, Has.One.Matches(x => x.Name == "baz" && x.Value == null), "Value 3"); - }); } } \ No newline at end of file diff --git a/Tests/CSF.Screenplay.Tests/Reporting/ScreenplayReportSerializerTests.cs b/Tests/CSF.Screenplay.Tests/Reporting/ScreenplayReportSerializerTests.cs index 8eaeccf4..a4692291 100644 --- a/Tests/CSF.Screenplay.Tests/Reporting/ScreenplayReportSerializerTests.cs +++ b/Tests/CSF.Screenplay.Tests/Reporting/ScreenplayReportSerializerTests.cs @@ -58,8 +58,7 @@ public async Task DeserializeAsyncShouldReturnAScreenplayReport(ScreenplayReport using var stream = new MemoryStream(Encoding.UTF8.GetBytes(reportJson)); var result = await sut.DeserializeAsync(stream); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(result, Is.Not.Null, "The deserialized report should not be null."); Assert.That(result.Metadata.Timestamp, Is.EqualTo(new DateTime(2021, 1, 1, 0, 0, 0, DateTimeKind.Utc)), "The timestamp should be correct."); Assert.That(result.Metadata.ReportFormatVersion, Is.EqualTo("2.0.0"), "The report format version should be correct."); @@ -84,7 +83,6 @@ public async Task DeserializeAsyncShouldReturnAScreenplayReport(ScreenplayReport Assert.That(performableReport.Assets.Single().FilePath, Is.EqualTo("../a/file/path.txt"), "The asset file path should be correct."); Assert.That(performableReport.Assets.Single().FileSummary, Is.EqualTo("This is a test asset"), "The asset file summary should be correct."); Assert.That(performableReport.Reportables, Has.Count.EqualTo(1), "There should be one nested reportable."); - }); } [Test, AutoMoqData] diff --git a/Tests/CSF.Screenplay.Tests/WebApis/MakeWebApiRequestsTests.cs b/Tests/CSF.Screenplay.Tests/WebApis/MakeWebApiRequestsTests.cs index 3a2b9ffc..f8560966 100644 --- a/Tests/CSF.Screenplay.Tests/WebApis/MakeWebApiRequestsTests.cs +++ b/Tests/CSF.Screenplay.Tests/WebApis/MakeWebApiRequestsTests.cs @@ -19,11 +19,9 @@ public void DisposeShouldDisposeEachClient([SendsMockHttpRequests] Actor actor) actor.Dispose(); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(IsDisposed(client1), Is.True, "Client 1 disposed"); Assert.That(IsDisposed(client2), Is.True, "Client 2 disposed"); - }); } [Test,AutoMoqData] diff --git a/Tests/CSF.Screenplay.Tests/WebApis/WebApiBuilderTests.cs b/Tests/CSF.Screenplay.Tests/WebApis/WebApiBuilderTests.cs index 46e84afa..e4303c62 100644 --- a/Tests/CSF.Screenplay.Tests/WebApis/WebApiBuilderTests.cs +++ b/Tests/CSF.Screenplay.Tests/WebApis/WebApiBuilderTests.cs @@ -90,11 +90,9 @@ public async Task GetTheJsonResultShouldGetAnActionWhichYieldsTheExpectedPayload #pragma warning restore CS8602 // Dereference of a possibly null reference. var result = await When(actor).AttemptsTo(GetTheJsonResult(endpoint)); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(result, Is.Not.SameAs(expectedPayload), "Result and expected payload are not the same object (it has been through a serialization roundtrip)"); Assert.That(result, Is.EqualTo(expectedPayload), "Result is equal to expected payload"); - }); } [Test,AutoMoqData] @@ -111,11 +109,9 @@ public async Task GetTheJsonResultWithParametersShouldGetAnActionWhichYieldsTheE #pragma warning restore CS8602 // Dereference of a possibly null reference. var result = await When(actor).AttemptsTo(GetTheJsonResult(endpoint, parameters)); - Assert.Multiple(() => - { + using var scope = Assert.EnterMultipleScope(); Assert.That(result, Is.Not.SameAs(expectedPayload), "Result and expected payload are not the same object (it has been through a serialization roundtrip)"); Assert.That(result, Is.EqualTo(expectedPayload), "Result is equal to expected payload"); - }); } }