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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CSF.Screenplay.Abstractions/IHasCustomTypeName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace CSF.Screenplay
{
/// <summary>
/// An object which can provide a custom human-readable .NET type name.
/// </summary>
/// <remarks>
/// <para>
/// This is particularly important when reporting, particularly when writing the name of the performable type
/// into a report. Some performables may be written using the adapter or decorator patterns, in which a general-use
/// class wraps a specific class which implements a subset of a performable. Using <see cref="System.Type.GetType()"/>
/// will yield the type name of the general-use 'outer' class, which is usually not very useful on its own.
/// </para>
/// <para>
/// If general-use performables, such as adapters, implement this interface, then they can return more useful human-readable
/// type names to the consuming logic, making use of their inner/wrapped implementation type.
/// </para>
/// </remarks>
public interface IHasCustomTypeName
{
/// <summary>
/// Gets a human-readable name of the type of the current instance.
/// </summary>
/// <remarks>
/// <para>See the remarks on <see cref="IHasCustomTypeName"/>; this does not need to be the same as <see cref="System.Type.GetType()"/>.</para>
/// </remarks>
/// <returns>A human-readable name of the .NET type of the current instance, which could (for example) be
/// qualified with additional context, such as a wrapped implementation.</returns>
string GetHumanReadableTypeName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace CSF.Screenplay.Selenium.Actions
/// to fetch it using the WebDriver more than once. As such, instances of this adapter (like all performables) should not be re-used.
/// </para>
/// </remarks>
public class SingleElementPerformableAdapter : IPerformable, ICanReport
public class SingleElementPerformableAdapter : IPerformable, ICanReport, IHasCustomTypeName
{
readonly ISingleElementPerformable performable;
readonly ITarget target;
Expand All @@ -38,6 +38,10 @@ public ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellatio
return performable.PerformAsAsync(actor, actor.GetAbility<BrowseTheWeb>().WebDriver, lazyElement, cancellationToken);
}

/// <inheritdoc/>
public string GetHumanReadableTypeName()
=> $"{performable.GetType().FullName}, via {nameof(SingleElementPerformableAdapter)}";

/// <summary>
/// Initializes a new instance of the <see cref="SingleElementPerformableAdapter"/> class with the specified performable and target.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace CSF.Screenplay.Selenium.Questions
/// to fetch it using the WebDriver more than once. As such, instances of this adapter (like all performables) should not be re-used.
/// </para>
/// </remarks>
public class ElementCollectionPerformableWithResultAdapter<TResult> : IPerformableWithResult<IReadOnlyList<TResult>>, ICanReport
public class ElementCollectionPerformableWithResultAdapter<TResult> : IPerformableWithResult<IReadOnlyList<TResult>>, ICanReport, IHasCustomTypeName
{
readonly IElementCollectionPerformableWithResult<TResult> performable;
readonly ITarget target;
Expand All @@ -39,6 +39,10 @@ public ValueTask<IReadOnlyList<TResult>> PerformAsAsync(ICanPerform actor, Cance
return performable.PerformAsAsync(actor, actor.GetAbility<BrowseTheWeb>().WebDriver, lazyElements, cancellationToken);
}

/// <inheritdoc/>
public string GetHumanReadableTypeName()
=> $"{performable.GetHumanReadableTypeName()}, via {nameof(ElementCollectionPerformableWithResultAdapter)}";

/// <summary>
/// Initializes a new instance of the <see cref="ElementCollectionPerformableWithResultAdapter{TResult}"/> class with the specified performable and target.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions CSF.Screenplay.Selenium/Questions/ElementCollectionQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
/// </remarks>
/// <example>
/// <para>
/// This example gets the text of every list item which has the class <c>todo</c>.

Check warning on line 29 in CSF.Screenplay.Selenium/Questions/ElementCollectionQuery.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Complete the task associated to this 'TODO' comment.
/// </para>
/// <code>
/// using CSF.Screenplay.Selenium.Elements;
/// using static CSF.Screenplay.Selenium.PerformableBuilder;
///
/// readonly ITarget todoItems = new CssSelector("li.todo", "the todo items");

Check warning on line 35 in CSF.Screenplay.Selenium/Questions/ElementCollectionQuery.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Complete the task associated to this 'TODO' comment.

Check warning on line 35 in CSF.Screenplay.Selenium/Questions/ElementCollectionQuery.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Complete the task associated to this 'TODO' comment.
///
/// // Within the logic of a custom task, deriving from IPerformableWithResult&lt;IReadOnlyList&lt;string&gt;&gt;
/// public async ValueTask&lt;IReadOnlyList&lt;string&gt;&gt; PerformAsAsync(ICanPerform actor, CancellationToken cancellationToken = default)
Expand All @@ -57,6 +57,10 @@
public ValueTask<IReadOnlyList<TResult>> PerformAsAsync(ICanPerform actor, IWebDriver webDriver, Lazy<SeleniumElementCollection> elements, CancellationToken cancellationToken = default)
=> new ValueTask<IReadOnlyList<TResult>>(elements.Value.Select(query.GetValue).ToList());

/// <inheritdoc/>
public string GetHumanReadableTypeName()
=> $"{query.GetType().FullName}, via {nameof(ElementCollectionQuery)}";

/// <summary>
/// Initializes a new instance of the <see cref="ElementCollectionQuery{TResult}"/> class with the specified query.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions CSF.Screenplay.Selenium/Questions/FindElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
/// </remarks>
/// <example>
/// <para>
/// This example gets a <see cref="SeleniumElement"/> within the list which has the ID <c>todo</c> which has the class

Check warning on line 34 in CSF.Screenplay.Selenium/Questions/FindElement.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Complete the task associated to this 'TODO' comment.
/// <c>urgent</c>.
/// </para>
/// <code>
/// using CSF.Screenplay.Selenium.Elements;
/// using static CSF.Screenplay.Selenium.PerformableBuilder;
///
/// readonly ITarget todoList = new CssSelector("ul#todo", "the to-do list");

Check warning on line 41 in CSF.Screenplay.Selenium/Questions/FindElement.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Complete the task associated to this 'TODO' comment.
/// readonly Locator urgent = new ClassName("urgent", "the urgent item");
///
/// // Within the logic of a custom task, deriving from IPerformableWithResult&lt;SeleniumElement&gt;
Expand Down Expand Up @@ -85,6 +85,9 @@
return new Lazy<IHasSearchContext>(() => searchContext);
}

/// <inheritdoc/>
public string GetHumanReadableTypeName() => GetType().FullName;

/// <summary>
/// Initializes a new instance of the <see cref="FindElement"/> class.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions CSF.Screenplay.Selenium/Questions/FindElements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
/// <example>
/// <para>
/// This example gets a <see cref="SeleniumElementCollection"/> which contains every element in the list which has
/// the ID <c>todo</c>, which also the class <c>low_priority</c>.

Check warning on line 34 in CSF.Screenplay.Selenium/Questions/FindElements.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Complete the task associated to this 'TODO' comment.
/// </para>
/// <code>
/// using CSF.Screenplay.Selenium.Elements;
/// using static CSF.Screenplay.Selenium.PerformableBuilder;
///
/// readonly ITarget todoList = new CssSelector("ul#todo", "the to-do list");

Check warning on line 40 in CSF.Screenplay.Selenium/Questions/FindElements.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Complete the task associated to this 'TODO' comment.
/// readonly Locator lowPriority = new ClassName("low_priority", "the low priority items");
///
/// // Within the logic of a custom task, deriving from IPerformableWithResult&lt;SeleniumElementCollection&gt;
Expand Down Expand Up @@ -84,6 +84,9 @@
return new Lazy<IHasSearchContext>(() => searchContext);
}

/// <inheritdoc/>
public string GetHumanReadableTypeName() => GetType().FullName;

/// <summary>
/// Initializes a new instance of the <see cref="FindElements"/> class.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions CSF.Screenplay.Selenium/Questions/GetShadowRootNatively.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public class GetShadowRootNatively : ISingleElementPerformableWithResult<Element
public ReportFragment GetReportFragment(Actor actor, Lazy<SeleniumElement> element, IFormatsReportFragment formatter)
=> formatter.Format("{Actor} gets the Shadow Root node from {Element} using the native Selenium technique", actor, element.Value);

/// <inheritdoc/>
public string GetHumanReadableTypeName() => GetType().FullName;

/// <inheritdoc/>
public ValueTask<Elements.ShadowRoot> PerformAsAsync(ICanPerform actor, IWebDriver webDriver, Lazy<SeleniumElement> element, CancellationToken cancellationToken = default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public class GetShadowRootWithJavaScript : ISingleElementPerformableWithResult<E
public ReportFragment GetReportFragment(Actor actor, Lazy<SeleniumElement> element, IFormatsReportFragment formatter)
=> formatter.Format("{Actor} gets the Shadow Root node from {Element} using JavaScript", actor, element.Value);

/// <inheritdoc/>
public string GetHumanReadableTypeName() => GetType().FullName;

/// <inheritdoc/>
public async ValueTask<Elements.ShadowRoot> PerformAsAsync(ICanPerform actor, IWebDriver webDriver, Lazy<SeleniumElement> element, CancellationToken cancellationToken = default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace CSF.Screenplay.Selenium.Questions
/// <see cref="SingleElementPerformableWithResultAdapter{TResult}"/>.
/// </para>
/// </remarks>
public interface IElementCollectionPerformableWithResult<TResult> : ICanReportForElements
public interface IElementCollectionPerformableWithResult<TResult> : ICanReportForElements, IHasCustomTypeName
{
/// <summary>
/// Counterpart to <see cref="IPerformableWithResult{TResult}.PerformAsAsync(ICanPerform, CancellationToken)"/> except that this method also offers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace CSF.Screenplay.Selenium.Questions
/// <see cref="SingleElementPerformableWithResultAdapter{TResult}"/>.
/// </para>
/// </remarks>
public interface ISingleElementPerformableWithResult<TResult> : ICanReportForElement
public interface ISingleElementPerformableWithResult<TResult> : ICanReportForElement, IHasCustomTypeName
{
/// <summary>
/// Counterpart to <see cref="IPerformableWithResult{TResult}.PerformAsAsync(ICanPerform, CancellationToken)"/> except that this method also offers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace CSF.Screenplay.Selenium.Questions
/// to fetch it using the WebDriver more than once. As such, instances of this adapter (like all performables) should not be re-used.
/// </para>
/// </remarks>
public class SingleElementPerformableWithResultAdapter<TResult> : IPerformableWithResult<TResult>, ICanReport
public class SingleElementPerformableWithResultAdapter<TResult> : IPerformableWithResult<TResult>, ICanReport, IHasCustomTypeName
{
readonly ISingleElementPerformableWithResult<TResult> performable;
readonly ITarget target;
Expand Down Expand Up @@ -46,6 +46,10 @@ public ValueTask<TResult> PerformAsAsync(ICanPerform actor, CancellationToken ca
}
}

/// <inheritdoc/>
public string GetHumanReadableTypeName()
=> $"{performable.GetHumanReadableTypeName()}, via {nameof(SingleElementPerformableWithResultAdapter)}";

/// <summary>
/// Initializes a new instance of the <see cref="SingleElementPerformableWithResultAdapter{TResult}"/> class with the specified performable and target.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions CSF.Screenplay.Selenium/Questions/SingleElementQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ public ReportFragment GetReportFragment(Actor actor, Lazy<SeleniumElement> eleme
public ValueTask<TResult> PerformAsAsync(ICanPerform actor, IWebDriver webDriver, Lazy<SeleniumElement> element, CancellationToken cancellationToken = default)
=> new ValueTask<TResult>(query.GetValue(element.Value));

/// <inheritdoc/>
public string GetHumanReadableTypeName()
=> $"{query.GetType().FullName}, via {nameof(SingleElementQuery)}";

/// <summary>
/// Initializes a new instance of the <see cref="SingleElementQuery{TResult}"/> class with the specified query.
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion CSF.Screenplay/Reporting/PerformanceReportBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ public void BeginPerformable(object performable, Actor actor, string performance
{
var performableReport = new PerformableReport
{
PerformableType = performable.GetType().FullName,
PerformableType = performable is IHasCustomTypeName customName
? customName.GetHumanReadableTypeName()
: performable.GetType().FullName,
ActorName = actor.Name,
PerformancePhase = performancePhase,
Started = reportTimer.GetCurrentTime(),
Expand Down
Loading