Skip to content

Commit 34ae3ac

Browse files
committed
Resolve #265 - Take a screenshot
This includes reworking some of the options to support a collection of event handler callbacks.
1 parent 32bbba7 commit 34ae3ac

5 files changed

Lines changed: 87 additions & 7 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using CSF.Screenplay.Actors;
2+
3+
namespace CSF.Screenplay.Selenium.Reporting
4+
{
5+
/// <summary>
6+
/// A service which subscribes to the <see cref="IHasPerformableEvents.PerformableFailed"/> event and takes a screenshot
7+
/// when a performable fails.
8+
/// </summary>
9+
/// <remarks>
10+
/// <para>
11+
/// This is useful when diagnosing and debugging failed performances.
12+
/// </para>
13+
/// </remarks>
14+
public interface ITakesScreenshotWhenPerformableFails
15+
{
16+
/// <summary>
17+
/// Handles the <see cref="IHasPerformableEvents.PerformableFailed"/> event.
18+
/// </summary>
19+
/// <param name="sender">The event sender</param>
20+
/// <param name="ev">The event args</param>
21+
void OnPerformableFailed(object sender, PerformableFailureEventArgs ev);
22+
}
23+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using CSF.Screenplay.Actors;
2+
using CSF.Screenplay.Performables;
3+
4+
namespace CSF.Screenplay.Selenium.Reporting
5+
{
6+
/// <summary>
7+
/// Implementation of <see cref="ITakesScreenshotWhenPerformableFails"/> which takes a screenshot in PNG format, with the name
8+
/// <c>Auto-screenshot on performable failure</c>.
9+
/// </summary>
10+
/// <remarks>
11+
/// <para>
12+
/// A screenshot is only taken if the <see cref="PerformableFailureEventArgs.Exception"/> is not an instance of
13+
/// <see cref="PerformableException"/> and if the <see cref="ActorEventArgs.Actor"/> has the ability <see cref="BrowseTheWeb"/>.
14+
/// If the exception is a performable exception then this indicates that an 'inner' performable is the true source of the exception
15+
/// and that this failure simply represents an exception bubbling upward through the performable stack.
16+
/// Additionally, if the actor cannot browse the web, then they of course cannot possibly take a screenshot.
17+
/// </para>
18+
/// <para>
19+
/// This handler internally uses the task <see cref="Tasks.TakeAndSaveScreenshot"/> and configures it to fail silently if taking
20+
/// a screenshot is not supported by the current browser. Thus, this is not certain to always result in a screenshot.
21+
/// When a screenshot is taken in this way, it is saved alongside the report as an asset file.
22+
/// </para>
23+
/// </remarks>
24+
public class PerformableFailureScreenshotTaker : ITakesScreenshotWhenPerformableFails
25+
{
26+
/// <inheritdoc/>
27+
public void OnPerformableFailed(object sender, PerformableFailureEventArgs ev)
28+
{
29+
if (ev.Exception is PerformableException) return;
30+
if (!((ICanPerform)ev.Actor).HasAbility<BrowseTheWeb>()) return;
31+
32+
var task = ev.Actor.PerformAsync(PerformableBuilder.TakeAndSaveAScreenshotIfSupported()
33+
.WithTheFormat(OpenQA.Selenium.ScreenshotImageFormat.Png)
34+
.WithTheName("Auto-screenshot on performable failure"));
35+
task.AsTask().Wait();
36+
}
37+
}
38+
}

CSF.Screenplay.Selenium/ServiceCollectionExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,22 @@ public static IServiceCollection AddSelenium(this IServiceCollection services)
1818
services.AddWebDriverFactory();
1919
services.AddWebDriverQuirks(BrowserQuirks.GetQuirksData());
2020

21+
services.AddSingleton<Reporting.ITakesScreenshotWhenPerformableFails, Reporting.PerformableFailureScreenshotTaker>();
22+
2123
services.AddTransient<Reporting.OptionsFormatter>();
2224
services.AddTransient<Reporting.ScreenshotFormatter>();
2325
services.Configure<ScreenplayOptions>(o =>
2426
{
2527
o.ValueFormatters.Add(typeof(Reporting.OptionsFormatter));
2628
o.ValueFormatters.Add(typeof(Reporting.ScreenshotFormatter));
29+
30+
o.PerformanceEventHandlers.Add((eventBus, s) =>
31+
{
32+
var screenshotTaker = s.GetRequiredService<Reporting.ITakesScreenshotWhenPerformableFails>();
33+
34+
eventBus.PerformableFailed -= screenshotTaker.OnPerformableFailed;
35+
eventBus.PerformableFailed += screenshotTaker.OnPerformableFailed;
36+
});
2737
});
2838

2939
return services;

CSF.Screenplay/ScreenplayOptions.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public sealed class ScreenplayOptions
3838
/// </para>
3939
/// <para>
4040
/// Make use of <see cref="ICollection{T}.Add(T)"/> to add new formatters to the end of this collection.
41-
/// It comes pre-loaded with three generalised formatters by default, in the following order.
41+
/// It comes pre-loaded with some general-use formatters by default, in the following order.
4242
/// </para>
4343
/// <list type="number">
4444
/// <item><description><see cref="ToStringFormatter"/> - a default/fallback implementation which may format any value at all</description></item>
@@ -94,7 +94,7 @@ public sealed class ScreenplayOptions
9494
public string ReportPath { get; set; } = $"ScreenplayReport_{DateTime.UtcNow.ToString("yyyy-MM-ddTHHmmssZ", CultureInfo.InvariantCulture)}";
9595

9696
/// <summary>
97-
/// An optional callback/action which exposes the various <see cref="IHasPerformanceEvents"/> which may be subscribed-to in order to be notified
97+
/// A collection of optional callbacks/actions which expose the various <see cref="IHasPerformanceEvents"/> which may be subscribed-to in order to be notified
9898
/// of the progress of a screenplay.
9999
/// </summary>
100100
/// <remarks>
@@ -104,12 +104,18 @@ public sealed class ScreenplayOptions
104104
/// If you wish, you may subscribe to these events from your own logic in order to develop new functionality or extend Screenplay.
105105
/// </para>
106106
/// <para>
107-
/// There is no need to add an explicit subscription to any events for the reporting infrastructure.
108-
/// Screenplay will automatically subscribe to this object from the reporting mechanism, unless the value of <see cref="ReportPath"/> means that
109-
/// reporting is disabled.
107+
/// There is no need to use this mechanism in order to use the Screenplay Reporting infrastructure.
108+
/// Built-in logic will automatically subscribe to the event published from the reporting infrastructure.
109+
/// Note that if <see cref="ReportPath"/> is a null or whitepsace-only string, the reporting infrastructure will be disabled.
110+
/// </para>
111+
/// <para>
112+
/// This collection of configuration callbacks may be used to subscribe to the event publisher from custom Screenplay extensions, in order
113+
/// be notified at particular points in the Screenplay's lifecycle.
114+
/// Each extension should add a single item to this list of callbacks. In this manner, multiple extensions may coexist without worrying about
115+
/// overwriting one another's event subscriptions.
110116
/// </para>
111117
/// </remarks>
112-
public Action<IHasPerformanceEvents> PerformanceEventsConfig { get; set; }
118+
public List<Action<IHasPerformanceEvents, IServiceProvider>> PerformanceEventHandlers { get; } = new List<Action<IHasPerformanceEvents, IServiceProvider>>();
113119

114120
/// <summary>
115121
/// Gets an ordered collection of actions which should be executed when the <see cref="Screenplay"/> begins, before the first

CSF.Screenplay/ScreenplayServiceCollectionExtensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ public static IServiceCollection AddScreenplay(this IServiceCollection services)
3636

3737
services
3838
.AddOptions<ScreenplayOptions>()
39-
.Configure((ScreenplayOptions o, PerformanceEventBus eventBus) => o.PerformanceEventsConfig?.Invoke(eventBus));
39+
.PostConfigure((ScreenplayOptions o, PerformanceEventBus eventBus, IServiceProvider s) => {
40+
foreach(var callback in o.PerformanceEventHandlers)
41+
callback.Invoke(eventBus, s);
42+
});
4043

4144
services
4245
.AddSingleton<Screenplay>()

0 commit comments

Comments
 (0)